Files
omnex/control-plane/api/internal/demos/project_provisioner_test.go
T
Xor290 76b1412968
ci-api / test (push) Successful in 24m6s
ci-web / test (push) Successful in 13m50s
chore: update
2026-09-20 11:50:02 +02:00

140 lines
4.6 KiB
Go

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 : "<ns>-<chart>".
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) }