diff --git a/scripts/lib/o365-calendar.js b/scripts/lib/o365-calendar.js index 38f341b..4122e43 100644 --- a/scripts/lib/o365-calendar.js +++ b/scripts/lib/o365-calendar.js @@ -58,7 +58,7 @@ function formatEventChoice(meeting) { return `📅 ${meeting.date} ${meeting.start}-${meeting.end} ${meeting.title}`; } -async function getCalendarEvents(daysAhead = 7) { +async function buildAuthenticatedClient() { const env = loadEnv(); const cca = new ConfidentialClientApplication({ @@ -73,9 +73,14 @@ async function getCalendarEvents(daysAhead = 7) { scopes: ['https://graph.microsoft.com/.default'] }); - const client = Client.init({ + return Client.init({ authProvider: (done) => done(null, tokenResponse.accessToken) }); +} + +async function getCalendarEvents(daysAhead = 7) { + const env = loadEnv(); + const client = await buildAuthenticatedClient(); const now = new Date(); const future = new Date(now); @@ -102,26 +107,22 @@ function extractJoinUrlFromBody(body) { } async function getEventById(eventId) { - const env = loadEnv(); - const cca = new ConfidentialClientApplication({ - auth: { - clientId: env.AZURE_CLIENT_ID, - clientSecret: env.AZURE_CLIENT_SECRET, - authority: `https://login.microsoftonline.com/${env.AZURE_TENANT_ID}` - } - }); - const tokenResponse = await cca.acquireTokenByClientCredential({ - scopes: ['https://graph.microsoft.com/.default'] - }); - const client = Client.init({ - authProvider: (done) => done(null, tokenResponse.accessToken) - }); + if (typeof eventId !== 'string' || !eventId.trim()) { + throw new TypeError('eventId must be a non-empty string'); + } - const event = await client - .api(`/users/${env.AZURE_USER_EMAIL}/events/${eventId}`) - .select('id,subject,start,end,body,attendees,seriesMasterId,onlineMeeting') - .get(); - return event; + try { + const env = loadEnv(); + const client = await buildAuthenticatedClient(); + + const event = await client + .api(`/users/${env.AZURE_USER_EMAIL}/events/${eventId}`) + .select('id,subject,start,end,body,attendees,seriesMasterId,onlineMeeting') + .get(); + return event; + } catch (cause) { + throw new Error(`Failed to fetch event ${eventId}: ${cause.message}`, { cause }); + } } module.exports = { diff --git a/scripts/test/o365-calendar.test.js b/scripts/test/o365-calendar.test.js index c4d5731..d173227 100644 --- a/scripts/test/o365-calendar.test.js +++ b/scripts/test/o365-calendar.test.js @@ -81,7 +81,7 @@ describe('isRecurring', () => { }); }); -const { extractJoinUrlFromBody } = require('../lib/o365-calendar.js'); +const { extractJoinUrlFromBody, getEventById } = require('../lib/o365-calendar.js'); describe('extractJoinUrlFromBody', () => { it('extracts teams meeting join URL from body html', () => { @@ -96,3 +96,26 @@ describe('extractJoinUrlFromBody', () => { assert.equal(extractJoinUrlFromBody('

nothing

'), null); }); }); + +describe('getEventById', () => { + it('throws TypeError when eventId is empty string', async () => { + await assert.rejects( + () => getEventById(''), + { name: 'TypeError', message: /eventId must be a non-empty string/ } + ); + }); + + it('throws TypeError when eventId is not a string', async () => { + await assert.rejects( + () => getEventById(null), + { name: 'TypeError', message: /eventId must be a non-empty string/ } + ); + }); + + it('throws TypeError when eventId is whitespace only', async () => { + await assert.rejects( + () => getEventById(' '), + { name: 'TypeError', message: /eventId must be a non-empty string/ } + ); + }); +});