diff options
Diffstat (limited to 'internal/world')
| -rw-r--r-- | internal/world/hardening_room_id_mismatch_test.go | 73 | ||||
| -rw-r--r-- | internal/world/world.go | 8 |
2 files changed, 81 insertions, 0 deletions
diff --git a/internal/world/hardening_room_id_mismatch_test.go b/internal/world/hardening_room_id_mismatch_test.go new file mode 100644 index 0000000..1ac1711 --- /dev/null +++ b/internal/world/hardening_room_id_mismatch_test.go @@ -0,0 +1,73 @@ +package world + +import ( + "bytes" + "log" + "os" + "path/filepath" + "strings" + "testing" +) + +// Regression: a room file whose declared id: disagrees with its numeric +// filename was silently loaded under the filename with no diagnostic. +// World.LoadRoom must warn, because room identity comes from the filename. + +func writeRoomFile(t *testing.T, dir, name, body string) { + t.Helper() + path := filepath.Join(dir, "rooms", name) + if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(path, []byte(body), 0o644); err != nil { + t.Fatal(err) + } +} + +func captureRoomLog(t *testing.T, fn func()) string { + t.Helper() + var buf bytes.Buffer + old := log.Writer() + log.SetOutput(&buf) + defer log.SetOutput(old) + fn() + return buf.String() +} + +func TestWorldWarnsOnDeclaredRoomIDMismatch(t *testing.T) { + dir := t.TempDir() + writeRoomFile(t, dir, "1.yaml", "id: 2\nname: Wrong Id\n") + w := New(dir) + + out := captureRoomLog(t, func() { + room, err := w.LoadRoom(1) + if err != nil { + t.Fatalf("load room: %v", err) + } + if room.ID != 1 { + t.Fatalf("filename must win: room.ID = %d, want 1", room.ID) + } + }) + if !strings.Contains(out, "declares id") { + t.Fatalf("expected a room id-mismatch warning, got log %q", out) + } +} + +func TestWorldDoesNotWarnWhenRoomIDMatchesOrAbsent(t *testing.T) { + dir := t.TempDir() + writeRoomFile(t, dir, "1.yaml", "id: 1\nname: Match\n") + writeRoomFile(t, dir, "2.yaml", "name: Absent\n") + w := New(dir) + + out := captureRoomLog(t, func() { + if _, err := w.LoadRoom(1); err != nil { + t.Fatal(err) + } + if _, err := w.LoadRoom(2); err != nil { + t.Fatal(err) + } + }) + if strings.Contains(out, "declares id") { + t.Fatalf("unexpected room id-mismatch warning: %q", out) + } +} diff --git a/internal/world/world.go b/internal/world/world.go index c5d0841..6a0313d 100644 --- a/internal/world/world.go +++ b/internal/world/world.go @@ -2,6 +2,7 @@ package world import ( "fmt" + "log" "os" "path/filepath" "strconv" @@ -52,6 +53,13 @@ func (w *World) LoadRoom(id int) (*Room, error) { if err := yaml.Unmarshal(data, &room); err != nil { return nil, fmt.Errorf("parse room %d: %w", id, err) } + // Room ID comes from the filename (room.ID = id below); a mismatched + // declared id: is silently ignored, so surface it rather than leave the + // author believing their written id is in use. + if room.ID != 0 && room.ID != id { + log.Printf("room %d: file %s declares id %d but is loaded as room %d; the filename is authoritative and the declared id is ignored", + id, path, room.ID, id) + } room.ID = id if room.Exits == nil { room.Exits = make(map[ExitDir]ExitDef) |
