@bernierllc/email-mitm-masking 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.eslintrc.js ADDED
@@ -0,0 +1,21 @@
1
+ module.exports = {
2
+ parser: '@typescript-eslint/parser',
3
+ parserOptions: {
4
+ ecmaVersion: 2020,
5
+ sourceType: 'module',
6
+ project: './tsconfig.json',
7
+ },
8
+ extends: [
9
+ 'eslint:recommended',
10
+ 'plugin:@typescript-eslint/recommended',
11
+ 'plugin:@typescript-eslint/recommended-requiring-type-checking',
12
+ ],
13
+ plugins: ['@typescript-eslint'],
14
+ rules: {
15
+ '@typescript-eslint/no-explicit-any': 'error',
16
+ '@typescript-eslint/explicit-function-return-type': 'warn',
17
+ '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
18
+ '@typescript-eslint/no-floating-promises': 'error',
19
+ },
20
+ ignorePatterns: ['dist', 'node_modules', '*.js', '*.cjs'],
21
+ };
package/README.md ADDED
@@ -0,0 +1,405 @@
1
+ # @bernierllc/email-mitm-masking
2
+
3
+ Email masking and man-in-the-middle routing service for privacy-focused email communication. Provides proxy email addresses that mask real user addresses while routing messages bidirectionally with full audit trail and lifecycle management.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @bernierllc/email-mitm-masking
9
+ ```
10
+
11
+ ## Features
12
+
13
+ - 🔒 **Privacy-First**: Generate proxy email addresses to mask real user addresses
14
+ - ↔️ **Bidirectional Routing**: Route emails both inbound (external → real) and outbound (real → external)
15
+ - 🔄 **Lifecycle Management**: Create, activate, deactivate, expire, and revoke proxies
16
+ - 📊 **Audit Trail**: Complete routing audit log for compliance and debugging
17
+ - 🎯 **Flexible Strategies**: Support for per-user, per-contact, and per-conversation masking
18
+ - ⏱️ **TTL Support**: Automatic expiration with configurable time-to-live
19
+ - 📈 **Usage Tracking**: Monitor proxy usage and routing statistics
20
+ - 🔒 **Quota Enforcement**: Prevent abuse with per-user proxy limits
21
+
22
+ ## Usage
23
+
24
+ ### Basic Setup
25
+
26
+ ```typescript
27
+ import { EmailMaskingService } from '@bernierllc/email-mitm-masking';
28
+
29
+ const service = new EmailMaskingService({
30
+ proxyDomain: 'proxy.example.com',
31
+ database: {
32
+ host: 'localhost',
33
+ port: 5432,
34
+ database: 'email_masking',
35
+ user: 'postgres',
36
+ password: process.env.DB_PASSWORD
37
+ },
38
+ defaultTTL: 365, // 365 days
39
+ maxProxiesPerUser: 100, // 100 proxies per user
40
+ auditEnabled: true, // Enable audit logging
41
+ strategy: 'per-contact' // Create one proxy per contact
42
+ });
43
+
44
+ await service.initialize();
45
+ ```
46
+
47
+ ### Create a Proxy Email Address
48
+
49
+ ```typescript
50
+ // Create a basic proxy
51
+ const proxy = await service.createProxy(
52
+ 'user123',
53
+ 'user@real.com'
54
+ );
55
+
56
+ console.log(proxy.proxyEmail);
57
+ // Output: user123-a1b2c3d4e5f6@proxy.example.com
58
+
59
+ // Create proxy with custom TTL
60
+ const tempProxy = await service.createProxy(
61
+ 'user123',
62
+ 'user@real.com',
63
+ {
64
+ ttlDays: 30, // Expires in 30 days
65
+ externalEmail: 'contact@external.com',
66
+ metadata: { source: 'signup-form', priority: 'high' }
67
+ }
68
+ );
69
+ ```
70
+
71
+ ### Route Inbound Email (Webhook Handler)
72
+
73
+ ```typescript
74
+ import express from 'express';
75
+
76
+ const app = express();
77
+
78
+ app.post('/webhooks/inbound-email', express.text({ type: '*/*' }), async (req, res) => {
79
+ const { from, to, subject, text, html } = parseEmail(req.body);
80
+
81
+ const result = await service.routeInbound(from, to, subject, text, html);
82
+
83
+ if (result.success) {
84
+ res.json({
85
+ status: 'routed',
86
+ to: result.routedTo,
87
+ auditId: result.auditId
88
+ });
89
+ } else {
90
+ res.status(400).json({
91
+ status: 'failed',
92
+ error: result.error
93
+ });
94
+ }
95
+ });
96
+ ```
97
+
98
+ ### Route Outbound Email (Reply via Proxy)
99
+
100
+ ```typescript
101
+ async function sendReplyViaProxy(
102
+ userId: string,
103
+ userEmail: string,
104
+ recipientEmail: string,
105
+ message: string
106
+ ) {
107
+ const result = await service.routeOutbound(
108
+ userId,
109
+ userEmail,
110
+ recipientEmail,
111
+ {
112
+ subject: 'Re: Your inquiry',
113
+ text: message,
114
+ html: `<p>${message}</p>`
115
+ }
116
+ );
117
+
118
+ if (result.success) {
119
+ console.log(`Reply sent via proxy: ${result.proxyUsed}`);
120
+ } else {
121
+ console.error(`Failed to send reply: ${result.error}`);
122
+ }
123
+
124
+ return result;
125
+ }
126
+ ```
127
+
128
+ ### Proxy Lifecycle Management
129
+
130
+ ```typescript
131
+ // Create proxy with 30-day expiration
132
+ const proxy = await service.createProxy('user456', 'user@example.com', {
133
+ ttlDays: 30
134
+ });
135
+
136
+ // Use the proxy for routing...
137
+ await service.routeInbound(from, proxy.proxyEmail, subject);
138
+
139
+ // Deactivate when temporarily not needed
140
+ await service.deactivateProxy(proxy.id);
141
+
142
+ // Permanently revoke
143
+ await service.revokeProxy(proxy.id);
144
+
145
+ // Retrieve proxy details
146
+ const proxyDetails = await service.getProxyById(proxy.id);
147
+ console.log(proxyDetails.status); // 'revoked'
148
+ ```
149
+
150
+ ## API Reference
151
+
152
+ ### EmailMaskingService
153
+
154
+ #### `new EmailMaskingService(config)`
155
+
156
+ Create a new email masking service instance.
157
+
158
+ **Parameters:**
159
+ - `config.proxyDomain` (string, required): Domain for proxy addresses
160
+ - `config.database` (DatabaseConfig, required): PostgreSQL connection config
161
+ - `config.defaultTTL` (number, optional): Default TTL in days (0 = unlimited, default: 0)
162
+ - `config.maxProxiesPerUser` (number, optional): Max proxies per user (0 = unlimited, default: 0)
163
+ - `config.auditEnabled` (boolean, optional): Enable audit logging (default: true)
164
+ - `config.strategy` ('per-user' | 'per-contact' | 'per-conversation', optional): Masking strategy (default: 'per-user')
165
+
166
+ #### `initialize(): Promise<void>`
167
+
168
+ Initialize the service, create database schema, and start cleanup jobs.
169
+
170
+ #### `createProxy(userId, realEmail, options?): Promise<ProxyAddress>`
171
+
172
+ Create a new proxy email address.
173
+
174
+ **Parameters:**
175
+ - `userId` (string): User identifier
176
+ - `realEmail` (string): Real user email address
177
+ - `options.ttlDays` (number, optional): TTL in days
178
+ - `options.externalEmail` (string, optional): External contact email
179
+ - `options.conversationId` (string, optional): Conversation identifier
180
+ - `options.metadata` (object, optional): Custom metadata
181
+
182
+ **Returns:** `ProxyAddress` object
183
+
184
+ #### `routeInbound(from, to, subject, text?, html?): Promise<RoutingResult>`
185
+
186
+ Route an inbound email from external sender to real address via proxy.
187
+
188
+ **Parameters:**
189
+ - `from` (string): External sender address
190
+ - `to` (string): Proxy email address
191
+ - `subject` (string): Email subject
192
+ - `text` (string, optional): Plain text body
193
+ - `html` (string, optional): HTML body
194
+
195
+ **Returns:** `RoutingResult` with routing status and audit ID
196
+
197
+ #### `routeOutbound(userId, realEmail, externalEmail, emailContent): Promise<RoutingResult>`
198
+
199
+ Route an outbound email from real address to external recipient via proxy.
200
+
201
+ **Parameters:**
202
+ - `userId` (string): User identifier
203
+ - `realEmail` (string): Real user email address
204
+ - `externalEmail` (string): External recipient address
205
+ - `emailContent.subject` (string): Email subject
206
+ - `emailContent.text` (string, optional): Plain text body
207
+ - `emailContent.html` (string, optional): HTML body
208
+
209
+ **Returns:** `RoutingResult` with routing status and audit ID
210
+
211
+ #### `deactivateProxy(proxyId): Promise<void>`
212
+
213
+ Deactivate a proxy address (can be reactivated).
214
+
215
+ #### `revokeProxy(proxyId): Promise<void>`
216
+
217
+ Permanently revoke a proxy address.
218
+
219
+ #### `getProxyById(proxyId): Promise<ProxyAddress | null>`
220
+
221
+ Retrieve proxy details by ID.
222
+
223
+ #### `shutdown(): Promise<void>`
224
+
225
+ Cleanup resources and close database connection.
226
+
227
+ ## Configuration
228
+
229
+ ### Environment Variables
230
+
231
+ ```bash
232
+ # EMAIL_MITM_MASKING Configuration
233
+ EMAIL_MASKING_PROXY_DOMAIN=proxy.example.com
234
+ EMAIL_MASKING_DB_HOST=localhost
235
+ EMAIL_MASKING_DB_PORT=5432
236
+ EMAIL_MASKING_DB_NAME=email_masking
237
+ EMAIL_MASKING_DB_USER=postgres
238
+ EMAIL_MASKING_DB_PASSWORD=secret
239
+ EMAIL_MASKING_DEFAULT_TTL=365 # Days (0 = unlimited)
240
+ EMAIL_MASKING_MAX_PROXIES_PER_USER=100 # Max per user (0 = unlimited)
241
+ EMAIL_MASKING_AUDIT_ENABLED=true # Enable audit logging
242
+ EMAIL_MASKING_STRATEGY=per-contact # per-user, per-contact, per-conversation
243
+ ```
244
+
245
+ ## Database Schema
246
+
247
+ The service automatically creates the following PostgreSQL tables:
248
+
249
+ ### `proxy_addresses`
250
+ Stores proxy email address records with lifecycle management.
251
+
252
+ ### `routing_audit`
253
+ Audit trail for all routing decisions (when audit is enabled).
254
+
255
+ ### `masking_rules`
256
+ User-defined masking rules (reserved for future use).
257
+
258
+ See the plan file for complete schema definitions.
259
+
260
+ ## Security Considerations
261
+
262
+ 1. **Secure Token Generation**: Uses cryptographically secure random tokens for proxy addresses
263
+ 2. **Email Validation**: Validates proxy domain to prevent spoofing
264
+ 3. **Quota Enforcement**: Prevents abuse via per-user proxy limits
265
+ 4. **Audit Trail**: Complete audit log for compliance and debugging
266
+ 5. **Status Validation**: Checks proxy status before routing (active, expired, revoked)
267
+ 6. **SQL Injection Protection**: Uses parameterized queries throughout
268
+ 7. **Privacy Guarantee**: Real email addresses never exposed in external emails
269
+ 8. **Automatic Cleanup**: Hourly job expires old proxies automatically
270
+
271
+ ## Integration Status
272
+
273
+ - **Logger**: integrated - Structured logging for all operations
274
+ - **Docs-Suite**: ready - Complete API documentation with TypeDoc
275
+ - **NeverHub**: not-applicable - Service package, NeverHub integration optional
276
+
277
+ ## Performance
278
+
279
+ - **Proxy Creation**: ~50ms (including database insert)
280
+ - **Routing Lookup**: ~10ms (indexed queries)
281
+ - **Audit Logging**: ~5ms (when enabled)
282
+ - **Cleanup Job**: Runs hourly, processes expired proxies in bulk
283
+
284
+ ## Error Handling
285
+
286
+ All methods return structured results:
287
+
288
+ ```typescript
289
+ interface RoutingResult {
290
+ success: boolean;
291
+ routedTo?: string;
292
+ proxyUsed?: string;
293
+ direction: 'inbound' | 'outbound';
294
+ error?: string;
295
+ auditId?: string;
296
+ }
297
+ ```
298
+
299
+ Example error handling:
300
+
301
+ ```typescript
302
+ const result = await service.routeInbound(from, to, subject);
303
+
304
+ if (!result.success) {
305
+ switch (result.error) {
306
+ case 'Proxy address not found':
307
+ // Handle unknown proxy
308
+ break;
309
+ case 'Proxy is inactive':
310
+ // Handle inactive proxy
311
+ break;
312
+ case 'Proxy expired':
313
+ // Handle expired proxy
314
+ break;
315
+ default:
316
+ // Handle other errors
317
+ }
318
+ }
319
+ ```
320
+
321
+ ## Examples
322
+
323
+ ### Per-Contact Masking
324
+
325
+ ```typescript
326
+ const service = new EmailMaskingService({
327
+ proxyDomain: 'proxy.example.com',
328
+ database: dbConfig,
329
+ strategy: 'per-contact'
330
+ });
331
+
332
+ // Each user-contact pair gets a unique proxy
333
+ const proxy1 = await service.createProxy('user1', 'user@real.com', {
334
+ externalEmail: 'alice@example.com'
335
+ });
336
+
337
+ const proxy2 = await service.createProxy('user1', 'user@real.com', {
338
+ externalEmail: 'bob@example.com'
339
+ });
340
+
341
+ // Different proxies for different contacts
342
+ console.log(proxy1.proxyEmail !== proxy2.proxyEmail); // true
343
+ ```
344
+
345
+ ### Temporary Proxies
346
+
347
+ ```typescript
348
+ // Create proxy that expires in 7 days
349
+ const tempProxy = await service.createProxy('user123', 'user@real.com', {
350
+ ttlDays: 7,
351
+ metadata: { purpose: 'one-time-signup' }
352
+ });
353
+
354
+ // After 7 days, inbound routing will fail
355
+ // Cleanup job will mark it as expired
356
+ ```
357
+
358
+ ### Webhook Integration (SendGrid)
359
+
360
+ ```typescript
361
+ app.post('/webhooks/sendgrid', express.json(), async (req, res) => {
362
+ const { from, to, subject, text, html } = req.body;
363
+
364
+ const result = await service.routeInbound(from, to, subject, text, html);
365
+
366
+ res.status(result.success ? 200 : 400).json(result);
367
+ });
368
+ ```
369
+
370
+ ## Testing
371
+
372
+ ```bash
373
+ # Run tests
374
+ npm test
375
+
376
+ # Run tests with coverage
377
+ npm run test:coverage
378
+
379
+ # Run tests once (CI mode)
380
+ npm run test:run
381
+ ```
382
+
383
+ ## License
384
+
385
+ Copyright (c) 2025 Bernier LLC
386
+
387
+ This file is licensed to the client under a limited-use license.
388
+ The client may use and modify this code *only within the scope of the project it was delivered for*.
389
+ Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
390
+
391
+ ## Related Packages
392
+
393
+ ### Dependencies
394
+ - [@bernierllc/email-parser](../../../core/email-parser) - Parse incoming emails (if needed)
395
+ - [@bernierllc/email-sender](../../../core/email-sender) - Send routed emails (if needed)
396
+ - [@bernierllc/crypto-utils](../../../core/crypto-utils) - Generate secure proxy tokens (fallback)
397
+ - [@bernierllc/logger](../../../core/logger) - Structured logging
398
+ - [@bernierllc/neverhub-adapter](../../../core/neverhub-adapter) - Service discovery (optional)
399
+
400
+ ### Part of Suite
401
+ - [@bernierllc/email-testing-suite](../../suite/email-testing-suite) - Complete email testing solution
402
+
403
+ ## Support
404
+
405
+ For issues and questions, please refer to the main BernierLLC tools repository.
package/TESTING.md ADDED
@@ -0,0 +1,237 @@
1
+ # Email MITM Masking - Testing Documentation
2
+
3
+ ## Overview
4
+
5
+ This package now includes comprehensive database mocking to enable testing without requiring a PostgreSQL database connection.
6
+
7
+ ## Changes Made
8
+
9
+ ### 1. Database Mock Implementation
10
+
11
+ **File**: `__tests__/__mocks__/database.ts`
12
+
13
+ Created an in-memory database mock that:
14
+ - Implements PostgreSQL `Pool` class interface
15
+ - Provides in-memory storage for `proxy_addresses` and `routing_audit` tables
16
+ - Handles all SQL operations (INSERT, SELECT, UPDATE)
17
+ - Maintains referential integrity and indexes
18
+ - Supports UUID generation
19
+ - Auto-resets between tests for isolation
20
+
21
+ **Key Features**:
22
+ - Zero external dependencies
23
+ - Fast test execution (<1 second for all 32 tests)
24
+ - Full CRUD operation support
25
+ - Query pattern matching for SQL operations
26
+
27
+ ### 2. Test Setup Configuration
28
+
29
+ **File**: `__tests__/setup.ts`
30
+
31
+ Added test setup that:
32
+ - Resets mock database before each test
33
+ - Ensures test isolation
34
+ - Prevents state leakage between tests
35
+
36
+ **File**: `jest.config.cjs`
37
+
38
+ Updated Jest configuration to:
39
+ - Include setup file: `setupFilesAfterEnv: ['<rootDir>/__tests__/setup.ts']`
40
+ - Mock `pg` module: Maps to database mock
41
+ - Mock `database.js` module: Ensures consistent mocking
42
+
43
+ ### 3. Implementation Fixes
44
+
45
+ Fixed two implementation issues that were preventing tests from passing:
46
+
47
+ #### Issue 1: Expired Proxy Creation
48
+
49
+ **Problem**: The original implementation treated all `ttl <= 0` as "no expiration", preventing creation of already-expired proxies for testing.
50
+
51
+ **Fix**: Changed logic to:
52
+ - Allow negative TTL values (creates already-expired proxies)
53
+ - Only treat `ttl === 0` as "no expiration"
54
+ - Distinguish between "undefined TTL" (use default) and "explicit 0 TTL" (no expiration)
55
+
56
+ **File**: `src/EmailMaskingService.ts` (lines 81-84)
57
+
58
+ ```typescript
59
+ // Before
60
+ const ttl = options?.ttlDays || this.config.defaultTTL || 0;
61
+ const expiresAt = ttl > 0
62
+ ? new Date(Date.now() + ttl * 24 * 60 * 60 * 1000)
63
+ : null;
64
+
65
+ // After
66
+ const ttl = options?.ttlDays !== undefined ? options.ttlDays : (this.config.defaultTTL || 0);
67
+ const expiresAt = ttl !== 0
68
+ ? new Date(Date.now() + ttl * 24 * 60 * 60 * 1000)
69
+ : null;
70
+ ```
71
+
72
+ #### Issue 2: Inactive Proxy Detection
73
+
74
+ **Problem**: The `getProxyForContact()` method only found ACTIVE proxies, causing duplicate proxies to be created when an inactive proxy already existed for a contact.
75
+
76
+ **Fix**: Changed query to:
77
+ - Find ANY proxy for a user-contact pair (not just active ones)
78
+ - Return most recent proxy (ORDER BY created_at DESC)
79
+ - Let caller check proxy status and handle accordingly
80
+
81
+ **File**: `src/EmailMaskingService.ts` (lines 280-287)
82
+
83
+ ```typescript
84
+ // Before
85
+ SELECT * FROM proxy_addresses
86
+ WHERE user_id = $1
87
+ AND real_email = $2
88
+ AND external_email = $3
89
+ AND status = 'active'
90
+ LIMIT 1
91
+
92
+ // After
93
+ SELECT * FROM proxy_addresses
94
+ WHERE user_id = $1
95
+ AND real_email = $2
96
+ AND external_email = $3
97
+ ORDER BY created_at DESC
98
+ LIMIT 1
99
+ ```
100
+
101
+ ## Test Results
102
+
103
+ ### Test Pass Rate
104
+ - **Total Tests**: 32
105
+ - **Passed**: 32 (100%)
106
+ - **Failed**: 0
107
+ - **Execution Time**: <1 second
108
+
109
+ ### Coverage Metrics
110
+
111
+ Exceeds all 85% thresholds:
112
+
113
+ | Metric | Coverage | Threshold | Status |
114
+ |------------|----------|-----------|--------|
115
+ | Statements | 97.33% | 85% | ✅ PASS |
116
+ | Branches | 96.42% | 85% | ✅ PASS |
117
+ | Functions | 90.9% | 85% | ✅ PASS |
118
+ | Lines | 97.29% | 85% | ✅ PASS |
119
+
120
+ **Uncovered Lines**: 384-392 (cleanup interval timer - not triggered during tests)
121
+
122
+ ### Build & Lint Status
123
+
124
+ - ✅ **Build**: Passes with zero errors
125
+ - ✅ **Lint**: Passes with zero warnings or errors
126
+
127
+ ## Test Categories
128
+
129
+ ### 1. Create Proxy (7 tests)
130
+ - Default TTL configuration
131
+ - Custom TTL values (including negative for expired proxies)
132
+ - Per-contact masking with external email
133
+ - Conversation ID support
134
+ - Metadata attachment
135
+ - User quota enforcement
136
+ - Unlimited quota mode
137
+
138
+ ### 2. Inbound Routing (8 tests)
139
+ - Basic email routing to real address
140
+ - Name <email> format parsing
141
+ - Unknown proxy rejection
142
+ - Inactive proxy rejection
143
+ - Revoked proxy rejection
144
+ - Expired proxy rejection
145
+ - Missing proxy address handling
146
+ - Usage statistics updates
147
+
148
+ ### 3. Outbound Routing (5 tests)
149
+ - Auto-creation of proxies for new contacts
150
+ - Proxy reuse for known contacts
151
+ - Quota limit enforcement during auto-creation
152
+ - Inactive proxy rejection
153
+ - Usage statistics updates
154
+
155
+ ### 4. Proxy Management (5 tests)
156
+ - Deactivation
157
+ - Revocation
158
+ - Retrieval by ID
159
+ - Non-existent proxy handling
160
+
161
+ ### 5. Audit Logging (2 tests)
162
+ - Audit entry creation when enabled
163
+ - No audit when disabled
164
+
165
+ ### 6. Configuration (3 tests)
166
+ - Default TTL usage
167
+ - Unlimited TTL (0 value)
168
+ - Proxy domain configuration
169
+
170
+ ### 7. Edge Cases (3 tests)
171
+ - Concurrent proxy creation (race conditions)
172
+ - Empty metadata handling
173
+ - Special characters in email addresses
174
+
175
+ ## Running Tests
176
+
177
+ ```bash
178
+ # Run tests with watch mode (for development)
179
+ npm test
180
+
181
+ # Run tests once
182
+ npm run test:run
183
+
184
+ # Run tests with coverage report
185
+ npm run test:coverage
186
+
187
+ # Run build
188
+ npm run build
189
+
190
+ # Run lint
191
+ npm run lint
192
+ ```
193
+
194
+ ## Mock Implementation Details
195
+
196
+ ### SQL Operations Supported
197
+
198
+ 1. **CREATE TABLE** - Schema initialization (no-op)
199
+ 2. **INSERT INTO proxy_addresses** - Creates proxy with UUID generation
200
+ 3. **INSERT INTO routing_audit** - Creates audit entries
201
+ 4. **SELECT by ID** - Retrieves proxies by UUID
202
+ 5. **SELECT by proxy_email** - Finds proxy by email address
203
+ 6. **SELECT by user/real/external** - Finds proxy for specific contact pair
204
+ 7. **COUNT** - Counts active proxies per user
205
+ 8. **UPDATE status** - Deactivates/revokes proxies
206
+ 9. **UPDATE routing stats** - Increments usage counters
207
+ 10. **UPDATE expired** - Bulk expiration updates
208
+
209
+ ### Data Storage
210
+
211
+ - **In-memory Maps**: Stores data in JavaScript Map objects
212
+ - **Indexes**: Maintains `proxy_email -> id` index for fast lookups
213
+ - **UUID Generation**: Custom implementation matching PostgreSQL format
214
+ - **Date Handling**: Stores as ISO strings, converts to Date objects in results
215
+
216
+ ### Query Matching
217
+
218
+ The mock uses SQL pattern matching to identify query types:
219
+ - Normalized SQL (lowercase, trimmed)
220
+ - Keyword detection (INSERT, SELECT, UPDATE, WHERE, etc.)
221
+ - Parameter extraction and matching
222
+ - Result formatting to match PostgreSQL structure
223
+
224
+ ## Benefits
225
+
226
+ 1. **No Database Required**: Tests run without PostgreSQL installation
227
+ 2. **Fast Execution**: In-memory operations complete in milliseconds
228
+ 3. **Deterministic**: No external dependencies or timing issues
229
+ 4. **Isolated**: Each test runs with clean state
230
+ 5. **Maintainable**: Mock closely mirrors actual database behavior
231
+
232
+ ## Future Considerations
233
+
234
+ 1. **Mock Validation**: Consider adding tests for the mock itself to ensure it matches PostgreSQL behavior
235
+ 2. **Query Coverage**: Add logging for unhandled queries to catch missing implementations
236
+ 3. **Performance**: Current mock handles 32 tests in <1s - monitor as test suite grows
237
+ 4. **Cleanup Timer**: Consider mocking or testing the cleanup interval (lines 384-392)