Builder SoC (builder/context.go):
- Moved generateDockerfile, runtimeBaseImage, zipToTarGz from handler/upload.go
to internal/builder/context.go.
Reason: knowledge about runtime images and build context structure is a
build concern, not an HTTP handler concern.
- Added PrepareContext(zipData []byte, runtime string) (*bytes.Buffer, error) —
single public entry point. Handler calls one function, gets ready buffer.
- zipToTarGz now accepts *zip.Reader instead of []byte to avoid double parsing.
- upload.go reduced from ~200 LOC to ~60 LOC (build logic gone).
auth.go — JWKS insertion point:
- Added verifySignature() stub with detailed comment explaining what v2
implementation needs (JWKS endpoint, kid lookup, RS256/ES256 verify).
- Shows exactly where to add the call in validateJWT.
Unit tests (9 total, all pass):
- controllers: TestBuildDeployment_EnvVarsSorted, TestBuildDeployment_EmptyEnv
- handler: TestHopByHopHeaders_* (3 tests)
- builder: TestPrepareContext_PythonWithRequirements, _NodeNoPackageJSON,
_UnsupportedRuntime, _DockerfileIsFirst
95 lines
3.3 KiB
Go
95 lines
3.3 KiB
Go
// Создано: 2026-03-11
|
|
// Юнит-тесты для invoke.go — фильтрация hop-by-hop заголовков.
|
|
// Не требуют k8s, работают с httptest.
|
|
|
|
package handler
|
|
|
|
import (
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// TestHopByHopHeaders_FilteredFromResponse проверяет что заголовки управления
|
|
// TCP-соединением НЕ пробрасываются клиенту из ответа функции.
|
|
// Transfer-Encoding особенно опасен: его пересылка ломает framing тела ответа.
|
|
func TestHopByHopHeaders_FilteredFromResponse(t *testing.T) {
|
|
// Мок-бэкенд — возвращает hop-by-hop и обычный заголовок
|
|
backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Header().Set("X-Custom", "keep-me")
|
|
w.Header().Set("Transfer-Encoding", "chunked") // должен быть отфильтрован
|
|
w.Header().Set("Connection", "close") // должен быть отфильтрован
|
|
w.WriteHeader(http.StatusOK)
|
|
_, _ = io.WriteString(w, `{"ok":true}`)
|
|
}))
|
|
defer backend.Close()
|
|
|
|
// Делаем запрос напрямую к бэкенду и применяем нашу логику фильтрации
|
|
resp, err := http.Get(backend.URL)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
rec := httptest.NewRecorder()
|
|
|
|
// Воспроизводим логику из InvokeFunction
|
|
for k, vals := range resp.Header {
|
|
if hopByHopHeaders[k] {
|
|
continue
|
|
}
|
|
for _, v := range vals {
|
|
rec.Header().Add(k, v)
|
|
}
|
|
}
|
|
|
|
// Обычные заголовки — должны пройти
|
|
if rec.Header().Get("Content-Type") == "" {
|
|
t.Error("Content-Type should pass through")
|
|
}
|
|
if rec.Header().Get("X-Custom") == "" {
|
|
t.Error("X-Custom should pass through")
|
|
}
|
|
|
|
// Hop-by-hop — должны быть отфильтрованы
|
|
if rec.Header().Get("Transfer-Encoding") != "" {
|
|
t.Error("Transfer-Encoding must NOT pass through")
|
|
}
|
|
if rec.Header().Get("Connection") != "" {
|
|
t.Error("Connection must NOT pass through")
|
|
}
|
|
}
|
|
|
|
// TestHopByHopHeaders_MapContainsAllRFC2616 проверяет что карта содержит
|
|
// все 8 hop-by-hop заголовков из RFC 2616 §13.5.1.
|
|
func TestHopByHopHeaders_MapContainsAllRFC2616(t *testing.T) {
|
|
required := []string{
|
|
"Connection", "Keep-Alive", "Proxy-Authenticate", "Proxy-Authorization",
|
|
"Te", "Trailers", "Transfer-Encoding", "Upgrade",
|
|
}
|
|
for _, h := range required {
|
|
if !hopByHopHeaders[h] {
|
|
t.Errorf("hopByHopHeaders missing: %s", h)
|
|
}
|
|
}
|
|
|
|
// Content-Type — обычный заголовок, не должен быть в списке
|
|
if hopByHopHeaders["Content-Type"] {
|
|
t.Error("Content-Type must NOT be in hopByHopHeaders")
|
|
}
|
|
}
|
|
|
|
// TestHopByHopHeaders_CaseCheck проверяет что ключи в карте — с заглавной буквы
|
|
// (canonical form которую Go http.Header использует внутри).
|
|
func TestHopByHopHeaders_CaseCheck(t *testing.T) {
|
|
for k := range hopByHopHeaders {
|
|
canonical := http.CanonicalHeaderKey(strings.ToLower(k))
|
|
if k != canonical {
|
|
t.Errorf("key %q should be in canonical form %q", k, canonical)
|
|
}
|
|
}
|
|
}
|