mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 18:07:31 +00:00
feat: call webhooks at least once (#5454)
* feat: call webhooks at least once * self review * feat: improve notification observability * feat: add notification tracing * test(e2e): test at-least-once webhook delivery * fix webhook notifications * dedicated quota notifications handler * fix linting * fix e2e test * wait less in e2e test * fix: don't ignore failed events in handlers * fix: don't ignore failed events in handlers * faster requeues * question * fix retries * fix retries * retry * don't instance ids query * revert handler_projection * statements can be nil * cleanup * make unit tests pass * add comments * add comments * lint * spool only active instances * feat(config): handle inactive instances * customizable HandleInactiveInstances * call inactive instances quota webhooks * test: handling with and w/o inactive instances * omit retrying noop statements * docs: describe projection options * enable global handling of inactive instances * self review * requeue quota notifications every 5m * remove caos_errors reference * fix comment styles * make handlers package flat * fix linting * fix repeating quota notifications * test with more usage * debug log channel init failures
This commit is contained in:
@@ -2,6 +2,7 @@ import { addQuota, ensureQuotaIsAdded, ensureQuotaIsRemoved, removeQuota, Unit }
|
||||
import { createHumanUser, ensureUserDoesntExist } from 'support/api/users';
|
||||
import { Context } from 'support/commands';
|
||||
import { ZITADELWebhookEvent } from 'support/types';
|
||||
import { textChangeRangeIsUnchanged } from 'typescript';
|
||||
|
||||
beforeEach(() => {
|
||||
cy.context().as('ctx');
|
||||
@@ -144,7 +145,7 @@ describe('quotas', () => {
|
||||
|
||||
const amount = 100;
|
||||
const percent = 10;
|
||||
const usage = 25;
|
||||
const usage = 35;
|
||||
|
||||
describe('without repetition', () => {
|
||||
beforeEach(() => {
|
||||
@@ -160,7 +161,7 @@ describe('quotas', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('fires once with the expected payload', () => {
|
||||
it('fires at least once with the expected payload', () => {
|
||||
cy.get<Array<string>>('@authenticatedUrls').then((urls) => {
|
||||
cy.get<Context>('@ctx').then((ctx) => {
|
||||
for (let i = 0; i < usage; i++) {
|
||||
@@ -175,19 +176,71 @@ describe('quotas', () => {
|
||||
});
|
||||
cy.waitUntil(() =>
|
||||
cy.task<Array<ZITADELWebhookEvent>>('handledWebhookEvents').then((events) => {
|
||||
if (events.length != 1) {
|
||||
if (events.length < 1) {
|
||||
return false;
|
||||
}
|
||||
return Cypress._.matches(<ZITADELWebhookEvent>{
|
||||
callURL: callURL,
|
||||
threshold: percent,
|
||||
unit: 1,
|
||||
usage: percent,
|
||||
sentStatus: 200,
|
||||
payload: {
|
||||
callURL: callURL,
|
||||
threshold: percent,
|
||||
unit: 1,
|
||||
usage: percent,
|
||||
},
|
||||
})(events[0]);
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('fires until the webhook returns a successful message', () => {
|
||||
cy.task('failWebhookEvents', 8);
|
||||
cy.get<Array<string>>('@authenticatedUrls').then((urls) => {
|
||||
cy.get<Context>('@ctx').then((ctx) => {
|
||||
for (let i = 0; i < usage; i++) {
|
||||
cy.request({
|
||||
url: urls[0],
|
||||
method: 'GET',
|
||||
auth: {
|
||||
bearer: ctx.api.token,
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
cy.waitUntil(
|
||||
() =>
|
||||
cy.task<Array<ZITADELWebhookEvent>>('handledWebhookEvents').then((events) => {
|
||||
if (events.length != 9) {
|
||||
return false;
|
||||
}
|
||||
return events.reduce<boolean>((a, b, i) => {
|
||||
return !a
|
||||
? a
|
||||
: i < 8
|
||||
? Cypress._.matches(<ZITADELWebhookEvent>{
|
||||
sentStatus: 500,
|
||||
payload: {
|
||||
callURL: callURL,
|
||||
threshold: percent,
|
||||
unit: 1,
|
||||
usage: percent,
|
||||
},
|
||||
})(b)
|
||||
: Cypress._.matches(<ZITADELWebhookEvent>{
|
||||
sentStatus: 200,
|
||||
payload: {
|
||||
callURL: callURL,
|
||||
threshold: percent,
|
||||
unit: 1,
|
||||
usage: percent,
|
||||
},
|
||||
})(b);
|
||||
}, true);
|
||||
}),
|
||||
{ timeout: 60_000 },
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('with repetition', () => {
|
||||
@@ -222,23 +275,25 @@ describe('quotas', () => {
|
||||
});
|
||||
cy.waitUntil(() =>
|
||||
cy.task<Array<ZITADELWebhookEvent>>('handledWebhookEvents').then((events) => {
|
||||
if (events.length != 1) {
|
||||
return false;
|
||||
}
|
||||
let foundExpected = 0;
|
||||
for (let i = 0; i < events.length; i++) {
|
||||
const threshold = percent * (i + 1);
|
||||
if (
|
||||
!Cypress._.matches(<ZITADELWebhookEvent>{
|
||||
callURL: callURL,
|
||||
threshold: threshold,
|
||||
unit: 1,
|
||||
usage: threshold,
|
||||
})(events[i])
|
||||
) {
|
||||
return false;
|
||||
for (let expect = 10; expect <= 30; expect += 10) {
|
||||
if (
|
||||
Cypress._.matches(<ZITADELWebhookEvent>{
|
||||
sentStatus: 200,
|
||||
payload: {
|
||||
callURL: callURL,
|
||||
threshold: expect,
|
||||
unit: 1,
|
||||
usage: expect,
|
||||
},
|
||||
})(events[i])
|
||||
) {
|
||||
foundExpected++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return foundExpected >= 3;
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
@@ -1,10 +1,13 @@
|
||||
let webhookEventSchema = {
|
||||
unit: 0,
|
||||
id: '',
|
||||
callURL: '',
|
||||
periodStart: new Date(),
|
||||
threshold: 0,
|
||||
usage: 0,
|
||||
sentStatus: 0,
|
||||
payload: {
|
||||
unit: 0,
|
||||
id: '',
|
||||
callURL: '',
|
||||
periodStart: new Date(),
|
||||
threshold: 0,
|
||||
usage: 0,
|
||||
},
|
||||
};
|
||||
|
||||
export type ZITADELWebhookEvent = typeof webhookEventSchema;
|
||||
|
Reference in New Issue
Block a user