Files
mesh-catalog/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/values_test.go
T

82 lines
4.8 KiB
Go

package main
import (
"encoding/json"
"reflect"
"testing"
"time"
mdc "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
)
// What go/delivery wrote into the seat's states, key order and all, must decode as it did and encode to the same
// content (ADR 0634 §2): the gate's working values are converted to the seat's types at the edge.
func TestStoredValuesOfTheEarlierLayoutDecodeUnchanged(t *testing.T) {
approved := time.Date(2026, 11, 5, 10, 0, 0, 0, time.UTC)
since := time.Date(2026, 11, 5, 10, 5, 0, 0, time.UTC)
head := mdc.Head{Repository: "novox/app", Number: 7, Head: "0123456789abcdef0123456789abcdef01234567"}
rows := []struct {
name string
stored string
decode func(string) (any, error)
encode func(any) ([]byte, error)
want any
}{
{"merge with a merged and a refused pull", `{"v":1,"group":"feat/x","ask":"merge.feat_x.0123456789ab","approved":{"novox/app@7":"` + head.Head + `","novox/web@8":"abc"},` +
`"pulls":[{"repository":"novox/app","number":7,"head":"` + head.Head + `","state":"merged","commit":"c0ffee"},` +
`{"repository":"novox/web","number":8,"head":"abc","state":"refused","words":"mesh/check (missing)"}]}`,
func(s string) (any, error) { return DecodeMerge([]byte(s)) }, func(v any) ([]byte, error) { return EncodeMerge(v.(Merge)) },
Merge{V: 1, Group: "feat/x", Ask: "merge.feat_x.0123456789ab", Approved: map[string]string{"novox/app@7": head.Head, "novox/web@8": "abc"}, Pulls: []MergePull{
{Head: head, State: mdc.MergeMerged, Commit: "c0ffee"},
{Head: mdc.Head{Repository: "novox/web", Number: 8, Head: "abc"}, State: mdc.MergeRefused, Words: "mesh/check (missing)"}}}},
{"ask keeps the router's ask as bytes", `{"v":1,"group":"feat/x","heads":[{"repository":"novox/app","number":7,"head":"` + head.Head + `"}],"ask":{"id":"a","options":["Approve"]},"asking":2}`,
func(s string) (any, error) { return DecodeAsk([]byte(s)) }, func(v any) ([]byte, error) { return EncodeAsk(v.(Ask)) },
Ask{V: 1, Group: "feat/x", Heads: []mdc.Head{head}, Ask: json.RawMessage(`{"id":"a","options":["Approve"]}`), Asking: 2}},
{"turn without and with a check waited for", `{"v":1,"repository":"novox/app","waiting":[` +
`{"group":"feat/x","heads":"0123456789ab","ask":"merge.x","approved":"2026-11-05T10:00:00Z","since":"2026-11-05T10:00:00Z"},` +
`{"group":"feat/y","heads":"ba9876543210","approved":"2026-11-05T10:00:00Z","since":"2026-11-05T10:00:00Z","waits-for":"feat/x.0123456789ab","waits-since":"2026-11-05T10:05:00Z","step":"unit"}]}`,
func(s string) (any, error) { return DecodeTurn([]byte(s)) }, func(v any) ([]byte, error) { return EncodeTurn(v.(Turn)) },
Turn{V: 1, Repository: "novox/app", Waiting: []TurnWait{
{Group: "feat/x", Heads: "0123456789ab", Ask: "merge.x", Approved: approved, Since: approved},
{Group: "feat/y", Heads: "ba9876543210", Approved: approved, Since: approved, WaitsFor: "feat/x.0123456789ab", WaitsSince: since, Step: "unit"}}}},
{"source", `{"v":1,"group":"feat/x","heads":{"novox/app":"c0ffee"}}`,
func(s string) (any, error) { return mdc.DecodeSource([]byte(s)) }, func(v any) ([]byte, error) { return mdc.EncodeSource(v.(Source)) },
Source{V: 1, Group: "feat/x", Heads: map[string]string{"novox/app": "c0ffee"}}},
{"owed status", `{"v":1,"act":"status","context":"mesh/delivery","target":"https://x/y","state":"pending"}`,
func(s string) (any, error) { return mdc.DecodeOwed([]byte(s)) }, func(v any) ([]byte, error) { return mdc.EncodeOwed(v.(Owed)) },
Owed{V: 1, Act: "status", Context: "mesh/delivery", Target: "https://x/y", State: "pending"}},
{"answer", `{"v":1,"option":"Approve","warrant":"w@2026-11-05T10:00:00Z","words":"chose Approve"}`,
func(s string) (any, error) { return mdc.DecodeAnswer([]byte(s)) }, func(v any) ([]byte, error) { return mdc.EncodeAnswer(v.(Answer)) },
Answer{V: 1, Option: "Approve", Warrant: "w@2026-11-05T10:00:00Z", Words: "chose Approve"}},
}
for _, r := range rows {
t.Run(r.name, func(t *testing.T) {
got, err := r.decode(r.stored)
if err != nil {
t.Fatalf("the earlier layout does not decode: %v", err)
}
if !sameValue(got, r.want) {
t.Fatalf("decoded %+v, want %+v", got, r.want)
}
raw, err := r.encode(got)
if err != nil {
t.Fatalf("encode: %v", err)
}
var a, b any
if json.Unmarshal(raw, &a) != nil || json.Unmarshal([]byte(r.stored), &b) != nil || !reflect.DeepEqual(a, b) {
t.Fatalf("encoded %s, the earlier layout was %s", raw, r.stored)
}
})
}
}
func sameValue(a, b any) bool {
if x, ok := a.(Ask); ok {
return SameAsk(x, b.(Ask))
}
if x, ok := a.(Turn); ok {
return SameTurn(x, b.(Turn)) && reflect.DeepEqual(len(x.Waiting), len(b.(Turn).Waiting)) && x.Waiting[len(x.Waiting)-1].WaitsSince.Equal(b.(Turn).Waiting[len(x.Waiting)-1].WaitsSince)
}
return reflect.DeepEqual(a, b)
}