Spaces:
Sleeping
Sleeping
firepenguindisopanda
feat(03): add QualityEngine, DomainTermWhitelist, calibrated budgets, and quality schemas
a746fba | """Tests for QualityEngine, DomainTermWhitelist, and quality schemas.""" | |
| import pytest | |
| from app.core.quality_engine import QualityEngine, DomainTermWhitelist | |
| from app.core.schemas import QualityReport, ARIResult, RePromptAttempt | |
| # Verify textstat is installed | |
| textstat = pytest.importorskip("textstat") | |
| # ββ QualityReport schema ββ | |
| def test_quality_report_defaults(): | |
| """QualityReport has sensible defaults and validates correctly.""" | |
| report = QualityReport( | |
| role="test", agent_output="Hello world", ari_before_any=5.0, final_ari=5.0 | |
| ) | |
| assert report.role == "test" | |
| assert report.re_prompt_count == 0 | |
| assert report.final_disposition == "passed" | |
| assert report.is_budget_met() # No dimensions = vacuously true | |
| def test_quality_report_with_dimensions(): | |
| """is_budget_met checks budget-named dimensions.""" | |
| report = QualityReport( | |
| role="test", | |
| agent_output="x", | |
| ari_before_any=1, | |
| final_ari=1, | |
| dimensions=[ | |
| { | |
| "name": "ari_budget_check", | |
| "score": 16, | |
| "threshold": 14, | |
| "passed": False, | |
| "details": "", | |
| }, | |
| ], | |
| ) | |
| assert not report.is_budget_met() | |
| def test_ari_result_passed(): | |
| """ARIResult.passed correctly reflects score <= budget.""" | |
| r = ARIResult(raw_score=12, whitelist_score=11, budget=14, passed=True) | |
| assert r.passed | |
| r2 = ARIResult(raw_score=18, whitelist_score=17, budget=14, passed=False) | |
| assert not r2.passed | |
| def test_reprompt_attempt_stagnation(): | |
| """RePromptAttempt.stagnant correctly identifies delta < 0.5.""" | |
| a = RePromptAttempt( | |
| attempt_number=1, | |
| strategy="simplify", | |
| ari_before=15.0, | |
| ari_after=15.3, | |
| ari_delta=0.3, | |
| stagnant=True, | |
| ) | |
| assert a.stagnant | |
| b = RePromptAttempt( | |
| attempt_number=2, | |
| strategy="restructure", | |
| ari_before=15.0, | |
| ari_after=13.2, | |
| ari_delta=-1.8, | |
| stagnant=False, | |
| ) | |
| assert not b.stagnant | |
| # ββ DomainTermWhitelist ββ | |
| def test_whitelist_strips_known_terms(): | |
| """Whitelisted terms are replaced with placeholders.""" | |
| wl = DomainTermWhitelist() | |
| text = "Use dependency injection and bounded context patterns." | |
| cleaned = wl.strip_known_terms(text) | |
| assert "dependency injection" not in cleaned | |
| assert "bounded context" not in cleaned | |
| assert "word" in cleaned.lower() # placeholder | |
| assert "patterns" in cleaned # non-whitelisted preserved | |
| def test_whitelist_matched_terms(): | |
| """get_matched_terms returns correct matches.""" | |
| wl = DomainTermWhitelist() | |
| text = "Use microservices with event-driven architecture and ACID compliance." | |
| matched = wl.get_matched_terms(text) | |
| assert "microservices" in matched | |
| assert "event-driven" in matched | |
| assert len(matched) >= 3 # microservices, event-driven, ACID | |
| def test_whitelist_multi_word_preserves_surrounding(): | |
| """Multi-word replacement preserves surrounding text.""" | |
| wl = DomainTermWhitelist() | |
| text = "The blue-green deployment strategy works." | |
| cleaned = wl.strip_known_terms(text) | |
| assert "blue-green deployment" not in cleaned | |
| assert "strategy" in cleaned # surrounding text preserved | |
| assert "works" in cleaned | |
| # ββ QualityEngine ββ | |
| def test_engine_score_output_returns_report(): | |
| """score_output returns a valid QualityReport with expected fields.""" | |
| engine = QualityEngine() | |
| report = engine.score_output( | |
| "The system shall authenticate users via JWT tokens.", "product_owner" | |
| ) | |
| assert isinstance(report, QualityReport) | |
| assert report.role == "product_owner" | |
| assert report.word_count > 0 | |
| assert report.sentence_count > 0 | |
| assert report.final_ari > 0 | |
| assert report.ari_result.budget == 12 # PO budget | |
| def test_engine_score_output_async(): | |
| """Async wrapper runs without error (basic smoke test).""" | |
| import asyncio | |
| engine = QualityEngine() | |
| report = asyncio.run( | |
| engine.score_output_async("Test output for async scoring.", "solution_architect") | |
| ) | |
| assert isinstance(report, QualityReport) | |
| assert report.final_ari > 0 | |
| def test_engine_role_specific_budgets(): | |
| """Different roles have different budgets enforced.""" | |
| engine = QualityEngine() | |
| long_text = ( | |
| "Asynchronous bidirectional synchronous replication infrastructure requires " | |
| "containerization orchestration implementation across heterogeneous environments. " | |
| "Microservices event-driven domain-driven architecture decomposition patterns." | |
| ) | |
| po_report = engine.score_output(long_text, "product_owner") | |
| sa_report = engine.score_output(long_text, "solution_architect") | |
| # PO has tighter budget; SA has looser | |
| assert po_report.ari_result.budget == 12 | |
| assert sa_report.ari_result.budget == 20 | |
| def test_engine_detect_stagnation(): | |
| """Stagnation detected when delta < 0.5 between last two scores.""" | |
| engine = QualityEngine() | |
| assert not engine.detect_stagnation([15.0]) # Only 1 score | |
| assert not engine.detect_stagnation([15.0, 14.2]) # Delta = 0.8 >= 0.5 | |
| assert engine.detect_stagnation([15.0, 15.3]) # Delta = 0.3 < 0.5 | |
| assert engine.detect_stagnation([18.0, 18.1]) # Delta = 0.1 < 0.5 | |