8e5bdcc781
A `subscriptionRemoved` lifecycle notification was routed to `renewSubscription`, which PATCHes a subscription Microsoft has already deleted and always 404s ([TWENTY-SERVER-J1N](https://twenty-v7.sentry.io/issues/7604034376/), 884 events). Every sampled webhook event on that issue was `subscriptionRemoved`. Each lifecycle event now gets its own path: `subscriptionRemoved` recreates and resyncs the gap, `reauthorizationRequired` renews in place, `missed` resyncs, unrecognised events are logged and ignored. Provider errors are parsed into driver exception codes following the message-import drivers. Max retry for the renewal cron is deliberately left out and will follow separately. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23707?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> --------- Co-authored-by: neo773 <huzef@twenty.com>
85 lines
2.1 KiB
TypeScript
85 lines
2.1 KiB
TypeScript
import { type Subscription } from '@microsoft/microsoft-graph-types';
|
|
import { http, HttpResponse } from 'msw';
|
|
|
|
import { type MswHandler } from 'test/integration/utils/http-mock.util';
|
|
|
|
export type MicrosoftSubscriptionStore = {
|
|
created: Subscription[];
|
|
renewed: string[];
|
|
deleted: string[];
|
|
reset: () => void;
|
|
};
|
|
|
|
export const createMicrosoftSubscriptionStore =
|
|
(): MicrosoftSubscriptionStore => {
|
|
const store: MicrosoftSubscriptionStore = {
|
|
created: [],
|
|
renewed: [],
|
|
deleted: [],
|
|
reset: () => {
|
|
store.created = [];
|
|
store.renewed = [];
|
|
store.deleted = [];
|
|
},
|
|
};
|
|
|
|
return store;
|
|
};
|
|
|
|
const subscriptionId = (request: Request) =>
|
|
new URL(request.url).pathname.split('/').pop() ?? '';
|
|
|
|
const resourceNotFound = () =>
|
|
HttpResponse.json(
|
|
{
|
|
error: {
|
|
code: 'ResourceNotFound',
|
|
message: 'The object was not found.',
|
|
},
|
|
},
|
|
{ status: 404 },
|
|
);
|
|
|
|
export const microsoftWebhookSubscriptionHandlers = (
|
|
store: MicrosoftSubscriptionStore,
|
|
{ renewalFails = false }: { renewalFails?: boolean } = {},
|
|
): MswHandler[] => [
|
|
http.post('*/subscriptions', async ({ request }) => {
|
|
const payload = (await request.json()) as Subscription;
|
|
const subscription: Subscription = {
|
|
...payload,
|
|
id: `subscription-${store.created.length + 1}`,
|
|
expirationDateTime:
|
|
payload.expirationDateTime ??
|
|
new Date(Date.now() + 3600 * 1000).toISOString(),
|
|
};
|
|
|
|
store.created.push(subscription);
|
|
|
|
return HttpResponse.json(subscription);
|
|
}),
|
|
http.patch('*/subscriptions/*', async ({ request }) => {
|
|
const id = subscriptionId(request);
|
|
|
|
if (renewalFails) {
|
|
return resourceNotFound();
|
|
}
|
|
|
|
store.renewed.push(id);
|
|
|
|
const payload = (await request.json()) as Subscription;
|
|
|
|
return HttpResponse.json<Subscription>({
|
|
id,
|
|
expirationDateTime:
|
|
payload.expirationDateTime ??
|
|
new Date(Date.now() + 3600 * 1000).toISOString(),
|
|
});
|
|
}),
|
|
http.delete('*/subscriptions/*', ({ request }) => {
|
|
store.deleted.push(subscriptionId(request));
|
|
|
|
return new HttpResponse(null, { status: 204 });
|
|
}),
|
|
];
|