mirror of
https://github.com/documenso/documenso.git
synced 2026-10-02 17:54:28 +10:00
209 lines
6.9 KiB
TypeScript
209 lines
6.9 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
|
|
import type { TBulkSendCsvError, TValidateBulkSendCsvResult } from './validate-bulk-send-csv';
|
|
import { validateBulkSendCsv } from './validate-bulk-send-csv';
|
|
|
|
const buildCsv = (headers: string[], rows: string[][]) =>
|
|
[headers.join(','), ...rows.map((row) => row.join(','))].join('\n');
|
|
|
|
const expectFailure = (result: TValidateBulkSendCsvResult): TBulkSendCsvError => {
|
|
if (result.success) {
|
|
throw new Error('Expected validation to fail, but it passed');
|
|
}
|
|
|
|
return result.error;
|
|
};
|
|
|
|
describe('validateBulkSendCsv', () => {
|
|
describe('valid CSVs', () => {
|
|
it('returns the parsed rows for a valid CSV', () => {
|
|
const csvContent = buildCsv(
|
|
['recipient_1_email', 'recipient_1_name'],
|
|
[
|
|
['alice@example.com', 'Alice'],
|
|
['bob@example.com', 'Bob'],
|
|
],
|
|
);
|
|
|
|
const result = validateBulkSendCsv({ csvContent, recipientCount: 1 });
|
|
|
|
expect(result).toEqual({
|
|
success: true,
|
|
data: [
|
|
{ recipient_1_email: 'alice@example.com', recipient_1_name: 'Alice' },
|
|
{ recipient_1_email: 'bob@example.com', recipient_1_name: 'Bob' },
|
|
],
|
|
});
|
|
});
|
|
|
|
it('allows an empty string email so template defaults can be used', () => {
|
|
const csvContent = buildCsv(['recipient_1_email', 'recipient_1_name'], [['', 'Alice']]);
|
|
|
|
const result = validateBulkSendCsv({ csvContent, recipientCount: 1 });
|
|
|
|
expect(result.success).toBe(true);
|
|
});
|
|
|
|
it('allows the optional name column to be omitted entirely', () => {
|
|
const csvContent = buildCsv(['recipient_1_email'], [['alice@example.com']]);
|
|
|
|
const result = validateBulkSendCsv({ csvContent, recipientCount: 1 });
|
|
|
|
expect(result.success).toBe(true);
|
|
});
|
|
|
|
it('allows unknown extra columns', () => {
|
|
const csvContent = buildCsv(['recipient_1_email', 'unrelated_column'], [['alice@example.com', 'anything']]);
|
|
|
|
const result = validateBulkSendCsv({ csvContent, recipientCount: 1 });
|
|
|
|
expect(result.success).toBe(true);
|
|
});
|
|
|
|
it('validates columns for every configured recipient', () => {
|
|
const csvContent = buildCsv(
|
|
['recipient_1_email', 'recipient_2_email'],
|
|
[['alice@example.com', 'bob@example.com']],
|
|
);
|
|
|
|
const result = validateBulkSendCsv({ csvContent, recipientCount: 2 });
|
|
|
|
expect(result.success).toBe(true);
|
|
});
|
|
|
|
it('allows exactly the maximum number of rows', () => {
|
|
const csvContent = buildCsv(
|
|
['recipient_1_email'],
|
|
Array.from({ length: 100 }, (_, index) => [`user${index}@example.com`]),
|
|
);
|
|
|
|
const result = validateBulkSendCsv({ csvContent, recipientCount: 1 });
|
|
|
|
expect(result.success).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('PARSE_ERROR', () => {
|
|
it('rejects a CSV that cannot be parsed', () => {
|
|
const csvContent = 'recipient_1_email\n"unclosed quote';
|
|
|
|
const error = expectFailure(validateBulkSendCsv({ csvContent, recipientCount: 1 }));
|
|
|
|
expect(error).toEqual({ type: 'PARSE_ERROR' });
|
|
});
|
|
|
|
it('rejects a CSV with inconsistent column counts', () => {
|
|
const csvContent = buildCsv(
|
|
['recipient_1_email', 'recipient_1_name'],
|
|
[['alice@example.com', 'Alice', 'unexpected-extra-value']],
|
|
);
|
|
|
|
const error = expectFailure(validateBulkSendCsv({ csvContent, recipientCount: 1 }));
|
|
|
|
expect(error).toEqual({ type: 'PARSE_ERROR' });
|
|
});
|
|
});
|
|
|
|
describe('EMPTY', () => {
|
|
it('rejects an empty file', () => {
|
|
const error = expectFailure(validateBulkSendCsv({ csvContent: '', recipientCount: 1 }));
|
|
|
|
expect(error).toEqual({ type: 'EMPTY' });
|
|
});
|
|
|
|
it('rejects a CSV containing only a header row', () => {
|
|
const csvContent = buildCsv(['recipient_1_email', 'recipient_1_name'], []);
|
|
|
|
const error = expectFailure(validateBulkSendCsv({ csvContent, recipientCount: 1 }));
|
|
|
|
expect(error).toEqual({ type: 'EMPTY' });
|
|
});
|
|
});
|
|
|
|
describe('ROW_LIMIT_EXCEEDED', () => {
|
|
it('rejects a CSV exceeding the default limit of 100 rows', () => {
|
|
const csvContent = buildCsv(
|
|
['recipient_1_email'],
|
|
Array.from({ length: 101 }, (_, index) => [`user${index}@example.com`]),
|
|
);
|
|
|
|
const error = expectFailure(validateBulkSendCsv({ csvContent, recipientCount: 1 }));
|
|
|
|
expect(error).toEqual({ type: 'ROW_LIMIT_EXCEEDED', rowCount: 101, maxRows: 100 });
|
|
});
|
|
|
|
it('respects a custom maxRows option', () => {
|
|
const csvContent = buildCsv(['recipient_1_email'], [['alice@example.com'], ['bob@example.com']]);
|
|
|
|
const error = expectFailure(validateBulkSendCsv({ csvContent, recipientCount: 1, maxRows: 1 }));
|
|
|
|
expect(error).toEqual({ type: 'ROW_LIMIT_EXCEEDED', rowCount: 2, maxRows: 1 });
|
|
});
|
|
});
|
|
|
|
describe('MISSING_COLUMNS', () => {
|
|
it('rejects a CSV missing a required email column', () => {
|
|
const csvContent = buildCsv(['recipient_1_name'], [['Alice']]);
|
|
|
|
const error = expectFailure(validateBulkSendCsv({ csvContent, recipientCount: 1 }));
|
|
|
|
expect(error).toEqual({ type: 'MISSING_COLUMNS', missingColumns: ['recipient_1_email'] });
|
|
});
|
|
|
|
it('reports every missing column', () => {
|
|
const csvContent = buildCsv(['recipient_1_email'], [['alice@example.com']]);
|
|
|
|
const error = expectFailure(validateBulkSendCsv({ csvContent, recipientCount: 3 }));
|
|
|
|
expect(error).toEqual({
|
|
type: 'MISSING_COLUMNS',
|
|
missingColumns: ['recipient_2_email', 'recipient_3_email'],
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('INVALID_RECIPIENTS', () => {
|
|
it('rejects a CSV containing an invalid email', () => {
|
|
const csvContent = buildCsv(['recipient_1_email'], [['not-an-email']]);
|
|
|
|
const error = expectFailure(validateBulkSendCsv({ csvContent, recipientCount: 1 }));
|
|
|
|
expect(error).toMatchObject({
|
|
type: 'INVALID_RECIPIENTS',
|
|
rowErrors: [{ row: 1, column: 'recipient_1_email' }],
|
|
});
|
|
});
|
|
|
|
it('references the offending row and column', () => {
|
|
const csvContent = buildCsv(['recipient_1_email'], [['alice@example.com'], ['not-an-email']]);
|
|
|
|
const error = expectFailure(validateBulkSendCsv({ csvContent, recipientCount: 1 }));
|
|
|
|
expect(error).toMatchObject({
|
|
type: 'INVALID_RECIPIENTS',
|
|
rowErrors: [{ row: 2, column: 'recipient_1_email' }],
|
|
});
|
|
});
|
|
|
|
it('aggregates errors across multiple rows and recipients', () => {
|
|
const csvContent = buildCsv(
|
|
['recipient_1_email', 'recipient_2_email'],
|
|
[
|
|
['not-an-email', 'bob@example.com'],
|
|
['alice@example.com', 'also-not-an-email'],
|
|
],
|
|
);
|
|
|
|
const error = expectFailure(validateBulkSendCsv({ csvContent, recipientCount: 2 }));
|
|
|
|
expect(error).toMatchObject({
|
|
type: 'INVALID_RECIPIENTS',
|
|
rowErrors: [
|
|
{ row: 1, column: 'recipient_1_email' },
|
|
{ row: 2, column: 'recipient_2_email' },
|
|
],
|
|
});
|
|
});
|
|
});
|
|
});
|