package main import ( "context" "encoding/json" "errors" "fmt" "strings" "testing" "time" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" dynamicfake "k8s.io/client-go/dynamic/fake" k8stesting "k8s.io/client-go/testing" ) func TestQualManual(t *testing.T) { c, now := qualFixture(t, "") build := publishQual(t, c, "point", strings.Repeat("1", 40)) stepQual(t, c) if len(readQual(t, c, "point").Attempts) != 0 { t.Fatal("omitted mode started automatic qualification") } requestQual(t, c, "point", build+"/first") stepQual(t, c) first := readQual(t, c, "point").Attempts[0] if first.Phase != "Reserved" || countWorkflows(t, c, "qualification") != 0 { t.Fatal("reservation was not persisted before submission") } stepQual(t, c) markQual(t, c, "delivery", first.WorkflowName, "Running", now, nil) stepQual(t, c) for _, id := range []string{"second", "third"} { requestQual(t, c, "point", build+"/"+id) stepQual(t, c) } q := readQual(t, c, "point") if len(q.Attempts) != 3 || q.Attempts[1].Phase != "Pending" || q.Attempts[2].Phase != "Pending" { t.Fatalf("accepted requests = %#v", q.Attempts) } requestQual(t, c, "point", build+"/first") stepQual(t, c) if len(readQual(t, c, "point").Attempts) != 3 || countWorkflows(t, c, "qualification") != 1 { t.Fatal("reapplying a consumed request created another run") } markQual(t, c, "delivery", first.WorkflowName, "Succeeded", now, map[string]string{"qualification-result": `{"report":"synthetic://reports/first"}`}) stepQual(t, c) stepQual(t, c) stepQual(t, c) if countWorkflows(t, c, "qualification") != 2 { t.Fatal("pending manual request did not advance after completion") } q = readQual(t, c, "point") if string(q.Attempts[0].Result) != `{"report":"synthetic://reports/first"}` { t.Fatalf("qualification result = %s", q.Attempts[0].Result) } } func TestQualInterval(t *testing.T) { c, now := qualFixture(t, "OnBuild") firstBuild := publishQual(t, c, "point", strings.Repeat("1", 40)) stepQual(t, c) stepQual(t, c) first := readQual(t, c, "point").Attempts[0] markQual(t, c, "delivery", first.WorkflowName, "Running", now, nil) stepQual(t, c) publishQual(t, c, "point", strings.Repeat("2", 40)) thirdBuild := publishQual(t, c, "point", strings.Repeat("3", 40)) markQual(t, c, "delivery", first.WorkflowName, "Failed", now, nil) stepQual(t, c) *now = now.Add(167 * time.Hour) stepQual(t, c) if len(readQual(t, c, "point").Attempts) != 1 { t.Fatal("automatic qualification ignored the interval after failure") } *now = now.Add(time.Hour) stepQual(t, c) q := readQual(t, c, "point") if len(q.Attempts) != 2 || q.Builds[q.Attempts[1].Build-1].WorkflowName != thirdBuild { t.Fatalf("expiry did not select latest build: %#v", q.Attempts) } stepQual(t, c) second := q.Attempts[1] markQual(t, c, "delivery", second.WorkflowName, "Succeeded", now, nil) stepQual(t, c) requestQual(t, c, "point", firstBuild+"/old-build") stepQual(t, c) stepQual(t, c) q = readQual(t, c, "point") manual := q.Attempts[2] if manual.Kind != "Manual" || manual.Build != 1 { t.Fatalf("manual selection = %#v", manual) } *now = now.Add(time.Hour) markQual(t, c, "delivery", manual.WorkflowName, "Succeeded", now, nil) stepQual(t, c) q = readQual(t, c, "point") if q.Covered != 3 || !q.LastStart.Equal(*now) { t.Fatalf("manual start accounting = %#v", q) } *now = now.Add(168 * time.Hour) stepQual(t, c) if len(readQual(t, c, "point").Attempts) != 3 { t.Fatal("expiry without a new build repeated qualification") } } func TestQualSnapshot(t *testing.T) { c, _ := qualFixture(t, "OnBuild") build := publishQual(t, c, "point", strings.Repeat("1", 40)) original := readQual(t, c, "point").Builds[0].Input object := getDelivery(t, c, "point") spec := object.Object["spec"].(map[string]any) spec["source"].(map[string]any)["repository"] = "https://example.test/other/repo" spec["targets"] = []any{map[string]any{"kind": "Other", "repository": "changed"}} if _, err := c.client.Resource(projectDeliveryGVR).Namespace("delivery").Update(context.Background(), object, metav1.UpdateOptions{}); err != nil { t.Fatal(err) } if err := c.client.Resource(workflowGVR).Namespace("delivery").Delete(context.Background(), build, metav1.DeleteOptions{}); err != nil { t.Fatal(err) } stepQual(t, c) object = getDelivery(t, c, "point") object.Object["spec"].(map[string]any)["qualification"].(map[string]any)["workflowTemplateRef"] = map[string]any{"name": "other-template"} if _, err := c.client.Resource(projectDeliveryGVR).Namespace("delivery").Update(context.Background(), object, metav1.UpdateOptions{}); err != nil { t.Fatal(err) } stepQual(t, c) attempt := readQual(t, c, "point").Attempts[0] workflow, err := c.client.Resource(workflowGVR).Namespace("delivery").Get(context.Background(), attempt.WorkflowName, metav1.GetOptions{}) if err != nil { t.Fatal(err) } template := workflow.GetAnnotations()[templateKey] if template != "qual-tests" { t.Fatalf("reserved template changed to %q", template) } for key, value := range original { if got := workflowParameter(t, workflow, key); got != value { t.Fatalf("parameter %s = %q, want %q", key, got, value) } } if workflowHasParameter(t, workflow, "image-digest") { t.Fatal("controller interpreted the publication result") } } func TestQualReservation(t *testing.T) { c, _ := qualFixture(t, "OnBuild") publishQual(t, c, "point", strings.Repeat("1", 40)) fail := true c.client.(*dynamicfake.FakeDynamicClient).PrependReactor("update", "projectdeliveries", func(action k8stesting.Action) (bool, runtime.Object, error) { if fail { fail = false return true, nil, errors.New("synthetic status write failure") } return false, nil, nil }) if err := c.qualifyAll(context.Background()); err == nil { t.Fatal("expected reservation write failure") } if countWorkflows(t, c, "qualification") != 0 || len(readQual(t, c, "point").Attempts) != 0 { t.Fatal("unpersisted reservation created a Workflow") } stepQual(t, c) restarted := *c restarted.qualCursor = "" conflicts := 0 c.client.(*dynamicfake.FakeDynamicClient).PrependReactor("update", "projectdeliveries", func(action k8stesting.Action) (bool, runtime.Object, error) { object := action.(k8stesting.UpdateAction).GetObject().(*unstructured.Unstructured) attempts, _, _ := unstructured.NestedSlice(object.Object, "status", "qualification", "attempts") queued := len(attempts) != 0 && attempts[0].(map[string]any)["phase"] == "Queued" if conflicts == 0 && queued { conflicts++ return true, nil, apierrors.NewConflict(projectDeliveryGVR.GroupResource(), "point", errors.New("synthetic conflict")) } return false, nil, nil }) stepQual(t, &restarted) stepQual(t, &restarted) if conflicts != 1 || countWorkflows(t, c, "qualification") != 1 { t.Fatalf("reservation recovery: conflicts=%d workflows=%d", conflicts, countWorkflows(t, c, "qualification")) } } func TestQualGlobalCap(t *testing.T) { c, now := qualFixture(t, "OnBuild") second := getDelivery(t, c, "point").DeepCopy() second.SetNamespace("other") second.SetUID("other-delivery") second.SetResourceVersion("") second.Object["spec"].(map[string]any)["qualification"].(map[string]any)["workflowTemplateRef"] = map[string]any{"name": "other-qual"} if _, err := c.client.Resource(projectDeliveryGVR).Namespace("other").Create(context.Background(), second, metav1.CreateOptions{}); err != nil { t.Fatal(err) } publishQual(t, c, "point", strings.Repeat("1", 40)) other, err := decodeProjectDelivery(second) if err != nil { t.Fatal(err) } input := map[string]string{"source-repository": "https://example.test/repo/other", "source-ref": "", "source-revision": strings.Repeat("2", 40), "targets": "[{}]"} observeQualBuild(&other, &attemptStatus{WorkflowName: "other-build", Phase: "Succeeded", Input: input, Result: json.RawMessage(`{"image":"other"}`)}) if err := c.updateStatus(context.Background(), second, &other); err != nil { t.Fatal(err) } stepQual(t, c) stepQual(t, c) first := readQual(t, c, "point").Attempts[0] markQual(t, c, "delivery", first.WorkflowName, "Running", now, nil) stepQual(t, c) objects, err := c.client.Resource(workflowGVR).Namespace(metav1.NamespaceAll).List(context.Background(), metav1.ListOptions{LabelSelector: purposeAnnotation + "=qualification"}) if err != nil || len(objects.Items) != 1 { t.Fatalf("global Workflows = %d, error=%v", len(objects.Items), err) } publishQual(t, c, "point", strings.Repeat("3", 40)) markQual(t, c, "delivery", first.WorkflowName, "Succeeded", now, nil) stepQual(t, c) *now = now.Add(168 * time.Hour) stepQual(t, c) stepQual(t, c) objects, err = c.client.Resource(workflowGVR).Namespace("other").List(context.Background(), metav1.ListOptions{LabelSelector: purposeAnnotation + "=qualification"}) if err != nil || len(objects.Items) != 1 { t.Fatalf("waiting namespace did not advance: count=%d error=%v", len(objects.Items), err) } } func TestQualPolicyChange(t *testing.T) { c, _ := qualFixture(t, "OnBuild") publishQual(t, c, "point", strings.Repeat("1", 40)) object := getDelivery(t, c, "point") spec := object.Object["spec"].(map[string]any)["qualification"].(map[string]any) spec["mode"] = "ManualOnly" if _, err := c.client.Resource(projectDeliveryGVR).Namespace("delivery").Update(context.Background(), object, metav1.UpdateOptions{}); err != nil { t.Fatal(err) } stepQual(t, c) if len(readQual(t, c, "point").Attempts) != 0 { t.Fatal("ManualOnly reserved an automatic candidate") } object = getDelivery(t, c, "point") object.Object["spec"].(map[string]any)["qualification"].(map[string]any)["mode"] = "OnBuild" if _, err := c.client.Resource(projectDeliveryGVR).Namespace("delivery").Update(context.Background(), object, metav1.UpdateOptions{}); err != nil { t.Fatal(err) } stepQual(t, c) if readQual(t, c, "point").Attempts[0].Phase != "Reserved" { t.Fatal("OnBuild did not reconsider the waiting candidate") } } func qualFixture(t *testing.T, mode string) (*controller, *time.Time) { t.Helper() now := time.Date(2026, time.September, 11, 0, 0, 0, 0, time.UTC) object := pointDelivery(strings.Repeat("1", 40)) object.Object["spec"].(map[string]any)["qualification"] = map[string]any{ "workflowTemplateRef": map[string]any{"name": "qual-tests"}, "mode": mode, "interval": "168h", } return testController(t, object, &mutableRefs{}, &now), &now } func publishQual(t *testing.T, c *controller, name, revision string) string { t.Helper() object := getDelivery(t, c, name) d, err := decodeProjectDelivery(object) if err != nil { t.Fatal(err) } attempt := attemptStatus{Revision: revision, Phase: "Pending"} if err := c.advanceAttempt(context.Background(), &d, &attempt); err != nil { t.Fatal(err) } result := fmt.Sprintf(`{"sourceRevision":%q,"targets":[{"reference":%q}]}`, revision, "synthetic/image@sha256:"+strings.Repeat("a", 64)) setNamedWorkflowStatus(t, c, attempt.WorkflowName, "Succeeded", map[string]string{"delivery-result": result}) if err := c.advanceAttempt(context.Background(), &d, &attempt); err != nil { t.Fatal(err) } d.Status.Points = append(d.Status.Points, attempt) if err := c.updateStatus(context.Background(), object, &d); err != nil { t.Fatal(err) } return attempt.WorkflowName } func stepQual(t *testing.T, c *controller) { t.Helper() if err := c.qualifyAll(context.Background()); err != nil { t.Fatal(err) } } func readQual(t *testing.T, c *controller, name string) *qualStatus { t.Helper() d, err := decodeProjectDelivery(getDelivery(t, c, name)) if err != nil { t.Fatal(err) } return d.Status.Qualification } func requestQual(t *testing.T, c *controller, name, request string) { t.Helper() object := getDelivery(t, c, name) object.SetAnnotations(map[string]string{qualAnnotation: request}) if _, err := c.client.Resource(projectDeliveryGVR).Namespace("delivery").Update(context.Background(), object, metav1.UpdateOptions{}); err != nil { t.Fatal(err) } } func markQual(t *testing.T, c *controller, namespace, name, phase string, started *time.Time, outputs map[string]string) { t.Helper() object, err := c.client.Resource(workflowGVR).Namespace(namespace).Get(context.Background(), name, metav1.GetOptions{}) if err != nil { t.Fatal(err) } params := []any{} for name, value := range outputs { params = append(params, map[string]any{"name": name, "value": value}) } object.Object["status"] = map[string]any{"phase": phase, "startedAt": started.Format(time.RFC3339), "outputs": map[string]any{"parameters": params}} if _, err := c.client.Resource(workflowGVR).Namespace(namespace).UpdateStatus(context.Background(), object, metav1.UpdateOptions{}); err != nil { t.Fatal(err) } }