mesh-build-node: prove with the real npm that a trunk package is published once and a candidate's never (issue 1587, S20, ADR 0498 §5)
A registry of the test's own speaks npm's publish protocol and asks for the credential: a candidate's build publishes nothing, a trunk build publishes the packed file the build digested, and a second trunk build of the same package finds it and publishes nothing.
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
//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) {
|
||||
for _, tool := range []string{"node", "npm", "bwrap"} {
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user