Build latest book artifacts / build (push) Canceled after 0s
dependency resolution / resolve (3.11) (push) Canceled after 0s
dependency resolution / resolve (3.13) (push) Canceled after 0s
deploy-pages / build (push) Canceled after 0s
deploy-pages / deploy (push) Canceled after 0s
i18n consistency check / check (push) Canceled after 0s
provider adoption tests / test (chapter2/context-compression) (push) Canceled after 0s
provider adoption tests / test (chapter2/prompt-injection) (push) Canceled after 0s
provider adoption tests / test (chapter2/system-hint) (push) Canceled after 0s
provider adoption tests / test (chapter3/log-sanitization) (push) Canceled after 0s
web-search-agent tests / test (push) Canceled after 0s
web-search-agent tests / agentbook (push) Canceled after 0s
139 lines
6.4 KiB
Python
139 lines
6.4 KiB
Python
"""Offline contract tests; these are not substitutes for the live campaign receipt."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import importlib.util
|
|
import json
|
|
import sys
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
|
|
import httpx
|
|
import pytest
|
|
|
|
HERE = Path(__file__).resolve().parent
|
|
MODULE_PATH = HERE / "unipile_mailbox_experiment.py"
|
|
SPEC = importlib.util.spec_from_file_location("experiment_6_1_unipile", MODULE_PATH)
|
|
experiment = importlib.util.module_from_spec(SPEC)
|
|
assert SPEC.loader is not None
|
|
sys.modules[SPEC.name] = experiment
|
|
SPEC.loader.exec_module(experiment)
|
|
|
|
|
|
def _email(kind: str, date: str, suffix: str) -> dict:
|
|
base = {"id": f"email-{suffix}", "account_id": "account-real",
|
|
"date": date, "role": "inbox", "folders": ["Inbox"]}
|
|
if kind == "meeting_invitation":
|
|
return {**base, "subject": "Meeting invitation: design review",
|
|
"body_plain": "START_UTC: 2026-08-03T10:00:00.000Z\n"
|
|
"END_UTC: 2026-08-03T11:00:00.000Z"}
|
|
if kind == "customer_complaint":
|
|
return {**base, "subject": "Customer complaint: delayed order",
|
|
"body_plain": "Customer complaint for order #E44-TEST. Please escalate."}
|
|
return {**base, "subject": "Marketing newsletter",
|
|
"body_plain": "Marketing newsletter promotion. Click to unsubscribe."}
|
|
|
|
|
|
def _provider(request: httpx.Request) -> httpx.Response:
|
|
assert request.headers.get("X-API-KEY") == "unit-secret"
|
|
if request.method == "GET" and request.url.path == "/api/v1/calendars":
|
|
return httpx.Response(200, json={"data": [{"id": "calendar-real",
|
|
"is_primary": True}]})
|
|
if request.method == "GET" and request.url.path.endswith("/events"):
|
|
return httpx.Response(200, json={"data": []})
|
|
if request.method == "GET" and request.url.path == "/api/v1/folders":
|
|
return httpx.Response(200, json={"items": [{"id": "folder-archive",
|
|
"name": "Archive",
|
|
"role": "archive"}]})
|
|
if request.method == "PUT" and request.url.path == "/api/v1/emails/email-marketing":
|
|
assert json.loads(request.content) == {"folders": ["archive"]}
|
|
return httpx.Response(200, json={"object": "EmailUpdated"})
|
|
if request.method == "GET" and request.url.path == "/api/v1/emails/email-marketing":
|
|
return httpx.Response(200, json={"id": "email-marketing",
|
|
"role": "archive", "folders": ["Archive"]})
|
|
return httpx.Response(404, json={"type": "unexpected_test_request"})
|
|
|
|
|
|
def test_classification_is_unique_and_meeting_interval_is_exact():
|
|
email = _email("meeting_invitation", "2026-08-01T00:00:01.000Z", "meeting")
|
|
assert experiment.classify_email(email) == "meeting_invitation"
|
|
start, end = experiment.meeting_interval(email)
|
|
assert start.isoformat() == "2026-08-03T10:00:00+00:00"
|
|
assert (end - start).total_seconds() == 3600
|
|
ambiguous = {**email, "subject": "Meeting invitation and marketing newsletter",
|
|
"body_plain": email["body_plain"] + "\nMarketing unsubscribe"}
|
|
with pytest.raises(ValueError, match="not unique"):
|
|
experiment.classify_email(ambiguous)
|
|
|
|
|
|
def test_real_api_error_is_receipted_and_raises():
|
|
def unauthorized(_: httpx.Request) -> httpx.Response:
|
|
return httpx.Response(401, json={"status": 401,
|
|
"type": "errors/missing_credentials",
|
|
"title": "Missing credentials"})
|
|
|
|
client = experiment.UnipileClient(
|
|
"api.example.invalid:12345", "unit-secret",
|
|
transport=httpx.MockTransport(unauthorized),
|
|
)
|
|
with pytest.raises(experiment.UnipileAPIError):
|
|
client.list_accounts()
|
|
assert client.calls == [{
|
|
**client.calls[0], "status": 401, "success": False,
|
|
"credential_scheme": "X-API-KEY",
|
|
"error_type": "errors/missing_credentials",
|
|
}]
|
|
assert "unit-secret" not in experiment.canonical_json(client.calls)
|
|
client.close()
|
|
|
|
def test_three_email_workflow_and_acceptance(tmp_path):
|
|
client = experiment.UnipileClient(
|
|
"api.example.invalid:12345", "unit-secret",
|
|
transport=httpx.MockTransport(_provider),
|
|
)
|
|
runner = experiment.MailboxExperiment(
|
|
client, tmp_path, "account-real", "account-real"
|
|
)
|
|
# Deliberately unordered input proves that the FIFO queue uses provider time.
|
|
emails = [
|
|
_email("marketing", "2026-08-01T00:00:03.000Z", "marketing"),
|
|
_email("meeting_invitation", "2026-08-01T00:00:01.000Z", "meeting"),
|
|
_email("customer_complaint", "2026-08-01T00:00:02.000Z", "complaint"),
|
|
]
|
|
runner.process_queue(emails)
|
|
assert [row["classification"] for row in runner.workflows] == [
|
|
"meeting_invitation", "customer_complaint", "marketing"
|
|
]
|
|
result = experiment.derive_acceptance(
|
|
runner.events, runner.workflows, client.calls,
|
|
[{"object": "EmailSent"}] * 3,
|
|
credential_secret="unit-secret", dsn_secret="api.example.invalid:12345",
|
|
)
|
|
assert result["status"] == "passed"
|
|
assert all(result["gates"].values())
|
|
assert (tmp_path / "artifacts" / "meeting_reply_draft.txt").stat().st_size > 100
|
|
assert (tmp_path / "artifacts" / "high_priority_notifications.jsonl").is_file()
|
|
assert "unit-secret" not in experiment.canonical_json(client.calls)
|
|
client.close()
|
|
|
|
|
|
def test_acceptance_fails_when_provider_archive_verification_is_missing(tmp_path):
|
|
client = experiment.UnipileClient(
|
|
"api.example.invalid:12345", "unit-secret",
|
|
transport=httpx.MockTransport(_provider),
|
|
)
|
|
runner = experiment.MailboxExperiment(client, tmp_path, "account-real", "account-real")
|
|
runner.process_queue([
|
|
_email("meeting_invitation", "2026-08-01T00:00:01.000Z", "meeting"),
|
|
_email("customer_complaint", "2026-08-01T00:00:02.000Z", "complaint"),
|
|
_email("marketing", "2026-08-01T00:00:03.000Z", "marketing"),
|
|
])
|
|
runner.workflows[-1]["archive"]["verified"] = False
|
|
result = experiment.derive_acceptance(
|
|
runner.events, runner.workflows, client.calls, [{}, {}, {}],
|
|
credential_secret="unit-secret", dsn_secret="api.example.invalid:12345",
|
|
)
|
|
assert result["status"] == "failed"
|
|
assert not result["gates"]["marketing_archived_and_verified_through_provider"]
|
|
client.close()
|