mirror of
https://github.com/nestriness/nestri.git
synced 2026-09-19 09:15:19 +03:00
The rule was "throw when the environment says production, otherwise log the code and carry on". The deployment sets no such marker, so the branch that ran was the developer one: every recipient and every usable sign-in code printed to a retained log, the screen reporting success, and nobody receiving anything. That is what a fail-open default costs. The deployment that forgets its mail settings is exactly the deployment with no marker saying it is a real one, so it takes the lenient branch precisely when it should not. Turned around: printing a live code is asked for by name and anything else is an error, so absence of configuration is a refusal instead of an assumption. Two settings out of three is also an error now, because it means somebody is halfway through wiring a provider up and a quiet fallback would hide the missing half. Stages anyone else can reach are checked at deploy time, so a missing setting stops the deploy with the name of the variable it wanted rather than surfacing later as a person waiting for mail that never comes.
127 lines
3.8 KiB
TypeScript
127 lines
3.8 KiB
TypeScript
import { describe, expect, test } from 'bun:test';
|
|
|
|
import { sendVerificationCode } from '../src/email.js';
|
|
|
|
describe('sending a sign-in code', () => {
|
|
test('printing the code to the log has to be asked for by name', async () => {
|
|
await sendVerificationCode({ EMAIL_DEV_LOG: 'true' }, 'ada@example.com', '123456');
|
|
});
|
|
|
|
// The regression this holds: the previous rule was "throw only when the
|
|
// environment says production", which meant a deployment that set no
|
|
// marker at all — which is what the real one did — took the developer
|
|
// branch and logged live codes. Absence is now a refusal.
|
|
test('nothing configured and nothing asked for is a refusal, not a log', async () => {
|
|
await expect(sendVerificationCode({}, 'ada@example.com', '123456')).rejects.toThrow(
|
|
/not configured/
|
|
);
|
|
});
|
|
|
|
test('a variable left holding something other than true does not switch logging on', async () => {
|
|
await expect(
|
|
sendVerificationCode({ EMAIL_DEV_LOG: 'false' }, 'ada@example.com', '123456')
|
|
).rejects.toThrow(/not configured/);
|
|
});
|
|
|
|
test('half a mailer is an error rather than a fallback', async () => {
|
|
await expect(
|
|
sendVerificationCode(
|
|
{ EMAIL_SEND_URL: 'https://mail.example.com/send', EMAIL_DEV_LOG: 'true' },
|
|
'ada@example.com',
|
|
'123456'
|
|
)
|
|
).rejects.toThrow(/half configured/);
|
|
});
|
|
|
|
test('a configured mailer is called with the address and the code', async () => {
|
|
let seen: { url: string; body: any; auth: string | null } | null = null;
|
|
const original = globalThis.fetch;
|
|
globalThis.fetch = (async (url: any, init: any) => {
|
|
seen = {
|
|
url: String(url),
|
|
body: JSON.parse(init.body),
|
|
auth: new Headers(init.headers).get('authorization')
|
|
};
|
|
return new Response('{}', { status: 200 });
|
|
}) as unknown as typeof fetch;
|
|
|
|
try {
|
|
await sendVerificationCode(
|
|
{
|
|
EMAIL_SEND_URL: 'https://mail.example.com/send',
|
|
EMAIL_API_KEY: 'key',
|
|
EMAIL_FROM: 'hello@nestri.io'
|
|
},
|
|
'ada@example.com',
|
|
'123456'
|
|
);
|
|
} finally {
|
|
globalThis.fetch = original;
|
|
}
|
|
|
|
expect(seen!.url).toBe('https://mail.example.com/send');
|
|
expect(seen!.auth).toBe('Bearer key');
|
|
expect(seen!.body.to).toEqual(['ada@example.com']);
|
|
expect(seen!.body.from).toBe('hello@nestri.io');
|
|
expect(seen!.body.text).toContain('123456');
|
|
});
|
|
|
|
test('a configured mailer sends even when dev logging is on', async () => {
|
|
let called = false;
|
|
const original = globalThis.fetch;
|
|
globalThis.fetch = (async () => {
|
|
called = true;
|
|
return new Response('{}', { status: 200 });
|
|
}) as unknown as typeof fetch;
|
|
|
|
try {
|
|
await sendVerificationCode(
|
|
{
|
|
EMAIL_SEND_URL: 'https://mail.example.com/send',
|
|
EMAIL_API_KEY: 'key',
|
|
EMAIL_FROM: 'hello@nestri.io',
|
|
EMAIL_DEV_LOG: 'true'
|
|
},
|
|
'ada@example.com',
|
|
'123456'
|
|
);
|
|
} finally {
|
|
globalThis.fetch = original;
|
|
}
|
|
|
|
expect(called).toBe(true);
|
|
});
|
|
|
|
test('a refusal from the mailer is not swallowed', async () => {
|
|
const original = globalThis.fetch;
|
|
globalThis.fetch = (async () =>
|
|
new Response('over quota', { status: 429 })) as unknown as typeof fetch;
|
|
try {
|
|
await expect(
|
|
sendVerificationCode(
|
|
{
|
|
EMAIL_SEND_URL: 'https://mail.example.com/send',
|
|
EMAIL_API_KEY: 'key',
|
|
EMAIL_FROM: 'hello@nestri.io'
|
|
},
|
|
'ada@example.com',
|
|
'123456'
|
|
)
|
|
).rejects.toThrow(/over quota/);
|
|
} finally {
|
|
globalThis.fetch = original;
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('the worker itself', () => {
|
|
// Cheap, and it catches the thing a type check cannot: the sign-in screen
|
|
// lives in a `.tsx` file, and whether that file can be imported across a
|
|
// package boundary at run time is decided by the package's export map
|
|
// rather than by the compiler.
|
|
test('loads, with every provider it wires resolvable', async () => {
|
|
const worker = await import('../src/index.js');
|
|
expect(typeof worker.default.fetch).toBe('function');
|
|
});
|
|
});
|