mirror of
https://github.com/n8n-io/n8n.git
synced 2026-10-17 13:39:44 +02:00
refactor(API): Migrate POST /settings/otel/test-trace to the decorator pattern (no-changelog) (#39716)
Co-authored-by: n8n-cat-bot[bot] <n8n-cat-bot[bot]@users.noreply.github.com> Co-authored-by: Uddish Verma <uddish.verma@n8n.io> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> Co-authored-by: Declan Carroll <declancarroll@gmail.com>
This commit is contained in:
parent
1ebd6265db
commit
cd1bfafe72
|
|
@ -598,6 +598,10 @@ export {
|
|||
OtelSettingsQueryPublicDto,
|
||||
UpdateOtelSettingsPublicDto,
|
||||
} from './otel/otel-settings-public.dto';
|
||||
export {
|
||||
OtelTestTraceRequestPublicDto,
|
||||
OtelTestTraceResultPublicDto,
|
||||
} from './otel/otel-test-trace-public.dto';
|
||||
|
||||
export {
|
||||
PromotionChangesDto,
|
||||
|
|
|
|||
|
|
@ -96,6 +96,16 @@ describe('UpdateOtelSettingsDto', () => {
|
|||
expect(result.data.exporterEndpoint).toBe(exporterEndpoint);
|
||||
});
|
||||
|
||||
it('trims whitespace around the exporter endpoint', () => {
|
||||
const result = UpdateOtelSettingsDto.safeParse({
|
||||
...validSettings,
|
||||
exporterEndpoint: ' http://localhost:4318 ',
|
||||
});
|
||||
|
||||
assert(result.success);
|
||||
expect(result.data.exporterEndpoint).toBe('http://localhost:4318');
|
||||
});
|
||||
|
||||
it.each(['localhost:4318', 'grpc://host:4317', 'ftp://x'])(
|
||||
'rejects %p as an exporter endpoint',
|
||||
(exporterEndpoint) => {
|
||||
|
|
@ -198,6 +208,16 @@ describe('TestOtelTraceDto', () => {
|
|||
expect(result.data.exporterEndpoint).toBe('https://collector.example.com:4317');
|
||||
});
|
||||
|
||||
it('trims whitespace around the exporter endpoint', () => {
|
||||
const result = TestOtelTraceDto.safeParse({
|
||||
...validConnection,
|
||||
exporterEndpoint: ' http://localhost:4318 ',
|
||||
});
|
||||
|
||||
assert(result.success);
|
||||
expect(result.data.exporterEndpoint).toBe('http://localhost:4318');
|
||||
});
|
||||
|
||||
it('rejects a non-http exporter endpoint scheme', () => {
|
||||
const result = TestOtelTraceDto.safeParse({
|
||||
...validConnection,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
import '../../openapi-extend';
|
||||
|
||||
import { z } from 'zod';
|
||||
|
||||
import {
|
||||
otelTestTraceRequestFieldDocs,
|
||||
otelTestTraceRequestDocs,
|
||||
otelTestTraceResultFieldDocs,
|
||||
otelTestTraceResultDocs,
|
||||
} from './otel-test-trace-public.openapi';
|
||||
import { TestOtelTraceDto } from './test-otel-trace.dto';
|
||||
import { Z } from '../../zod-class';
|
||||
|
||||
const connectionFields = TestOtelTraceDto.schema.shape;
|
||||
|
||||
export class OtelTestTraceRequestPublicDto extends Z.class(
|
||||
{
|
||||
exporterProtocol: connectionFields.exporterProtocol.openapi(
|
||||
otelTestTraceRequestFieldDocs.exporterProtocol,
|
||||
),
|
||||
exporterEndpoint: connectionFields.exporterEndpoint.openapi(
|
||||
otelTestTraceRequestFieldDocs.exporterEndpoint,
|
||||
),
|
||||
exporterTracingPath: connectionFields.exporterTracingPath.openapi(
|
||||
otelTestTraceRequestFieldDocs.exporterTracingPath,
|
||||
),
|
||||
exporterServiceName: connectionFields.exporterServiceName.openapi(
|
||||
otelTestTraceRequestFieldDocs.exporterServiceName,
|
||||
),
|
||||
exporterHeaders: connectionFields.exporterHeaders.openapi(
|
||||
otelTestTraceRequestFieldDocs.exporterHeaders,
|
||||
),
|
||||
startupConnectivityTimeoutMs: connectionFields.startupConnectivityTimeoutMs.openapi(
|
||||
otelTestTraceRequestFieldDocs.startupConnectivityTimeoutMs,
|
||||
),
|
||||
},
|
||||
{ strict: true },
|
||||
) {
|
||||
static schema = super.schema.openapi(otelTestTraceRequestDocs);
|
||||
}
|
||||
|
||||
export class OtelTestTraceResultPublicDto extends Z.class(
|
||||
{
|
||||
success: z.boolean().openapi(otelTestTraceResultFieldDocs.success),
|
||||
error: z.string().optional().openapi(otelTestTraceResultFieldDocs.error),
|
||||
},
|
||||
{ strict: true },
|
||||
) {
|
||||
static schema = super.schema.openapi(otelTestTraceResultDocs);
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
import type { ZodOpenAPIMetadata } from '@asteasolutions/zod-to-openapi';
|
||||
|
||||
import { otelSettingsPublicFieldDocs } from './otel-settings-public.openapi';
|
||||
|
||||
export const otelTestTraceRequestDocs = {
|
||||
description:
|
||||
'The connection details to test against an OTLP collector. Fields managed declaratively via environment variables are overridden with their effective value before the test is sent.',
|
||||
} satisfies ZodOpenAPIMetadata;
|
||||
|
||||
export const otelTestTraceResultDocs = {
|
||||
description: 'The outcome of the test connection to the OTLP collector.',
|
||||
} satisfies ZodOpenAPIMetadata;
|
||||
|
||||
export const otelTestTraceRequestFieldDocs = {
|
||||
exporterProtocol: {
|
||||
description:
|
||||
'The wire protocol used to send the test span. Collectors conventionally serve OTLP/HTTP on port 4318 and OTLP/gRPC on port 4317. The endpoint scheme (`http://` or `https://`) controls TLS for both protocols. Optional: omitting it selects `http/protobuf`, unless an environment variable manages this field, which then supplies the value.',
|
||||
example: 'http/protobuf',
|
||||
},
|
||||
exporterEndpoint: otelSettingsPublicFieldDocs.exporterEndpoint,
|
||||
exporterTracingPath: otelSettingsPublicFieldDocs.exporterTracingPath,
|
||||
exporterServiceName: {
|
||||
description: 'The `service.name` resource attribute reported on the test span.',
|
||||
example: 'n8n',
|
||||
},
|
||||
exporterHeaders: otelSettingsPublicFieldDocs.exporterHeaders,
|
||||
startupConnectivityTimeoutMs: {
|
||||
description: 'How long, in milliseconds, to wait for the collector to respond.',
|
||||
example: 2000,
|
||||
},
|
||||
} as const satisfies Record<string, ZodOpenAPIMetadata>;
|
||||
|
||||
export const otelTestTraceResultFieldDocs = {
|
||||
success: {
|
||||
description: 'Whether the test span was accepted by the collector.',
|
||||
example: true,
|
||||
},
|
||||
error: {
|
||||
description:
|
||||
'The error reported by the collector or exporter. Present only when `success` is false.',
|
||||
example: 'Failed to connect: 401 Unauthorized',
|
||||
},
|
||||
} as const satisfies Record<string, ZodOpenAPIMetadata>;
|
||||
|
|
@ -10,6 +10,7 @@ export const otlpProtocolSchema = z.enum(OTLP_PROTOCOLS);
|
|||
|
||||
export const exporterEndpointSchema = z
|
||||
.string()
|
||||
.trim()
|
||||
.url()
|
||||
.regex(/^https?:\/\//i, 'Endpoint must start with http:// or https://. The scheme selects TLS.');
|
||||
|
||||
|
|
|
|||
|
|
@ -141,7 +141,6 @@ export default defineConfig(
|
|||
'./src/public-api/v1/handlers/ldap/ldap.handler.ts',
|
||||
'./src/public-api/v1/handlers/log-streaming/log-streaming.handler.ts',
|
||||
'./src/public-api/v1/handlers/n8n-packages/n8n-packages.handler.ts',
|
||||
'./src/public-api/v1/handlers/otel/otel.handler.ts',
|
||||
'./src/public-api/v1/handlers/sso-saml/sso-saml.handler.ts',
|
||||
'./src/public-api/v1/handlers/workflows/workflows.handler.ts',
|
||||
],
|
||||
|
|
|
|||
|
|
@ -135,7 +135,6 @@ export default defineConfig({
|
|||
'./src/public-api/v1/handlers/ldap/ldap.handler.ts',
|
||||
'./src/public-api/v1/handlers/log-streaming/log-streaming.handler.ts',
|
||||
'./src/public-api/v1/handlers/n8n-packages/n8n-packages.handler.ts',
|
||||
'./src/public-api/v1/handlers/otel/otel.handler.ts',
|
||||
'./src/public-api/v1/handlers/sso-saml/sso-saml.handler.ts',
|
||||
'./src/public-api/v1/handlers/workflows/workflows.handler.ts',
|
||||
],
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import type {
|
||||
UpdateDataTableRowDto,
|
||||
PublicCreateDestination,
|
||||
TestOtelTraceDto,
|
||||
UpdateSamlConfigurationDto,
|
||||
UpdateLdapConfigurationDto,
|
||||
LdapSyncDto,
|
||||
|
|
@ -166,14 +165,6 @@ export declare namespace SsoSamlRequest {
|
|||
type Update = AuthenticatedRequest<{}, {}, UpdateSamlConfigurationDto>;
|
||||
}
|
||||
|
||||
// ----------------------------------
|
||||
// /settings/otel
|
||||
// ----------------------------------
|
||||
|
||||
export declare namespace OtelSettingsRequest {
|
||||
type Test = AuthenticatedRequest<{}, {}, TestOtelTraceDto>;
|
||||
}
|
||||
|
||||
// ----------------------------------
|
||||
// /settings/ldap
|
||||
// ----------------------------------
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import {
|
||||
OtelSettingsPublicDto,
|
||||
OtelSettingsQueryPublicDto,
|
||||
OtelTestTraceRequestPublicDto,
|
||||
OtelTestTraceResultPublicDto,
|
||||
UpdateOtelSettingsPublicDto,
|
||||
} from '@n8n/api-types';
|
||||
import { ModuleRegistry } from '@n8n/backend-common';
|
||||
|
|
@ -14,6 +16,7 @@ import {
|
|||
ApiTags,
|
||||
Body,
|
||||
Get,
|
||||
Post,
|
||||
PublicApiController,
|
||||
Put,
|
||||
Query,
|
||||
|
|
@ -89,4 +92,23 @@ export class OtelPublicController {
|
|||
|
||||
return toOtelSettingsResponse(updated);
|
||||
}
|
||||
|
||||
@Post('/test-trace')
|
||||
@ApiKeyScope('otel:manage')
|
||||
@ApiSummary('Test the connection to an OTLP collector')
|
||||
@ApiDescription(
|
||||
'Send a single test span to the given OTLP collector and report whether it was accepted. This tests the supplied connection details without changing the stored configuration. Fields managed declaratively via environment variables are overridden with their effective value before the test is sent. Requires the `otel:manage` scope.',
|
||||
)
|
||||
@ApiTags(tags)
|
||||
@ApiResponse(200, OtelTestTraceResultPublicDto)
|
||||
async testOtelTrace(
|
||||
_req: AuthenticatedRequest,
|
||||
_res: Response,
|
||||
@Body body: OtelTestTraceRequestPublicDto,
|
||||
@Query _query: OtelSettingsQueryPublicDto,
|
||||
): Promise<OtelTestTraceResultPublicDto> {
|
||||
const connection = this.settingsService.resolveTestConnection(body);
|
||||
|
||||
return await this.otelService.sendTestTrace(connection);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,34 +0,0 @@
|
|||
import { TestOtelTraceDto } from '@n8n/api-types';
|
||||
import { Container } from '@n8n/di';
|
||||
import { BadRequestError } from '@n8n/errors';
|
||||
|
||||
import { OtelSettingsService } from '@/modules/otel/otel-settings.service';
|
||||
import { OtelService } from '@/modules/otel/otel.service';
|
||||
|
||||
import type { OtelSettingsRequest } from '../../../types';
|
||||
import type { PublicAPIEndpoint } from '../../shared/handler.types';
|
||||
import { apiKeyHasScopeWithGlobalScopeFallback } from '../../shared/middlewares/global.middleware';
|
||||
|
||||
type OtelHandlers = {
|
||||
testOtelTrace: PublicAPIEndpoint<OtelSettingsRequest.Test>;
|
||||
};
|
||||
|
||||
const otelHandlers: OtelHandlers = {
|
||||
testOtelTrace: [
|
||||
apiKeyHasScopeWithGlobalScopeFallback({ scope: 'otel:manage' }),
|
||||
async (req, res) => {
|
||||
const payload = TestOtelTraceDto.safeParse(req.body);
|
||||
if (!payload.success) {
|
||||
throw new BadRequestError(payload.error.errors[0]?.message ?? 'Invalid request body');
|
||||
}
|
||||
|
||||
const settingsService = Container.get(OtelSettingsService);
|
||||
const connection = settingsService.resolveTestConnection(payload.data);
|
||||
const result = await Container.get(OtelService).sendTestTrace(connection);
|
||||
|
||||
return res.json(result);
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export = otelHandlers;
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
post:
|
||||
x-eov-operation-id: testOtelTrace
|
||||
x-required-scope: otel:manage
|
||||
x-eov-operation-handler: v1/handlers/otel/otel.handler
|
||||
tags:
|
||||
- SettingsOtel
|
||||
summary: Test the connection to an OTLP collector
|
||||
description: >
|
||||
Send a single test span to the given OTLP collector and report whether it was accepted. This
|
||||
tests the supplied connection details without changing the stored configuration. Fields managed
|
||||
declaratively via environment variables are overridden with their effective value before the
|
||||
test is sent. Requires the `otel:manage` scope.
|
||||
requestBody:
|
||||
description: The connection details to test.
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '../schemas/otel-test-trace.yml'
|
||||
responses:
|
||||
'200':
|
||||
description: Operation successful.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '../schemas/otel-test-trace-result.yml'
|
||||
'400':
|
||||
$ref: '../../../../shared/spec/responses/badRequest.yml'
|
||||
'401':
|
||||
$ref: '../../../../shared/spec/responses/unauthorized.yml'
|
||||
'403':
|
||||
$ref: '../../../../shared/spec/responses/forbidden.yml'
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
type: object
|
||||
additionalProperties: false
|
||||
description: The outcome of the test connection to the OTLP collector.
|
||||
required:
|
||||
- success
|
||||
properties:
|
||||
success:
|
||||
type: boolean
|
||||
description: Whether the test span was accepted by the collector.
|
||||
example: true
|
||||
error:
|
||||
type: string
|
||||
description: >
|
||||
The error reported by the collector or exporter. Present only when `success` is false.
|
||||
example: 'Failed to connect: 401 Unauthorized'
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
type: object
|
||||
additionalProperties: false
|
||||
description: >
|
||||
The connection details to test against an OTLP collector. Fields managed declaratively via
|
||||
environment variables are overridden with their effective value before the test is sent.
|
||||
required:
|
||||
- exporterEndpoint
|
||||
- exporterTracingPath
|
||||
- exporterServiceName
|
||||
- exporterHeaders
|
||||
- startupConnectivityTimeoutMs
|
||||
properties:
|
||||
exporterProtocol:
|
||||
type: string
|
||||
enum:
|
||||
- http/protobuf
|
||||
- grpc
|
||||
default: http/protobuf
|
||||
description: >
|
||||
The wire protocol used to send the test span. Collectors conventionally serve OTLP/HTTP on
|
||||
port 4318 and OTLP/gRPC on port 4317. The endpoint scheme (`http://` or `https://`)
|
||||
controls TLS for both protocols. Optional: omitting it selects `http/protobuf`, unless an
|
||||
environment variable manages this field, which then supplies the value.
|
||||
example: http/protobuf
|
||||
exporterEndpoint:
|
||||
type: string
|
||||
format: uri
|
||||
description: >
|
||||
The base URL of the OTLP collector to export traces to. The value must be an `http://` or
|
||||
`https://` URL, because the scheme selects TLS — for gRPC, `https://` uses SSL and
|
||||
`http://` is plaintext.
|
||||
example: http://localhost:4318
|
||||
exporterTracingPath:
|
||||
type: string
|
||||
description: >
|
||||
The path appended to the endpoint for the OTLP traces signal. Ignored when
|
||||
`exporterProtocol` is `grpc`, which takes no URL path.
|
||||
example: /v1/traces
|
||||
exporterServiceName:
|
||||
type: string
|
||||
minLength: 1
|
||||
description: The `service.name` resource attribute reported on the test span.
|
||||
example: n8n
|
||||
exporterHeaders:
|
||||
type: string
|
||||
description: >
|
||||
Additional headers sent to the OTLP collector, as a single string of comma-separated
|
||||
`key=value` pairs (e.g. `authorization=Bearer my-token,x-tenant-id=acme`). Whitespace
|
||||
around each key and value is trimmed; a value may contain spaces but not commas. Use an
|
||||
empty string when unused.
|
||||
example: authorization=Bearer my-token,x-tenant-id=acme
|
||||
startupConnectivityTimeoutMs:
|
||||
type: integer
|
||||
minimum: 0
|
||||
description: How long, in milliseconds, to wait for the collector to respond.
|
||||
example: 2000
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
operationId: testOtelTrace
|
||||
tags:
|
||||
- SettingsOtel
|
||||
summary: Test the connection to an OTLP collector
|
||||
description: Send a single test span to the given OTLP collector and report whether it was accepted. This tests the supplied connection details without changing the stored configuration. Fields managed declaratively via environment variables are overridden with their effective value before the test is sent. Requires the `otel:manage` scope.
|
||||
x-required-scope: otel:manage
|
||||
x-eov-operation-id: unreachable
|
||||
x-eov-operation-handler: v1/handlers/decorator-routed.handler
|
||||
x-decorator-routed: true
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
exporterProtocol:
|
||||
type: string
|
||||
enum:
|
||||
- http/protobuf
|
||||
- grpc
|
||||
default: http/protobuf
|
||||
description: 'The wire protocol used to send the test span. Collectors conventionally serve OTLP/HTTP on port 4318 and OTLP/gRPC on port 4317. The endpoint scheme (`http://` or `https://`) controls TLS for both protocols. Optional: omitting it selects `http/protobuf`, unless an environment variable manages this field, which then supplies the value.'
|
||||
example: http/protobuf
|
||||
exporterEndpoint:
|
||||
type: string
|
||||
format: uri
|
||||
pattern: ^https?:\/\/
|
||||
description: The base URL of the OTLP collector to export traces to. The value must be an `http://` or `https://` URL, because the scheme selects TLS — for gRPC, `https://` uses SSL and `http://` is plaintext.
|
||||
example: http://localhost:4318
|
||||
exporterTracingPath:
|
||||
type: string
|
||||
description: The path appended to the endpoint for the OTLP traces signal. Ignored when `exporterProtocol` is `grpc`, which takes no URL path.
|
||||
example: /v1/traces
|
||||
exporterServiceName:
|
||||
type: string
|
||||
minLength: 1
|
||||
description: The `service.name` resource attribute reported on the test span.
|
||||
example: n8n
|
||||
exporterHeaders:
|
||||
type: string
|
||||
description: Additional headers sent to the OTLP collector, as a single string of comma-separated `key=value` pairs (e.g. `authorization=Bearer my-token,x-tenant-id=acme`). Whitespace around each key and value is trimmed; a value may contain spaces but not commas. Use an empty string when unused.
|
||||
example: authorization=Bearer my-token,x-tenant-id=acme
|
||||
startupConnectivityTimeoutMs:
|
||||
type: integer
|
||||
minimum: 0
|
||||
description: How long, in milliseconds, to wait for the collector to respond.
|
||||
example: 2000
|
||||
required:
|
||||
- exporterEndpoint
|
||||
- exporterTracingPath
|
||||
- exporterServiceName
|
||||
- exporterHeaders
|
||||
- startupConnectivityTimeoutMs
|
||||
additionalProperties: false
|
||||
description: The connection details to test against an OTLP collector. Fields managed declaratively via environment variables are overridden with their effective value before the test is sent.
|
||||
responses:
|
||||
'200':
|
||||
description: Operation successful.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
success:
|
||||
type: boolean
|
||||
description: Whether the test span was accepted by the collector.
|
||||
example: true
|
||||
error:
|
||||
type: string
|
||||
description: The error reported by the collector or exporter. Present only when `success` is false.
|
||||
example: 'Failed to connect: 401 Unauthorized'
|
||||
required:
|
||||
- success
|
||||
additionalProperties: false
|
||||
description: The outcome of the test connection to the OTLP collector.
|
||||
'400':
|
||||
$ref: ../../../../shared/spec/responses/badRequest.yml
|
||||
'401':
|
||||
$ref: ../../../../shared/spec/responses/unauthorized.yml
|
||||
'403':
|
||||
$ref: ../../../../shared/spec/responses/forbidden.yml
|
||||
'415':
|
||||
$ref: ../../../../shared/spec/responses/unsupportedMediaType.yml
|
||||
|
|
@ -176,6 +176,9 @@ paths:
|
|||
$ref: ./handlers/settings/spec/paths/getOtelSettings.generated.yml
|
||||
put:
|
||||
$ref: ./handlers/settings/spec/paths/updateOtelSettings.generated.yml
|
||||
/settings/otel/test-trace:
|
||||
post:
|
||||
$ref: ./handlers/settings/spec/paths/testOtelTrace.generated.yml
|
||||
/projects:
|
||||
get:
|
||||
$ref: ./handlers/projects/spec/paths/getProjects.generated.yml
|
||||
|
|
|
|||
|
|
@ -83,8 +83,6 @@ paths:
|
|||
$ref: './handlers/ldap/spec/paths/settings.ldap.yml'
|
||||
/settings/ldap/sync:
|
||||
$ref: './handlers/ldap/spec/paths/settings.ldap.sync.yml'
|
||||
/settings/otel/test-trace:
|
||||
$ref: './handlers/otel/spec/paths/settings.otel.test-trace.yml'
|
||||
/settings/sso/saml:
|
||||
$ref: './handlers/sso-saml/spec/paths/settings.sso.saml.yml'
|
||||
/workflows/{id}/{versionId}:
|
||||
|
|
|
|||
|
|
@ -248,6 +248,19 @@ describe('OpenTelemetry settings in Public API', () => {
|
|||
expect(replaced.body.exporterProtocol).toBe('http/protobuf');
|
||||
});
|
||||
|
||||
it('stores the exporter endpoint without surrounding whitespace', async () => {
|
||||
const response = await testServer
|
||||
.publicApiAgentFor(owner)
|
||||
.put('/settings/otel')
|
||||
.send({ ...validSettings, exporterEndpoint: ` ${validSettings.exporterEndpoint} ` });
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body.exporterEndpoint).toBe(validSettings.exporterEndpoint);
|
||||
|
||||
const read = await testServer.publicApiAgentFor(owner).get('/settings/otel');
|
||||
expect(read.body.exporterEndpoint).toBe(validSettings.exporterEndpoint);
|
||||
});
|
||||
|
||||
it('rejects an unsupported exporter protocol with 400', async () => {
|
||||
const response = await testServer
|
||||
.publicApiAgentFor(owner)
|
||||
|
|
@ -529,6 +542,22 @@ describe('OpenTelemetry settings in Public API', () => {
|
|||
expect(response.body).toEqual({ success: false, error: '401 Unauthorized' });
|
||||
});
|
||||
|
||||
it('sends the exporter endpoint to the collector without surrounding whitespace', async () => {
|
||||
const sendTestTrace = vi
|
||||
.spyOn(Container.get(OtelService), 'sendTestTrace')
|
||||
.mockResolvedValue({ success: true });
|
||||
|
||||
const response = await testServer
|
||||
.publicApiAgentFor(owner)
|
||||
.post('/settings/otel/test-trace')
|
||||
.send({ ...testConnection, exporterEndpoint: ` ${testConnection.exporterEndpoint} ` });
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(sendTestTrace).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ exporterEndpoint: testConnection.exporterEndpoint }),
|
||||
);
|
||||
});
|
||||
|
||||
it('rejects a partial body with 400', async () => {
|
||||
const response = await testServer
|
||||
.publicApiAgentFor(owner)
|
||||
|
|
@ -538,6 +567,20 @@ describe('OpenTelemetry settings in Public API', () => {
|
|||
expect(response.status).toBe(400);
|
||||
});
|
||||
|
||||
it('rejects an unknown field with 400', async () => {
|
||||
const sendTestTrace = vi
|
||||
.spyOn(Container.get(OtelService), 'sendTestTrace')
|
||||
.mockResolvedValue({ success: true });
|
||||
|
||||
const response = await testServer
|
||||
.publicApiAgentFor(owner)
|
||||
.post('/settings/otel/test-trace')
|
||||
.send({ ...testConnection, unknown: true });
|
||||
|
||||
expect(response.status).toBe(400);
|
||||
expect(sendTestTrace).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('accepts a connection body written before exporterProtocol existed', async () => {
|
||||
const sendTestTrace = vi
|
||||
.spyOn(Container.get(OtelService), 'sendTestTrace')
|
||||
|
|
@ -563,5 +606,21 @@ describe('OpenTelemetry settings in Public API', () => {
|
|||
|
||||
expect(response.status).toBe(401);
|
||||
});
|
||||
|
||||
it('rejects with 403 when the API key lacks the otel:manage scope', async () => {
|
||||
const scopedOwner = await createOwnerWithApiKey({ scopes: ['workflow:read'] });
|
||||
const sendTestTrace = vi
|
||||
.spyOn(Container.get(OtelService), 'sendTestTrace')
|
||||
.mockResolvedValue({ success: true });
|
||||
|
||||
const response = await testServer
|
||||
.publicApiAgentFor(scopedOwner)
|
||||
.post('/settings/otel/test-trace')
|
||||
.send(testConnection);
|
||||
|
||||
expect(response.status).toBe(403);
|
||||
expect(response.body).toEqual({ message: 'Forbidden' });
|
||||
expect(sendTestTrace).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user