package player import ( "encoding/base64" "encoding/binary" "strings" "testing" "gopkg.in/yaml.v3" ) func TestRoomSetYAMLRoundTrip(t *testing.T) { stats := PlayerStats{ RoomsVisited: NewRoomSetFrom(1, 5, 300), MobKills: map[string]int{"rat": 3}, TotalKills: 3, Deaths: 0, } data, err := yaml.Marshal(stats) if err != nil { t.Fatalf("marshal: %v", err) } out := string(data) if !strings.Contains(out, "rooms_visited:") { t.Fatalf("expected rooms_visited key in output:\n%s", out) } var loaded PlayerStats if err := yaml.Unmarshal(data, &loaded); err != nil { t.Fatalf("unmarshal: %v", err) } if loaded.RoomsVisited.Len() != 3 { t.Errorf("Len = %d, want 3", loaded.RoomsVisited.Len()) } for _, id := range []int{1, 5, 300} { if !loaded.RoomsVisited.Has(id) { t.Errorf("expected Has(%d) == true", id) } } if loaded.RoomsVisited.Has(2) { t.Error("expected Has(2) == false") } } func TestRoomSetOmitEmpty(t *testing.T) { stats := PlayerStats{} data, err := yaml.Marshal(stats) if err != nil { t.Fatalf("marshal: %v", err) } out := string(data) if strings.Contains(out, "rooms_visited:") { t.Errorf("expected rooms_visited to be absent for empty set:\n%s", out) } var loaded PlayerStats if err := yaml.Unmarshal(data, &loaded); err != nil { t.Fatalf("unmarshal: %v", err) } if loaded.RoomsVisited.Len() != 0 { t.Errorf("Len = %d, want 0", loaded.RoomsVisited.Len()) } } func TestRoomSetIsZero(t *testing.T) { var zero RoomSet if !zero.IsZero() { t.Error("zero value should be IsZero") } visited := NewRoomSetFrom(1) if visited.IsZero() { t.Error("non-empty set should not be IsZero") } } func TestRoomSetMarshalEmpty(t *testing.T) { var rs RoomSet v, err := rs.MarshalYAML() if err != nil { t.Fatalf("MarshalYAML: %v", err) } s, ok := v.(string) if !ok { t.Fatalf("MarshalYAML returned %T, want string", v) } if s != "" { t.Errorf("MarshalYAML empty = %q, want empty string", s) } } // Sparse, high room IDs are the reason RoomSet is not a dense bitset: the // encoding must stay proportional to the visited count, not the largest ID. func TestRoomSetSparseEncodingIsCompact(t *testing.T) { var rs RoomSet for _, id := range []int{1001, 1002, 1003, 2001, 2006, 99999301} { rs.Add(id) } data, err := yaml.Marshal(PlayerStats{RoomsVisited: rs}) if err != nil { t.Fatalf("marshal: %v", err) } if len(data) > 200 { t.Fatalf("encoding is not compact for sparse IDs: %d bytes\n%s", len(data), data) } var loaded PlayerStats if err := yaml.Unmarshal(data, &loaded); err != nil { t.Fatalf("unmarshal: %v", err) } for _, id := range []int{1001, 1002, 1003, 2001, 2006, 99999301} { if !loaded.RoomsVisited.Has(id) { t.Errorf("round trip lost room %d", id) } } if loaded.RoomsVisited.Has(5000) { t.Error("round trip invented room 5000") } } // The prototype has no legacy formats: anything without the v1 prefix is a // hard error rather than a silently-decoded old bitset. func TestRoomSetRejectsUnknownEncoding(t *testing.T) { var wrap struct { Visited RoomSet `yaml:"rooms_visited"` } if err := yaml.Unmarshal([]byte("rooms_visited: AQIDBA==\n"), &wrap); err == nil { t.Fatal("expected error for unprefixed encoding") } if err := yaml.Unmarshal([]byte("rooms_visited: [7, 42, 900]\n"), &wrap); err == nil { t.Fatal("expected error for YAML sequence encoding") } } func TestRoomSetRejectsOutOfRangeDelta(t *testing.T) { // A valid uvarint delta above roomSetMaxID must fail rather than wrap or // synthesize an absurd room ID. buf := binary.AppendUvarint(nil, uint64(roomSetMaxID)+1) encoded := roomSetV1Prefix + base64.StdEncoding.EncodeToString(buf) var wrap struct { Visited RoomSet `yaml:"rooms_visited"` } if err := yaml.Unmarshal([]byte("rooms_visited: "+encoded+"\n"), &wrap); err == nil { t.Fatal("expected error for out-of-range delta") } }