- Config loader with env var expansion and validation - SQLite database with tasks and toolchain_versions tables - Gitea REST API client for repository and branch operations - K8s client for Job lifecycle management (with noop fallback for local dev) - REST API: tasks CRUD, retry, plugins, version management - Worker/scheduler with polling, concurrency control, and job tracking - Static frontend: generate, tasks, settings pages - Toolchain images: Go and Node.js Dockerfiles with entrypoint scripts - Helm chart with deployment, service, configmap, ingress, PVC - Multi-stage Dockerfile for service - Unit tests for config, database, gitclient, k8s, api, worker - E2E tests for idempotency, version switch, failure recovery
152 lines
4.1 KiB
Go
152 lines
4.1 KiB
Go
package e2e
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"proto-gen/internal/api"
|
|
"proto-gen/internal/config"
|
|
"proto-gen/internal/database"
|
|
"proto-gen/internal/gitclient"
|
|
"proto-gen/internal/models"
|
|
)
|
|
|
|
type mockGitClient struct{}
|
|
|
|
func (m *mockGitClient) ListRepositories(owner string) ([]gitclient.Repository, error) {
|
|
return nil, nil
|
|
}
|
|
func (m *mockGitClient) CreateRepository(owner, name, desc string, private bool) (*gitclient.Repository, error) {
|
|
return &gitclient.Repository{Name: name}, nil
|
|
}
|
|
func (m *mockGitClient) GetRepository(owner, name string) (*gitclient.Repository, error) {
|
|
return &gitclient.Repository{Name: name}, nil
|
|
}
|
|
func (m *mockGitClient) ListBranches(owner, repo string) ([]gitclient.Branch, error) {
|
|
return []gitclient.Branch{{Name: "main", Commit: "abc123"}}, nil
|
|
}
|
|
func (m *mockGitClient) GetBranch(owner, repo, branch string) (*gitclient.Branch, error) {
|
|
return &gitclient.Branch{Name: branch, Commit: "abc123"}, nil
|
|
}
|
|
|
|
func newTestServer(t *testing.T) (*api.Server, *database.DB) {
|
|
t.Helper()
|
|
db, err := database.New(t.TempDir() + "/test.db")
|
|
if err != nil {
|
|
t.Fatalf("creating db: %v", err)
|
|
}
|
|
t.Cleanup(func() { db.Close() })
|
|
|
|
cfg := &config.Config{
|
|
ToolchainRegistry: "registry.example.com/proto-gen",
|
|
TargetRepoTemplate: "{proto_repo}-gen-{lang}",
|
|
Plugins: []config.PluginConfig{
|
|
{Language: "go", Image: "proto-gen-go:v1.0"},
|
|
{Language: "nodejs", Image: "proto-gen-ts:v1.0"},
|
|
},
|
|
}
|
|
|
|
return api.NewServer(db, cfg, &mockGitClient{}), db
|
|
}
|
|
|
|
func TestIdempotency(t *testing.T) {
|
|
srv, db := newTestServer(t)
|
|
|
|
body, _ := json.Marshal(map[string]string{
|
|
"language": "go",
|
|
"proto_repo": "test-owner/yoresee_doc",
|
|
"proto_branch": "main",
|
|
})
|
|
|
|
req := httptest.NewRequest("POST", "/api/v1/tasks", bytes.NewReader(body))
|
|
w := httptest.NewRecorder()
|
|
srv.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusCreated {
|
|
t.Fatalf("first create: status = %d, want %d", w.Code, http.StatusCreated)
|
|
}
|
|
|
|
req = httptest.NewRequest("POST", "/api/v1/tasks", bytes.NewReader(body))
|
|
w = httptest.NewRecorder()
|
|
srv.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusCreated {
|
|
t.Fatalf("second create: status = %d, want %d", w.Code, http.StatusCreated)
|
|
}
|
|
|
|
tasks, _ := db.ListTasks("", 100, 0)
|
|
goTasks := 0
|
|
for _, task := range tasks {
|
|
if task.Language == "go" {
|
|
goTasks++
|
|
}
|
|
}
|
|
if goTasks != 2 {
|
|
t.Errorf("go tasks = %d, want 2", goTasks)
|
|
}
|
|
}
|
|
|
|
func TestVersionSwitch(t *testing.T) {
|
|
srv, _ := newTestServer(t)
|
|
|
|
req := httptest.NewRequest("GET", "/api/v1/plugins/go/versions", nil)
|
|
w := httptest.NewRecorder()
|
|
srv.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("get version: status = %d, want %d", w.Code, http.StatusOK)
|
|
}
|
|
|
|
body, _ := json.Marshal(map[string]string{"image": "proto-gen-go:v2.0"})
|
|
req = httptest.NewRequest("PUT", "/api/v1/plugins/go/versions", bytes.NewReader(body))
|
|
w = httptest.NewRecorder()
|
|
srv.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("update version: status = %d, want %d", w.Code, http.StatusOK)
|
|
}
|
|
|
|
var plugin config.PluginConfig
|
|
json.NewDecoder(w.Body).Decode(&plugin)
|
|
if plugin.Image != "proto-gen-go:v2.0" {
|
|
t.Errorf("Image = %q, want %q", plugin.Image, "proto-gen-go:v2.0")
|
|
}
|
|
}
|
|
|
|
func TestFailureRecovery(t *testing.T) {
|
|
srv, db := newTestServer(t)
|
|
|
|
task := &models.Task{
|
|
ID: "fail-task-1",
|
|
Status: models.StatusFailed,
|
|
Language: "go",
|
|
ProtoRepo: "test/yoresee_doc",
|
|
ProtoBranch: "main",
|
|
TargetRepo: "yoresee_doc-gen-go",
|
|
ToolchainImage: "proto-gen-go:v1.0",
|
|
Error: "some error",
|
|
}
|
|
db.CreateTask(task)
|
|
|
|
body, _ := json.Marshal(map[string]string{})
|
|
req := httptest.NewRequest("POST", "/api/v1/tasks/fail-task-1/retry", bytes.NewReader(body))
|
|
w := httptest.NewRecorder()
|
|
srv.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("retry: status = %d, want %d", w.Code, http.StatusOK)
|
|
}
|
|
|
|
var retried models.Task
|
|
json.NewDecoder(w.Body).Decode(&retried)
|
|
if retried.Status != models.StatusPending {
|
|
t.Errorf("Status = %q, want %q", retried.Status, models.StatusPending)
|
|
}
|
|
if retried.Retries != 1 {
|
|
t.Errorf("Retries = %d, want 1", retried.Retries)
|
|
}
|
|
}
|