139 lines
4.5 KiB
Go
139 lines
4.5 KiB
Go
//go:build integration
|
|
|
|
package builderkeys
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"slices"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
|
"github.com/nats-io/nats.go"
|
|
"github.com/nats-io/nats.go/jetstream"
|
|
"github.com/novox/mesh-controller/internal/broker"
|
|
"github.com/novox/mesh-controller/internal/testbus"
|
|
)
|
|
|
|
func aBus(t *testing.T) jetstream.JetStream {
|
|
t.Helper()
|
|
conn, err := nats.Connect(testbus.URL(t))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(conn.Close)
|
|
js, err := jetstream.New(conn)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return js
|
|
}
|
|
|
|
func put(t *testing.T, kv jetstream.KeyValue, key string, value []byte) {
|
|
t.Helper()
|
|
if _, err := kv.Put(context.Background(), key, value); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
// The controller reads the builder's keys, the indexer's values by commit and its manifests (A1, A2), and a key the
|
|
// builder writes wakes it with the key's module (A1's watch, no timer).
|
|
func TestTheControllerReadsTheBuildersKeysAndTheIndexersValues(t *testing.T) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
|
defer cancel()
|
|
js := aBus(t)
|
|
r := Reader{JS: js}
|
|
|
|
// Nothing there yet: no bucket is no key and no error; no value is ErrNoValue.
|
|
if k, err := r.Build(ctx, "x", fp); err != nil || k.Found {
|
|
t.Fatalf("no bucket: %+v %v", k, err)
|
|
}
|
|
if _, err := r.ModuleAt(ctx, "x", "abcdef1"); !errors.Is(err, ErrNoValue) {
|
|
t.Fatalf("no bucket: %v", err)
|
|
}
|
|
|
|
builds, err := js.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: delivery.BuilderBuildsBucket,
|
|
History: delivery.BuilderBuildsHistory})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
modules, err := js.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: delivery.ModulesBucket,
|
|
History: delivery.ModulesHistory})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
manifests, err := js.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: delivery.ManifestsBucket})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
woke := make(chan string, 8)
|
|
if err := Watch(ctx, js, func(m string) { woke <- m }); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
raw := `{"module":"x","version":"1"}`
|
|
digest := delivery.ManifestDigest([]byte(raw))
|
|
value, err := delivery.EncodeManifest(delivery.Manifest{Module: "x", JSON: raw})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
put(t, manifests, digest, value)
|
|
|
|
key, _ := delivery.BuilderBuildKey("x", fp)
|
|
building, _ := delivery.EncodeBuilderBuild(delivery.BuilderBuild{State: delivery.BuildBuilding, By: "ace"})
|
|
put(t, builds, key, building)
|
|
select {
|
|
case m := <-woke:
|
|
if m != "x" {
|
|
t.Fatalf("woken for %q", m)
|
|
}
|
|
case <-ctx.Done():
|
|
t.Fatal("the builder's write woke nothing")
|
|
}
|
|
done, _ := delivery.EncodeBuilderBuild(delivery.BuilderBuild{State: delivery.BuildDone, Manifest: digest,
|
|
Artifacts: []delivery.BuiltArtifact{{Name: "a", Kind: "bundle", Digest: "sha256:1",
|
|
Reference: "artifact-store://x/a/blobs/sha256:1"}}})
|
|
put(t, builds, key, done)
|
|
k, err := r.Build(ctx, "x", fp)
|
|
if err != nil || !k.Done() || len(k.Unreferenced()) != 0 || k.Build.Artifacts[0].Reference == "" {
|
|
t.Fatalf("the done key: %+v %v", k, err)
|
|
}
|
|
m, err := r.Manifest(ctx, k.Build.Manifest)
|
|
if err != nil || m.JSON != raw {
|
|
t.Fatalf("the manifest: %+v %v", m, err)
|
|
}
|
|
|
|
other := "src2:" + strings.Repeat("e", 64)
|
|
for _, v := range []struct{ commit, fp2 string }{{"1111111111", other}, {"2222222222", fp}, {"3333333333", other}} {
|
|
raw, err := delivery.EncodeModule(delivery.Module{FP: "src1:" + strings.Repeat("a", 64), FP2: v.fp2,
|
|
Manifest: digest, Repository: "novox/r", Commit: v.commit})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
put(t, modules, "x", raw)
|
|
}
|
|
at, err := r.ModuleAt(ctx, "x", "2222222222")
|
|
if err != nil || at.FP2 != fp {
|
|
t.Fatalf("FP2 at the plan's commit, overwritten by a later merge: %+v %v", at, err)
|
|
}
|
|
if now, err := r.ModuleAt(ctx, "x", ""); err != nil || now.Commit != "3333333333" {
|
|
t.Fatalf("the value now: %+v %v", now, err)
|
|
}
|
|
if _, err := r.ModuleAt(ctx, "x", "9999999999"); !errors.Is(err, ErrNoValue) {
|
|
t.Fatalf("a commit the indexer never wrote: %v", err)
|
|
}
|
|
}
|
|
|
|
// The controller's account reaches the builder's and the indexer's buckets: it is granted the JetStream API whole.
|
|
func TestTheControllersAccountReachesTheBuckets(t *testing.T) {
|
|
perms, err := broker.PermissionsFor(broker.Principal{Kind: broker.KindController, PasswordHash: "x"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !slices.Contains(perms.Publish, "$JS.API.>") || !slices.Contains(perms.Subscribe, "$JS.API.>") {
|
|
t.Fatalf("the controller is not granted the JetStream API: %v / %v", perms.Publish, perms.Subscribe)
|
|
}
|
|
}
|