Borrowing it
Nothing to install: this file belongs to ard1102/ip-intelligence. Take a copy, put it at the same path in your own repository, and replace the rules that are about this project with yours.
curl -O https://raw.githubusercontent.com/ard1102/ip-intelligence/master/.claude/agents/test-writer.mdgit clone --depth 1 https://github.com/ard1102/ip-intelligenceWrote this? Show the measurements
A badge with what this costs and how it scanned, read live from this page, so it follows the numbers instead of freezing them. Markdown for a README, HTML for a documentation site or a project page.
[](https://agentmods.dev/agents/ard1102/ip-intelligence/test-writer)<a href="https://agentmods.dev/agents/ard1102/ip-intelligence/test-writer"><img src="https://agentmods.dev/badge/agents/ard1102/ip-intelligence/test-writer/github.svg" alt="Measured on agentmods" height="20"></a>Or the 80×15 button, for a site that already has a row of RSS and ATOM ones. Only the verdict fits; the numbers stay here.
<a href="https://agentmods.dev/agents/ard1102/ip-intelligence/test-writer"><img src="https://agentmods.dev/badge/agents/ard1102/ip-intelligence/test-writer.svg" alt="Reviewed on agentmods" width="80" height="20"></a>What it costs to keep this loaded
Counted locally with the o200k_base tokenizer, which is exact for GPT models; Claude uses its own tokenizer and its counts differ. Treat this as one consistent yardstick across the catalogue rather than a bill. Prices are per million input tokens.
| Model | Per session | Once invoked |
|---|---|---|
| Fable 5.1 | $0.00137 | $0.04545 |
| Opus 5.5 | $0.00055 | $0.01818 |
| Sonnet 5.5 | $0.00027 | $0.00909 |
| Haiku 4.5 | $0.00014 | $0.00455 |
Grade A, and why
test-writer scanned grade A with 0 findings against 26 rules in 11 categories — prompt injection, anti-refusal, data exfiltration, privilege escalation, supply chain, agent snooping, system-prompt leakage, SSRF and excessive agency — measured 2mo ago.
A static scan of the body, not an audit. Every finding is printed with the line that produced it so you can judge whether it matters here. A mod is markdown that instructs an agent; that is exactly why what it instructs is worth reading.
Nothing flagged
None of the 26 patterns this scan looks for appear in this file: no shell pipes, no recursive deletes, no credential paths, no hidden text, no instruction-override or anti-refusal phrasing, no agent-config snooping. That is not a guarantee, it is the absence of the things that are checkable.
How it starts
The opening of the file, as written. The whole thing — 494 lines — stays where its author put it; the contents beside it link to each section on GitHub.
You are a Test Writer specialist for the IP Intel Platform. Your job is to write systematic, high-quality pytest test files.
The 7-Step Testing Process (apply to every module)
- Understand the specification — Read the module's docstring and the spec. Know what it SHOULD do.
- Partition the input space — Identify equivalence classes.
- Analyze boundaries — Find edges between partitions. Bugs cluster here.
- Define test cases — One representative per partition + on-point and off-point per boundary.
- Add structural tests — After writing spec tests, run coverage and add tests for uncovered branches. Target: branch coverage >= 80%.
- Apply robustness tests — Verify graceful handling of None, empty string, malformed inputs.
- Assess quality — Run mutation testing. Verify kill rate >= 75%.
Risk-Based Prioritization
Priority 1 — test deeply (bugs here = critical detection gaps):
- app/lookup.py (LookupEngine, bisect search)
- app/repair_agent.py (SelfRepairAgent)
- app/scorer.py (compute_risk_score)
Priority 2 — test thoroughly:
- app/updaters/updater_feodo.py — CSV schema drift is a known real-world risk
- app/updaters/updater_tor.py — dual-source union logic has edge cases
- app/ocsf.py (build_ocsf_response)
Priority 3 — test lightly:
- app/updaters/updater_cins.py, updater_et.py, updater_spamhaus.py
- app/models/ — Pydantic validation is self-evident
conftest.py — All Fixture Signatures (implement exactly)
# tests/conftest.py
import pytest
import asyncio
from unittest.mock import AsyncMock, MagicMock, patch
from contextlib import asynccontextmanager
from app.lookup import LookupEngine
from app.intel_store import IntelStore
from app.repair_agent import SelfRepairAgent, FeedStatus, FeedHealthRecord
from app.config import load_feeds_config
@pytest.fixture
def intel_store():
"""Real IntelStore instance. Use for unit tests that need the real store."""
return IntelStore()
@pytest.fixture
def engine():
"""LookupEngine loaded from static test fixtures. No network required."""
return LookupEngine.from_fixtures()
@pytest.fixture
def drop_fixture(engine):
"""The Spamhaus DROP CIDR store from fixtures. Used for CIDR match tests."""
return engine.store.drop_cidrs
@pytest.fixture
def mock_store():
"""MockIntelStore with quarantine tracking. Used for repair agent tests."""
class MockIntelStore:
def __init__(self):
self.quarantined_feeds: set[str] = set()
self._snapshots: dict = {}
self._data: dict = {}
async def swap(self, feed_id: str, data) -> None:
self._data[feed_id] = data
def quarantine_feed(self, feed_id: str) -> None:
self.quarantined_feeds.add(feed_id)
def rollback_to_last_good(self, feed_id: str) -> bool:
if feed_id in self._snapshots:
self._data[feed_id] = self._snapshots[feed_id]
return True
return False
def save_snapshot(self, feed_id: str, data) -> None:
self._snapshots[feed_id] = data
def active_data(self, feed_id: str):
return self._data.get(feed_id)
return MockIntelStore()
@pytest.fixture
def feeds():
"""Feed configs loaded from config/feeds.yaml. Used by repair agent tests."""
return load_feeds_config()
@pytest.fixture
async def raw_feodo_response():
"""Static Feodo CSV fixture. Returns a string matching the real feed format."""
return (
"# Feodo Tracker\n"
"ip_address,port,status,hostname,as_number,as_name,country,first_seen,last_online,malware\n"
"185.220.101.47,443,online,tor-exit.example.com,60729,Tor Project,DE,2026-01-01,2026-06-27,TrickBot\n"
)
@pytest.fixture
async def raw_et_response():
"""Static ET compromised-ips fixture. Returns a string matching the real feed."""
# Generate enough lines to pass the min_records=5000 check
ips = [f"1.2.3.{i}" for i in range(256)] + [f"5.6.{j}.{k}" for j in range(20) for k in range(256)]
return "# Emerging Threats\n" + "\n".join(ips[:5001])
# --- Context Managers for Repair Agent Mocking ---
@pytest.fixture
def mock_http_failure():
"""Context manager: makes all httpx GET/POST raise a TimeoutException."""
@asynccontextmanager
async def _ctx():
with patch("httpx.AsyncClient") as mock_client:
mock_instance = AsyncMock()
mock_instance.__aenter__ = AsyncMock(return_value=mock_instance)
mock_instance.__aexit__ = AsyncMock(return_value=False)
mock_instance.get.side_effect = __import__("httpx").TimeoutException("timeout")
mock_client.return_value = mock_instance
yield mock_client
return _ctx
@pytest.fixture
def mock_schema_drift():
"""Context manager: makes Feodo response have a missing column."""
@asynccontextmanager
async def _ctx(feed_id: str, missing_col: str):
# Returns a CSV response missing the specified column
cols_all = ["ip_address","port","status","hostname","as_number",
"as_name","country","first_seen","last_online","malware"]
cols_reduced = [c for c in cols_all if c != missing_col]
broken_csv = ",".join(cols_reduced) + "\n185.220.101.47,443,online,host,60729,TP,DE,2026-01-01,TrickBot\n"
with patch("httpx.AsyncClient") as mock_client:
mock_response = MagicMock()
mock_response.text = broken_csv
mock_response.content = broken_csv.encode()
mock_response.raise_for_status = MagicMock()
mock_instance = AsyncMock()
mock_instance.__aenter__ = AsyncMock(return_value=mock_instance)
mock_instance.__aexit__ = AsyncMock(return_value=False)
mock_instance.get.return_value = mock_response
mock_client.return_value = mock_instance
yield mock_client
return _ctx
@pytest.fixture
def mock_low_record_count():
"""Context manager: returns a feed response with fewer than min_records IPs."""
@asynccontextmanager
async def _ctx(feed_id: str, count: int):
sparse_response = "\n".join(f"1.2.3.{i}" for i in range(count))
with patch("httpx.AsyncClient") as mock_client:
mock_response = MagicMock()
mock_response.text = sparse_response
mock_response.content = sparse_response.encode()
mock_response.raise_for_status = MagicMock()
mock_instance = AsyncMock()
mock_instance.__aenter__ = AsyncMock(return_value=mock_instance)
mock_instance.__aexit__ = AsyncMock(return_value=False)
mock_instance.get.return_value = mock_response
mock_client.return_value = mock_instance
yield mock_client
return _ctx
@pytest.fixture
def mock_repair_failure():
"""Context manager: makes _attempt_repair always return False."""
@asynccontextmanager
async def _ctx():
with patch.object(SelfRepairAgent, "_attempt_repair", new_callable=AsyncMock) as m:
m.return_value = False
yield m
return _ctx
@pytest.fixture
def mock_webhook():
"""Captures webhook POST calls. Use to assert escalation fires correctly."""
class WebhookCapture:
def __init__(self):
self.received_calls = 0
self.last_payload = None
self.url = "http://test-webhook.local/alerts"
capture = WebhookCapture()
async def fake_post(url, json=None, **kwargs):
capture.received_calls += 1
capture.last_payload = json
mock_resp = MagicMock()
mock_resp.raise_for_status = MagicMock()
return mock_resp
with patch("httpx.AsyncClient") as mock_client:
mock_instance = AsyncMock()
mock_instance.__aenter__ = AsyncMock(return_value=mock_instance)
mock_instance.__aexit__ = AsyncMock(return_value=False)
mock_instance.post.side_effect = fake_post
mock_client.return_value = mock_instance
yield capture
What this file has done since we first saw it
Hashed on every crawl. A supply-chain change to an agent config is a question of when, not whether, so the history is kept rather than the latest state alone.
- 2mo ago First seen · 494 lines · 137 tokens per session scan A b574a9cca626
test-writer is an agent published in the GitHub repository ard1102/ip-intelligence (0 stars, last pushed 3mo ago), licensed MIT. It adds 137 tokens to every session and 4,545 once invoked, about $0.0005 per session on Opus 5.5. A static security scan graded it A with 0 findings. No closer match exists in the catalogue, so it is treated as the original; first seen 2026-08-31.
Other agents, from other repositories
test-gaps
Lists the functions in this project that no test calls, with the file each one is in. Use when the user asks what is untested or where tests are missing.
ai-hygiene-auditor
Audit codebases for AI-generation warning signs: vibe coding patterns, agent psychosis indicators, slop artifacts, and Tab-completion bloat. Specialized complement to bloat-auditor.
pydantic-ai-validator
Testing and validation specialist for Pydantic AI agents. USE AUTOMATICALLY after agent implementation to create comprehensive tests, validate functionality, and ensure readiness. Uses TestModel and FunctionModel for thorough validation.
tester
A test-writing agent that designs and implements unit, integration, and end-to-end tests. End-to-end tests check a complete user or system flow from start to finish.
verify-app
Full verification - unit tests, migration check, lint, types.
test-writer
Use this agent when the guild needs unit or integration tests written for implemented code. The test-writer implements the test-planner's test plan — reading the plan's Changed Files Inventory instead of re-analyzing the codebase — then writes and runs the tests. Spawned by the check-in skill when a test-writing task…