Implement P05 checked services and safe selected delivery recovery
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a092fe-13b1-7f12-ac74-7d258af4d79c
This commit is contained in:
parent
99618488e0
commit
ac0eb14b75
15 changed files with 735 additions and 88 deletions
|
|
@ -41,7 +41,20 @@ try{
|
|||
await identity('operator');await navigate('/platform');
|
||||
await check(`!!document.querySelector('a[href="/platform/operations"]')`,'P01 platform recovery navigation');
|
||||
await navigate('/platform/operations');
|
||||
await check(`document.body.innerText.includes("Live sign-in, email receipt and authenticator health are not verified here")`,'P05 unknown provider health remains explicit');
|
||||
await check(`document.body.innerText.includes("Component checks do not prove a complete sign-in or inbox receipt")`,'P05 unknown provider health remains explicit');
|
||||
await navigate('/platform/operations?event_id=p05-browser');
|
||||
await check(`document.body.innerText.includes("No mail submission is recorded")`,'P05 selected mail evidence');
|
||||
await evaluate(`document.querySelector('form[action="/platform/operations/deliver"] button').click()`);
|
||||
await waitFor('document.body.innerText.includes("Confirm change")');
|
||||
await check(`document.body.innerText.includes("p05-browser")`,'P05 selected delivery requires review');
|
||||
await evaluate(`document.querySelector('input[name="confirm_token"]').form.querySelector('button').click()`);
|
||||
await waitFor('document.body.innerText.includes("definitely failed")');
|
||||
await check(`!!document.querySelector('form[action="/platform/operations/deliver"]')`,'P05 failure offers controlled retry');
|
||||
await evaluate(`document.querySelector('form[action="/platform/operations/deliver"] button').click()`);
|
||||
await waitFor('document.body.innerText.includes("Confirm change")');
|
||||
await evaluate(`document.querySelector('input[name="confirm_token"]').form.querySelector('button').click()`);
|
||||
await waitFor('document.body.innerText.includes("mail provider accepted")');
|
||||
await check(`!document.querySelector('form[action="/platform/operations/deliver"]') && document.body.innerText.includes("does not prove inbox receipt")`,'P05 completed delivery cannot be blindly repeated');
|
||||
await navigate('/platform/activity');
|
||||
await check(`!!document.querySelector('input[name="reference"]') && !!document.querySelector('input[name="tenant"]')`,'P08 platform investigation filters');
|
||||
await evaluate(`document.querySelector('input[name="reference"]').value='synthetic-missing';document.querySelector('form[action="/platform/activity"] button').click()`);
|
||||
|
|
|
|||
|
|
@ -22,6 +22,16 @@ if not chrome:
|
|||
if not chrome or not shutil.which('node'):
|
||||
raise SystemExit('Chromium and Node are required; set JOURNEY_CHROME to the Chromium executable. Browser tests were not run.')
|
||||
fixture=FactorRecoveryJourney();fixture.setUp();fixture.member(email='actual.login@example.test')
|
||||
from user_engine.domain import OutboxEvent
|
||||
fixture.app.service.store.append_outbox(OutboxEvent(event_id="p05-browser", event_type="family_member.invited", aggregate_id="fixture", tenant="tenant:trial:demo-company", correlation_id="p05-browser", payload={"primary_email":"fixture@example.test", "invitation_id":"fixture"}))
|
||||
class Delivery:
|
||||
attempts = 0
|
||||
def __call__(self, event):
|
||||
self.attempts += 1
|
||||
if self.attempts == 1: raise RuntimeError("synthetic outage")
|
||||
def mail_delivery_status(self, event_id):
|
||||
return {"state":"failed" if self.attempts == 1 else "provider_accepted" if self.attempts else "not_found"}
|
||||
fixture.app.outbox_delivery = Delivery()
|
||||
class Quiet(WSGIRequestHandler):
|
||||
def log_message(self,*args):pass
|
||||
server=make_server('127.0.0.1',0,fixture.app,handler_class=Quiet)
|
||||
|
|
|
|||
123
scripts/p05_mail_acceptance.py
Normal file
123
scripts/p05_mail_acceptance.py
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
#!/usr/bin/env python3
|
||||
"""P05 portal -> HTTP -> SMTP/IMAP acceptance; loopback fixtures only.
|
||||
|
||||
Requires sibling email-connect source and its owned GreenMail harness on 3025/3143.
|
||||
No production configuration or recipients are loaded.
|
||||
"""
|
||||
|
||||
import imaplib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import tempfile
|
||||
from threading import Thread
|
||||
from wsgiref.simple_server import make_server, WSGIRequestHandler
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path[:0] = [
|
||||
str(ROOT / "src"),
|
||||
str(ROOT / "tests"),
|
||||
str(ROOT.parent / "email-connect/src"),
|
||||
]
|
||||
from email_connect.transactional import (
|
||||
SMTPProvider,
|
||||
SQLiteDeliveryStore,
|
||||
TransactionalApplication,
|
||||
)
|
||||
from user_engine.adapters.delivery import HTTPOutboxDeliveryAdapter
|
||||
from test_service_operations import ServiceOperations
|
||||
|
||||
|
||||
class Quiet(WSGIRequestHandler):
|
||||
def log_message(self, *args):
|
||||
pass
|
||||
|
||||
|
||||
with tempfile.TemporaryDirectory(prefix="p05-mail-") as directory:
|
||||
mail = TransactionalApplication(
|
||||
SQLiteDeliveryStore(directory + "/mail.db"),
|
||||
SMTPProvider(
|
||||
"127.0.0.1",
|
||||
3025,
|
||||
"fixture",
|
||||
"fixture",
|
||||
"noreply@harness.email-connect.test",
|
||||
security="plaintext",
|
||||
),
|
||||
"fixture-bearer",
|
||||
"https://users.example.test",
|
||||
)
|
||||
calls = []
|
||||
|
||||
def receiver(env, start):
|
||||
if env["PATH_INFO"] == "/audit":
|
||||
calls.append(True)
|
||||
start(
|
||||
"503 Service Unavailable" if len(calls) == 1 else "202 Accepted",
|
||||
[("Content-Type", "application/json")],
|
||||
)
|
||||
return [b"{}"]
|
||||
return mail(env, start)
|
||||
|
||||
server = make_server("127.0.0.1", 0, receiver, handler_class=Quiet)
|
||||
thread = Thread(target=server.serve_forever, daemon=True)
|
||||
thread.start()
|
||||
fixture = ServiceOperations()
|
||||
fixture.setUp()
|
||||
try:
|
||||
event = fixture.seed()
|
||||
address = event.payload["primary_email"]
|
||||
with imaplib.IMAP4("127.0.0.1", 3143) as inbox:
|
||||
inbox.login(address, "fixture")
|
||||
inbox.select("INBOX")
|
||||
before = len(inbox.search(None, "ALL")[1][0].split())
|
||||
base = "http://127.0.0.1:" + str(server.server_port)
|
||||
adapter = HTTPOutboxDeliveryAdapter(
|
||||
event_url=base + "/audit",
|
||||
event_bearer_token="fixture-bearer",
|
||||
mail_url=base + "/v1/send",
|
||||
mail_bearer_token="fixture-bearer",
|
||||
)
|
||||
fixture.app.outbox_delivery = adapter
|
||||
assert adapter.mail_status()["checks"]["smtp_authentication"] == "ok"
|
||||
fixture.post(
|
||||
"/platform/operations/deliver",
|
||||
who="operator",
|
||||
event_id=event.event_id,
|
||||
confirmed=True,
|
||||
)
|
||||
first = fixture.app.service.store.outbox_event(event.event_id)
|
||||
assert (
|
||||
first.failed_at and first.claimed_by is None and first.delivered_at is None
|
||||
)
|
||||
assert (
|
||||
adapter.mail_delivery_status(event.event_id)["state"] == "provider_accepted"
|
||||
)
|
||||
fixture.post(
|
||||
"/platform/operations/deliver",
|
||||
who="operator",
|
||||
event_id=event.event_id,
|
||||
confirmed=True,
|
||||
)
|
||||
assert fixture.app.service.store.outbox_event(event.event_id).delivered_at
|
||||
with imaplib.IMAP4("127.0.0.1", 3143) as inbox:
|
||||
inbox.login(address, "fixture")
|
||||
inbox.select("INBOX")
|
||||
after = len(inbox.search(None, "ALL")[1][0].split())
|
||||
assert after - before == 1 and len(calls) == 2
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
"success": True,
|
||||
"scope": "loopback SMTP/IMAP fixtures only",
|
||||
"mail_received": 1,
|
||||
"audit_failure_recovered": True,
|
||||
"retry_did_not_duplicate_mail": True,
|
||||
}
|
||||
)
|
||||
)
|
||||
finally:
|
||||
server.shutdown()
|
||||
server.server_close()
|
||||
thread.join(timeout=5)
|
||||
fixture.tearDown()
|
||||
Loading…
Add table
Add a link
Reference in a new issue