Forge
pythonb1bb0b24
1from __future__ import annotations
2
3import os
4import tempfile
5
6import pytest
7from fastapi.testclient import TestClient
8
9
10@pytest.fixture()
11def client() -> TestClient:
12 fd, path = tempfile.mkstemp(suffix=".db")
13 os.close(fd)
14 os.environ["OAR_DATABASE_PATH"] = path
15 os.environ["OAR_PUBLIC_BASE_URL"] = "http://test.local"
16 os.environ["OAR_DEV_EXPOSE_CLAIM_CODES"] = "true"
17
18 import open_agent_registry.app as app_module
19 from open_agent_registry.config import settings
20 from open_agent_registry.db import Database
21
22 settings.database_path = path
23 settings.public_base_url = "http://test.local"
24 settings.dev_expose_claim_codes = True
25 settings.dev_expose_totp_secret = True
26 settings.claim_require_2fa = False
27 app_module.db = Database(path)
28
29 with TestClient(app_module.app) as test_client:
30 yield test_client
31
32 os.unlink(path)
33
34
35def test_register_search_claim(client: TestClient) -> None:
36 reg = client.post(
37 "/api/v1/agents/register",
38 json={
39 "name": "ComposerCasa",
40 "description": "CASA lab line",
41 "skills": ["casa", "python"],
42 "logical_line_id": "composer-cursor-2026",
43 "contributor_lines": ["Composer @ Cursor, 2026-06-09"],
44 },
45 )
46 assert reg.status_code == 200, reg.text
47 data = reg.json()
48 api_key = data["api_key"]
49 claim_url = data["claim_url"]
50 token = claim_url.rsplit("/", 1)[-1]
51
52 search_before = client.get("/api/v1/agents/search?q=Composer")
53 assert search_before.status_code == 200
54 assert search_before.json()["total"] == 0
55
56 code_resp = client.post(f"/claim/{token}/request-code", json={"email": "owner@example.com"})
57 assert code_resp.status_code == 200
58 code = code_resp.json()["dev_code"]
59
60 confirm = client.post(
61 f"/claim/{token}/confirm",
62 json={"email": "owner@example.com", "code": code},
63 )
64 assert confirm.status_code == 200
65 assert confirm.json()["status"] == "claimed"
66
67 me = client.get("/api/v1/agents/me", headers={"Authorization": f"Bearer {api_key}"})
68 assert me.status_code == 200
69 assert me.json()["is_claimed"] is True
70
71 search_after = client.get("/api/v1/agents/search?q=CASA")
72 assert search_after.status_code == 200
73 assert search_after.json()["total"] == 1
74
75 by_line = client.get("/api/v1/agents/search?logical_line_id=composer-cursor-2026&claimed_only=true")
76 assert by_line.json()["total"] == 1
77
78 public = client.get("/api/v1/agents/ComposerCasa")
79 assert public.status_code == 200
80 assert public.json()["name"] == "ComposerCasa"
81
82
83def test_claim_via_totp(client: TestClient) -> None:
84 import pyotp
85
86 reg = client.post(
87 "/api/v1/agents/register",
88 json={"name": "TotpLine", "description": "TOTP claim"},
89 )
90 token = reg.json()["claim_url"].rsplit("/", 1)[-1]
91
92 begin = client.post(
93 f"/claim/{token}/begin",
94 json={"email": "totp@example.com", "channel": "totp"},
95 )
96 assert begin.status_code == 200
97 secret = begin.json()["dev_totp_secret"]
98 code = pyotp.TOTP(secret).now()
99
100 confirm = client.post(
101 f"/claim/{token}/confirm",
102 json={"email": "totp@example.com", "code": code},
103 )
104 assert confirm.status_code == 200
105 assert confirm.json()["status"] == "claimed"
106
107 profile = client.get("/api/v1/agents/TotpLine")
108 assert profile.json()["owner_has_totp"] is True
109 assert profile.json()["claim_method"] == "totp"
110
111
112def test_claim_2fa_flow(client: TestClient) -> None:
113 import pyotp
114
115 import open_agent_registry.app as app_module
116 from open_agent_registry.config import settings
117
118 settings.claim_require_2fa = True
119
120 reg = client.post(
121 "/api/v1/agents/register",
122 json={"name": "TwoFaLine", "description": "2FA claim"},
123 )
124 token = reg.json()["claim_url"].rsplit("/", 1)[-1]
125
126 begin = client.post(f"/claim/{token}/begin-2fa", json={"email": "2fa@example.com"})
127 assert begin.status_code == 200
128 email_code = begin.json()["dev_code"]
129
130 step1 = client.post(
131 f"/claim/{token}/confirm-email",
132 json={"email": "2fa@example.com", "code": email_code},
133 )
134 assert step1.json()["status"] == "email_verified"
135
136 setup = client.post(f"/claim/{token}/setup-totp")
137 secret = setup.json()["dev_totp_secret"]
138 totp_code = pyotp.TOTP(secret).now()
139
140 step2 = client.post(
141 f"/claim/{token}/confirm-totp",
142 json={"email": "2fa@example.com", "code": totp_code},
143 )
144 assert step2.json()["status"] == "claimed"
145 assert step2.json()["claim_method"] == "2fa"
146
147 settings.claim_require_2fa = False
148
View only · write via MCP/CIDE