n8n/packages/cli/test/integration/shared/utils/test-server.ts

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

279 lines
7.3 KiB
TypeScript
Raw Normal View History

import cookieParser from 'cookie-parser';
import express from 'express';
import type superagent from 'superagent';
import request from 'supertest';
import { Container } from 'typedi';
import { URL } from 'url';
import { AuthService } from '@/auth/auth.service';
import config from '@/config';
import { AUTH_COOKIE_NAME } from '@/constants';
import type { User } from '@/databases/entities/user';
import { ControllerRegistry } from '@/decorators';
import { License } from '@/license';
import { Logger } from '@/logger';
import { rawBodyReader, bodyParser } from '@/middlewares';
import { PostHogClient } from '@/posthog';
import { Push } from '@/push';
import type { APIRequest } from '@/requests';
import { Telemetry } from '@/telemetry';
import { mockInstance } from '../../../shared/mocking';
import { PUBLIC_API_REST_PATH_SEGMENT, REST_PATH_SEGMENT } from '../constants';
import { LicenseMocker } from '../license';
import * as testDb from '../test-db';
import type { SetupProps, TestServer } from '../types';
/**
* Plugin to prefix a path segment into a request URL pathname.
*
* Example: http://127.0.0.1:62100/me/password → http://127.0.0.1:62100/rest/me/password
*/
function prefix(pathSegment: string) {
return async function (request: superagent.SuperAgentRequest) {
const url = new URL(request.url);
// enforce consistency at call sites
if (url.pathname[0] !== '/') {
throw new Error('Pathname must start with a forward slash');
}
url.pathname = pathSegment + url.pathname;
request.url = url.toString();
return await request;
};
}
const browserId = 'test-browser-id';
function createAgent(
app: express.Application,
options?: { auth: boolean; user?: User; noRest?: boolean },
) {
const agent = request.agent(app);
const withRestSegment = !options?.noRest;
if (withRestSegment) void agent.use(prefix(REST_PATH_SEGMENT));
if (options?.auth && options?.user) {
const token = Container.get(AuthService).issueJWT(options.user, browserId);
agent.jar.setCookie(`${AUTH_COOKIE_NAME}=${token}`);
}
return agent;
}
function publicApiAgent(
app: express.Application,
{ user, version = 1 }: { user: User; version?: number },
) {
const agent = request.agent(app);
void agent.use(prefix(`${PUBLIC_API_REST_PATH_SEGMENT}/v${version}`));
if (user.apiKey) {
void agent.set({ 'X-N8N-API-KEY': user.apiKey });
}
return agent;
}
export const setupTestServer = ({
endpointGroups,
enabledFeatures,
quotas,
}: SetupProps): TestServer => {
const app = express();
app.use(rawBodyReader);
app.use(cookieParser());
app.use((req: APIRequest, _, next) => {
req.browserId = browserId;
next();
});
// Mock all telemetry and logging
mockInstance(Logger);
mockInstance(PostHogClient);
mockInstance(Push);
mockInstance(Telemetry);
refactor(core): Implement soft-deletions for executions (#7092) Based on #7065 | Story: https://linear.app/n8n/issue/PAY-771 n8n on filesystem mode marks binary data to delete on manual execution deletion, on unsaved execution completion, and on every execution pruning cycle. We later prune binary data in a separate cycle via these marker files, based on the configured TTL. In the context of introducing an S3 client to manage binary data, the filesystem mode's mark-and-prune setup is too tightly coupled to the general binary data management client interface. This PR... - Ensures the deletion of an execution causes the deletion of any binary data associated to it. This does away with the need for binary data TTL and simplifies the filesystem mode's mark-and-prune setup. - Refactors all execution deletions (including pruning) to cause soft deletions, hard-deletes soft-deleted executions based on the existing pruning config, and adjusts execution endpoints to filter out soft-deleted executions. This reduces DB load, and keeps binary data around long enough for users to access it when building workflows with unsaved executions. - Moves all execution pruning work from an execution lifecycle hook to `execution.repository.ts`. This keeps related logic in a single place. - Removes all marking logic from the binary data manager. This simplifies the interface that the S3 client will meet. - Adds basic sanity-check tests to pruning logic and execution deletion. Out of scope: - Improving existing pruning logic. - Improving existing execution repository logic. - Adjusting dir structure for filesystem mode. --------- Co-authored-by: कारतोफ्फेलस्क्रिप्ट™ <aditya@netroy.in>
2023-09-20 06:21:42 -07:00
const testServer: TestServer = {
app,
httpServer: app.listen(0),
authAgentFor: (user: User) => createAgent(app, { auth: true, user }),
authlessAgent: createAgent(app),
restlessAgent: createAgent(app, { auth: false, noRest: true }),
publicApiAgentFor: (user) => publicApiAgent(app, { user }),
license: new LicenseMocker(),
};
// eslint-disable-next-line complexity
beforeAll(async () => {
await testDb.init();
config.set('userManagement.jwtSecret', 'My JWT secret');
config.set('userManagement.isInstanceOwnerSetUp', true);
testServer.license.mock(Container.get(License));
if (enabledFeatures) {
testServer.license.setDefaults({
features: enabledFeatures,
quotas,
});
}
if (!endpointGroups) return;
app.use(bodyParser);
const enablePublicAPI = endpointGroups?.includes('publicApi');
if (enablePublicAPI) {
const { loadPublicApiVersions } = await import('@/public-api');
const { apiRouters } = await loadPublicApiVersions(PUBLIC_API_REST_PATH_SEGMENT);
app.use(...apiRouters);
}
if (endpointGroups?.includes('health')) {
app.get('/healthz/readiness', async (_req, res) => {
testDb.isReady()
? res.status(200).send({ status: 'ok' })
: res.status(503).send({ status: 'error' });
});
}
if (endpointGroups.length) {
for (const group of endpointGroups) {
switch (group) {
case 'annotationTags':
await import('@/controllers/annotation-tags.controller');
break;
case 'credentials':
await import('@/credentials/credentials.controller');
break;
case 'workflows':
await import('@/workflows/workflows.controller');
break;
case 'executions':
await import('@/executions/executions.controller');
break;
case 'variables':
await import('@/environments/variables/variables.controller.ee');
break;
case 'license':
await import('@/license/license.controller');
break;
case 'metrics':
const { PrometheusMetricsService } = await import(
'@/metrics/prometheus-metrics.service'
);
await Container.get(PrometheusMetricsService).init(app);
break;
case 'eventBus':
await import('@/eventbus/event-bus.controller');
break;
case 'auth':
await import('@/controllers/auth.controller');
break;
case 'oauth2':
await import('@/controllers/oauth/oauth2-credential.controller');
break;
case 'mfa':
await import('@/controllers/mfa.controller');
break;
case 'ldap':
const { LdapService } = await import('@/ldap/ldap.service.ee');
await import('@/ldap/ldap.controller.ee');
testServer.license.enable('feat:ldap');
await Container.get(LdapService).init();
break;
case 'saml':
const { setSamlLoginEnabled } = await import('@/sso/saml/saml-helpers');
await import('@/sso/saml/routes/saml.controller.ee');
await setSamlLoginEnabled(true);
break;
case 'sourceControl':
await import('@/environments/source-control/source-control.controller.ee');
break;
case 'community-packages':
await import('@/controllers/community-packages.controller');
break;
case 'me':
await import('@/controllers/me.controller');
break;
case 'passwordReset':
await import('@/controllers/password-reset.controller');
break;
case 'owner':
await import('@/controllers/owner.controller');
break;
case 'users':
await import('@/controllers/users.controller');
break;
case 'invitations':
await import('@/controllers/invitation.controller');
break;
case 'tags':
await import('@/controllers/tags.controller');
break;
case 'externalSecrets':
await import('@/external-secrets/external-secrets.controller.ee');
break;
case 'workflowHistory':
await import('@/workflows/workflow-history/workflow-history.controller.ee');
break;
case 'binaryData':
await import('@/controllers/binary-data.controller');
break;
feat(core): Add multi-main setup debug endpoint (no-changelog) (#7991) ## Summary Provide details about your pull request and what it adds, fixes, or changes. Photos and videos are recommended. Adi's idea here to help diagnose: https://n8nio.slack.com/archives/C069KJBJ8HE/p1702300349277609?thread_ts=1702299930.728029&cid=C069KJBJ8HE ... #### How to test the change: 1. ... ## Issues fixed Include links to Github issue or Community forum post or **Linear ticket**: > Important in order to close automatically and provide context to reviewers ... ## Review / Merge checklist - [ ] PR title and summary are descriptive. **Remember, the title automatically goes into the changelog. Use `(no-changelog)` otherwise.** ([conventions](https://github.com/n8n-io/n8n/blob/master/.github/pull_request_title_conventions.md)) - [ ] [Docs updated](https://github.com/n8n-io/n8n-docs) or follow-up ticket created. - [ ] Tests included. > A bug is not considered fixed, unless a test is added to prevent it from happening again. A feature is not complete without tests. > > *(internal)* You can use Slack commands to trigger [e2e tests](https://www.notion.so/n8n/How-to-use-Test-Instances-d65f49dfc51f441ea44367fb6f67eb0a?pvs=4#a39f9e5ba64a48b58a71d81c837e8227) or [deploy test instance](https://www.notion.so/n8n/How-to-use-Test-Instances-d65f49dfc51f441ea44367fb6f67eb0a?pvs=4#f6a177d32bde4b57ae2da0b8e454bfce) or [deploy early access version on Cloud](https://www.notion.so/n8n/Cloudbot-3dbe779836004972b7057bc989526998?pvs=4#fef2d36ab02247e1a0f65a74f6fb534e).
2023-12-12 06:18:32 -08:00
case 'debug':
await import('@/controllers/debug.controller');
feat(core): Add multi-main setup debug endpoint (no-changelog) (#7991) ## Summary Provide details about your pull request and what it adds, fixes, or changes. Photos and videos are recommended. Adi's idea here to help diagnose: https://n8nio.slack.com/archives/C069KJBJ8HE/p1702300349277609?thread_ts=1702299930.728029&cid=C069KJBJ8HE ... #### How to test the change: 1. ... ## Issues fixed Include links to Github issue or Community forum post or **Linear ticket**: > Important in order to close automatically and provide context to reviewers ... ## Review / Merge checklist - [ ] PR title and summary are descriptive. **Remember, the title automatically goes into the changelog. Use `(no-changelog)` otherwise.** ([conventions](https://github.com/n8n-io/n8n/blob/master/.github/pull_request_title_conventions.md)) - [ ] [Docs updated](https://github.com/n8n-io/n8n-docs) or follow-up ticket created. - [ ] Tests included. > A bug is not considered fixed, unless a test is added to prevent it from happening again. A feature is not complete without tests. > > *(internal)* You can use Slack commands to trigger [e2e tests](https://www.notion.so/n8n/How-to-use-Test-Instances-d65f49dfc51f441ea44367fb6f67eb0a?pvs=4#a39f9e5ba64a48b58a71d81c837e8227) or [deploy test instance](https://www.notion.so/n8n/How-to-use-Test-Instances-d65f49dfc51f441ea44367fb6f67eb0a?pvs=4#f6a177d32bde4b57ae2da0b8e454bfce) or [deploy early access version on Cloud](https://www.notion.so/n8n/Cloudbot-3dbe779836004972b7057bc989526998?pvs=4#fef2d36ab02247e1a0f65a74f6fb534e).
2023-12-12 06:18:32 -08:00
break;
case 'project':
await import('@/controllers/project.controller');
break;
case 'role':
await import('@/controllers/role.controller');
break;
case 'dynamic-node-parameters':
await import('@/controllers/dynamic-node-parameters.controller');
break;
}
}
Container.get(ControllerRegistry).activate(app);
}
});
afterAll(async () => {
await testDb.terminate();
testServer.httpServer.close();
});
beforeEach(() => {
testServer.license.reset();
});
return testServer;
};