Skip to content

Commit bc013ff

Browse files
captainpacketclaude
andcommitted
test: live coverage for the Predict families, and an exact trigger match
Read-only live tests for change sets, diffs, webhooks and configuration, each skipping rather than failing on an instance with nothing to read, so the weekly backstop watches these shapes too. All 23 pass against the local build with writes, and 21 pass against a second Predict-enabled instance with the rest skipped. That second instance found a test bug worth naming. Its newest predictions were unprocessed, which Forward refuses with 409 and the documented SNAPSHOT_UNAVAILABLE reason, correctly. The test's fix was to pick a processed prediction, and the first attempt at that used a suffix match on the state, which is wrong for a reason that reads like a joke: "UNPROCESSED" ends with "PROCESSED". The same idiom was in the SDK's own predicted-snapshot check, harmless there because no trigger name ends in PREDICT other than PREDICT, but it is now an exact match on the value so it cannot become harmful when a name is added. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019Kbxab7ihjPntGAmnsZtqG
1 parent 80b192f commit bc013ff

3 files changed

Lines changed: 30 additions & 9 deletions

File tree

‎src/forward_sdk/_async/services/snapshots.py‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,12 @@ def __repr__(self) -> str: # pragma: no cover - debugging aid
7777
def is_predicted(snapshot: SnapshotInfo) -> bool:
7878
"""Whether Forward made this snapshot to analyse a change set."""
7979
trigger = getattr(snapshot, "processing_trigger", None)
80-
return trigger is not None and str(trigger).upper().endswith(PREDICT_TRIGGER)
80+
if trigger is None:
81+
return False
82+
# Exact match on the value. A suffix test would be wrong for the same
83+
# reason "UNPROCESSED" ends with "PROCESSED".
84+
value = getattr(trigger, "value", trigger)
85+
return str(value).upper() == PREDICT_TRIGGER
8186

8287

8388
def _state_of(snapshot: SnapshotInfo) -> str:

‎src/forward_sdk/_sync/services/snapshots.py‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,12 @@ def __repr__(self) -> str: # pragma: no cover - debugging aid
7979
def is_predicted(snapshot: SnapshotInfo) -> bool:
8080
"""Whether Forward made this snapshot to analyse a change set."""
8181
trigger = getattr(snapshot, "processing_trigger", None)
82-
return trigger is not None and str(trigger).upper().endswith(PREDICT_TRIGGER)
82+
if trigger is None:
83+
return False
84+
# Exact match on the value. A suffix test would be wrong for the same
85+
# reason "UNPROCESSED" ends with "PROCESSED".
86+
value = getattr(trigger, "value", trigger)
87+
return str(value).upper() == PREDICT_TRIGGER
8388

8489

8590
def _state_of(snapshot: SnapshotInfo) -> str:

‎tests/live/test_live.py‎

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -244,13 +244,24 @@ def test_change_set_summary_and_commits(self, client: ForwardClient, network_id:
244244
def test_diffs_between_a_base_and_its_prediction(
245245
self, client: ForwardClient, network_id: str
246246
) -> None:
247-
candidates = [cs for cs in client.change_sets.list(network_id) if cs.predicted_snapshots]
248-
if not candidates:
249-
pytest.skip("no change set has a predicted snapshot")
250-
base = candidates[0].snapshot_id
251-
predicted = candidates[0].predicted_snapshots or []
252-
after = predicted[0].id if predicted else None
253-
assert base and after
247+
# A prediction that has not finished processing is refused with 409
248+
# SNAPSHOT_UNAVAILABLE, correctly, so pick one that has. Compare the
249+
# state exactly: "UNPROCESSED" ends with "PROCESSED".
250+
base = after = None
251+
for info in client.change_sets.list(network_id):
252+
if not info.predicted_snapshots or not info.id:
253+
continue
254+
handle = client.change_sets.handle(info.id, network_id=network_id)
255+
processed = [
256+
snap
257+
for snap in handle.predicted_snapshots()
258+
if str(snap.state) == "PROCESSED" and snap.id
259+
]
260+
if processed:
261+
base, after = info.snapshot_id, processed[0].id
262+
break
263+
if not (base and after):
264+
pytest.skip("no change set has a processed predicted snapshot")
254265
diffs = client.snapshot_diffs
255266
connectivity = diffs.wait_for_subnet_connectivity(base, after, timeout=300)
256267
assert not connectivity.is_partial_result

0 commit comments

Comments
 (0)