//go:build integration package main import ( "context" "encoding/base64" "encoding/json" "io" "net/http" "net/http/httptest" "os/exec" "path/filepath" "strings" "sync" "testing" "git.novox.be/novox/mesh-sdk/go/forgekey" ) // npmRegistry is a registry of the test's own speaking npm's publish protocol: a PUT of the package's document with the // packed file attached, then GET of the document and the file; it asks for the account's credential. type npmRegistry struct { mu sync.Mutex versions map[string]map[string]any files map[string][]byte publishes int } func (r *npmRegistry) serve(t *testing.T) *httptest.Server { var srv *httptest.Server srv = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, q *http.Request) { r.mu.Lock() defer r.mu.Unlock() if u, p, ok := q.BasicAuth(); !ok || u != "builder" || p != "the write credential" { w.WriteHeader(http.StatusUnauthorized) return } doc := strings.TrimPrefix(q.URL.EscapedPath(), "/npm/") switch { case q.Method == http.MethodPut && (doc == "@novox%2fmesh-sdk" || doc == "@novox%2Fmesh-sdk"): var body struct { Versions map[string]map[string]any `json:"versions"` Attachments map[string]struct { Data string `json:"data"` } `json:"_attachments"` } raw, _ := io.ReadAll(q.Body) if err := json.Unmarshal(raw, &body); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } for v, meta := range body.Versions { for name, a := range body.Attachments { data, _ := base64.StdEncoding.DecodeString(a.Data) r.files[name] = data meta["dist"] = map[string]any{"tarball": srv.URL + "/files/" + name, "integrity": integrityOf(data)} } r.versions[v] = meta } r.publishes++ w.WriteHeader(http.StatusCreated) w.Write([]byte(`{"ok":true}`)) case q.Method == http.MethodGet && (doc == "@novox%2fmesh-sdk" || doc == "@novox%2Fmesh-sdk"): if len(r.versions) == 0 { http.NotFound(w, q) return } json.NewEncoder(w).Encode(map[string]any{"name": "@novox/mesh-sdk", "versions": r.versions}) case strings.HasPrefix(q.URL.Path, "/files/"): w.Write(r.files[strings.TrimPrefix(q.URL.Path, "/files/")]) default: http.NotFound(w, q) } })) t.Cleanup(srv.Close) return srv } // 1587-S20 (ADR 0498 ยง5): the real npm publishes a trunk build's package to a registry speaking npm's protocol, with // the module's credential, once: a second trunk build of the same package finds its file there and publishes nothing; // a candidate publishes nothing at all. func TestARealTrunkPackageIsPublishedOnce(t *testing.T) { needBubblewrap(t) for _, tool := range []string{"node", "npm"} { if _, err := exec.LookPath(tool); err != nil { t.Skipf("NOT RUN: no %s where this check runs; every build node has it", tool) } } m, _, work := newMachine(t) m.Run = forgekey.ExecRunner reg := &npmRegistry{versions: map[string]map[string]any{}, files: map[string][]byte{}} srv := reg.serve(t) m.Toolchain.Registry = func() (Registry, error) { return Registry{URL: srv.URL + "/npm/", Username: "builder", Password: "the write credential"}, nil } m.HTTP = srv.Client() files := map[string]string{} for n, c := range sdkFixture { files[n] = c } workspaceWith(t, work, files) dir := filepath.Join(work, "w1", "tree") if err := m.installDev(context.Background(), filepath.Join(work, "w1"), dir, "."); err != nil { t.Fatal(err) } args := map[string]any{"workspace": "w1", "module": ".", "artifact": "lib"} if out, err := m.build(context.Background(), args); err != nil || out.(BuildResult).Reference != "" || reg.publishes != 0 { t.Fatalf("a candidate: %+v %v, %d publishes", out, err, reg.publishes) } args["publish"] = true out, err := m.build(context.Background(), args) if err != nil || out.(BuildResult).Reference != "@novox/mesh-sdk@9.9.9" || reg.publishes != 1 { t.Fatalf("a trunk build: %+v %v, %d publishes", out, err, reg.publishes) } if got := digestOf(reg.files["@novox/mesh-sdk-9.9.9.tgz"]); got != out.(BuildResult).Digest { t.Errorf("the registry holds %s, the build packed %s (files %d)", got, out.(BuildResult).Digest, len(reg.files)) } if again, err := m.build(context.Background(), args); err != nil || again.(BuildResult).Reference != "@novox/mesh-sdk@9.9.9" || reg.publishes != 1 { t.Errorf("the same package again: %+v %v, %d publishes", again, err, reg.publishes) } }