PSA/server/docs/EMAIL_TEST_PATTERNS.md
Hermes 284313f908
Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Initial import of AlgaPSA codebase from PSA server
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz

Source: /opt/alga-psa on psa.joliet.tech
2026-06-22 16:12:17 -05:00

303 lines
8.9 KiB
Markdown

# Email Test Patterns
This guide explains the abstracted patterns for writing email E2E tests that are maintainable, reliable, and easy to understand.
## Overview
Email testing involves complex tenant synchronization, workflow processing, and database state management. Rather than repeating this complexity in every test, we've abstracted common patterns into reusable utilities.
## Quick Start
### Basic Email Test
```typescript
import { createPersistentE2EHelpers } from './utils/persistent-test-context';
import { createEmailTestHelpers } from './utils/email-test-helpers';
describe('My Email Tests', () => {
let context, emailHelpers;
beforeAll(async () => {
context = await createPersistentE2EHelpers().beforeAll();
emailHelpers = createEmailTestHelpers(context);
});
it('should process an email', async () => {
// Arrange - Create scenario with automatic tenant handling
const scenario = await emailHelpers.createEmailScenario();
// Act - Send email
await scenario.sendEmail({
subject: 'Test Email',
body: 'Test body'
});
await scenario.waitForProcessing();
// Assert - Verify results
const tickets = await scenario.getTickets();
expect(tickets).toHaveLength(1);
expect(tickets[0].title).toContain('Test Email');
});
});
```
## Core Abstractions
### 1. Email Test Helpers (`EmailTestHelpers`)
The main abstraction that handles:
- ✅ Tenant creation and synchronization
- ✅ Database transaction management
- ✅ MailHog service coordination
- ✅ Data visibility across connections
- ✅ Debug logging
**Methods:**
- `createEmailScenario()` - Create complete test scenario
- `createUnknownEmailScenario()` - For testing unknown senders
### 2. Email Test Scenario (`EmailTestScenario`)
A scenario object that provides:
- ✅ Pre-configured tenant, company, and contact
- ✅ Simple email sending with automatic tenant handling
- ✅ Workflow processing coordination
- ✅ Easy data retrieval methods
**Methods:**
- `sendEmail(config)` - Send email with tenant synchronization
- `waitForProcessing(timeout?)` - Wait for workflow completion
- `getTickets()` - Get tickets for this scenario's contact
- `getComments(ticketId)` - Get comments for a ticket
- `getDocuments()` - Get attachments/documents
### 3. Static Assertion Helpers (`EmailTestHelpers`)
Pre-built assertions for common test scenarios:
-`assertTicketCreated()` - Verify ticket creation
-`assertAttachmentProcessed()` - Verify attachment handling
-`assertEmailThreading()` - Verify reply threading
## Test Patterns
### Pattern 1: Simple Email Processing
```typescript
it('should process a simple email', async () => {
const scenario = await emailHelpers.createEmailScenario();
await scenario.sendEmail({
subject: 'Support Request',
body: 'Please help me with this issue.'
});
await scenario.waitForProcessing();
const tickets = await scenario.getTickets();
EmailTestHelpers.assertTicketCreated(tickets, 'Support Request', scenario.contact.email);
});
```
### Pattern 2: Email with Attachments
```typescript
it('should handle email attachments', async () => {
const scenario = await emailHelpers.createEmailScenario();
await scenario.sendEmail({
subject: 'Document Upload',
body: 'Please see attached document.',
attachments: [{
filename: 'report.pdf',
content: Buffer.from('PDF content'),
contentType: 'application/pdf'
}]
});
await scenario.waitForProcessing();
const documents = await scenario.getDocuments();
EmailTestHelpers.assertAttachmentProcessed(documents, 'report.pdf');
});
```
### Pattern 3: Email Threading/Replies
```typescript
it('should thread email replies', async () => {
const scenario = await emailHelpers.createEmailScenario();
// Send initial email
const { sentEmail } = await scenario.sendEmail({
subject: 'Initial Request',
body: 'Original message'
});
await scenario.waitForProcessing();
const initialTickets = await scenario.getTickets();
// Send reply
await scenario.sendEmail({
subject: 'Re: Initial Request',
body: 'Reply message',
inReplyTo: sentEmail.messageId,
references: sentEmail.messageId
});
await scenario.waitForProcessing();
const finalTickets = await scenario.getTickets();
const comments = await scenario.getComments(initialTickets[0].ticket_id);
EmailTestHelpers.assertEmailThreading(
initialTickets, finalTickets, comments,
'Original message', 'Reply message'
);
});
```
### Pattern 4: Unknown Email Addresses
```typescript
it('should handle unknown senders', async () => {
const unknownScenario = await emailHelpers.createUnknownEmailScenario();
await unknownScenario.sendEmail({
subject: 'Unknown Sender',
body: 'Email from unknown address'
});
await unknownScenario.waitForProcessing();
const tickets = await unknownScenario.getTickets();
// Verify appropriate handling (may create task for manual review)
expect(tickets.length).toBeGreaterThanOrEqual(0);
});
```
## What's Abstracted Away
### Before (Manual Pattern)
```typescript
it('should process email', async () => {
// 20+ lines of tenant setup
const { tenant, company, contact } = await context.emailTestFactory.createBasicEmailScenario();
console.log(`[TENANT-DEBUG] Test scenario created: tenant=${tenant.tenant}`);
const tenantCheck = await context.db('tenants').where('tenant', tenant.tenant).first();
if (!tenantCheck) {
throw new Error(`Tenant not found`);
}
try {
await context.db.raw('COMMIT');
await context.db.raw('BEGIN');
} catch (error) {
// Handle transaction errors
}
await new Promise(resolve => setTimeout(resolve, 3000));
const testEmail = { /* email config */ };
const { sentEmail, capturedEmail } = await context.sendAndCaptureEmail(testEmail);
await context.waitForWorkflowProcessing(30000);
// 10+ lines of manual queries and assertions
const ticketResult = await context.db.raw(`SELECT...`);
const tickets = ticketResult.rows || ticketResult;
expect(tickets).toHaveLength(1);
// etc...
});
```
### After (Abstracted Pattern)
```typescript
it('should process email', async () => {
const scenario = await emailHelpers.createEmailScenario();
await scenario.sendEmail({
subject: 'Test Email',
body: 'Test body'
});
await scenario.waitForProcessing();
const tickets = await scenario.getTickets();
EmailTestHelpers.assertTicketCreated(tickets, 'Test Email', scenario.contact.email);
});
```
## Benefits
### ✅ **Reduced Complexity**
- 5-10 lines vs 50+ lines per test
- No need to understand tenant synchronization details
- Built-in error handling and logging
### ✅ **Consistency**
- All tests use the same proven patterns
- Eliminates copy-paste errors
- Standardized debugging output
### ✅ **Maintainability**
- Changes to tenant handling logic in one place
- Easy to add new helper methods
- Clear separation of concerns
### ✅ **Readability**
- Tests focus on business logic, not infrastructure
- Self-documenting method names
- Consistent assertion patterns
### ✅ **Reliability**
- Proven tenant synchronization patterns
- Automatic timeout management
- Robust error handling
## Migration Guide
### For Existing Tests
1. Replace manual tenant handling with `emailHelpers.createEmailScenario()`
2. Replace manual email sending with `scenario.sendEmail()`
3. Replace manual queries with scenario methods (`getTickets()`, etc.)
4. Replace manual assertions with static helper methods
### For New Tests
1. Import the helper utilities
2. Use the abstracted patterns from the start
3. Focus on test business logic, not infrastructure
## Extension Points
### Adding New Scenario Types
```typescript
// In EmailTestHelpers class
async createBulkEmailScenario(): Promise<BulkEmailScenario> {
// Implementation for testing bulk email processing
}
```
### Adding New Assertions
```typescript
// In EmailTestHelpers class
static assertPrioritySet(tickets: any[], expectedPriority: string): void {
expect(tickets[0].priority_name).toBe(expectedPriority);
}
```
### Custom Email Configurations
```typescript
await scenario.sendEmail({
subject: 'Custom Test',
body: 'Custom body',
from: 'custom@sender.com',
to: 'custom@recipient.com',
attachments: [/* custom attachments */],
inReplyTo: 'custom-reply-id',
references: 'custom-references'
});
```
## File Structure
```
src/test/e2e/
├── utils/
│ ├── email-test-helpers.ts # Main abstraction
│ ├── persistent-test-context.ts # Persistent harness
│ └── ...
├── email-processing-simplified.test.ts # Example using abstractions
├── email-processing-persistent.test.ts # Direct persistent usage
└── email-processing.test.ts # Original manual pattern
```
This abstraction approach ensures that all future email tests will be fast, reliable, and maintainable while hiding the complexity of tenant synchronization and workflow coordination.