//go:build unix package hostfiles import ( "context" "io" "os" "path/filepath" "reflect" "testing" "github.com/carlosframework/platform/internal/release" ) func write(t *testing.T, dir, rel, body string) { t.Helper() full := filepath.Join(dir, rel) if err := os.MkdirAll(filepath.Dir(full), 0o755); err != nil { t.Fatal(err) } if err := os.WriteFile(full, []byte(body), 0o640); err != nil { t.Fatal(err) } } func TestSyncDirPublishesNewChangedAndDeleted(t *testing.T) { ctx, s, src := context.Background(), release.NewDirStore(t.TempDir()), t.TempDir() p := open(t, s) write(t, src, "instance.key", "k") write(t, src, "uploads/a.txt", "a") st, err := SyncDir(ctx, p, src, nil) if err != nil || st.Published != 2 { t.Fatalf("first sync: %+v, %v", st, err) } commit(t, p) write(t, src, "uploads/a.txt", "a2") if err := os.Remove(filepath.Join(src, "instance.key")); err != nil { t.Fatal(err) } write(t, src, "new.txt", "n") st, err = SyncDir(ctx, p, src, nil) if err != nil || st.Published != 2 || st.Deleted != 1 { t.Fatalf("second sync: %+v, %v", st, err) } commit(t, p) dst := t.TempDir() if _, _, err := Restore(ctx, s, pfx, dst, nil); err != nil { t.Fatal(err) } if read(t, dst, "uploads/a.txt") != "a2" || read(t, dst, "new.txt") != "n" { t.Fatal("content differs") } if _, err := os.Stat(filepath.Join(dst, "instance.key")); !os.IsNotExist(err) { t.Fatal("deleted file restored") } fi, _ := os.Stat(filepath.Join(dst, "new.txt")) if fi.Mode().Perm() != 0o640 { t.Fatalf("mode = %v, want 0640", fi.Mode().Perm()) } } func TestSyncDirOfAnUnchangedDirectoryStagesNothing(t *testing.T) { ctx, s, src := context.Background(), release.NewDirStore(t.TempDir()), t.TempDir() p := open(t, s) write(t, src, "a", "a") if _, err := SyncDir(ctx, p, src, nil); err != nil { t.Fatal(err) } commit(t, p) st, err := SyncDir(ctx, p, src, nil) if err != nil || st.Published != 0 || st.Deleted != 0 || p.Dirty() { t.Fatalf("second sync: %+v dirty=%v err=%v", st, p.Dirty(), err) } } // Review Focus 2: never follow or open what is not a regular file. func TestSyncDirSkipsWhatIsNotARegularFile(t *testing.T) { ctx, s, src := context.Background(), release.NewDirStore(t.TempDir()), t.TempDir() outside := filepath.Join(t.TempDir(), "secret") if err := os.WriteFile(outside, []byte("do not publish"), 0o600); err != nil { t.Fatal(err) } write(t, src, "real.txt", "r") if err := os.Symlink(outside, filepath.Join(src, "link")); err != nil { t.Fatal(err) } if err := os.Symlink(filepath.Dir(outside), filepath.Join(src, "linkdir")); err != nil { t.Fatal(err) } p := open(t, s) st, err := SyncDir(ctx, p, src, nil) if err != nil { t.Fatal(err) } if st.Published != 1 || !reflect.DeepEqual(st.NotRegular, []string{"link", "linkdir"}) { t.Fatalf("stats = %+v", st) } if got := p.Paths(); !reflect.DeepEqual(got, []string{"real.txt"}) { t.Fatalf("Paths = %v", got) } } // Review Focus 3: too large is recorded, never read whole, never dropped. func TestSyncDirRecordsAFileOverTheCapWithoutReadingIt(t *testing.T) { ctx, s, src := context.Background(), release.NewDirStore(t.TempDir()), t.TempDir() big := filepath.Join(src, "huge.bin") f, err := os.Create(big) if err != nil { t.Fatal(err) } if err := f.Truncate(MaxFileBytes + 1); err != nil { // sparse: costs no disk t.Fatal(err) } f.Close() reads := 0 orig := readAll readAll = func(r io.Reader) ([]byte, error) { reads++; return orig(r) } t.Cleanup(func() { readAll = orig }) p := open(t, s) st, err := SyncDir(ctx, p, src, nil) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(st.TooLarge, []string{"huge.bin"}) || st.Published != 0 { t.Fatalf("stats = %+v", st) } if reads != 0 { t.Fatalf("the oversized file was read %d times; it must be refused on its size alone", reads) } commit(t, p) _, rs, err := Restore(ctx, s, pfx, t.TempDir(), nil) if err != nil || !reflect.DeepEqual(rs.Skipped, []string{"huge.bin"}) { t.Fatalf("restore skipped %v, err %v", rs.Skipped, err) } } func TestSyncDirHonoursSkip(t *testing.T) { ctx, s, src := context.Background(), release.NewDirStore(t.TempDir()), t.TempDir() write(t, src, "keep.txt", "k") write(t, src, ".files-gen", "marker") write(t, src, "cache/x", "x") p := open(t, s) skip := func(rel string, isDir bool) bool { return rel == ".files-gen" || (isDir && rel == "cache") } if _, err := SyncDir(ctx, p, src, skip); err != nil { t.Fatal(err) } if got := p.Paths(); !reflect.DeepEqual(got, []string{"keep.txt"}) { t.Fatalf("Paths = %v", got) } } func TestSyncDirOfAMissingDirectoryTombstonesNothing(t *testing.T) { ctx, s, src := context.Background(), release.NewDirStore(t.TempDir()), t.TempDir() p := open(t, s) write(t, src, "a", "a") if _, err := SyncDir(ctx, p, src, nil); err != nil { t.Fatal(err) } commit(t, p) if _, err := SyncDir(ctx, p, filepath.Join(src, "does-not-exist"), nil); err == nil { t.Fatal("a missing root must be an error, not an empty directory") } if p.Dirty() { t.Fatal("a missing root staged tombstones") } } // An excluded directory's files, published before the exclusion applied, // are left as the manifest has them: not republished, not tombstoned. func TestSyncDirDoesNotTombstoneWhatASkippedDirectoryHolds(t *testing.T) { ctx, s, src := context.Background(), release.NewDirStore(t.TempDir()), t.TempDir() write(t, src, "cache/x", "x") write(t, src, "keep.txt", "k") p := open(t, s) if _, err := SyncDir(ctx, p, src, nil); err != nil { t.Fatal(err) } commit(t, p) skip := func(rel string, isDir bool) bool { return isDir && rel == "cache" } st, err := SyncDir(ctx, p, src, skip) if err != nil || st.Deleted != 0 || p.Dirty() { t.Fatalf("stats = %+v dirty=%v err=%v", st, p.Dirty(), err) } if got := p.Paths(); !reflect.DeepEqual(got, []string{"cache/x", "keep.txt"}) { t.Fatalf("Paths = %v", got) } }