n8n/packages/cli/test/unit/services/webhook.service.test.ts
Iván Ovejero 0511458d41
perf(core): Cache webhooks (#6825)
* refactor: Initial setup

* Refactor for clarity

* Comments to clarify

* More replacements

* Simplify with `fullPath`

* Fix tests

* Implement remaining methods

* chore: Fix misresolved conflicts

* Simplify syntax

* Reduce diff

* Minor cleanup

* Fix lint

* Inject dependency

* Improve typings

* Remove unused method

* Restore method

* Add comment

* Rename in test

* Restore comments

* Clean up dynamic webhook handling

* Clean up tests

* Remove redundant `cache` prefix

* fix: Correct `uniquePath` for dynamic webhooks
2023-08-04 11:52:45 +02:00

215 lines
6.9 KiB
TypeScript

import { v4 as uuid } from 'uuid';
import config from '@/config';
import { mockInstance } from '../../integration/shared/utils/';
import { WebhookRepository } from '@/databases/repositories';
import { CacheService } from '@/services/cache.service';
import { WebhookService } from '@/services/webhook.service';
import { WebhookEntity } from '@/databases/entities/WebhookEntity';
const createWebhook = (method: string, path: string, webhookId?: string, pathSegments?: number) =>
Object.assign(new WebhookEntity(), {
method,
webhookPath: path,
webhookId,
pathSegments,
}) as WebhookEntity;
describe('WebhookService', () => {
const webhookRepository = mockInstance(WebhookRepository);
const cacheService = mockInstance(CacheService);
const webhookService = new WebhookService(webhookRepository, cacheService);
beforeEach(() => {
config.load(config.default);
jest.clearAllMocks();
});
[true, false].forEach((isCacheEnabled) => {
const tag = '[' + ['cache', isCacheEnabled ? 'enabled' : 'disabled'].join(' ') + ']';
describe(`findWebhook() - static case ${tag}`, () => {
test('should return the webhook if found', async () => {
const method = 'GET';
const path = 'user/profile';
const mockWebhook = createWebhook(method, path);
webhookRepository.findOneBy.mockResolvedValue(mockWebhook);
const returnedWebhook = await webhookService.findWebhook(method, path);
expect(returnedWebhook).toBe(mockWebhook);
});
test('should return null if not found', async () => {
webhookRepository.findOneBy.mockResolvedValue(null); // static
webhookRepository.findBy.mockResolvedValue([]);
const returnValue = await webhookService.findWebhook('GET', 'user/profile');
expect(returnValue).toBeNull();
});
});
describe(`findWebhook() - dynamic case ${tag}`, () => {
test('should return the webhook if found', async () => {
const method = 'GET';
const webhookId = uuid();
const path = 'user/:id/posts';
const mockWebhook = createWebhook(method, path, webhookId, 3);
webhookRepository.findOneBy.mockResolvedValue(null); // static
webhookRepository.findBy.mockResolvedValue([mockWebhook]); // dynamic
const returnedWebhook = await webhookService.findWebhook(
method,
[webhookId, 'user/123/posts'].join('/'),
);
expect(returnedWebhook).toBe(mockWebhook);
});
test('should handle subset dynamic path case', async () => {
const method1 = 'GET';
const webhookId1 = uuid();
const path1 = 'user/:id/posts';
const mockWebhook1 = createWebhook(method1, path1, webhookId1, 3);
const method2 = 'GET';
const webhookId2 = uuid();
const path2 = 'user/:id/posts/:postId/comments';
const mockWebhook2 = createWebhook(method2, path2, webhookId2, 3);
webhookRepository.findOneBy.mockResolvedValue(null); // static
webhookRepository.findBy.mockResolvedValue([mockWebhook1, mockWebhook2]); // dynamic
const fullPath1 = [webhookId1, 'user/123/posts'].join('/');
const returnedWebhook1 = await webhookService.findWebhook(method1, fullPath1);
const fullPath2 = [webhookId1, 'user/123/posts/456/comments'].join('/');
const returnedWebhook2 = await webhookService.findWebhook(method2, fullPath2);
expect(returnedWebhook1).toBe(mockWebhook1);
expect(returnedWebhook2).toBe(mockWebhook2);
});
test('should handle single-segment dynamic path case', async () => {
const method1 = 'GET';
const webhookId1 = uuid();
const path1 = ':var';
const mockWebhook1 = createWebhook(method1, path1, webhookId1, 3);
const method2 = 'GET';
const webhookId2 = uuid();
const path2 = 'user/:id/posts/:postId/comments';
const mockWebhook2 = createWebhook(method2, path2, webhookId2, 3);
webhookRepository.findOneBy.mockResolvedValue(null); // static
webhookRepository.findBy.mockResolvedValue([mockWebhook1, mockWebhook2]); // dynamic
const fullPath = [webhookId1, 'user/123/posts/456'].join('/');
const returnedWebhook = await webhookService.findWebhook(method1, fullPath);
expect(returnedWebhook).toBe(mockWebhook1);
});
test('should return null if not found', async () => {
const fullPath = [uuid(), 'user/:id/posts'].join('/');
webhookRepository.findOneBy.mockResolvedValue(null); // static
webhookRepository.findBy.mockResolvedValue([]); // dynamic
const returnValue = await webhookService.findWebhook('GET', fullPath);
expect(returnValue).toBeNull();
});
});
});
describe('getWebhookMethods()', () => {
test('should return all methods for webhook', async () => {
const path = 'user/profile';
webhookRepository.find.mockResolvedValue([
createWebhook('GET', path),
createWebhook('POST', path),
createWebhook('PUT', path),
createWebhook('PATCH', path),
]);
const returnedMethods = await webhookService.getWebhookMethods(path);
expect(returnedMethods).toEqual(['GET', 'POST', 'PUT', 'PATCH']);
});
test('should return empty array if no webhooks found', async () => {
webhookRepository.find.mockResolvedValue([]);
const returnedMethods = await webhookService.getWebhookMethods('user/profile');
expect(returnedMethods).toEqual([]);
});
});
describe('deleteInstanceWebhooks()', () => {
test('should delete all webhooks of the instance', async () => {
const mockInstanceWebhooks = [
createWebhook('PUT', 'users'),
createWebhook('GET', 'user/:id'),
createWebhook('POST', ':var'),
];
webhookRepository.find.mockResolvedValue(mockInstanceWebhooks);
await webhookService.deleteInstanceWebhooks();
expect(webhookRepository.remove).toHaveBeenCalledWith(mockInstanceWebhooks);
});
test('should not delete any webhooks if none found', async () => {
webhookRepository.find.mockResolvedValue([]);
await webhookService.deleteInstanceWebhooks();
expect(webhookRepository.remove).toHaveBeenCalledWith([]);
});
});
describe('deleteWorkflowWebhooks()', () => {
test('should delete all webhooks of the workflow', async () => {
const mockWorkflowWebhooks = [
createWebhook('PUT', 'users'),
createWebhook('GET', 'user/:id'),
createWebhook('POST', ':var'),
];
webhookRepository.findBy.mockResolvedValue(mockWorkflowWebhooks);
const workflowId = uuid();
await webhookService.deleteWorkflowWebhooks(workflowId);
expect(webhookRepository.remove).toHaveBeenCalledWith(mockWorkflowWebhooks);
});
test('should not delete any webhooks if none found', async () => {
webhookRepository.findBy.mockResolvedValue([]);
const workflowId = uuid();
await webhookService.deleteWorkflowWebhooks(workflowId);
expect(webhookRepository.remove).toHaveBeenCalledWith([]);
});
});
describe('createWebhook()', () => {
test('should create the webhook', async () => {
const mockWebhook = createWebhook('GET', 'user/:id');
await webhookService.storeWebhook(mockWebhook);
expect(webhookRepository.insert).toHaveBeenCalledWith(mockWebhook);
});
});
});