package store import ( "crypto/sha256" "encoding/hex" "errors" "io" "os" "path/filepath" "reflect" "testing" ) func open(t *testing.T, dir string) *Store { t.Helper() s, err := Open(dir) if err != nil { t.Fatal(err) } t.Cleanup(func() { s.Close() }) return s } func hashOf(data string) string { sum := sha256.Sum256([]byte(data)) return hex.EncodeToString(sum[:]) } func TestFiles(t *testing.T) { dir := t.TempDir() s := open(t, dir) hash, err := s.PutFile([]byte("hello")) if err != nil { t.Fatal(err) } if hash != hashOf("hello") { t.Fatalf("hash %s", hash) } want := filepath.Join(dir, "files", hash[:2], hash) if s.Path(hash) != want { t.Fatalf("path %s, want %s", s.Path(hash), want) } again, err := s.PutFile([]byte("hello")) if err != nil || again != hash { t.Fatalf("again: %s, %v", again, err) } f, err := s.Open(hash) if err != nil { t.Fatal(err) } data, _ := io.ReadAll(f) f.Close() if string(data) != "hello" { t.Fatalf("data %q", data) } // No temp files left beside it. entries, _ := os.ReadDir(filepath.Dir(want)) if len(entries) != 1 { t.Fatalf("%d entries in %s", len(entries), filepath.Dir(want)) } if _, err := s.Open(hashOf("missing")); !errors.Is(err, ErrNotFound) { t.Fatalf("missing: %v", err) } for _, bad := range []string{"", "../catalog.db", hash[:63], "A" + hash[1:]} { if _, err := s.Open(bad); !errors.Is(err, ErrNotFound) { t.Fatalf("%q: %v", bad, err) } } } func TestRevisions(t *testing.T) { dir := t.TempDir() s := open(t, dir) shared, general, adult := hashOf("shared"), hashOf("general"), hashOf("adult") if hosted, _, err := s.FileAccess(shared); err != nil || hosted { t.Fatalf("before: hosted %v, %v", hosted, err) } a := Revision{Product: "aaaaaaaaaa", Revision: 1, Rating: Adult, Files: []File{ {Name: "a.png", SHA256: adult, Size: 5}, {Name: "index.xml", SHA256: shared, Size: 6}, }} if err := s.AddRevision(a); err != nil { t.Fatal(err) } check := func(hash string, wantHosted, wantAdult bool) { t.Helper() hosted, adultOnly, err := s.FileAccess(hash) if err != nil || hosted != wantHosted || adultOnly != wantAdult { t.Fatalf("%s: hosted %v adult %v (%v), want %v %v", hash[:8], hosted, adultOnly, err, wantHosted, wantAdult) } } check(shared, true, true) check(adult, true, true) check(general, false, false) g := Revision{Product: "bbbbbbbbbb", Revision: 2, Rating: General, Files: []File{ {Name: "g.png", SHA256: general, Size: 7}, {Name: "index.xml", SHA256: shared, Size: 6}, }} if err := s.AddRevision(g); err != nil { t.Fatal(err) } check(shared, true, false) // also in a general revision check(adult, true, true) check(general, true, false) got, err := s.Revision("aaaaaaaaaa", 1) if err != nil || !reflect.DeepEqual(*got, a) { t.Fatalf("got %+v, %v", got, err) } if _, err := s.Revision("aaaaaaaaaa", 2); !errors.Is(err, ErrNotFound) { t.Fatalf("missing revision: %v", err) } if err := s.AddRevision(Revision{Product: "c", Revision: 1, Rating: "GA"}); err == nil { t.Fatal("bad rating accepted") } // Recording a revision again replaces it, and the data survives reopening. a.Rating = General if err := s.AddRevision(a); err != nil { t.Fatal(err) } s.Close() s = open(t, dir) got, err = s.Revision("aaaaaaaaaa", 1) if err != nil || got.Rating != General { t.Fatalf("after reopening: %+v, %v", got, err) } if hosted, adultOnly, _ := s.FileAccess(adult); !hosted || adultOnly { t.Fatal("adult file still adult-only after the revision became general") } } func TestTempCleared(t *testing.T) { dir := t.TempDir() s := open(t, dir) if err := os.WriteFile(filepath.Join(s.TempDir(), "leftover"), []byte("x"), 0o644); err != nil { t.Fatal(err) } s.Close() s = open(t, dir) if entries, _ := os.ReadDir(s.TempDir()); len(entries) != 0 { t.Fatalf("temp folder not emptied: %v", entries) } }