Аудит провайдера vs каноничный Fission. Добавлено:
Environment:
- builder_image, builder_command для Go и языков с build step
Package:
- deploy_type (literal/source) для переключения deployment/source archive
- loadPackageSourceArchive() — zip-упаковка source_dir
- Убран пустой source:{} из literal mode
Function:
- executor_type (poolmgr/newdeploy/container)
- function_timeout, idle_timeout
- min_scale, max_scale для ExecutionStrategy
Все 21 тест пройден. Обратная совместимость проверена.
Документация: doc/AUDIT_PROVIDER_VS_FISSION_2026-06-03.md
261 lines
7.7 KiB
Go
261 lines
7.7 KiB
Go
package resources
|
|
|
|
import (
|
|
"archive/zip"
|
|
"bytes"
|
|
"context"
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/hashicorp/terraform-plugin-framework/diag"
|
|
"github.com/hashicorp/terraform-plugin-framework/types"
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
)
|
|
|
|
func TestValidatePackageSource(t *testing.T) {
|
|
var diagnostics diag.Diagnostics
|
|
ok := validatePackageSource(types.StringValue("code"), types.StringNull(), &diagnostics)
|
|
if !ok || diagnostics.HasError() {
|
|
t.Fatalf("expected valid source_dir-only config")
|
|
}
|
|
|
|
diagnostics = diag.Diagnostics{}
|
|
ok = validatePackageSource(types.StringNull(), types.StringValue("main.zip"), &diagnostics)
|
|
if !ok || diagnostics.HasError() {
|
|
t.Fatalf("expected valid code_path-only config")
|
|
}
|
|
|
|
diagnostics = diag.Diagnostics{}
|
|
ok = validatePackageSource(types.StringNull(), types.StringNull(), &diagnostics)
|
|
if ok || !diagnostics.HasError() {
|
|
t.Fatalf("expected invalid config when both values are empty")
|
|
}
|
|
}
|
|
|
|
func TestLoadPackageLiteralFromSourceDir(t *testing.T) {
|
|
tempDir := t.TempDir()
|
|
mainPath := filepath.Join(tempDir, "main.py")
|
|
content := []byte("def main():\n return 'ok'\n")
|
|
if err := os.WriteFile(mainPath, content, 0o600); err != nil {
|
|
t.Fatalf("write temp main.py: %v", err)
|
|
}
|
|
|
|
loaded, err := loadPackageLiteral(tempDir, "")
|
|
if err != nil {
|
|
t.Fatalf("loadPackageLiteral returned error: %v", err)
|
|
}
|
|
if string(loaded) != string(content) {
|
|
t.Fatalf("unexpected loaded content")
|
|
}
|
|
}
|
|
|
|
func TestLoadPackageLiteralFromSourceDirJS(t *testing.T) {
|
|
tempDir := t.TempDir()
|
|
mainPath := filepath.Join(tempDir, "main.js")
|
|
content := []byte("module.exports = async function() { return 'ok'; }\n")
|
|
if err := os.WriteFile(mainPath, content, 0o600); err != nil {
|
|
t.Fatalf("write temp main.js: %v", err)
|
|
}
|
|
|
|
loaded, err := loadPackageLiteral(tempDir, "")
|
|
if err != nil {
|
|
t.Fatalf("loadPackageLiteral returned error: %v", err)
|
|
}
|
|
if string(loaded) != string(content) {
|
|
t.Fatalf("unexpected loaded content")
|
|
}
|
|
}
|
|
|
|
func TestLoadPackageLiteralFromSourceDirGo(t *testing.T) {
|
|
tempDir := t.TempDir()
|
|
mainPath := filepath.Join(tempDir, "main.go")
|
|
content := []byte("package main\nfunc main() {}\n")
|
|
if err := os.WriteFile(mainPath, content, 0o600); err != nil {
|
|
t.Fatalf("write temp main.go: %v", err)
|
|
}
|
|
|
|
loaded, err := loadPackageLiteral(tempDir, "")
|
|
if err != nil {
|
|
t.Fatalf("loadPackageLiteral returned error: %v", err)
|
|
}
|
|
if string(loaded) != string(content) {
|
|
t.Fatalf("unexpected loaded content")
|
|
}
|
|
}
|
|
|
|
func TestPackageToUnstructured(t *testing.T) {
|
|
input := packageResourceModel{
|
|
Name: types.StringValue("pkg-a"),
|
|
Environment: types.StringValue("env-a"),
|
|
}
|
|
|
|
obj := packageToUnstructured(input, "default", []byte("print('hi')"))
|
|
literal, found, err := unstructured.NestedString(obj.Object, "spec", "deployment", "literal")
|
|
if err != nil || !found {
|
|
t.Fatalf("deployment.literal not found")
|
|
}
|
|
|
|
decoded, err := base64.StdEncoding.DecodeString(literal)
|
|
if err != nil {
|
|
t.Fatalf("decode literal: %v", err)
|
|
}
|
|
if string(decoded) != "print('hi')" {
|
|
t.Fatalf("unexpected decoded literal: %q", string(decoded))
|
|
}
|
|
|
|
// Verify no empty source map is generated
|
|
_, sourceFound, _ := unstructured.NestedMap(obj.Object, "spec", "source")
|
|
if sourceFound {
|
|
t.Fatalf("empty source should not be present in deployment mode")
|
|
}
|
|
}
|
|
|
|
func TestPackageToUnstructuredSourceMode(t *testing.T) {
|
|
input := packageResourceModel{
|
|
Name: types.StringValue("pkg-go"),
|
|
Environment: types.StringValue("go-env"),
|
|
DeployType: types.StringValue("source"),
|
|
BuildCmd: types.StringValue("build"),
|
|
}
|
|
|
|
obj := packageToUnstructured(input, "default", []byte("zip-content"))
|
|
sourceLiteral, found, err := unstructured.NestedString(obj.Object, "spec", "source", "literal")
|
|
if err != nil || !found {
|
|
t.Fatalf("source.literal not found")
|
|
}
|
|
|
|
decoded, err := base64.StdEncoding.DecodeString(sourceLiteral)
|
|
if err != nil {
|
|
t.Fatalf("decode source literal: %v", err)
|
|
}
|
|
if string(decoded) != "zip-content" {
|
|
t.Fatalf("unexpected source literal: %q", string(decoded))
|
|
}
|
|
|
|
// Verify no deployment section in source mode
|
|
_, deployFound, _ := unstructured.NestedMap(obj.Object, "spec", "deployment")
|
|
if deployFound {
|
|
t.Fatalf("deployment should not be present in source mode")
|
|
}
|
|
|
|
// Verify buildcmd is set
|
|
buildCmd, _, _ := unstructured.NestedString(obj.Object, "spec", "buildcmd")
|
|
if buildCmd != "build" {
|
|
t.Fatalf("unexpected buildcmd: %q", buildCmd)
|
|
}
|
|
}
|
|
|
|
func TestResolveNamespace(t *testing.T) {
|
|
if got := resolveNamespace(types.StringValue("custom"), "default"); got != "custom" {
|
|
t.Fatalf("unexpected namespace: %q", got)
|
|
}
|
|
if got := resolveNamespace(types.StringNull(), "default"); got != "default" {
|
|
t.Fatalf("unexpected namespace fallback: %q", got)
|
|
}
|
|
}
|
|
|
|
func TestCalculateCodeHash(t *testing.T) {
|
|
input := []byte("def main():\n return 'ok'\n")
|
|
got := calculateCodeHash(input)
|
|
|
|
expected := fmt.Sprintf("%x", sha256.Sum256(input))
|
|
if got != expected {
|
|
t.Fatalf("unexpected code hash: got %q want %q", got, expected)
|
|
}
|
|
}
|
|
|
|
func TestUnstructuredToPackageModelSetsNullComputed(t *testing.T) {
|
|
obj := &unstructured.Unstructured{Object: map[string]interface{}{
|
|
"apiVersion": "fission.io/v1",
|
|
"kind": "Package",
|
|
"metadata": map[string]interface{}{
|
|
"name": "pkg-a",
|
|
"namespace": "default",
|
|
},
|
|
"spec": map[string]interface{}{
|
|
"environment": map[string]interface{}{"name": "env-a"},
|
|
},
|
|
}}
|
|
|
|
state := unstructuredToPackageModel(obj, packageResourceModel{})
|
|
if !state.BuildStatus.IsNull() {
|
|
t.Fatalf("expected build_status to be null when status is absent")
|
|
}
|
|
if !state.BuildLog.IsNull() {
|
|
t.Fatalf("expected build_log to be null when status is absent")
|
|
}
|
|
}
|
|
|
|
func TestHTTPTriggerRoundTrip(t *testing.T) {
|
|
ctx := context.Background()
|
|
methods, diagnostics := types.ListValueFrom(ctx, types.StringType, []string{"GET", "POST"})
|
|
if diagnostics.HasError() {
|
|
t.Fatalf("failed to create methods list")
|
|
}
|
|
|
|
model := httpTriggerResourceModel{
|
|
Name: types.StringValue("tr-a"),
|
|
Function: types.StringValue("fn-a"),
|
|
URL: types.StringValue("/hello"),
|
|
Methods: methods,
|
|
CreateIngress: types.BoolValue(true),
|
|
Host: types.StringValue("fission.kube5s.ru"),
|
|
}
|
|
|
|
obj, err := httpTriggerToUnstructured(ctx, model, "default")
|
|
if err != nil {
|
|
t.Fatalf("httpTriggerToUnstructured error: %v", err)
|
|
}
|
|
|
|
state := unstructuredToHTTPTriggerModel(ctx, obj, model)
|
|
if state.Name.ValueString() != "tr-a" {
|
|
t.Fatalf("unexpected trigger name: %q", state.Name.ValueString())
|
|
}
|
|
if state.Function.ValueString() != "fn-a" {
|
|
t.Fatalf("unexpected function name: %q", state.Function.ValueString())
|
|
}
|
|
if state.URL.ValueString() != "/hello" {
|
|
t.Fatalf("unexpected url: %q", state.URL.ValueString())
|
|
}
|
|
}
|
|
|
|
func TestLoadPackageSourceArchive(t *testing.T) {
|
|
tempDir := t.TempDir()
|
|
// Create multiple files to zip
|
|
files := map[string]string{
|
|
"main.go": "package main\n\nimport \"net/http\"\n\nfunc Handler(w http.ResponseWriter, r *http.Request) {}\n",
|
|
"go.mod": "module example.com/fn\n\ngo 1.21\n",
|
|
}
|
|
for name, content := range files {
|
|
if err := os.WriteFile(filepath.Join(tempDir, name), []byte(content), 0o600); err != nil {
|
|
t.Fatalf("write %s: %v", name, err)
|
|
}
|
|
}
|
|
|
|
zipBytes, err := loadPackageSourceArchive(tempDir)
|
|
if err != nil {
|
|
t.Fatalf("loadPackageSourceArchive error: %v", err)
|
|
}
|
|
|
|
// Verify it's a valid zip
|
|
reader, err := zip.NewReader(bytes.NewReader(zipBytes), int64(len(zipBytes)))
|
|
if err != nil {
|
|
t.Fatalf("invalid zip: %v", err)
|
|
}
|
|
|
|
foundFiles := map[string]bool{}
|
|
for _, f := range reader.File {
|
|
foundFiles[f.Name] = true
|
|
}
|
|
if !foundFiles["main.go"] {
|
|
t.Fatalf("main.go not found in zip")
|
|
}
|
|
if !foundFiles["go.mod"] {
|
|
t.Fatalf("go.mod not found in zip")
|
|
}
|
|
}
|