交易服务收敛为仅 rakuten:移除 rakuma 登录态与下单入口

trading 不再管理 ラクマ 的购物车购买、支付与订单监控。删除 auth_site /
auth_session / login_runner / models.AuthSite 中的 rakuma 分支与常量、
account.yaml.example 的 rakuma 段,并清理相关测试。scraping 侧的
ラクマ 抓取 API(/api/rakuma/*)保留不动。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-07-28 11:50:07 +08:00
co-authored by Claude Opus 4.6
parent 6247d68fb5
commit 65e3ed31f8
13 changed files with 63 additions and 121 deletions
+1 -37
View File
@@ -22,8 +22,6 @@ from app.trading.services.auth_session import AuthSession
CART_LOGGED_OUT = f"<html><body>買い物かご {auth_site.RAKUTEN_LOGGED_OUT_MARKER}</body></html>"
CART_LOGGED_IN = "<html><body>買い物かご 商品が1点入っています</body></html>"
MYPAGE_HTML = "<html><body>マイページ</body></html>"
def make_settings(tmp_path: Path, **overrides) -> Settings:
base = {
@@ -45,7 +43,7 @@ def write_state(settings: Settings, site: str, cookies: list[dict]) -> Path:
async def build_session(settings: Settings, handler) -> AuthSession:
"""构建 AuthSession 并把两站 client 换成 MockTransport 版本
"""构建 AuthSession 并把 client 换成 MockTransport 版本
换掉 client 会丢掉 start() 时灌进去的 cookie,因此重新走一次 reload
把登录态读回新客户端;reload 同时清空探测缓存,正好是测试想要的干净起点。
@@ -79,8 +77,6 @@ async def test_loads_cookies_from_state_file(tmp_path):
try:
names = {c.name for c in session.client("rakuten").cookies.jar}
assert "SESSION" in names
# 没有 state 文件的那一站应为空,而不是报错
assert not list(session.client("rakuma").cookies.jar)
finally:
await session.close()
@@ -139,38 +135,6 @@ async def test_rakuten_detects_logged_in(tmp_path):
await session.close()
async def test_rakuma_detects_logged_out_by_redirect(tmp_path):
"""/mypage 被重定向到登录页即判定未登录
ラクマ 未登录时返回 302 而非改文案,因此判据是落地 URL 不是页面内容。
"""
settings = make_settings(tmp_path)
def handler(request: httpx.Request) -> httpx.Response:
if request.url.path == "/mypage":
return httpx.Response(302, headers={"Location": auth_site.RAKUMA_LOGIN_URL})
return httpx.Response(200, text="<html>ログイン</html>")
session = await build_session(settings, handler)
try:
status = await session.check("rakuma")
assert status.logged_in is False
assert "登录页" in status.detail
finally:
await session.close()
async def test_rakuma_detects_logged_in(tmp_path):
"""/mypage 正常返回即判定已登录"""
settings = make_settings(tmp_path)
session = await build_session(settings, lambda r: httpx.Response(200, text=MYPAGE_HTML))
try:
status = await session.check("rakuma")
assert status.logged_in is True
finally:
await session.close()
async def test_error_status_counts_as_logged_out(tmp_path):
"""探测页返回 4xx/5xx 时保守判定为未登录,不放行下单"""
settings = make_settings(tmp_path)
+7 -7
View File
@@ -27,7 +27,7 @@ class StubAuthSession:
@property
def sites(self) -> tuple[str, ...]:
return ("rakuten", "rakuma")
return ("rakuten",)
def _status(self, site: str) -> AuthStatus:
return AuthStatus(
@@ -87,7 +87,7 @@ def test_health_needs_no_token(client):
assert response.status_code == 200
body = response.json()
assert body["data"]["status"] == "ok"
assert set(body["data"]["auth"]) == {"rakuten", "rakuma"}
assert set(body["data"]["auth"]) == {"rakuten"}
def test_health_does_not_probe_the_site(client, stub):
@@ -116,12 +116,12 @@ def test_auth_endpoints_reject_wrong_token(client):
# ---- 登录态查询 ----
def test_status_probes_both_sites_by_default(client, stub):
def test_status_probes_site_by_default(client, stub):
response = client.post("/api/auth/status", json={}, headers=AUTH)
assert response.status_code == 200
body = response.json()
assert [item["site"] for item in body["data"]["sites"]] == ["rakuten", "rakuma"]
assert stub.checked == ["rakuten", "rakuma"]
assert [item["site"] for item in body["data"]["sites"]] == ["rakuten"]
assert stub.checked == ["rakuten"]
def test_status_can_skip_the_probe(client, stub):
@@ -132,9 +132,9 @@ def test_status_can_skip_the_probe(client, stub):
def test_status_accepts_a_single_site(client, stub):
response = client.post("/api/auth/status", json={"site": "rakuma"}, headers=AUTH)
response = client.post("/api/auth/status", json={"site": "rakuten"}, headers=AUTH)
assert response.status_code == 200
assert stub.checked == ["rakuma"]
assert stub.checked == ["rakuten"]
def test_status_rejects_unknown_site(client):
+2 -2
View File
@@ -252,11 +252,11 @@ async def test_unimplemented_site_interaction_becomes_needs_human(
assert "未实现" in terminal["detail"]
# ---- ラクマ 不实现 → needs_human ----
# ---- ラクマ 不在交易范围 → needs_human ----
async def test_rakuma_site_reports_needs_human(runner: WorkerRunner):
"""首版只对乐天;其他站点直接转人工"""
"""交易服务只对乐天;其他站点(含 ラクマ)直接转人工"""
await runner.handle(_make_task(task_id="t1", site="rakuma"))
gateway: FakeGateway = runner._gateway_for_test # type: ignore[attr-defined]