Antonio Mika
·
2026-10-10
1package pgs
2
3import (
4 "bytes"
5 "io"
6 "log/slog"
7 "os"
8 "path/filepath"
9 "strings"
10 "testing"
11
12 "github.com/picosh/pico/pkg/shared"
13 "github.com/picosh/pico/pkg/storage"
14)
15
16type fakeCmdSession struct{ bytes.Buffer }
17
18func (s *fakeCmdSession) Exit(int) error { return nil }
19func (s *fakeCmdSession) Close() error { return nil }
20func (s *fakeCmdSession) Stderr() io.ReadWriter { return &s.Buffer }
21
22// Commands that change how a project is served purge its cached pages and
23// records.
24func TestCliPurges(t *testing.T) {
25 logger := slog.New(slog.DiscardHandler)
26 dbpool := NewPgsDb(logger)
27 user := dbpool.Users[0]
28 if _, err := dbpool.InsertProject(user.ID, "other", "other"); err != nil {
29 t.Fatal(err)
30 }
31 st, err := storage.NewStorageMemory(map[string]map[string]string{
32 shared.GetAssetBucketName(user.ID): {"/test/index.html": "hello"},
33 })
34 if err != nil {
35 t.Fatal(err)
36 }
37 cfg := NewConfig(logger, dbpool, st, discardPubsub{})
38 cmd := &Cmd{User: user, Session: &fakeCmdSession{}, Log: logger, Store: st, Dbpool: dbpool, Write: true, Cfg: cfg}
39
40 tests := []struct {
41 name string
42 run func() error
43 }{
44 {"acl", func() error { return cmd.acl("test", "private", nil) }},
45 {"link", func() error { return cmd.link("test", "other") }},
46 {"unlink", func() error { return cmd.unlink("test") }},
47 {"rm", func() error { return cmd.rm("test") }},
48 }
49 want := getSurrogateKey(user.Name, "test")
50 for _, tt := range tests {
51 if err := tt.run(); err != nil {
52 t.Fatalf("%s: %v", tt.name, err)
53 }
54 select {
55 case got := <-cfg.CacheClearingQueue:
56 if got != want {
57 t.Fatalf("%s purged %q, want %q", tt.name, got, want)
58 }
59 default:
60 t.Fatalf("%s purged nothing", tt.name)
61 }
62 for len(cfg.CacheClearingQueue) > 0 {
63 <-cfg.CacheClearingQueue
64 }
65 }
66}
67
68// Removing a project's assets also removes its directories, which storage
69// keeps after their last file is deleted.
70func TestRmProjectAssetsRemovesDirs(t *testing.T) {
71 logger := slog.New(slog.DiscardHandler)
72 dbpool := NewPgsDb(logger)
73 user := dbpool.Users[0]
74 st, err := storage.NewStorageFS(logger, t.TempDir())
75 if err != nil {
76 t.Fatal(err)
77 }
78 bucket, err := st.UpsertBucket(shared.GetAssetBucketName(user.ID))
79 if err != nil {
80 t.Fatal(err)
81 }
82 for _, name := range []string{"test/index.html", "test/css/deep/site.css", "other/index.html"} {
83 if _, _, err := st.PutObject(bucket, name, strings.NewReader("x"), &storage.ObjectInfo{}); err != nil {
84 t.Fatal(err)
85 }
86 }
87 if err := st.PutDir(bucket, "test/empty/nested"); err != nil {
88 t.Fatal(err)
89 }
90
91 cfg := NewConfig(logger, dbpool, st, discardPubsub{})
92 cmd := &Cmd{User: user, Session: &fakeCmdSession{}, Log: logger, Store: st, Dbpool: dbpool, Write: true, Cfg: cfg}
93 if err := cmd.RmProjectAssets("test"); err != nil {
94 t.Fatal(err)
95 }
96 if _, err := os.Stat(filepath.Join(bucket.Path, "test")); !os.IsNotExist(err) {
97 t.Fatalf("project directory still exists: %v", err)
98 }
99 if _, err := os.Stat(filepath.Join(bucket.Path, "other/index.html")); err != nil {
100 t.Fatalf("another project was touched: %v", err)
101 }
102}