Skip to content

Replace ban notice text with structured banned message#1084

Open
Louyijun wants to merge 1 commit into
FAForever:developfrom
Louyijun:codex/banned-message
Open

Replace ban notice text with structured banned message#1084
Louyijun wants to merge 1 commit into
FAForever:developfrom
Louyijun:codex/banned-message

Conversation

@Louyijun
Copy link
Copy Markdown

@Louyijun Louyijun commented May 11, 2026

Summary

  • send a structured banned command when a user is rejected for an active ban
  • include the ban reason and UTC ISO expires_at value for client-side localization
  • update login and live-ban tests to assert the structured response

Validation

  • python -m py_compile FAForever-server\server\exceptions.py FAForever-server\server\lobbyconnection.py FAForever-server\tests\integration_tests\test_login.py FAForever-server\tests\integration_tests\test_server.py FAForever-server\tests\unit_tests\test_lobbyconnection.py
  • python -m pytest tests\unit_tests\test_lobbyconnection.py::test_abort_connection_if_banned tests\integration_tests\test_login.py::test_server_ban tests\integration_tests\test_login.py::test_server_ban_token tests\integration_tests\test_server.py::test_server_ban_prevents_hosting --mysql_database=faf -q

Result: 13 passed, 8 warnings from test RSA key length.

Closes #640

Summary by CodeRabbit

Bug Fixes

  • Ban notifications now provide structured information including the ban reason and expiration time in a standardized format.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 11, 2026

📝 Walkthrough

Walkthrough

The PR converts ban error messages from human-readable notice payloads to a structured banned command response with ISO-8601 timestamp and reason. BanError gains a to_dict() method that serializes ban details; LobbyConnection uses this new method instead of manual payload construction; all integration and unit tests are updated to validate the structured response format.

Changes

Ban Message Serialization

Layer / File(s) Summary
Exception Serialization
server/exceptions.py
BanError.to_dict() normalizes ban_expiry to UTC and returns a dict with command: "banned", ISO-8601 expires_at, and reason.
Message Handler
server/lobbyconnection.py
on_message_received sends ban responses via e.to_dict() instead of a manually constructed notice payload.
Test Updates
tests/unit_tests/test_lobbyconnection.py, tests/integration_tests/test_login.py, tests/integration_tests/test_server.py
Unit and integration tests assert banned command with structured fields (command, reason, expires_at) instead of notice/HTML text; re import removed.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐰 A ban message grows bright and clear,
Structured data, crisp and dear,
No English text to cloud the way,
The client speaks what it should say,
Localization hops and plays! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: replacing English ban notice text with a structured banned message format.
Linked Issues check ✅ Passed The PR fully addresses issue #640 requirements: sends structured 'banned' command, includes ISO-8601 UTC timestamps in 'expires_at' field, and provides ban reason for client-side localization.
Out of Scope Changes check ✅ Passed All changes are directly scoped to the ban message restructuring: exception method addition, connection response update, and corresponding test assertions.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@Louyijun Louyijun marked this pull request as ready for review May 11, 2026 02:19
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
server/lobbyconnection.py (1)

225-226: ⚡ Quick win

Decouple ban disconnect logging from legacy BanError.message()

Line 226 still builds the old humanized message path just for abort logging. Using the structured payload here keeps ban handling fully protocol-agnostic and unblocks cleanup of legacy ban-text code paths.

Proposed refactor
         except BanError as e:
-            await self.send(e.to_dict())
-            await self.abort(e.message())
+            banned_payload = e.to_dict()
+            await self.send(banned_payload)
+            await self.abort(
+                f"Banned user rejected: reason={banned_payload['reason']!r}, "
+                f"expires_at={banned_payload['expires_at']}"
+            )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/lobbyconnection.py` around lines 225 - 226, The code calls await
self.abort(e.message()) which rebuilds legacy humanized ban text; change it to
pass the structured payload returned by e.to_dict() instead so ban handling
stays protocol-agnostic. Update the call in lobbyconnection.py to await
self.abort(e.to_dict()), leaving the preceding await self.send(e.to_dict())
intact, and ensure abort(self, payload) accepts and forwards the structured dict
(not a string) from the BanError instance (referenced as e with methods
to_dict() and message()).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/integration_tests/test_login.py`:
- Around line 41-43: The test currently only asserts that "expires_at" exists in
msg; instead parse msg["expires_at"] (used in the assertions around the msg
dict) as an ISO-8601 timestamp and validate it represents UTC (either tzinfo ==
timezone.utc or an offset of +00:00 / a trailing "Z") and that parsing does not
raise; replace the simple presence check with this parse+UTC-check for both
occurrences (the block asserting msg["command"]=="banned" / msg["reason"] and
the similar asserts at lines 70-72) so the test fails if the server returns a
non-ISO or non-UTC timestamp.

---

Nitpick comments:
In `@server/lobbyconnection.py`:
- Around line 225-226: The code calls await self.abort(e.message()) which
rebuilds legacy humanized ban text; change it to pass the structured payload
returned by e.to_dict() instead so ban handling stays protocol-agnostic. Update
the call in lobbyconnection.py to await self.abort(e.to_dict()), leaving the
preceding await self.send(e.to_dict()) intact, and ensure abort(self, payload)
accepts and forwards the structured dict (not a string) from the BanError
instance (referenced as e with methods to_dict() and message()).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e48cb2f9-a457-4183-9ad3-e7d0bda0513d

📥 Commits

Reviewing files that changed from the base of the PR and between e36de71 and d202700.

📒 Files selected for processing (5)
  • server/exceptions.py
  • server/lobbyconnection.py
  • tests/integration_tests/test_login.py
  • tests/integration_tests/test_server.py
  • tests/unit_tests/test_lobbyconnection.py

Comment on lines +41 to +43
assert msg["command"] == "banned"
assert msg["reason"] == "Test permanent ban"
assert "expires_at" in msg
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Assert expires_at is UTC ISO-8601, not just present

Current checks won’t fail if the server returns a non-ISO or non-UTC timestamp. Parse and validate timezone to lock the contract required by Issue #640.

Proposed test hardening
+from datetime import datetime, timezone
 from time import time
@@
     msg = await proto.read_message()
     assert msg["command"] == "banned"
     assert msg["reason"] == "Test permanent ban"
-    assert "expires_at" in msg
+    expires_at = datetime.fromisoformat(msg["expires_at"])
+    assert expires_at.tzinfo is not None
+    assert expires_at.utcoffset() == timezone.utc.utcoffset(expires_at)
@@
     msg = await proto.read_message()
     assert msg["command"] == "banned"
     assert msg["reason"] == "Test permanent ban"
-    assert "expires_at" in msg
+    expires_at = datetime.fromisoformat(msg["expires_at"])
+    assert expires_at.tzinfo is not None
+    assert expires_at.utcoffset() == timezone.utc.utcoffset(expires_at)

Also applies to: 70-72

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/integration_tests/test_login.py` around lines 41 - 43, The test
currently only asserts that "expires_at" exists in msg; instead parse
msg["expires_at"] (used in the assertions around the msg dict) as an ISO-8601
timestamp and validate it represents UTC (either tzinfo == timezone.utc or an
offset of +00:00 / a trailing "Z") and that parsing does not raise; replace the
simple presence check with this parse+UTC-check for both occurrences (the block
asserting msg["command"]=="banned" / msg["reason"] and the similar asserts at
lines 70-72) so the test fails if the server returns a non-ISO or non-UTC
timestamp.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Replace ban text with localizable message

1 participant