Files
dhkatzandClaude Opus 5.5 8742829e77
CI / build (push) Successful in 54s
t5de-catalog: store and serve community product files
A Go server that takes product uploads with hub upload tokens, checks them by
t5de-api's pack rules, stores files by sha256, registers revisions with the hub
(POST /api/v1/catalog/revisions), and serves files with immutable caching;
files used only by adult revisions need an adult access token. Contract:
t5de-hub's docs/products.md.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-29 18:59:16 -07:00

148 lines
3.8 KiB
Go

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)
}
}