|
|
@@ -1,167 +1,15 @@
|
|
|
-import asyncio
|
|
|
import pytest
|
|
|
-from cq2_sdk.transport.tcp import TcpClient
|
|
|
-from cq2_sdk.transport.framing import WIRE_HEADER_SIZE, decode_header, encode_frame
|
|
|
-from cq2_sdk.login.game import GameLogin, GameSession
|
|
|
-from cq2_sdk.login.models import BoxSession, ServerInfo
|
|
|
-
|
|
|
-
|
|
|
-SCRIPTED_RESPONSES = [
|
|
|
- # CS 2001 -> SC 529
|
|
|
- (529, 1, 0, 0, 0, b"loginKey-abc/user-1"),
|
|
|
- # CS 104 -> SC 530
|
|
|
- (530, 999, 0, 0, 0, b"ok"),
|
|
|
- # CS 100 -> SC 520 (empty role list -> need to create)
|
|
|
- (520, 0, 0, 0, 0, b'{"list":[]}'),
|
|
|
- # CS 101 -> SC 521 (create role success)
|
|
|
- (521, 0, 0, 0, 0, b"auto/12345"),
|
|
|
- # CS 100 -> SC 520 (re-fetch, now has role)
|
|
|
- (520, 0, 0, 0, 0, b'{"list":[{"chr":12345,"name":"auto"}]}'),
|
|
|
-]
|
|
|
-
|
|
|
-
|
|
|
-@pytest.fixture
|
|
|
-async def scripted_server():
|
|
|
- state = {"i": 0, "server": None}
|
|
|
|
|
|
- async def handler(reader, writer):
|
|
|
- try:
|
|
|
- while True:
|
|
|
- try:
|
|
|
- hdr_raw = await reader.readexactly(WIRE_HEADER_SIZE)
|
|
|
- except asyncio.IncompleteReadError:
|
|
|
- break
|
|
|
- header = decode_header(hdr_raw)
|
|
|
- body = await reader.readexactly(header.body_len) if header.body_len else b""
|
|
|
- # Respond with next scripted SC
|
|
|
- idx = state["i"]
|
|
|
- if idx < len(SCRIPTED_RESPONSES):
|
|
|
- mid, r, p1o, p2o, p3o, b = SCRIPTED_RESPONSES[idx]
|
|
|
- state["i"] = idx + 1
|
|
|
- resp = encode_frame(mid, r, p1o, p2o, p3o, b)
|
|
|
- writer.write(resp)
|
|
|
- await writer.drain()
|
|
|
- else:
|
|
|
- await asyncio.sleep(0.01)
|
|
|
- finally:
|
|
|
- writer.close()
|
|
|
-
|
|
|
- server = await asyncio.start_server(handler, "127.0.0.1", 0)
|
|
|
- state["server"] = server
|
|
|
- port = server.sockets[0].getsockname()[1]
|
|
|
- yield port
|
|
|
- server.close()
|
|
|
- await server.wait_closed()
|
|
|
-
|
|
|
-
|
|
|
-@pytest.mark.asyncio
|
|
|
-async def test_login_full_flow_creates_role(scripted_server):
|
|
|
- tcp = TcpClient("127.0.0.1", scripted_server)
|
|
|
- await tcp.connect()
|
|
|
- try:
|
|
|
- gl = GameLogin(tcp)
|
|
|
- sess = await gl.login(
|
|
|
- BoxSession(uid="u1", token="t1", sdkid="sdk1", signkey="sk1"),
|
|
|
- ServerInfo(server_id="s1", name="n", host="h", port=1),
|
|
|
- )
|
|
|
- assert isinstance(sess, GameSession)
|
|
|
- assert sess.session_id == 999 # recog from SC 530
|
|
|
- assert sess.char_id == 12345 # from re-fetched role list
|
|
|
- assert sess.char_name == "auto"
|
|
|
- finally:
|
|
|
- await tcp.close()
|
|
|
+from cq2_sdk.login.game import GameLogin
|
|
|
+from cq2_sdk.login.models import BoxSession, ServerInfo
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
-async def test_login_with_existing_role(scripted_server):
|
|
|
- """Server returns a non-empty role list on first CS 100, skipping the create path."""
|
|
|
- # Override the scripted server to skip creation
|
|
|
- state = {"i": 0}
|
|
|
- responses = [
|
|
|
- (529, 1, 0, 0, 0, b"lk/uid"),
|
|
|
- (530, 42, 0, 0, 0, b"ok"),
|
|
|
- (520, 0, 0, 0, 0, b'{"list":[{"chr":999,"name":"existing"}]}'),
|
|
|
- ]
|
|
|
+async def test_login_rejects_unverified_game_protocol():
|
|
|
+ login = GameLogin(tcp=object()) # type: ignore[arg-type]
|
|
|
|
|
|
- async def handler(reader, writer):
|
|
|
- try:
|
|
|
- while True:
|
|
|
- try:
|
|
|
- hdr_raw = await reader.readexactly(WIRE_HEADER_SIZE)
|
|
|
- except asyncio.IncompleteReadError:
|
|
|
- break
|
|
|
- header = decode_header(hdr_raw)
|
|
|
- if header.body_len:
|
|
|
- await reader.readexactly(header.body_len)
|
|
|
- idx = state["i"]
|
|
|
- if idx < len(responses):
|
|
|
- mid, r, p1, p2, p3, b = responses[idx]
|
|
|
- state["i"] = idx + 1
|
|
|
- resp = encode_frame(mid, r, p1, p2, p3, b)
|
|
|
- writer.write(resp)
|
|
|
- await writer.drain()
|
|
|
- else:
|
|
|
- await asyncio.sleep(0.01)
|
|
|
- finally:
|
|
|
- writer.close()
|
|
|
-
|
|
|
- server = await asyncio.start_server(handler, "127.0.0.1", 0)
|
|
|
- port = server.sockets[0].getsockname()[1]
|
|
|
- try:
|
|
|
- tcp = TcpClient("127.0.0.1", port)
|
|
|
- await tcp.connect()
|
|
|
- gl = GameLogin(tcp)
|
|
|
- sess = await gl.login(
|
|
|
- BoxSession(uid="u", token="t", sdkid="s", signkey="k"),
|
|
|
+ with pytest.raises(NotImplementedError, match="CS2001 body"):
|
|
|
+ await login.login(
|
|
|
+ BoxSession(uid="u1", token="t1", sdkid="sdk1", signkey="sk1"),
|
|
|
ServerInfo(server_id="s1", name="n", host="h", port=1),
|
|
|
)
|
|
|
- assert sess.session_id == 42
|
|
|
- assert sess.char_id == 999
|
|
|
- assert sess.char_name == "existing"
|
|
|
- await tcp.close()
|
|
|
- finally:
|
|
|
- server.close()
|
|
|
- await server.wait_closed()
|
|
|
-
|
|
|
-
|
|
|
-@pytest.mark.asyncio
|
|
|
-async def test_login_wrong_response_raises():
|
|
|
- """If server returns unexpected msg_id, login should raise RuntimeError."""
|
|
|
- state = {"i": 0}
|
|
|
-
|
|
|
- async def handler(reader, writer):
|
|
|
- try:
|
|
|
- while True:
|
|
|
- try:
|
|
|
- hdr_raw = await reader.readexactly(WIRE_HEADER_SIZE)
|
|
|
- except asyncio.IncompleteReadError:
|
|
|
- break
|
|
|
- header = decode_header(hdr_raw)
|
|
|
- if header.body_len:
|
|
|
- await reader.readexactly(header.body_len)
|
|
|
- # Return wrong msg_id (9999 instead of 529)
|
|
|
- if state["i"] == 0:
|
|
|
- resp = encode_frame(9999, 0, 0, 0, 0, b"")
|
|
|
- writer.write(resp)
|
|
|
- await writer.drain()
|
|
|
- state["i"] += 1
|
|
|
- else:
|
|
|
- await asyncio.sleep(0.01)
|
|
|
- finally:
|
|
|
- writer.close()
|
|
|
-
|
|
|
- server = await asyncio.start_server(handler, "127.0.0.1", 0)
|
|
|
- port = server.sockets[0].getsockname()[1]
|
|
|
- try:
|
|
|
- tcp = TcpClient("127.0.0.1", port)
|
|
|
- await tcp.connect()
|
|
|
- gl = GameLogin(tcp)
|
|
|
- with pytest.raises(RuntimeError, match="Expected SC 529"):
|
|
|
- await gl.login(
|
|
|
- BoxSession(uid="u", token="t", sdkid="s", signkey="k"),
|
|
|
- ServerInfo(server_id="s1", name="n", host="h", port=1),
|
|
|
- )
|
|
|
- await tcp.close()
|
|
|
- finally:
|
|
|
- server.close()
|
|
|
- await server.wait_closed()
|