Issue 1585, S19: the strict parser, every manifest problem function and the judge's tables (192 declarations, about 5,200 lines) and their 244 tests leave; composition keeps its own refusals and the helpers it uses. The catalogue's tests decode the manifests they build. ParseManifest, ParseManifestWith, ParseManifestForInputs, CatalogueProblems and the rest are gone; mesh-delivery-indexer/judge holds them. Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
104 lines
5.1 KiB
Go
104 lines
5.1 KiB
Go
package catalogue
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// A provider's consumers' credentials reach it in a file its machine's provisioner client fills (novox/hq ADR 0373,
|
|
// issue 679): `${consumers:<provision>}`, which only a provider's template may hold, for a provision it provides, in a
|
|
// file of mode 0600 owned at runtime; filled by the client alone, so a module not handed to it on a machine is
|
|
// refused there rather than written the placeholder's text, or nothing.
|
|
|
|
const (
|
|
handedProvider = `{"module":"db","version":"1","moved":true,
|
|
"provides":[{"name":"postgres-database","scope":"mesh","identity":{"max":63,"in":"a PostgreSQL role"}}],
|
|
"build":{"artifacts":[{"name":"files","kind":"archive","from":"files"}]},
|
|
"resources":[
|
|
{"id":"state","type":"directory","mode":"0700","place":"."},
|
|
{"id":"consumers","type":"file","path":"${dir:state}/consumers.json","mode":"0600","template":"files/consumers.json"}]}`
|
|
consumersText = "${consumers:postgres-database}"
|
|
)
|
|
|
|
func builtProvider(text string) []Built {
|
|
return []Built{{Name: "files", Kind: ArtifactArchive,
|
|
Reference: ArtifactStoreScheme + "db/files/blobs/sha256:" + strings.Repeat("f", 64),
|
|
Digest: "sha256:" + strings.Repeat("f", 64), Texts: map[string]string{"consumers.json": text}}}
|
|
}
|
|
|
|
func TestAHandedProvidersConsumersFileIsHandedToTheClient(t *testing.T) {
|
|
r := notifierOn(t, held(t, handedProvider, builtProvider(consumersText)))
|
|
c, got := composeWith(t, r, handingTo("store:5000"))
|
|
if strings.Contains(got, "${consumers:") {
|
|
t.Fatalf("the declaration carries the placeholder: %s", got)
|
|
}
|
|
plan := c.Plans["db"]
|
|
if len(plan.Files) != 1 || plan.Files[0].Template != "consumers.json" || plan.Files[0].OwnerAtRuntime != RuntimeOwned {
|
|
t.Fatalf("the plan fills %+v, want the consumers' file owned at runtime", plan.Files)
|
|
}
|
|
}
|
|
|
|
func TestWhatAConsumersPlaceholderMayNotBeIsRefused(t *testing.T) {
|
|
for why, c := range map[string]struct{ raw, text, want string }{
|
|
"a provision the module does not provide": {handedProvider, "${consumers:cache}", "does not provide"},
|
|
"a module providing nothing": {strings.Replace(handedProvider, `"provides":[{"name":"postgres-database","scope":"mesh","identity":{"max":63,"in":"a PostgreSQL role"}}],`,
|
|
"", 1), consumersText, "does not provide"},
|
|
"a file others may read": {strings.Replace(handedProvider, `"mode":"0600","template"`,
|
|
`"mode":"0640","template"`, 1), consumersText, "0600"},
|
|
"a file the program keeps": {strings.Replace(handedProvider, `"mode":"0600","template"`,
|
|
`"mode":"0600","create-once":true,"template"`, 1), consumersText, "owned"},
|
|
} {
|
|
m, err := decodeForTest([]byte(c.raw))
|
|
if err != nil {
|
|
t.Fatalf("%s: %v", why, err)
|
|
}
|
|
resolved, err := m.Resolve(builtProvider(c.text))
|
|
if err == nil {
|
|
r := notifierOn(t, resolved)
|
|
_, err = r.Compose(handingTo("store:5000"))
|
|
}
|
|
if err == nil || !strings.Contains(err.Error(), c.want) {
|
|
t.Errorf("%s: %v, want a refusal saying %q", why, err, c.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Where the provider is not handed, nothing fills the placeholder, and it is never written as its text or as an empty
|
|
// list the provider would read as no consumers: the file is left out, said once, and the module composes exactly as
|
|
// it would without the file, so a provider below the handing contract keeps working from its grant files (issue 780).
|
|
func TestAProvidersConsumersFileIsLeftOutWhereItIsNotHanded(t *testing.T) {
|
|
withFile := notifierOn(t, held(t, handedProvider, builtProvider(consumersText)))
|
|
without := strings.Replace(handedProvider, `,
|
|
{"id":"consumers","type":"file","path":"${dir:state}/consumers.json","mode":"0600","template":"files/consumers.json"}`, "", 1)
|
|
if without == handedProvider {
|
|
t.Fatal("the manifest without the file was not made")
|
|
}
|
|
bare := notifierOn(t, held(t, strings.Replace(strings.Replace(without, `"moved":true,`, "", 1),
|
|
`"build":{"artifacts":[{"name":"files","kind":"archive","from":"files"}]},`, "", 1), nil))
|
|
for name, with := range map[string]Rendering{
|
|
"a node-engine that fills no file": {},
|
|
"a node-engine that fills, no client": {Hands: true, ArtifactStore: "store:5000"},
|
|
"a client, a node-engine that does not": {Provisioner: "mesh-provisioner-client", ArtifactStore: "store:5000"},
|
|
} {
|
|
c, got := composeWith(t, withFile, with)
|
|
_, want := composeWith(t, bare, with)
|
|
if got != want {
|
|
t.Errorf("%s: composes\n%s\nwithout its file, it composed\n%s", name, got, want)
|
|
}
|
|
if strings.Contains(got, "consumers") {
|
|
t.Errorf("%s: the declaration names the consumers' file: %s", name, got)
|
|
}
|
|
if len(c.HandedOnly) != 1 || !strings.Contains(c.HandedOnly[0], "left out, handed only") {
|
|
t.Errorf("%s: said %q, want the one line saying it is left out", name, c.HandedOnly)
|
|
}
|
|
if len(c.Unplaced) != 0 || len(c.Filled) != 0 || len(c.Plans) != 0 {
|
|
t.Errorf("%s: unplaced %v, filled %v, plans %v", name, c.Unplaced, c.Filled, c.Plans)
|
|
}
|
|
}
|
|
// Where it is handed, the file is there and nothing is said.
|
|
c, _ := composeWith(t, withFile, handingTo("store:5000"))
|
|
if len(c.HandedOnly) != 0 || len(c.Filled) != 1 {
|
|
t.Fatalf("handed: said %q, filled %v", c.HandedOnly, c.Filled)
|
|
}
|
|
}
|