Files
sless/internal/api/handler/invoke_test.go
T
“Naeel” 18f25e7a65 refactor: Builder SoC + JWKS stub + unit tests
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
2026-03-11 09:34:34 +04:00

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)
}
}
}