//go:build integration package main import ( "context" "encoding/json" "errors" "fmt" sdkdelivery "git.novox.be/novox/mesh-sdk/go/delivery" "testing" "time" "github.com/nats-io/nats.go/jetstream" "git.novox.be/novox/mesh-sdk/go/catalogue" "github.com/novox/mesh-controller/internal/broker" "github.com/novox/mesh-controller/internal/inventory" "github.com/novox/mesh-controller/internal/link" "github.com/novox/mesh-controller/internal/testbus" ) // 1146/AC4: a new module from a repository nothing registered is built from, whole against a real bus and the store // (novox/hq ADR 0432, issue 1146; the warrant is read as for a move, ADR 0420 ยง7, issue 1025): mesh-delivery's word that it // merged a pull request is published on the subject the controller consumes and kept in its bucket; the router's // record of the ask is read back from the router's bucket by a direct get; and the build of a module from the // repository that merge was made in is registered from there only when that record holds the operator's Approve of // the merged head. Store-backed: skips without one. func TestANewModuleFromANewRepositoryIsRegisteredOnlyOnTheOperatorsApproveOnTheBus(t *testing.T) { for _, c := range []struct { what string answer bool // the router holds the operator's Approve of the merged heads moved bool }{ {what: "the router holds the operator's Approve", answer: true, moved: true}, {what: "the router holds no record of the ask"}, } { t.Run(c.what, func(t *testing.T) { open := theCatalogue(t) ctx := t.Context() inv := open.inventory // The module declaring the router's records, as the controller finds them. if err := inv.RegisterModule(ctx, catalogue.Manifest{Module: "operator-channel", Version: "1", DefinesSeats: []catalogue.SeatDeclaration{{Name: broker.AsksSeat, Records: []string{"asks"}}}}, inventory.Source{Repository: "novox/mesh-catalog", Seat: "git", Path: "modules/operator-channel", BuiltFrom: "c0ffee"}); err != nil { t.Fatal(err) } js, err := broker.Dial(testbus.URL(t)) if err != nil { t.Fatal(err) } t.Cleanup(js.Close) if err := broker.AssertMeshStreams(js); err != nil { t.Fatal(err) } if err := js.EnsureControllerBuckets(); err != nil { t.Fatal(err) } api, err := jetstream.New(js.Conn()) if err != nil { t.Fatal(err) } records, err := api.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: broker.BucketName("operator-channel", "asks")}) if err != nil { t.Fatal(err) } a := aDeliveryMergeOf(t, "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1", map[string]string{"novox/mesh-delivery@a1a1a1a1a1a1": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1"}) if c.answer { record, _ := json.Marshal(map[string]any{"state": "closed", "ask": a.ask, "warrant": a.warrant}) if _, err := records.Put(ctx, sdkdelivery.TellerModule+"."+a.merge.AskID, record); err != nil { t.Fatal(err) } } server := link.ConnectNats(js, nil, noListener{}) if err := server.KeepsDeliveryMerges(keepingDeliveryMerges{js: server.JetStream, catalog: theDeliveryClaims}); err != nil { t.Fatal(err) } // The events consumer exists, from now on, before the word is published: Receive would make it too late. if err := broker.AssertServingConsumers(js); err != nil { t.Fatal(err) } serving, stop := context.WithCancel(context.Background()) done := make(chan struct{}) go func() { defer close(done); _ = server.Serve(serving) }() t.Cleanup(func() { stop(); <-done }) servingBus.Store(js) t.Cleanup(func() { servingBus.Store(nil) }) // mesh-delivery says it merged the pull request, and the controller keeps what it said. body, _ := json.Marshal(a.merge) if _, err := js.Context().Publish(broker.DeliveryMergedSubjectOf(theDelivery), body); err != nil { t.Fatal(err) } becomes(t, 15*time.Second, "the merge being kept", func() bool { _, kept, err := keptDeliveryMerge(ctx, "novox", "mesh-delivery", 7, a.merge.HeadSHA) return err == nil && kept }) // The forge's merge of that pull request, as the controller heard it, and the build it asked for. const commit = "c0ffee0123456789" event, _ := json.Marshal(map[string]any{"owner": "novox", "repo": "mesh-delivery", "base": "main", "number": 7, "head_sha": a.merge.HeadSHA, "merge_commit_sha": commit}) var forgeMerge link.SourceMoved if err := json.Unmarshal(event, &forgeMerge); err != nil { t.Fatal(err) } if err := keepForgeMerge(ctx, forgeMerge); err != nil { t.Fatal(err) } id := fmt.Sprintf("build-%d", time.Now().Add(time.Hour).UnixNano()) if err := inv.RecordBuildRequest(ctx, inventory.BuildRequest{ID: id, Repository: "novox/mesh-delivery", Seat: "git", Path: "modules/mesh-delivery-view", Ref: "main", Commit: commit, For: "merge"}); err != nil { t.Fatal(err) } moved := onTrunk(id, "novox/mesh-delivery", "git", "modules/mesh-delivery-view", map[string]any{"module": "mesh-delivery-view", "version": "2"}) moved.Commit = commit _, _, err = takeIn(ctx, inv, moved) src, _ := inv.SourceOf(ctx, "mesh-delivery-view") if c.moved && (err != nil || src.Repository != "novox/mesh-delivery") { t.Fatalf("the Approved move was not registered from its repository: %v (built from %q)", err, src.Repository) } if !c.moved && (!errors.Is(err, errNotItsSource) || src.Repository != "") { t.Fatalf("a new module with no Approve was not refused as it is: %v (built from %q)", err, src.Repository) } }) } }