mailgun-optin-cli
Version:
CLI tool for sending opt-in confirmation emails via Mailgun
213 lines (177 loc) • 6.42 kB
JavaScript
import { expect } from 'chai';
import { promises as fs } from 'fs';
import { join } from 'path';
import { parseCSV, validateEmail, validateCSVStructure } from '../src/csv-parser.js';
describe('CSV Parser Module', () => {
const testDataDir = join(process.cwd(), 'test', 'fixtures');
before(async () => {
// Create test fixtures directory
await fs.mkdir(testDataDir, { recursive: true });
});
after(async () => {
// Clean up test fixtures
try {
await fs.rm(testDataDir, { recursive: true, force: true });
} catch {
// Ignore cleanup errors
}
});
describe('validateEmail', () => {
it('should validate correct email addresses', () => {
const validEmails = [
'test@example.com',
'user.name@domain.co.uk',
'user+tag@example.org',
'firstname.lastname@company.com',
'email@123.123.123.123', // IP address
'user@domain-name.com',
];
validEmails.forEach(email => {
expect(validateEmail(email)).to.be.true;
});
});
it('should reject invalid email addresses', () => {
const invalidEmails = [
'invalid-email',
'@example.com',
'user@',
'user..name@example.com',
'user@.com',
'user@domain.',
'',
null,
undefined,
'user name@example.com', // space
'user@domain..com', // double dot
];
invalidEmails.forEach(email => {
expect(validateEmail(email)).to.be.false;
});
});
});
describe('validateCSVStructure', () => {
it('should validate CSV with required email column', () => {
const validData = [
{ email: 'test@example.com' },
{ email: 'user@domain.com', first_name: 'John' },
{ email: 'another@test.com', first_name: 'Jane', last_name: 'Doe' },
];
expect(() => validateCSVStructure(validData)).to.not.throw();
});
it('should throw error for CSV without email column', () => {
const invalidData = [
{ name: 'John', surname: 'Doe' },
{ first_name: 'Jane', last_name: 'Smith' },
];
expect(() => validateCSVStructure(invalidData)).to.throw(
'CSV must contain an "email" column',
);
});
it('should throw error for empty CSV data', () => {
expect(() => validateCSVStructure([])).to.throw('CSV file is empty');
});
it('should throw error for invalid email addresses in data', () => {
const invalidData = [
{ email: 'valid@example.com' },
{ email: 'invalid-email' },
{ email: 'another@valid.com' },
];
expect(() => validateCSVStructure(invalidData)).to.throw(
'Invalid email address found: invalid-email',
);
});
});
describe('parseCSV', () => {
beforeEach(async () => {
// Create test CSV files for each test
const validCSV = `email,first_name,last_name
test@example.com,John,Doe
user@domain.com,Jane,Smith
another@test.com,Bob,Johnson`;
const invalidEmailCSV = `email,first_name,last_name
test@example.com,John,Doe
invalid-email,Jane,Smith
another@test.com,Bob,Johnson`;
const noEmailColumnCSV = `name,surname
John,Doe
Jane,Smith`;
const emptyCSV = 'email,first_name,last_name';
await fs.writeFile(join(testDataDir, 'valid.csv'), validCSV);
await fs.writeFile(join(testDataDir, 'invalid-email.csv'), invalidEmailCSV);
await fs.writeFile(join(testDataDir, 'no-email-column.csv'), noEmailColumnCSV);
await fs.writeFile(join(testDataDir, 'empty.csv'), emptyCSV);
});
it('should parse valid CSV file successfully', async () => {
const filePath = join(testDataDir, 'valid.csv');
const result = await parseCSV(filePath);
expect(result).to.be.an('array');
expect(result).to.have.length(3);
expect(result[0]).to.deep.equal({
email: 'test@example.com',
first_name: 'John',
last_name: 'Doe',
});
expect(result[1]).to.deep.equal({
email: 'user@domain.com',
first_name: 'Jane',
last_name: 'Smith',
});
});
it('should handle CSV with only email column', async () => {
const emailOnlyCSV = `email
test@example.com
user@domain.com`;
await fs.writeFile(join(testDataDir, 'email-only.csv'), emailOnlyCSV);
const filePath = join(testDataDir, 'email-only.csv');
const result = await parseCSV(filePath);
expect(result).to.have.length(2);
expect(result[0]).to.deep.equal({ email: 'test@example.com' });
});
it('should throw error for non-existent file', async () => {
const filePath = join(testDataDir, 'non-existent.csv');
try {
await parseCSV(filePath);
expect.fail('Should have thrown an error');
} catch (error) {
expect(error.message).to.include('ENOENT');
}
});
it('should throw error for CSV with invalid email addresses', async () => {
const filePath = join(testDataDir, 'invalid-email.csv');
try {
await parseCSV(filePath);
expect.fail('Should have thrown an error');
} catch (error) {
expect(error.message).to.include('Invalid email address found: invalid-email');
}
});
it('should throw error for CSV without email column', async () => {
const filePath = join(testDataDir, 'no-email-column.csv');
try {
await parseCSV(filePath);
expect.fail('Should have thrown an error');
} catch (error) {
expect(error.message).to.include('CSV must contain an "email" column');
}
});
it('should throw error for empty CSV file', async () => {
const filePath = join(testDataDir, 'empty.csv');
try {
await parseCSV(filePath);
expect.fail('Should have thrown an error');
} catch (error) {
expect(error.message).to.include('CSV file is empty');
}
});
it('should handle CSV with extra whitespace in emails', async () => {
const whitespaceCSV = `email,first_name
test@example.com ,John
user@domain.com,Jane `;
await fs.writeFile(join(testDataDir, 'whitespace.csv'), whitespaceCSV);
const filePath = join(testDataDir, 'whitespace.csv');
const result = await parseCSV(filePath);
expect(result[0].email).to.equal('test@example.com');
expect(result[1].email).to.equal('user@domain.com');
});
});
});