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
105 lines
4.0 KiB
Go
105 lines
4.0 KiB
Go
package resolution
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
mcat "git.novox.be/novox/mesh-sdk/go/catalogue"
|
|
)
|
|
|
|
func outsideManifest(t *testing.T) mcat.Manifest {
|
|
t.Helper()
|
|
m, err := decodeForTest([]byte(`{"module":"assistant","version":"1",
|
|
"own-secrets":{
|
|
"password":{"path":"/var/lib/assistant/password.secret","taken":"at-start"},
|
|
"vendor-key":{"path":"/var/lib/assistant/vendor-key.secret","taken":"at-start","issued-by":"outside"}},
|
|
"resources":[
|
|
{"id":"env","type":"file","path":"/var/lib/assistant/env","mode":"0600",
|
|
"content":"PASSWORD=${secret:password}\nVENDOR_KEY=${secret:vendor-key}\n"},
|
|
{"id":"server","type":"container","name":"assistant","image":"assistant:1",
|
|
"env-file":["/var/lib/assistant/env"],
|
|
"volumes":["/var/lib/assistant/vendor-key.secret:/run/vendor-key:ro"],
|
|
"secrets-in-environment":"reads its settings from the environment only"}]}`))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return m
|
|
}
|
|
|
|
func resourceAt(out []map[string]any, path string) map[string]any {
|
|
for _, r := range out {
|
|
if r["path"] == path {
|
|
return r
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func resourceNamed(out []map[string]any, name string) map[string]any {
|
|
for _, r := range out {
|
|
if r["name"] == name {
|
|
return r
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func TestAnOutsideSecretNotGivenIsNoFileAndAFileNamingItSaysEmpty(t *testing.T) {
|
|
r := mcat.Resolution{Node: "server", Modules: []mcat.Manifest{outsideManifest(t)}}
|
|
out, err := Declaration(r, mcat.Rendering{Needed: map[string]map[string]string{"assistant": {"password": "sealed-password"}}})
|
|
if err != nil {
|
|
t.Fatalf("a module whose outside secret nobody gave did not compose: %v", err)
|
|
}
|
|
if got := resourceAt(out, "/var/lib/assistant/vendor-key.secret"); got != nil {
|
|
t.Fatalf("a secret nobody gave was placed: %v", got)
|
|
}
|
|
env := resourceAt(out, "/var/lib/assistant/env")
|
|
if env == nil {
|
|
t.Fatal("the module's own file is not composed")
|
|
}
|
|
if content := env["content"].(string); content != "PASSWORD=${secret:password}\nVENDOR_KEY=\n" {
|
|
t.Fatalf("the file names the secret nobody gave as %q", content)
|
|
}
|
|
secrets, _ := env["secrets"].(map[string]any)
|
|
if len(secrets) != 1 || secrets["password"] != "sealed-password" {
|
|
t.Fatalf("the file is filled from %v", secrets)
|
|
}
|
|
server := resourceNamed(out, "assistant")
|
|
if on := strings.Join(stringsIn(server["restart-on"]), " "); strings.Contains(on, "vendor-key") {
|
|
t.Fatalf("the container restarts on a secret nobody gave: %q", on)
|
|
}
|
|
}
|
|
|
|
func TestAnOutsideSecretGivenIsPlacedAndFilled(t *testing.T) {
|
|
r := mcat.Resolution{Node: "server", Modules: []mcat.Manifest{outsideManifest(t)}}
|
|
out, err := Declaration(r, mcat.Rendering{Needed: map[string]map[string]string{"assistant": {
|
|
"password": "sealed-password", "vendor-key": "sealed-vendor-key"}}})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
placed := resourceAt(out, "/var/lib/assistant/vendor-key.secret")
|
|
if placed == nil || placed["sealed"] != "sealed-vendor-key" {
|
|
t.Fatalf("the secret given is placed as %v", placed)
|
|
}
|
|
env := resourceAt(out, "/var/lib/assistant/env")
|
|
if content := env["content"].(string); content != "PASSWORD=${secret:password}\nVENDOR_KEY=${secret:vendor-key}\n" {
|
|
t.Fatalf("the file was changed: %q", content)
|
|
}
|
|
if secrets, _ := env["secrets"].(map[string]any); secrets["vendor-key"] != "sealed-vendor-key" {
|
|
t.Fatalf("the file is filled from %v", secrets)
|
|
}
|
|
server := resourceNamed(out, "assistant")
|
|
if on := strings.Join(stringsIn(server["restart-on"]), " "); !strings.Contains(on, "assistant."+mcat.NeedID("vendor-key")) {
|
|
t.Fatalf("the container does not restart on the secret given: %q", on)
|
|
}
|
|
}
|
|
|
|
// A secret the mesh makes is still refused when it was not made: only an outside one is left out.
|
|
func TestAnOwnSecretTheMeshMakesIsStillRefusedWhenNotMade(t *testing.T) {
|
|
r := mcat.Resolution{Node: "server", Modules: []mcat.Manifest{outsideManifest(t)}}
|
|
_, err := Declaration(r, mcat.Rendering{})
|
|
if nm, ok := err.(*NotMadeError); !ok || nm.Name != "password" {
|
|
t.Fatalf("a secret the mesh makes, not made: %v", err)
|
|
}
|
|
}
|