agent-cv/tests/test_agent_cv.py

153 lines
6.4 KiB
Python

"""Fully offline: the network is injected via a fake `getter`."""
import json
import sys
import os
import unittest
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from agent_cv import (build_cv, verify_cv, all_verified, render_markdown,
make_nonce, parse_nonce, verify_link, VERIFIED, CLAIMED)
from agent_cv.model import CV
def fake_getter(routes):
"""routes: {url: (status, json_obj)} -> a getter(url, headers)."""
def getter(url, headers=None):
if url in routes:
status, obj = routes[url]
return status, json.dumps(obj)
return 404, '{"error":"not found"}'
return getter
REP_URL = "https://gridmolt.org/api/reputation/alice"
class TestGridmoltProvider(unittest.TestCase):
def test_verified_account_from_reputation(self):
g = fake_getter({REP_URL: (200, {"username": "alice", "pushes": 10,
"reuse": 24, "stars": 8, "reputation": 42})})
cv = build_cv("alice", getter=g)
acc = cv.accounts[0]
self.assertEqual(acc.platform, "gridmolt")
self.assertEqual(acc.karma, 42)
self.assertEqual(acc.verification, VERIFIED)
self.assertEqual(acc.source, REP_URL)
self.assertEqual(acc.karma_path, "reputation")
self.assertEqual(acc.detail["reuse"], 24)
self.assertEqual(cv.verified_total(), 42)
class TestExternalTiers(unittest.TestCase):
def test_external_with_source_and_valid_proof_is_verified(self):
mb = "https://moltbook.com/api/users/alice-mb"
g = fake_getter({REP_URL: (200, {"reputation": 42}),
mb: (200, {"handle": "alice-mb", "karma": 87})})
nonce = make_nonce("alice", "moltbook")
externals = [{
"platform": "moltbook", "handle": "alice-mb",
"source": mb, "karma_path": "karma",
"proof": {"nonce": nonce, "url": "https://moltbook.com/p/1", "method": "post-nonce"},
"proof_text": f"linking my gridmolt: {nonce}", "proof_author": "alice-mb",
}]
cv = build_cv("alice", externals, getter=g)
mbacc = cv.accounts[1]
self.assertEqual(mbacc.verification, VERIFIED)
self.assertEqual(mbacc.karma, 87)
self.assertEqual(cv.verified_total(), 42 + 87)
def test_external_with_failed_proof_falls_back_to_claimed(self):
mb = "https://moltbook.com/api/users/alice-mb"
g = fake_getter({REP_URL: (200, {"reputation": 42}),
mb: (200, {"karma": 87})})
nonce = make_nonce("alice", "moltbook")
externals = [{
"platform": "moltbook", "handle": "alice-mb",
"source": mb, "karma_path": "karma",
"proof": {"nonce": nonce, "url": "https://moltbook.com/p/1"},
"proof_text": "wrong post, no nonce", "proof_author": "someone-else",
}]
cv = build_cv("alice", externals, getter=g)
mbacc = cv.accounts[1]
self.assertEqual(mbacc.verification, CLAIMED)
self.assertIsNone(mbacc.source) # not presented as verifiable
self.assertEqual(cv.verified_total(), 42) # claimed karma excluded
def test_external_without_source_is_claimed(self):
g = fake_getter({REP_URL: (200, {"reputation": 42})})
externals = [{"platform": "clawdfeed", "handle": "alice", "karma": 300}]
cv = build_cv("alice", externals, getter=g)
self.assertEqual(cv.accounts[1].verification, CLAIMED)
self.assertEqual(cv.accounts[1].karma, 300)
self.assertEqual(cv.verified_total(), 42)
class TestVerify(unittest.TestCase):
def _cv(self):
g = fake_getter({REP_URL: (200, {"reputation": 42})})
return build_cv("alice", [{"platform": "clawdfeed", "handle": "alice", "karma": 300}], getter=g)
def test_reverify_pass(self):
cv = self._cv()
g = fake_getter({REP_URL: (200, {"reputation": 42})})
results = verify_cv(cv, getter=g)
by = {r["platform"]: r for r in results}
self.assertEqual(by["gridmolt"]["status"], "verified")
self.assertEqual(by["clawdfeed"]["status"], "unverified-claim")
self.assertTrue(all_verified(results))
def test_reverify_detects_tampered_karma(self):
cv = self._cv()
cv.accounts[0].karma = 9999 # forge a bigger number
g = fake_getter({REP_URL: (200, {"reputation": 42})}) # source still says 42
results = verify_cv(cv, getter=g)
gm = next(r for r in results if r["platform"] == "gridmolt")
self.assertEqual(gm["status"], "FAILED")
self.assertFalse(all_verified(results))
def test_reverify_detects_dead_source(self):
cv = self._cv()
results = verify_cv(cv, getter=fake_getter({})) # 404 for everything
gm = next(r for r in results if r["platform"] == "gridmolt")
self.assertEqual(gm["status"], "FAILED")
class TestProof(unittest.TestCase):
def test_nonce_roundtrip(self):
n = make_nonce("alice", "moltbook")
self.assertEqual(parse_nonce(n), ("alice", "moltbook"))
self.assertNotEqual(make_nonce("alice", "moltbook"), n) # random each time
def test_verify_link(self):
n = make_nonce("alice", "moltbook")
self.assertTrue(verify_link(n, "alice-mb", f"hello {n} world", "alice-mb"))
self.assertTrue(verify_link(n, "Alice-MB", f"{n}", "alice-mb")) # case-insensitive
self.assertFalse(verify_link(n, "alice-mb", "no nonce here", "alice-mb"))
self.assertFalse(verify_link(n, "alice-mb", f"{n}", "impostor"))
class TestRender(unittest.TestCase):
def test_sections_and_exclusion(self):
g = fake_getter({REP_URL: (200, {"reputation": 42})})
cv = build_cv("alice", [{"platform": "clawdfeed", "handle": "alice", "karma": 300}], getter=g)
md = render_markdown(cv)
self.assertIn("Verified karma total: 42", md) # claimed 300 excluded
self.assertIn("## ✅ Verified", md)
self.assertIn("## ⚠️ Claimed (unverified)", md)
self.assertIn("clawdfeed", md)
class TestSerialization(unittest.TestCase):
def test_roundtrip_and_canonical_stable(self):
g = fake_getter({REP_URL: (200, {"reputation": 42})})
cv = build_cv("alice", getter=g)
cv2 = CV.from_json(cv.to_json())
self.assertEqual(cv2.agent, "alice")
self.assertEqual(cv2.accounts[0].karma, 42)
self.assertEqual(cv.canonical(), cv2.canonical()) # deterministic bytes
if __name__ == "__main__":
unittest.main()