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
189 lines
7.5 KiB
Go
189 lines
7.5 KiB
Go
package resolution
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
mcat "git.novox.be/novox/mesh-sdk/go/catalogue"
|
|
)
|
|
|
|
func notifier() mcat.Manifest {
|
|
return mcat.Manifest{Module: "notifier",
|
|
Settings: map[string]mcat.SettingDeclaration{
|
|
"font-size": {Kind: mcat.KindPreference, Default: float64(10), Why: "readable at a scale of one"},
|
|
"width": {Kind: mcat.KindPreference, Default: float64(250), Why: "fits a title of forty characters"},
|
|
},
|
|
Resources: []map[string]any{{"id": "configuration", "type": "file", "path": "/x/notifierrc",
|
|
"content": "font = Inter ${setting:font-size}\nwidth = ${setting:width}\n"}},
|
|
}
|
|
}
|
|
|
|
func TestAnUnsetPreferenceTakesItsDefault(t *testing.T) {
|
|
t.Run("the node over the mesh over the default", theNodeOverTheMeshOverTheDefault)
|
|
m := notifier()
|
|
for _, layers := range [][]mcat.Layer{nil, {{From: mcat.MeshWideLayer, Values: map[string]any{}}}} {
|
|
file := map[string]any{}
|
|
for k, v := range m.Resources[0] {
|
|
file[k] = v
|
|
}
|
|
if err := settingInto(file, WithDefaults(m, layers), m.Module); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if file["content"] != "font = Inter 10\nwidth = 250\n" {
|
|
t.Fatalf("filled as %q", file["content"])
|
|
}
|
|
}
|
|
if err := JudgeSettings(m, nil, false); err != nil {
|
|
t.Fatalf("a module whose every key has a default does not compose with no layer: %v", err)
|
|
}
|
|
}
|
|
|
|
func theNodeOverTheMeshOverTheDefault(t *testing.T) {
|
|
m := notifier()
|
|
layers := []mcat.Layer{
|
|
{From: mcat.MeshWideLayer, Values: map[string]any{"width": float64(300)}},
|
|
{From: "laptop", Values: map[string]any{"font-size": float64(13), "width": float64(340)}},
|
|
}
|
|
file := map[string]any{"type": "file", "content": m.Resources[0]["content"]}
|
|
if err := settingInto(file, WithDefaults(m, layers), m.Module); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if file["content"] != "font = Inter 13\nwidth = 340\n" {
|
|
t.Fatalf("filled as %q", file["content"])
|
|
}
|
|
file = map[string]any{"type": "file", "content": m.Resources[0]["content"]}
|
|
if err := settingInto(file, WithDefaults(m, layers[:1]), m.Module); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if file["content"] != "font = Inter 10\nwidth = 300\n" {
|
|
t.Fatalf("the mesh's layer over the default filled as %q", file["content"])
|
|
}
|
|
}
|
|
|
|
// Composed for a machine, the way a push writes it: the default reaches the file, and the node's
|
|
// layer overrides it.
|
|
func TestAComposedMachineGetsTheDefaultAndTheNodesValue(t *testing.T) {
|
|
r := anAdoptedAnchor()
|
|
r.Modules = append(r.Modules, notifier())
|
|
with := anchorRendering(false)
|
|
composed, err := Compose(r, with)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if why, left := composed.LeftOut["notifier"]; left {
|
|
t.Fatalf("the notifier was left out: %s", why)
|
|
}
|
|
if c := byID(composed.Resources)["notifier.configuration"]["content"]; c != "font = Inter 10\nwidth = 250\n" {
|
|
t.Fatalf("composed with no layer as %q", c)
|
|
}
|
|
with.Settings["notifier"] = []mcat.Layer{{From: "anchor", Values: map[string]any{"font-size": float64(13)}}}
|
|
if composed, err = Compose(r, with); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if c := byID(composed.Resources)["notifier.configuration"]["content"]; c != "font = Inter 13\nwidth = 250\n" {
|
|
t.Fatalf("composed with the node's font size as %q", c)
|
|
}
|
|
}
|
|
|
|
// A default fills ${setting:…} only: it never becomes a key of a mergeable file (issue 168).
|
|
func TestADefaultIsNotMergedIntoAJSONFile(t *testing.T) {
|
|
m := notifier()
|
|
m.Resources = append(m.Resources, map[string]any{"id": "other", "type": "file", "path": "/x/other.json",
|
|
"merge": mcat.MergeJSON, "content": `{"keep": 1}`})
|
|
out, err := ApplySettings(m.Resources[1], WithDefaults(m, nil))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if strings.Contains(out["content"].(string), "font-size") {
|
|
t.Fatalf("a default reached a mergeable file: %s", out["content"])
|
|
}
|
|
}
|
|
|
|
// A value that is the operator's has no default: no layer setting it is refused by name, as before.
|
|
func TestAnOperatorsValueWithoutADefaultIsStillRefused(t *testing.T) {
|
|
m := notifier()
|
|
m.Resources[0]["content"] = "font = Inter ${setting:font-size}\nwidth = ${setting:width}\nfrom = ${setting:domain}\n"
|
|
err := JudgeSettings(m, nil, false)
|
|
if err == nil || !strings.Contains(err.Error(), "${setting:domain}") {
|
|
t.Fatalf("judged %v", err)
|
|
}
|
|
if strings.Contains(err.Error(), "font-size") {
|
|
t.Fatalf("a default was named as set today: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestEveryValueSaysWhereItCameFrom(t *testing.T) {
|
|
m := notifier()
|
|
m.Resources[0]["content"] = m.Resources[0]["content"].(string) + "x = ${setting:position}\n"
|
|
got := Effective(m, []mcat.Layer{
|
|
{From: mcat.MeshWideLayer, Values: map[string]any{"width": float64(300), "position": "top-right"}},
|
|
{From: "laptop", Values: map[string]any{"font-size": float64(13)}},
|
|
})
|
|
want := map[string]string{"font-size": "laptop", "position": mcat.MeshWideLayer, "width": mcat.MeshWideLayer}
|
|
if len(got) != 3 {
|
|
t.Fatalf("effective: %+v", got)
|
|
}
|
|
for _, s := range got {
|
|
if s.From != want[s.Key] {
|
|
t.Errorf("%s from %q, want %q", s.Key, s.From, want[s.Key])
|
|
}
|
|
}
|
|
if got[0].Key != "font-size" || got[0].Value != float64(13) || got[0].Default != float64(10) {
|
|
t.Fatalf("font-size: %+v", got[0])
|
|
}
|
|
only := Effective(m, nil)
|
|
if only[0].From != mcat.DefaultLayer || only[0].Value != float64(10) {
|
|
t.Fatalf("with no layer: %+v", only)
|
|
}
|
|
}
|
|
|
|
// A node may be called `default`. Its layer is a node's like any other: it overrides the module's
|
|
// default, it merges into a mergeable file, and it is named among what is set.
|
|
func TestANodeCalledDefaultIsANodesLayer(t *testing.T) {
|
|
m := notifier()
|
|
node := []mcat.Layer{{From: mcat.DefaultLayer, Values: map[string]any{"font-size": float64(13)}}}
|
|
file := map[string]any{"type": "file", "content": m.Resources[0]["content"]}
|
|
if err := settingInto(file, WithDefaults(m, node), m.Module); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if file["content"] != "font = Inter 13\nwidth = 250\n" {
|
|
t.Fatalf("the node called default was dropped: %q", file["content"])
|
|
}
|
|
out, err := ApplySettings(map[string]any{"id": "j", "type": "file", "merge": mcat.MergeJSON, "content": `{}`}, node)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !strings.Contains(out["content"].(string), `"font-size": 13`) {
|
|
t.Fatalf("the node called default did not merge: %s", out["content"])
|
|
}
|
|
if got := Effective(m, node); got[0].FromDefault || got[0].Value != float64(13) {
|
|
t.Fatalf("the node's value read as the default: %+v", got[0])
|
|
}
|
|
}
|
|
|
|
// A default fills ${setting:…} in what a module contributes and serves, and never replaces a value a
|
|
// contribution states itself.
|
|
func TestADefaultFillsAContributionAndAServedFactButNotALiteral(t *testing.T) {
|
|
m := notifier()
|
|
m.Settings["site-title"] = mcat.SettingDeclaration{Kind: mcat.KindPreference, Default: "Notes", Why: "x"}
|
|
contribution := map[string]any{"title": "${setting:site-title}", "width": "fixed", "port": float64(8080)}
|
|
got, err := overridden(contribution, WithDefaults(m, nil), "a contribution")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got["title"] != "Notes" || got["width"] != "fixed" {
|
|
t.Fatalf("contribution: %v", got)
|
|
}
|
|
got, err = overridden(contribution, WithDefaults(m, []mcat.Layer{{From: "laptop", Values: map[string]any{"width": "wide"}}}), "a contribution")
|
|
if err != nil || got["width"] != "wide" {
|
|
t.Fatalf("a node's value did not override a contribution's own key: %v %v", got, err)
|
|
}
|
|
served, err := Settle(map[string]any{"name": "${setting:site-title}"}, WithDefaults(m, nil))
|
|
if err != nil || served["name"] != "Notes" {
|
|
t.Fatalf("served: %v %v", served, err)
|
|
}
|
|
if _, err := Settle(map[string]any{"name": "${setting:site-title}"}, nil); err == nil {
|
|
t.Fatal("a served fact without the defaults was filled")
|
|
}
|
|
}
|