chore(core): Update workflow status endpoint to use absolute URL (#23285)

This commit is contained in:
Andreas Fitzek
2025-12-16 16:08:05 +01:00
committed by GitHub
parent 5c3b7b6186
commit 7e94eb88c3
2 changed files with 51 additions and 5 deletions
@@ -5,10 +5,14 @@ import { BadRequestError } from '@/errors/response-errors/bad-request.error';
import { WorkflowStatusController } from '../workflow-status.controller';
import type { CredentialResolverWorkflowService } from '../services/credential-resolver-workflow.service';
import { UnauthenticatedError } from '@/errors/response-errors/unauthenticated.error';
import type { UrlService } from '@/services/url.service';
import type { GlobalConfig } from '@n8n/config';
describe('WorkflowStatusController', () => {
let controller: WorkflowStatusController;
let mockService: jest.Mocked<CredentialResolverWorkflowService>;
let mockUrlService: jest.Mocked<UrlService>;
let mockGlobalConfig: jest.Mocked<GlobalConfig>;
beforeEach(() => {
jest.clearAllMocks();
@@ -17,7 +21,17 @@ describe('WorkflowStatusController', () => {
getWorkflowStatus: jest.fn(),
} as unknown as jest.Mocked<CredentialResolverWorkflowService>;
controller = new WorkflowStatusController(mockService);
mockUrlService = {
getInstanceBaseUrl: jest.fn().mockReturnValue('https://n8n.example.com'),
} as unknown as jest.Mocked<UrlService>;
mockGlobalConfig = {
endpoints: {
rest: 'rest',
},
} as unknown as jest.Mocked<GlobalConfig>;
controller = new WorkflowStatusController(mockService, mockUrlService, mockGlobalConfig);
});
describe('checkWorkflowForExecution', () => {
@@ -165,7 +179,7 @@ describe('WorkflowStatusController', () => {
const result = await controller.checkWorkflowForExecution(req, res);
expect(result.credentials?.[0].authorizationUrl).toBe(
'/credentials/cred-1/authorize?resolverId=resolver-1',
'https://n8n.example.com/rest/credentials/cred-1/authorize?resolverId=resolver-1',
);
});
@@ -188,10 +202,34 @@ describe('WorkflowStatusController', () => {
const result = await controller.checkWorkflowForExecution(req, res);
expect(result.credentials?.[0].authorizationUrl).toBe(
'/credentials/cred-1/authorize?resolverId=resolver%2Fwith%2Fspecial%20chars',
'https://n8n.example.com/rest/credentials/cred-1/authorize?resolverId=resolver%2Fwith%2Fspecial%20chars',
);
});
it('should return absolute URLs with base path from UrlService', async () => {
const req = mock<Request>({
params: { workflowId: 'workflow-1' },
headers: { authorization: 'Bearer token-123' },
});
const res = mock<Response>();
mockService.getWorkflowStatus.mockResolvedValue([
{
credentialId: 'cred-1',
resolverId: 'resolver-1',
status: 'configured',
credentialType: 'oauth2Api',
},
]);
const result = await controller.checkWorkflowForExecution(req, res);
// Verify that the URL is absolute and starts with the base URL
expect(result.credentials?.[0].authorizationUrl).toMatch(/^https:\/\//);
expect(result.credentials?.[0].authorizationUrl).toContain('https://n8n.example.com');
expect(mockUrlService.getInstanceBaseUrl).toHaveBeenCalled();
});
it('should map service response to DTO correctly', async () => {
const req = mock<Request>({
params: { workflowId: 'workflow-1' },
@@ -218,7 +256,8 @@ describe('WorkflowStatusController', () => {
credentialId: 'cred-1',
credentialStatus: 'missing',
credentialType: 'oauth2Api',
authorizationUrl: '/credentials/cred-1/authorize?resolverId=resolver-1',
authorizationUrl:
'https://n8n.example.com/rest/credentials/cred-1/authorize?resolverId=resolver-1',
},
],
});
@@ -5,11 +5,15 @@ import { BadRequestError } from '@/errors/response-errors/bad-request.error';
import { CredentialResolverWorkflowService } from './services/credential-resolver-workflow.service';
import { WorkflowExecutionStatus } from '@n8n/api-types';
import { getBearerToken } from './utils';
import { UrlService } from '@/services/url.service';
import { GlobalConfig } from '@n8n/config';
@RestController('/workflows')
export class WorkflowStatusController {
constructor(
private readonly credentialResolverWorkflowService: CredentialResolverWorkflowService,
private readonly urlService: UrlService,
private readonly globalConfig: GlobalConfig,
) {}
/**
@@ -37,6 +41,9 @@ export class WorkflowStatusController {
const isReady = status.every((s) => s.status === 'configured');
const basePath = this.urlService.getInstanceBaseUrl();
const restPath = this.globalConfig.endpoints.rest;
const executionStatus: WorkflowExecutionStatus = {
workflowId,
readyToExecute: isReady,
@@ -44,7 +51,7 @@ export class WorkflowStatusController {
credentialId: s.credentialId,
credentialStatus: s.status,
credentialType: s.credentialType,
authorizationUrl: `/credentials/${s.credentialId}/authorize?resolverId=${encodeURIComponent(s.resolverId)}`,
authorizationUrl: `${basePath}/${restPath}/credentials/${s.credentialId}/authorize?resolverId=${encodeURIComponent(s.resolverId)}`,
})),
};
return executionStatus;