Files
mesh-sdk/go/catalogue/files_test.go
T
jschoubben 1c7356c247
mesh/delivery-group group feat/sdk1 rejected: composed together: the checker's verdict at the members' heads (428d048dd50a): unit: git.novox.be/novox/mesh-sdk/…
mesh/delivery closed: a newer head of the same pull request
mesh/check failed: unit: git.novox.be/novox/mesh-sdk/go/process.TestStatePutWritesAKeyWhole: a Put through a short blackout: 0, mesh-mail's state send…
catalogue: the composition's rules and refusals leave for the node-engine, so mesh-sdk renders no declaration and refuses no composition (ADR 0593 §7)
Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-02 19:19:25 +01:00

157 lines
5.3 KiB
Go

package catalogue
import (
"sort"
"strings"
"testing"
)
// issue 900, novox/hq ADR 0394 §1: node-files is a node seat whose verbs say the commands they replace.
func TestNodeFilesIsANodeSeatWithEveryVerbOfADR0394(t *testing.T) {
s, ok := SeatNamed(FilesSeat)
if !ok {
t.Fatalf("the mesh defines no seat %s", FilesSeat)
}
if s.Scope != ScopeNode || s.Delivers != "" || !strings.Contains(s.Decision, "ADR 0394") {
t.Errorf("%s: scope %q, delivers %q, decision %q", FilesSeat, s.Scope, s.Delivers, s.Decision)
}
have := map[string]bool{}
replaced := map[string]string{}
for _, v := range s.Serves {
have[v.Name] = true
if v.Optional {
t.Errorf("%s.%s is optional", FilesSeat, v.Name)
}
// upload and download (ADR 0468 §9, issue 1718) move bytes through the object store: no command is theirs.
if len(v.Replaces) == 0 && v.Name != "upload" && v.Name != "download" {
t.Errorf("%s.%s says nothing it replaces", FilesSeat, v.Name)
}
for _, c := range v.Replaces {
replaced[c] = v.Name
}
}
for _, name := range []string{"stat", "readlink", "list", "read", "search", "account", "write", "copy", "move",
"delete", "mkdir", "chmod", "chown", "upload", "download", "trash", "restore"} {
if !have[name] {
t.Errorf("%s serves no verb %s", FilesSeat, name)
}
}
for command, verb := range map[string]string{"cat": "read", "tee": "write", "cp": "copy", "mv": "move",
"rm": "delete", "mkdir -p": "mkdir", "chmod": "chmod", "chgrp": "chown"} {
if replaced[command] != verb {
t.Errorf("%q is replaced by %q, want %s.%s", command, replaced[command], FilesSeat, verb)
}
}
}
// issue 900: restore, like every write verb, takes a reason and an override.
func TestNodeFilesRestoreTakesIdAndReason(t *testing.T) {
s, _ := SeatNamed(FilesSeat)
for _, v := range s.Serves {
if v.Name != "restore" {
continue
}
props, _ := v.Input["properties"].(map[string]any)
for _, p := range []string{"id", "reason", "override"} {
if _, has := props[p]; !has {
t.Errorf("restore has no input %s", p)
}
}
if req, _ := v.Input["required"].([]string); strings.Join(req, ",") != "id,reason" {
t.Errorf("restore requires %v, want id and reason", req)
}
return
}
t.Error("no restore verb")
}
// 1718/AC1: node-files names upload and download, in mesh-files' order, before trash.
func TestNodeFilesNamesUploadAndDownloadBeforeTrash(t *testing.T) {
s, _ := SeatNamed(FilesSeat)
var names []string
for _, v := range s.Serves {
names = append(names, v.Name)
}
joined := strings.Join(names, ",")
if !strings.Contains(joined, "chown,upload,download,trash") {
t.Errorf("1718/AC1: node-files' verbs are %s, want upload and download between chown and trash", joined)
}
}
// 1718/AC2: a holder serving every verb including upload and download is accepted; one naming a verb outside the
// contract is still refused, and one leaving upload out is refused.
func TestAHolderOfNodeFilesServesUploadAndDownloadAndNothingElse(t *testing.T) {
s, _ := SeatNamed(FilesSeat)
var verbs []string
for _, v := range s.Serves {
verbs = append(verbs, v.Name)
}
hold := func(serves []string) error {
return CanHold(Manifest{Module: "mesh-files", Version: "1",
Claims: []Claim{{Name: FilesSeat, Scope: ScopeNode, Serves: serves}}}, s)
}
if err := hold(verbs); err != nil {
t.Errorf("1718/AC2: a holder serving every verb was refused: %v", err)
}
if err := hold(append(append([]string{}, verbs...), "sync")); err == nil || !strings.Contains(err.Error(), "does not promise") {
t.Errorf("1718/AC2: a verb outside the contract was accepted: %v", err)
}
var without []string
for _, v := range verbs {
if v != "upload" {
without = append(without, v)
}
}
if err := hold(without); err == nil || !strings.Contains(err.Error(), "upload") {
t.Errorf("1718/AC2: a holder leaving upload out was accepted: %v", err)
}
}
// 1718/AC3: upload requires path and bucket; download requires bucket, key, path and reason; sha256, key (upload)
// and override are plain strings, not true/false switches.
func TestNodeFilesUploadAndDownloadInputs(t *testing.T) {
s, _ := SeatNamed(FilesSeat)
want := map[string]struct {
required string
strs []string
}{
"upload": {"path,bucket", []string{"path", "bucket", "key"}},
"download": {"bucket,key,path,reason", []string{"bucket", "key", "path", "sha256", "reason", "override"}},
}
seen := 0
for _, v := range s.Serves {
w, ok := want[v.Name]
if !ok {
continue
}
seen++
req, _ := v.Input["required"].([]string)
got := append([]string{}, req...)
sortStrings(got)
exp := strings.Split(w.required, ",")
sortStrings(exp)
if strings.Join(got, ",") != strings.Join(exp, ",") {
t.Errorf("1718/AC3: %s requires %v, want %s", v.Name, req, w.required)
}
props, _ := v.Input["properties"].(map[string]any)
if len(props) != len(w.strs) {
t.Errorf("1718/AC3: %s has %d inputs, want %v", v.Name, len(props), w.strs)
}
for _, name := range w.strs {
p, has := props[name].(map[string]any)
if !has {
t.Errorf("1718/AC3: %s has no input %s", v.Name, name)
continue
}
if _, enum := p["enum"]; enum || p["type"] != "string" {
t.Errorf("1718/AC3: %s input %s is %v, want a plain string", v.Name, name, p)
}
}
}
if seen != 2 {
t.Errorf("1718/AC3: found %d of upload and download", seen)
}
}
func sortStrings(s []string) { sort.Strings(s) }