content_pipeline / tests /test_comprehensive.py
AI Engineer
Deploy Streamlit Content Agent
0b29030
Raw
History Blame Contribute Delete
18.2 kB
"""Comprehensive tests for every core module.
Runs with: python -m tests.test_comprehensive
"""
import os
import sys
import tempfile
# Ensure stdout handles Unicode on Windows.
if sys.stdout.encoding and sys.stdout.encoding.lower() not in ("utf-8", "utf8"):
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from core.config import Settings, get_settings, PROVIDER_ENDPOINTS, DEFAULT_MODELS
from core.schemas import (CrawledPage, BrandBrain, Brief, FactPack,
GuardrailReport, Variant, GenerationResult)
from core.llm import LLMClient, _extract_json, LLMError
from core import mock_backend
from core import crawler
from core import brand_brain as bb_mod
from core import brief as brief_mod
from core import fact_pack as fp_mod
from core import writers as writers_mod
from core import guardrails
from core import ranker
from core import ppt as ppt_mod
from core.pipeline import ContentAgent
passed = 0
failed = 0
def check(name: str, condition: bool, detail: str = ""):
global passed, failed
if condition:
passed += 1
print(f" ✅ {name}")
else:
failed += 1
print(f" ❌ {name}{detail}")
# ──────────────────────── SCHEMAS ────────────────────────
def test_schemas():
print("\n── schemas ──")
# CrawledPage
p = CrawledPage(url="https://x.com", title="Home", headings=["H1"], text="body")
check("CrawledPage fields", p.url == "https://x.com" and p.title == "Home")
# BrandBrain
bb = BrandBrain(product_name="Test")
d = bb.to_dict()
check("BrandBrain.to_dict", isinstance(d, dict) and d["product_name"] == "Test")
check("BrandBrain defaults", bb.value_props == [] and bb.forbidden_claims == [])
# Brief
b = Brief(raw_input="note")
check("Brief defaults", b.channels == ["linkedin", "instagram", "whatsapp"])
check("Brief.to_dict", "raw_input" in b.to_dict())
# FactPack.allowed_text
fp = FactPack(approved_facts=["fact1"], approved_proof=["proof1"], user_update="update")
check("FactPack.allowed_text", "fact1" in fp.allowed_text() and "update" in fp.allowed_text())
fp_empty = FactPack()
check("FactPack empty allowed_text", fp_empty.allowed_text() == "")
# GuardrailReport.all_flags
gr = GuardrailReport(claim_flags=["c1"], lint_flags=["l1"], policy_flags=["p1"])
check("GuardrailReport.all_flags", len(gr.all_flags()) == 3)
gr_clean = GuardrailReport()
check("GuardrailReport clean passed", gr_clean.passed is True and gr_clean.all_flags() == [])
# Variant
v = Variant(channel="linkedin", text="hello")
check("Variant defaults", v.score == 0.0 and v.guardrails.passed)
# GenerationResult
result = GenerationResult(brief=b, fact_pack=fp)
check("GenerationResult", result.variants_by_channel == {} and result.brand_brain is None)
# ──────────────────────── CONFIG ────────────────────────
def test_config():
print("\n── config ──")
s = Settings()
check("Settings defaults", s.provider == "groq")
check("Settings.resolved_model", s.resolved_model() == DEFAULT_MODELS["groq"])
check("Settings.endpoint", s.endpoint() == PROVIDER_ENDPOINTS["groq"])
# Mock mode + no key → not live
s_mock = Settings(mock_mode=True)
check("mock_mode → not live", not s_mock.is_live())
# Live mode + key → live
s_live = Settings(mock_mode=False, api_key="test-key-1234")
check("live + key → live", s_live.is_live())
# to_dict masks key
d = s_live.to_dict()
check("to_dict masks key", d["api_key"] == "test…")
# get_settings with overrides
s2 = get_settings(mock_mode=True, provider="openrouter")
check("get_settings overrides", s2.mock_mode is True and s2.provider == "openrouter")
# Unknown provider falls back to groq defaults
s3 = Settings(provider="unknown")
check("unknown provider resolved_model fallback", s3.resolved_model() == DEFAULT_MODELS["groq"])
check("unknown provider endpoint fallback", s3.endpoint() == PROVIDER_ENDPOINTS["groq"])
# ──────────────────────── LLM / JSON EXTRACT ────────────────────────
def test_llm():
print("\n── llm ──")
# _extract_json: plain JSON
check("extract plain JSON", _extract_json('{"a": 1}') == {"a": 1})
# With code fences
check("extract fenced JSON", _extract_json('```json\n{"b": 2}\n```') == {"b": 2})
# With surrounding text
check("extract embedded JSON",
_extract_json('Sure! Here is: {"c": 3} hope that helps') == {"c": 3})
# Array
check("extract JSON array", _extract_json('[1, 2, 3]') == [1, 2, 3])
# Bad JSON raises
try:
_extract_json("not json at all")
check("bad JSON raises", False, "should have raised")
except LLMError:
check("bad JSON raises", True)
# LLMClient mock mode
settings = Settings(mock_mode=True)
client = LLMClient(settings)
check("LLMClient.live is False in mock", not client.live)
data = client.generate_json("suggest_prompts", "sys", "user", {})
check("LLMClient mock returns data", "suggestions" in data)
# ──────────────────────── MOCK BACKEND ────────────────────────
def test_mock_backend():
print("\n── mock_backend ──")
tasks = ["suggest_prompts", "build_brief", "brand_brain", "fact_pack",
"write_linkedin", "write_instagram", "write_whatsapp", "ppt_outline"]
for task in tasks:
ctx = {"product_name": "TestProd", "raw_input": "test", "user_update": "test", "num_variants": 2}
result = mock_backend.respond(task, ctx)
check(f"mock {task}", isinstance(result, dict) and len(result) > 0, f"got: {type(result)}")
# Unknown task → empty dict
check("mock unknown task", mock_backend.respond("nonexistent", {}) == {})
# ──────────────────────── CRAWLER ────────────────────────
def test_crawler():
print("\n── crawler ──")
settings = Settings(mock_mode=True)
pages = crawler.crawl("https://example.com", settings)
check("mock crawl returns pages", len(pages) == 3)
check("mock crawl page has text", bool(pages[0].text))
check("mock crawl page has URL", "example.com" in pages[0].url)
# _same_domain
check("_same_domain match",
crawler._same_domain("https://a.com/foo", "https://a.com/bar"))
check("_same_domain mismatch",
not crawler._same_domain("https://a.com", "https://b.com"))
# ──────────────────────── BRAND BRAIN ────────────────────────
def test_brand_brain():
print("\n── brand_brain ──")
settings = Settings(mock_mode=True)
client = LLMClient(settings)
pages = crawler._mock_site("https://acme.com")
bb = bb_mod.build_brand_brain(pages, client)
check("brand_brain product_name", bool(bb.product_name))
check("brand_brain value_props", len(bb.value_props) > 0)
check("brand_brain source_urls", len(bb.source_urls) == len(pages))
check("brand_brain forbidden_claims", len(bb.forbidden_claims) > 0)
# _guess_name
check("_guess_name from URL", bb_mod._guess_name(pages) == "Acme")
check("_guess_name empty", bb_mod._guess_name([]) == "YourProduct")
# ──────────────────────── BRIEF ────────────────────────
def test_brief():
print("\n── brief ──")
settings = Settings(mock_mode=True)
client = LLMClient(settings)
bb = BrandBrain(product_name="Test", one_liner="Test product", value_props=["fast"])
suggestions = brief_mod.suggest_prompts(bb, client)
check("suggest_prompts returns list", isinstance(suggestions, list) and len(suggestions) >= 1)
built = brief_mod.build_brief("shipped a feature", bb, client,
channels=["linkedin"], num_variants=2)
check("build_brief returns brief", "brief" in built and isinstance(built["brief"], Brief))
check("build_brief suggested_prompt", bool(built["suggested_prompt"]))
check("build_brief channels override", built["brief"].channels == ["linkedin"])
check("build_brief num_variants", built["brief"].num_variants == 2)
# ──────────────────────── FACT PACK ────────────────────────
def test_fact_pack():
print("\n── fact_pack ──")
settings = Settings(mock_mode=True)
client = LLMClient(settings)
bb = BrandBrain(product_name="Test", one_liner="A test product",
value_props=["fast"], features=["auto"])
brief = Brief(raw_input="launched today", objective="launch")
fp = fp_mod.build_fact_pack(bb, brief, client)
check("fact_pack approved_facts", len(fp.approved_facts) > 0)
check("fact_pack cannot_claim", len(fp.cannot_claim) > 0)
check("fact_pack user_update preserved", fp.user_update == "launched today")
# ──────────────────────── WRITERS ────────────────────────
def test_writers():
print("\n── writers ──")
settings = Settings(mock_mode=True)
client = LLMClient(settings)
fp = FactPack(approved_facts=["fact1"], approved_proof=["proof1"],
user_update="shipped a feature")
brief = Brief(raw_input="shipped a feature", num_variants=2)
for channel in ["linkedin", "instagram", "whatsapp"]:
variants = writers_mod.write_channel(channel, fp, brief, client)
check(f"write_{channel} returns variants",
isinstance(variants, list) and len(variants) == 2)
check(f"write_{channel} variant has text", bool(variants[0].text))
check(f"write_{channel} variant.channel", variants[0].channel == channel)
# ──────────────────────── GUARDRAILS ────────────────────────
def test_guardrails():
print("\n── guardrails ──")
fp = FactPack(approved_facts=["We integrate with Slack"])
# Clean variant
clean = Variant(channel="linkedin", text="We integrate with Slack for better collaboration.")
report = guardrails.evaluate(clean, fp)
check("clean variant passes", report.passed)
# Unsupported claims
bad_claims = Variant(channel="linkedin",
text="We have 10000 users and are the #1 fastest guaranteed platform.")
report2 = guardrails.evaluate(bad_claims, fp)
check("bad claims flagged", not report2.passed)
check("claim_flags populated", len(report2.claim_flags) > 0)
# Specific claim patterns
for text, expected_claims in [
("50% faster", ["50%"]),
("guaranteed results", ["guaranteed"]),
("world's best", ["world's", "best"]),
("HIPAA compliant", ["compliant", "hipaa"]),
("$2M ARR", ["arr"]),
]:
v = Variant(channel="linkedin", text=text)
flags = guardrails.check_claims(v, fp)
check(f"claim pattern: '{text}'", len(flags) > 0, f"flags={flags}")
# Lint checks
long_v = Variant(channel="whatsapp", text="x" * 800)
lint_flags = guardrails.check_lints(long_v)
check("lint: too long whatsapp", any("Too long" in f for f in lint_flags))
hashtag_v = Variant(channel="linkedin", text="hello",
hashtags=["#" + str(i) for i in range(20)])
lint2 = guardrails.check_lints(hashtag_v)
check("lint: too many hashtags", any("hashtags" in f.lower() for f in lint2))
empty_v = Variant(channel="linkedin", text=" ")
lint3 = guardrails.check_lints(empty_v)
check("lint: empty content", any("Empty" in f for f in lint3))
# Policy filter
bad_policy = Variant(channel="linkedin", text="This is a scam product")
policy_flags = guardrails.check_policy(bad_policy)
check("policy: banned term", len(policy_flags) > 0)
clean_policy = Variant(channel="linkedin", text="Great product for teams")
check("policy: clean text", len(guardrails.check_policy(clean_policy)) == 0)
# ──────────────────────── RANKER ────────────────────────
def test_ranker():
print("\n── ranker ──")
fp = FactPack(approved_facts=["Ships integrations", "Built for lean teams"])
# Passed guardrails + CTA + good length → high score
good = Variant(channel="linkedin",
text="Ships integrations for lean teams. " * 5,
cta="Try it now",
guardrails=GuardrailReport(passed=True))
score = ranker.score_variant(good, fp)
check("good variant score > 3", score > 3.0, f"score={score}")
# Failed guardrails → low score
bad = Variant(channel="linkedin",
text="This is a game changer. Revolutionary and next level.",
guardrails=GuardrailReport(passed=False, claim_flags=["flag1", "flag2"]))
bad_score = ranker.score_variant(bad, fp)
check("bad variant score < good", bad_score < score)
# Generic phrases penalized
generic = Variant(channel="linkedin",
text="This is a game changer and cutting edge next level tool " * 3,
guardrails=GuardrailReport(passed=True))
generic_score = ranker.score_variant(generic, fp)
non_generic = Variant(channel="linkedin",
text="Our integrations connect with Slack and Notion for faster workflows " * 3,
guardrails=GuardrailReport(passed=True))
non_generic_score = ranker.score_variant(non_generic, fp)
check("generic penalized vs non-generic", generic_score < non_generic_score,
f"generic={generic_score}, non_generic={non_generic_score}")
# Ranking sorts correctly
variants = [bad, good, generic, non_generic]
ranked = ranker.rank(variants, fp)
check("rank returns sorted desc", ranked[0].score >= ranked[-1].score)
# Very short text → low specificity
short = Variant(channel="linkedin", text="Hello",
guardrails=GuardrailReport(passed=True))
check("short text specificity = 0.3",
ranker._specificity(short.text) == 0.3)
# ──────────────────────── PPT ────────────────────────
def test_ppt():
print("\n── ppt ──")
settings = Settings(mock_mode=True)
client = LLMClient(settings)
fp = FactPack(approved_facts=["fact1"], approved_proof=["proof1"],
user_update="shipped integrations")
brief = Brief(raw_input="shipped integrations", objective="daily_update")
outline = ppt_mod.build_outline(fp, brief, client)
check("ppt outline has title", "title" in outline)
check("ppt outline has slides", len(outline.get("slides", [])) > 0)
out_dir = tempfile.mkdtemp()
out_path = os.path.join(out_dir, "test_deck.pptx")
result_path = ppt_mod.render_pptx(outline, out_path)
check("pptx file created", os.path.exists(result_path))
check("pptx file non-empty", os.path.getsize(result_path) > 0)
# Render with empty outline
empty_outline = {"title": "Empty", "subtitle": "", "slides": []}
empty_path = os.path.join(out_dir, "empty_deck.pptx")
ppt_mod.render_pptx(empty_outline, empty_path)
check("empty outline renders", os.path.exists(empty_path))
# ──────────────────────── PIPELINE (INTEGRATION) ────────────────────────
def test_pipeline():
print("\n── pipeline (integration) ──")
settings = get_settings(mock_mode=True)
agent = ContentAgent(settings)
# Full flow
brand = agent.ingest_website("https://testproduct.io")
check("pipeline: ingest", bool(brand.product_name))
suggestions = agent.suggest_prompts(brand)
check("pipeline: suggestions", len(suggestions) >= 1)
built = agent.build_brief("we added dark mode", brand,
channels=["linkedin", "whatsapp"], num_variants=2)
brief = built["brief"]
check("pipeline: brief channels", brief.channels == ["linkedin", "whatsapp"])
result = agent.generate(brief, brand)
check("pipeline: generate has channels",
set(result.variants_by_channel.keys()) == {"linkedin", "whatsapp"})
for ch, variants in result.variants_by_channel.items():
check(f"pipeline: {ch} variants count", len(variants) == 2)
check(f"pipeline: {ch} ranked (scores desc)",
variants[0].score >= variants[-1].score)
# PPT export
out = os.path.join(tempfile.mkdtemp(), "pipeline_deck.pptx")
path = agent.export_ppt(result.fact_pack, brief, out)
check("pipeline: pptx exists", os.path.exists(path) and os.path.getsize(path) > 0)
# ──────────────────────── RUN ALL ────────────────────────
def main():
test_schemas()
test_config()
test_llm()
test_mock_backend()
test_crawler()
test_brand_brain()
test_brief()
test_fact_pack()
test_writers()
test_guardrails()
test_ranker()
test_ppt()
test_pipeline()
print(f"\n{'='*50}")
print(f" Results: {passed} passed, {failed} failed")
print(f"{'='*50}")
if failed:
sys.exit(1)
else:
print(" ALL COMPREHENSIVE TESTS PASSED ✅")
if __name__ == "__main__":
main()