diff --git a/docs/FALUKANT_DEATH_SUCCESSION_SMOKE_TEST.md b/docs/FALUKANT_DEATH_SUCCESSION_SMOKE_TEST.md new file mode 100644 index 0000000..3c2debb --- /dev/null +++ b/docs/FALUKANT_DEATH_SUCCESSION_SMOKE_TEST.md @@ -0,0 +1,110 @@ +# Falukant Smoke-Test: Tod -> Erbwechsel -> UI-Status + +Dieser Smoke-Test validiert den kritischen Ablauf: + +- Spieler-Charakter stirbt +- Erbe wird gesetzt +- alte charakterbezogene Daten sind bereinigt +- UI bekommt verlässlich den Status-Refresh + +## Ziel + +Verhindern, dass nach Tod/Erbe ein Mischzustand entsteht +(z. B. Erben-Bild, aber alte Statusdaten des verstorbenen Charakters). + +## Voraussetzungen + +- Daemon läuft mit aktuellen Änderungen. +- Zugriff auf DB (psql). +- Zugriff auf WebSocket-Eventstream oder Daemon-Log. +- Ein Test-User mit: + - aktivem Spieler-Charakter (`user_id` gesetzt), + - mindestens einem potenziellen Erben in `child_relation`. + +## Testfall A: Tod über `UserCharacterWorker` (health <= 0) + +1. Testkandidaten wählen: + +```sql +SELECT c.id AS character_id, c.user_id +FROM falukant_data.character c +WHERE c.user_id IS NOT NULL +ORDER BY c.updated_at DESC +LIMIT 20; +``` + +2. Einen Charakter künstlich auf `health = 0` setzen: + +```sql +UPDATE falukant_data.character +SET health = 0, updated_at = NOW() +WHERE id = ; +``` + +3. Worker laufen lassen (Death-Check oder nächster Verarbeitungstakt). + +4. WebSocket/Log prüfen, erwartet: + - `CharacterDeath` mit altem `character_id` + - `falukantUpdateStatus` mit `user_id` des betroffenen Spielers + +5. DB prüfen, erwartet: + +```sql +-- alter Charakter weg +SELECT 1 +FROM falukant_data.character +WHERE id = ; + +-- Spieler zeigt auf Erben +SELECT c.id, c.user_id +FROM falukant_data.character c +WHERE c.user_id = ; +``` + +```sql +-- Todes-Cleanup (sollte leer sein) +SELECT COUNT(*) FROM falukant_data.knowledge WHERE character_id = ; +SELECT COUNT(*) FROM falukant_data.debtors_prism WHERE character_id = ; +SELECT COUNT(*) FROM falukant_data.political_office WHERE character_id = ; +SELECT COUNT(*) FROM falukant_data.election_candidate WHERE character_id = ; +``` + +```sql +-- politische Ämter historisiert (falls vorher vorhanden) +SELECT * +FROM falukant_log.political_office_history +WHERE character_id = +ORDER BY end_date DESC +LIMIT 5; +``` + +Passkriterium: +- alter Charakter gelöscht, +- Nachfolger gesetzt, +- Cleanup-Counts = 0, +- `falukantUpdateStatus` gesendet. + +## Testfall B: Tod über `EventsWorker` + +Der gleiche Prüfsatz wie in Testfall A, aber mit Tod aus Event-Pfad. + +Hinweis: +- Falls kein reproduzierbares Zufallsereignis vorliegt, kann derselbe technische Endzustand + pragmatisch über A validiert werden. +- Zusätzlicher Fokus hier: auch im Event-Pfad müssen Cleanup + Status-Refresh identisch sein. + +## UI-Schnellcheck + +Nach Eintreffen von `falukantUpdateStatus`: + +- Avatar/Bild entspricht dem Erben. +- Statusdaten (Familie/Politik/bezogene Anzeigen) zeigen keine Relikte des Verstorbenen. +- Kein inkonsistenter Mischzustand nach manuellem Reload. + +## Regression-Checkliste + +- [ ] `CharacterDeath` kommt genau für den verstorbenen Charakter. +- [ ] `falukantUpdateStatus` kommt nach Erbwechsel für den betroffenen User. +- [ ] `knowledge`/`debtors_prism`/`political_office`/`election_candidate` sind bereinigt. +- [ ] `political_office_history` enthält entfernte Ämter (falls vorhanden). +- [ ] UI bleibt konsistent (Bild + Status gehören zum selben Charakter). diff --git a/src/worker/events.rs b/src/worker/events.rs index 54068c7..0344159 100644 --- a/src/worker/events.rs +++ b/src/worker/events.rs @@ -46,7 +46,11 @@ use crate::worker::sql::{ QUERY_GET_USER_ID, QUERY_DELETE_CHILD_RELATION, QUERY_DELETE_CHILD_RELATION_BY_PARENT, + QUERY_DELETE_DEBTORS_PRISM, QUERY_DELETE_CHARACTER, + QUERY_DELETE_ELECTION_CANDIDATE, + QUERY_DELETE_KNOWLEDGE, + QUERY_DELETE_POLITICAL_OFFICE, QUERY_GET_HEIR, QUERY_SET_CHARACTER_USER, QUERY_GET_CURRENT_MONEY, @@ -1815,6 +1819,12 @@ impl EventsWorker { broker: &MessageBroker, character_id: i32, ) -> Result<(), DbError> { + let death_event = format!( + r#"{{"event":"CharacterDeath","character_id":{}}}"#, + character_id + ); + broker.publish(death_event); + // Diese Funktion verwendet die gleiche Logik wie CharacterCreationWorker // Wir müssen die Queries aus character_creation.rs verwenden let mut conn = pool @@ -1922,7 +1932,17 @@ impl EventsWorker { } } - // 5) Charakter löschen + // 5) Weitere charaktergebundene Daten löschen (wie UserCharacterWorker) + conn.prepare("delete_knowledge", QUERY_DELETE_KNOWLEDGE)?; + conn.prepare("delete_debtors_prism", QUERY_DELETE_DEBTORS_PRISM)?; + conn.prepare("delete_political_office", QUERY_DELETE_POLITICAL_OFFICE)?; + conn.prepare("delete_election_candidate", QUERY_DELETE_ELECTION_CANDIDATE)?; + conn.execute("delete_knowledge", &[&character_id])?; + conn.execute("delete_debtors_prism", &[&character_id])?; + conn.execute("delete_political_office", &[&character_id])?; + conn.execute("delete_election_candidate", &[&character_id])?; + + // 6) Charakter löschen conn.prepare("delete_character", QUERY_DELETE_CHARACTER)?; conn.execute("delete_character", &[&character_id])?; @@ -2040,6 +2060,13 @@ impl EventsWorker { &format!("Erbe für Charakter {}", deceased_character_id), )?; + // Expliziter Status-Refresh für die UI nach Erbwechsel. + let status = format!( + r#"{{"event":"falukantUpdateStatus","user_id":{}}}"#, + falukant_user_id + ); + broker.publish(status); + eprintln!( "[EventsWorker] Erbe {} übernimmt Vermögen von Charakter {} (User {}): {:.2} (von {:.2} Gesamtvermögen, {} weitere Kinder)", heir_id, deceased_character_id, falukant_user_id, final_money, total_assets, child_count diff --git a/src/worker/user_character.rs b/src/worker/user_character.rs index 6e7100c..efb4565 100644 --- a/src/worker/user_character.rs +++ b/src/worker/user_character.rs @@ -960,6 +960,13 @@ impl UserCharacterWorker { } self.set_new_money(falukant_user_id, new_money)?; + // Nach Erbwechsel den Status sicher neu laden lassen. + let update_status = format!( + r#"{{"event":"falukantUpdateStatus","user_id":{}}}"#, + falukant_user_id + ); + self.base.broker.publish(update_status); + Ok(()) }