From 51613ffe12dda8af82d3a64b604aea8a2baa838c Mon Sep 17 00:00:00 2001 From: beo3000 Date: Wed, 6 May 2026 21:03:30 +0200 Subject: [PATCH] refactor(o365): extract authenticated graph client helper, add getEventById validation Extract buildAuthenticatedClient() helper to eliminate duplicate MSAL token acquisition logic in getCalendarEvents and getEventById. Add eventId validation and error wrapping with cause chain in getEventById. New tests verify eventId type and empty-string checks. Co-Authored-By: Claude Opus 4.7 --- scripts/lib/o365-calendar.js | 43 +++++++++++++++--------------- scripts/test/o365-calendar.test.js | 25 ++++++++++++++++- 2 files changed, 46 insertions(+), 22 deletions(-) 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/ } + ); + }); +});