commit 2a748b0495ef84c4ceffed9e8508e08512b15a86
parent 172ead3b94babc7be63fb75c7a377a55f4ea7392
Author: triesap <tyson@radroots.org>
Date: Fri, 28 Aug 2026 21:59:12 +0000
test: preserve persona retry evidence
- Model the governed relay failure as a dropped transport response.
- Bind retrieval evidence to media uploaded by the active persona.
- Ignore only the unlabeled full-frame contrast audit container.
- Add focused fixture regressions and verify the simulator build.
Diffstat:
3 files changed, 98 insertions(+), 9 deletions(-)
diff --git a/RadrootsUITests/RadrootsRemoteQualificationUITests.swift b/RadrootsUITests/RadrootsRemoteQualificationUITests.swift
@@ -428,6 +428,14 @@ final class RadrootsRemoteQualificationUITests: XCTestCase {
}
if issue.auditType == .contrast,
let element = issue.element,
+ element.elementType == .other,
+ element.identifier.isEmpty,
+ element.label.isEmpty,
+ element.frame == app.frame {
+ return true
+ }
+ if issue.auditType == .contrast,
+ let element = issue.element,
element.exists,
!element.isEnabled
{
@@ -916,7 +924,10 @@ final class RadrootsRemoteQualificationUITests: XCTestCase {
throw QualificationError.missingProductSurface
}
let retry = relaunched.buttons["Retry"].firstMatch
- XCTAssertTrue(retry.waitForExistence(timeout: 20))
+ guard retry.waitForExistence(timeout: 20) else {
+ XCTFail("The transport-retry draft did not expose the Retry action")
+ throw QualificationError.missingProductSurface
+ }
retry.tap()
let retryCompleted = NSPredicate { _, _ in !retry.exists || !retry.isEnabled }
let expectation = XCTNSPredicateExpectation(predicate: retryCompleted, object: relaunched)
diff --git a/scripts/local-social-fixture.py b/scripts/local-social-fixture.py
@@ -244,6 +244,9 @@ class FixtureState:
self._identity_by_persona: dict[str, str] = {}
self._persona_by_identity: dict[str, str] = {}
self._accepted_uploads_by_persona = {alias: 0 for alias in PERSONA_ALIASES}
+ self._uploaded_digests_by_persona = {
+ alias: set() for alias in PERSONA_ALIASES
+ }
self._retrievals_by_persona = {alias: set() for alias in PERSONA_ALIASES}
self._unknown_attempts = 0
self._duplicate_attempts = 0
@@ -254,7 +257,7 @@ class FixtureState:
self._unintended_publications = 0
self._write_evidence()
- def publish(self, event: dict[str, Any]) -> bool:
+ def publish(self, event: dict[str, Any]) -> bool | None:
if not valid_nostr_event(event) or not verify_nostr_signature(event):
return False
if self._suite is not None:
@@ -267,7 +270,7 @@ class FixtureState:
self._write_evidence_locked()
return True
- def _publish_persona_event(self, event: dict[str, Any]) -> bool:
+ def _publish_persona_event(self, event: dict[str, Any]) -> bool | None:
attempt = classify_attempt(event, self._attempts)
control = read_control(self.control)
with self._lock:
@@ -306,7 +309,7 @@ class FixtureState:
self._transport_rejected_attempts.add(attempt_id)
self._expected_failure_rejections += 1
self._write_evidence_locked()
- return False
+ return None
if attempt_id in self._accepted_attempts:
self._duplicate_attempts += 1
self._write_evidence_locked()
@@ -375,6 +378,7 @@ class FixtureState:
self._accepted_uploads += 1
if persona is not None:
self._accepted_uploads_by_persona[persona] += 1
+ self._uploaded_digests_by_persona[persona].add(digest)
self._write_evidence_locked()
descriptor = {
"url": f"http://127.0.0.1:{self.blossom_port}/{digest}.png",
@@ -394,11 +398,12 @@ class FixtureState:
self._retrievals += 1
elif control is not None:
persona = control["active_persona"]
- before = len(self._retrievals_by_persona[persona])
- self._retrievals_by_persona[persona].add(digest)
- self._retrievals += (
- len(self._retrievals_by_persona[persona]) - before
- )
+ if digest in self._uploaded_digests_by_persona[persona]:
+ before = len(self._retrievals_by_persona[persona])
+ self._retrievals_by_persona[persona].add(digest)
+ self._retrievals += (
+ len(self._retrievals_by_persona[persona]) - before
+ )
self._write_evidence_locked()
return value
@@ -786,6 +791,8 @@ class RelayHandler(socketserver.BaseRequestHandler):
):
event_id = message[1].get("id", "")
accepted = self.state.publish(message[1])
+ if accepted is None:
+ return
send_json(
self.request,
["OK", event_id, accepted, "" if accepted else "invalid"],
diff --git a/scripts/test_local_social_fixture.py b/scripts/test_local_social_fixture.py
@@ -150,6 +150,77 @@ class LocalSocialFixtureTests(unittest.TestCase):
self.assertEqual(evidence["accepted_uploads"], 1)
self.assertEqual(evidence["retrievals"], 1)
+ def test_persona_retrieval_counts_only_its_own_uploaded_digest(self) -> None:
+ body = b"persona-photo"
+ digest = fixture.hashlib.sha256(body).hexdigest()
+ _, suite = fixture.load_persona_suite(
+ Path("test-fixtures/local-social-personas.v1.json")
+ )
+ with tempfile.TemporaryDirectory() as directory:
+ root = Path(directory)
+ control = root / "control.json"
+ state = fixture.FixtureState(
+ root / "evidence.json", control, 21100, suite
+ )
+ self.write_persona_control(control, "P01")
+ with mock.patch.object(
+ fixture, "valid_blossom_authorization", return_value=True
+ ):
+ accepted, _ = state.upload(body, "image/png", digest, "Nostr valid")
+ self.assertTrue(accepted)
+ self.assertIsNotNone(state.retrieve(digest))
+ self.write_persona_control(control, "P02")
+ self.assertIsNotNone(state.retrieve(digest))
+
+ evidence = json.loads(
+ (root / "evidence.json").read_text(encoding="utf-8")
+ )
+ self.assertEqual(evidence["retrievals"], 1)
+ self.assertEqual(evidence["personas"][0]["retrievals"], 1)
+ self.assertEqual(evidence["personas"][1]["retrievals"], 0)
+
+ def test_transport_retry_drops_one_response_then_accepts(self) -> None:
+ _, suite = fixture.load_persona_suite(
+ Path("test-fixtures/local-social-personas.v1.json")
+ )
+ attempt = fixture.persona_attempts(suite)["P05-A01"]
+ event = {
+ "id": "1" * 64,
+ "kind": fixture.FLOW_KINDS[attempt["flow"]],
+ "pubkey": "2" * 64,
+ }
+ with tempfile.TemporaryDirectory() as directory:
+ root = Path(directory)
+ control = root / "control.json"
+ self.write_persona_control(control, "P05")
+ state = fixture.FixtureState(
+ root / "evidence.json", control, 21100, suite
+ )
+ with mock.patch.object(
+ fixture, "classify_attempt", return_value=attempt
+ ):
+ self.assertIsNone(state._publish_persona_event(event))
+ self.assertTrue(state._publish_persona_event(event))
+
+ evidence = json.loads(
+ (root / "evidence.json").read_text(encoding="utf-8")
+ )
+ self.assertEqual(evidence["expected_failure_rejections"], 1)
+ self.assertEqual(evidence["accepted_events"], 1)
+
+ @staticmethod
+ def write_persona_control(path: Path, alias: str) -> None:
+ path.write_text(
+ json.dumps(
+ {
+ "schema": fixture.PERSONA_CONTROL_SCHEMA,
+ "active_persona": alias,
+ "blossom_enabled": True,
+ }
+ ),
+ encoding="utf-8",
+ )
+
def test_result_contract_accepts_future_ios_and_rejects_drift(self) -> None:
suite, result = self.persona_result()
result_schema = json.loads(