package demos import ( "os" "os/exec" "path/filepath" "strings" "testing" "go.yaml.in/yaml/v2" "github.com/omnex/control-plane/api/internal/config" ) func ptr[T any](v T) *T { return &v } func TestNextReplicas_SuspendThenResumeRestoresOriginalCount(t *testing.T) { annotations := map[string]string{} current := ptr(int32(3)) target, changed := nextReplicas(current, &annotations, false) if !changed || target != 0 { t.Fatalf("suspension: target=%d changed=%v", target, changed) } if annotations[replicasBeforeSuspendAnnotation] != "3" { t.Fatalf("annotation=%q, attendu 3", annotations[replicasBeforeSuspendAnnotation]) } // Déjà à 0 : suspendre à nouveau ne doit pas écraser l'annotation. current = ptr(int32(0)) if _, changed := nextReplicas(current, &annotations, false); changed { t.Error("suspension répétée non idempotente") } if annotations[replicasBeforeSuspendAnnotation] != "3" { t.Error("l'annotation a été écrasée") } target, changed = nextReplicas(current, &annotations, true) if !changed || target != 3 { t.Fatalf("relance: target=%d changed=%v, attendu 3", target, changed) } if _, still := annotations[replicasBeforeSuspendAnnotation]; still { t.Error("annotation non nettoyée après la relance") } } func TestNextReplicas_ResumeWithoutAnnotationDefaultsToOne(t *testing.T) { annotations := map[string]string{} target, changed := nextReplicas(ptr(int32(0)), &annotations, true) if !changed || target != 1 { t.Errorf("target=%d changed=%v, attendu 1", target, changed) } } func TestNextReplicas_ResumeRunningWorkloadIsNoop(t *testing.T) { annotations := map[string]string{} if _, changed := nextReplicas(ptr(int32(2)), &annotations, true); changed { t.Error("relance d'une charge déjà active") } } // TestVitrineValuesRenderWithCharts génère les valeurs comme ProvisionProject // puis rend les charts de deploy/chart-vitrine avec helm : garantit que les // clés attendues existent, que les validations des charts passent et que les // noms de Services calculés côté Go sont bien ceux des charts. func TestVitrineValuesRenderWithCharts(t *testing.T) { if _, err := exec.LookPath("helm"); err != nil { t.Skip("helm absent du PATH") } chartsDir, err := filepath.Abs("../../../../deploy/chart-vitrine") if err != nil { t.Fatal(err) } h := &HelmProvisioner{cfg: &config.Config{ VitrineBackendImage: "registry.example/vitrine-backend:1.2.3", VitrineFrontendImage: "registry.example/vitrine-frontend:1.2.3", }} p := ProjectSpec{ Namespace: "vitrine-ab12cd34", Host: "vitrine-ab12cd34.vitrine-omnex.club", URL: "https://vitrine-ab12cd34.vitrine-omnex.club", AdminUsername: "admin", AdminPassword: "un-mot-de-passe-solide", AdminNumber: 3, } cases := []struct { chart string values map[string]interface{} wants []string }{ {"postgresql", h.buildVitrinePostgresValues("pgpass"), []string{"name: postgres\n"}}, {"redis", h.buildVitrineRedisValues("redispass"), []string{"name: redis\n"}}, {"backend", h.buildVitrineBackendValues(p, "pgpass", "redispass", "jwt"), []string{ "name: " + vitrineBackendService(p.Namespace) + "\n", `ADMIN_NUMBER: "3"`, `SEED_ADMIN_USERNAME: "admin"`, "registry.example/vitrine-backend:1.2.3", "post-install", // seedJob activé "postgres://postgres:pgpass@postgres:5432/vitrine_db", }}, {"frontend", h.buildVitrineFrontendValues(), []string{ "name: " + vitrineFrontendService(p.Namespace) + "\n", "registry.example/vitrine-frontend:1.2.3", }}, {"ingressroute", h.buildVitrineIngressRouteValues(p), []string{ "Host(`vitrine-ab12cd34.vitrine-omnex.club`)", "name: " + vitrineBackendService(p.Namespace) + "\n", "name: " + vitrineFrontendService(p.Namespace) + "\n", "tls: {}", // certificat servi par le TLSStore, pas par projet }}, } for _, c := range cases { t.Run(c.chart, func(t *testing.T) { raw, err := yaml.Marshal(c.values) if err != nil { t.Fatal(err) } valuesFile := filepath.Join(t.TempDir(), "values.yaml") if err := writeFile(valuesFile, raw); err != nil { t.Fatal(err) } // Release et namespace comme installChartFrom : "-". out, err := exec.Command("helm", "template", p.Namespace+"-"+c.chart, filepath.Join(chartsDir, c.chart), "--namespace", p.Namespace, "--values", valuesFile).CombinedOutput() if err != nil { t.Fatalf("helm template: %v\n%s", err, out) } for _, want := range c.wants { if !strings.Contains(string(out), want) { t.Errorf("rendu de %s : %q introuvable", c.chart, want) } } }) } } func writeFile(path string, data []byte) error { return os.WriteFile(path, data, 0o600) }