assertion-cli 0.5.16__tar.gz → 0.5.17__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {assertion_cli-0.5.16 → assertion_cli-0.5.17}/PKG-INFO +1 -1
- {assertion_cli-0.5.16 → assertion_cli-0.5.17}/assertion_cli.egg-info/PKG-INFO +1 -1
- {assertion_cli-0.5.16 → assertion_cli-0.5.17}/main.py +35 -13
- {assertion_cli-0.5.16 → assertion_cli-0.5.17}/pyproject.toml +1 -1
- {assertion_cli-0.5.16 → assertion_cli-0.5.17}/templates/SKILL.md +1 -1
- {assertion_cli-0.5.16 → assertion_cli-0.5.17}/tests/test_verify.py +78 -76
- {assertion_cli-0.5.16 → assertion_cli-0.5.17}/tests/test_verify_integration.py +45 -0
- {assertion_cli-0.5.16 → assertion_cli-0.5.17}/README.md +0 -0
- {assertion_cli-0.5.16 → assertion_cli-0.5.17}/api.py +0 -0
- {assertion_cli-0.5.16 → assertion_cli-0.5.17}/assertion_cli.egg-info/SOURCES.txt +0 -0
- {assertion_cli-0.5.16 → assertion_cli-0.5.17}/assertion_cli.egg-info/dependency_links.txt +0 -0
- {assertion_cli-0.5.16 → assertion_cli-0.5.17}/assertion_cli.egg-info/entry_points.txt +0 -0
- {assertion_cli-0.5.16 → assertion_cli-0.5.17}/assertion_cli.egg-info/requires.txt +0 -0
- {assertion_cli-0.5.16 → assertion_cli-0.5.17}/assertion_cli.egg-info/top_level.txt +0 -0
- {assertion_cli-0.5.16 → assertion_cli-0.5.17}/bundle.py +0 -0
- {assertion_cli-0.5.16 → assertion_cli-0.5.17}/credentials.py +0 -0
- {assertion_cli-0.5.16 → assertion_cli-0.5.17}/git.py +0 -0
- {assertion_cli-0.5.16 → assertion_cli-0.5.17}/link.py +0 -0
- {assertion_cli-0.5.16 → assertion_cli-0.5.17}/models.py +0 -0
- {assertion_cli-0.5.16 → assertion_cli-0.5.17}/session.py +0 -0
- {assertion_cli-0.5.16 → assertion_cli-0.5.17}/setup.cfg +0 -0
- {assertion_cli-0.5.16 → assertion_cli-0.5.17}/templates/AGENTS.md +0 -0
- {assertion_cli-0.5.16 → assertion_cli-0.5.17}/templates/__init__.py +0 -0
- {assertion_cli-0.5.16 → assertion_cli-0.5.17}/tests/test_api.py +0 -0
- {assertion_cli-0.5.16 → assertion_cli-0.5.17}/tests/test_auth.py +0 -0
- {assertion_cli-0.5.16 → assertion_cli-0.5.17}/tests/test_bundle.py +0 -0
- {assertion_cli-0.5.16 → assertion_cli-0.5.17}/tests/test_credentials.py +0 -0
- {assertion_cli-0.5.16 → assertion_cli-0.5.17}/tests/test_decision.py +0 -0
- {assertion_cli-0.5.16 → assertion_cli-0.5.17}/tests/test_git.py +0 -0
- {assertion_cli-0.5.16 → assertion_cli-0.5.17}/tests/test_install.py +0 -0
- {assertion_cli-0.5.16 → assertion_cli-0.5.17}/tests/test_link.py +0 -0
- {assertion_cli-0.5.16 → assertion_cli-0.5.17}/tests/test_main.py +0 -0
- {assertion_cli-0.5.16 → assertion_cli-0.5.17}/tests/test_prompt.py +0 -0
- {assertion_cli-0.5.16 → assertion_cli-0.5.17}/tests/test_session.py +0 -0
- {assertion_cli-0.5.16 → assertion_cli-0.5.17}/tests/test_session_start.py +0 -0
|
@@ -694,22 +694,36 @@ def _build_verify_bundle(
|
|
|
694
694
|
return bundle_bytes, review_diff_text, compute_review_diff_hash(review_diff_text)
|
|
695
695
|
|
|
696
696
|
|
|
697
|
-
def
|
|
697
|
+
def _compute_review_diff_fingerprint(ctx) -> tuple[str, str]:
|
|
698
|
+
"""Return (review_diff_text, review_diff_hash) without bundle side effects."""
|
|
699
|
+
verify_base = read_init_verify_base_sha(ctx.assertion_dir)
|
|
700
|
+
review_diff_text = get_full_diff(ctx.repo_root, verify_base)
|
|
701
|
+
return review_diff_text, compute_review_diff_hash(review_diff_text)
|
|
702
|
+
|
|
703
|
+
|
|
704
|
+
def _try_reuse_verify(
|
|
698
705
|
assertion_dir: Path,
|
|
699
706
|
*,
|
|
700
707
|
review_diff_hash: str,
|
|
701
708
|
json_output: bool,
|
|
702
709
|
) -> bool:
|
|
703
|
-
"""Reuse
|
|
710
|
+
"""Reuse a prior verification when the review diff is unchanged.
|
|
704
711
|
|
|
705
|
-
Returns True when
|
|
712
|
+
Returns True when an existing session is reused (no new submission). Covers
|
|
713
|
+
both terminal ``succeeded`` runs and in-flight ``created``/``running`` runs
|
|
714
|
+
so a repeated ``asrt verify`` does not submit twice for the same diff.
|
|
706
715
|
"""
|
|
707
716
|
stored_hash = read_review_diff_hash(assertion_dir)
|
|
708
717
|
if stored_hash != review_diff_hash:
|
|
709
718
|
return False
|
|
710
719
|
|
|
711
720
|
last_status = read_verify_last_status(assertion_dir)
|
|
712
|
-
|
|
721
|
+
reusable_statuses = {
|
|
722
|
+
SessionStatus.SUCCEEDED.value,
|
|
723
|
+
SessionStatus.CREATED.value,
|
|
724
|
+
SessionStatus.RUNNING.value,
|
|
725
|
+
}
|
|
726
|
+
if last_status not in reusable_statuses:
|
|
713
727
|
return False
|
|
714
728
|
|
|
715
729
|
session_id = _read_optional_verify_session_id(assertion_dir)
|
|
@@ -726,12 +740,12 @@ def _try_reuse_successful_verify(
|
|
|
726
740
|
{
|
|
727
741
|
"session_id": session_id,
|
|
728
742
|
"url": session_url,
|
|
729
|
-
"status":
|
|
743
|
+
"status": last_status,
|
|
730
744
|
"reused": True,
|
|
731
745
|
}
|
|
732
746
|
)
|
|
733
747
|
)
|
|
734
|
-
|
|
748
|
+
elif last_status == SessionStatus.SUCCEEDED.value:
|
|
735
749
|
typer.echo(
|
|
736
750
|
"Review diff unchanged since the last successful verification — "
|
|
737
751
|
f"reusing session {session_id}."
|
|
@@ -740,6 +754,13 @@ def _try_reuse_successful_verify(
|
|
|
740
754
|
typer.echo(
|
|
741
755
|
"Embed `asrt get-link` in the PR body if the branch name may differ."
|
|
742
756
|
)
|
|
757
|
+
else:
|
|
758
|
+
typer.echo(
|
|
759
|
+
"Review diff unchanged since the last verification submission — "
|
|
760
|
+
f"session {session_id} is still {last_status}."
|
|
761
|
+
)
|
|
762
|
+
typer.echo(f"URL: {session_url}")
|
|
763
|
+
typer.echo("Poll `asrt verify-status` instead of submitting again.")
|
|
743
764
|
return True
|
|
744
765
|
|
|
745
766
|
|
|
@@ -763,24 +784,25 @@ def verify(
|
|
|
763
784
|
this command only submits — poll `asrt verify-status` (one-shot) on whatever
|
|
764
785
|
interval your harness prefers to learn the outcome.
|
|
765
786
|
|
|
766
|
-
When the review-scope diff is unchanged since the last
|
|
767
|
-
|
|
768
|
-
Pass ``--force`` to always
|
|
787
|
+
When the review-scope diff is unchanged since the last verification
|
|
788
|
+
submission, the CLI reuses that session instead of submitting again —
|
|
789
|
+
including while a prior run is still ``running``. Pass ``--force`` to always
|
|
790
|
+
submit.
|
|
769
791
|
"""
|
|
770
792
|
ctx, metadata = load_existing_session(Path.cwd())
|
|
771
793
|
stack_id = metadata.stack_id
|
|
772
794
|
assert stack_id is not None
|
|
773
|
-
|
|
774
|
-
ctx, metadata
|
|
775
|
-
)
|
|
795
|
+
_, review_diff_hash = _compute_review_diff_fingerprint(ctx)
|
|
776
796
|
|
|
777
|
-
if not force and
|
|
797
|
+
if not force and _try_reuse_verify(
|
|
778
798
|
ctx.assertion_dir,
|
|
779
799
|
review_diff_hash=review_diff_hash,
|
|
780
800
|
json_output=json_output,
|
|
781
801
|
):
|
|
782
802
|
return
|
|
783
803
|
|
|
804
|
+
bundle_bytes, _, review_diff_hash = _build_verify_bundle(ctx, metadata)
|
|
805
|
+
|
|
784
806
|
client = AssertionClient()
|
|
785
807
|
resp = client.verify(
|
|
786
808
|
stack_id=stack_id,
|
|
@@ -144,7 +144,7 @@ Do not call `verify` mid-task. It is not a substitute for checkpoints; it is the
|
|
|
144
144
|
|
|
145
145
|
`verify` is **non-blocking**: it submits the verification and returns immediately with the session id and URL. The verification itself can take several minutes — you poll `asrt verify-status` on your own cadence to learn the outcome. This avoids tripping any shell-timeout in the harness.
|
|
146
146
|
|
|
147
|
-
When the review diff is unchanged since the last
|
|
147
|
+
When the review diff is unchanged since the last verification submission, `asrt verify` reuses that session instead of submitting again — including while a prior run is still `running` (poll `asrt verify-status` instead). After a **succeeded** run, reuse skips a redundant submission. Use `asrt verify --force` to submit anyway. After a new git branch, run `asrt session start` only when the verification base should change — check the scope line it prints, or pass `--from <ref>` if the default is wrong.
|
|
148
148
|
|
|
149
149
|
```
|
|
150
150
|
asrt verify # submit; prints session id + URL, exits 0
|
|
@@ -9,6 +9,7 @@ from typer.testing import CliRunner
|
|
|
9
9
|
import main
|
|
10
10
|
from models import MetadataPayload, SessionStatus, StatusResponse, VerifyResponse
|
|
11
11
|
from session import (
|
|
12
|
+
SessionContext,
|
|
12
13
|
compute_review_diff_hash,
|
|
13
14
|
read_review_diff_hash,
|
|
14
15
|
read_verify_last_status,
|
|
@@ -33,11 +34,23 @@ def _write_session(assertion_dir: Path) -> None:
|
|
|
33
34
|
)
|
|
34
35
|
|
|
35
36
|
|
|
37
|
+
def _session_ctx(assertion_dir: Path, repo_root: Path) -> SessionContext:
|
|
38
|
+
return SessionContext(
|
|
39
|
+
repo_root=repo_root,
|
|
40
|
+
assertion_dir=assertion_dir,
|
|
41
|
+
prompts_path=assertion_dir / "prompts",
|
|
42
|
+
checkpoint_path=assertion_dir / "checkpoint",
|
|
43
|
+
metadata_path=assertion_dir / "metadata.json",
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
36
47
|
@patch("main.AssertionClient")
|
|
37
48
|
@patch("main._build_verify_bundle")
|
|
49
|
+
@patch("main._compute_review_diff_fingerprint")
|
|
38
50
|
@patch("main.load_existing_session")
|
|
39
51
|
def test_verify_reuses_unchanged_successful_diff(
|
|
40
52
|
mock_load_session,
|
|
53
|
+
mock_fingerprint,
|
|
41
54
|
mock_build_bundle,
|
|
42
55
|
MockClient,
|
|
43
56
|
tmp_path: Path,
|
|
@@ -54,17 +67,9 @@ def test_verify_reuses_unchanged_successful_diff(
|
|
|
54
67
|
"https://app.tryassertion.com/sessions/sess-1\n"
|
|
55
68
|
)
|
|
56
69
|
|
|
57
|
-
from session import SessionContext
|
|
58
|
-
|
|
59
|
-
ctx = SessionContext(
|
|
60
|
-
repo_root=tmp_path,
|
|
61
|
-
assertion_dir=assertion_dir,
|
|
62
|
-
prompts_path=assertion_dir / "prompts",
|
|
63
|
-
checkpoint_path=assertion_dir / "checkpoint",
|
|
64
|
-
metadata_path=assertion_dir / "metadata.json",
|
|
65
|
-
)
|
|
66
70
|
metadata = MetadataPayload(session_id="sess-1", stack_id="stack-1")
|
|
67
|
-
mock_load_session.return_value = (
|
|
71
|
+
mock_load_session.return_value = (_session_ctx(assertion_dir, tmp_path), metadata)
|
|
72
|
+
mock_fingerprint.return_value = (review_diff, digest)
|
|
68
73
|
mock_build_bundle.return_value = (b"bundle", review_diff, digest)
|
|
69
74
|
|
|
70
75
|
monkeypatch.chdir(tmp_path)
|
|
@@ -73,13 +78,53 @@ def test_verify_reuses_unchanged_successful_diff(
|
|
|
73
78
|
assert result.exit_code == 0
|
|
74
79
|
assert "reusing session sess-1" in result.stdout
|
|
75
80
|
MockClient.return_value.verify.assert_not_called()
|
|
81
|
+
mock_build_bundle.assert_not_called()
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@patch("main.AssertionClient")
|
|
85
|
+
@patch("main._build_verify_bundle")
|
|
86
|
+
@patch("main._compute_review_diff_fingerprint")
|
|
87
|
+
@patch("main.load_existing_session")
|
|
88
|
+
def test_verify_reuses_in_flight_running_diff(
|
|
89
|
+
mock_load_session,
|
|
90
|
+
mock_fingerprint,
|
|
91
|
+
mock_build_bundle,
|
|
92
|
+
MockClient,
|
|
93
|
+
tmp_path: Path,
|
|
94
|
+
monkeypatch,
|
|
95
|
+
) -> None:
|
|
96
|
+
assertion_dir = tmp_path / ".assertion"
|
|
97
|
+
_write_session(assertion_dir)
|
|
98
|
+
review_diff = "diff unchanged\n"
|
|
99
|
+
digest = compute_review_diff_hash(review_diff)
|
|
100
|
+
save_review_diff_hash(assertion_dir, digest)
|
|
101
|
+
save_verify_last_status(assertion_dir, SessionStatus.RUNNING.value)
|
|
102
|
+
(assertion_dir / "verify_session_id").write_text("sess-1\n")
|
|
103
|
+
(assertion_dir / "link").write_text(
|
|
104
|
+
"https://app.tryassertion.com/sessions/sess-1\n"
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
metadata = MetadataPayload(session_id="sess-1", stack_id="stack-1")
|
|
108
|
+
mock_load_session.return_value = (_session_ctx(assertion_dir, tmp_path), metadata)
|
|
109
|
+
mock_fingerprint.return_value = (review_diff, digest)
|
|
110
|
+
|
|
111
|
+
monkeypatch.chdir(tmp_path)
|
|
112
|
+
result = runner.invoke(main.app, ["verify"])
|
|
113
|
+
|
|
114
|
+
assert result.exit_code == 0
|
|
115
|
+
assert "session sess-1 is still running" in result.stdout
|
|
116
|
+
assert "Poll `asrt verify-status`" in result.stdout
|
|
117
|
+
MockClient.return_value.verify.assert_not_called()
|
|
118
|
+
mock_build_bundle.assert_not_called()
|
|
76
119
|
|
|
77
120
|
|
|
78
121
|
@patch("main.AssertionClient")
|
|
79
122
|
@patch("main._build_verify_bundle")
|
|
123
|
+
@patch("main._compute_review_diff_fingerprint")
|
|
80
124
|
@patch("main.load_existing_session")
|
|
81
125
|
def test_verify_submits_when_diff_changed(
|
|
82
126
|
mock_load_session,
|
|
127
|
+
mock_fingerprint,
|
|
83
128
|
mock_build_bundle,
|
|
84
129
|
MockClient,
|
|
85
130
|
tmp_path: Path,
|
|
@@ -91,23 +136,12 @@ def test_verify_submits_when_diff_changed(
|
|
|
91
136
|
save_review_diff_hash(assertion_dir, old_digest)
|
|
92
137
|
save_verify_last_status(assertion_dir, SessionStatus.SUCCEEDED.value)
|
|
93
138
|
|
|
94
|
-
from session import SessionContext
|
|
95
|
-
|
|
96
|
-
ctx = SessionContext(
|
|
97
|
-
repo_root=tmp_path,
|
|
98
|
-
assertion_dir=assertion_dir,
|
|
99
|
-
prompts_path=assertion_dir / "prompts",
|
|
100
|
-
checkpoint_path=assertion_dir / "checkpoint",
|
|
101
|
-
metadata_path=assertion_dir / "metadata.json",
|
|
102
|
-
)
|
|
103
139
|
metadata = MetadataPayload(session_id="sess-1", stack_id="stack-1")
|
|
104
|
-
mock_load_session.return_value = (
|
|
140
|
+
mock_load_session.return_value = (_session_ctx(assertion_dir, tmp_path), metadata)
|
|
105
141
|
new_diff = "new diff\n"
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
compute_review_diff_hash(new_diff),
|
|
110
|
-
)
|
|
142
|
+
new_digest = compute_review_diff_hash(new_diff)
|
|
143
|
+
mock_fingerprint.return_value = (new_diff, new_digest)
|
|
144
|
+
mock_build_bundle.return_value = (b"bundle", new_diff, new_digest)
|
|
111
145
|
MockClient.return_value.verify.return_value = VerifyResponse(
|
|
112
146
|
session_id="sess-1",
|
|
113
147
|
url="https://app.tryassertion.com/sessions/sess-1",
|
|
@@ -119,15 +153,17 @@ def test_verify_submits_when_diff_changed(
|
|
|
119
153
|
assert result.exit_code == 0
|
|
120
154
|
assert "Verification submitted" in result.stdout
|
|
121
155
|
MockClient.return_value.verify.assert_called_once()
|
|
122
|
-
assert read_review_diff_hash(assertion_dir) ==
|
|
156
|
+
assert read_review_diff_hash(assertion_dir) == new_digest
|
|
123
157
|
assert read_verify_last_status(assertion_dir) == SessionStatus.RUNNING.value
|
|
124
158
|
|
|
125
159
|
|
|
126
160
|
@patch("main.AssertionClient")
|
|
127
161
|
@patch("main._build_verify_bundle")
|
|
162
|
+
@patch("main._compute_review_diff_fingerprint")
|
|
128
163
|
@patch("main.load_existing_session")
|
|
129
164
|
def test_verify_does_not_reuse_after_failure(
|
|
130
165
|
mock_load_session,
|
|
166
|
+
mock_fingerprint,
|
|
131
167
|
mock_build_bundle,
|
|
132
168
|
MockClient,
|
|
133
169
|
tmp_path: Path,
|
|
@@ -140,17 +176,9 @@ def test_verify_does_not_reuse_after_failure(
|
|
|
140
176
|
save_review_diff_hash(assertion_dir, digest)
|
|
141
177
|
save_verify_last_status(assertion_dir, SessionStatus.FAILED.value)
|
|
142
178
|
|
|
143
|
-
from session import SessionContext
|
|
144
|
-
|
|
145
|
-
ctx = SessionContext(
|
|
146
|
-
repo_root=tmp_path,
|
|
147
|
-
assertion_dir=assertion_dir,
|
|
148
|
-
prompts_path=assertion_dir / "prompts",
|
|
149
|
-
checkpoint_path=assertion_dir / "checkpoint",
|
|
150
|
-
metadata_path=assertion_dir / "metadata.json",
|
|
151
|
-
)
|
|
152
179
|
metadata = MetadataPayload(session_id="sess-1", stack_id="stack-1")
|
|
153
|
-
mock_load_session.return_value = (
|
|
180
|
+
mock_load_session.return_value = (_session_ctx(assertion_dir, tmp_path), metadata)
|
|
181
|
+
mock_fingerprint.return_value = (review_diff, digest)
|
|
154
182
|
mock_build_bundle.return_value = (b"bundle", review_diff, digest)
|
|
155
183
|
MockClient.return_value.verify.return_value = VerifyResponse(
|
|
156
184
|
session_id="sess-1",
|
|
@@ -166,9 +194,11 @@ def test_verify_does_not_reuse_after_failure(
|
|
|
166
194
|
|
|
167
195
|
@patch("main.AssertionClient")
|
|
168
196
|
@patch("main._build_verify_bundle")
|
|
197
|
+
@patch("main._compute_review_diff_fingerprint")
|
|
169
198
|
@patch("main.load_existing_session")
|
|
170
199
|
def test_verify_force_bypasses_reuse(
|
|
171
200
|
mock_load_session,
|
|
201
|
+
mock_fingerprint,
|
|
172
202
|
mock_build_bundle,
|
|
173
203
|
MockClient,
|
|
174
204
|
tmp_path: Path,
|
|
@@ -185,17 +215,9 @@ def test_verify_force_bypasses_reuse(
|
|
|
185
215
|
"https://app.tryassertion.com/sessions/sess-1\n"
|
|
186
216
|
)
|
|
187
217
|
|
|
188
|
-
from session import SessionContext
|
|
189
|
-
|
|
190
|
-
ctx = SessionContext(
|
|
191
|
-
repo_root=tmp_path,
|
|
192
|
-
assertion_dir=assertion_dir,
|
|
193
|
-
prompts_path=assertion_dir / "prompts",
|
|
194
|
-
checkpoint_path=assertion_dir / "checkpoint",
|
|
195
|
-
metadata_path=assertion_dir / "metadata.json",
|
|
196
|
-
)
|
|
197
218
|
metadata = MetadataPayload(session_id="sess-1", stack_id="stack-1")
|
|
198
|
-
mock_load_session.return_value = (
|
|
219
|
+
mock_load_session.return_value = (_session_ctx(assertion_dir, tmp_path), metadata)
|
|
220
|
+
mock_fingerprint.return_value = (review_diff, digest)
|
|
199
221
|
mock_build_bundle.return_value = (b"bundle", review_diff, digest)
|
|
200
222
|
MockClient.return_value.verify.return_value = VerifyResponse(
|
|
201
223
|
session_id="sess-1",
|
|
@@ -222,17 +244,8 @@ def test_verify_status_persists_terminal_status(
|
|
|
222
244
|
assertion_dir = tmp_path / ".assertion"
|
|
223
245
|
_write_session(assertion_dir)
|
|
224
246
|
|
|
225
|
-
from session import SessionContext
|
|
226
|
-
|
|
227
|
-
ctx = SessionContext(
|
|
228
|
-
repo_root=tmp_path,
|
|
229
|
-
assertion_dir=assertion_dir,
|
|
230
|
-
prompts_path=assertion_dir / "prompts",
|
|
231
|
-
checkpoint_path=assertion_dir / "checkpoint",
|
|
232
|
-
metadata_path=assertion_dir / "metadata.json",
|
|
233
|
-
)
|
|
234
247
|
metadata = MetadataPayload(session_id="sess-1", stack_id="stack-1")
|
|
235
|
-
mock_load_session.return_value = (
|
|
248
|
+
mock_load_session.return_value = (_session_ctx(assertion_dir, tmp_path), metadata)
|
|
236
249
|
mock_load_session_id.return_value = "sess-1"
|
|
237
250
|
MockClient.return_value.status.return_value = StatusResponse(
|
|
238
251
|
status=SessionStatus.SUCCEEDED,
|
|
@@ -249,9 +262,11 @@ def test_verify_status_persists_terminal_status(
|
|
|
249
262
|
|
|
250
263
|
@patch("main.AssertionClient")
|
|
251
264
|
@patch("main._build_verify_bundle")
|
|
265
|
+
@patch("main._compute_review_diff_fingerprint")
|
|
252
266
|
@patch("main.load_existing_session")
|
|
253
267
|
def test_verify_reuse_json_output(
|
|
254
268
|
mock_load_session,
|
|
269
|
+
mock_fingerprint,
|
|
255
270
|
mock_build_bundle,
|
|
256
271
|
MockClient,
|
|
257
272
|
tmp_path: Path,
|
|
@@ -268,17 +283,9 @@ def test_verify_reuse_json_output(
|
|
|
268
283
|
"https://app.tryassertion.com/sessions/sess-1\n"
|
|
269
284
|
)
|
|
270
285
|
|
|
271
|
-
from session import SessionContext
|
|
272
|
-
|
|
273
|
-
ctx = SessionContext(
|
|
274
|
-
repo_root=tmp_path,
|
|
275
|
-
assertion_dir=assertion_dir,
|
|
276
|
-
prompts_path=assertion_dir / "prompts",
|
|
277
|
-
checkpoint_path=assertion_dir / "checkpoint",
|
|
278
|
-
metadata_path=assertion_dir / "metadata.json",
|
|
279
|
-
)
|
|
280
286
|
metadata = MetadataPayload(session_id="sess-1", stack_id="stack-1")
|
|
281
|
-
mock_load_session.return_value = (
|
|
287
|
+
mock_load_session.return_value = (_session_ctx(assertion_dir, tmp_path), metadata)
|
|
288
|
+
mock_fingerprint.return_value = (review_diff, digest)
|
|
282
289
|
mock_build_bundle.return_value = (b"bundle", review_diff, digest)
|
|
283
290
|
|
|
284
291
|
monkeypatch.chdir(tmp_path)
|
|
@@ -289,13 +296,16 @@ def test_verify_reuse_json_output(
|
|
|
289
296
|
assert payload["reused"] is True
|
|
290
297
|
assert payload["status"] == "succeeded"
|
|
291
298
|
MockClient.return_value.verify.assert_not_called()
|
|
299
|
+
mock_build_bundle.assert_not_called()
|
|
292
300
|
|
|
293
301
|
|
|
294
302
|
@patch("main.AssertionClient")
|
|
295
303
|
@patch("main._build_verify_bundle")
|
|
304
|
+
@patch("main._compute_review_diff_fingerprint")
|
|
296
305
|
@patch("main.load_existing_session")
|
|
297
306
|
def test_verify_missing_session_id_falls_through_to_submit(
|
|
298
307
|
mock_load_session,
|
|
308
|
+
mock_fingerprint,
|
|
299
309
|
mock_build_bundle,
|
|
300
310
|
MockClient,
|
|
301
311
|
tmp_path: Path,
|
|
@@ -308,17 +318,9 @@ def test_verify_missing_session_id_falls_through_to_submit(
|
|
|
308
318
|
save_review_diff_hash(assertion_dir, digest)
|
|
309
319
|
save_verify_last_status(assertion_dir, SessionStatus.SUCCEEDED.value)
|
|
310
320
|
|
|
311
|
-
from session import SessionContext
|
|
312
|
-
|
|
313
|
-
ctx = SessionContext(
|
|
314
|
-
repo_root=tmp_path,
|
|
315
|
-
assertion_dir=assertion_dir,
|
|
316
|
-
prompts_path=assertion_dir / "prompts",
|
|
317
|
-
checkpoint_path=assertion_dir / "checkpoint",
|
|
318
|
-
metadata_path=assertion_dir / "metadata.json",
|
|
319
|
-
)
|
|
320
321
|
metadata = MetadataPayload(session_id="sess-1", stack_id="stack-1")
|
|
321
|
-
mock_load_session.return_value = (
|
|
322
|
+
mock_load_session.return_value = (_session_ctx(assertion_dir, tmp_path), metadata)
|
|
323
|
+
mock_fingerprint.return_value = (review_diff, digest)
|
|
322
324
|
mock_build_bundle.return_value = (b"bundle", review_diff, digest)
|
|
323
325
|
MockClient.return_value.verify.return_value = VerifyResponse(
|
|
324
326
|
session_id="sess-1",
|
|
@@ -137,6 +137,51 @@ def test_verify_end_to_end_git_flow_submits_bundle(MockClient, tmp_path: Path, m
|
|
|
137
137
|
assert read_verify_last_status(tmp_path / ".assertion") == SessionStatus.RUNNING.value
|
|
138
138
|
|
|
139
139
|
|
|
140
|
+
@patch("main.AssertionClient")
|
|
141
|
+
def test_verify_double_submit_avoids_second_api_call(
|
|
142
|
+
MockClient, tmp_path: Path, monkeypatch
|
|
143
|
+
) -> None:
|
|
144
|
+
"""Repeated verify with unchanged diff must not submit twice while in-flight."""
|
|
145
|
+
_setup_branching_repo(tmp_path)
|
|
146
|
+
_write_assertion_session(tmp_path)
|
|
147
|
+
MockClient.return_value.verify.return_value = VerifyResponse(
|
|
148
|
+
session_id="sess-int",
|
|
149
|
+
url="https://app.tryassertion.com/sessions/sess-int",
|
|
150
|
+
)
|
|
151
|
+
monkeypatch.chdir(tmp_path)
|
|
152
|
+
|
|
153
|
+
first = runner.invoke(main.app, ["verify"])
|
|
154
|
+
assert first.exit_code == 0, first.stdout + first.stderr
|
|
155
|
+
assert "Verification submitted" in first.stdout
|
|
156
|
+
|
|
157
|
+
second = runner.invoke(main.app, ["verify"])
|
|
158
|
+
assert second.exit_code == 0, second.stdout + second.stderr
|
|
159
|
+
assert "Poll `asrt verify-status`" in second.stdout
|
|
160
|
+
assert MockClient.return_value.verify.call_count == 1
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
@patch("main.AssertionClient")
|
|
164
|
+
def test_verify_review_diff_hash_stable_across_invocations(
|
|
165
|
+
MockClient, tmp_path: Path, monkeypatch
|
|
166
|
+
) -> None:
|
|
167
|
+
_setup_branching_repo(tmp_path)
|
|
168
|
+
_write_assertion_session(tmp_path)
|
|
169
|
+
MockClient.return_value.verify.return_value = VerifyResponse(
|
|
170
|
+
session_id="sess-int",
|
|
171
|
+
url="https://app.tryassertion.com/sessions/sess-int",
|
|
172
|
+
)
|
|
173
|
+
monkeypatch.chdir(tmp_path)
|
|
174
|
+
|
|
175
|
+
runner.invoke(main.app, ["verify"])
|
|
176
|
+
hash_after_first = read_review_diff_hash(tmp_path / ".assertion")
|
|
177
|
+
|
|
178
|
+
runner.invoke(main.app, ["verify"])
|
|
179
|
+
hash_after_second = read_review_diff_hash(tmp_path / ".assertion")
|
|
180
|
+
|
|
181
|
+
assert hash_after_first is not None
|
|
182
|
+
assert hash_after_first == hash_after_second
|
|
183
|
+
|
|
184
|
+
|
|
140
185
|
@patch("main.AssertionClient")
|
|
141
186
|
def test_verify_reuse_after_status_succeeded(MockClient, tmp_path: Path, monkeypatch) -> None:
|
|
142
187
|
_setup_branching_repo(tmp_path)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|