mirror of
https://github.com/n8n-io/n8n.git
synced 2026-10-17 13:39:44 +02:00
fix: Replace deprecated APIs in services and tooling (#40199)
Co-authored-by: n8n-cat-bot[bot] <n8n-cat-bot[bot]@users.noreply.github.com>
This commit is contained in:
parent
388b4036d4
commit
be2ee6f356
|
|
@ -1,5 +1,5 @@
|
|||
import { context, trace } from '@opentelemetry/api';
|
||||
import { AsyncHooksContextManager } from '@opentelemetry/context-async-hooks';
|
||||
import { AsyncLocalStorageContextManager } from '@opentelemetry/context-async-hooks';
|
||||
import {
|
||||
BasicTracerProvider,
|
||||
InMemorySpanExporter,
|
||||
|
|
@ -24,8 +24,7 @@ export class OtelTestProvider {
|
|||
private constructor(
|
||||
private readonly provider: BasicTracerProvider,
|
||||
private readonly exporter: InMemorySpanExporter,
|
||||
// oxlint-disable-next-line typescript/no-deprecated
|
||||
private readonly contextManager: AsyncHooksContextManager,
|
||||
private readonly contextManager: AsyncLocalStorageContextManager,
|
||||
) {}
|
||||
|
||||
static create(): OtelTestProvider {
|
||||
|
|
@ -34,8 +33,7 @@ export class OtelTestProvider {
|
|||
spanProcessors: [new SimpleSpanProcessor(exporter)],
|
||||
});
|
||||
trace.setGlobalTracerProvider(provider);
|
||||
// oxlint-disable-next-line typescript/no-deprecated
|
||||
const contextManager = new AsyncHooksContextManager().enable();
|
||||
const contextManager = new AsyncLocalStorageContextManager().enable();
|
||||
context.setGlobalContextManager(contextManager);
|
||||
return new OtelTestProvider(provider, exporter, contextManager);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// oxlint-disable-next-line typescript/no-deprecated
|
||||
export { ApplicationError } from './application.error';
|
||||
// Keep this export for community nodes that still import ApplicationError.
|
||||
export * from './application.error';
|
||||
export { BaseError, type BaseErrorOptions } from './base.error';
|
||||
export { IsolateError } from './isolate.error';
|
||||
export { OperationalError, type OperationalErrorOptions } from './operational.error';
|
||||
|
|
|
|||
|
|
@ -16,10 +16,7 @@ function runProbeWithFrame(html: string, frameHtml: string) {
|
|||
url: 'http://test.local/page',
|
||||
});
|
||||
const frame = dom.window.document.querySelector('iframe');
|
||||
frame?.contentDocument?.open();
|
||||
// oxlint-disable-next-line typescript/no-deprecated
|
||||
frame?.contentDocument?.write(frameHtml);
|
||||
frame?.contentDocument?.close();
|
||||
if (frame?.contentDocument?.body) frame.contentDocument.body.innerHTML = frameHtml;
|
||||
return dom.window.eval(HTML_PROBE_SCRIPT);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,10 +17,7 @@ async function lintFile(filePath: string, eslintConfig: typeof config) {
|
|||
const eslint = new ESLint({
|
||||
cwd: path.dirname(filePath),
|
||||
overrideConfigFile: true,
|
||||
// `tseslint.config()` returns the typescript-eslint `ConfigArray`, which
|
||||
// is structurally a `Linter.Config[]` but nominally a different type due
|
||||
// to duplicated `Parser` definitions across packages.
|
||||
overrideConfig: eslintConfig as unknown as ESLint.Options['overrideConfig'],
|
||||
overrideConfig: eslintConfig,
|
||||
});
|
||||
const [result] = await eslint.lintFiles([filePath]);
|
||||
return result;
|
||||
|
|
|
|||
|
|
@ -1,18 +1,20 @@
|
|||
import eslint from '@eslint/js';
|
||||
import { n8nCommunityNodesPlugin } from '@n8n/eslint-plugin-community-nodes';
|
||||
import { globalIgnores } from 'eslint/config';
|
||||
import type { Linter } from 'eslint';
|
||||
import { defineConfig, globalIgnores } from 'eslint/config';
|
||||
import { createTypeScriptImportResolver } from 'eslint-import-resolver-typescript';
|
||||
import importPlugin from 'eslint-plugin-import-x';
|
||||
import n8nNodesPlugin from 'eslint-plugin-n8n-nodes-base';
|
||||
import tseslint, { type ConfigArray } from 'typescript-eslint';
|
||||
import tseslint from 'typescript-eslint';
|
||||
|
||||
type ConfigArray = ReturnType<typeof defineConfig>;
|
||||
|
||||
function createConfig(supportCloud = true): ConfigArray {
|
||||
const communityNodesRecommended = supportCloud
|
||||
? n8nCommunityNodesPlugin.configs.recommended
|
||||
: n8nCommunityNodesPlugin.configs.recommendedWithoutN8nCloudSupport;
|
||||
|
||||
// oxlint-disable-next-line typescript/no-deprecated - We're moving away from eslint
|
||||
return tseslint.config(
|
||||
return defineConfig(
|
||||
globalIgnores(['dist']),
|
||||
{
|
||||
files: ['**/*.ts'],
|
||||
|
|
@ -20,7 +22,8 @@ function createConfig(supportCloud = true): ConfigArray {
|
|||
eslint.configs.recommended,
|
||||
tseslint.configs.recommended,
|
||||
communityNodesRecommended,
|
||||
importPlugin.configs['flat/recommended'],
|
||||
// import-x uses typescript-eslint's ESLint types for its preset.
|
||||
importPlugin.configs['flat/recommended'] as Linter.Config,
|
||||
],
|
||||
rules: {
|
||||
'prefer-spread': 'off',
|
||||
|
|
@ -44,7 +47,8 @@ function createConfig(supportCloud = true): ConfigArray {
|
|||
...n8nNodesPlugin.configs.community.rules,
|
||||
},
|
||||
languageOptions: {
|
||||
parser: tseslint.parser,
|
||||
// typescript-eslint bundles a parser type that differs from ESLint's parser type.
|
||||
parser: tseslint.parser as Linter.Parser,
|
||||
parserOptions: {
|
||||
extraFileExtensions: ['.json'],
|
||||
},
|
||||
|
|
|
|||
|
|
@ -188,6 +188,7 @@
|
|||
"@n8n_io/ai-assistant-sdk": "catalog:",
|
||||
"@n8n_io/license-sdk": "3.1.0",
|
||||
"@opentelemetry/api": "catalog:",
|
||||
"@opentelemetry/context-async-hooks": "^2.7.1",
|
||||
"@opentelemetry/core": "2.10.0",
|
||||
"@opentelemetry/exporter-trace-otlp-grpc": "^0.221.0",
|
||||
"@opentelemetry/exporter-trace-otlp-proto": "^0.221.0",
|
||||
|
|
|
|||
|
|
@ -1,133 +1,21 @@
|
|||
import type { RedisOptions } from 'ioredis';
|
||||
import type { IProcessedDataConfig } from 'n8n-workflow';
|
||||
|
||||
import type { schema } from './schema';
|
||||
|
||||
// -----------------------------------
|
||||
// transformers
|
||||
// -----------------------------------
|
||||
|
||||
/**
|
||||
* Transform an object (convict schema) into a union of string arrays (path segments),
|
||||
* one for every valid path in the schema object, filtered by type.
|
||||
*
|
||||
* ```ts
|
||||
* ["port", "default"] | ["queue", "bull", "redis", "port", "default"] | ["queue", "bull", "redis", "db", "default"] | ["queue", "bull", "redis", "timeoutThreshold", "default"] | etc
|
||||
* ```
|
||||
*/
|
||||
type GetPathSegments<Traversable, Filter> = Traversable extends Filter
|
||||
? []
|
||||
: {
|
||||
[K in ValidKeys<Traversable>]: [K, ...GetPathSegments<Traversable[K], Filter>];
|
||||
}[ValidKeys<Traversable>];
|
||||
|
||||
/**
|
||||
* Transform a union of string arrays (path segments) into a union of strings (dotted paths).
|
||||
*
|
||||
* ```ts
|
||||
* "port" | "queue.bull.redis.port" | "queue.bull.redis.db" | "queue.bull.redis.timeoutThreshold" | etc
|
||||
* ```
|
||||
*/
|
||||
type JoinByDotting<T extends string[]> = T extends [infer F]
|
||||
? F
|
||||
: T extends [infer F, ...infer R]
|
||||
? F extends string
|
||||
? R extends string[]
|
||||
? `${F}.${JoinByDotting<R>}`
|
||||
: never
|
||||
: never
|
||||
: string;
|
||||
|
||||
type ToDottedPath<T> = JoinByDotting<RemoveExcess<T>>;
|
||||
|
||||
// oxlint-disable-next-line typescript/no-deprecated
|
||||
type CollectPathsByType<T> = ToDottedPath<GetPathSegments<typeof schema, T>>;
|
||||
|
||||
// -----------------------------------
|
||||
// path-to-return-type mapper
|
||||
// -----------------------------------
|
||||
|
||||
type NumericPath = CollectPathsByType<number>;
|
||||
|
||||
type BooleanPath = CollectPathsByType<boolean>;
|
||||
|
||||
type StringLiteralArrayPath = CollectPathsByType<readonly string[]>;
|
||||
|
||||
type StringPath = CollectPathsByType<string>;
|
||||
|
||||
type ConfigOptionPath =
|
||||
| NumericPath
|
||||
| BooleanPath
|
||||
| StringPath
|
||||
| StringLiteralArrayPath
|
||||
| keyof ExceptionPaths;
|
||||
|
||||
type ToReturnType<T extends ConfigOptionPath> = T extends NumericPath
|
||||
? number
|
||||
: T extends BooleanPath
|
||||
? boolean
|
||||
: T extends StringLiteralArrayPath
|
||||
? StringLiteralMap[T]
|
||||
: T extends keyof ExceptionPaths
|
||||
? ExceptionPaths[T]
|
||||
: T extends StringPath
|
||||
? string
|
||||
: unknown;
|
||||
|
||||
type ExceptionPaths = {
|
||||
// Keep the typed paths that the legacy convict config and its external consumers use.
|
||||
type LegacyConfigPaths = {
|
||||
'userManagement.isInstanceOwnerSetUp': boolean;
|
||||
'userManagement.authenticationMethod': 'email' | 'ldap' | 'saml';
|
||||
'endpoints.rest': string;
|
||||
'ai.enabled': boolean;
|
||||
'ai.allowSendingParameterValues': boolean;
|
||||
'queue.bull.redis': RedisOptions;
|
||||
processedDataManager: IProcessedDataConfig;
|
||||
'ui.banners.dismissed': string[] | undefined;
|
||||
easyAIWorkflowOnboarded: boolean | undefined;
|
||||
};
|
||||
|
||||
// -----------------------------------
|
||||
// string literals map
|
||||
// -----------------------------------
|
||||
|
||||
type GetPathSegmentsWithUnions<T> = T extends ReadonlyArray<infer C>
|
||||
? [C]
|
||||
: {
|
||||
[K in ValidKeys<T>]: [K, ...GetPathSegmentsWithUnions<T[K]>];
|
||||
}[ValidKeys<T>];
|
||||
|
||||
type ToPathUnionPair<T extends string[]> = T extends [...infer Path, infer Union]
|
||||
? Path extends string[]
|
||||
? { path: ToDottedPath<Path>; union: Union }
|
||||
: never
|
||||
: never;
|
||||
|
||||
type ToStringLiteralMap<T extends { path: string; union: string }> = {
|
||||
[Path in T['path']]: Extract<T, { path: Path }>['union'];
|
||||
};
|
||||
|
||||
type StringLiteralMap = ToStringLiteralMap<
|
||||
// oxlint-disable-next-line typescript/no-deprecated
|
||||
ToPathUnionPair<GetPathSegmentsWithUnions<typeof schema>>
|
||||
>;
|
||||
|
||||
// -----------------------------------
|
||||
// utils
|
||||
// -----------------------------------
|
||||
|
||||
type ValidKeys<T> = keyof T extends string
|
||||
? keyof T extends keyof NumberConstructor
|
||||
? never
|
||||
: keyof T
|
||||
: never;
|
||||
|
||||
type RemoveExcess<T> = T extends [...infer Path, 'format' | 'default']
|
||||
? Path extends string[]
|
||||
? Path
|
||||
: never
|
||||
: never;
|
||||
|
||||
// -----------------------------------
|
||||
// module augmentation
|
||||
// -----------------------------------
|
||||
|
||||
declare module 'convict' {
|
||||
interface Config<T> {
|
||||
getEnv<Path extends ConfigOptionPath>(path: Path): ToReturnType<Path>;
|
||||
getEnv<Path extends keyof LegacyConfigPaths>(path: Path): LegacyConfigPaths[Path];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,8 @@
|
|||
import type { Logger } from '@n8n/backend-common';
|
||||
import type { OutboundHttp } from '@n8n/backend-network';
|
||||
import type { Context } from '@opentelemetry/api';
|
||||
import {
|
||||
context,
|
||||
createContextKey,
|
||||
propagation,
|
||||
ProxyTracerProvider,
|
||||
ROOT_CONTEXT,
|
||||
trace,
|
||||
} from '@opentelemetry/api';
|
||||
import type { ReadableSpan } from '@opentelemetry/sdk-trace-base';
|
||||
import { context, createContextKey, propagation, ROOT_CONTEXT, trace } from '@opentelemetry/api';
|
||||
import { AlwaysOffSampler, type ReadableSpan } from '@opentelemetry/sdk-trace-base';
|
||||
import {
|
||||
InMemorySpanExporter,
|
||||
NodeTracerProvider,
|
||||
|
|
@ -73,13 +66,6 @@ function activeContextInside(ctx: Context): Context {
|
|||
return context.with(ctx, () => context.active());
|
||||
}
|
||||
|
||||
function globalTracerProviderDelegate() {
|
||||
const globalProvider = trace.getTracerProvider();
|
||||
return globalProvider instanceof ProxyTracerProvider
|
||||
? globalProvider.getDelegate()
|
||||
: globalProvider;
|
||||
}
|
||||
|
||||
function registerForeignProvider() {
|
||||
const exporter = new InMemorySpanExporter();
|
||||
const provider = new NodeTracerProvider({ spanProcessors: [new SimpleSpanProcessor(exporter)] });
|
||||
|
|
@ -155,8 +141,29 @@ describe('OtelService tracer provider', () => {
|
|||
expect(foreign?.exporter.getFinishedSpans().map((span) => span.name)).toEqual([
|
||||
'GET /webhook',
|
||||
]);
|
||||
expect(globalTracerProviderDelegate()).toBe(foreign?.provider);
|
||||
expect(activeContextInside(markedContext)).toBe(markedContext);
|
||||
|
||||
await service.shutdown();
|
||||
expect(service.getTracer('n8n-workflow').startSpan('workflow.execute').isRecording()).toBe(
|
||||
false,
|
||||
);
|
||||
expect(trace.getTracer('foreign').startSpan('another request').isRecording()).toBe(true);
|
||||
});
|
||||
|
||||
it('does not replace a foreign provider that samples no spans', async () => {
|
||||
await foreign?.provider.shutdown();
|
||||
trace.disable();
|
||||
const provider = new NodeTracerProvider({ sampler: new AlwaysOffSampler() });
|
||||
provider.register();
|
||||
foreign = { provider, exporter: new InMemorySpanExporter() };
|
||||
|
||||
await service.init();
|
||||
await service.restart();
|
||||
|
||||
expect(trace.getTracer('foreign').startSpan('test').isRecording()).toBe(false);
|
||||
service.getTracer('n8n-workflow').startSpan('workflow.execute').end();
|
||||
expect(exportedSpanNames()).toEqual(['workflow.execute']);
|
||||
expect(logger.info).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('logs the foreign owner once for the lifetime of the service', async () => {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import type { Logger } from '@n8n/backend-common';
|
||||
import type { OutboundHttp } from '@n8n/backend-network';
|
||||
import { diag } from '@opentelemetry/api';
|
||||
import { diag, trace } from '@opentelemetry/api';
|
||||
import { OTLPTraceExporter as OTLPGrpcTraceExporter } from '@opentelemetry/exporter-trace-otlp-grpc';
|
||||
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto';
|
||||
import { BasicTracerProvider } from '@opentelemetry/sdk-trace-base';
|
||||
|
|
@ -13,7 +13,6 @@ import type { OtelConfig } from '../otel.config';
|
|||
import { ATTR, OTEL_TEST_SPAN_NAME } from '../otel.constants';
|
||||
import { OtelService } from '../otel.service';
|
||||
|
||||
const register = vi.fn();
|
||||
const shutdown = vi.fn();
|
||||
const providerGetTracer = vi.fn();
|
||||
|
||||
|
|
@ -115,6 +114,7 @@ vi.mock('@grpc/grpc-js', () => ({
|
|||
}));
|
||||
|
||||
vi.mock('@opentelemetry/sdk-trace-base', () => ({
|
||||
AlwaysOffSampler: vi.fn(),
|
||||
BatchSpanProcessor: vi.fn(),
|
||||
BasicTracerProvider: vi.fn().mockImplementation(function (config: {
|
||||
spanProcessors?: unknown[];
|
||||
|
|
@ -123,7 +123,7 @@ vi.mock('@opentelemetry/sdk-trace-base', () => ({
|
|||
mockSpanEnd.mockImplementation(() => {
|
||||
for (const processor of processors) processor.onEnd({ name: 'n8n.test_trace' });
|
||||
});
|
||||
mockStartSpan.mockReturnValue({ end: mockSpanEnd });
|
||||
mockStartSpan.mockReturnValue({ end: mockSpanEnd, isRecording: () => false });
|
||||
mockGetTracer.mockReturnValue({ startSpan: mockStartSpan });
|
||||
return { getTracer: mockGetTracer, shutdown: mockProviderShutdown };
|
||||
}),
|
||||
|
|
@ -142,7 +142,7 @@ vi.mock('@opentelemetry/sdk-trace-node', () => ({
|
|||
return {};
|
||||
}),
|
||||
NodeTracerProvider: vi.fn().mockImplementation(function () {
|
||||
return { register, shutdown, getTracer: providerGetTracer };
|
||||
return { shutdown, getTracer: providerGetTracer };
|
||||
}),
|
||||
}));
|
||||
|
||||
|
|
@ -196,6 +196,7 @@ describe('OtelService', () => {
|
|||
);
|
||||
service = new OtelService(otelSettingsService, instanceSettings, logger, outboundHttp);
|
||||
});
|
||||
afterEach(() => vi.restoreAllMocks());
|
||||
|
||||
describe('init', () => {
|
||||
it('does not start a tracer provider when enabled is false', async () => {
|
||||
|
|
@ -484,7 +485,7 @@ describe('OtelService', () => {
|
|||
|
||||
it('shuts the provider down when registering it with the global API throws', async () => {
|
||||
otelSettingsService.loadSettings.mockResolvedValue(enabledSettings);
|
||||
register.mockImplementationOnce(() => {
|
||||
vi.spyOn(trace, 'setGlobalTracerProvider').mockImplementationOnce(() => {
|
||||
throw new Error('provider registration failed');
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -2,8 +2,14 @@ import type { Metadata } from '@grpc/grpc-js';
|
|||
import { Logger } from '@n8n/backend-common';
|
||||
import { OutboundHttp } from '@n8n/backend-network';
|
||||
import { Service } from '@n8n/di';
|
||||
import type { DiagLogger, Tracer, TracerProvider } from '@opentelemetry/api';
|
||||
import { DiagLogLevel, ProxyTracerProvider, diag, trace } from '@opentelemetry/api';
|
||||
import type { DiagLogger, Tracer } from '@opentelemetry/api';
|
||||
import { DiagLogLevel, context, diag, propagation, trace } from '@opentelemetry/api';
|
||||
import { AsyncLocalStorageContextManager } from '@opentelemetry/context-async-hooks';
|
||||
import {
|
||||
CompositePropagator,
|
||||
W3CBaggagePropagator,
|
||||
W3CTraceContextPropagator,
|
||||
} from '@opentelemetry/core';
|
||||
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto';
|
||||
import {
|
||||
detectResources,
|
||||
|
|
@ -13,6 +19,7 @@ import {
|
|||
resourceFromAttributes,
|
||||
} from '@opentelemetry/resources';
|
||||
import {
|
||||
AlwaysOffSampler,
|
||||
BasicTracerProvider,
|
||||
BatchSpanProcessor,
|
||||
type ReadableSpan,
|
||||
|
|
@ -35,17 +42,7 @@ export type OtelTestTraceResult = { success: true } | { success: false; error: s
|
|||
const stripEmptyResolutionNote = (message: string) =>
|
||||
message.replace(/\s*Resolution note:\s*$/, '');
|
||||
|
||||
// Deprecated in `@opentelemetry/api` 1.x, but the only public source of a no-op tracer provider.
|
||||
// oxlint-disable-next-line typescript/no-deprecated
|
||||
const noopTracerProvider = new ProxyTracerProvider();
|
||||
|
||||
function registeredGlobalTracerProvider(): TracerProvider | undefined {
|
||||
const globalProvider = trace.getTracerProvider();
|
||||
const delegate =
|
||||
// oxlint-disable-next-line typescript/no-deprecated
|
||||
globalProvider instanceof ProxyTracerProvider ? globalProvider.getDelegate() : globalProvider;
|
||||
return delegate === noopTracerProvider.getDelegate() ? undefined : delegate;
|
||||
}
|
||||
let noopTracerProvider: BasicTracerProvider | undefined;
|
||||
|
||||
@Service()
|
||||
export class OtelService {
|
||||
|
|
@ -73,7 +70,9 @@ export class OtelService {
|
|||
}
|
||||
|
||||
getTracer(name: string): Tracer {
|
||||
return (this.provider ?? noopTracerProvider).getTracer(name);
|
||||
if (this.provider) return this.provider.getTracer(name);
|
||||
noopTracerProvider ??= new BasicTracerProvider({ sampler: new AlwaysOffSampler() });
|
||||
return noopTracerProvider.getTracer(name);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -191,13 +190,20 @@ export class OtelService {
|
|||
return;
|
||||
}
|
||||
|
||||
if (registeredGlobalTracerProvider()) {
|
||||
// Claim the global API before installing context and propagation defaults.
|
||||
// A failed claim leaves another library's global registrations in place.
|
||||
this.ownsGlobalApi = trace.setGlobalTracerProvider(provider);
|
||||
if (!this.ownsGlobalApi) {
|
||||
this.logForeignGlobalApiOwner();
|
||||
return;
|
||||
}
|
||||
|
||||
provider.register();
|
||||
this.ownsGlobalApi = registeredGlobalTracerProvider() === provider;
|
||||
context.setGlobalContextManager(new AsyncLocalStorageContextManager().enable());
|
||||
propagation.setGlobalPropagator(
|
||||
new CompositePropagator({
|
||||
propagators: [new W3CTraceContextPropagator(), new W3CBaggagePropagator()],
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
private logForeignGlobalApiOwner(): void {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import { mock, type MockProxy } from 'vitest-mock-extended';
|
|||
import type { Cipher, InstanceSettings } from 'n8n-core';
|
||||
import { CREDENTIAL_BLANKING_VALUE } from 'n8n-workflow';
|
||||
import type { IdentityProviderInstance, ServiceProviderInstance } from 'samlify';
|
||||
import type { BindingContext } from 'samlify/types/src/entity';
|
||||
|
||||
import { BadRequestError, ForbiddenError } from '@n8n/errors';
|
||||
import type { ProvisioningService } from '@/modules/provisioning.ee/provisioning.service.ee';
|
||||
|
|
@ -167,6 +168,7 @@ describe('SamlService', () => {
|
|||
let cipher: Cipher;
|
||||
let cacheService: Mocked<CacheService>;
|
||||
let outboundHttp: Mocked<OutboundHttp>;
|
||||
let urlService: MockProxy<UrlService>;
|
||||
let httpRequest: Mock;
|
||||
const validator = new SamlValidator(mock());
|
||||
const logger = mockLogger();
|
||||
|
|
@ -226,6 +228,7 @@ describe('SamlService', () => {
|
|||
httpRequest = vi.fn();
|
||||
outboundHttp = mock<OutboundHttp>();
|
||||
outboundHttp.requests.mockReturnValue(mock<HttpRequestClient>({ request: httpRequest }));
|
||||
urlService = mock<UrlService>({ getInstanceBaseUrl: () => 'http://localhost:5678' });
|
||||
|
||||
vi.spyOn(ssoHelpers, 'reloadAuthenticationMethod').mockImplementation(
|
||||
async () => await Promise.resolve(),
|
||||
|
|
@ -234,7 +237,7 @@ describe('SamlService', () => {
|
|||
|
||||
samlService = new SamlService(
|
||||
logger,
|
||||
mock<UrlService>(),
|
||||
urlService,
|
||||
validator,
|
||||
userRepository,
|
||||
settingsRepository,
|
||||
|
|
@ -282,6 +285,26 @@ describe('SamlService', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('getLoginRequestUrl', () => {
|
||||
it('passes each relay state with its request and uses the instance URL by default', async () => {
|
||||
const idp = mock<IdentityProviderInstance>();
|
||||
const sp = mock<ServiceProviderInstance>();
|
||||
sp.createLoginRequest.mockReturnValue(mock<BindingContext>());
|
||||
vi.spyOn(samlService, 'getIdentityProviderInstance').mockReturnValue(idp);
|
||||
vi.spyOn(samlService, 'getServiceProviderInstance').mockReturnValue(sp);
|
||||
|
||||
await samlService.getLoginRequestUrl('http://localhost:5678/first', 'redirect');
|
||||
await samlService.getLoginRequestUrl(undefined, 'post');
|
||||
|
||||
expect(sp.createLoginRequest).toHaveBeenNthCalledWith(1, idp, 'redirect', {
|
||||
relayState: 'http://localhost:5678/first',
|
||||
});
|
||||
expect(sp.createLoginRequest).toHaveBeenNthCalledWith(2, idp, 'post', {
|
||||
relayState: 'http://localhost:5678',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAttributesFromLoginResponse', () => {
|
||||
test('throws when any attribute is missing', async () => {
|
||||
// ARRANGE
|
||||
|
|
|
|||
|
|
@ -308,9 +308,9 @@ export class SamlService {
|
|||
|
||||
binding ??= this._samlPreferences.loginBinding ?? 'redirect';
|
||||
const sp = this.getServiceProviderInstance();
|
||||
// oxlint-disable-next-line typescript/no-deprecated
|
||||
sp.entitySetting.relayState = relayState ?? this.urlService.getInstanceBaseUrl();
|
||||
const loginRequest = sp.createLoginRequest(idp, binding);
|
||||
const loginRequest = sp.createLoginRequest(idp, binding, {
|
||||
relayState: relayState ?? this.urlService.getInstanceBaseUrl(),
|
||||
});
|
||||
return {
|
||||
binding,
|
||||
context: binding === 'post' ? loginRequest : loginRequest,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { Container } from '@n8n/di';
|
||||
import type { SchedulingFunctions, Workflow, CronContext, Cron } from 'n8n-workflow';
|
||||
import type { SchedulingFunctions, Workflow, Cron } from 'n8n-workflow';
|
||||
|
||||
import { ScheduledTaskManager } from '../../scheduled-task-manager';
|
||||
|
||||
|
|
@ -13,22 +13,13 @@ export const getSchedulingFunctions = (
|
|||
const scheduledTaskManager = Container.get(ScheduledTaskManager);
|
||||
return {
|
||||
registerCron: ({ expression, recurrence }: Cron, onTick: (scheduledT: Date) => void) => {
|
||||
// oxlint-disable-next-line typescript/no-deprecated
|
||||
const ctx: CronContext = {
|
||||
expression,
|
||||
recurrence,
|
||||
nodeId,
|
||||
workflowId,
|
||||
timezone,
|
||||
};
|
||||
|
||||
return scheduledTaskManager.register(
|
||||
{
|
||||
group: { type: WORKFLOW_SCHEDULE_GROUP_TYPE, id: ctx.workflowId },
|
||||
targetId: ctx.nodeId,
|
||||
timezone: ctx.timezone,
|
||||
expression: ctx.expression,
|
||||
recurrence: ctx.recurrence,
|
||||
group: { type: WORKFLOW_SCHEDULE_GROUP_TYPE, id: workflowId },
|
||||
targetId: nodeId,
|
||||
timezone,
|
||||
expression,
|
||||
recurrence,
|
||||
},
|
||||
onTick,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { CronLoggingConfig } from '@n8n/config';
|
|||
import { Time } from '@n8n/constants';
|
||||
import { Service } from '@n8n/di';
|
||||
import { CronJob, CronTime } from 'cron';
|
||||
import type { CronContext } from 'n8n-workflow';
|
||||
import type { Cron } from 'n8n-workflow';
|
||||
|
||||
import { InstanceSettings } from '@/instance-settings';
|
||||
|
||||
|
|
@ -21,8 +21,7 @@ export type ScheduledTaskContext = {
|
|||
targetId: string;
|
||||
timezone: string;
|
||||
expression: string;
|
||||
// oxlint-disable-next-line typescript/no-deprecated
|
||||
recurrence?: CronContext['recurrence'];
|
||||
recurrence?: Cron['recurrence'];
|
||||
};
|
||||
|
||||
@Service()
|
||||
|
|
|
|||
|
|
@ -1129,8 +1129,7 @@ type CronRecurrenceRule =
|
|||
/**
|
||||
* @deprecated Remnant of the legacy in-memory scheduling path. `registerCron`
|
||||
* takes {@link Cron}, not this type; the durable scheduler path never uses it.
|
||||
* Only `ScheduledTaskManager` and its helper still reference it (and only for
|
||||
* `CronContext['recurrence']`). Slated to go away with `ScheduledTaskManager`.
|
||||
* Legacy test helpers still use it. Use {@link Cron} for new scheduling code.
|
||||
*/
|
||||
export type CronContext = {
|
||||
nodeId: string;
|
||||
|
|
|
|||
|
|
@ -4758,6 +4758,9 @@ importers:
|
|||
'@opentelemetry/api':
|
||||
specifier: 1.9.1
|
||||
version: 1.9.1
|
||||
'@opentelemetry/context-async-hooks':
|
||||
specifier: ^2.7.1
|
||||
version: 2.10.0(@opentelemetry/api@1.9.1)
|
||||
'@opentelemetry/core':
|
||||
specifier: 2.10.0
|
||||
version: 2.10.0(@opentelemetry/api@1.9.1)
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user