Files
mesh-controller/internal/builderkeys/builderkeys_integration_test.go
T

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)
}
}