mirror of
https://github.com/Monadical-SAS/reflector.git
synced 2026-04-10 15:46:55 +00:00
feat: allow participants to ask for email transcript (#923)
* feat: allow participants to ask for email transcript * fix: set email update in a transaction
This commit is contained in:
committed by
GitHub
parent
41e7b3e84f
commit
55222ecc47
@@ -40,6 +40,11 @@ x-backend-env: &backend-env
|
||||
# Garage S3 credentials — hardcoded test keys, containers are ephemeral
|
||||
TRANSCRIPT_STORAGE_AWS_ACCESS_KEY_ID: GK0123456789abcdef01234567 # gitleaks:allow
|
||||
TRANSCRIPT_STORAGE_AWS_SECRET_ACCESS_KEY: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" # gitleaks:allow
|
||||
# Email / SMTP — Mailpit captures emails without sending
|
||||
SMTP_HOST: mailpit
|
||||
SMTP_PORT: "1025"
|
||||
SMTP_FROM_EMAIL: test@reflector.local
|
||||
SMTP_USE_TLS: "false"
|
||||
# NOTE: DAILYCO_STORAGE_AWS_* intentionally NOT set — forces fallback to
|
||||
# get_transcripts_storage() which has ENDPOINT_URL pointing at Garage.
|
||||
# Setting them would bypass the endpoint and generate presigned URLs for AWS.
|
||||
@@ -101,6 +106,14 @@ services:
|
||||
retries: 10
|
||||
start_period: 5s
|
||||
|
||||
mailpit:
|
||||
image: axllent/mailpit:latest
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-q", "--spider", "http://localhost:8025/api/v1/messages"]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 5
|
||||
|
||||
mock-daily:
|
||||
build:
|
||||
context: .
|
||||
@@ -131,6 +144,8 @@ services:
|
||||
condition: service_healthy
|
||||
mock-daily:
|
||||
condition: service_healthy
|
||||
mailpit:
|
||||
condition: service_healthy
|
||||
volumes:
|
||||
- server_data:/app/data
|
||||
|
||||
@@ -194,6 +209,7 @@ services:
|
||||
DATABASE_URL: postgresql+asyncpg://reflector:reflector@postgres:5432/reflector
|
||||
SERVER_URL: http://server:1250
|
||||
GARAGE_ENDPOINT: http://garage:3900
|
||||
MAILPIT_URL: http://mailpit:8025
|
||||
depends_on:
|
||||
server:
|
||||
condition: service_started
|
||||
|
||||
@@ -17,6 +17,7 @@ from sqlalchemy.ext.asyncio import create_async_engine
|
||||
|
||||
SERVER_URL = os.environ.get("SERVER_URL", "http://server:1250")
|
||||
GARAGE_ENDPOINT = os.environ.get("GARAGE_ENDPOINT", "http://garage:3900")
|
||||
MAILPIT_URL = os.environ.get("MAILPIT_URL", "http://mailpit:8025")
|
||||
DATABASE_URL = os.environ.get(
|
||||
"DATABASE_URL_ASYNC",
|
||||
os.environ.get(
|
||||
@@ -114,3 +115,44 @@ async def _poll_transcript_status(
|
||||
def poll_transcript_status():
|
||||
"""Returns the poll_transcript_status async helper function."""
|
||||
return _poll_transcript_status
|
||||
|
||||
|
||||
@pytest_asyncio.fixture
|
||||
async def mailpit_client():
|
||||
"""HTTP client for Mailpit API — query captured emails."""
|
||||
async with httpx.AsyncClient(
|
||||
base_url=MAILPIT_URL,
|
||||
timeout=httpx.Timeout(10.0),
|
||||
) as client:
|
||||
# Clear inbox before each test
|
||||
await client.delete("/api/v1/messages")
|
||||
yield client
|
||||
|
||||
|
||||
async def _poll_mailpit_messages(
|
||||
mailpit: httpx.AsyncClient,
|
||||
to_email: str,
|
||||
max_wait: int = 30,
|
||||
interval: int = 2,
|
||||
) -> list[dict]:
|
||||
"""
|
||||
Poll Mailpit API until at least one message is delivered to the given address.
|
||||
Returns the list of matching messages.
|
||||
"""
|
||||
elapsed = 0
|
||||
while elapsed < max_wait:
|
||||
resp = await mailpit.get("/api/v1/messages", params={"query": f"to:{to_email}"})
|
||||
resp.raise_for_status()
|
||||
data = resp.json()
|
||||
messages = data.get("messages", [])
|
||||
if messages:
|
||||
return messages
|
||||
await asyncio.sleep(interval)
|
||||
elapsed += interval
|
||||
raise TimeoutError(f"No email delivered to {to_email} within {max_wait}s")
|
||||
|
||||
|
||||
@pytest_asyncio.fixture
|
||||
def poll_mailpit_messages():
|
||||
"""Returns the poll_mailpit_messages async helper function."""
|
||||
return _poll_mailpit_messages
|
||||
|
||||
@@ -4,10 +4,12 @@ Integration test: Multitrack → DailyMultitrackPipeline → full processing.
|
||||
Exercises: S3 upload → DB recording setup → process endpoint →
|
||||
Hatchet DiarizationPipeline → mock Daily API → whisper per-track transcription →
|
||||
diarization → mixdown → LLM summarization/topics → status "ended".
|
||||
Also tests email transcript notification via Mailpit SMTP sink.
|
||||
"""
|
||||
|
||||
import json
|
||||
from datetime import datetime, timezone
|
||||
import uuid
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
import pytest
|
||||
from sqlalchemy import text
|
||||
@@ -22,6 +24,9 @@ TRACK_KEYS = [
|
||||
]
|
||||
|
||||
|
||||
TEST_EMAIL = "integration-test@reflector.local"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_multitrack_pipeline_end_to_end(
|
||||
api_client,
|
||||
@@ -30,6 +35,8 @@ async def test_multitrack_pipeline_end_to_end(
|
||||
test_records_dir,
|
||||
bucket_name,
|
||||
poll_transcript_status,
|
||||
mailpit_client,
|
||||
poll_mailpit_messages,
|
||||
):
|
||||
"""Set up multitrack recording in S3/DB and verify the full pipeline completes."""
|
||||
# 1. Upload test audio as two separate tracks to Garage S3
|
||||
@@ -52,16 +59,41 @@ async def test_multitrack_pipeline_end_to_end(
|
||||
transcript = resp.json()
|
||||
transcript_id = transcript["id"]
|
||||
|
||||
# 3. Insert Recording row and link to transcript via direct DB access
|
||||
# 3. Insert Meeting, Recording, and link to transcript via direct DB access
|
||||
recording_id = f"rec-integration-{transcript_id[:8]}"
|
||||
meeting_id = str(uuid.uuid4())
|
||||
now = datetime.now(timezone.utc)
|
||||
|
||||
async with db_engine.begin() as conn:
|
||||
# Insert recording with track_keys
|
||||
# Insert meeting with email_recipients for email notification test
|
||||
await conn.execute(
|
||||
text("""
|
||||
INSERT INTO recording (id, bucket_name, object_key, recorded_at, status, track_keys)
|
||||
VALUES (:id, :bucket_name, :object_key, :recorded_at, :status, CAST(:track_keys AS json))
|
||||
INSERT INTO meeting (
|
||||
id, room_name, room_url, host_room_url,
|
||||
start_date, end_date, platform, email_recipients
|
||||
)
|
||||
VALUES (
|
||||
:id, :room_name, :room_url, :host_room_url,
|
||||
:start_date, :end_date, :platform, CAST(:email_recipients AS json)
|
||||
)
|
||||
"""),
|
||||
{
|
||||
"id": meeting_id,
|
||||
"room_name": "integration-test-room",
|
||||
"room_url": "https://test.daily.co/integration-test-room",
|
||||
"host_room_url": "https://test.daily.co/integration-test-room",
|
||||
"start_date": now,
|
||||
"end_date": now + timedelta(hours=1),
|
||||
"platform": "daily",
|
||||
"email_recipients": json.dumps([TEST_EMAIL]),
|
||||
},
|
||||
)
|
||||
|
||||
# Insert recording with track_keys, linked to meeting
|
||||
await conn.execute(
|
||||
text("""
|
||||
INSERT INTO recording (id, bucket_name, object_key, recorded_at, status, track_keys, meeting_id)
|
||||
VALUES (:id, :bucket_name, :object_key, :recorded_at, :status, CAST(:track_keys AS json), :meeting_id)
|
||||
"""),
|
||||
{
|
||||
"id": recording_id,
|
||||
@@ -70,6 +102,7 @@ async def test_multitrack_pipeline_end_to_end(
|
||||
"recorded_at": now,
|
||||
"status": "completed",
|
||||
"track_keys": json.dumps(TRACK_KEYS),
|
||||
"meeting_id": meeting_id,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -127,3 +160,22 @@ async def test_multitrack_pipeline_end_to_end(
|
||||
assert (
|
||||
len(participants) >= 2
|
||||
), f"Expected at least 2 speakers for multitrack, got {len(participants)}"
|
||||
|
||||
# 7. Verify email transcript notification
|
||||
# The send_email pipeline task should have:
|
||||
# a) Set the transcript to public share_mode
|
||||
# b) Sent an email to TEST_EMAIL via Mailpit
|
||||
transcript_resp = await api_client.get(f"/transcripts/{transcript_id}")
|
||||
transcript_resp.raise_for_status()
|
||||
transcript_data = transcript_resp.json()
|
||||
assert (
|
||||
transcript_data.get("share_mode") == "public"
|
||||
), "Transcript should be set to public when email recipients exist"
|
||||
|
||||
# Poll Mailpit for the delivered email (send_email task runs async after finalize)
|
||||
messages = await poll_mailpit_messages(mailpit_client, TEST_EMAIL, max_wait=30)
|
||||
assert len(messages) >= 1, "Should have received at least 1 email"
|
||||
email_msg = messages[0]
|
||||
assert (
|
||||
"Transcript Ready" in email_msg.get("Subject", "")
|
||||
), f"Email subject should contain 'Transcript Ready', got: {email_msg.get('Subject')}"
|
||||
|
||||
Reference in New Issue
Block a user