package apply import ( "os" "path/filepath" "strconv" "testing" ) // A declaration this host does not fully understand is refused whole — the property the whole // project is built around, tested at the boundary it matters most. func TestParseRefusesWhatItCannotFullyUnderstand(t *testing.T) { cases := map[string]string{ "unknown version": `{"version":2,"resources":[]}`, "unknown type": `{"version":1,"resources":[ {"id":"a","type":"container","path":"/x"}]}`, "unknown field on a known type": `{"version":1,"resources":[ {"id":"a","type":"directory","path":"/x","colour":"blue"}]}`, "unknown top-level field": `{"version":1,"nodes":[],"resources":[]}`, "resource without an id": `{"version":1,"resources":[{"type":"directory","path":"/x"}]}`, "resource without a type": `{"version":1,"resources":[{"id":"a","path":"/x"}]}`, "resource without a path": `{"version":1,"resources":[{"id":"a","type":"directory"}]}`, "two resources sharing id": `{"version":1,"resources":[{"id":"a","type":"directory","path":"/x"},{"id":"a","type":"directory","path":"/y"}]}`, } for name, raw := range cases { t.Run(name, func(t *testing.T) { if _, err := Parse([]byte(raw)); err == nil { t.Fatalf("accepted a declaration it should have refused whole") } }) } } func TestParseAcceptsAWellFormedDeclaration(t *testing.T) { raw := `{"version":1,"for":"anchor","resources":[ {"id":"state","type":"directory","path":"/var/lib/x","mode":"0700"}, {"id":"conf","type":"file","path":"/var/lib/x/conf","mode":"0600","content":"k=v\n"}]}` d, err := Parse([]byte(raw)) if err != nil { t.Fatal(err) } if d.For != "anchor" || len(d.Resources) != 2 { t.Fatalf("parsed wrong: %+v", d) } if d.Resources[0].Path() != "/var/lib/x" { t.Fatalf("path accessor wrong: %q", d.Resources[0].Path()) } } // Applying a declaration lands the resources, and applying it again changes nothing — the // idempotency the node host promises. func TestApplyIsIdempotent(t *testing.T) { root := t.TempDir() storePath := filepath.Join(root, "store.json") dir := filepath.Join(root, "svc") file := filepath.Join(dir, "conf") decl := mustParse(t, `{"version":1,"resources":[ {"id":"d","type":"directory","path":"`+dir+`","mode":"0755"}, {"id":"f","type":"file","path":"`+file+`","mode":"0644","content":"hello\n"}]}`) for i := 0; i < 2; i++ { store, err := LoadStore(storePath) if err != nil { t.Fatal(err) } res, err := Apply(decl, "", store, Appliers()) if err != nil { t.Fatalf("apply %d: %v", i, err) } if len(res.Applied) != 2 { t.Fatalf("apply %d: expected 2 applied, got %d", i, len(res.Applied)) } } if got, _ := os.ReadFile(file); string(got) != "hello\n" { t.Fatalf("file content wrong: %q", got) } } // A resource dropped from a declaration is removed on the next apply — but only because the host // created it. This is desired-state convergence with the data-loss guard intact. func TestUndeclaredResourceIsRemovedWhenHostCreatedIt(t *testing.T) { root := t.TempDir() storePath := filepath.Join(root, "store.json") dir := filepath.Join(root, "svc") gone := filepath.Join(dir, "gone") kept := filepath.Join(dir, "kept") first := mustParse(t, `{"version":1,"resources":[ {"id":"d","type":"directory","path":"`+dir+`"}, {"id":"gone","type":"file","path":"`+gone+`","content":"x"}, {"id":"kept","type":"file","path":"`+kept+`","content":"y"}]}`) store, _ := LoadStore(storePath) if _, err := Apply(first, "", store, Appliers()); err != nil { t.Fatal(err) } if _, err := os.Stat(gone); err != nil { t.Fatalf("first apply did not create the file: %v", err) } second := mustParse(t, `{"version":1,"resources":[ {"id":"d","type":"directory","path":"`+dir+`"}, {"id":"kept","type":"file","path":"`+kept+`","content":"y"}]}`) store, _ = LoadStore(storePath) res, err := Apply(second, "", store, Appliers()) if err != nil { t.Fatal(err) } if len(res.Removed) != 1 || res.Removed[0].ID != "gone" { t.Fatalf("expected 'gone' removed, got %+v", res.Removed) } if _, err := os.Stat(gone); !os.IsNotExist(err) { t.Fatalf("the undeclared file was not removed") } if _, err := os.Stat(kept); err != nil { t.Fatalf("the still-declared file was wrongly removed: %v", err) } } // "created" is sticky across re-applies. A resource created once, then re-applied (so it already // exists the second time), must still be removed when later dropped — the host does not forget it // was the creator just because the resource was present on a subsequent apply. func TestCreatedIsStickyAcrossReapplies(t *testing.T) { root := t.TempDir() storePath := filepath.Join(root, "store.json") dir := filepath.Join(root, "svc") file := filepath.Join(dir, "conf") full := mustParse(t, `{"version":1,"resources":[ {"id":"d","type":"directory","path":"`+dir+`"}, {"id":"f","type":"file","path":"`+file+`","content":"x"}]}`) // Apply it twice. On the second apply everything already exists, so a naive "created" would // flip to false and the file would later be treated as adopted. for i := 0; i < 2; i++ { store, _ := LoadStore(storePath) if _, err := Apply(full, "", store, Appliers()); err != nil { t.Fatalf("apply %d: %v", i, err) } } dropped := mustParse(t, `{"version":1,"resources":[ {"id":"d","type":"directory","path":"`+dir+`"}]}`) store, _ := LoadStore(storePath) res, err := Apply(dropped, "", store, Appliers()) if err != nil { t.Fatal(err) } if len(res.Removed) != 1 || res.Removed[0].ID != "f" { t.Fatalf("re-applied-then-dropped file was not removed: %+v", res.Removed) } if _, err := os.Stat(file); !os.IsNotExist(err) { t.Fatal("host reported the file removed but it is still on disk (created flag was not sticky)") } } // The host never removes what it did not create. A directory it merely adopted — one that // already existed, holding data — survives being dropped from the declaration. func TestAdoptedResourceIsNeverRemoved(t *testing.T) { root := t.TempDir() storePath := filepath.Join(root, "store.json") existing := filepath.Join(root, "data") // pre-exists: the host will adopt, not create it if err := os.Mkdir(existing, 0o755); err != nil { t.Fatal(err) } sentinel := filepath.Join(existing, "precious") if err := os.WriteFile(sentinel, []byte("workload data"), 0o644); err != nil { t.Fatal(err) } first := mustParse(t, `{"version":1,"resources":[ {"id":"data","type":"directory","path":"`+existing+`","mode":"0755"}]}`) store, _ := LoadStore(storePath) res, err := Apply(first, "", store, Appliers()) if err != nil { t.Fatal(err) } if res.Applied[0].Created { t.Fatalf("host claimed to have created a directory that already existed") } // Drop it from the declaration entirely. An adopted directory is not the host's to remove. empty := mustParse(t, `{"version":1,"resources":[]}`) store, _ = LoadStore(storePath) if _, err := Apply(empty, "", store, Appliers()); err != nil { t.Fatal(err) } if _, err := os.Stat(sentinel); err != nil { t.Fatalf("adopted directory (and its data) was removed — the guard failed: %v", err) } } // A failed step fails the apply, and the store records what actually landed — never more. func TestFailedStepFailsTheApplyAndRecordsOnlyWhatLanded(t *testing.T) { root := t.TempDir() storePath := filepath.Join(root, "store.json") good := filepath.Join(root, "good") // The second file's parent does not exist, so writing it fails — a mid-declaration failure. bad := filepath.Join(root, "nonexistent-dir", "bad") decl := mustParse(t, `{"version":1,"resources":[ {"id":"good","type":"file","path":"`+good+`","content":"ok"}, {"id":"bad","type":"file","path":"`+bad+`","content":"no"}]}`) store, _ := LoadStore(storePath) if _, err := Apply(decl, "", store, Appliers()); err == nil { t.Fatal("apply reported success despite a step that could not be done") } // The store must record 'good' (it landed) and not 'bad' (it did not). reloaded, _ := LoadStore(storePath) ids := map[string]bool{} for _, r := range reloaded.Records() { ids[r.ID] = true } if !ids["good"] { t.Fatalf("the store forgot a resource that actually landed") } if ids["bad"] { t.Fatalf("the store recorded a resource that never landed — a report from intent") } } // A declaration addressed to another node is refused; one addressed here, or unaddressed, is // applied. func TestAddressing(t *testing.T) { root := t.TempDir() dir := filepath.Join(root, "d") decl := mustParse(t, `{"version":1,"for":"anchor","resources":[ {"id":"d","type":"directory","path":"`+dir+`"}]}`) store, _ := LoadStore(filepath.Join(root, "s1.json")) if _, err := Apply(decl, "workstation", store, Appliers()); err == nil { t.Fatal("a node applied a declaration addressed to a different node") } store, _ = LoadStore(filepath.Join(root, "s2.json")) if _, err := Apply(decl, "anchor", store, Appliers()); err != nil { t.Fatalf("a node refused a declaration addressed to it: %v", err) } // The first node — no identity yet — applies whatever it carries. store, _ = LoadStore(filepath.Join(root, "s3.json")) if _, err := Apply(decl, "", store, Appliers()); err != nil { t.Fatalf("a node with no identity refused its own bundle: %v", err) } } // Read-back catches a value that did not take. Mode and owner are asserted against the machine // after applying, so a file written with the wrong permissions is a failure, not a success. func TestApplyReadsModeBack(t *testing.T) { root := t.TempDir() file := filepath.Join(root, "f") decl := mustParse(t, `{"version":1,"resources":[ {"id":"f","type":"file","path":"`+file+`","mode":"0600","content":"x"}]}`) store, _ := LoadStore(filepath.Join(root, "s.json")) if _, err := Apply(decl, "", store, Appliers()); err != nil { t.Fatal(err) } info, _ := os.Stat(file) if info.Mode().Perm() != 0o600 { t.Fatalf("mode not applied: %04o", info.Mode().Perm()) } } // A directory the host created but that now holds something is not removed — os.Remove refuses a // non-empty directory, and that refusal is the guard, surfaced as a failed apply. func TestCreatedDirectoryHoldingDataIsNotSilentlyDeleted(t *testing.T) { root := t.TempDir() storePath := filepath.Join(root, "store.json") dir := filepath.Join(root, "svc") first := mustParse(t, `{"version":1,"resources":[ {"id":"d","type":"directory","path":"`+dir+`"}]}`) store, _ := LoadStore(storePath) if _, err := Apply(first, "", store, Appliers()); err != nil { t.Fatal(err) } // Something drops data into the host-created directory after the fact. if err := os.WriteFile(filepath.Join(dir, "appeared"), []byte("data"), 0o644); err != nil { t.Fatal(err) } empty := mustParse(t, `{"version":1,"resources":[]}`) store, _ = LoadStore(storePath) if _, err := Apply(empty, "", store, Appliers()); err == nil { t.Fatal("host deleted, or claimed to delete, a non-empty directory it once created") } if _, err := os.Stat(filepath.Join(dir, "appeared")); err != nil { t.Fatalf("data in the directory was lost: %v", err) } } func mustParse(t *testing.T, raw string) Declaration { t.Helper() d, err := Parse([]byte(raw)) if err != nil { t.Fatalf("test declaration did not parse: %v", err) } return d } // Sanity: the current uid is what owner read-back compares against, so an owner naming this user // verifies rather than needing root. func TestOwnerReadBackAgainstCurrentUser(t *testing.T) { root := t.TempDir() dir := filepath.Join(root, "d") owner := strconv.Itoa(os.Getuid()) + ":" + strconv.Itoa(os.Getgid()) decl := mustParse(t, `{"version":1,"resources":[ {"id":"d","type":"directory","path":"`+dir+`","mode":"0755","owner":"`+owner+`"}]}`) store, _ := LoadStore(filepath.Join(root, "s.json")) if _, err := Apply(decl, "", store, Appliers()); err != nil { t.Fatalf("applying an owner matching the current user failed: %v", err) } }