package main import ( "context" "errors" "reflect" "testing" "time" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) func TestTemplateDrain(t *testing.T) { for _, purpose := range []string{"build", "qualification"} { for _, outcome := range []string{"Succeeded", "Failed", "Error"} { t.Run(purpose+"/"+outcome, func(t *testing.T) { ctx := context.Background() now := time.Date(2026, 10, 2, 0, 0, 0, 0, time.UTC) c := testController(t, continuousDelivery(), &mutableRefs{}, &now) d, err := decodeProjectDelivery(continuousDelivery()) if err != nil { t.Fatal(err) } if err := c.ensureWorkflow(ctx, &d, "first", "nuthatch-delivery", purpose, nil, nil); err != nil { t.Fatal(err) } old := boundTemplate(t, c, "first").DeepCopy() c.config.templateQuiet = time.Minute changeImage(t, c, "nuthatch-delivery", "example.invalid/builder:v2") if _, err := c.readyTemplate(ctx, "delivery", "nuthatch-delivery"); !errors.Is(err, templatePending) { t.Fatalf("first change = %v", err) } now = now.Add(30 * time.Second) changeImage(t, c, "nuthatch-delivery", "example.invalid/builder:v3") if _, err := c.readyTemplate(ctx, "delivery", "nuthatch-delivery"); !errors.Is(err, templatePending) { t.Fatalf("latest change = %v", err) } now = now.Add(time.Minute) if _, err := c.readyTemplate(ctx, "delivery", "nuthatch-delivery"); !errors.Is(err, templatePending) { t.Fatalf("queued drain = %v", err) } setNamedWorkflowStatus(t, c, "first", "Running", nil) if _, err := c.readyTemplate(ctx, "delivery", "nuthatch-delivery"); !errors.Is(err, templatePending) { t.Fatalf("running drain = %v", err) } if err := c.ensureWorkflow(ctx, &d, "first", "nuthatch-delivery", purpose, nil, nil); err != nil { t.Fatal(err) } if err := c.ensureWorkflow(ctx, &d, "second", "nuthatch-delivery", purpose, nil, nil); !errors.Is(err, templatePending) { t.Fatalf("new admission = %v", err) } setNamedWorkflowStatus(t, c, "first", outcome, nil) if err := c.ensureWorkflow(ctx, &d, "second", "nuthatch-delivery", purpose, nil, nil); err != nil { t.Fatal(err) } latest := boundTemplate(t, c, "second") templates, _, _ := unstructured.NestedSlice(latest.Object, "spec", "templates") if image := templates[0].(map[string]any)["container"].(map[string]any)["image"]; image != "example.invalid/builder:v3" { t.Fatalf("published image = %v", image) } if !reflect.DeepEqual(old.Object["spec"], boundTemplate(t, c, "first").Object["spec"]) { t.Fatal("terminal Workflow lost its captured specification") } resource := c.client.Resource(templateGVR).Namespace("delivery") alias, err := resource.Get(ctx, "nuthatch-delivery", metav1.GetOptions{}) if err != nil { t.Fatal(err) } if alias.GetAnnotations()[publishedKey] != latest.GetName() { t.Fatal("published selection did not retain the latest revision") } }) } } } func changeImage(t *testing.T, c *controller, name, image string) { t.Helper() resource := c.client.Resource(templateGVR).Namespace("delivery") object, err := resource.Get(context.Background(), name, metav1.GetOptions{}) if err != nil { t.Fatal(err) } object.SetGeneration(object.GetGeneration() + 1) if err := unstructured.SetNestedSlice(object.Object, []any{map[string]any{"name": "build", "container": map[string]any{"image": image}}}, "spec", "templates"); err != nil { t.Fatal(err) } if _, err := resource.Update(context.Background(), object, metav1.UpdateOptions{}); err != nil { t.Fatal(err) } }