n8n/packages/cli/test/integration/external-secrets/external-secrets.module.test.ts
2026-03-10 13:34:08 +00:00

207 lines
6.0 KiB
TypeScript

import { Logger } from '@n8n/backend-common';
import { mockInstance, mockLogger, testDb, testModules } from '@n8n/backend-test-utils';
import { SecretsProviderConnectionRepository, SettingsRepository } from '@n8n/db';
import { ShutdownMetadata } from '@n8n/decorators';
import { Container } from '@n8n/di';
import { Cipher } from 'n8n-core';
import { ExternalSecretsProviders } from '@/modules/external-secrets.ee/external-secrets-providers.ee';
import { ExternalSecretsConfig } from '@/modules/external-secrets.ee/external-secrets.config';
import { ExternalSecretsModule } from '@/modules/external-secrets.ee/external-secrets.module';
import {
AnotherDummyProvider,
DummyProvider,
MockProviders,
} from '../../shared/external-secrets/utils';
const mockProvidersInstance = new MockProviders();
mockInstance(ExternalSecretsProviders, mockProvidersInstance);
describe('ExternalSecretsModule', () => {
beforeAll(async () => {
await testModules.loadModules(['external-secrets']);
await testDb.init();
Container.set(Logger, mockLogger());
});
afterAll(async () => {
await testDb.terminate();
});
describe('decorator verification', () => {
it('should have shutdown method decorated with @OnShutdown', () => {
const shutdownMetadata = Container.get(ShutdownMetadata);
const handlers = shutdownMetadata.getHandlersByPriority().flat();
const hasShutdownHandler = handlers.some(
(handler) =>
handler.serviceClass.name === 'ExternalSecretsModule' &&
handler.methodName === 'shutdown',
);
expect(hasShutdownHandler).toBe(true);
});
});
describe('using settings store', () => {
let module: ExternalSecretsModule;
beforeAll(async () => {
mockProvidersInstance.setProviders({
dummy: DummyProvider,
another_dummy: AnotherDummyProvider,
});
const settingsRepository = Container.get(SettingsRepository);
const cipher = Container.get(Cipher);
const config = Container.get(ExternalSecretsConfig);
config.externalSecretsForProjects = false;
config.externalSecretsMultipleConnections = false;
module = Container.get(ExternalSecretsModule);
const settings = {
dummy: {
connected: true,
connectedAt: new Date(),
settings: {},
},
another_dummy: {
connected: false,
connectedAt: new Date(),
settings: {},
},
};
await settingsRepository.save({
key: 'feature.externalSecrets',
value: cipher.encrypt(settings),
loadOnStartup: false,
});
});
afterEach(async () => {
await module.shutdown();
});
it('should load enabled providers on init', async () => {
const initSpy = jest.spyOn(DummyProvider.prototype, 'init');
const connectSpy = jest.spyOn(DummyProvider.prototype, 'connect');
const updateSpy = jest.spyOn(DummyProvider.prototype, 'update');
const initDisabledSpy = jest.spyOn(AnotherDummyProvider.prototype, 'init');
const connectDisabledSpy = jest.spyOn(AnotherDummyProvider.prototype, 'connect');
const updateDisabledSpy = jest.spyOn(AnotherDummyProvider.prototype, 'update');
await module.init();
expect(initSpy).toHaveBeenCalled();
expect(connectSpy).toHaveBeenCalled();
expect(updateSpy).toHaveBeenCalled();
expect(initDisabledSpy).toHaveBeenCalled();
expect(connectDisabledSpy).not.toHaveBeenCalled();
expect(updateDisabledSpy).not.toHaveBeenCalled();
initSpy.mockRestore();
connectSpy.mockRestore();
updateSpy.mockRestore();
initDisabledSpy.mockRestore();
connectDisabledSpy.mockRestore();
updateDisabledSpy.mockRestore();
});
it('should disconnect providers after shutdown', async () => {
await module.init();
const disconnectSpy = jest.spyOn(DummyProvider.prototype, 'disconnect');
await module.shutdown();
expect(disconnectSpy).toHaveBeenCalled();
disconnectSpy.mockRestore();
});
});
describe('using provider connections entities', () => {
let connectionRepository: SecretsProviderConnectionRepository;
let cipher: Cipher;
let module: ExternalSecretsModule;
beforeAll(async () => {
mockProvidersInstance.setProviders({
dummy: DummyProvider,
another_dummy: AnotherDummyProvider,
});
connectionRepository = Container.get(SecretsProviderConnectionRepository);
cipher = Container.get(Cipher);
const config = Container.get(ExternalSecretsConfig);
config.externalSecretsForProjects = true;
module = Container.get(ExternalSecretsModule);
const encryptedSettings = cipher.encrypt(JSON.stringify({}));
await connectionRepository.save({
providerKey: 'my-vault',
type: 'dummy',
encryptedSettings,
isEnabled: true,
});
await connectionRepository.save({
providerKey: 'another-vault',
type: 'another_dummy',
encryptedSettings,
isEnabled: true,
});
});
afterEach(async () => {
await module.shutdown();
});
it('should load and connect all providers on init', async () => {
const initSpy = jest.spyOn(DummyProvider.prototype, 'init');
const connectSpy = jest.spyOn(DummyProvider.prototype, 'connect');
const updateSpy = jest.spyOn(DummyProvider.prototype, 'update');
const initAnotherSpy = jest.spyOn(AnotherDummyProvider.prototype, 'init');
const connectAnotherSpy = jest.spyOn(AnotherDummyProvider.prototype, 'connect');
const updateAnotherSpy = jest.spyOn(AnotherDummyProvider.prototype, 'update');
await module.init();
expect(initSpy).toHaveBeenCalled();
expect(connectSpy).toHaveBeenCalled();
expect(updateSpy).toHaveBeenCalled();
expect(initAnotherSpy).toHaveBeenCalled();
expect(connectAnotherSpy).toHaveBeenCalled();
expect(updateAnotherSpy).toHaveBeenCalled();
initSpy.mockRestore();
connectSpy.mockRestore();
updateSpy.mockRestore();
initAnotherSpy.mockRestore();
connectAnotherSpy.mockRestore();
updateAnotherSpy.mockRestore();
});
it('should disconnect providers after shutdown', async () => {
await module.init();
const disconnectSpy = jest.spyOn(DummyProvider.prototype, 'disconnect');
await module.shutdown();
expect(disconnectSpy).toHaveBeenCalled();
disconnectSpy.mockRestore();
});
});
});