mirror of
https://github.com/dreamhunter2333/cloudflare_temp_email.git
synced 2026-05-06 20:32:55 +08:00
refactor: E2E tests use real email() handler (#870)
* refactor: add receive_mail E2E endpoint using real email() handler Add /admin/test/receive_mail that constructs a mock ForwardableEmailMessage and calls the real email() handler, so E2E tests exercise the full mail processing pipeline. Extract both test endpoints into e2e_test_api.ts. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: trigger CI --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -29,7 +29,8 @@ export async function createTestAddress(
|
||||
}
|
||||
|
||||
/**
|
||||
* Seed a test email into the worker DB via the admin test endpoint.
|
||||
* Seed a test email by exercising the real worker email() handler
|
||||
* via the admin test endpoint.
|
||||
*/
|
||||
export async function seedTestMail(
|
||||
ctx: APIRequestContext,
|
||||
@@ -41,11 +42,13 @@ export async function seedTestMail(
|
||||
const boundary = `----E2E${Date.now()}`;
|
||||
const htmlPart = opts.html || `<p>${opts.text || 'Hello from E2E'}</p>`;
|
||||
const textPart = opts.text || 'Hello from E2E';
|
||||
const messageId = `<e2e-${Date.now()}-${Math.random().toString(36).slice(2, 10)}@test>`;
|
||||
|
||||
const raw = [
|
||||
`From: ${from}`,
|
||||
`To: ${address}`,
|
||||
`Subject: ${subject}`,
|
||||
`Message-ID: ${messageId}`,
|
||||
`MIME-Version: 1.0`,
|
||||
`Content-Type: multipart/alternative; boundary="${boundary}"`,
|
||||
``,
|
||||
@@ -60,12 +63,16 @@ export async function seedTestMail(
|
||||
`--${boundary}--`,
|
||||
].join('\r\n');
|
||||
|
||||
const res = await ctx.post(`${WORKER_URL}/admin/test/seed_mail`, {
|
||||
data: { address, source: from, raw },
|
||||
const res = await ctx.post(`${WORKER_URL}/admin/test/receive_mail`, {
|
||||
data: { from, to: address, raw },
|
||||
});
|
||||
if (!res.ok()) {
|
||||
throw new Error(`Failed to seed mail: ${res.status()} ${await res.text()}`);
|
||||
}
|
||||
const body = await res.json();
|
||||
if (!body.success) {
|
||||
throw new Error(`Mail was rejected: ${body.rejected || 'unknown reason'}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
63
worker/src/admin_api/e2e_test_api.ts
Normal file
63
worker/src/admin_api/e2e_test_api.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { Context } from 'hono'
|
||||
import { getBooleanValue } from '../utils'
|
||||
|
||||
// Direct DB insert — bypasses the email() handler.
|
||||
const seedMail = async (c: Context<HonoCustomType>) => {
|
||||
if (!getBooleanValue(c.env.E2E_TEST_MODE)) {
|
||||
return c.text("Not available", 404);
|
||||
}
|
||||
const { address, source, raw, message_id } = await c.req.json();
|
||||
if (!address || !raw) {
|
||||
return c.text("address and raw are required", 400);
|
||||
}
|
||||
if (raw.length > 1_000_000) {
|
||||
return c.text("raw content too large", 400);
|
||||
}
|
||||
if (message_id && message_id.length > 255) {
|
||||
return c.text("message_id too long", 400);
|
||||
}
|
||||
const msgId = message_id || `<e2e-${Date.now()}@test>`;
|
||||
const { success } = await c.env.DB.prepare(
|
||||
`INSERT INTO raw_mails (message_id, source, address, raw, created_at)`
|
||||
+ ` VALUES (?, ?, ?, ?, datetime('now'))`
|
||||
).bind(msgId, source || address, address, raw).run();
|
||||
return c.json({ success });
|
||||
};
|
||||
|
||||
// Exercises the real email() handler with a mock ForwardableEmailMessage.
|
||||
const receiveMail = async (c: Context<HonoCustomType>) => {
|
||||
if (!getBooleanValue(c.env.E2E_TEST_MODE)) {
|
||||
return c.text("Not available", 404);
|
||||
}
|
||||
const { from, to, raw } = await c.req.json();
|
||||
if (!from || !to || !raw) {
|
||||
return c.text("from, to and raw are required", 400);
|
||||
}
|
||||
|
||||
// Parse MIME headers (unfold continuation lines, extract key:value pairs)
|
||||
const headerSection = raw.substring(0, Math.max(0, raw.indexOf('\r\n\r\n')));
|
||||
const headers = new Headers();
|
||||
for (const line of headerSection.replace(/\r\n(?=[ \t])/g, ' ').split('\r\n')) {
|
||||
const idx = line.indexOf(':');
|
||||
if (idx > 0) headers.append(line.substring(0, idx).trim(), line.substring(idx + 1).trim());
|
||||
}
|
||||
if (!headers.has('Message-ID')) headers.set('Message-ID', `<e2e-${Date.now()}@test>`);
|
||||
|
||||
const rawBytes = new TextEncoder().encode(raw);
|
||||
let rejected: string | undefined;
|
||||
const mockMessage: ForwardableEmailMessage = {
|
||||
from, to, headers,
|
||||
rawSize: rawBytes.byteLength,
|
||||
raw: new ReadableStream({ start(ctrl) { ctrl.enqueue(rawBytes); ctrl.close(); } }),
|
||||
setReject(reason: string) { rejected = reason; },
|
||||
forward: async () => ({ messageId: '' }),
|
||||
reply: async () => ({ messageId: '' }),
|
||||
};
|
||||
|
||||
const { email: emailHandler } = await import('../email');
|
||||
await emailHandler(mockMessage, c.env, { waitUntil: () => {}, passThroughOnException: () => {} });
|
||||
|
||||
return c.json({ success: !rejected, ...(rejected ? { rejected } : {}) });
|
||||
};
|
||||
|
||||
export default { seedMail, receiveMail };
|
||||
@@ -17,6 +17,7 @@ import db_api from './db_api'
|
||||
import ip_blacklist_settings from './ip_blacklist_settings'
|
||||
import ai_extract_settings from './ai_extract_settings'
|
||||
import { EmailRuleSettings } from '../models'
|
||||
import e2e_test_api from './e2e_test_api'
|
||||
|
||||
export const api = new Hono<HonoCustomType>()
|
||||
|
||||
@@ -389,25 +390,6 @@ api.post("/admin/ip_blacklist/settings", ip_blacklist_settings.saveIpBlacklistSe
|
||||
api.get("/admin/ai_extract/settings", ai_extract_settings.getAiExtractSettings);
|
||||
api.post("/admin/ai_extract/settings", ai_extract_settings.saveAiExtractSettings);
|
||||
|
||||
// Test-only endpoint for seeding emails. MUST NOT be enabled in production.
|
||||
api.post('/admin/test/seed_mail', async (c) => {
|
||||
if (!getBooleanValue(c.env.E2E_TEST_MODE)) {
|
||||
return c.text("Not available", 404);
|
||||
}
|
||||
const { address, source, raw, message_id } = await c.req.json();
|
||||
if (!address || !raw) {
|
||||
return c.text("address and raw are required", 400);
|
||||
}
|
||||
if (raw.length > 1_000_000) {
|
||||
return c.text("raw content too large", 400);
|
||||
}
|
||||
if (message_id && message_id.length > 255) {
|
||||
return c.text("message_id too long", 400);
|
||||
}
|
||||
const msgId = message_id || `<e2e-${Date.now()}@test>`;
|
||||
const { success } = await c.env.DB.prepare(
|
||||
`INSERT INTO raw_mails (message_id, source, address, raw, created_at)`
|
||||
+ ` VALUES (?, ?, ?, ?, datetime('now'))`
|
||||
).bind(msgId, source || address, address, raw).run();
|
||||
return c.json({ success });
|
||||
});
|
||||
// E2E test endpoints
|
||||
api.post('/admin/test/seed_mail', e2e_test_api.seedMail);
|
||||
api.post('/admin/test/receive_mail', e2e_test_api.receiveMail);
|
||||
|
||||
Reference in New Issue
Block a user