The engine names the filesystem a waiting folder is on; a warrant is recorded for that filesystem, and a folder on another is asked about again and not taken there meanwhile. Entries the engine could not take out while their disk was away, stated before a send, do not hold that delivery's gate. mesh-host pinned to d2d9fbbd.
171 lines
6.8 KiB
Go
171 lines
6.8 KiB
Go
package inventory
|
||
|
||
import (
|
||
"encoding/json"
|
||
"strings"
|
||
"testing"
|
||
|
||
"github.com/novox/mesh-controller/internal/catalogue"
|
||
)
|
||
|
||
// novox/hq ADR 0325 §3, §11: a module gets one allocation, the same whoever asks and however often, from the range
|
||
// of its own; no allocation is 0 or a root group's; a number once given is never given again.
|
||
func TestAModuleIsGivenOneIdentityOnceAndNeverRoot(t *testing.T) {
|
||
inv := fresh(t)
|
||
ctx := t.Context()
|
||
radarr, err := inv.AllocateIdentity(ctx, "radarr")
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
again, err := inv.AllocateIdentity(ctx, "radarr")
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if radarr != again {
|
||
t.Fatalf("radarr was given %+v, then %+v: one allocation per module", radarr, again)
|
||
}
|
||
plex, err := inv.AllocateIdentity(ctx, "plex")
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
for _, id := range []catalogue.ModuleIdentity{radarr, plex} {
|
||
if id.UID == 0 || id.GID == 0 || !catalogue.AllocatableID(id.UID) || !catalogue.AllocatableID(id.GID) ||
|
||
id.UID != id.GID {
|
||
t.Errorf("%s was given %d:%d: within %d–%d, never 0, uid and gid one number", id.Module, id.UID, id.GID,
|
||
catalogue.IdentityLow, catalogue.IdentityHigh)
|
||
}
|
||
}
|
||
if radarr.UID == plex.UID {
|
||
t.Fatalf("radarr and plex share %d", plex.UID)
|
||
}
|
||
|
||
// Read back as composition reads it, from another connection's view: the same numbers (stable across restarts
|
||
// and machines, since every machine's composition reads this one row).
|
||
all, err := inv.Identities(ctx)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if all["radarr"].UID != radarr.UID || all["plex"].UID != plex.UID {
|
||
t.Fatalf("the identities read back are %+v", all)
|
||
}
|
||
|
||
// A module forgotten keeps its row, and the next module is given a new number: never one given before.
|
||
if _, err := inv.store.Pool().Exec(ctx, `delete from module_identity where module = 'plex'`); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
sonarr, err := inv.AllocateIdentity(ctx, "sonarr")
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if sonarr.UID == plex.UID || sonarr.UID == radarr.UID {
|
||
t.Fatalf("sonarr was given %d, a number given before", sonarr.UID)
|
||
}
|
||
|
||
// The store itself refuses a number outside the range, root's above all: no write by hand makes one root.
|
||
for _, n := range []int{0, 10, 1000, 49999, 60000} {
|
||
if _, err := inv.store.Pool().Exec(ctx,
|
||
`insert into module_identity (module, uid, gid) values ($1, $2, $2)`, "byhand", n); err == nil {
|
||
t.Errorf("the store took %d as an identity", n)
|
||
}
|
||
}
|
||
}
|
||
|
||
// novox/hq ADR 0325 §8: a folder taken is recorded once per machine, under a warrant, and read back by its path.
|
||
func TestAFolderIsTakenOnceUnderAWarrant(t *testing.T) {
|
||
inv := fresh(t)
|
||
ctx := t.Context()
|
||
if _, err := inv.AddNode(ctx, "ace"); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if err := inv.RecordTaken(ctx, "ace", TakenFolder{Path: "/storage/media/movies/", By: "the operator"}); err == nil {
|
||
t.Fatal("a folder was recorded taken with no warrant")
|
||
}
|
||
first := TakenFolder{Path: "/storage/media/movies/", By: "the operator", Ask: "c01", Warrant: "c01", Said: "started"}
|
||
if err := inv.RecordTaken(ctx, "ace", first); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if err := inv.RecordTaken(ctx, "ace", TakenFolder{Path: "/storage/media/movies", By: "someone else", Ask: "c02",
|
||
Warrant: "c02"}); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
kept, err := inv.KeptFolders(ctx, "ace")
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
got, has := kept["/storage/media/movies"]
|
||
if !has || got.Ask != "c01" || got.By != "the operator" {
|
||
t.Fatalf("the taken folder reads %+v (%v): the first take, by its clean path", got, has)
|
||
}
|
||
// The folder moved to another filesystem and the operator was asked again: the new take replaces the old.
|
||
if err := inv.RecordTaken(ctx, "ace", TakenFolder{Path: "/storage/media/movies", By: "the operator", Ask: "c03",
|
||
Warrant: "c03", Placed: "fs-0000000000ff"}); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
kept, err = inv.KeptFolders(ctx, "ace")
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if got := kept["/storage/media/movies"]; got.Ask != "c03" || got.Placed != "fs-0000000000ff" {
|
||
t.Fatalf("after a take on another filesystem the folder reads %+v", got)
|
||
}
|
||
}
|
||
|
||
// novox/hq issue 575: a module registered without the access movies, whose newest build on its branch declares it —
|
||
// the build the delivery under way brings — takes a proposal placing movies: judged against that build, not refused.
|
||
// Without such a build it is refused, as before.
|
||
func TestAPlacementIsJudgedAgainstTheBuildThatBringsTheAccess(t *testing.T) {
|
||
inv := fresh(t)
|
||
ctx := t.Context()
|
||
if _, err := inv.AddNode(ctx, "ace"); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
registered := catalogue.Manifest{Module: "media-importer", Version: "1",
|
||
Accesses: []catalogue.Access{{ID: "downloads", Path: "/services/media/downloads", Mode: catalogue.AccessReadWrite}},
|
||
Resources: []map[string]any{{"id": "server", "type": "container", "name": "importer", "image": "x",
|
||
"volumes": []any{"${access:downloads}:/downloads"}}}}
|
||
if err := inv.RegisterModule(ctx, registered, Source{Repository: "https://forge.invalid/media.git",
|
||
Ref: "main", BuiltFrom: "c0ffee33"}); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
placing := map[string]any{catalogue.AccessesSetting: map[string]any{"movies": "/storage/media/movies"}}
|
||
|
||
err := inv.JudgeSettings(ctx, "ace", "media-importer", placing)
|
||
if err == nil || !strings.Contains(err.Error(), `places the access "movies", which it does not declare`) {
|
||
t.Fatalf("a placement no build declares: %v", err)
|
||
}
|
||
|
||
// main's manifest — the build of #34 — declares movies.
|
||
ahead := registered
|
||
ahead.Accesses = append([]catalogue.Access{}, registered.Accesses...)
|
||
ahead.Accesses = append(ahead.Accesses, catalogue.Access{ID: "movies", Path: "/services/media/movies",
|
||
Mode: catalogue.AccessReadWrite})
|
||
raw, err := json.Marshal(ahead)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
b := aBuild("b34", "media-importer", "")
|
||
b.Ref, b.Manifest = "main", raw
|
||
if err := inv.RecordBuild(ctx, b); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if err := inv.JudgeSettings(ctx, "ace", "media-importer", placing); err != nil {
|
||
t.Fatalf("the proposal placing movies, which main's build declares, was refused: %v", err)
|
||
}
|
||
if err := inv.SetSettings(ctx, "ace", "media-importer", placing); err != nil {
|
||
t.Fatalf("the layer placing movies was not stored: %v", err)
|
||
}
|
||
// Stored ahead, it does not cost the registered module its place: composition passes it by.
|
||
layers, err := inv.SettingsFor(ctx, "ace", "media-importer")
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if err := catalogue.ComposesWith(registered, layers, false); err != nil {
|
||
t.Fatalf("the registered module no longer composes with the layer stored ahead: %v", err)
|
||
}
|
||
// A typo is still a typo: an access neither build declares is refused.
|
||
typo := map[string]any{catalogue.AccessesSetting: map[string]any{"moveis": "/storage/media/movies"}}
|
||
if err := inv.JudgeSettings(ctx, "ace", "media-importer", typo); err == nil {
|
||
t.Fatal("a placement no build declares was taken")
|
||
}
|
||
}
|