ADR 0593 §7: the composer with its refusals leaves mesh-sdk, so mesh-sdk carries no composition rule. The catalogue functions only the node-engine's composition reaches (Resolve, Resolution's Compose, Declaration, DataItems, LeftOut, Rules and the rest, the settings, placeholder and template refusals, the rendering helpers) are copied with their tests into internal/compose/resolution; a method of a mesh-sdk shape becomes a function taking it first. The composition calls its own copies, and the captured catalogue its tests read lives here now, shared by mesh-compose's tests. Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
106 lines
3.9 KiB
Go
106 lines
3.9 KiB
Go
package resolution
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
|
|
mcat "git.novox.be/novox/mesh-sdk/go/catalogue"
|
|
)
|
|
|
|
const filesWithShares = `{"module":"files","version":"1","requires":["shell",
|
|
{"name":"s3-bucket","shares":[{"to":"seat:node-files","methods":["put"]},{"to":"print-and-scan","methods":["get","put"]}]}]}`
|
|
|
|
func minioLike() mcat.Manifest {
|
|
return mcat.Manifest{Module: "minio", Version: "1", Provides: mcat.Offers("s3-bucket"),
|
|
Receives: map[string]string{"s3-bucket": "/grants/mesh.json"}}
|
|
}
|
|
|
|
func givenToMinio(t *testing.T, out []map[string]any) []mcat.Contribution {
|
|
t.Helper()
|
|
for _, r := range out {
|
|
if r["path"] != "/grants/mesh.json" {
|
|
continue
|
|
}
|
|
var parsed struct {
|
|
Given []mcat.Contribution `json:"given"`
|
|
}
|
|
if err := json.Unmarshal([]byte(r["content"].(string)), &parsed); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return parsed.Given
|
|
}
|
|
t.Fatalf("minio was given no file: %v", out)
|
|
return nil
|
|
}
|
|
|
|
func sharesOf(t *testing.T, values map[string]any) string {
|
|
t.Helper()
|
|
raw, err := json.Marshal(values["shares"])
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return string(raw)
|
|
}
|
|
|
|
const sortedSharesJSON = `[{"methods":["get","put"],"to":"print-and-scan"},{"methods":["put"],"to":"seat:node-files"}]`
|
|
|
|
// 1591/AC3: the values a consumer gives carry the shares sorted by `to`, remote (grant) and co-located alike.
|
|
func TestTheGrantCarriesTheSharesSorted(t *testing.T) {
|
|
m := mustParse(t, filesWithShares)
|
|
remote := mcat.Resolution{Node: "laptop", Modules: []mcat.Manifest{m}}
|
|
values, asks, err := ContributionsFrom(remote, "s3-bucket", "files", mcat.SettingsBy{})
|
|
if err != nil || !asks {
|
|
t.Fatalf("asks %v, err %v", asks, err)
|
|
}
|
|
if got := sharesOf(t, values); got != sortedSharesJSON {
|
|
t.Fatalf("the grant values carry %s", got)
|
|
}
|
|
// As the provider's file shows it, through a grant.
|
|
provider, err := Resolve(shelf(minioLike()), []string{"minio"}, workstation(), mcat.World{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
out, err := Declaration(provider, mcat.Rendering{Grants: []mcat.Grant{{Provision: "s3-bucket", Consumer: "laptop", From: "files", Values: values}}})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
given := givenToMinio(t, out)
|
|
if len(given) != 1 || sharesOf(t, given[0].Values) != sortedSharesJSON {
|
|
t.Fatalf("the remote consumer's shares did not reach the file: %+v", given)
|
|
}
|
|
|
|
// Co-located: the consumer contributes to s3-bucket too, and no grant is passed.
|
|
co := mustParse(t, `{"module":"files","version":"1","contributes":{"s3-bucket":{"name":"files"}},
|
|
"requires":[{"name":"s3-bucket","shares":[{"to":"seat:node-files","methods":["put"]},{"to":"print-and-scan","methods":["put","get"]}]}]}`)
|
|
both, err := Resolve(shelf(minioLike(), co), []string{"files"}, workstation(), mcat.World{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
out, err = Declaration(both, mcat.Rendering{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
given = givenToMinio(t, out)
|
|
if len(given) != 1 || sharesOf(t, given[0].Values) != sortedSharesJSON || given[0].Values["name"] != "files" {
|
|
t.Fatalf("the co-located consumer's shares did not reach the file: %+v", given)
|
|
}
|
|
}
|
|
|
|
// 1591/AC5: a consumer whose contributes to s3-bucket names `shares` itself is refused.
|
|
func TestAConsumerCannotContributeSharesItself(t *testing.T) {
|
|
_, err := decodeForTest([]byte(`{"module":"files","version":"1",
|
|
"contributes":{"s3-bucket":{"shares":"x"}},
|
|
"requires":[{"name":"s3-bucket","shares":[{"to":"a","methods":["get"]}]}]}`))
|
|
if err == nil || !strings.Contains(err.Error(), "ADR 0468 §8") {
|
|
t.Errorf("both written, accepted: %v", err)
|
|
}
|
|
// Without the field the grant is still refused, so the provider is never told something the manifest did not say.
|
|
m := mcat.Manifest{Module: "files", Requires: []string{"s3-bucket"},
|
|
Contributes: map[string]map[string]any{"s3-bucket": {"shares": "x"}}}
|
|
r := mcat.Resolution{Node: "laptop", Modules: []mcat.Manifest{m}}
|
|
if _, _, err := ContributionsFrom(r, "s3-bucket", "files", mcat.SettingsBy{}); err == nil {
|
|
t.Error("a contributed `shares` reached the grant")
|
|
}
|
|
}
|