generated from kgod/ai-review-template
feat: initialize resume agent with OfferPai sync
This commit is contained in:
@@ -0,0 +1,265 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from app.main import create_app
|
||||
from app.offerpai_auth import (
|
||||
OfferPaiAuthClient,
|
||||
OfferPaiAuthError,
|
||||
OfferPaiIdentity,
|
||||
)
|
||||
from app.settings import Settings
|
||||
from test_api import BASE, active_component, event
|
||||
|
||||
|
||||
TOKEN = "header.payload.signature-value"
|
||||
|
||||
|
||||
def test_offerpai_client_uses_cookie_for_both_get_requests() -> None:
|
||||
requests: list[httpx.Request] = []
|
||||
|
||||
def handler(request: httpx.Request) -> httpx.Response:
|
||||
requests.append(request)
|
||||
assert request.headers.get("cookie") == f"Token={TOKEN}"
|
||||
if request.url.path == "/api/public/checkLogin":
|
||||
return httpx.Response(200, json={"code": "0", "data": True})
|
||||
if request.url.path == "/api/user/manage/info":
|
||||
return httpx.Response(
|
||||
200,
|
||||
json={
|
||||
"code": "0",
|
||||
"data": {
|
||||
"id": "2081575100391407617",
|
||||
"mobileNumber": "13421012384",
|
||||
"nick": "用户2384",
|
||||
"inviteCode": "2GOTI0H0X7",
|
||||
"createTime": 1785121152000,
|
||||
},
|
||||
},
|
||||
)
|
||||
return httpx.Response(404)
|
||||
|
||||
with httpx.Client(
|
||||
base_url="https://test.offerpai.com.cn",
|
||||
transport=httpx.MockTransport(handler),
|
||||
) as http_client:
|
||||
identity = OfferPaiAuthClient(
|
||||
"https://test.offerpai.com.cn", client=http_client
|
||||
).authenticate(TOKEN)
|
||||
|
||||
assert [request.method for request in requests] == ["GET", "GET"]
|
||||
assert [request.url.path for request in requests] == [
|
||||
"/api/public/checkLogin",
|
||||
"/api/user/manage/info",
|
||||
]
|
||||
assert identity.user_id == "2081575100391407617"
|
||||
assert identity.mobile_number == "13421012384"
|
||||
assert identity.nick == "用户2384"
|
||||
|
||||
|
||||
def test_offerpai_client_rejects_failed_login_without_profile_request() -> None:
|
||||
requests: list[httpx.Request] = []
|
||||
|
||||
def handler(request: httpx.Request) -> httpx.Response:
|
||||
requests.append(request)
|
||||
return httpx.Response(200, json={"code": "0", "data": False})
|
||||
|
||||
with httpx.Client(
|
||||
base_url="https://test.offerpai.com.cn",
|
||||
transport=httpx.MockTransport(handler),
|
||||
) as http_client:
|
||||
provider = OfferPaiAuthClient("https://test.offerpai.com.cn", client=http_client)
|
||||
try:
|
||||
provider.authenticate(TOKEN)
|
||||
except OfferPaiAuthError as exc:
|
||||
assert exc.code == "external_auth_invalid"
|
||||
assert exc.status_code == 401
|
||||
else:
|
||||
raise AssertionError("Expected invalid external authentication")
|
||||
|
||||
assert len(requests) == 1
|
||||
|
||||
|
||||
class FakeIdentityProvider:
|
||||
def __init__(self, error: OfferPaiAuthError | None = None) -> None:
|
||||
self.error = error
|
||||
self.tokens: list[str] = []
|
||||
|
||||
def authenticate(self, token: str) -> OfferPaiIdentity:
|
||||
self.tokens.append(token)
|
||||
if self.error is not None:
|
||||
raise self.error
|
||||
return OfferPaiIdentity(
|
||||
user_id="2081575100391407617",
|
||||
mobile_number="13421012384",
|
||||
nick="用户2384",
|
||||
invite_code="2GOTI0H0X7",
|
||||
create_time=1785121152000,
|
||||
)
|
||||
|
||||
|
||||
def auth_client(
|
||||
tmp_path: Path, provider: Any, *, auth_required: bool = True
|
||||
) -> tuple[Any, TestClient]:
|
||||
application = create_app(
|
||||
database_path=tmp_path / "offerpai-auth.db",
|
||||
cors_origins=["http://localhost:5173"],
|
||||
settings=Settings(
|
||||
llm_provider="rule", offerpai_auth_required=auth_required
|
||||
),
|
||||
offerpai_identity_provider=provider,
|
||||
)
|
||||
return application, TestClient(application)
|
||||
|
||||
|
||||
def test_session_creation_authenticates_and_defaults_account_phone(tmp_path: Path) -> None:
|
||||
provider = FakeIdentityProvider()
|
||||
application, client = auth_client(tmp_path, provider)
|
||||
with client:
|
||||
response = client.post(
|
||||
f"{BASE}/sessions",
|
||||
json={},
|
||||
headers={"Authorization": f"Bearer {TOKEN}"},
|
||||
)
|
||||
assert response.status_code == 201, response.text
|
||||
created = response.json()
|
||||
session_id = created["session_id"]
|
||||
|
||||
with application.state.database.transaction() as connection:
|
||||
session = application.state.database.fetch_session(connection, session_id)
|
||||
assert session is not None
|
||||
profile = session["profile"]
|
||||
assert profile["account_phone"] == "13421012384"
|
||||
assert profile["external_account"] == {
|
||||
"provider": "offerpai",
|
||||
"user_id": "2081575100391407617",
|
||||
"mobile_number": "13421012384",
|
||||
"nick": "用户2384",
|
||||
"invite_code": "2GOTI0H0X7",
|
||||
"create_time": 1785121152000,
|
||||
}
|
||||
assert TOKEN not in json.dumps(profile, ensure_ascii=False)
|
||||
|
||||
auth_headers = {"Authorization": f"Bearer {TOKEN}"}
|
||||
source = event(
|
||||
client,
|
||||
session_id,
|
||||
created,
|
||||
"accept",
|
||||
{"accepted": True},
|
||||
headers=auth_headers,
|
||||
).json()
|
||||
phone_selector = event(
|
||||
client,
|
||||
session_id,
|
||||
source,
|
||||
"select",
|
||||
{"value": "manual"},
|
||||
headers=auth_headers,
|
||||
).json()
|
||||
data = active_component(phone_selector)["data"]
|
||||
assert data["has_account_phone"] is True
|
||||
assert data["masked_phone"] == "134****2384"
|
||||
assert data["default_value"] == "account"
|
||||
|
||||
personal = event(
|
||||
client,
|
||||
session_id,
|
||||
phone_selector,
|
||||
"select",
|
||||
{"source": "account"},
|
||||
headers=auth_headers,
|
||||
)
|
||||
assert personal.status_code == 200, personal.text
|
||||
with application.state.database.transaction() as connection:
|
||||
updated = application.state.database.fetch_session(connection, session_id)
|
||||
assert updated is not None
|
||||
assert updated["profile"]["phone"] == "13421012384"
|
||||
assert updated["profile"]["phone_source"] == "account"
|
||||
|
||||
assert provider.tokens == [TOKEN, TOKEN, TOKEN, TOKEN]
|
||||
|
||||
|
||||
def test_invalid_external_token_does_not_create_session(tmp_path: Path) -> None:
|
||||
provider = FakeIdentityProvider(
|
||||
OfferPaiAuthError(
|
||||
"external_auth_invalid",
|
||||
"登录凭证无效或已过期,请重新从 OfferPai 进入。",
|
||||
status_code=401,
|
||||
)
|
||||
)
|
||||
application, client = auth_client(tmp_path, provider)
|
||||
with client:
|
||||
response = client.post(
|
||||
f"{BASE}/sessions",
|
||||
json={},
|
||||
headers={"Authorization": f"Bearer {TOKEN}"},
|
||||
)
|
||||
assert response.status_code == 401
|
||||
assert response.json()["error"]["code"] == "external_auth_invalid"
|
||||
with application.state.database.transaction() as connection:
|
||||
count = connection.execute("SELECT COUNT(*) FROM sessions").fetchone()[0]
|
||||
assert count == 0
|
||||
|
||||
|
||||
def test_optional_anonymous_mode_does_not_call_external_provider(tmp_path: Path) -> None:
|
||||
provider = FakeIdentityProvider()
|
||||
_application, client = auth_client(tmp_path, provider, auth_required=False)
|
||||
with client:
|
||||
response = client.post(f"{BASE}/sessions", json={})
|
||||
assert response.status_code == 201
|
||||
assert provider.tokens == []
|
||||
|
||||
|
||||
def test_optional_mode_still_protects_sessions_bound_to_offerpai(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
provider = FakeIdentityProvider()
|
||||
_application, client = auth_client(tmp_path, provider, auth_required=False)
|
||||
with client:
|
||||
created = client.post(
|
||||
f"{BASE}/sessions",
|
||||
json={},
|
||||
headers={"Authorization": f"Bearer {TOKEN}"},
|
||||
)
|
||||
assert created.status_code == 201, created.text
|
||||
session_id = created.json()["session_id"]
|
||||
|
||||
missing = client.get(f"{BASE}/sessions/{session_id}/timeline")
|
||||
assert missing.status_code == 401
|
||||
assert missing.json()["error"]["code"] == "external_auth_required"
|
||||
|
||||
|
||||
def test_authentication_is_required_when_enabled(tmp_path: Path) -> None:
|
||||
provider = FakeIdentityProvider()
|
||||
_application, client = auth_client(tmp_path, provider)
|
||||
with client:
|
||||
response = client.post(f"{BASE}/sessions", json={})
|
||||
assert response.status_code == 401
|
||||
assert response.json()["error"]["code"] == "external_auth_required"
|
||||
assert provider.tokens == []
|
||||
|
||||
|
||||
def test_authenticated_start_restores_latest_user_session(tmp_path: Path) -> None:
|
||||
provider = FakeIdentityProvider()
|
||||
_application, client = auth_client(tmp_path, provider, auth_required=True)
|
||||
with client:
|
||||
first = client.post(
|
||||
f"{BASE}/sessions",
|
||||
json={},
|
||||
headers={"Authorization": f"Bearer {TOKEN}"},
|
||||
)
|
||||
second = client.post(
|
||||
f"{BASE}/sessions",
|
||||
json={},
|
||||
headers={"Authorization": f"Bearer {TOKEN}"},
|
||||
)
|
||||
assert first.status_code == 201
|
||||
assert second.status_code == 201
|
||||
assert second.json()["session_id"] == first.json()["session_id"]
|
||||
assert provider.tokens == [TOKEN, TOKEN]
|
||||
Reference in New Issue
Block a user