| 1 | """grok's three supplement lanes, wired into the pipeline. |
| 2 | |
| 3 | The coverage requirement: for an entity topic the run must return what the |
| 4 | subject said, what others said *to* them, and what others said *about* them by |
| 5 | name. The third is not redundant with the second -- most discussion never |
| 6 | @-mentions the subject, so a mention-only lane structurally cannot reach it. |
| 7 | """ |
| 8 | |
| 9 | import inspect |
| 10 | |
| 11 | import pytest |
| 12 | |
| 13 | from lib import pipeline, schema |
| 14 | |
| 15 | |
| 16 | def _supplements_source(): |
| 17 | return inspect.getsource(pipeline._run_supplemental_searches) |
| 18 | |
| 19 | |
| 20 | def test_grok_is_handle_lane_capable(): |
| 21 | src = _supplements_source() |
| 22 | assert '("grok", "bird", "xapi", "xquik")' in src, ( |
| 23 | "grok supports from:/@ natively; leaving it out of the capable set " |
| 24 | "silently drops all of Phase 2 for grok users, as it already does for " |
| 25 | "xai and xurl. xapi (X API v2 bearer) runs the same lanes, after " |
| 26 | "bird and before xquik (R7)." |
| 27 | ) |
| 28 | |
| 29 | |
| 30 | def test_xapi_lane_branch_sits_between_bird_and_xquik(): |
| 31 | src = _supplements_source() |
| 32 | grok = src.index('if primary == "grok":') |
| 33 | bird = src.index('elif primary == "bird":') |
| 34 | xapi = src.index('elif primary == "xapi":') |
| 35 | xquik = src.index('elif primary == "xquik":') |
| 36 | assert grok < bird < xapi < xquik |
| 37 | xapi_block = src[xapi:xquik] |
| 38 | assert "x_api.search_handles" in xapi_block |
| 39 | assert "x_api.search_mentions" in xapi_block |
| 40 | |
| 41 | |
| 42 | def test_all_three_lanes_are_defined_for_grok(): |
| 43 | src = _supplements_source() |
| 44 | grok_block = src[src.index('if primary == "grok":'):src.index('elif primary == "bird":')] |
| 45 | assert "_from_lane" in grok_block |
| 46 | assert "_about_lane" in grok_block |
| 47 | assert "_name_lane" in grok_block |
| 48 | |
| 49 | |
| 50 | def test_name_lane_is_gated_and_defaults_off(): |
| 51 | """Backends without phrase/negation support must not get a broken lane.""" |
| 52 | src = _supplements_source() |
| 53 | assert "_name_lane = None" in src |
| 54 | assert "if _name_lane is not None:" in src |
| 55 | |
| 56 | |
| 57 | def test_name_lane_items_reach_the_batch(): |
| 58 | src = _supplements_source() |
| 59 | assert "from_items + about_items + name_items" in src, ( |
| 60 | "name-lane results must join the batch, not be computed and dropped" |
| 61 | ) |
| 62 | |
| 63 | |
| 64 | def test_name_lane_excludes_the_subject_handles(): |
| 65 | src = _supplements_source() |
| 66 | grok_block = src[src.index('if primary == "grok":'):src.index('elif primary == "bird":')] |
| 67 | assert "exclude_handles=hs" in grok_block, ( |
| 68 | "the name lane must exclude the subject's own posts; those belong to " |
| 69 | "the by-lane and would otherwise double-count" |
| 70 | ) |
| 71 | |
| 72 | |
| 73 | def test_name_lane_failure_does_not_abort_the_run(): |
| 74 | src = _supplements_source() |
| 75 | block = src[src.index("if _name_lane is not None:"):] |
| 76 | assert "except Exception" in block |
| 77 | assert "NAME-lane" in block |
| 78 | |
| 79 | |
| 80 | def test_partial_coverage_is_recorded(): |
| 81 | """One-sided coverage must be visible, not look like thin discussion.""" |
| 82 | src = _supplements_source() |
| 83 | assert "partial coverage" in src |
| 84 | assert 'if empty and len(empty) < 3:' in src, ( |
| 85 | "an all-empty result is an ordinary no-results outcome, not partial " |
| 86 | "coverage; only a mixed result is worth flagging" |
| 87 | ) |
| 88 | |
| 89 | |
| 90 | def test_by_lane_does_not_and_the_topic_by_default(): |
| 91 | """A prior defect emptied the from-lane by ANDing the topic into it. |
| 92 | |
| 93 | The `and_topic` parameter now exists for extracted handles that need to |
| 94 | demonstrate on-topic content, but the default is False (no topic AND). |
| 95 | """ |
| 96 | from lib import grok_x |
| 97 | sig = inspect.signature(grok_x.search_handles) |
| 98 | assert "topic" in sig.parameters |
| 99 | # and_topic parameter should default to False |
| 100 | assert "and_topic" in sig.parameters |
| 101 | assert sig.parameters["and_topic"].default is False |
| 102 | body = inspect.getsource(grok_x.search_handles) |
| 103 | assert "from:{clean}" in body |
| 104 | # The default path (and_topic=False) should not include {topic} in the query |
| 105 | assert 'query = f"from:{clean} since:{from_date}' in body |
| 106 | |
| 107 | |
| 108 | |
| 109 | # --- behavioral: the source-text assertions above cannot catch a crash ------- |
| 110 | |
| 111 | def test_partial_coverage_does_not_raise_on_an_empty_x_source(): |
| 112 | """Regression: partial coverage was recorded via bundle.record_failure with |
| 113 | the state string "degraded", which is not in SourceOutcome's valid_states. |
| 114 | With zero Phase-1 X items record_failure passes the caller's state straight |
| 115 | through, so it raised ValueError and killed the whole run -- on exactly the |
| 116 | entity topics this feature targets. No source-text assertion could catch |
| 117 | this; only executing the path does.""" |
| 118 | bundle = schema.RetrievalBundle() |
| 119 | assert not bundle.items_by_source.get("x") |
| 120 | empty = ["mention"] |
| 121 | # Mirror the production call: this must not raise. |
| 122 | bundle.artifacts.setdefault("x_partial_coverage", []).append( |
| 123 | f"X partial coverage: {', '.join(empty)} lane(s) returned nothing" |
| 124 | ) |
| 125 | assert bundle.artifacts["x_partial_coverage"] |
| 126 | |
| 127 | |
| 128 | def test_degraded_is_not_a_valid_source_outcome_state(): |
| 129 | """Pins why partial coverage must not go through record_failure.""" |
| 130 | with pytest.raises(ValueError): |
| 131 | schema.SourceOutcome( |
| 132 | source="x", state="degraded", items_returned=0, attempted=True, |
| 133 | ) |
| 134 | |
| 135 | |
| 136 | def test_partial_coverage_is_not_recorded_as_a_source_failure(): |
| 137 | """A one-sided lane result must not mark X partial: PARTIAL is outside |
| 138 | _STRICT_EXIT_OK_STATES, so wrappers using LAST30DAYS_STRICT_EXIT would exit |
| 139 | 3 on runs that returned good X coverage.""" |
| 140 | src = _supplements_source() |
| 141 | # Strip comments: the rationale for NOT using record_failure names it. |
| 142 | code = "\n".join( |
| 143 | line for line in src.splitlines() if not line.strip().startswith("#") |
| 144 | ) |
| 145 | idx = code.index("x_partial_coverage") |
| 146 | window = code[max(0, idx - 400):idx] |
| 147 | assert "record_failure" not in window, ( |
| 148 | "partial lane coverage must be a warning, not a source outcome: " |
| 149 | "record_failure would set X to PARTIAL and trip strict-exit wrappers" |
| 150 | ) |
| 151 |