Spaces:
Sleeping
Sleeping
| """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() | |