Files
mesh-catalog/modules/gitea/cmd/package-registry/seats_test.go
T
jschoubben 4492bc3013 CAT-G2: the packages token is renewed on the forge's refusal, said as a finding, never on a timer
The Go bundle raises packages-token-refused in the module's findings state when the forge
answers 401 to a write; the watcher mints afresh on that key's event and clears it (lead's
ruling on A3n, map-onto-how-the-mesh-is-built: nothing waits on a timer).

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 14:47:26 +01:00

198 lines
8.3 KiB
Go

package main
import (
"archive/tar"
"archive/zip"
"bytes"
"compress/gzip"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"sync"
"testing"
"time"
"git.novox.be/novox/mesh-sdk/go/health"
"gitea/internal/forgehttp"
"gitea/internal/packageseats"
)
func tarball(t *testing.T, manifest string) []byte {
var b bytes.Buffer
gz := gzip.NewWriter(&b)
tw := tar.NewWriter(gz)
_ = tw.WriteHeader(&tar.Header{Name: "package/package.json", Mode: 0o644, Size: int64(len(manifest))})
_, _ = tw.Write([]byte(manifest))
_ = tw.Close()
_ = gz.Close()
return b.Bytes()
}
func moduleZip(t *testing.T, prefix string) []byte {
var b bytes.Buffer
z := zip.NewWriter(&b)
w, _ := z.Create(prefix + "/go.mod")
_, _ = w.Write([]byte("module x\n"))
_ = z.Close()
return b.Bytes()
}
func sha(b []byte) string { s := sha256.Sum256(b); return hex.EncodeToString(s[:]) }
// registry is the forge's package registry: what was uploaded, by path, and what the files API answers.
type registry struct {
mu sync.Mutex
uploaded map[string][]byte // "npm <name>" or "go"
held map[string][]byte // "<type>/<name>/<version>" -> file
deleted []string
}
func (g *registry) handle(w http.ResponseWriter, r *http.Request) {
g.mu.Lock()
defer g.mu.Unlock()
body, _ := io.ReadAll(r.Body)
p := r.URL.EscapedPath()
// Reads come as the tools' account (read:package), writes as the packages account (write:package), never the admin.
want := "token reader"
if r.Method != http.MethodGet {
want = "token writer"
}
if got := r.Header.Get("Authorization"); got == "token stale" {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
} else if got != want {
http.Error(w, r.Method+" "+p+" came as "+got+", not "+want, http.StatusForbidden)
return
}
switch {
case r.Method == http.MethodPut && p == "/api/packages/acme/go/upload":
if r.Header.Get("Content-Type") != "application/zip" {
http.Error(w, "not a zip", 400)
return
}
g.uploaded["go"] = body
w.WriteHeader(http.StatusCreated)
case r.Method == http.MethodPut && strings.HasPrefix(p, "/api/packages/acme/npm/"):
g.uploaded["npm "+strings.TrimPrefix(p, "/api/packages/acme/npm/")] = body
w.WriteHeader(http.StatusCreated)
case strings.HasPrefix(p, "/api/v1/packages/acme/") && strings.HasSuffix(p, "/files"):
f, ok := g.held[strings.TrimSuffix(strings.TrimPrefix(r.URL.Path, "/api/v1/packages/acme/"), "/files")]
if !ok {
http.NotFound(w, r)
return
}
_ = json.NewEncoder(w).Encode([]map[string]any{{"sha256": sha(f), "size": len(f)}})
case r.Method == http.MethodGet && p == "/api/v1/packages/acme":
var out []map[string]any
for k := range g.held {
parts := strings.Split(k, "/")
if parts[0] == r.URL.Query().Get("type") {
out = append(out, map[string]any{"name": strings.Join(parts[1:len(parts)-1], "/"), "version": parts[len(parts)-1], "created_at": time.Unix(int64(len(parts[len(parts)-1])), 0)})
}
}
_ = json.NewEncoder(w).Encode(out)
case r.Method == http.MethodDelete:
g.deleted = append(g.deleted, strings.TrimPrefix(p, "/api/v1/packages/acme/"))
w.WriteHeader(http.StatusNoContent)
default:
http.Error(w, "unexpected "+r.Method+" "+p, 500)
}
}
// **The package seats store into the forge's package registry** (design 51 *Publishing*): an npm package goes up as
// npm's publish sends it, its package.json from the tarball and its tag as its dist-tag; a Go module's zip goes up to
// the Go upload, its module path read from the zip; held, versions and remove read and delete through the forge's
// package API, a Go version with Go's leading v.
func TestThePackageSeatsStoreIntoTheForge(t *testing.T) {
g := &registry{uploaded: map[string][]byte{}, held: map[string][]byte{}}
srv := httptest.NewServer(http.HandlerFunc(g.handle))
defer srv.Close()
dir := t.TempDir()
kept := func(name, token, line string) string {
file := filepath.Join(dir, name)
_ = os.WriteFile(file, []byte(token+"\n"), 0o600)
_ = os.WriteFile(file+".scopes", []byte(line+"\n"), 0o600)
return file
}
reader := forgehttp.Client{URL: srv.URL, TokenFile: kept("token", "reader", "read:package,read:user for mesh-tools"), Account: "mesh-tools"}
writer := forgehttp.Client{URL: srv.URL, TokenFile: kept("packages-token", "writer", "write:package for mesh-packages"), Account: "mesh-packages"}
forge := &Forge{Client: reader, Writer: &writer, Owner: "acme"}
if _, err := (seatStore{forge: func() (*Forge, error) { return &Forge{Client: reader, Owner: "acme"}, nil }}).Put(packageseats.GoSeat, packageseats.Publish{}, nil); err == nil ||
!strings.Contains(err.Error(), "write side") {
t.Errorf("a forge handed no write side wrote: %v", err)
}
s := seatStore{forge: func() (*Forge, error) { return forge, nil }}
// The forge refuses the write side's token: the finding the TypeScript bundle renews on is raised under its key,
// and the next write the forge takes clears it (no timer looks at the token).
var refused []bool
forge.Refused = func(raised bool) { refused = append(refused, raised) }
if k := health.FindingKey(health.ScopeSeat, packageseats.Self, PackagesTokenRefused+".write"); k != PackagesTokenRefusedKey {
t.Errorf("the refusal's key is %s, the watcher watches %s", k, PackagesTokenRefusedKey)
}
_ = os.WriteFile(writer.TokenFile, []byte("stale\n"), 0o600)
if _, err := s.Put(packageseats.GoSeat, packageseats.Publish{Version: "0.1.15"}, moduleZip(t, "git.example/acme/sdk@v0.1.15")); err == nil ||
len(refused) != 1 || !refused[0] {
t.Errorf("a write the forge refused with 401 raised no finding: %v %v", err, refused)
}
_ = os.WriteFile(writer.TokenFile, []byte("writer\n"), 0o600)
tgz := tarball(t, `{"name":"@acme/contracts","version":"0.1.15-feat-x.2","main":"index.js"}`)
p := packageseats.Publish{Name: "@acme/contracts", Version: "0.1.15-feat-x.2", Tag: "feat-x", Kind: packageseats.KindPackage, Digest: "sha256:" + sha(tgz)}
if module, err := s.Put(packageseats.NpmSeat, p, tgz); err != nil || module != "" {
t.Fatalf("npm put: %q, %v", module, err)
}
var doc struct {
DistTags map[string]string `json:"dist-tags"`
Versions map[string]map[string]any
Attachments map[string]struct{ Data string } `json:"_attachments"`
}
if err := json.Unmarshal(g.uploaded["npm @acme%2Fcontracts"], &doc); err != nil {
t.Fatalf("npm's publish body %v: %s", err, g.uploaded)
}
att := doc.Attachments["contracts-0.1.15-feat-x.2.tgz"].Data
if doc.DistTags["feat-x"] != "0.1.15-feat-x.2" || doc.DistTags["latest"] != "" || doc.Versions["0.1.15-feat-x.2"]["main"] != "index.js" ||
att != base64.StdEncoding.EncodeToString(tgz) {
t.Errorf("npm's publish body: %+v", doc)
}
p.Name = "@acme/other"
if _, err := s.Put(packageseats.NpmSeat, p, tgz); err == nil || !strings.Contains(err.Error(), "not @acme/other") {
t.Errorf("a tarball of another package was uploaded: %v", err)
}
zipped := moduleZip(t, "git.example/acme/sdk@v0.1.15")
gp := packageseats.Publish{Name: "git.example/acme/sdk", Version: "0.1.15", Kind: packageseats.KindPackage, Digest: "sha256:" + sha(zipped)}
if module, err := s.Put(packageseats.GoSeat, gp, zipped); err != nil || module != "git.example/acme/sdk" || !bytes.Equal(g.uploaded["go"], zipped) {
t.Fatalf("go put: %q, %v", module, err)
}
gp.Version = "0.1.16"
if _, err := s.Put(packageseats.GoSeat, gp, zipped); err == nil {
t.Error("a zip of another version was uploaded")
}
g.held["go/git.example/acme/sdk/v0.1.15"] = zipped
if d, ok, err := s.Held(packageseats.GoSeat, "git.example/acme/sdk", "0.1.15"); err != nil || !ok || d != "sha256:"+sha(zipped) {
t.Errorf("held: %s %v %v", d, ok, err)
}
if _, ok, err := s.Held(packageseats.NpmSeat, "x", "0.1.0"); ok || err != nil {
t.Errorf("a version the forge does not hold: %v %v", ok, err)
}
g.held["go/git.example/acme/sdk/v0.1.9"] = zipped
if v, err := s.Versions(packageseats.GoSeat, "git.example/acme/sdk"); err != nil || len(v) != 2 || v[0].Version != "0.1.15" {
t.Errorf("versions: %+v %v", v, err)
}
if err := s.Remove(packageseats.GoSeat, "git.example/acme/sdk", "0.1.15-feat.1"); err != nil || len(g.deleted) != 1 ||
g.deleted[0] != "go/git.example%2Facme%2Fsdk/v0.1.15-feat.1" {
t.Errorf("remove: %v %v", err, g.deleted)
}
if len(refused) < 2 || refused[len(refused)-1] {
t.Errorf("a write the forge took after the refusal did not clear it: %v", refused)
}
}