250 lines
9.4 KiB
Go
250 lines
9.4 KiB
Go
package packageseats
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
stdio "git.novox.be/novox/mesh-sdk/go"
|
|
"git.novox.be/novox/mesh-sdk/go/parts"
|
|
)
|
|
|
|
type memObjects map[string][]byte
|
|
|
|
func (m memObjects) Put(n string, d []byte) error { m[n] = append([]byte(nil), d...); return nil }
|
|
func (m memObjects) Get(n string) ([]byte, error) {
|
|
if d, ok := m[n]; ok {
|
|
return d, nil
|
|
}
|
|
return nil, errors.New("no object " + n)
|
|
}
|
|
func (m memObjects) Delete(n string) error { delete(m, n); return nil }
|
|
func (m memObjects) Used() (uint64, error) {
|
|
var u uint64
|
|
for _, d := range m {
|
|
u += uint64(len(d))
|
|
}
|
|
return u, nil
|
|
}
|
|
|
|
type memTransfers map[string]Transfer
|
|
|
|
func (m memTransfers) Get(id string) (Transfer, bool, error) { t, ok := m[id]; return t, ok, nil }
|
|
func (m memTransfers) Put(t Transfer) error { m[t.ID] = t; return nil }
|
|
func (m memTransfers) Delete(id string) error { delete(m, id); return nil }
|
|
func (m memTransfers) List() ([]Transfer, error) {
|
|
var out []Transfer
|
|
for _, t := range m {
|
|
out = append(out, t)
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
type builder struct{ ok bool }
|
|
|
|
func (b builder) Recorded(_, _, _ string) (bool, string, error) { return b.ok, "not numbered", nil }
|
|
|
|
// forge is the forge's package registry: seat -> name@version -> file.
|
|
type forge struct {
|
|
files map[string]map[string][]byte
|
|
removed []string
|
|
}
|
|
|
|
func (f *forge) Held(seat, name, version string) (string, bool, error) {
|
|
b, ok := f.files[seat][name+"@"+version]
|
|
if !ok {
|
|
return "", false, nil
|
|
}
|
|
return digest(b), true, nil
|
|
}
|
|
func (f *forge) Put(seat string, p Publish, file []byte) (string, error) {
|
|
if f.files[seat] == nil {
|
|
f.files[seat] = map[string][]byte{}
|
|
}
|
|
f.files[seat][p.Name+"@"+p.Version] = file
|
|
if seat == GoSeat {
|
|
return "git.example/" + p.Name, nil
|
|
}
|
|
return "", nil
|
|
}
|
|
func (f *forge) Versions(seat, name string) ([]Version, error) {
|
|
var out []Version
|
|
for k, b := range f.files[seat] {
|
|
if n, v, _ := strings.Cut(k, "@"); n == name {
|
|
out = append(out, Version{Version: v, Digest: digest(b)})
|
|
}
|
|
}
|
|
return out, nil
|
|
}
|
|
func (f *forge) Remove(seat, name, version string) error {
|
|
delete(f.files[seat], name+"@"+version)
|
|
f.removed = append(f.removed, seat+" "+name+"@"+version)
|
|
return nil
|
|
}
|
|
|
|
func digest(b []byte) string { s := sha256.Sum256(b); return "sha256:" + hex.EncodeToString(s[:]) }
|
|
|
|
type said struct {
|
|
seat, event string
|
|
o Outcome
|
|
}
|
|
|
|
type bed struct {
|
|
h *Holder
|
|
forge *forge
|
|
said []said
|
|
findings map[string]bool
|
|
}
|
|
|
|
func newBed(numbered bool) *bed {
|
|
b := &bed{forge: &forge{files: map[string]map[string][]byte{}}, findings: map[string]bool{}}
|
|
b.h = &Holder{Callers: DefaultCallers(), Objects: memObjects{}, Transfers: memTransfers{}, Builder: builder{ok: numbered},
|
|
Store: b.forge, Now: func() time.Time { return time.Unix(1e9, 0) },
|
|
Say: func(seat, event string, o Outcome) error { b.said = append(b.said, said{seat, event, o}); return nil },
|
|
Finding: func(kind, subject, _ string, raised bool) { b.findings[kind+" "+subject] = raised }}
|
|
return b
|
|
}
|
|
|
|
func pub(name, version string, file []byte) Publish {
|
|
return Publish{Transfer: "t-" + name, Name: name, Version: version, Kind: KindPackage, Digest: digest(file),
|
|
Parts: []parts.Want{{Digest: digest(file), Size: int64(len(file))}}}
|
|
}
|
|
|
|
const goBuild, nodeBuild = "ace.mesh-build-go", "ace.mesh-build-node"
|
|
|
|
// **The forge module holds both package seats** (design 51 *Publishing*, *Testing* for the holder): a package's one
|
|
// file comes in parts, is checked against the builder's numbering and its digest, and is uploaded with the forge's own
|
|
// write side; the outcome is said on the seat's events.
|
|
func TestTheForgeHoldsBothPackageSeats(t *testing.T) {
|
|
file := []byte("a package's tarball")
|
|
t.Run("a publish in parts is uploaded and said published", func(t *testing.T) {
|
|
for _, seat := range []string{NpmSeat, GoSeat} {
|
|
b := newBed(true)
|
|
a, err := b.h.Publish(seat, goBuild, pub("mesh-sdk", "0.1.15", file))
|
|
if err != nil || len(a.Lacks) != 1 || a.Transfer != "t-mesh-sdk" {
|
|
t.Fatalf("%s: publish answered %+v, %v", seat, a, err)
|
|
}
|
|
a, err = b.h.PublishPart(seat, goBuild, "t-mesh-sdk", digest(file), 0, true, file)
|
|
if err != nil || !a.Ended {
|
|
t.Fatalf("%s: the last chunk answered %+v, %v", seat, a, err)
|
|
}
|
|
if len(b.said) != 1 || b.said[0].event != EventPublished || b.said[0].o.Reference != "mesh-sdk@0.1.15" {
|
|
t.Fatalf("%s: said %+v", seat, b.said)
|
|
}
|
|
if (seat == GoSeat) != (b.said[0].o.Module != "") {
|
|
t.Errorf("%s: the Go module path said is %q", seat, b.said[0].o.Module)
|
|
}
|
|
if len(b.h.Objects.(memObjects)) != 0 || len(b.h.Transfers.(memTransfers)) != 0 {
|
|
t.Errorf("%s: the transfer's chunks or key stayed after its end", seat)
|
|
}
|
|
if v, _ := b.h.Versions(seat, "g14.anyone", "mesh-sdk"); len(v) != 1 || v[0].Digest != digest(file) {
|
|
t.Errorf("%s: versions answered %+v", seat, v)
|
|
}
|
|
// A rebuild of the published version is held: nothing uploaded, published said again.
|
|
if a, err := b.h.Publish(seat, goBuild, pub("mesh-sdk", "0.1.15", file)); err != nil || !a.Ended || len(a.Lacks) != 0 {
|
|
t.Errorf("%s: a held version answered %+v, %v", seat, a, err)
|
|
}
|
|
}
|
|
})
|
|
t.Run("refusals", func(t *testing.T) {
|
|
for _, c := range []struct {
|
|
name string
|
|
do func(b *bed) error
|
|
want string
|
|
}{
|
|
{"a caller not on publish's list", func(b *bed) error {
|
|
_, err := b.h.Publish(NpmSeat, "ace.claude-code", pub("x", "0.1.0", file))
|
|
return err
|
|
}, "not on the holder's caller list"},
|
|
{"an unproven caller", func(b *bed) error {
|
|
_, err := b.h.Publish(NpmSeat, stdio.Unproven, pub("x", "0.1.0", file))
|
|
return err
|
|
}, "no caller the tool runner proved"},
|
|
{"a seat the forge does not hold", func(b *bed) error {
|
|
_, err := b.h.Publish("cargo-package-registry", goBuild, pub("x", "0.1.0", file))
|
|
return err
|
|
}, "holds no seat"},
|
|
{"a package in two files", func(b *bed) error {
|
|
p := pub("x", "0.1.0", file)
|
|
p.Parts = append(p.Parts, parts.Want{Digest: digest([]byte("b")), Size: 1})
|
|
_, err := b.h.Publish(NpmSeat, goBuild, p)
|
|
return err
|
|
}, "one file"},
|
|
{"a part from another module than its opener", func(b *bed) error {
|
|
if _, err := b.h.Publish(NpmSeat, nodeBuild, pub("x", "0.1.0", file)); err != nil {
|
|
return err
|
|
}
|
|
_, err := b.h.PublishPart(NpmSeat, goBuild, "t-x", digest(file), 0, true, file)
|
|
return err
|
|
}, "opened"},
|
|
{"a transfer of the other seat", func(b *bed) error {
|
|
if _, err := b.h.Publish(NpmSeat, nodeBuild, pub("x", "0.1.0", file)); err != nil {
|
|
return err
|
|
}
|
|
_, err := b.h.PublishPart(GoSeat, nodeBuild, "t-x", digest(file), 0, true, file)
|
|
return err
|
|
}, "not open"},
|
|
{"remove by a build module", func(b *bed) error { _, err := b.h.Remove(GoSeat, goBuild, "x", "0.1.1-feat.1", "tag-gone"); return err }, "not on the holder's caller list"},
|
|
{"remove of a version with no tag", func(b *bed) error {
|
|
_, err := b.h.Remove(GoSeat, "novox.mesh-delivery-builder", "x", "0.1.1", "tag-gone")
|
|
return err
|
|
}, "has no tag"},
|
|
} {
|
|
b := newBed(true)
|
|
if err := c.do(b); err == nil || !strings.Contains(err.Error(), c.want) {
|
|
t.Errorf("%s: %v, want %q", c.name, err, c.want)
|
|
}
|
|
}
|
|
})
|
|
t.Run("a refused publish is said refused and raises the finding; nothing is uploaded", func(t *testing.T) {
|
|
for _, c := range []struct {
|
|
name string
|
|
numbered bool
|
|
data []byte
|
|
want string
|
|
}{{"the builder did not number it", false, file, "did not number"}} {
|
|
b := newBed(c.numbered)
|
|
if _, err := b.h.Publish(NpmSeat, nodeBuild, pub("x", "0.1.0", file)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_, _ = b.h.PublishPart(NpmSeat, nodeBuild, "t-x", digest(file), 0, true, c.data)
|
|
if len(b.said) != 1 || b.said[0].event != EventRefused || !strings.Contains(b.said[0].o.Reason, c.want) {
|
|
t.Errorf("%s: said %+v", c.name, b.said)
|
|
}
|
|
if !b.findings["publish-refused x@0.1.0"] || len(b.forge.files[NpmSeat]) != 0 {
|
|
t.Errorf("%s: finding %v, uploaded %v", c.name, b.findings, b.forge.files)
|
|
}
|
|
}
|
|
// A part whose bytes do not match its digest is refused to its sender, to send again; nothing is said yet.
|
|
b := newBed(true)
|
|
if _, err := b.h.Publish(NpmSeat, nodeBuild, pub("x", "0.1.0", file)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := b.h.PublishPart(NpmSeat, nodeBuild, "t-x", digest(file), 0, true, []byte("other bytes")); err == nil ||
|
|
!strings.Contains(err.Error(), "send the part again") || len(b.said) != 0 || len(b.forge.files[NpmSeat]) != 0 {
|
|
t.Errorf("a part with other bytes: %v, said %+v", err, b.said)
|
|
}
|
|
// The same version published with other content is refused, never uploaded over.
|
|
b = newBed(true)
|
|
b.forge.files[NpmSeat] = map[string][]byte{"x@0.1.0": []byte("first")}
|
|
if _, err := b.h.Publish(NpmSeat, nodeBuild, pub("x", "0.1.0", file)); err != nil || len(b.said) != 1 || b.said[0].event != EventRefused {
|
|
t.Errorf("a published version with other content: %v, said %+v", err, b.said)
|
|
}
|
|
})
|
|
t.Run("the builder removes a tagged version; one already gone is said removed all the same", func(t *testing.T) {
|
|
b := newBed(true)
|
|
b.forge.files[GoSeat] = map[string][]byte{"x@0.1.1-feat.1": file}
|
|
for range 2 {
|
|
if _, err := b.h.Remove(GoSeat, "novox.mesh-delivery-builder", "x", "0.1.1-feat.1", "tag-gone"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
if len(b.forge.removed) != 1 || len(b.said) != 2 || b.said[0].event != EventRemoved || b.said[0].o.Digest != digest(file) {
|
|
t.Errorf("removed %v, said %+v", b.forge.removed, b.said)
|
|
}
|
|
})
|
|
}
|