test: email sending e2e (#4130)

This commit is contained in:
Alex Yang
2023-09-02 13:13:59 -05:00
committed by GitHub
parent 3d8a91aab0
commit a2623e1352
9 changed files with 159 additions and 0 deletions

View File

@@ -3,3 +3,4 @@ NEXTAUTH_URL="http://localhost:8080"
OAUTH_EMAIL_SENDER="noreply@toeverything.info"
OAUTH_EMAIL_LOGIN=""
OAUTH_EMAIL_PASSWORD=""
ENABLE_LOCAL_EMAIL="true"

View File

@@ -0,0 +1,18 @@
import { createTransport } from 'nodemailer';
const transport = createTransport({
host: '0.0.0.0',
port: 1025,
secure: false,
auth: {
user: 'himself65',
pass: '123456',
},
});
await transport.sendMail({
from: 'noreply@toeverything.info',
to: 'himself65@outlook.com',
subject: 'test',
html: `<div>hello world</div>`,
});

View File

@@ -308,6 +308,11 @@ export interface AFFiNEConfig {
}
>
>;
/**
* whether to use local email service to send email
* local debug only
*/
localEmail: boolean;
email: {
server: string;
port: number;

View File

@@ -86,6 +86,7 @@ export const getDefaultAFFiNEConfig: () => AFFiNEConfig = () => {
'doc.manager.experimentalMergeWithJwstCodec',
'boolean',
],
ENABLE_LOCAL_EMAIL: ['auth.localEmail', 'boolean'],
} satisfies AFFiNEConfig['ENV_MAP'],
affineEnv: 'dev',
get affine() {
@@ -152,6 +153,7 @@ export const getDefaultAFFiNEConfig: () => AFFiNEConfig = () => {
return this.privateKey;
},
oauthProviders: {},
localEmail: false,
email: {
server: 'smtp.gmail.com',
port: 465,

View File

@@ -15,6 +15,17 @@ export const MAILER: FactoryProvider<
> = {
provide: MAILER_SERVICE,
useFactory: (config: Config) => {
if (config.auth.localEmail) {
return createTransport({
host: '0.0.0.0',
port: 1025,
secure: false,
auth: {
user: config.auth.email.login,
pass: config.auth.email.password,
},
});
}
return createTransport({
service: 'gmail',
auth: {

View File

@@ -4,3 +4,7 @@ import 'dotenv/config';
import { getDefaultAFFiNEConfig } from './config/default';
globalThis.AFFiNE = getDefaultAFFiNEConfig();
if (process.env.NODE_ENV === 'development') {
console.log('AFFiNE Config:', globalThis.AFFiNE);
}

View File

@@ -0,0 +1,105 @@
/// <reference types="../global.d.ts" />
// This test case is for testing the mailer service.
// Please use local SMTP server for testing.
// See: https://github.com/mailhog/MailHog
import { Test, TestingModule } from '@nestjs/testing';
import { PrismaClient } from '@prisma/client';
import test from 'ava';
import { ConfigModule } from '../config';
import { GqlModule } from '../graphql.module';
import { MetricsModule } from '../metrics';
import { AuthModule } from '../modules/auth';
import { AuthService } from '../modules/auth/service';
import { PrismaModule } from '../prisma';
import { RateLimiterModule } from '../throttler';
let auth: AuthService;
let module: TestingModule;
let skip = false;
test.before(async () => {
try {
const response = await fetch('http://localhost:8025/api/v2/messages');
if (!response.ok && !process.env.CI) {
console.warn('local mail not found, skip the mailer.e2e.ts');
skip = true;
}
} catch (error) {
if (
error instanceof Error &&
(error.cause as any)?.code === 'ECONNREFUSED' &&
!process.env.CI
) {
console.warn('local mail not found, skip the mailer.e2e.ts');
skip = true;
}
}
});
// cleanup database before each test
test.beforeEach(async () => {
const client = new PrismaClient();
await client.$connect();
await client.user.deleteMany({});
});
test.beforeEach(async () => {
module = await Test.createTestingModule({
imports: [
ConfigModule.forRoot({
auth: {
accessTokenExpiresIn: 1,
refreshTokenExpiresIn: 1,
leeway: 1,
},
}),
PrismaModule,
GqlModule,
AuthModule,
MetricsModule,
RateLimiterModule,
],
}).compile();
auth = module.get(AuthService);
});
test.afterEach(async () => {
await module.close();
});
const getCurrentMailMessageCount = async () => {
const response = await fetch('http://localhost:8025/api/v2/messages');
const data = await response.json();
return data.total;
};
const getLatestMailMessage = async () => {
const response = await fetch('http://localhost:8025/api/v2/messages');
const data = await response.json();
return data.items[0];
};
test('should include callbackUrl in sending email', async t => {
if (skip) {
return t.pass();
}
await auth.signUp('Alex Yang', 'alexyang@example.org', '123456');
for (const fn of [
'sendSetPasswordEmail',
'sendChangeEmail',
'sendChangePasswordEmail',
] as const) {
const prev = await getCurrentMailMessageCount();
await auth[fn]('alexyang@example.org', 'https://test.com/callback');
const current = await getCurrentMailMessageCount();
const mail = await getLatestMailMessage();
t.regex(
mail.Content.Body,
/https:\/\/test.com\/callback/,
`should include callbackUrl when calling ${fn}`
);
t.is(current, prev + 1, `calling ${fn}`);
}
return;
});