DLV-FB1: the forge bridge says a branch's end as branch-deleted and answers bridge_tags
The bridge consumes the forge module's forge.delete and writes a branch-deleted message on FORGE for a deleted branch (never the trunk, never a tag), so the builder removes the branch's tagged versions (design 51 *The forge bridge says a branch's end*). Only the forge module's account may say forge.delete: the bus refuses it from another (a case of the bridge's whole-module test). bridge_tags, served by a new tools bundle, answers a package's git tags by prefix through the forge module's gitea_api, for the builder's floor. One unit test merged into another, so the tags test adds no function (test budget). Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
// Command mesh-delivery-forge-bridge-tools serves the forge bridge's tools (novox/hq design 51): bridge_tags, a
|
||||
// package's git tags read from the forge through the forge module's gitea_api, for the builder's floor. The runtime
|
||||
// launches it for a call; the bridge's program serves no request subject (ADR 0437 §8).
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
|
||||
"mesh-delivery-forge-bridge/internal/bridge"
|
||||
)
|
||||
|
||||
// tools are the bridge's tools over api.
|
||||
func tools(api bridge.API) []stdio.Tool {
|
||||
str := func(args map[string]any, k string) string { s, _ := args[k].(string); return strings.TrimSpace(s) }
|
||||
return []stdio.Tool{{
|
||||
Name: bridge.TagsTool,
|
||||
Description: "A package's git tags on the forge, for the builder's floor (design 51): every tag of repository whose name starts " +
|
||||
"with prefix (go/v for a Go package in the go directory, v for one at the root), sorted. Read-only; read from the forge " +
|
||||
"through the forge module's gitea_api, at most " + fmt.Sprint(bridge.TagsMaxPages*bridge.TagsPageSize) + " tags (more says so).",
|
||||
Input: map[string]any{"type": "object", "required": []string{"repository"}, "properties": map[string]any{
|
||||
"repository": map[string]any{"type": "string", "description": "<owner>/<name>"},
|
||||
"prefix": map[string]any{"type": "string", "description": "only tags starting with it"}}},
|
||||
Run: func(args map[string]any) (any, error) {
|
||||
return bridge.Tags(api, str(args, "repository"), str(args, "prefix"))
|
||||
},
|
||||
}}
|
||||
}
|
||||
|
||||
func main() {
|
||||
api := func(path string) (json.RawMessage, error) {
|
||||
return stdio.Ask("gitea.gitea_api", map[string]any{"path": path, "method": "GET"})
|
||||
}
|
||||
if err := stdio.Serve("", tools(api)); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "[mesh-delivery-forge-bridge-tools] %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
+40
-2
@@ -118,6 +118,7 @@ accounts {
|
||||
subscribe { allow: [
|
||||
"_INBOX.n1.mesh-delivery-forge-bridge.>"
|
||||
"mesh.assignment.n1.mesh-delivery-forge-bridge"
|
||||
"mesh.mod.gitea.event.forge.delete"
|
||||
"mesh.mod.gitea.event.pull.closed"
|
||||
"mesh.mod.gitea.event.pull.merged"
|
||||
"mesh.mod.gitea.event.pull.updated"
|
||||
@@ -235,7 +236,7 @@ func startBed(t *testing.T, openReply string, prepare func(ctx context.Context,
|
||||
}
|
||||
if _, err := js.CreateOrUpdateConsumer(ctx, "EVENTS", jetstream.ConsumerConfig{
|
||||
Durable: "n1_mesh-delivery-forge-bridge", AckPolicy: jetstream.AckExplicitPolicy, DeliverPolicy: jetstream.DeliverNewPolicy,
|
||||
FilterSubjects: []string{"mesh.mod.gitea.event.pull.updated", "mesh.mod.gitea.event.pull.merged", "mesh.mod.gitea.event.pull.closed"},
|
||||
FilterSubjects: []string{"mesh.mod.gitea.event.pull.updated", "mesh.mod.gitea.event.pull.merged", "mesh.mod.gitea.event.pull.closed", "mesh.mod.gitea.event.forge.delete"},
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -293,7 +294,8 @@ func startBed(t *testing.T, openReply string, prepare func(ctx context.Context,
|
||||
|
||||
// TestBridgeWritesPullMessages starts the program the way the node does (its entry, run, with MESH_BROKER_FILE).
|
||||
func TestBridgeWritesPullMessages(t *testing.T) { // 1235/AC2
|
||||
_, js, ctx, _ := startBridge(t, `{"result":{"pulls":[]}}`)
|
||||
ctl, js, ctx, _ := startBridge(t, `{"result":{"pulls":[]}}`)
|
||||
defer branchDeletedCase(t, ctl.ConnectedUrl(), js, ctx)
|
||||
|
||||
// gitea.pull.updated as the forge module publishes it: the body of trunk.ts:233-250 on mesh.mod.gitea.event.pull.updated.
|
||||
ev, _ := json.Marshal(map[string]any{"owner": "novox", "repo": "mesh-delivery", "number": 12, "title": "t", "body": "b", "base": "main",
|
||||
@@ -346,6 +348,42 @@ func TestBridgeWritesPullMessages(t *testing.T) { // 1235/AC2
|
||||
t.Errorf("the event is not acknowledged: %+v", ci)
|
||||
}
|
||||
|
||||
// branchDeletedCase is DLV-FB1's case of the bridge's whole-module test (design 51): the forge module's forge.delete of
|
||||
// a branch is a branch-deleted message on FORGE, and a forge.delete from another account than the forge module's is
|
||||
// refused by the bus.
|
||||
func branchDeletedCase(t *testing.T, srv string, js jetstream.JetStream, ctx context.Context) {
|
||||
t.Helper()
|
||||
ev, _ := json.Marshal(map[string]any{"owner": "novox", "repo": "mesh-sdk", "ref": "feat/x", "ref_type": "branch", "sha": head})
|
||||
if _, err := js.Publish(ctx, "mesh.mod.gitea.event.forge.delete", ev); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
forge, err := js.Stream(ctx, "FORGE")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
subject := "mesh.stream.mesh-delivery-forge-bridge.event.forge.novox.mesh-sdk.branch-deleted"
|
||||
var raw *jetstream.RawStreamMsg
|
||||
for deadline := time.Now().Add(20 * time.Second); time.Now().Before(deadline); time.Sleep(50 * time.Millisecond) {
|
||||
if raw, err = forge.GetLastMsgForSubject(ctx, subject); err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if err != nil || !strings.Contains(string(raw.Data), `"branch":"feat/x"`) {
|
||||
t.Fatalf("no branch-deleted message on FORGE within 20 s: %v", err)
|
||||
}
|
||||
other, err := nats.Connect(srv, nats.UserInfo("n1.mesh-delivery-forge-bridge", "pw"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer other.Close()
|
||||
ojs, _ := jetstream.New(other)
|
||||
octx, cancel := context.WithTimeout(ctx, 2*time.Second)
|
||||
defer cancel()
|
||||
if _, err := ojs.Publish(octx, "mesh.mod.gitea.event.forge.delete", ev); err == nil {
|
||||
t.Error("a forge.delete from the bridge's account was taken by the bus: only the forge module's account may say it")
|
||||
}
|
||||
}
|
||||
|
||||
const merge = "89abcdef0123456789abcdef0123456789abcdef"
|
||||
|
||||
// TestBridgeWritesMergedMessage has a stand-in answer the forge's gitea_commit_trees on its tool subject
|
||||
|
||||
@@ -68,8 +68,62 @@ const (
|
||||
UpdatedSubject = "mesh.mod.gitea.event.pull.updated"
|
||||
ClosedSubject = "mesh.mod.gitea.event.pull.closed"
|
||||
MergedSubject = "mesh.mod.gitea.event.pull.merged"
|
||||
// DeletedSubject is the forge module's forge.delete: a branch or a tag deleted (mesh-catalog modules/gitea
|
||||
// webhook.ts: owner, repo, ref, ref_type, sha). Only the forge module's account may publish it, the subject being
|
||||
// in its own namespace (design 32 § 2), so no other module can end a branch's tagged versions.
|
||||
DeletedSubject = "mesh.mod.gitea.event.forge.delete"
|
||||
)
|
||||
|
||||
// ForgeBranchDeleted is the kind of a branch-deleted message on FORGE (novox/hq design 51 *The forge bridge says a
|
||||
// branch's end*): the builder removes the branch's tagged versions on it, as on a pull request's merged or closed.
|
||||
// Every existing consumer of FORGE ignores a kind it does not know.
|
||||
const ForgeBranchDeleted = "branch-deleted"
|
||||
|
||||
// BranchDeleted is the body of a branch-deleted message.
|
||||
type BranchDeleted struct {
|
||||
V int `json:"v"`
|
||||
Kind string `json:"kind"`
|
||||
Repository string `json:"repository"`
|
||||
Branch string `json:"branch"`
|
||||
// Head is the commit the branch named when it was deleted, when the forge said it.
|
||||
Head string `json:"head,omitempty"`
|
||||
}
|
||||
|
||||
// deleted is the part of forge.delete the bridge reads.
|
||||
type deleted struct {
|
||||
Owner string `json:"owner"`
|
||||
Repo string `json:"repo"`
|
||||
Ref string `json:"ref"`
|
||||
RefType string `json:"ref_type"`
|
||||
SHA string `json:"sha"`
|
||||
}
|
||||
|
||||
// branchDeleted turns forge.delete into a branch-deleted message; a deleted tag is not handled.
|
||||
func branchDeleted(subject string, data []byte) (Message, error) {
|
||||
var d deleted
|
||||
if err := json.Unmarshal(data, &d); err != nil {
|
||||
return Message{}, unreadable(subject, "the body is not json: %v", err)
|
||||
}
|
||||
if d.RefType != "branch" {
|
||||
return Message{}, fmt.Errorf("%w: %s of a %s (%s)", ErrNotHandled, subject, d.RefType, d.Ref)
|
||||
}
|
||||
branch := strings.TrimPrefix(d.Ref, "refs/heads/")
|
||||
switch {
|
||||
case d.Owner == "" || d.Repo == "" || strings.ContainsAny(d.Owner+d.Repo, "./ *>"):
|
||||
return Message{}, unreadable(subject, "no owner or repo a forge event can name (%q/%q)", d.Owner, d.Repo)
|
||||
case branch == "":
|
||||
return Message{}, unreadable(subject, "no branch")
|
||||
case branch == Trunk:
|
||||
return Message{}, fmt.Errorf("%w: %s of the trunk %s", ErrNotHandled, subject, Trunk)
|
||||
}
|
||||
body, err := json.Marshal(BranchDeleted{V: delivery.Version, Kind: ForgeBranchDeleted, Repository: d.Owner + "/" + d.Repo, Branch: branch, Head: d.SHA})
|
||||
if err != nil {
|
||||
return Message{}, unreadable(subject, "%v", err)
|
||||
}
|
||||
return Message{Event: "forge." + d.Owner + "." + d.Repo + "." + ForgeBranchDeleted, Data: body,
|
||||
Said: fmt.Sprintf("branch %s of %s/%s deleted: its tagged versions end", branch, d.Owner, d.Repo)}, nil
|
||||
}
|
||||
|
||||
var sha40 = regexp.MustCompile(`^[0-9a-f]{40}$`)
|
||||
|
||||
// pull is the part of gitea.pull.merged (index.ts:186-207), gitea.pull.updated (mesh-catalog modules/gitea/trunk.ts:233-250) and gitea.pull.closed
|
||||
@@ -115,6 +169,8 @@ func ConvertKept(subject string, data []byte, trees Trees, files Files, kept Kep
|
||||
action = delivery.PullClosed
|
||||
case MergedSubject:
|
||||
action = delivery.PullClosed // unless it merged into the trunk, below
|
||||
case DeletedSubject:
|
||||
return branchDeleted(subject, data)
|
||||
default:
|
||||
return Message{}, fmt.Errorf("%w: %s", ErrNotHandled, subject)
|
||||
}
|
||||
|
||||
@@ -63,14 +63,13 @@ func TestUpdatedBecomesAPullMessage(t *testing.T) { // 1235/AC2 (the conversion;
|
||||
if !delivery.SameForge(f, want) {
|
||||
t.Errorf("message = %+v, want %+v", f, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDirsNotSaidAreNotKnown(t *testing.T) { // 1235/AC3
|
||||
msg, err := Convert(updatedSubject, updated(map[string]any{"module_dirs": nil, "module_dirs_said": nil}), nil, anyFiles)
|
||||
// 1235/AC3, merged into this test for the test budget: dirs not said are not known.
|
||||
msg, err = Convert(updatedSubject, updated(map[string]any{"module_dirs": nil, "module_dirs_said": nil}), nil, anyFiles)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f, _ := delivery.DecodeForge(msg.Data)
|
||||
f, _ = delivery.DecodeForge(msg.Data)
|
||||
if f.DirsKnown || len(f.Dirs) != 0 {
|
||||
t.Errorf("dirs = %v known = %v, want not known", f.Dirs, f.DirsKnown)
|
||||
}
|
||||
@@ -171,11 +170,31 @@ func mustDrop(body []byte, key string) []byte {
|
||||
}
|
||||
|
||||
func TestOtherSubjectsAreNotHandled(t *testing.T) { // 1235
|
||||
for _, s := range []string{"mesh.mod.gitea.event.something.else", "mesh.mod.other.event.pull.updated"} {
|
||||
for _, s := range []string{"mesh.mod.gitea.event.something.else", "mesh.mod.other.event.pull.updated", "mesh.mod.other.event.forge.delete"} {
|
||||
if _, err := Convert(s, updated(nil), nil, anyFiles); !errors.Is(err, ErrNotHandled) {
|
||||
t.Errorf("%s: err = %v, want ErrNotHandled", s, err)
|
||||
}
|
||||
}
|
||||
// The forge module's forge.delete of a branch is a branch-deleted message (DLV-FB1, design 51); of a tag or the
|
||||
// trunk it is not handled, and a body naming no repository is unreadable.
|
||||
del := func(refType, ref string) []byte {
|
||||
b, _ := json.Marshal(map[string]any{"owner": "novox", "repo": "mesh-sdk", "full_name": "novox/mesh-sdk", "ref": ref, "ref_type": refType, "sha": head})
|
||||
return b
|
||||
}
|
||||
msg, err := Convert(DeletedSubject, del("branch", "feat/x"), nil, anyFiles)
|
||||
var bd BranchDeleted
|
||||
if err != nil || msg.Event != "forge.novox.mesh-sdk.branch-deleted" || json.Unmarshal(msg.Data, &bd) != nil ||
|
||||
bd.Kind != ForgeBranchDeleted || bd.Repository != "novox/mesh-sdk" || bd.Branch != "feat/x" || bd.Head != head || msg.Said == "" {
|
||||
t.Errorf("forge.delete of a branch = %+v (%s), %v", msg, msg.Data, err)
|
||||
}
|
||||
for _, c := range []struct{ refType, ref string }{{"tag", "go/v0.1.13"}, {"branch", "main"}} {
|
||||
if _, err := Convert(DeletedSubject, del(c.refType, c.ref), nil, anyFiles); !errors.Is(err, ErrNotHandled) {
|
||||
t.Errorf("forge.delete of %s %s: err = %v, want ErrNotHandled", c.refType, c.ref, err)
|
||||
}
|
||||
}
|
||||
if _, err := Convert(DeletedSubject, []byte(`{"ref":"x","ref_type":"branch"}`), nil, anyFiles); !errors.Is(err, ErrUnreadable) {
|
||||
t.Errorf("forge.delete naming no repository: err = %v, want ErrUnreadable", err)
|
||||
}
|
||||
}
|
||||
|
||||
const merge = "89abcdef0123456789abcdef0123456789abcdef"
|
||||
@@ -395,3 +414,50 @@ func TestTrackSaysWhichHeadOfWhichPullRequestWasWritten(t *testing.T) { // 1279/
|
||||
t.Error("an open message was tracked")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBridgeTagsAnswersAPackagesTagsByPrefixPageByPage(t *testing.T) { // DLV-FB1, design 51 *The floor*
|
||||
var asked []string
|
||||
page := func(n int) json.RawMessage {
|
||||
var tags []map[string]string
|
||||
for i := 0; i < n; i++ {
|
||||
tags = append(tags, map[string]string{"name": "x" + strings.Repeat("y", i)})
|
||||
}
|
||||
b, _ := json.Marshal(tags)
|
||||
return b
|
||||
}
|
||||
api := func(path string) (json.RawMessage, error) {
|
||||
asked = append(asked, path)
|
||||
if strings.Contains(path, "page=1&") {
|
||||
full := page(TagsPageSize - 2)
|
||||
var tags []map[string]string
|
||||
_ = json.Unmarshal(full, &tags)
|
||||
tags = append(tags, map[string]string{"name": "go/v0.1.13"}, map[string]string{"name": "v0.0.1"})
|
||||
b, _ := json.Marshal(tags)
|
||||
return b, nil
|
||||
}
|
||||
return json.RawMessage(`[{"name":"go/v0.1.2"}]`), nil
|
||||
}
|
||||
a, err := Tags(api, "novox/mesh-sdk", "go/v")
|
||||
if err != nil || !slicesEqual(a.Tags, []string{"go/v0.1.13", "go/v0.1.2"}) || a.More || len(asked) != 2 ||
|
||||
asked[0] != "/repos/novox/mesh-sdk/tags?page=1&limit=50" {
|
||||
t.Errorf("Tags = %+v, %v; asked %v", a, err, asked)
|
||||
}
|
||||
if _, err := Tags(func(string) (json.RawMessage, error) { return nil, errors.New("down") }, "novox/mesh-sdk", ""); err == nil || !strings.Contains(err.Error(), "page 1") {
|
||||
t.Errorf("a forge not answering = %v, want an error naming the page", err)
|
||||
}
|
||||
if _, err := Tags(api, "mesh-sdk", ""); err == nil {
|
||||
t.Error("a repository with no owner answered")
|
||||
}
|
||||
}
|
||||
|
||||
func slicesEqual(a, b []string) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i := range a {
|
||||
if a[i] != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
package bridge
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// TagsTool is the forge bridge's tool bridge_tags (novox/hq design 51 *The floor*): a package's git tags, which the
|
||||
// builder's floor reads as one of its three sources.
|
||||
const TagsTool = "bridge_tags"
|
||||
|
||||
// TagsPageSize is how many tags one page of the forge's tags answers; TagsMaxPages bounds the read.
|
||||
const (
|
||||
TagsPageSize = 50
|
||||
TagsMaxPages = 40
|
||||
)
|
||||
|
||||
// API asks the forge one GET of its API (the forge module's gitea_api), answering the body.
|
||||
type API func(path string) (json.RawMessage, error)
|
||||
|
||||
// TagsAnswer is what bridge_tags answers.
|
||||
type TagsAnswer struct {
|
||||
Repository string `json:"repository"`
|
||||
Prefix string `json:"prefix,omitempty"`
|
||||
Tags []string `json:"tags"`
|
||||
// More says the read stopped at TagsMaxPages pages with more left.
|
||||
More bool `json:"more,omitempty"`
|
||||
}
|
||||
|
||||
// Tags is bridge_tags: every tag of repository (`<owner>/<name>`) whose name starts with prefix (`go/v` for the Go
|
||||
// package of mesh-sdk's `go` directory, `v` or empty for a package at the root), sorted. A forge that does not answer
|
||||
// is an error naming the page: the builder then counts this source silent.
|
||||
func Tags(api API, repository, prefix string) (TagsAnswer, error) {
|
||||
owner, name, ok := strings.Cut(strings.TrimSpace(repository), "/")
|
||||
if !ok || owner == "" || name == "" || strings.Contains(name, "/") {
|
||||
return TagsAnswer{}, fmt.Errorf("repository %q is not <owner>/<name>", repository)
|
||||
}
|
||||
out := TagsAnswer{Repository: owner + "/" + name, Prefix: prefix, Tags: []string{}}
|
||||
for page := 1; ; page++ {
|
||||
if page > TagsMaxPages {
|
||||
out.More = true
|
||||
break
|
||||
}
|
||||
raw, err := api(fmt.Sprintf("/repos/%s/%s/tags?page=%d&limit=%d", url.PathEscape(owner), url.PathEscape(name), page, TagsPageSize))
|
||||
if err != nil {
|
||||
return TagsAnswer{}, fmt.Errorf("the forge did not answer the tags of %s (page %d): %w", out.Repository, page, err)
|
||||
}
|
||||
var tags []struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
if err := json.Unmarshal(raw, &tags); err != nil {
|
||||
return TagsAnswer{}, fmt.Errorf("the forge's tags of %s (page %d) are no list of tags: %w", out.Repository, page, err)
|
||||
}
|
||||
for _, t := range tags {
|
||||
if strings.HasPrefix(t.Name, prefix) {
|
||||
out.Tags = append(out.Tags, t.Name)
|
||||
}
|
||||
}
|
||||
if len(tags) < TagsPageSize {
|
||||
break
|
||||
}
|
||||
}
|
||||
sort.Strings(out.Tags)
|
||||
return out, nil
|
||||
}
|
||||
@@ -28,18 +28,20 @@ type state struct {
|
||||
}
|
||||
|
||||
type artifact struct {
|
||||
Name string `json:"name"`
|
||||
Kind string `json:"kind"`
|
||||
Language string `json:"language"`
|
||||
System string `json:"system"`
|
||||
From string `json:"from"`
|
||||
Binary string `json:"binary"`
|
||||
Name string `json:"name"`
|
||||
Kind string `json:"kind"`
|
||||
Language string `json:"language"`
|
||||
System string `json:"system"`
|
||||
From string `json:"from"`
|
||||
Binary string `json:"binary"`
|
||||
Loads []string `json:"loads"`
|
||||
}
|
||||
|
||||
type manifest struct {
|
||||
Module string `json:"module"`
|
||||
Version string `json:"version"`
|
||||
Slug string `json:"slug"`
|
||||
Tools []string `json:"tools"`
|
||||
Consumes []string `json:"consumes"`
|
||||
Streams []stream `json:"streams"`
|
||||
State []state `json:"state"`
|
||||
@@ -87,13 +89,14 @@ func TestManifestDeclarations(t *testing.T) { // 1235/AC1
|
||||
t.Errorf("streams = %+v", m.Streams)
|
||||
}
|
||||
// The checker's owed is the forge module's to write (ADR 0521, R4e finding R2-1): the bridge neither hears nor reads it.
|
||||
if want := []string{"gitea.pull.closed", "gitea.pull.merged", "gitea.pull.updated"}; !reflect.DeepEqual(sorted(m.Consumes), want) {
|
||||
// gitea.forge.delete: a branch's end, said as a branch-deleted kind (design 51, DLV-FB1).
|
||||
if want := []string{"gitea.forge.delete", "gitea.pull.closed", "gitea.pull.merged", "gitea.pull.updated"}; !reflect.DeepEqual(sorted(m.Consumes), want) {
|
||||
t.Errorf("consumes = %v, want %v", m.Consumes, want)
|
||||
}
|
||||
if want := []string{"mesh-delivery-gate.merges", "mesh-delivery-gate.owed"}; !reflect.DeepEqual(sorted(m.Reads), want) {
|
||||
t.Errorf("reads = %v, want %v", m.Reads, want)
|
||||
}
|
||||
if want := []string{"gitea.gitea_add_comment", "gitea.gitea_commit_status", "gitea.gitea_commit_trees", "gitea.gitea_delivery_view",
|
||||
if want := []string{"gitea.gitea_add_comment", "gitea.gitea_api", "gitea.gitea_commit_status", "gitea.gitea_commit_trees", "gitea.gitea_delivery_view",
|
||||
"gitea.gitea_get_file", "gitea.gitea_merge_pull_request", "gitea.gitea_note_append", "gitea.gitea_open_pulls"}; !reflect.DeepEqual(sorted(m.Invokes), want) {
|
||||
t.Errorf("invokes = %v, want %v", m.Invokes, want)
|
||||
}
|
||||
@@ -110,8 +113,12 @@ func TestManifestDeclarations(t *testing.T) { // 1235/AC1
|
||||
if !reflect.DeepEqual(m.State, []state{{Name: "heads", History: 1}, {Name: "performed", History: 1}, {Name: "inputs", History: 1}}) { // 1279/AC6, 1341, 1126
|
||||
t.Errorf("state = %+v, want the buckets heads, performed and inputs", m.State)
|
||||
}
|
||||
// No other state, no bus, no seat, no tool, no event (ADR 0437 §4; ADR 0521: no check.status).
|
||||
for _, k := range []string{"logs", "emits", "claims", "provides", "requires", "uses", "defines-seats", "tools", "seat", "seats"} {
|
||||
// One tool, bridge_tags, served by its tools bundle, for the builder's floor (design 51).
|
||||
if !reflect.DeepEqual(m.Tools, []string{"bridge_tags"}) {
|
||||
t.Errorf("tools = %v, want bridge_tags", m.Tools)
|
||||
}
|
||||
// No other state, no bus, no seat, no event (ADR 0437 §4; ADR 0521: no check.status).
|
||||
for _, k := range []string{"logs", "emits", "claims", "provides", "requires", "uses", "defines-seats", "seat", "seats"} {
|
||||
if _, ok := top[k]; ok {
|
||||
t.Errorf("the manifest declares %q: the bridge has none", k)
|
||||
}
|
||||
@@ -143,8 +150,13 @@ func TestManifestProcess(t *testing.T) { // 1235/AC1
|
||||
if _, bus := p["bus"]; bus {
|
||||
t.Errorf("the process declares a bus: it runs on its own broker credential")
|
||||
}
|
||||
if len(m.Build.Artifacts) != 1 {
|
||||
t.Fatalf("artifacts = %+v", m.Build.Artifacts)
|
||||
if len(m.Build.Artifacts) != 2 {
|
||||
t.Fatalf("artifacts = %+v, want the program and the tools", m.Build.Artifacts)
|
||||
}
|
||||
if a := m.Build.Artifacts[1]; a.Name != "tools" || a.Binary != "mesh-delivery-forge-bridge-tools" || !reflect.DeepEqual(a.Loads, []string{a.Binary}) {
|
||||
t.Errorf("tools artifact = %+v", a)
|
||||
} else if st, err := os.Stat(a.From); err != nil || !st.IsDir() {
|
||||
t.Errorf("the tools' source %s: %v", a.From, err)
|
||||
}
|
||||
a := m.Build.Artifacts[0]
|
||||
if st, err := os.Stat(a.From); err != nil || !st.IsDir() || a.Binary != "mesh-delivery-forge-bridge" || !strings.HasSuffix(a.From, a.Binary) {
|
||||
|
||||
@@ -2,10 +2,14 @@
|
||||
"module": "mesh-delivery-forge-bridge",
|
||||
"version": "1",
|
||||
"slug": "forge-bridge",
|
||||
"tools": [
|
||||
"bridge_tags"
|
||||
],
|
||||
"consumes": [
|
||||
"gitea.pull.updated",
|
||||
"gitea.pull.merged",
|
||||
"gitea.pull.closed"
|
||||
"gitea.pull.closed",
|
||||
"gitea.forge.delete"
|
||||
],
|
||||
"streams": [
|
||||
{ "name": "forge", "events": ["forge.**"] }
|
||||
@@ -27,7 +31,8 @@
|
||||
"gitea.gitea_add_comment",
|
||||
"gitea.gitea_open_pulls",
|
||||
"gitea.gitea_commit_trees",
|
||||
"gitea.gitea_get_file"
|
||||
"gitea.gitea_get_file",
|
||||
"gitea.gitea_api"
|
||||
],
|
||||
"own-secrets": {
|
||||
"broker": "${dir:state}/broker"
|
||||
@@ -75,6 +80,17 @@
|
||||
"system": "arch",
|
||||
"from": "cmd/mesh-delivery-forge-bridge",
|
||||
"binary": "mesh-delivery-forge-bridge"
|
||||
},
|
||||
{
|
||||
"name": "tools",
|
||||
"kind": "bundle",
|
||||
"language": "go",
|
||||
"system": "arch",
|
||||
"from": "cmd/mesh-delivery-forge-bridge-tools",
|
||||
"binary": "mesh-delivery-forge-bridge-tools",
|
||||
"loads": [
|
||||
"mesh-delivery-forge-bridge-tools"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user