"""Unit tests for the rule-based ABSA pipeline aspect extraction + fallbacks.""" from __future__ import annotations import os os.environ.setdefault("DATABASE_URL", "sqlite:///./tests/fixtures/test.db") from absa.pipeline.absa_pipeline import pipeline def _aspects(text: str) -> list[dict]: result = pipeline.predict(text, "en") return [a.model_dump() if hasattr(a, "model_dump") else dict(a) for a in result.aspects] def _sentiment_of(aspects: list[dict], aspect: str) -> str | None: for a in aspects: if a["aspect"].lower() == aspect.lower(): return a["sentiment"] return None def test_food_and_service_extracted_from_sentiment_words(): aspects = _aspects("The food was great but the service was terrible.") labels = [a["aspect"].lower() for a in aspects] assert "food" in labels assert "service" in labels assert _sentiment_of(aspects, "food") == "positive" assert _sentiment_of(aspects, "service") == "negative" def test_noun_after_sentiment_word_extracted(): aspects = _aspects("Great camera quality and a lovely experience overall.") labels = [a["aspect"].lower() for a in aspects] # "camera quality" is in the lexicon; "experience" must come from the fallback. assert any("camera" in label or "experience" in label for label in labels) def test_product_target_fallback(): aspects = _aspects("I absolutely love this product.") assert _sentiment_of(aspects, "product") == "positive" def test_generic_overall_fallback_when_no_aspect_noun(): aspects = _aspects("Absolutely terrible, do not recommend.") assert aspects, "should always return at least one aspect" assert _sentiment_of(aspects, "overall") == "negative" def test_positive_bare_comment_returns_result(): aspects = _aspects("Amazing!") assert aspects, "should always return at least one aspect" assert _sentiment_of(aspects, "overall") == "positive" def test_neutral_text_returns_overall_neutral(): aspects = _aspects("Hello there, just checking.") assert aspects assert _sentiment_of(aspects, "overall") == "neutral" def test_devanagari_comment_gets_aspect_and_sentiment(): aspects = _aspects("खाना बहुत अच्छा था।") assert aspects, "Devanagari comment should still produce a result" assert any(a["aspect"].lower() in ("खाना", "overall") for a in aspects) assert _sentiment_of(aspects, "खाना") == "positive" def test_empty_text_never_crashes(): aspects = _aspects("") assert aspects # generic fallback keeps the response non-empty def test_lexicon_still_used_first(): aspects = _aspects("The battery life is amazing but the screen is too dim.") labels = [a["aspect"].lower() for a in aspects] assert "battery life" in labels or "battery" in labels assert _sentiment_of(aspects, "battery life") == "positive" assert _sentiment_of(aspects, "screen") == "negative" def test_purchase_target_with_strong_negation(): aspects = _aspects("Worst purchase ever, do not buy.") assert _sentiment_of(aspects, "purchase") == "negative" def test_devanagari_mixed_clauses_split_on_lekin(): aspects = _aspects("खाना बढ़िया था लेकिन सेवा खराब थी।") assert _sentiment_of(aspects, "खाना") == "positive" assert _sentiment_of(aspects, "सेवा") == "negative" def test_hinglish_mixed_clauses_split_on_lekin(): aspects = _aspects("The phone ka design badhiya hai lekin battery life kharab hai.") assert _sentiment_of(aspects, "design") == "positive" assert _sentiment_of(aspects, "battery life") == "negative" def test_devanagari_word_never_split_inside(): # Regression: "बढ़िया" used to be split by the "या" clause separator. aspects = _aspects("खाना बहुत बढ़िया था।") assert _sentiment_of(aspects, "खाना") == "positive" def test_experience_positive(): aspects = _aspects("Great experience overall, will order again.") assert _sentiment_of(aspects, "experience") == "positive"