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.
122 lines
4.3 KiB
Go
122 lines
4.3 KiB
Go
//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)
|
|
}
|
|
}
|