fix(meet): mint unique correlationId per upcoming-meeting join (#4349)

This commit is contained in:
Cyrus Gray
2026-07-01 14:32:03 +05:30
committed by GitHub
parent 7a67f6ca35
commit 98925dbdbd
2 changed files with 49 additions and 7 deletions
+15 -2
View File
@@ -346,7 +346,20 @@ export function UpcomingTable({
const handleJoin = async (meeting: UpcomingMeeting) => {
if (!meeting.meet_url) return;
const platform = meeting.platform ?? inferPlatformFromUrl(meeting.meet_url) ?? undefined;
log('[upcoming] joining %s platform=%s', meeting.calendar_event_id, platform);
// Mint a fresh correlation id per join. It becomes the call record's
// `request_id` (recent-calls list key + per-call detail filename), so it
// MUST be unique per join — reusing the deterministic `calendar_event_id`
// collapsed re-joins of the same event onto one request_id, overwriting the
// earlier call's transcript and double-highlighting the history row (#4338).
// `calendar_event_id` stays the dedup/policy key only (handleJoinPolicyChange,
// setJoiningId), mirroring the background auto-join in calendar.rs.
const correlationId = crypto.randomUUID();
log(
'[upcoming] joining %s platform=%s correlationId=%s',
meeting.calendar_event_id,
platform,
correlationId
);
setJoiningId(meeting.calendar_event_id);
try {
await joinMeetViaBackendBot({
@@ -356,7 +369,7 @@ export function UpcomingTable({
systemPrompt: personaDescription || undefined,
mascotId: mascotId || undefined,
listenOnly: true,
correlationId: meeting.calendar_event_id,
correlationId,
riveColors,
});
} catch (err) {
@@ -183,12 +183,41 @@ describe('UpcomingTable', () => {
await waitFor(() => expect(joinMock).toHaveBeenCalledOnce());
expect(joinMock).toHaveBeenCalledWith(
expect.objectContaining({
meetUrl: 'https://meet.google.com/abc-def-ghi',
listenOnly: true,
correlationId: 'evt-1',
})
expect.objectContaining({ meetUrl: 'https://meet.google.com/abc-def-ghi', listenOnly: true })
);
// The correlation id MUST be a freshly-minted unique id, NOT the
// deterministic calendar_event_id — reusing the event id collapsed
// re-joins onto one request_id (#4338).
const { correlationId } = joinMock.mock.calls[0][0] as { correlationId: string };
expect(correlationId).toBeTruthy();
expect(correlationId).not.toBe('evt-1');
});
it('mints a unique correlationId per join so re-joining the same event does not collide (#4338)', async () => {
joinMock.mockResolvedValue({
meetUrl: 'https://meet.google.com/abc-def-ghi',
platform: 'gmeet',
});
// Same meeting (same calendar_event_id) returned across reloads.
listMock.mockResolvedValue([makeMeeting()]);
renderWithProviders(<UpcomingTable />);
const joinBtn = await screen.findByRole('button', { name: /^join$/i });
fireEvent.click(joinBtn);
await waitFor(() => expect(joinMock).toHaveBeenCalledOnce());
// handleJoin disables the row via `joiningId` until its `finally` runs;
// wait for the button to re-enable before the second click so it isn't
// swallowed by the disabled state (timing-dependent otherwise).
await waitFor(() => expect((joinBtn as HTMLButtonElement).disabled).toBe(false));
fireEvent.click(joinBtn);
await waitFor(() => expect(joinMock).toHaveBeenCalledTimes(2));
const first = (joinMock.mock.calls[0][0] as { correlationId: string }).correlationId;
const second = (joinMock.mock.calls[1][0] as { correlationId: string }).correlationId;
expect(first).not.toBe('evt-1');
expect(second).not.toBe('evt-1');
// Two joins of the same calendar event must yield distinct correlation ids.
expect(first).not.toBe(second);
});
it('does not show a join button for meetings without a conferencing URL', async () => {