From b1b990ada3a45a1c770ead7d881dc08bf4bdda0b Mon Sep 17 00:00:00 2001 From: Naeel Date: Tue, 14 Apr 2026 23:02:20 +0300 Subject: [PATCH] test: add live acceptance CRUD test and document test flow --- README.md | 7 + doc/progress.md | 11 ++ doc/thinking/2026-04-14.md | 16 ++ .../internal/client/client_acceptance_test.go | 171 ++++++++++++++++++ 4 files changed, 205 insertions(+) create mode 100644 terraform/provider/internal/client/client_acceptance_test.go diff --git a/README.md b/README.md index 6a51379..1da9cb6 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,13 @@ cd terraform/provider go test ./... ``` +Acceptance-тест (реальный кластер, опционально): + +```bash +cd terraform/provider +ACCEPTANCE_TEST=1 KUBECONFIG=/home/naeel/.kube/config go test ./internal/client -run TestAcceptanceFissionCRUD -v +``` + Импорт существующего ресурса (пример): ```bash diff --git a/doc/progress.md b/doc/progress.md index 5810885..1261034 100644 --- a/doc/progress.md +++ b/doc/progress.md @@ -128,3 +128,14 @@ ### Следующий шаг - Добавить acceptance-тесты с опциональным запуском по env-флагу. + +### Обновление этапа (acceptance тест) +- Добавлен acceptance-тест `terraform/provider/internal/client/client_acceptance_test.go`. +- Тест выполняет живой CRUD в кластере для цепочки: + - Environment -> Package -> Function -> HTTPTrigger +- Запуск теста: + - `ACCEPTANCE_TEST=1 KUBECONFIG=/home/naeel/.kube/config go test ./internal/client -run TestAcceptanceFissionCRUD -v` +- Результат: PASS. + +### Следующий шаг +- Расширить acceptance-тест проверкой реального HTTP-вызова функции через ingress. diff --git a/doc/thinking/2026-04-14.md b/doc/thinking/2026-04-14.md index 5f299b8..678fedf 100644 --- a/doc/thinking/2026-04-14.md +++ b/doc/thinking/2026-04-14.md @@ -156,3 +156,19 @@ - Повторный `terraform apply` успешен. - Финальная проверка endpoint: - `GET /tf-hello` -> `hello from fission via terraform`. + +--- + +Агент: GitHub Copilot +Модель: GPT-5.3-Codex + +## План (до действий, этап 9) +1. Добавить acceptance-тест для реального CRUD через `internal/client`. +2. Прогнать unit + acceptance тесты. +3. Зафиксировать результат в документации. + +## Результат (после действий, этап 9) +- Добавлен `internal/client/client_acceptance_test.go`. +- Unit-тесты: PASS. +- Acceptance-тест `TestAcceptanceFissionCRUD`: PASS. +- Тест создает и удаляет Environment/Package/Function/HTTPTrigger в реальном кластере. diff --git a/terraform/provider/internal/client/client_acceptance_test.go b/terraform/provider/internal/client/client_acceptance_test.go new file mode 100644 index 0000000..810e66e --- /dev/null +++ b/terraform/provider/internal/client/client_acceptance_test.go @@ -0,0 +1,171 @@ +package client + +import ( + "context" + "encoding/base64" + "fmt" + "os" + "testing" + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// TestAcceptanceFissionCRUD выполняет интеграционный CRUD тест в реальном кластере. +func TestAcceptanceFissionCRUD(t *testing.T) { + if os.Getenv("ACCEPTANCE_TEST") != "1" { + t.Skip("set ACCEPTANCE_TEST=1 to run acceptance tests") + } + + kubeconfigPath := os.Getenv("KUBECONFIG") + if kubeconfigPath == "" { + kubeconfigPath = "/home/naeel/.kube/config" + } + + namespace := os.Getenv("FISSION_NAMESPACE") + if namespace == "" { + namespace = "default" + } + + c, err := New(kubeconfigPath, os.Getenv("KUBE_CONTEXT"), namespace) + if err != nil { + t.Fatalf("create client: %v", err) + } + + ctx := context.Background() + suffix := time.Now().UnixNano() + envName := fmt.Sprintf("acc-env-%d", suffix) + pkgName := fmt.Sprintf("acc-pkg-%d", suffix) + fnName := fmt.Sprintf("acc-fn-%d", suffix) + triggerName := fmt.Sprintf("acc-tr-%d", suffix) + urlPath := fmt.Sprintf("/acc-%d", suffix) + + envObj := &unstructured.Unstructured{Object: map[string]interface{}{ + "apiVersion": "fission.io/v1", + "kind": "Environment", + "metadata": map[string]interface{}{ + "name": envName, + "namespace": namespace, + }, + "spec": map[string]interface{}{ + "version": 3, + "runtime": map[string]interface{}{ + "image": "ghcr.io/fission/python-env", + }, + "poolsize": 1, + }, + }} + + if _, err = c.CreateEnvironment(ctx, envObj); err != nil { + t.Fatalf("create environment: %v", err) + } + defer func() { _ = c.DeleteEnvironment(ctx, namespace, envName) }() + + if _, err = c.GetEnvironment(ctx, namespace, envName); err != nil { + t.Fatalf("get environment: %v", err) + } + + literal := base64.StdEncoding.EncodeToString([]byte("def main():\n return 'ok from acceptance'\n")) + pkgObj := &unstructured.Unstructured{Object: map[string]interface{}{ + "apiVersion": "fission.io/v1", + "kind": "Package", + "metadata": map[string]interface{}{ + "name": pkgName, + "namespace": namespace, + }, + "spec": map[string]interface{}{ + "deployment": map[string]interface{}{ + "type": "literal", + "literal": literal, + }, + "environment": map[string]interface{}{ + "name": envName, + "namespace": namespace, + }, + "source": map[string]interface{}{}, + }, + }} + + if _, err = c.CreatePackage(ctx, pkgObj); err != nil { + t.Fatalf("create package: %v", err) + } + defer func() { _ = c.DeletePackage(ctx, namespace, pkgName) }() + + if _, err = c.GetPackage(ctx, namespace, pkgName); err != nil { + t.Fatalf("get package: %v", err) + } + + fnObj := &unstructured.Unstructured{Object: map[string]interface{}{ + "apiVersion": "fission.io/v1", + "kind": "Function", + "metadata": map[string]interface{}{ + "name": fnName, + "namespace": namespace, + }, + "spec": map[string]interface{}{ + "environment": map[string]interface{}{ + "name": envName, + "namespace": namespace, + }, + "InvokeStrategy": map[string]interface{}{ + "ExecutionStrategy": map[string]interface{}{ + "ExecutorType": "poolmgr", + }, + "StrategyType": "execution", + }, + "package": map[string]interface{}{ + "packageref": map[string]interface{}{ + "name": pkgName, + "namespace": namespace, + }, + "functionName": "main.main", + }, + }, + }} + + if _, err = c.CreateFunction(ctx, fnObj); err != nil { + t.Fatalf("create function: %v", err) + } + defer func() { _ = c.DeleteFunction(ctx, namespace, fnName) }() + + if _, err = c.GetFunction(ctx, namespace, fnName); err != nil { + t.Fatalf("get function: %v", err) + } + + trObj := &unstructured.Unstructured{Object: map[string]interface{}{ + "apiVersion": "fission.io/v1", + "kind": "HTTPTrigger", + "metadata": map[string]interface{}{ + "name": triggerName, + "namespace": namespace, + }, + "spec": map[string]interface{}{ + "relativeurl": urlPath, + "methods": []interface{}{"GET"}, + "functionref": map[string]interface{}{ + "type": "name", + "name": fnName, + }, + "createingress": false, + }, + }} + + if _, err = c.CreateHTTPTrigger(ctx, trObj); err != nil { + t.Fatalf("create http trigger: %v", err) + } + defer func() { _ = c.DeleteHTTPTrigger(ctx, namespace, triggerName) }() + + if _, err = c.GetHTTPTrigger(ctx, namespace, triggerName); err != nil { + t.Fatalf("get http trigger: %v", err) + } + + gvr := schema.GroupVersionResource{Group: "fission.io", Version: "v1", Resource: "httptriggers"} + if err = c.DynClient.Resource(gvr).Namespace(namespace).Delete(ctx, triggerName, metav1.DeleteOptions{}); err != nil { + t.Fatalf("delete trigger via dynamic client: %v", err) + } + if err = c.DeleteHTTPTrigger(ctx, namespace, triggerName); err == nil { + t.Fatalf("expected not found error after trigger deletion") + } +}