package game // Regression (P0): a purged account must leave every one of its sessions in a // safe protocol state. A queued-mode Close only drops the transport after the // writer drains its outbox, so input already buffered in the connection can // still be dispatched to the owner after the purge; a session left in the // character menu with a nil account then dereferences sess.Account and kills // the owner goroutine (and with it every session and all unsaved state). import ( "testing" "thehouseoficarus/internal/net" ) func TestPurgeResetsEveryAccountSession(t *testing.T) { h := newHarness(t) h.seedAccount("alice") h.seedCharacter("alice", "alice") other := h.connectMenu("alice") purger := h.connectMenu("alice") h.g.RunOnOwner(func() { h.g.handlePurgeAccount(purger, "PURGE alice") }) for name, sess := range map[string]*net.Session{"other": other, "purger": purger} { if got := sess.State(); got != net.StateAccountName { t.Errorf("%s session state = %v, want StateAccountName", name, got) } if sess.Account != nil { t.Errorf("%s session retained the purged account: %+v", name, sess.Account) } } // Input already buffered in either transport is still routed to the owner: // it must land on the account-name prompt instead of faulting. before := h.g.owner.panics.Load() h.g.RunOnOwner(func() { h.g.handleMenu(other, "c") h.g.handleMenu(other, "l") h.g.handlePurgeAccount(purger, "PURGE alice") h.g.handleNewCharName(purger, "bob") }) if got := h.g.owner.panics.Load(); got != before { t.Fatalf("post-purge input panicked the owner (panic count %d -> %d)", before, got) } if got := other.State(); got != net.StateAccountName { t.Errorf("state = %v after post-purge input, want StateAccountName", got) } }