teleo-infrastructure/tests/test_contributor.py
m3taversal 53dc18afd5
Some checks are pending
CI / lint-and-test (push) Waiting to run
Phase 5: Extract contributor.py from merge.py (−234 lines)
5 functions extracted: is_knowledge_pr, refine_commit_type,
record_contributor_attribution, upsert_contributor, recalculate_tier.

git_fn parameter injection avoids circular import (merge→contributor,
contributor needs _git from merge). Single call site passes _git.

merge.py: 1912 → 1678 lines. 23 new tests, zero regressions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 13:08:26 +01:00

263 lines
9.8 KiB
Python

"""Tests for lib/contributor.py — contributor attribution functions."""
import sqlite3
import asyncio
import sys
import os
from unittest.mock import AsyncMock, MagicMock, patch
sys.modules.setdefault("aiohttp", MagicMock())
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
from lib.contributor import (
is_knowledge_pr,
refine_commit_type,
record_contributor_attribution,
upsert_contributor,
recalculate_tier,
)
# --- is_knowledge_pr ---
def test_knowledge_pr_domains():
diff = "+++ b/domains/crypto/some-claim.md\n"
assert is_knowledge_pr(diff) is True
def test_knowledge_pr_core():
diff = "+++ b/core/epistemology.md\n"
assert is_knowledge_pr(diff) is True
def test_knowledge_pr_foundations():
diff = "--- a/foundations/overview.md\n"
assert is_knowledge_pr(diff) is True
def test_knowledge_pr_decisions():
diff = "+++ b/decisions/some-decision.md\n"
assert is_knowledge_pr(diff) is True
def test_pipeline_only_pr():
diff = "+++ b/inbox/source.md\n+++ b/entities/metadao.md\n"
assert is_knowledge_pr(diff) is False
def test_mixed_pr_counts_as_knowledge():
diff = "+++ b/inbox/source.md\n+++ b/domains/crypto/claim.md\n"
assert is_knowledge_pr(diff) is True
def test_empty_diff():
assert is_knowledge_pr("") is False
# --- refine_commit_type ---
def test_refine_non_extract_unchanged():
assert refine_commit_type("anything", "research") == "research"
assert refine_commit_type("anything", "entity") == "entity"
def test_refine_extract_new_files():
diff = "diff --git a/x b/y\nnew file\n+++ b/domains/crypto/claim.md\n"
assert refine_commit_type(diff, "extract") == "extract"
def test_refine_extract_challenge():
diff = "diff --git a/x b/y\n+++ b/domains/crypto/claim.md\n+challenged_by: other\n"
assert refine_commit_type(diff, "extract") == "challenge"
def test_refine_extract_enrich():
diff = "diff --git a/x b/y\n+++ b/domains/crypto/claim.md\n+confidence: 0.8\n"
assert refine_commit_type(diff, "extract") == "enrich"
def test_refine_extract_mixed_new_and_modified():
diff = (
"diff --git a/x b/y\nnew file\n+++ b/domains/crypto/new.md\n"
"diff --git a/x b/z\n+++ b/domains/crypto/existing.md\n+foo\n"
)
assert refine_commit_type(diff, "extract") == "extract"
# --- upsert_contributor + recalculate_tier ---
def _make_db():
conn = sqlite3.connect(":memory:")
conn.row_factory = sqlite3.Row
conn.execute("""CREATE TABLE contributors (
handle TEXT PRIMARY KEY,
agent_id TEXT,
tier TEXT DEFAULT 'new',
first_contribution TEXT,
last_contribution TEXT,
claims_merged INTEGER DEFAULT 0,
challenges_survived INTEGER DEFAULT 0,
sourcer_count INTEGER DEFAULT 0,
extractor_count INTEGER DEFAULT 0,
challenger_count INTEGER DEFAULT 0,
synthesizer_count INTEGER DEFAULT 0,
reviewer_count INTEGER DEFAULT 0,
updated_at TEXT
)""")
conn.execute("""CREATE TABLE audit_log (
id INTEGER PRIMARY KEY,
ts TEXT DEFAULT (datetime('now')),
stage TEXT,
event TEXT,
detail TEXT
)""")
return conn
def test_upsert_new_contributor():
conn = _make_db()
with patch("lib.contributor.config") as mock_config:
mock_config.CONTRIBUTOR_TIER_RULES = {
"veteran": {"claims_merged": 50, "min_days_since_first": 90, "challenges_survived": 5},
"contributor": {"claims_merged": 10},
}
upsert_contributor(conn, "rio", "uuid-123", "extractor", "2026-04-16")
row = conn.execute("SELECT * FROM contributors WHERE handle = 'rio'").fetchone()
assert row["extractor_count"] == 1
assert row["claims_merged"] == 1
assert row["tier"] == "new"
def test_upsert_increment():
conn = _make_db()
upsert_contributor(conn, "rio", "uuid-123", "extractor", "2026-04-16")
upsert_contributor(conn, "rio", "uuid-123", "extractor", "2026-04-17")
row = conn.execute("SELECT * FROM contributors WHERE handle = 'rio'").fetchone()
assert row["extractor_count"] == 2
assert row["claims_merged"] == 2
def test_upsert_reviewer_no_claim_increment():
conn = _make_db()
upsert_contributor(conn, "leo", None, "reviewer", "2026-04-16")
row = conn.execute("SELECT * FROM contributors WHERE handle = 'leo'").fetchone()
assert row["reviewer_count"] == 1
assert row["claims_merged"] == 0
def test_upsert_unknown_role():
conn = _make_db()
upsert_contributor(conn, "rio", None, "wizard", "2026-04-16")
row = conn.execute("SELECT * FROM contributors WHERE handle = 'rio'").fetchone()
assert row is None # Should not insert
def test_recalculate_tier_contributor():
conn = _make_db()
conn.execute(
"""INSERT INTO contributors (handle, claims_merged, challenges_survived, first_contribution, tier)
VALUES ('rio', 15, 0, '2026-01-01', 'new')"""
)
with patch("lib.contributor.config") as mock_config:
mock_config.CONTRIBUTOR_TIER_RULES = {
"veteran": {"claims_merged": 50, "min_days_since_first": 90, "challenges_survived": 5},
"contributor": {"claims_merged": 10},
}
recalculate_tier(conn, "rio")
row = conn.execute("SELECT tier FROM contributors WHERE handle = 'rio'").fetchone()
assert row["tier"] == "contributor"
def test_recalculate_tier_veteran():
conn = _make_db()
conn.execute(
"""INSERT INTO contributors (handle, claims_merged, challenges_survived, first_contribution, tier)
VALUES ('rio', 60, 10, '2025-01-01', 'contributor')"""
)
with patch("lib.contributor.config") as mock_config:
mock_config.CONTRIBUTOR_TIER_RULES = {
"veteran": {"claims_merged": 50, "min_days_since_first": 90, "challenges_survived": 5},
"contributor": {"claims_merged": 10},
}
recalculate_tier(conn, "rio")
row = conn.execute("SELECT tier FROM contributors WHERE handle = 'rio'").fetchone()
assert row["tier"] == "veteran"
# --- record_contributor_attribution ---
def _make_attribution_db():
conn = _make_db()
conn.execute("""CREATE TABLE prs (
number INTEGER PRIMARY KEY,
commit_type TEXT,
agent TEXT
)""")
conn.execute("INSERT INTO prs VALUES (100, 'extract', 'rio')")
return conn
def test_record_skips_pipeline_only():
conn = _make_attribution_db()
mock_diff = "+++ b/inbox/source.md\n"
async def run():
with patch("lib.contributor.get_pr_diff", new_callable=AsyncMock, return_value=mock_diff):
git_fn = AsyncMock(return_value=(0, ""))
await record_contributor_attribution(conn, 100, "extract/test", git_fn)
asyncio.run(run())
row = conn.execute("SELECT * FROM contributors").fetchone()
assert row is None # No attribution for pipeline-only
def test_record_fallback_to_pr_agent():
conn = _make_attribution_db()
mock_diff = "+++ b/domains/crypto/claim.md\n+some content\n"
async def run():
with patch("lib.contributor.get_pr_diff", new_callable=AsyncMock, return_value=mock_diff):
git_fn = AsyncMock(return_value=(0, "no trailers here"))
with patch("lib.contributor.config") as mock_config:
mock_config.CONTRIBUTOR_TIER_RULES = {
"veteran": {"claims_merged": 50, "min_days_since_first": 90, "challenges_survived": 5},
"contributor": {"claims_merged": 10},
}
await record_contributor_attribution(conn, 100, "extract/test", git_fn)
asyncio.run(run())
row = conn.execute("SELECT * FROM contributors WHERE handle = 'rio'").fetchone()
assert row is not None
assert row["extractor_count"] == 1
def test_record_parses_pentagon_trailer():
conn = _make_attribution_db()
mock_diff = "+++ b/domains/crypto/claim.md\n+new file content\n"
trailer = "Pentagon-Agent: Theseus <uuid-456>"
async def run():
with patch("lib.contributor.get_pr_diff", new_callable=AsyncMock, return_value=mock_diff):
git_fn = AsyncMock(return_value=(0, trailer))
with patch("lib.contributor.config") as mock_config:
mock_config.CONTRIBUTOR_TIER_RULES = {
"veteran": {"claims_merged": 50, "min_days_since_first": 90, "challenges_survived": 5},
"contributor": {"claims_merged": 10},
}
await record_contributor_attribution(conn, 100, "extract/test", git_fn)
asyncio.run(run())
row = conn.execute("SELECT * FROM contributors WHERE handle = 'theseus'").fetchone()
assert row is not None
assert row["agent_id"] == "uuid-456"
def test_record_refines_commit_type():
conn = _make_attribution_db()
mock_diff = "diff --git a/x b/y\n+++ b/domains/crypto/claim.md\n+challenged_by: foo\n"
async def run():
with patch("lib.contributor.get_pr_diff", new_callable=AsyncMock, return_value=mock_diff):
git_fn = AsyncMock(return_value=(0, ""))
with patch("lib.contributor.config") as mock_config:
mock_config.CONTRIBUTOR_TIER_RULES = {
"veteran": {"claims_merged": 50, "min_days_since_first": 90, "challenges_survived": 5},
"contributor": {"claims_merged": 10},
}
await record_contributor_attribution(conn, 100, "extract/test", git_fn)
asyncio.run(run())
row = conn.execute("SELECT commit_type FROM prs WHERE number = 100").fetchone()
assert row["commit_type"] == "challenge"
def test_record_no_diff_returns_early():
conn = _make_attribution_db()
async def run():
with patch("lib.contributor.get_pr_diff", new_callable=AsyncMock, return_value=None):
git_fn = AsyncMock()
await record_contributor_attribution(conn, 100, "extract/test", git_fn)
git_fn.assert_not_called()
asyncio.run(run())