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