// Создано: 2026-03-11 // Тесты для builder/context.go — PrepareContext и вспомогательных функций. // Работают без k8s, только со стандартной библиотекой. package builder import ( "archive/tar" "archive/zip" "bytes" "compress/gzip" "io" "strings" "testing" ) // makeTestZip создаёт zip-архив с указанными файлами. func makeTestZip(t *testing.T, files map[string]string) []byte { t.Helper() var buf bytes.Buffer w := zip.NewWriter(&buf) for name, content := range files { f, err := w.Create(name) if err != nil { t.Fatalf("create zip entry %s: %v", name, err) } if _, err := f.Write([]byte(content)); err != nil { t.Fatalf("write zip entry %s: %v", name, err) } } if err := w.Close(); err != nil { t.Fatal(err) } return buf.Bytes() } // readTarGz разбирает tar.gz и возвращает map имя→содержимое. func readTarGz(t *testing.T, data *bytes.Buffer) map[string]string { t.Helper() gr, err := gzip.NewReader(data) if err != nil { t.Fatalf("gzip reader: %v", err) } defer gr.Close() result := make(map[string]string) tr := tar.NewReader(gr) for { hdr, err := tr.Next() if err == io.EOF { break } if err != nil { t.Fatalf("tar next: %v", err) } b, _ := io.ReadAll(tr) result[hdr.Name] = string(b) } return result } // TestPrepareContext_PythonWithRequirements проверяет что для python3.11 с // requirements.txt генерируется правильный Dockerfile с pip install. func TestPrepareContext_PythonWithRequirements(t *testing.T) { zip := makeTestZip(t, map[string]string{ "handler.py": "def handle(req): return 'ok'", "requirements.txt": "flask==3.0.0\n", }) buf, err := PrepareContext(zip, "python3.11") if err != nil { t.Fatalf("PrepareContext: %v", err) } entries := readTarGz(t, buf) // Dockerfile должен быть df, ok := entries["Dockerfile"] if !ok { t.Fatal("Dockerfile not found in tar") } if !strings.Contains(df, "naeel/sless-runtime-python3.11") { t.Errorf("Dockerfile missing python runtime image: %s", df) } if !strings.Contains(df, "pip install") { t.Errorf("Dockerfile missing pip install: %s", df) } // Исходный файл должен быть в архиве if _, ok := entries["handler.py"]; !ok { t.Error("handler.py not found in tar") } } // TestPrepareContext_NodeNoPackageJSON проверяет что для nodejs20 без package.json // не добавляется npm install в Dockerfile. func TestPrepareContext_NodeNoPackageJSON(t *testing.T) { zip := makeTestZip(t, map[string]string{ "index.js": "module.exports.handle = () => 'ok'", }) buf, err := PrepareContext(zip, "nodejs20") if err != nil { t.Fatalf("PrepareContext: %v", err) } entries := readTarGz(t, buf) df := entries["Dockerfile"] if strings.Contains(df, "npm install") { t.Error("npm install should NOT appear without package.json") } if !strings.Contains(df, "naeel/sless-runtime-nodejs20") { t.Errorf("Dockerfile missing nodejs runtime: %s", df) } } // TestPrepareContext_UnsupportedRuntime проверяет что неизвестный runtime // возвращает ошибку а не паникует. func TestPrepareContext_UnsupportedRuntime(t *testing.T) { zip := makeTestZip(t, map[string]string{"main.rb": "puts 'hi'"}) _, err := PrepareContext(zip, "ruby3.2") if err == nil { t.Fatal("expected error for unsupported runtime") } if !strings.Contains(err.Error(), "unsupported runtime") { t.Errorf("unexpected error message: %v", err) } } // TestPrepareContext_DockerfileIsFirst проверяет что Dockerfile — первый файл в tar. // Это требование kaniko: он ищет Dockerfile в корне контекста. func TestPrepareContext_DockerfileIsFirst(t *testing.T) { zip := makeTestZip(t, map[string]string{"app.py": "pass"}) buf, err := PrepareContext(zip, "python3.11") if err != nil { t.Fatal(err) } gr, _ := gzip.NewReader(buf) defer gr.Close() tr := tar.NewReader(gr) hdr, err := tr.Next() if err != nil { t.Fatal(err) } if hdr.Name != "Dockerfile" { t.Errorf("first tar entry should be Dockerfile, got %q", hdr.Name) } }