@@ -3,6 +3,7 @@ import { UpdateOtelSettingsDto } from '../update-otel-settings.dto';
33
44const validSettings = {
55 enabled : true ,
6+ exporterProtocol : 'http/protobuf' ,
67 exporterEndpoint : 'http://localhost:4318' ,
78 exporterTracingPath : '/v1/traces' ,
89 exporterServiceName : 'n8n' ,
@@ -14,17 +15,29 @@ const validSettings = {
1415 productionExecutionsOnly : true ,
1516} ;
1617
18+ const defaultedFields = [ 'exporterProtocol' ] ;
19+
1720describe ( 'UpdateOtelSettingsDto' , ( ) => {
18- it ( 'requires every field (stays strict, so a partial body is rejected)' , ( ) => {
21+ it ( 'requires every field except the defaulted ones ( so a partial body is rejected)' , ( ) => {
1922 const result = UpdateOtelSettingsDto . safeParse ( { } ) ;
2023
2124 assert ( ! result . success , 'Expected validation to fail for an empty body' ) ;
2225
23- // An empty body must report every field as missing. A field that carries a
24- // default would parse successfully instead of erroring — this guards the
25- // public API PUT against silently resetting omitted fields.
2626 const missing = [ ...new Set ( result . error . issues . map ( ( issue ) => String ( issue . path [ 0 ] ) ) ) ] . sort ( ) ;
27- expect ( missing ) . toEqual ( Object . keys ( validSettings ) . sort ( ) ) ;
27+ expect ( missing ) . toEqual (
28+ Object . keys ( validSettings )
29+ . filter ( ( key ) => ! defaultedFields . includes ( key ) )
30+ . sort ( ) ,
31+ ) ;
32+ } ) ;
33+
34+ it ( 'defaults the exporter protocol when omitted (body predates the field)' , ( ) => {
35+ const { exporterProtocol : _omitted , ...withoutProtocol } = validSettings ;
36+
37+ const result = UpdateOtelSettingsDto . safeParse ( withoutProtocol ) ;
38+
39+ assert ( result . success , 'Expected a body without exporterProtocol to stay valid' ) ;
40+ expect ( result . data . exporterProtocol ) . toBe ( 'http/protobuf' ) ;
2841 } ) ;
2942
3043 it ( 'accepts a full body' , ( ) => {
@@ -48,6 +61,70 @@ describe('UpdateOtelSettingsDto', () => {
4861 ) ;
4962 } ) ;
5063
64+ it . each ( [ 'http/protobuf' , 'grpc' ] ) ( 'accepts the %s exporter protocol' , ( exporterProtocol ) => {
65+ const result = UpdateOtelSettingsDto . safeParse ( { ...validSettings , exporterProtocol } ) ;
66+
67+ assert ( result . success , `Expected ${ exporterProtocol } to be a valid exporter protocol` ) ;
68+ expect ( result . data . exporterProtocol ) . toBe ( exporterProtocol ) ;
69+ } ) ;
70+
71+ it . each ( [ 'http/json' , 'HTTP/PROTOBUF' , 'http' , 'gRPC' , '' ] ) (
72+ 'rejects %p as an exporter protocol' ,
73+ ( exporterProtocol ) => {
74+ const result = UpdateOtelSettingsDto . safeParse ( { ...validSettings , exporterProtocol } ) ;
75+
76+ assert ( ! result . success , `Expected ${ exporterProtocol } to be an invalid exporter protocol` ) ;
77+ expect ( result . error . issues ) . toContainEqual (
78+ expect . objectContaining ( {
79+ code : 'invalid_enum_value' ,
80+ path : [ 'exporterProtocol' ] ,
81+ } ) ,
82+ ) ;
83+ } ,
84+ ) ;
85+
86+ it . each ( [
87+ 'http://localhost:4318' ,
88+ 'https://collector.example.com:4317' ,
89+ 'http://[::1]:4317' ,
90+ 'HTTP://localhost:4318' ,
91+ 'HttpS://collector.example.com:4317' ,
92+ ] ) ( 'accepts %p as an exporter endpoint' , ( exporterEndpoint ) => {
93+ const result = UpdateOtelSettingsDto . safeParse ( { ...validSettings , exporterEndpoint } ) ;
94+
95+ assert ( result . success , `Expected ${ exporterEndpoint } to be a valid exporter endpoint` ) ;
96+ expect ( result . data . exporterEndpoint ) . toBe ( exporterEndpoint ) ;
97+ } ) ;
98+
99+ it . each ( [ 'localhost:4318' , 'grpc://host:4317' , 'ftp://x' ] ) (
100+ 'rejects %p as an exporter endpoint' ,
101+ ( exporterEndpoint ) => {
102+ const result = UpdateOtelSettingsDto . safeParse ( { ...validSettings , exporterEndpoint } ) ;
103+
104+ assert ( ! result . success , `Expected ${ exporterEndpoint } to be an invalid exporter endpoint` ) ;
105+ expect ( result . error . issues ) . toContainEqual (
106+ expect . objectContaining ( { path : [ 'exporterEndpoint' ] } ) ,
107+ ) ;
108+ } ,
109+ ) ;
110+
111+ it ( 'explains why a non-http endpoint scheme is rejected' , ( ) => {
112+ const result = UpdateOtelSettingsDto . safeParse ( {
113+ ...validSettings ,
114+ exporterEndpoint : 'grpc://host:4317' ,
115+ } ) ;
116+
117+ assert ( ! result . success , 'Expected validation to fail for a grpc:// exporter endpoint' ) ;
118+ expect ( result . error . issues ) . toContainEqual (
119+ expect . objectContaining ( {
120+ code : 'invalid_string' ,
121+ validation : 'regex' ,
122+ path : [ 'exporterEndpoint' ] ,
123+ message : 'Endpoint must start with http:// or https://. The scheme selects TLS.' ,
124+ } ) ,
125+ ) ;
126+ } ) ;
127+
51128 it ( 'rejects a sample rate outside the 0..1 range' , ( ) => {
52129 const result = UpdateOtelSettingsDto . safeParse ( { ...validSettings , tracesSampleRate : 2 } ) ;
53130
@@ -64,39 +141,76 @@ describe('UpdateOtelSettingsDto', () => {
64141
65142describe ( 'TestOtelTraceDto' , ( ) => {
66143 const validConnection = {
144+ exporterProtocol : 'http/protobuf' ,
67145 exporterEndpoint : 'http://localhost:4318' ,
68146 exporterTracingPath : '/v1/traces' ,
69147 exporterServiceName : 'n8n' ,
70148 exporterHeaders : '' ,
71149 startupConnectivityTimeoutMs : 2_000 ,
72150 } ;
73151
74- it ( 'requires every connection field (stays strict)' , ( ) => {
152+ it ( 'requires every connection field except the defaulted ones (stays strict)' , ( ) => {
75153 const result = TestOtelTraceDto . safeParse ( { } ) ;
76154
77155 assert ( ! result . success , 'Expected validation to fail for an empty body' ) ;
78156
79157 const missing = [ ...new Set ( result . error . issues . map ( ( issue ) => String ( issue . path [ 0 ] ) ) ) ] . sort ( ) ;
80- expect ( missing ) . toEqual ( Object . keys ( validConnection ) . sort ( ) ) ;
158+ expect ( missing ) . toEqual (
159+ Object . keys ( validConnection )
160+ . filter ( ( key ) => ! defaultedFields . includes ( key ) )
161+ . sort ( ) ,
162+ ) ;
81163 } ) ;
82164
83- it ( 'accepts a full connection body' , ( ) => {
84- const result = TestOtelTraceDto . safeParse ( validConnection ) ;
85- expect ( result . success ) . toBe ( true ) ;
165+ it ( 'accepts a gRPC connection body' , ( ) => {
166+ const result = TestOtelTraceDto . safeParse ( {
167+ ...validConnection ,
168+ exporterProtocol : 'grpc' ,
169+ exporterEndpoint : 'http://localhost:4317' ,
170+ } ) ;
171+
172+ assert ( result . success , 'Expected a gRPC connection body to be valid' ) ;
173+ expect ( result . data . exporterProtocol ) . toBe ( 'grpc' ) ;
86174 } ) ;
87175
88- it ( 'rejects an invalid exporter endpoint ' , ( ) => {
176+ it ( 'rejects an unsupported exporter protocol ' , ( ) => {
89177 const result = TestOtelTraceDto . safeParse ( {
90178 ...validConnection ,
91- exporterEndpoint : 'not-a-url ' ,
179+ exporterProtocol : 'http/json ' ,
92180 } ) ;
93181
94- assert ( ! result . success , 'Expected validation to fail for an invalid exporter endpoint' ) ;
182+ assert ( ! result . success , 'Expected validation to fail for an unsupported exporter protocol' ) ;
183+ expect ( result . error . issues ) . toContainEqual (
184+ expect . objectContaining ( {
185+ code : 'invalid_enum_value' ,
186+ path : [ 'exporterProtocol' ] ,
187+ } ) ,
188+ ) ;
189+ } ) ;
190+
191+ it ( 'accepts an https exporter endpoint' , ( ) => {
192+ const result = TestOtelTraceDto . safeParse ( {
193+ ...validConnection ,
194+ exporterEndpoint : 'https://collector.example.com:4317' ,
195+ } ) ;
196+
197+ assert ( result . success , 'Expected an https exporter endpoint to be valid' ) ;
198+ expect ( result . data . exporterEndpoint ) . toBe ( 'https://collector.example.com:4317' ) ;
199+ } ) ;
200+
201+ it ( 'rejects a non-http exporter endpoint scheme' , ( ) => {
202+ const result = TestOtelTraceDto . safeParse ( {
203+ ...validConnection ,
204+ exporterEndpoint : 'grpc://host:4317' ,
205+ } ) ;
206+
207+ assert ( ! result . success , 'Expected validation to fail for a grpc:// exporter endpoint' ) ;
95208 expect ( result . error . issues ) . toContainEqual (
96209 expect . objectContaining ( {
97210 code : 'invalid_string' ,
98- validation : 'url ' ,
211+ validation : 'regex ' ,
99212 path : [ 'exporterEndpoint' ] ,
213+ message : 'Endpoint must start with http:// or https://. The scheme selects TLS.' ,
100214 } ) ,
101215 ) ;
102216 } ) ;
0 commit comments