chore: bump version to 1.7.7 and update CHANGELOG
Build and Push Docker Image / build (push) Successful in 2m17s
Docs Check / Markdown lint (push) Successful in 2m27s
Licence Check / Licence compatibility and copyright header verification (push) Successful in 2m44s
CI / Security audit (push) Successful in 3m10s
Docs Check / Mermaid diagram parse check (push) Successful in 3m54s
CI / Tests & coverage (push) Successful in 4m6s
CI / Swagger Validation & Coverage (push) Successful in 4m23s
Build and Push Docker Image / build (push) Successful in 2m17s
Docs Check / Markdown lint (push) Successful in 2m27s
Licence Check / Licence compatibility and copyright header verification (push) Successful in 2m44s
CI / Security audit (push) Successful in 3m10s
Docs Check / Mermaid diagram parse check (push) Successful in 3m54s
CI / Tests & coverage (push) Successful in 4m6s
CI / Swagger Validation & Coverage (push) Successful in 4m23s
This commit is contained in:
@@ -97,6 +97,9 @@ function makeApp() {
|
||||
process.env.RADARR_INSTANCES = JSON.stringify([
|
||||
{ id: 'radarr-1', name: 'Main Radarr', url: 'https://radarr.test', apiKey: 'rk' }
|
||||
]);
|
||||
process.env.OMBI_INSTANCES = JSON.stringify([
|
||||
{ id: 'ombi-1', name: 'Main Ombi', url: 'https://ombi.test', apiKey: 'ok' }
|
||||
]);
|
||||
return createApp({ skipRateLimits: true });
|
||||
}
|
||||
|
||||
@@ -113,9 +116,10 @@ function postRadarr(app, payload, secret = VALID_SECRET) {
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
// Block outbound *arr calls made by processWebhookEvent (fire-and-forget)
|
||||
// Block outbound *arr and Ombi calls made by processWebhookEvent (fire-and-forget)
|
||||
nock('https://sonarr.test').persist().get(/.*/).reply(200, { records: [] });
|
||||
nock('https://radarr.test').persist().get(/.*/).reply(200, { records: [] });
|
||||
nock('https://ombi.test').persist().get(/.*/).reply(200, []);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -125,6 +129,7 @@ afterEach(() => {
|
||||
delete process.env.SOFARR_BASE_URL;
|
||||
delete process.env.SONARR_INSTANCES;
|
||||
delete process.env.RADARR_INSTANCES;
|
||||
delete process.env.OMBI_INSTANCES;
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -518,3 +523,129 @@ describe('GET /api/webhook/config', () => {
|
||||
expect(res.body.missing).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Ombi webhook receiver
|
||||
// ---------------------------------------------------------------------------
|
||||
describe('POST /api/webhook/ombi', () => {
|
||||
function postOmbi(app, payload, secret = VALID_SECRET) {
|
||||
const req = request(app).post('/api/webhook/ombi').send(payload);
|
||||
if (secret !== null) req.set('X-Sofarr-Webhook-Secret', secret);
|
||||
return req;
|
||||
}
|
||||
|
||||
it('returns 401 when X-Sofarr-Webhook-Secret header is missing', async () => {
|
||||
const app = makeApp();
|
||||
const res = await postOmbi(app, { notificationType: 'NewRequest', requestId: 1 }, null);
|
||||
expect(res.status).toBe(401);
|
||||
expect(res.body.error).toBe('Unauthorized');
|
||||
});
|
||||
|
||||
it('returns 401 when X-Sofarr-Webhook-Secret header is wrong', async () => {
|
||||
const app = makeApp();
|
||||
const res = await postOmbi(app, { notificationType: 'NewRequest', requestId: 1 }, 'wrong-secret');
|
||||
expect(res.status).toBe(401);
|
||||
expect(res.body.error).toBe('Unauthorized');
|
||||
});
|
||||
|
||||
it('returns 400 when notificationType is missing or invalid', async () => {
|
||||
const app = makeApp();
|
||||
const res = await postOmbi(app, { requestId: 1 });
|
||||
expect(res.status).toBe(400);
|
||||
expect(res.body.error).toBe('Invalid or missing notificationType');
|
||||
});
|
||||
|
||||
it('returns 400 when notificationType is unknown', async () => {
|
||||
const app = makeApp();
|
||||
const res = await postOmbi(app, { notificationType: 'UnknownNotification', requestId: 1 });
|
||||
expect(res.status).toBe(400);
|
||||
expect(res.body.error).toBe('Invalid or missing notificationType');
|
||||
});
|
||||
|
||||
it('returns 200 { received: true } for a valid NewRequest event', async () => {
|
||||
const app = makeApp();
|
||||
|
||||
// Nock requests endpoint since processWebhookEvent will fetch requests
|
||||
nock('https://ombi.test')
|
||||
.get('/api/v1/Request/movie')
|
||||
.reply(200, []);
|
||||
nock('https://ombi.test')
|
||||
.get('/api/v1/Request/tv')
|
||||
.reply(200, []);
|
||||
|
||||
const payload = {
|
||||
notificationType: 'NewRequest',
|
||||
requestId: 123,
|
||||
requestedUser: 'gordon',
|
||||
title: 'New Movie',
|
||||
type: 'Movie',
|
||||
requestStatus: 'Pending',
|
||||
applicationUrl: 'https://ombi.test',
|
||||
requestedDate: '2026-05-23T20:30:00.000Z'
|
||||
};
|
||||
|
||||
const res = await postOmbi(app, payload);
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.body.received).toBe(true);
|
||||
expect(res.body.duplicate).toBeUndefined();
|
||||
});
|
||||
|
||||
it('returns 200 { received: true } for a valid RequestAvailable event', async () => {
|
||||
const app = makeApp();
|
||||
|
||||
nock('https://ombi.test')
|
||||
.get('/api/v1/Request/movie')
|
||||
.reply(200, []);
|
||||
nock('https://ombi.test')
|
||||
.get('/api/v1/Request/tv')
|
||||
.reply(200, []);
|
||||
|
||||
const payload = {
|
||||
notificationType: 'RequestAvailable',
|
||||
requestId: 124,
|
||||
requestedUser: 'gordon',
|
||||
title: 'Available Movie',
|
||||
type: 'Movie',
|
||||
requestStatus: 'Available',
|
||||
applicationUrl: 'https://ombi.test',
|
||||
requestedDate: '2026-05-23T20:31:00.000Z'
|
||||
};
|
||||
|
||||
const res = await postOmbi(app, payload);
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.body.received).toBe(true);
|
||||
});
|
||||
|
||||
it('returns duplicate: true for a replay of the same event', async () => {
|
||||
const app = makeApp();
|
||||
|
||||
nock('https://ombi.test').persist()
|
||||
.get('/api/v1/Request/movie')
|
||||
.reply(200, []);
|
||||
nock('https://ombi.test').persist()
|
||||
.get('/api/v1/Request/tv')
|
||||
.reply(200, []);
|
||||
|
||||
const payload = {
|
||||
notificationType: 'NewRequest',
|
||||
requestId: 125,
|
||||
requestedUser: 'gordon',
|
||||
title: 'New Movie',
|
||||
type: 'Movie',
|
||||
requestStatus: 'Pending',
|
||||
applicationUrl: 'https://ombi.test',
|
||||
requestedDate: '2026-05-23T20:32:00.000Z'
|
||||
};
|
||||
|
||||
// First request
|
||||
const res1 = await postOmbi(app, payload);
|
||||
expect(res1.status).toBe(200);
|
||||
expect(res1.body.duplicate).toBeUndefined();
|
||||
|
||||
// Replay
|
||||
const res2 = await postOmbi(app, payload);
|
||||
expect(res2.status).toBe(200);
|
||||
expect(res2.body.duplicate).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user