package delivery import ( "encoding/json" "reflect" "strings" "testing" "time" ) func composeAsk() StepAsk { return StepAsk{ID: "mh3k2x9qa1b2c5", Kind: AskCheck, Language: AskGo, Asker: "mesh-delivery-assigner", At: time.Date(2026, 10, 24, 3, 0, 0, 0, time.UTC), Compose: &ComposeJob{Fetch: map[string]any{"repository": "novox/mesh-sdk", "commit": askCommit}, Dir: "go", Package: "git.novox.be/novox/mesh-sdk/go/cmd/mesh-compose", Request: json.RawMessage(`{"v":1,"cases":[{"name":"change","manifests":{"demo":{"digest":"d1"}}}]}`), Reader: "mesh-sdk head"}} } // runAsk is the checker's run of a checking program built at a mesh-delivery head, over two heads (issue 1921, B0). func runAsk() StepAsk { in, err := RunInputOf("mesh-delivery-checker.inputs", []byte(`{"v":1}`)) if err != nil { panic(err) } return StepAsk{ID: "mh3k2x9qa1b2c6", Kind: AskCheck, Language: AskGo, Asker: "mesh-delivery-checker", At: time.Date(2026, 11, 6, 22, 0, 0, 0, time.UTC), Run: &RunJob{Program: RunProgram{Repository: "novox/mesh-delivery", Commit: askCommit, Dir: "modules/mesh-delivery-checker", Package: "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-checker/cmd/mesh-checking-program"}, Heads: []RunHead{{Repository: "novox/mesh-delivery", Commit: askCommit}, {Repository: "novox/mesh-sdk", Commit: askCommit}}, Input: in, Args: []string{"-verdict"}, Reader: "checking program of mesh-delivery head"}, Packages: PackageVersions{"git.novox.be/novox/mesh-sdk/go": askCommit}} } // 1622: a compose ask encodes and decodes with every field; 1921 (B0): a run ask too. func TestAComposeAskRoundTrips(t *testing.T) { for _, a := range []StepAsk{composeAsk(), runAsk()} { raw, err := EncodeStepAsk(a) if err != nil { t.Fatal(err) } got, err := DecodeStepAsk(raw) if err != nil { t.Fatal(err) } a.V = StepAskVersion if !reflect.DeepEqual(got, a) { t.Fatalf("round trip:\n got %+v\nwant %+v", got, a) } } } // 1622: a compose ask goes only on the Go seat's check stream, holds no other job beside it, fetches a full commit, // builds a package in a clean directory of the repository and carries a request object. func TestAComposeAskIsRefusedWhereItDoesNotBelong(t *testing.T) { for name, change := range map[string]func(*StepAsk){ "on the delivery stream": func(a *StepAsk) { a.Kind = AskDelivery }, "on the recheck stream": func(a *StepAsk) { a.Kind = AskRecheck }, "on the Node seat": func(a *StepAsk) { a.Language = AskNode }, "beside a check": func(a *StepAsk) { a.Check = checkAsk().Check }, "with a short commit": func(a *StepAsk) { a.Compose.Fetch["commit"] = "0123456" }, "in no directory": func(a *StepAsk) { a.Compose.Dir = "" }, "outside the repository": func(a *StepAsk) { a.Compose.Dir = "../x" }, "with no package": func(a *StepAsk) { a.Compose.Package = "" }, "with no request": func(a *StepAsk) { a.Compose.Request = nil }, "with a request not JSON": func(a *StepAsk) { a.Compose.Request = json.RawMessage(`[1]`) }, } { a := composeAsk() change(&a) if _, err := EncodeStepAsk(a); err == nil { t.Errorf("a compose ask %s is encoded", name) } } // 1921 (B0): a run ask likewise goes only on the Go seat's check stream, alone, builds a clean package at a full // commit, reads each named head once at a full commit, and names an input kept by digest within its bound. for name, change := range map[string]func(*StepAsk){ "on the delivery stream": func(a *StepAsk) { a.Kind = AskDelivery }, "on the Node seat": func(a *StepAsk) { a.Language = AskNode }, "beside a compose": func(a *StepAsk) { a.Compose = composeAsk().Compose }, "with a short commit": func(a *StepAsk) { a.Run.Program.Commit = "0123456" }, "outside the repository": func(a *StepAsk) { a.Run.Program.Dir = "../x" }, "with no package": func(a *StepAsk) { a.Run.Program.Package = "" }, "with no head": func(a *StepAsk) { a.Run.Heads = nil }, "with a head twice": func(a *StepAsk) { a.Run.Heads = append(a.Run.Heads, a.Run.Heads[0]) }, "with a head at a branch": func(a *StepAsk) { a.Run.Heads[1].Commit = "main" }, "with an input in no state": func(a *StepAsk) { a.Run.Input.State = "inputs" }, "with an input by no digest": func(a *StepAsk) { a.Run.Input.Digest = "d1" }, "with an input over its bound": func(a *StepAsk) { a.Run.Input.Size = RunInputBound + 1 }, "with an argument of the shell": func(a *StepAsk) { a.Run.Args = []string{"-x; rm"} }, } { a := runAsk() change(&a) if _, err := EncodeStepAsk(a); err == nil { t.Errorf("a run ask %s is encoded", name) } } // The asker refuses an input over its bound before it asks, naming its size; the build module refuses bytes that // are not the input named. if _, err := RunInputOf("mesh-delivery-checker.inputs", make([]byte, RunInputBound+1)); err == nil || !strings.Contains(err.Error(), "786433 bytes") { t.Errorf("an input over its bound: %v", err) } in := runAsk().Run.Input if err := in.Verify([]byte(`{"v":1}`)); err != nil { t.Errorf("the input named: %v", err) } if err := in.Verify([]byte(`{"v":2}`)); err == nil { t.Error("another input read as the one named") } } // 1622/AC3, 1921: a composer is kept under one key per repository, commit, directory, package, toolchain and asked versions, so two asks of // one head build it once on a node, and another head or toolchain builds its own. func TestAComposerIsKeptByWhatItWasBuiltFrom(t *testing.T) { tc := ToolchainUsed{Version: "go1.26.0", Recipe: "3"} k := ComposerKey("novox/mesh-sdk", askCommit, "go", "p", tc, nil) if k != ComposerKey("novox/mesh-sdk", askCommit, "go", "p", tc, nil) || !strings.HasPrefix(k, "composer-") || len(k) != len("composer-")+16 { t.Fatalf("key = %q", k) } for _, other := range []string{ ComposerKey("novox/mesh-sdk", strings.Repeat("1", 40), "go", "p", tc, nil), ComposerKey("novox/mesh-controller", askCommit, ".", "p", tc, nil), ComposerKey("novox/mesh-sdk", askCommit, "go", "p", ToolchainUsed{Version: "go1.26.1", Recipe: "3"}, nil), ComposerKey("novox/mesh-sdk", askCommit, "go", "p", tc, PackageVersions{"git.novox.be/novox/mesh-sdk/go": strings.Repeat("2", 40)}), } { if other == k { t.Errorf("two composers built from different inputs share the key %s", k) } } // 1921: the asked versions are in the key, in any order of naming; none, or an empty ask, gives the key without them. a, b := strings.Repeat("a", 40), strings.Repeat("b", 40) k1 := ComposerKey("r", askCommit, "go", "p", tc, PackageVersions{"m/x": a, "m/y": b}) if k1 != ComposerKey("r", askCommit, "go", "p", tc, PackageVersions{"m/y": b, "m/x": a}) { t.Error("the key depends on the order of the versions") } if k1 == ComposerKey("r", askCommit, "go", "p", tc, PackageVersions{"m/x": b, "m/y": a}) || k1 == ComposerKey("r", askCommit, "go", "p", tc, nil) { t.Error("other versions, or none, share the key") } if ComposerKey("r", askCommit, "go", "p", tc, nil) != ComposerKey("r", askCommit, "go", "p", tc, PackageVersions{}) { t.Error("an empty ask changed the key") } // 1921 (B0): a run's program is kept the same way, under a key of its own kind. p := RunProgram{Repository: "novox/mesh-delivery", Commit: askCommit, Dir: "modules/mesh-delivery-checker", Package: "p"} rk := RunKey(p, tc, nil) if !strings.HasPrefix(rk, "program-") || len(rk) != len("program-")+16 || rk != RunKey(p, tc, nil) { t.Errorf("run key = %q", rk) } q := p q.Commit = strings.Repeat("1", 40) if RunKey(q, tc, nil) == rk || RunKey(p, tc, PackageVersions{"m/x": a}) == rk { t.Error("two programs built from different inputs share the key") } }