Files
cloud-svc/manifest_test.go
claude-timemachine 1752ef05a6
CI / validate (push) Successful in 26s
CI / docker (push) Failing after 8s
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.
2026-06-02 18:52:25 +02:00

110 lines
3.0 KiB
Go

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