package builder import ( "context" "os" "path/filepath" "strings" "testing" "github.com/novox/mesh-controller/internal/beside" "github.com/novox/mesh-controller/internal/catalogue" ) // **A build that needs the registry fails loudly while the build agent has none** (novox/hq ADR 0336 // §5, §6, issue 121). On a holder with neither the npm-package-registry binding nor the genesis // document, a `package` artifact, an image whose Dockerfile names .npmrc and a TypeScript artifact // with an `@novox/` dependency each fail before anything is built or published, naming the machine, // the unbound requirement and the module that provides it. A Go artifact, and an image that does not // name .npmrc, build there as before. func TestABuildNeedingTheRegistryIsRefusedOnAnAgentWithoutOne(t *testing.T) { none := Npmrc{Machine: "workstation"} cases := map[string]struct { manifest string files map[string]string held map[string]string }{ "a package": {aPackage, map[string]string{"package.json": `{"name":"@novox/mesh-sdk","version":"0.1.0"}`}, nil}, "an image naming .npmrc": {withBoth, map[string]string{ "Dockerfile": "FROM scratch\nCOPY .npmrc /root/.npmrc\n", "files/x": "y"}, nil}, "a TypeScript bundle depending on the SDK": {aBundle, map[string]string{ "index.ts": "console.log(1)", "package.json": `{"dependencies":{"@novox/mesh-sdk":"^0.1.0"}}`}, map[string]string{"mesh-tools/build": "registry.invalid/mesh-tools/build@sha256:" + strings.Repeat("b", 64)}}, } for name, c := range cases { t.Run(name, func(t *testing.T) { r, workspace := aRepository(t, c.manifest, c.files) var said []string _, err := Build(context.Background(), compiling{r}.run, r, "https://forge.invalid/m.git", "", "", workspace, c.held, none, GitCredential{}, func(step, message string) { said = append(said, step+": "+message) }) if err == nil { t.Fatal("built with no package registry") } for _, want := range []string{"workstation", "npm-package-registry", "unbound", "gitea"} { if !strings.Contains(err.Error(), want) { t.Errorf("the refusal does not say %q: %v", want, err) } } for _, line := range r.ran { if strings.HasPrefix(line, "docker build") || strings.HasPrefix(line, "docker run") || strings.HasPrefix(line, "docker pull") { t.Errorf("something was built after the refusal: %s", line) } } if len(r.images) != 0 || len(r.archives) != 0 { t.Errorf("something was published after the refusal: %v %v", r.images, r.archives) } if !strings.Contains(strings.Join(said, "\n"), "REFUSED") { t.Errorf("the build's log does not say it was refused:\n%s", strings.Join(said, "\n")) } }) } // A Go artifact builds on the same agent. files := aSharedRepository("one", "1") manifest := `{"module":"ctl","version":"1","build":{"artifacts":[ {"name":"controller","kind":"bundle","language":"go","system":"arch","from":"cmd/ctl","binary":"ctl"}]}, "resources":[{"id":"controller","type":"process","name":"ctl","artifact":"controller","run":["./ctl"]}]}` r := &recorded{contents: map[string]string{ManifestName: manifest}} for k, v := range files { r.contents[k] = v } held := map[string]string{"mesh-tools-go/build": "registry.invalid/mesh-tools-go/build@sha256:" + strings.Repeat("b", 64)} if _, err := Build(context.Background(), onTrunk{recorded: r}.run, r, "https://forge.invalid/ctl.git", "", "", t.TempDir(), held, none, GitCredential{}, nil); err != nil { t.Fatalf("a Go artifact did not build on an agent without the registry: %v", err) } // And an image that does not name .npmrc. r, workspace := aRepository(t, withBoth, map[string]string{"Dockerfile": "FROM scratch\nCOPY . .", "files/x": "y"}) if _, err := Build(context.Background(), r.run, r, "https://forge.invalid/meshboard.git", "", "", workspace, nil, none, GitCredential{}, nil); err != nil { t.Fatalf("an image not naming .npmrc did not build on an agent without the registry: %v", err) } } // **The build's log says which registry it resolved from** (novox/hq ADR 0336 §4). func TestABuildSaysWhichRegistryItResolvedFrom(t *testing.T) { for _, from := range []string{FromBinding, FromGenesis} { r, workspace := aRepository(t, aPackage, map[string]string{ "package.json": `{"name":"@novox/mesh-sdk","version":"0.1.0"}`, }) n := Npmrc{Scope: "@novox", Registry: "http://127.0.0.1:3000/api/packages/novox/npm/", Username: "mesh-builder", Password: "p", From: from, Machine: "anchor"} var said []string if _, err := Build(context.Background(), r.run, r, "https://forge.invalid/mesh-sdk.git", "", "", workspace, nil, n, GitCredential{}, func(step, message string) { said = append(said, step+": "+message) }); err != nil { t.Fatalf("%s: %v", from, err) } if !strings.Contains(strings.Join(said, "\n"), "from "+from) { t.Errorf("the log does not say the registry came from %s:\n%s", from, strings.Join(said, "\n")) } } } // The refusal names the module that provides the registry; it is the catalogue's provider. func TestTheRegistryABuildIsRefusedForNamesItsProvider(t *testing.T) { raw, err := os.ReadFile(filepath.Join(beside.Catalogue(t), RegistryProvider, "module.json")) if err != nil { t.Fatal(err) } forge, err := catalogue.ParseManifest(raw) if err != nil { t.Fatal(err) } provides := false for _, p := range forge.Provides { provides = provides || p.Name == RegistryRequirement } if !provides { t.Fatalf("%s does not provide %s, which a refused build names it for", RegistryProvider, RegistryRequirement) } }