carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1//go:build linux
2
3package hostfiles
4
5import (
6 "context"
7 "crypto/sha256"
8 "encoding/hex"
9 "os"
10 "path/filepath"
11 "reflect"
12 "testing"
13
14 "github.com/carlosframework/platform/internal/release"
15)
16
17// Astra round 2 finding 9: after a good install, a truncated env, another
18// key, a directory or a symlink in its place is not present. A manifest
19// without the env entry wants no env file only by the caller's evidence
20// (Task 8c: absence is never evidence).
21func TestPresentReadsTheEnvBack(t *testing.T) {
22 env := []byte("CARLOS_ADMIN_TOKEN=t\nCARLOS_INSTANCE_KEY=k1\n")
23 sum := sha256.Sum256(env)
24 m := Manifest{V: ManifestVersion, Entries: map[string]Entry{}, Platform: map[string]Entry{EnvItem: {Object: "o", SHA256: hex.EncodeToString(sum[:]), Size: int64(len(env)), Seq: 1}}}
25 for name, tc := range map[string]struct {
26 make func(path string) error
27 want bool
28 }{
29 "the published bytes": {func(p string) error { return os.WriteFile(p, env, 0o600) }, true},
30 "truncated": {func(p string) error { return os.WriteFile(p, env[:10], 0o600) }, false},
31 "another key, same size": {func(p string) error {
32 other := append([]byte(nil), env...)
33 other[len(other)-2] = '2'
34 return os.WriteFile(p, other, 0o600)
35 }, false},
36 "a directory": {func(p string) error { return os.Mkdir(p, 0o700) }, false},
37 "a symlink to the bytes": {func(p string) error {
38 real := p + ".real"
39 if err := os.WriteFile(real, env, 0o600); err != nil {
40 return err
41 }
42 return os.Symlink(real, p)
43 }, false},
44 "absent": {func(string) error { return nil }, false},
45 } {
46 t.Run(name, func(t *testing.T) {
47 dir := t.TempDir()
48 path := filepath.Join(dir, "shop.example.env")
49 if err := tc.make(path); err != nil {
50 t.Fatal(err)
51 }
52 if ok, err := Present(t.TempDir(), path, m, false); err != nil || ok != tc.want {
53 t.Fatalf("Present = %v, %v; want %v", ok, err, tc.want)
54 }
55 })
56 }
57 // Production never writes a platform tombstone: StagePlatformDelete
58 // drops the item. A manifest without the env entry is what a deleted
59 // env file and one never published both publish.
60 none := m
61 none.Platform = map[string]Entry{}
62 dir := t.TempDir()
63 path := filepath.Join(dir, "shop.example.env")
64 if ok, err := Present(dir, path, none, false); err != nil || ok {
65 t.Fatalf("no env entry and no evidence read as no env file: %v %v", ok, err)
66 }
67 if ok, err := Present(dir, path, none, true); err != nil || !ok {
68 t.Fatalf("evidenced no env file, none there: %v %v", ok, err)
69 }
70 if err := os.WriteFile(path, env, 0o600); err != nil {
71 t.Fatal(err)
72 }
73 if ok, _ := Present(dir, path, none, true); ok {
74 t.Fatal("evidenced no env file accepted an env file")
75 }
76 // The evidence never overrides a live entry, and a tombstone (which no
77 // publisher writes) is never present.
78 if ok, err := Present(dir, path, m, true); err != nil || !ok {
79 t.Fatalf("a live entry with its bytes: %v %v", ok, err)
80 }
81 if err := os.Remove(path); err != nil {
82 t.Fatal(err)
83 }
84 if ok, _ := Present(dir, path, m, true); ok {
85 t.Fatal("the evidence excused a live entry's missing env file")
86 }
87 tomb := m
88 tomb.Platform = map[string]Entry{EnvItem: {Deleted: true, Seq: 2}}
89 for _, evidenced := range []bool{false, true} {
90 if ok, err := Present(dir, path, tomb, evidenced); err != nil || ok {
91 t.Fatalf("a tombstone read as no env file (evidence %v): %v %v", evidenced, ok, err)
92 }
93 }
94}
95
96// Ruling 52: Present reads every live entry back at its size and digest;
97// deleted, torn and skipped entries are not looked for; a missing, short,
98// changed or non-regular file is not present; extra files are not checked.
99func TestPresentReadsTheManifestBack(t *testing.T) {
100 entry := func(body string) Entry {
101 sum := sha256.Sum256([]byte(body))
102 return Entry{Object: "o", SHA256: hex.EncodeToString(sum[:]), Size: int64(len(body)), Seq: 1}
103 }
104 m := Manifest{V: ManifestVersion, Platform: map[string]Entry{}, Entries: map[string]Entry{
105 "public/a.txt": entry("alpha"),
106 "b/c/d.bin": entry("delta"),
107 "gone.txt": {Deleted: true, Seq: 2},
108 "torn.db": {Torn: true, Seq: 2},
109 "skipped/big": {Skipped: "too large", Seq: 2},
110 }}
111 write := func(t *testing.T, dir string, files map[string]string) {
112 t.Helper()
113 for rel, body := range files {
114 p := filepath.Join(dir, filepath.FromSlash(rel))
115 if err := os.MkdirAll(filepath.Dir(p), 0o700); err != nil {
116 t.Fatal(err)
117 }
118 if err := os.WriteFile(p, []byte(body), 0o600); err != nil {
119 t.Fatal(err)
120 }
121 }
122 }
123 env := filepath.Join(t.TempDir(), "shop.example.env") // no EnvItem: absence evidenced by the caller
124 for name, tc := range map[string]struct {
125 files map[string]string
126 mutate func(t *testing.T, dir string)
127 want bool
128 }{
129 "every live entry": {map[string]string{"public/a.txt": "alpha", "b/c/d.bin": "delta"}, nil, true},
130 "plus files the app wrote": {map[string]string{"public/a.txt": "alpha", "b/c/d.bin": "delta", "extra": "x"}, nil, true},
131 "one missing": {map[string]string{"public/a.txt": "alpha"}, nil, false},
132 "one short": {map[string]string{"public/a.txt": "alph", "b/c/d.bin": "delta"}, nil, false},
133 "same size, other bytes": {map[string]string{"public/a.txt": "alphA", "b/c/d.bin": "delta"}, nil, false},
134 "a directory in its place": {map[string]string{"b/c/d.bin": "delta"}, func(t *testing.T, dir string) {
135 if err := os.MkdirAll(filepath.Join(dir, "public", "a.txt"), 0o700); err != nil {
136 t.Fatal(err)
137 }
138 }, false},
139 } {
140 t.Run(name, func(t *testing.T) {
141 dir := t.TempDir()
142 write(t, dir, tc.files)
143 if tc.mutate != nil {
144 tc.mutate(t, dir)
145 }
146 if ok, err := Present(dir, env, m, true); err != nil || ok != tc.want {
147 t.Fatalf("Present = %v, %v; want %v", ok, err, tc.want)
148 }
149 })
150 }
151 dir := t.TempDir()
152 write(t, dir, map[string]string{"public/a.txt": "alpha", "b/c/d.bin": "delta"})
153 if err := os.WriteFile(env, []byte("X=1\n"), 0o600); err != nil {
154 t.Fatal(err)
155 }
156 if ok, err := Present(dir, env, m, true); err != nil || ok {
157 t.Fatalf("an env file with no EnvItem was accepted: %v %v", ok, err)
158 }
159}
160
161// Stage B final review A1 (Astra 1): Restore resolves a live file and a
162// live entry beneath it by sequence and leaves the loser unwritten;
163// Present accepts exactly that, in both directions, through the one rule
164// (superseded). Without it VerifyLocal answers false forever and
165// EnsureLocal repairs on every pass.
166func TestPresentAcceptsWhatRestoreWroteForOverlappingEntries(t *testing.T) {
167 ctx := context.Background()
168 for name, tc := range map[string]struct {
169 stage [][2]string // in order: the later one is the newer
170 winner string
171 loser string
172 }{
173 "the file is newer than the entry beneath it": {[][2]string{{"a/b", "child"}, {"a", "file"}}, "a", "a/b"},
174 "the entry beneath is newer than the file": {[][2]string{{"a", "file"}, {"a/b", "child"}}, "a/b", "a"},
175 } {
176 t.Run(name, func(t *testing.T) {
177 s := release.NewDirStore(t.TempDir())
178 p := open(t, s)
179 for _, st := range tc.stage {
180 stage(t, p, st[0], st[1])
181 }
182 commit(t, p)
183 dir := t.TempDir()
184 m, st, err := Restore(ctx, s, pfx, dir, nil)
185 if err != nil || st.Written != 1 || !reflect.DeepEqual(st.Superseded, []string{tc.loser}) {
186 t.Fatalf("Restore: written %d, superseded %v, %v", st.Written, st.Superseded, err)
187 }
188 env := filepath.Join(t.TempDir(), "shop.example.env")
189 if ok, err := Present(dir, env, m, true); err != nil || !ok {
190 t.Fatalf("Present after Restore = %v, %v; want true", ok, err)
191 }
192 // The winner is still checked: without it the install is not whole.
193 if err := os.RemoveAll(filepath.Join(dir, filepath.FromSlash(tc.winner))); err != nil {
194 t.Fatal(err)
195 }
196 if ok, err := Present(dir, env, m, true); err != nil || ok {
197 t.Fatalf("Present without %s = %v, %v; want false", tc.winner, ok, err)
198 }
199 })
200 }
201}
202