package catalogue import ( "strings" "testing" ) // The fault 04-ISSUES/036 records: a media stack is several modules — a library server, the // acquisition managers, a download client — that must share directories on one machine. Written // as owned `directory` resources, two of them on one node are refused as rivalrous owners, which // is right in general and wrong here: sharing those directories is the whole point of the stack. // // Declared as accesses to an operator-owned path (novox/hq ADR 0051), they co-resolve. An access // is not a resource and never enters the duplicate-owner map, so this is the exact case the // resolver refuses above when the same path is owned — and does not, when it is merely accessed. func TestTwoModulesAccessingOnePathCoResolve(t *testing.T) { a := mod("a", nil, nil, nil) a.Accesses = []Access{{Path: "/services/media/downloads", Mode: AccessReadWrite}} b := mod("b", nil, nil, nil) b.Accesses = []Access{{Path: "/services/media/downloads", Mode: AccessRead}} got, err := Resolve(shelf(a, b), []string{"a", "b"}, workstation(), World{}) if err != nil { t.Fatalf("two modules accessing one operator-owned path were refused: %v", err) } // And each accessing module's grant reaches the machine as its own `access` resource, so the // host can find the path present or refuse — both naming the one operator-owned path. var accesses int for _, r := range mustDeclare(t, got) { if r["type"] == "access" && r["path"] == "/services/media/downloads" { accesses++ } } if accesses != 2 { t.Errorf("expected both modules' accesses in the declaration, got %d", accesses) } } // The mirror of the case above, so the sharing vocabulary does not weaken the rule it sits beside: // two modules OWNING one path is still refused, exactly as before accesses existed. func TestTwoModulesOwningOnePathAreStillRefused(t *testing.T) { a := mod("a", nil, nil, nil) a.Resources = []map[string]any{ {"id": "lib", "type": "directory", "path": "/services/media/movies", "mode": "0755"}} b := mod("b", nil, nil, nil) b.Resources = []map[string]any{ {"id": "lib", "type": "directory", "path": "/services/media/movies", "mode": "0755"}} _, err := Resolve(shelf(a, b), []string{"a", "b"}, workstation(), World{}) if err == nil { t.Fatal("two modules owning the same directory were both assigned") } if !strings.Contains(err.Error(), "/services/media/movies") { t.Errorf("the refusal does not name the path: %v", err) } } // Shared data is the operator's, owned by no module (novox/hq ADR 0051). A module owning the path // another module was told to expect the operator to provide would create and chown it — and the // two intentions cannot both hold, so it is refused by name. This is what keeps the distinction // machine-checkable rather than a convention nobody enforces. func TestAModuleMayNotOwnWhatAnotherAccesses(t *testing.T) { owns := mod("owns", nil, nil, nil) owns.Resources = []map[string]any{ {"id": "lib", "type": "directory", "path": "/services/media/movies", "mode": "0755"}} uses := mod("uses", nil, nil, nil) uses.Accesses = []Access{{Path: "/services/media/movies", Mode: AccessRead}} _, err := Resolve(shelf(owns, uses), []string{"owns", "uses"}, workstation(), World{}) if err == nil { t.Fatal("a module owning a path another accesses was allowed") } if !strings.Contains(err.Error(), "/services/media/movies") || !strings.Contains(err.Error(), "the operator's") { t.Errorf("the refusal does not explain the ownership conflict: %v", err) } } // A manifest states an access's extent, the way a listening port states its source. An absolute // path and a mode of read or read-write; anything else is refused rather than acted on wrongly. func TestAnAccessIsValidated(t *testing.T) { good := []byte(`{"module":"m","accesses":[{"path":"/services/media","mode":"read-write"}]}`) if _, err := ParseManifest(good); err != nil { t.Fatalf("a valid access was refused: %v", err) } // Mode is optional and narrows to read — the safe default, because the danger with an access // is being given more than was meant, not less. bare := []byte(`{"module":"m","accesses":[{"path":"/services/media"}]}`) m, err := ParseManifest(bare) if err != nil { t.Fatalf("an access with no mode was refused: %v", err) } if m.Accesses[0].At() != AccessRead { t.Errorf("an access with no mode is %q, want %q", m.Accesses[0].At(), AccessRead) } relative := []byte(`{"module":"m","accesses":[{"path":"services/media","mode":"read"}]}`) if _, err := ParseManifest(relative); err == nil { t.Error("a relative access path was accepted") } wrong := []byte(`{"module":"m","accesses":[{"path":"/services/media","mode":"append"}]}`) if _, err := ParseManifest(wrong); err == nil { t.Error("an access with an unknown mode was accepted") } }