feature: Implement Gmail message fetching and Excel sheet name truncation

This commit is contained in:
Keith Solomon
2026-05-17 10:59:44 -05:00
parent cb568597dc
commit 379526114c
9 changed files with 289 additions and 163 deletions
+39
View File
@@ -0,0 +1,39 @@
import { mkdtemp, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import XLSX from 'xlsx';
import { ExcelWriter } from '../src/output/excel.js';
let dir = '';
beforeEach(async () => {
dir = await mkdtemp(join(tmpdir(), 'nlc-excel-'));
});
afterEach(async () => {
await rm(dir, { force: true, recursive: true });
});
describe('ExcelWriter', () => {
it('truncates newsletter sheet names to the Excel 31-character limit', async () => {
const path = join(dir, 'catalog.xlsx');
const newsletter = 'A Very Long Newsletter Name That Exceeds The Excel Limit';
await new ExcelWriter(path).write({
rows: [
{
'Source Newsletter': newsletter,
Title: 'Post',
'Link URL': 'https://example.com'
}
],
sponsors: [],
deadLinks: []
});
const workbook = XLSX.readFile(path);
expect(workbook.SheetNames[0]).toBe('A Very Long Newsletter Name Tha');
expect(workbook.SheetNames[0].length).toBe(31);
});
});
+79
View File
@@ -0,0 +1,79 @@
import { describe, expect, it } from 'vitest';
import { buildBrowserCommand, GmailClient } from '../src/gmail/client.js';
describe('GmailClient', () => {
it('uses PowerShell to open Windows OAuth URLs without splitting query parameters', () => {
const url =
'https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&response_type=code';
expect(buildBrowserCommand(url, 'win32')).toEqual({
file: 'powershell.exe',
args: ['-NoProfile', '-Command', 'Start-Process -FilePath $args[0]', url]
});
});
it('loads HTML messages from the configured Gmail label', async () => {
const calls: string[] = [];
const gmail = {
users: {
labels: {
list: async () => ({
data: { labels: [{ id: 'Label_1', name: 'Newsletters' }] }
})
},
messages: {
list: async (_params: unknown) => {
calls.push('list');
return { data: { messages: [{ id: 'msg-1' }] } };
},
get: async () => {
calls.push('get');
return {
data: {
id: 'msg-1',
payload: {
headers: [
{ name: 'Message-ID', value: '<msg-1@example.com>' },
{ name: 'From', value: 'Weekly <weekly@example.com>' },
{ name: 'Date', value: 'Sat, 16 May 2026 10:00:00 -0500' }
],
parts: [
{
mimeType: 'text/html',
body: {
data: Buffer.from(
'<h2>Python</h2><a href="https://example.com">Post</a>'
).toString('base64url')
}
}
]
}
}
};
}
}
}
};
const messages = await new GmailClient(gmail).fetchMessages('Newsletters', { maxResults: 5 });
expect(calls).toEqual(['list', 'get']);
expect(messages).toEqual([
{
id: 'msg-1',
messageId: '<msg-1@example.com>',
from: 'Weekly <weekly@example.com>',
date: '2026-05-16T15:00:00.000Z',
subject: '',
html: '<h2>Python</h2><a href="https://example.com">Post</a>',
headers: {
date: 'Sat, 16 May 2026 10:00:00 -0500',
from: 'Weekly <weekly@example.com>',
listId: undefined,
messageId: '<msg-1@example.com>',
subject: ''
}
}
]);
});
});