initial: Steam-Cloud-style per-user state sync skeleton
HTTP API + on-disk storage + auth-service token verification + dev mode. 31 tests pass, vet clean. See DESIGN.md for the architecture and README.md for the operator surface. Pending: pg-backed per-user quota override, snapshot retention / blob GC, tarball-vs-manifest content cross-check, end-to-end deploy on john.
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func goodFileEntry() FileEntry {
|
||||
return FileEntry{
|
||||
SHA256: strings.Repeat("ab", 32),
|
||||
Size: 100,
|
||||
Mtime: time.Date(2026, 6, 2, 12, 0, 0, 0, time.UTC),
|
||||
}
|
||||
}
|
||||
|
||||
func TestManifest_Validate_OK(t *testing.T) {
|
||||
m := Manifest{
|
||||
SnapshotID: "01J9XQK4Z3ABCDEF",
|
||||
CreatedAt: time.Now().UTC(),
|
||||
Files: map[string]FileEntry{
|
||||
"options.txt": goodFileEntry(),
|
||||
"config/voicechat-client.json": goodFileEntry(),
|
||||
"journeymap/data/sp/world/m.bin": goodFileEntry(),
|
||||
},
|
||||
}
|
||||
if err := m.Validate(); err != nil {
|
||||
t.Fatalf("expected nil, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestManifest_Validate_RejectsBadPaths(t *testing.T) {
|
||||
cases := map[string]string{
|
||||
"": "empty",
|
||||
"/etc/passwd": "absolute",
|
||||
"../escape": "traversal",
|
||||
"./current": "non-canonical",
|
||||
"config//double": "non-canonical empty segment",
|
||||
`config\back`: "backslash",
|
||||
"a/../../b": "traversal in middle",
|
||||
}
|
||||
for badPath, why := range cases {
|
||||
t.Run(why, func(t *testing.T) {
|
||||
m := Manifest{
|
||||
SnapshotID: "x",
|
||||
CreatedAt: time.Now().UTC(),
|
||||
Files: map[string]FileEntry{badPath: goodFileEntry()},
|
||||
}
|
||||
if err := m.Validate(); err == nil {
|
||||
t.Errorf("path %q (%s): expected error, got nil", badPath, why)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestManifest_Validate_RejectsBadSHA(t *testing.T) {
|
||||
cases := map[string]FileEntry{
|
||||
"too short": {SHA256: "abcd", Size: 1, Mtime: time.Now()},
|
||||
"non-hex": {SHA256: strings.Repeat("zz", 32), Size: 1, Mtime: time.Now()},
|
||||
"empty": {SHA256: "", Size: 1, Mtime: time.Now()},
|
||||
"negative size": {SHA256: strings.Repeat("ab", 32), Size: -1, Mtime: time.Now()},
|
||||
"zero mtime": {SHA256: strings.Repeat("ab", 32), Size: 1},
|
||||
}
|
||||
for name, fe := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
m := Manifest{
|
||||
SnapshotID: "x",
|
||||
CreatedAt: time.Now().UTC(),
|
||||
Files: map[string]FileEntry{"options.txt": fe},
|
||||
}
|
||||
if err := m.Validate(); err == nil {
|
||||
t.Errorf("%s: expected error, got nil", name)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestManifest_Validate_EmptySnapshotID(t *testing.T) {
|
||||
m := Manifest{CreatedAt: time.Now().UTC()}
|
||||
if err := m.Validate(); err == nil {
|
||||
t.Errorf("empty snapshot_id: expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadManifest_RejectsUnknownFields(t *testing.T) {
|
||||
body := `{"snapshot_id":"x","created_at":"2026-01-01T00:00:00Z","files":{},"surprise":true}`
|
||||
_, err := ReadManifest(bytes.NewBufferString(body))
|
||||
if err == nil {
|
||||
t.Fatal("expected error for unknown field, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadManifest_RejectsInvalidManifest(t *testing.T) {
|
||||
// Well-formed JSON but bad sha
|
||||
body := `{"snapshot_id":"x","created_at":"2026-01-01T00:00:00Z","files":{"options.txt":{"sha256":"short","size":1,"mtime":"2026-01-01T00:00:00Z"}}}`
|
||||
_, err := ReadManifest(bytes.NewBufferString(body))
|
||||
if err == nil {
|
||||
t.Fatal("expected validation error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHashBytes(t *testing.T) {
|
||||
got := HashBytes([]byte("hello"))
|
||||
want := "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
|
||||
if got != want {
|
||||
t.Errorf("got %s, want %s", got, want)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user