feat(mail): clarify mailgun configuration feedback

This commit is contained in:
William Valentin
2025-09-23 10:30:12 -07:00
parent 71c37f4b7b
commit e7dbe30763
3 changed files with 246 additions and 42 deletions

View File

@@ -1,15 +1,17 @@
// Mock the mailgun config before any imports
const mockGetMailgunConfig = jest.fn().mockReturnValue({
apiKey: 'test-api-key',
domain: 'test.mailgun.org',
baseUrl: 'https://api.mailgun.net/v3',
fromName: 'Test App',
fromEmail: 'test@example.com',
});
jest.mock('../mailgun.config', () => {
const defaultConfig = {
apiKey: 'test-api-key',
domain: 'test.mailgun.org',
baseUrl: 'https://api.mailgun.net/v3',
fromName: 'Test App',
fromEmail: 'test@example.com',
};
jest.mock('../mailgun.config', () => ({
getMailgunConfig: mockGetMailgunConfig,
}));
return {
getMailgunConfig: jest.fn(() => defaultConfig),
};
});
// Mock the app config
jest.mock('../../config/unified.config', () => ({
@@ -18,6 +20,7 @@ jest.mock('../../config/unified.config', () => ({
baseUrl: 'http://localhost:3000',
},
},
getAppConfig: jest.fn(() => ({ baseUrl: 'http://localhost:3000' })),
}));
// Mock global fetch and related APIs
@@ -32,10 +35,28 @@ global.btoa = jest
// Import the service after mocks are set up
import { MailgunService } from '../mailgun.service';
import { getMailgunConfig } from '../mailgun.config';
import { logger } from '../logging';
const mockGetMailgunConfig = getMailgunConfig as jest.MockedFunction<
typeof getMailgunConfig
>;
mockGetMailgunConfig.mockReturnValue({
apiKey: 'test-api-key',
domain: 'test.mailgun.org',
baseUrl: 'https://api.mailgun.net/v3',
fromName: 'Test App',
fromEmail: 'test@example.com',
});
describe('MailgunService', () => {
let mockFetch: jest.MockedFunction<typeof fetch>;
let mockFormData: jest.MockedFunction<any>;
let warnSpy: jest.SpyInstance;
let infoSpy: jest.SpyInstance;
let errorSpy: jest.SpyInstance;
let debugSpy: jest.SpyInstance;
const mockConfig = {
apiKey: 'test-api-key',
@@ -47,10 +68,13 @@ describe('MailgunService', () => {
beforeEach(() => {
jest.clearAllMocks();
console.warn = jest.fn();
console.error = jest.fn();
mockFetch = fetch as jest.MockedFunction<typeof fetch>;
mockFormData = MockFormData;
warnSpy = jest.spyOn(logger, 'warn').mockImplementation(() => undefined);
infoSpy = jest.spyOn(logger, 'info').mockImplementation(() => undefined);
errorSpy = jest.spyOn(logger, 'error').mockImplementation(() => undefined);
debugSpy = jest.spyOn(logger, 'debug').mockImplementation(() => undefined);
});
describe('constructor', () => {
@@ -67,11 +91,28 @@ describe('MailgunService', () => {
new MailgunService();
expect(console.warn).toHaveBeenCalledWith(
'📧 Mailgun Service: Running in development mode (emails will be logged only)'
expect(warnSpy).toHaveBeenCalledWith(
'Mailgun running in development mode; emails will not be delivered',
'MAILGUN',
{
missingFields: [
'VITE_MAILGUN_API_KEY',
'VITE_MAILGUN_DOMAIN',
'VITE_MAILGUN_FROM_EMAIL',
],
domain: undefined,
}
);
expect(console.warn).toHaveBeenCalledWith(
'💡 To enable real emails, configure Mailgun credentials in .env.local'
expect(infoSpy).toHaveBeenCalledWith(
'To enable email delivery, configure Mailgun environment variables',
'MAILGUN',
{
requiredVariables: [
'VITE_MAILGUN_API_KEY',
'VITE_MAILGUN_DOMAIN',
'VITE_MAILGUN_FROM_EMAIL',
],
}
);
});
@@ -80,10 +121,15 @@ describe('MailgunService', () => {
new MailgunService();
expect(console.warn).toHaveBeenCalledWith(
'📧 Mailgun Service: Configured for production with domain:',
'test.mailgun.org'
expect(infoSpy).toHaveBeenCalledWith(
'Mailgun configured for delivery',
'MAILGUN',
{
domain: 'test.mailgun.org',
fromEmail: 'test@example.com',
}
);
expect(warnSpy).not.toHaveBeenCalled();
});
});
@@ -122,8 +168,9 @@ describe('MailgunService', () => {
}),
})
);
expect(console.warn).toHaveBeenCalledWith(
'📧 Email sent successfully via Mailgun:',
expect(infoSpy).toHaveBeenCalledWith(
'Email sent via Mailgun',
'MAILGUN',
{
to: 'test@example.com',
subject: 'Test Subject',
@@ -148,8 +195,10 @@ describe('MailgunService', () => {
const result = await service.sendEmail('test@example.com', template);
expect(result).toBe(false);
expect(console.error).toHaveBeenCalledWith(
'Email sending failed:',
expect(errorSpy).toHaveBeenCalledWith(
'Mailgun email send failed',
'MAILGUN',
{ domain: 'test.mailgun.org' },
expect.any(Error)
);
});
@@ -165,8 +214,10 @@ describe('MailgunService', () => {
const result = await service.sendEmail('test@example.com', template);
expect(result).toBe(false);
expect(console.error).toHaveBeenCalledWith(
'Email sending failed:',
expect(errorSpy).toHaveBeenCalledWith(
'Mailgun email send failed',
'MAILGUN',
{ domain: 'test.mailgun.org' },
expect.any(Error)
);
});
@@ -237,6 +288,56 @@ describe('MailgunService', () => {
expect.anything()
);
});
test('logs preview and skips send when configuration is missing', async () => {
const unconfiguredConfig = {
apiKey: undefined,
domain: undefined,
baseUrl: 'https://api.mailgun.net/v3',
fromName: 'Test App',
fromEmail: undefined,
};
mockGetMailgunConfig.mockReturnValue(unconfiguredConfig);
const unconfiguredService = new MailgunService();
const template = {
subject: 'Test Subject',
html: '<p>Test HTML</p>',
};
const result = await unconfiguredService.sendEmail(
'test@example.com',
template
);
expect(result).toBe(false);
expect(mockFetch).not.toHaveBeenCalled();
expect(warnSpy).toHaveBeenCalledWith(
'Skipping email send; Mailgun is not configured',
'MAILGUN',
expect.objectContaining({
to: 'test@example.com',
missingFields: [
'VITE_MAILGUN_API_KEY',
'VITE_MAILGUN_DOMAIN',
'VITE_MAILGUN_FROM_EMAIL',
],
preview: true,
})
);
expect(debugSpy).toHaveBeenCalledWith(
'Mailgun email preview',
'MAILGUN',
expect.objectContaining({
to: 'test@example.com',
subject: 'Test Subject',
html: '<p>Test HTML</p>',
})
);
mockGetMailgunConfig.mockReturnValue(mockConfig);
});
});
describe('sendVerificationEmail', () => {
@@ -360,6 +461,7 @@ describe('MailgunService', () => {
mode: 'production',
domain: 'test.mailgun.org',
fromEmail: 'test@example.com',
missingFields: [],
});
});
@@ -382,6 +484,11 @@ describe('MailgunService', () => {
mode: 'development',
domain: undefined,
fromEmail: undefined,
missingFields: [
'VITE_MAILGUN_API_KEY',
'VITE_MAILGUN_DOMAIN',
'VITE_MAILGUN_FROM_EMAIL',
],
});
});
@@ -404,6 +511,13 @@ describe('MailgunService', () => {
mode: 'development',
domain: '',
fromEmail: '',
missingFields: [
'VITE_MAILGUN_API_KEY',
'VITE_MAILGUN_DOMAIN',
'VITE_MAILGUN_FROM_EMAIL',
'VITE_MAILGUN_BASE_URL',
'VITE_MAILGUN_FROM_NAME',
],
});
});
});