mirror of
https://github.com/dreamhunter2333/cloudflare_temp_email.git
synced 2026-09-05 23:47:50 +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:
co-authored by
Claude Opus 4.6
parent
f98bbce234
commit
e38015a5b6
@@ -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(
|
export async function seedTestMail(
|
||||||
ctx: APIRequestContext,
|
ctx: APIRequestContext,
|
||||||
@@ -41,11 +42,13 @@ export async function seedTestMail(
|
|||||||
const boundary = `----E2E${Date.now()}`;
|
const boundary = `----E2E${Date.now()}`;
|
||||||
const htmlPart = opts.html || `<p>${opts.text || 'Hello from E2E'}</p>`;
|
const htmlPart = opts.html || `<p>${opts.text || 'Hello from E2E'}</p>`;
|
||||||
const textPart = opts.text || 'Hello from E2E';
|
const textPart = opts.text || 'Hello from E2E';
|
||||||
|
const messageId = `<e2e-${Date.now()}-${Math.random().toString(36).slice(2, 10)}@test>`;
|
||||||
|
|
||||||
const raw = [
|
const raw = [
|
||||||
`From: ${from}`,
|
`From: ${from}`,
|
||||||
`To: ${address}`,
|
`To: ${address}`,
|
||||||
`Subject: ${subject}`,
|
`Subject: ${subject}`,
|
||||||
|
`Message-ID: ${messageId}`,
|
||||||
`MIME-Version: 1.0`,
|
`MIME-Version: 1.0`,
|
||||||
`Content-Type: multipart/alternative; boundary="${boundary}"`,
|
`Content-Type: multipart/alternative; boundary="${boundary}"`,
|
||||||
``,
|
``,
|
||||||
@@ -60,12 +63,16 @@ export async function seedTestMail(
|
|||||||
`--${boundary}--`,
|
`--${boundary}--`,
|
||||||
].join('\r\n');
|
].join('\r\n');
|
||||||
|
|
||||||
const res = await ctx.post(`${WORKER_URL}/admin/test/seed_mail`, {
|
const res = await ctx.post(`${WORKER_URL}/admin/test/receive_mail`, {
|
||||||
data: { address, source: from, raw },
|
data: { from, to: address, raw },
|
||||||
});
|
});
|
||||||
if (!res.ok()) {
|
if (!res.ok()) {
|
||||||
throw new Error(`Failed to seed mail: ${res.status()} ${await res.text()}`);
|
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'}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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 ip_blacklist_settings from './ip_blacklist_settings'
|
||||||
import ai_extract_settings from './ai_extract_settings'
|
import ai_extract_settings from './ai_extract_settings'
|
||||||
import { EmailRuleSettings } from '../models'
|
import { EmailRuleSettings } from '../models'
|
||||||
|
import e2e_test_api from './e2e_test_api'
|
||||||
|
|
||||||
export const api = new Hono<HonoCustomType>()
|
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.get("/admin/ai_extract/settings", ai_extract_settings.getAiExtractSettings);
|
||||||
api.post("/admin/ai_extract/settings", ai_extract_settings.saveAiExtractSettings);
|
api.post("/admin/ai_extract/settings", ai_extract_settings.saveAiExtractSettings);
|
||||||
|
|
||||||
// Test-only endpoint for seeding emails. MUST NOT be enabled in production.
|
// E2E test endpoints
|
||||||
api.post('/admin/test/seed_mail', async (c) => {
|
api.post('/admin/test/seed_mail', e2e_test_api.seedMail);
|
||||||
if (!getBooleanValue(c.env.E2E_TEST_MODE)) {
|
api.post('/admin/test/receive_mail', e2e_test_api.receiveMail);
|
||||||
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 });
|
|
||||||
});
|
|
||||||
|
|||||||
Reference in New Issue
Block a user