From 6f5c63334574da2240b4e2ba85654cbc78b3947b Mon Sep 17 00:00:00 2001 From: arcogabbo Date: Fri, 27 Jun 2025 11:46:49 +0200 Subject: [PATCH 1/8] add operation to mocks --- __mocks__/api.yaml | 15 +++++++++++++++ __mocks__/openapi_v3/api.yaml | 19 ++++++++++++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/__mocks__/api.yaml b/__mocks__/api.yaml index 2c50aa5..882c107 100644 --- a/__mocks__/api.yaml +++ b/__mocks__/api.yaml @@ -307,6 +307,21 @@ paths: responses: "200": description: "Ok" + /test-with-overridden-security: + get: + operationId: "testOverriddenSecurity" + security: + - bearerToken: [] + responses: + "200": + description: "Ok" + /test-with-overridden-security-no-auth: + get: + operationId: "testOverriddenSecurity" + security: [] + responses: + "200": + description: "Ok" definitions: Person: diff --git a/__mocks__/openapi_v3/api.yaml b/__mocks__/openapi_v3/api.yaml index abc4938..526d6ce 100644 --- a/__mocks__/openapi_v3/api.yaml +++ b/__mocks__/openapi_v3/api.yaml @@ -5,6 +5,8 @@ info: version: 1.0.0 servers: - url: https://localhost/api/v1 +security: + - customToken : [] paths: /test-auth-bearer: get: @@ -335,7 +337,22 @@ paths: responses: "200": description: "Ok" - + /test-with-overridden-security: + get: + operationId: "testOverriddenSecurity" + security: + - bearerToken: [] + responses: + "200": + description: "Ok" + /test-with-overridden-security-no-auth: + get: + operationId: "testOverriddenSecurity" + security: [] + responses: + "200": + description: "Ok" + # ------------- # Components # ------------- From 85f36316d6686d4a2ca3ca6bc5dcfa347e480f98 Mon Sep 17 00:00:00 2001 From: arcogabbo Date: Fri, 27 Jun 2025 11:47:08 +0200 Subject: [PATCH 2/8] fix: allow security override at operation level --- .../gen-api-models/__tests__/parse.test.ts | 70 +++++++++++++++++++ src/commands/gen-api-models/parse.v2.ts | 32 +++++++-- src/commands/gen-api-models/parse.v3.ts | 26 +++++-- src/lib/utils.ts | 11 +++ 4 files changed, 126 insertions(+), 13 deletions(-) diff --git a/src/commands/gen-api-models/__tests__/parse.test.ts b/src/commands/gen-api-models/__tests__/parse.test.ts index 10a0fb9..9a45791 100644 --- a/src/commands/gen-api-models/__tests__/parse.test.ts +++ b/src/commands/gen-api-models/__tests__/parse.test.ts @@ -269,6 +269,76 @@ describe.each` ); }); + it("should parse an operation with security overridden", () => { + const parsed = getParser(spec).parseOperation( + //@ts-ignore + spec, + "/test-with-overridden-security", + [ + //global security defined + { + authScheme: "bearer", + headerName: "Authorization", + in: "header", + name: "bearerToken", + tokenType: "apiKey", + type: "string" + } + ], + "undefined", + "undefined" + )("get"); + + expect(parsed).toEqual( + expect.objectContaining({ + method: "get", + path: "/test-with-overridden-security", + headers: expect.arrayContaining(["Authorization"]), + parameters: expect.arrayContaining([ + { + authScheme: "bearer", + tokenType: "apiKey", + headerName: "Authorization", + in: "header", + name: "bearerToken", + type: "string" + } + ]) + }) + ); + }); + + it("should parse an operation with security overridden and no auth specified", () => { + const parsed = getParser(spec).parseOperation( + //@ts-ignore + spec, + "/test-with-overridden-security-no-auth", + [ + //global security defined + { + authScheme: "bearer", + headerName: "Authorization", + in: "header", + name: "bearerToken", + tokenType: "apiKey", + type: "string" + } + ], + "undefined", + "undefined" + )("get"); + + console.log(parsed); + expect(parsed).toEqual( + expect.objectContaining({ + method: "get", + path: "/test-with-overridden-security-no-auth", + headers: expect.not.arrayContaining(["Authorization"]), + parameters: [] + }) + ); + }); + // We are not currently supporting responses as $ref it("should parse an operation with response with no schema setting type to undefined", () => { const parsed = getParser(spec).parseOperation( diff --git a/src/commands/gen-api-models/parse.v2.ts b/src/commands/gen-api-models/parse.v2.ts index 4c5e259..34b50eb 100644 --- a/src/commands/gen-api-models/parse.v2.ts +++ b/src/commands/gen-api-models/parse.v2.ts @@ -4,7 +4,7 @@ import { ITuple2, Tuple2, Tuple3 } from "@pagopa/ts-commons/lib/tuples"; import { OpenAPIV2, IJsonSchema, OpenAPI } from "openapi-types"; -import { uncapitalize } from "../../lib/utils"; +import { isAuthHeaderParameter, uncapitalize } from "../../lib/utils"; import { inferDefinitionType } from "./parse.utils"; import { ExtendedOpenAPIV2SecuritySchemeApiKey, @@ -301,14 +301,32 @@ export const parseOperation = ( ) : []; - const authHeadersAndParams = operation.security - ? // eslint-disable-next-line @typescript-eslint/no-use-before-define - getAuthHeaders(securityDefinitions, operation.security) - : []; + // eslint-disable-next-line functional/no-let + let refinedExtraParameters; + const isGlobalSecurityDefined = api.securityDefinitions !== undefined; + if (isGlobalSecurityDefined && operation.security) { + // we encountered an operation level override of security definition + // so we filter out already defined auth for this operation and proceed + refinedExtraParameters = extraParameters.filter( + element => !isAuthHeaderParameter(element) + ); + } else { + refinedExtraParameters = extraParameters; + } + + const authHeadersAndParams = + operation.security && operation.security.length !== 0 + ? // eslint-disable-next-line @typescript-eslint/no-use-before-define + getAuthHeaders(securityDefinitions, operation.security) + : []; const authParams = authHeadersAndParams; - const parameters = [...extraParameters, ...authParams, ...operationParams]; + const parameters = [ + ...refinedExtraParameters, + ...authParams, + ...operationParams + ]; const contentTypeHeaders = (method === "post" || method === "put") && @@ -319,7 +337,7 @@ export const parseOperation = ( // eslint-disable-next-line @typescript-eslint/no-use-before-define const authHeaders = authHeadersAndParams.map(pick("headerName")); - const extraHeaders = extraParameters + const extraHeaders = refinedExtraParameters .filter((p): p is IHeaderParameterInfo => p.in === "header") // eslint-disable-next-line @typescript-eslint/no-use-before-define .map(pick("headerName")); diff --git a/src/commands/gen-api-models/parse.v3.ts b/src/commands/gen-api-models/parse.v3.ts index 75412ec..e1f4c47 100644 --- a/src/commands/gen-api-models/parse.v3.ts +++ b/src/commands/gen-api-models/parse.v3.ts @@ -5,7 +5,7 @@ import { ITuple2, Tuple2, Tuple3 } from "@pagopa/ts-commons/lib/tuples"; import { OpenAPI, OpenAPIV3, IJsonSchema } from "openapi-types"; -import { uncapitalize } from "../../lib/utils"; +import { isAuthHeaderParameter, uncapitalize } from "../../lib/utils"; import { inferDefinitionType } from "./parse.utils"; import { ExtendedOpenAPIV2SecuritySchemeApiKey, @@ -318,9 +318,23 @@ export const parseOperation = ( .filter((e): e is IParameterInfo => typeof e !== "undefined") : []; - const authHeadersAndParams = operation.security - ? getAuthHeaders(securityDefinitions, operation.security) - : []; + // eslint-disable-next-line functional/no-let + let refinedExtraParameters; + const isGlobalSecurityDefined = api.security !== undefined; + if (isGlobalSecurityDefined && operation.security) { + // we encountered an operation level override of security definition + // so we filter out already defined auth for this operation and proceed + refinedExtraParameters = extraParameters.filter( + element => !isAuthHeaderParameter(element) + ); + } else { + refinedExtraParameters = extraParameters; + } + + const authHeadersAndParams = + operation.security && operation.security.length !== 0 + ? getAuthHeaders(securityDefinitions, operation.security) + : []; const authParams = authHeadersAndParams; @@ -377,7 +391,7 @@ export const parseOperation = ( } const parameters = [ - ...extraParameters, + ...refinedExtraParameters, ...authParams, ...operationParams, ...bodyParam @@ -391,7 +405,7 @@ export const parseOperation = ( const authHeaders = authHeadersAndParams.map(pick("headerName")); - const extraHeaders = extraParameters + const extraHeaders = refinedExtraParameters .filter((p): p is IHeaderParameterInfo => p.in === "header") .map(pick("headerName")); diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 67f5c47..d6c172f 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -3,6 +3,13 @@ * * @param s string to be capitalized */ + +import { + IAuthHeaderParameterInfo, + IHeaderParameterInfo, + IParameterInfo +} from "../commands/gen-api-models/types"; + // eslint-disable-next-line prefer-arrow/prefer-arrow-functions export function capitalize(s: string): string { return `${s[0].toUpperCase()}${s.slice(1)}`; @@ -60,3 +67,7 @@ export function withGenerics( // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-function-return-type export const pipe = (...fns: ReadonlyArray<(a: any) => any>) => (value: any) => fns.reduce((p, f) => f(p), value); + +export const isAuthHeaderParameter = ( + parameter: IHeaderParameterInfo | IParameterInfo +): parameter is IAuthHeaderParameterInfo => "authScheme" in parameter; From 382f24e9fac62e19984e1cc375143c59000adb54 Mon Sep 17 00:00:00 2001 From: arcogabbo Date: Fri, 27 Jun 2025 11:57:11 +0200 Subject: [PATCH 3/8] fix: tests and snapshots --- __mocks__/api.yaml | 2 + .../__snapshots__/index.test.ts.snap | 300 ++++++++++++++---- .../gen-api-models/__tests__/index.test.ts | 25 +- 3 files changed, 267 insertions(+), 60 deletions(-) diff --git a/__mocks__/api.yaml b/__mocks__/api.yaml index 882c107..3bcfe06 100644 --- a/__mocks__/api.yaml +++ b/__mocks__/api.yaml @@ -7,6 +7,8 @@ host: localhost basePath: /api/v1 schemes: - https +security: + - customToken: [] securityDefinitions: bearerToken: type: apiKey diff --git a/src/commands/gen-api-models/__tests__/__snapshots__/index.test.ts.snap b/src/commands/gen-api-models/__tests__/__snapshots__/index.test.ts.snap index e8ea05e..63f955a 100644 --- a/src/commands/gen-api-models/__tests__/__snapshots__/index.test.ts.snap +++ b/src/commands/gen-api-models/__tests__/__snapshots__/index.test.ts.snap @@ -952,7 +952,11 @@ import { TestHeaderWithSchemaRefT, testHeaderWithSchemaRefDefaultDecoder, TestHeaderOptionalT, - testHeaderOptionalDefaultDecoder + testHeaderOptionalDefaultDecoder, + TestOverriddenSecurityT, + testOverriddenSecurityDefaultDecoder, + TestOverriddenSecurityT, + testOverriddenSecurityDefaultDecoder } from \\"./requestTypes\\"; // This is a placeholder for undefined when dealing with object keys @@ -980,7 +984,9 @@ export type ApiOperation = TypeofApiCall & TypeofApiCall & TypeofApiCall & TypeofApiCall & - TypeofApiCall; + TypeofApiCall & + TypeofApiCall & + TypeofApiCall; export type ParamKeys = keyof (TypeofApiParams & TypeofApiParams & @@ -1001,7 +1007,9 @@ export type ParamKeys = keyof (TypeofApiParams & TypeofApiParams & TypeofApiParams & TypeofApiParams & - TypeofApiParams); + TypeofApiParams & + TypeofApiParams & + TypeofApiParams); /** * Defines an adapter for TypeofApiCall which omit one or more parameters in the signature @@ -1044,7 +1052,9 @@ export type WithDefaultsT< | TestWithEmptyResponseT | TestParamWithSchemaRefT | TestHeaderWithSchemaRefT - | TestHeaderOptionalT, + | TestHeaderOptionalT + | TestOverriddenSecurityT + | TestOverriddenSecurityT, K >; @@ -1105,6 +1115,10 @@ export type Client< readonly testHeaderWithSchemaRef: TypeofApiCall; readonly testHeaderOptional: TypeofApiCall; + + readonly testOverriddenSecurity: TypeofApiCall; + + readonly testOverriddenSecurity: TypeofApiCall; } : { readonly testAuthBearer: TypeofApiCall< @@ -1246,6 +1260,20 @@ export type Client< Omit, K> > >; + + readonly testOverriddenSecurity: TypeofApiCall< + ReplaceRequestParams< + TestOverriddenSecurityT, + Omit, K> + > + >; + + readonly testOverriddenSecurity: TypeofApiCall< + ReplaceRequestParams< + TestOverriddenSecurityT, + Omit, K> + > + >; }; /** @@ -1335,8 +1363,9 @@ export function createClient({ > = { method: \\"get\\", - headers: () => ({}), - + headers: ({ [\\"customToken\\"]: customToken }) => ({ + \\"custom-token\\": customToken + }), response_decoder: testMultipleSuccessDefaultDecoder(), url: ({}) => \`\${basePath}/test-multiple-success\`, @@ -1353,7 +1382,9 @@ export function createClient({ > = { method: \\"post\\", - headers: () => ({ + headers: ({ [\\"customToken\\"]: customToken }) => ({ + \\"custom-token\\": customToken, + \\"Content-Type\\": \\"multipart/form-data\\" }), response_decoder: testFileUploadDefaultDecoder(), @@ -1378,7 +1409,9 @@ export function createClient({ // reference: https://github.com/github/fetch/issues/505#issuecomment-293064470 // The solution is to skip the Content-Type header and let fetch add it for us. // @ts-ignore as IRequestType would require something - headers: () => ({}), + headers: ({ [\\"customToken\\"]: customToken }) => ({ + \\"custom-token\\": customToken + }), response_decoder: testBinaryFileUploadDefaultDecoder(), url: ({}) => \`\${basePath}/test-binary-file-upload\`, @@ -1405,8 +1438,9 @@ export function createClient({ > = { method: \\"get\\", - headers: () => ({}), - + headers: ({ [\\"customToken\\"]: customToken }) => ({ + \\"custom-token\\": customToken + }), response_decoder: testBinaryFileDownloadDefaultDecoder(), url: ({}) => \`\${basePath}/test-binary-file-download\`, @@ -1423,8 +1457,9 @@ export function createClient({ > = { method: \\"get\\", - headers: () => ({}), - + headers: ({ [\\"customToken\\"]: customToken }) => ({ + \\"custom-token\\": customToken + }), response_decoder: testResponseHeaderDefaultDecoder(), url: ({}) => \`\${basePath}/test-response-header\`, @@ -1441,7 +1476,9 @@ export function createClient({ > = { method: \\"post\\", - headers: () => ({ + headers: ({ [\\"customToken\\"]: customToken }) => ({ + \\"custom-token\\": customToken, + \\"Content-Type\\": \\"application/json\\" }), response_decoder: testParameterWithReferenceDefaultDecoder(), @@ -1463,7 +1500,9 @@ export function createClient({ > = { method: \\"post\\", - headers: () => ({ + headers: ({ [\\"customToken\\"]: customToken }) => ({ + \\"custom-token\\": customToken, + \\"Content-Type\\": \\"application/json\\" }), response_decoder: testParameterWithBodyReferenceDefaultDecoder(), @@ -1490,7 +1529,9 @@ export function createClient({ > = { method: \\"put\\", - headers: () => ({ + headers: ({ [\\"customToken\\"]: customToken }) => ({ + \\"custom-token\\": customToken, + \\"Content-Type\\": \\"application/json\\" }), response_decoder: putTestParameterWithBodyReferenceDefaultDecoder(), @@ -1518,9 +1559,12 @@ export function createClient({ method: \\"get\\", headers: ({ + [\\"customToken\\"]: customToken, [\\"headerInlineParam\\"]: headerInlineParam, [\\"x-header-param\\"]: xHeaderParam }) => ({ + \\"custom-token\\": customToken, + headerInlineParam: headerInlineParam, \\"x-header-param\\": xHeaderParam @@ -1544,9 +1588,12 @@ export function createClient({ method: \\"get\\", headers: ({ + [\\"customToken\\"]: customToken, [\\"headerInlineParam\\"]: headerInlineParam, [\\"x-header-param\\"]: xHeaderParam }) => ({ + \\"custom-token\\": customToken, + headerInlineParam: headerInlineParam, \\"x-header-param\\": xHeaderParam @@ -1569,8 +1616,9 @@ export function createClient({ > = { method: \\"get\\", - headers: () => ({}), - + headers: ({ [\\"customToken\\"]: customToken }) => ({ + \\"custom-token\\": customToken + }), response_decoder: testWithTwoParamsDefaultDecoder(), url: ({ [\\"first-param\\"]: firstParam, [\\"second-param\\"]: secondParam }) => \`\${basePath}/test-two-path-params/\${firstParam}/\${secondParam}\`, @@ -1588,8 +1636,9 @@ export function createClient({ > = { method: \\"get\\", - headers: () => ({}), - + headers: ({ [\\"customToken\\"]: customToken }) => ({ + \\"custom-token\\": customToken + }), response_decoder: testParametersAtPathLevelDefaultDecoder(), url: ({}) => \`\${basePath}/test-path-level-parameter\`, @@ -1607,7 +1656,9 @@ export function createClient({ > = { method: \\"patch\\", - headers: () => ({ + headers: ({ [\\"customToken\\"]: customToken }) => ({ + \\"custom-token\\": customToken, + \\"Content-Type\\": \\"application/json\\" }), response_decoder: testSimplePatchDefaultDecoder(), @@ -1647,8 +1698,9 @@ export function createClient({ > = { method: \\"get\\", - headers: () => ({}), - + headers: ({ [\\"customToken\\"]: customToken }) => ({ + \\"custom-token\\": customToken + }), response_decoder: testWithEmptyResponseDefaultDecoder(), url: ({}) => \`\${basePath}/test-with-empty-response\`, @@ -1665,8 +1717,9 @@ export function createClient({ > = { method: \\"get\\", - headers: () => ({}), - + headers: ({ [\\"customToken\\"]: customToken }) => ({ + \\"custom-token\\": customToken + }), response_decoder: testParamWithSchemaRefDefaultDecoder(), url: ({ [\\"param\\"]: param }) => \`\${basePath}/test-param-with-schema-ref/\${param}\`, @@ -1684,7 +1737,9 @@ export function createClient({ > = { method: \\"get\\", - headers: ({ [\\"param\\"]: param }) => ({ + headers: ({ [\\"customToken\\"]: customToken, [\\"param\\"]: param }) => ({ + \\"custom-token\\": customToken, + param: param }), response_decoder: testHeaderWithSchemaRefDefaultDecoder(), @@ -1703,7 +1758,9 @@ export function createClient({ > = { method: \\"get\\", - headers: ({ [\\"param\\"]: param }) => ({ + headers: ({ [\\"customToken\\"]: customToken, [\\"param\\"]: param }) => ({ + \\"custom-token\\": customToken, + param: param }), response_decoder: testHeaderOptionalDefaultDecoder(), @@ -1716,6 +1773,43 @@ export function createClient({ options ); + const testOverriddenSecurityT: ReplaceRequestParams< + TestOverriddenSecurityT, + RequestParams + > = { + method: \\"get\\", + + headers: ({ [\\"bearerToken\\"]: bearerToken }) => ({ + Authorization: \`Bearer \${bearerToken}\` + }), + response_decoder: testOverriddenSecurityDefaultDecoder(), + url: ({}) => \`\${basePath}/test-with-overridden-security\`, + + query: () => withoutUndefinedValues({}) + }; + const testOverriddenSecurity: TypeofApiCall = createFetchRequestForApi( + testOverriddenSecurityT, + options + ); + + const testOverriddenSecurityT: ReplaceRequestParams< + TestOverriddenSecurityT, + RequestParams + > = { + method: \\"get\\", + + headers: () => ({}), + + response_decoder: testOverriddenSecurityDefaultDecoder(), + url: ({}) => \`\${basePath}/test-with-overridden-security-no-auth\`, + + query: () => withoutUndefinedValues({}) + }; + const testOverriddenSecurity: TypeofApiCall = createFetchRequestForApi( + testOverriddenSecurityT, + options + ); + return { testAuthBearer: (withDefaults || identity)(testAuthBearer), testSimpleToken: (withDefaults || identity)(testSimpleToken), @@ -1748,7 +1842,9 @@ export function createClient({ testHeaderWithSchemaRef: (withDefaults || identity)( testHeaderWithSchemaRef ), - testHeaderOptional: (withDefaults || identity)(testHeaderOptional) + testHeaderOptional: (withDefaults || identity)(testHeaderOptional), + testOverriddenSecurity: (withDefaults || identity)(testOverriddenSecurity), + testOverriddenSecurity: (withDefaults || identity)(testOverriddenSecurity) }; } " @@ -2840,7 +2936,11 @@ import { TestHeaderWithSchemaRefT, testHeaderWithSchemaRefDefaultDecoder, TestHeaderOptionalT, - testHeaderOptionalDefaultDecoder + testHeaderOptionalDefaultDecoder, + TestOverriddenSecurityT, + testOverriddenSecurityDefaultDecoder, + TestOverriddenSecurityT, + testOverriddenSecurityDefaultDecoder } from \\"./requestTypes\\"; // This is a placeholder for undefined when dealing with object keys @@ -2869,7 +2969,9 @@ export type ApiOperation = TypeofApiCall & TypeofApiCall & TypeofApiCall & TypeofApiCall & - TypeofApiCall; + TypeofApiCall & + TypeofApiCall & + TypeofApiCall; export type ParamKeys = keyof (TypeofApiParams & TypeofApiParams & @@ -2891,7 +2993,9 @@ export type ParamKeys = keyof (TypeofApiParams & TypeofApiParams & TypeofApiParams & TypeofApiParams & - TypeofApiParams); + TypeofApiParams & + TypeofApiParams & + TypeofApiParams); /** * Defines an adapter for TypeofApiCall which omit one or more parameters in the signature @@ -2935,7 +3039,9 @@ export type WithDefaultsT< | TestWithEmptyResponseT | TestParamWithSchemaRefT | TestHeaderWithSchemaRefT - | TestHeaderOptionalT, + | TestHeaderOptionalT + | TestOverriddenSecurityT + | TestOverriddenSecurityT, K >; @@ -2998,6 +3104,10 @@ export type Client< readonly testHeaderWithSchemaRef: TypeofApiCall; readonly testHeaderOptional: TypeofApiCall; + + readonly testOverriddenSecurity: TypeofApiCall; + + readonly testOverriddenSecurity: TypeofApiCall; } : { readonly testAuthBearer: TypeofApiCall< @@ -3146,6 +3256,20 @@ export type Client< Omit, K> > >; + + readonly testOverriddenSecurity: TypeofApiCall< + ReplaceRequestParams< + TestOverriddenSecurityT, + Omit, K> + > + >; + + readonly testOverriddenSecurity: TypeofApiCall< + ReplaceRequestParams< + TestOverriddenSecurityT, + Omit, K> + > + >; }; /** @@ -3255,8 +3379,9 @@ export function createClient({ > = { method: \\"get\\", - headers: () => ({}), - + headers: ({ [\\"customToken\\"]: customToken }) => ({ + \\"custom-token\\": customToken + }), response_decoder: testMultipleSuccessDefaultDecoder(), url: ({}) => \`\${basePath}/test-multiple-success\`, @@ -3277,7 +3402,9 @@ export function createClient({ // reference: https://github.com/github/fetch/issues/505#issuecomment-293064470 // The solution is to skip the Content-Type header and let fetch add it for us. // @ts-ignore as IRequestType would require something - headers: () => ({}), + headers: ({ [\\"customToken\\"]: customToken }) => ({ + \\"custom-token\\": customToken + }), response_decoder: testFileUploadDefaultDecoder(), url: ({}) => \`\${basePath}/test-file-upload\`, @@ -3308,7 +3435,9 @@ export function createClient({ // reference: https://github.com/github/fetch/issues/505#issuecomment-293064470 // The solution is to skip the Content-Type header and let fetch add it for us. // @ts-ignore as IRequestType would require something - headers: () => ({}), + headers: ({ [\\"customToken\\"]: customToken }) => ({ + \\"custom-token\\": customToken + }), response_decoder: testBinaryFileUploadDefaultDecoder(), url: ({}) => \`\${basePath}/test-binary-file-upload\`, @@ -3335,8 +3464,9 @@ export function createClient({ > = { method: \\"get\\", - headers: () => ({}), - + headers: ({ [\\"customToken\\"]: customToken }) => ({ + \\"custom-token\\": customToken + }), response_decoder: testBinaryFileDownloadDefaultDecoder(), url: ({}) => \`\${basePath}/test-binary-file-download\`, @@ -3353,8 +3483,9 @@ export function createClient({ > = { method: \\"get\\", - headers: () => ({}), - + headers: ({ [\\"customToken\\"]: customToken }) => ({ + \\"custom-token\\": customToken + }), response_decoder: testResponseHeaderDefaultDecoder(), url: ({}) => \`\${basePath}/test-response-header\`, @@ -3371,7 +3502,9 @@ export function createClient({ > = { method: \\"post\\", - headers: () => ({ + headers: ({ [\\"customToken\\"]: customToken }) => ({ + \\"custom-token\\": customToken, + \\"Content-Type\\": \\"application/json\\" }), response_decoder: testParameterWithReferenceDefaultDecoder(), @@ -3393,7 +3526,9 @@ export function createClient({ > = { method: \\"post\\", - headers: () => ({ + headers: ({ [\\"customToken\\"]: customToken }) => ({ + \\"custom-token\\": customToken, + \\"Content-Type\\": \\"application/json\\" }), response_decoder: testParameterWithBodyReferenceDefaultDecoder(), @@ -3420,7 +3555,9 @@ export function createClient({ > = { method: \\"put\\", - headers: () => ({ + headers: ({ [\\"customToken\\"]: customToken }) => ({ + \\"custom-token\\": customToken, + \\"Content-Type\\": \\"application/json\\" }), response_decoder: putTestParameterWithBodyReferenceDefaultDecoder(), @@ -3448,9 +3585,12 @@ export function createClient({ method: \\"get\\", headers: ({ + [\\"customToken\\"]: customToken, [\\"headerInlineParam\\"]: headerInlineParam, [\\"x-header-param\\"]: xHeaderParam }) => ({ + \\"custom-token\\": customToken, + headerInlineParam: headerInlineParam, \\"x-header-param\\": xHeaderParam @@ -3474,9 +3614,12 @@ export function createClient({ method: \\"get\\", headers: ({ + [\\"customToken\\"]: customToken, [\\"headerInlineParam\\"]: headerInlineParam, [\\"x-header-param\\"]: xHeaderParam }) => ({ + \\"custom-token\\": customToken, + headerInlineParam: headerInlineParam, \\"x-header-param\\": xHeaderParam @@ -3499,8 +3642,9 @@ export function createClient({ > = { method: \\"get\\", - headers: () => ({}), - + headers: ({ [\\"customToken\\"]: customToken }) => ({ + \\"custom-token\\": customToken + }), response_decoder: testWithTwoParamsDefaultDecoder(), url: ({ [\\"first-param\\"]: firstParam, [\\"second-param\\"]: secondParam }) => \`\${basePath}/test-two-path-params/\${firstParam}/\${secondParam}\`, @@ -3518,8 +3662,9 @@ export function createClient({ > = { method: \\"get\\", - headers: () => ({}), - + headers: ({ [\\"customToken\\"]: customToken }) => ({ + \\"custom-token\\": customToken + }), response_decoder: testParametersAtPathLevelDefaultDecoder(), url: ({}) => \`\${basePath}/test-path-level-parameter\`, @@ -3537,7 +3682,9 @@ export function createClient({ > = { method: \\"patch\\", - headers: () => ({ + headers: ({ [\\"customToken\\"]: customToken }) => ({ + \\"custom-token\\": customToken, + \\"Content-Type\\": \\"application/json\\" }), response_decoder: testSimplePatchDefaultDecoder(), @@ -3577,8 +3724,9 @@ export function createClient({ > = { method: \\"get\\", - headers: () => ({}), - + headers: ({ [\\"customToken\\"]: customToken }) => ({ + \\"custom-token\\": customToken + }), response_decoder: testWithEmptyResponseDefaultDecoder(), url: ({}) => \`\${basePath}/test-with-empty-response\`, @@ -3595,8 +3743,9 @@ export function createClient({ > = { method: \\"get\\", - headers: () => ({}), - + headers: ({ [\\"customToken\\"]: customToken }) => ({ + \\"custom-token\\": customToken + }), response_decoder: testParamWithSchemaRefDefaultDecoder(), url: ({ [\\"param\\"]: param }) => \`\${basePath}/test-param-with-schema-ref/\${param}\`, @@ -3614,7 +3763,9 @@ export function createClient({ > = { method: \\"get\\", - headers: ({ [\\"param\\"]: param }) => ({ + headers: ({ [\\"customToken\\"]: customToken, [\\"param\\"]: param }) => ({ + \\"custom-token\\": customToken, + param: param }), response_decoder: testHeaderWithSchemaRefDefaultDecoder(), @@ -3633,7 +3784,9 @@ export function createClient({ > = { method: \\"get\\", - headers: ({ [\\"param\\"]: param }) => ({ + headers: ({ [\\"customToken\\"]: customToken, [\\"param\\"]: param }) => ({ + \\"custom-token\\": customToken, + param: param }), response_decoder: testHeaderOptionalDefaultDecoder(), @@ -3646,6 +3799,43 @@ export function createClient({ options ); + const testOverriddenSecurityT: ReplaceRequestParams< + TestOverriddenSecurityT, + RequestParams + > = { + method: \\"get\\", + + headers: ({ [\\"bearerToken\\"]: bearerToken }) => ({ + Authorization: \`Bearer \${bearerToken}\` + }), + response_decoder: testOverriddenSecurityDefaultDecoder(), + url: ({}) => \`\${basePath}/test-with-overridden-security\`, + + query: () => withoutUndefinedValues({}) + }; + const testOverriddenSecurity: TypeofApiCall = createFetchRequestForApi( + testOverriddenSecurityT, + options + ); + + const testOverriddenSecurityT: ReplaceRequestParams< + TestOverriddenSecurityT, + RequestParams + > = { + method: \\"get\\", + + headers: () => ({}), + + response_decoder: testOverriddenSecurityDefaultDecoder(), + url: ({}) => \`\${basePath}/test-with-overridden-security-no-auth\`, + + query: () => withoutUndefinedValues({}) + }; + const testOverriddenSecurity: TypeofApiCall = createFetchRequestForApi( + testOverriddenSecurityT, + options + ); + return { testAuthBearer: (withDefaults || identity)(testAuthBearer), testAuthBearerHttp: (withDefaults || identity)(testAuthBearerHttp), @@ -3679,7 +3869,9 @@ export function createClient({ testHeaderWithSchemaRef: (withDefaults || identity)( testHeaderWithSchemaRef ), - testHeaderOptional: (withDefaults || identity)(testHeaderOptional) + testHeaderOptional: (withDefaults || identity)(testHeaderOptional), + testOverriddenSecurity: (withDefaults || identity)(testOverriddenSecurity), + testOverriddenSecurity: (withDefaults || identity)(testOverriddenSecurity) }; } " diff --git a/src/commands/gen-api-models/__tests__/index.test.ts b/src/commands/gen-api-models/__tests__/index.test.ts index b529e5f..c77c7ac 100644 --- a/src/commands/gen-api-models/__tests__/index.test.ts +++ b/src/commands/gen-api-models/__tests__/index.test.ts @@ -596,6 +596,19 @@ describe.each` }); it("should parse operations", () => { + const commonAuth = { + parameters: [ + { + in: "header", + name: "customToken", + tokenType: "apiKey", + authScheme: "none", + headerName: "custom-token", + type: "string" + } + ], + headers: ["custom-token"] + }; const expected = [ { path: "/test-auth-bearer", @@ -635,12 +648,11 @@ describe.each` produces: "application/json" }, { + ...commonAuth, path: "/test-multiple-success", - headers: [], importedTypes: new Set(["Message", "OneOfTest"]), method: "get", operationId: "testMultipleSuccess", - parameters: [], responses: [ { e1: "200", e2: "Message", e3: [] }, { e1: "202", e2: "undefined", e3: [] }, @@ -651,11 +663,12 @@ describe.each` }, { path: "/test-file-upload", - headers: ["Content-Type"], + headers: ["Content-Type", ...commonAuth.headers], importedTypes: new Set(), method: "post", operationId: "testFileUpload", parameters: [ + ...commonAuth.parameters, { name: version === 2 ? "file" : "body", type: @@ -670,12 +683,11 @@ describe.each` produces: "application/json" }, { + ...commonAuth, path: "/test-response-header", - headers: [], importedTypes: new Set(["Message"]), method: "get", operationId: "testResponseHeader", - parameters: [], responses: [ { e1: "201", e2: "Message", e3: ["Location", "Id"] }, { e1: "500", e2: "undefined", e3: [] } @@ -684,11 +696,12 @@ describe.each` }, { path: "/test-binary-file-upload", - headers: ["Content-Type"], + headers: ["Content-Type", ...commonAuth.headers], importedTypes: new Set(), method: "post", operationId: "testBinaryFileUpload", parameters: [ + ...commonAuth.parameters, { name: version === 2 ? "logo" : "body", type: `File`, From 88262541e921b305e1e99054daed819b90125a5a Mon Sep 17 00:00:00 2001 From: arcogabbo Date: Fri, 27 Jun 2025 13:42:48 +0200 Subject: [PATCH 4/8] fix: e2e tests didnt include default security strategy --- __mocks__/api.yaml | 2 +- __mocks__/openapi_v3/api.yaml | 2 +- e2e/src/__tests__/test-api-v3/client.test.ts | 76 +++++++++++--------- e2e/src/__tests__/test-api/client.test.ts | 74 ++++++++++--------- 4 files changed, 85 insertions(+), 69 deletions(-) diff --git a/__mocks__/api.yaml b/__mocks__/api.yaml index 3bcfe06..b9d6025 100644 --- a/__mocks__/api.yaml +++ b/__mocks__/api.yaml @@ -319,7 +319,7 @@ paths: description: "Ok" /test-with-overridden-security-no-auth: get: - operationId: "testOverriddenSecurity" + operationId: "testOverriddenSecurityNoAuth" security: [] responses: "200": diff --git a/__mocks__/openapi_v3/api.yaml b/__mocks__/openapi_v3/api.yaml index 526d6ce..3b0d703 100644 --- a/__mocks__/openapi_v3/api.yaml +++ b/__mocks__/openapi_v3/api.yaml @@ -347,7 +347,7 @@ paths: description: "Ok" /test-with-overridden-security-no-auth: get: - operationId: "testOverriddenSecurity" + operationId: "testOverriddenSecurityNoAuth" security: [] responses: "200": diff --git a/e2e/src/__tests__/test-api-v3/client.test.ts b/e2e/src/__tests__/test-api-v3/client.test.ts index e6dd673..2acb210 100644 --- a/e2e/src/__tests__/test-api-v3/client.test.ts +++ b/e2e/src/__tests__/test-api-v3/client.test.ts @@ -18,6 +18,7 @@ const { generatedFilesDir, mockPort, isSpecEnabled } = config.specs.testapiV3; // if there's no need for this suite in this particular run, just skip it const describeSuite = skipClient || !isSpecEnabled ? describe.skip : describe; +const customToken = "anAPIKey"; // given a spied fetch call, check if the request has been made using the provided parameter in querystring const hasQueryParam = (paramName: string) => ([input]: Parameters< @@ -82,7 +83,8 @@ describeSuite("Http client generated from Test API spec", () => { headerInlineParam: "value", "path-param": "value", "request-id": "value", - "x-header-param": "value" + "x-header-param": "value", + customToken }); expect(isRight(result)).toBe(true); }); @@ -103,7 +105,8 @@ describeSuite("Http client generated from Test API spec", () => { headerInlineParam: "value", "path-param": "value", "request-id": "value", - "x-header-param": "value" + "x-header-param": "value", + customToken }); expect(isRight(result)).toBe(true); }); @@ -134,7 +137,8 @@ describeSuite("Http client generated from Test API spec", () => { headerInlineParam: "value", "path-param": "value", "request-id": "value", - "x-header-param": "value" + "x-header-param": "value", + customToken }); expect(spiedFetch).toHaveBeenCalledTimes(1); @@ -200,18 +204,21 @@ describeSuite("Http client generated from Test API spec", () => { // It can be called with expected query parameters const resultWithCursor = await client.testParametersAtPathLevel({ "request-id": "an id", - cursor: "a cursor" + cursor: "a cursor", + customToken }); // It can be called without optional parameters const result = await client.testParametersAtPathLevel({ - "request-id": "an id" + "request-id": "an id", + customToken }); // It cannot be called without optional parameters // @ts-expect-error await client.testParametersAtPathLevel({ - cursor: "a cursor" + cursor: "a cursor", + customToken }); }); @@ -239,7 +246,8 @@ describeSuite("Http client generated from Test API spec", () => { expect.assertions(2); try { await client.testBinaryFileUpload({ - body: {} as File + body: {} as File, + customToken }); } catch (e) { expect(e).toEqual( @@ -259,13 +267,7 @@ describeSuite("Http client generated from Test API spec", () => { expect(client.testParameterWithDash).toEqual(expect.any(Function)); - const result = await client.testSimplePatch({ - "foo-bar": "value", - headerInlineParam: "value", - "path-param": "value", - "request-id": "value", - "x-header-param": "value" - }); + const result = await client.testSimplePatch({ customToken }); expect(isRight(result)).toBe(true); }); @@ -282,7 +284,7 @@ describeSuite("Http client generated from Test API spec", () => { expect(client.testBinaryFileDownload).toEqual(expect.any(Function)); - const result = await client.testBinaryFileDownload({}); + const result = await client.testBinaryFileDownload({ customToken }); expect(result).toMatchObject( right({ @@ -320,9 +322,9 @@ describeSuite("Http client generated from Test API spec", () => { }; expect(client.testParameterWithBodyReference).toEqual(expect.any(Function)); - client.testParameterWithBodyReference({ body: aData }); + client.testParameterWithBodyReference({ body: aData, customToken }); // @ts-expect-error - client.testParameterWithBodyReference({ body: "" }); + client.testParameterWithBodyReference({ body: "", customToken }); }); it("should handle model ref model in body for put operation", async () => { @@ -340,11 +342,10 @@ describeSuite("Http client generated from Test API spec", () => { expect(client.putTestParameterWithBodyReference).toEqual( expect.any(Function) ); - client.putTestParameterWithBodyReference({ body: aData }); + client.putTestParameterWithBodyReference({ body: aData, customToken }); // @ts-expect-error - client.putTestParameterWithBodyReference({ body: "" }); + client.putTestParameterWithBodyReference({ body: "", customToken }); }); - it("should handle model ref model in body as readablestream", async () => { const aData: NewModel = { @@ -352,31 +353,36 @@ describeSuite("Http client generated from Test API spec", () => { name: "aName" }; - const mockTestEndpoint = jest.fn((request: IncomingMessage, response: ServerResponse) => { - let data = ""; - request.on("data", chunk => data += chunk) - request.on("end", () => { - expect(JSON.parse(data)).toEqual(aData); - response.statusCode = 201; - response.end(); - }) - - } ); - const server = await startServer(mockPort+10, mockTestEndpoint); + const mockTestEndpoint = jest.fn( + (request: IncomingMessage, response: ServerResponse) => { + let data = ""; + request.on("data", chunk => (data += chunk)); + request.on("end", () => { + expect(JSON.parse(data)).toEqual(aData); + response.statusCode = 201; + response.end(); + }); + } + ); + const server = await startServer(mockPort + 10, mockTestEndpoint); const client = createClient({ - baseUrl: `http://localhost:${mockPort+10}`, + baseUrl: `http://localhost:${mockPort + 10}`, fetchApi: (nodeFetch as any) as typeof fetch, basePath: "" }); - const aDataAsBuffer = Readable.from(Buffer.from(JSON.stringify(aData))) as unknown as ReadableStream; + const aDataAsBuffer = (Readable.from( + Buffer.from(JSON.stringify(aData)) + ) as unknown) as ReadableStream; expect(client.testParameterWithBodyReference).toEqual(expect.any(Function)); - const response = await client.testParameterWithBodyReference({ body: aDataAsBuffer }); + const response = await client.testParameterWithBodyReference({ + body: aDataAsBuffer, + customToken + }); expect(mockTestEndpoint).toHaveBeenCalledTimes(1); - await closeServer(server); }); diff --git a/e2e/src/__tests__/test-api/client.test.ts b/e2e/src/__tests__/test-api/client.test.ts index 9bb25e7..338462d 100644 --- a/e2e/src/__tests__/test-api/client.test.ts +++ b/e2e/src/__tests__/test-api/client.test.ts @@ -15,6 +15,8 @@ leaked.set({ debugSockets: true }); const { skipClient } = config; const { generatedFilesDir, mockPort, isSpecEnabled } = config.specs.testapi; +const customToken = "anAPIKey"; + // if there's no need for this suite in this particular run, just skip it const describeSuite = skipClient || !isSpecEnabled ? describe.skip : describe; @@ -81,7 +83,8 @@ describeSuite("Http client generated from Test API spec", () => { headerInlineParam: "value", "path-param": "value", "request-id": "value", - "x-header-param": "value" + "x-header-param": "value", + customToken }); expect(isRight(result)).toBe(true); }); @@ -102,7 +105,8 @@ describeSuite("Http client generated from Test API spec", () => { headerInlineParam: "value", "path-param": "value", "request-id": "value", - "x-header-param": "value" + "x-header-param": "value", + customToken }); expect(isRight(result)).toBe(true); }); @@ -133,7 +137,8 @@ describeSuite("Http client generated from Test API spec", () => { headerInlineParam: "value", "path-param": "value", "request-id": "value", - "x-header-param": "value" + "x-header-param": "value", + customToken }); expect(spiedFetch).toHaveBeenCalledTimes(1); @@ -199,18 +204,21 @@ describeSuite("Http client generated from Test API spec", () => { // It can be called with expected query parameters const resultWithCursor = await client.testParametersAtPathLevel({ "request-id": "an id", - cursor: "a cursor" + cursor: "a cursor", + customToken }); // It can be called without optional parameters const result = await client.testParametersAtPathLevel({ - "request-id": "an id" + "request-id": "an id", + customToken }); // It cannot be called without optional parameters // @ts-expect-error await client.testParametersAtPathLevel({ - cursor: "a cursor" + cursor: "a cursor", + customToken }); }); @@ -238,7 +246,8 @@ describeSuite("Http client generated from Test API spec", () => { expect.assertions(2); try { await client.testBinaryFileUpload({ - logo: {} as File + logo: {} as File, + customToken }); } catch (e) { expect(e).toEqual( @@ -262,7 +271,7 @@ describeSuite("Http client generated from Test API spec", () => { expect(client.testBinaryFileDownload).toEqual(expect.any(Function)); - const result = await client.testBinaryFileDownload({}); + const result = await client.testBinaryFileDownload({ customToken }); expect(result).toMatchObject( right({ @@ -282,11 +291,7 @@ describeSuite("Http client generated from Test API spec", () => { expect(client.testParameterWithDash).toEqual(expect.any(Function)); const result = await client.testSimplePatch({ - "foo-bar": "value", - headerInlineParam: "value", - "path-param": "value", - "request-id": "value", - "x-header-param": "value" + customToken }); expect(isRight(result)).toBe(true); }); @@ -319,9 +324,9 @@ describeSuite("Http client generated from Test API spec", () => { }; expect(client.testParameterWithBodyReference).toEqual(expect.any(Function)); - client.testParameterWithBodyReference({ body: aData }); + client.testParameterWithBodyReference({ body: aData, customToken }); // @ts-expect-error - client.testParameterWithBodyReference({ body: "" }); + client.testParameterWithBodyReference({ body: "", customToken }); }); it("should handle model ref model in body for put operation", async () => { @@ -339,9 +344,9 @@ describeSuite("Http client generated from Test API spec", () => { expect(client.putTestParameterWithBodyReference).toEqual( expect.any(Function) ); - client.putTestParameterWithBodyReference({ body: aData }); + client.putTestParameterWithBodyReference({ body: aData, customToken }); // @ts-expect-error - client.putTestParameterWithBodyReference({ body: "" }); + client.putTestParameterWithBodyReference({ body: "", customToken }); }); it("should handle model ref model in body as readablestream", async () => { @@ -350,31 +355,36 @@ describeSuite("Http client generated from Test API spec", () => { name: "aName" }; - const mockTestEndpoint = jest.fn((request: IncomingMessage, response: ServerResponse) => { - let data = ""; - request.on("data", chunk => data += chunk) - request.on("end", () => { - expect(JSON.parse(data)).toEqual(aData); - response.statusCode = 201; - response.end(); - }) - - } ); - const server = await startServer(mockPort+10, mockTestEndpoint); + const mockTestEndpoint = jest.fn( + (request: IncomingMessage, response: ServerResponse) => { + let data = ""; + request.on("data", chunk => (data += chunk)); + request.on("end", () => { + expect(JSON.parse(data)).toEqual(aData); + response.statusCode = 201; + response.end(); + }); + } + ); + const server = await startServer(mockPort + 10, mockTestEndpoint); const client = createClient({ - baseUrl: `http://localhost:${mockPort+10}`, + baseUrl: `http://localhost:${mockPort + 10}`, fetchApi: (nodeFetch as any) as typeof fetch, basePath: "" }); - const aDataAsBuffer = Readable.from(Buffer.from(JSON.stringify(aData))) as unknown as ReadableStream; + const aDataAsBuffer = (Readable.from( + Buffer.from(JSON.stringify(aData)) + ) as unknown) as ReadableStream; expect(client.testParameterWithBodyReference).toEqual(expect.any(Function)); - const response = await client.testParameterWithBodyReference({ body: aDataAsBuffer }); + const response = await client.testParameterWithBodyReference({ + body: aDataAsBuffer, + customToken + }); expect(mockTestEndpoint).toHaveBeenCalledTimes(1); - await closeServer(server); }); From 9f5a86388abb0ac564b011da57010a7e75422992 Mon Sep 17 00:00:00 2001 From: arcogabbo Date: Fri, 27 Jun 2025 13:45:02 +0200 Subject: [PATCH 5/8] new snapshot after fix of operationId --- .../__snapshots__/index.test.ts.snap | 72 ++++++++++--------- 1 file changed, 40 insertions(+), 32 deletions(-) diff --git a/src/commands/gen-api-models/__tests__/__snapshots__/index.test.ts.snap b/src/commands/gen-api-models/__tests__/__snapshots__/index.test.ts.snap index 63f955a..ec31f56 100644 --- a/src/commands/gen-api-models/__tests__/__snapshots__/index.test.ts.snap +++ b/src/commands/gen-api-models/__tests__/__snapshots__/index.test.ts.snap @@ -955,8 +955,8 @@ import { testHeaderOptionalDefaultDecoder, TestOverriddenSecurityT, testOverriddenSecurityDefaultDecoder, - TestOverriddenSecurityT, - testOverriddenSecurityDefaultDecoder + TestOverriddenSecurityNoAuthT, + testOverriddenSecurityNoAuthDefaultDecoder } from \\"./requestTypes\\"; // This is a placeholder for undefined when dealing with object keys @@ -986,7 +986,7 @@ export type ApiOperation = TypeofApiCall & TypeofApiCall & TypeofApiCall & TypeofApiCall & - TypeofApiCall; + TypeofApiCall; export type ParamKeys = keyof (TypeofApiParams & TypeofApiParams & @@ -1009,7 +1009,7 @@ export type ParamKeys = keyof (TypeofApiParams & TypeofApiParams & TypeofApiParams & TypeofApiParams & - TypeofApiParams); + TypeofApiParams); /** * Defines an adapter for TypeofApiCall which omit one or more parameters in the signature @@ -1054,7 +1054,7 @@ export type WithDefaultsT< | TestHeaderWithSchemaRefT | TestHeaderOptionalT | TestOverriddenSecurityT - | TestOverriddenSecurityT, + | TestOverriddenSecurityNoAuthT, K >; @@ -1118,7 +1118,9 @@ export type Client< readonly testOverriddenSecurity: TypeofApiCall; - readonly testOverriddenSecurity: TypeofApiCall; + readonly testOverriddenSecurityNoAuth: TypeofApiCall< + TestOverriddenSecurityNoAuthT + >; } : { readonly testAuthBearer: TypeofApiCall< @@ -1268,10 +1270,10 @@ export type Client< > >; - readonly testOverriddenSecurity: TypeofApiCall< + readonly testOverriddenSecurityNoAuth: TypeofApiCall< ReplaceRequestParams< - TestOverriddenSecurityT, - Omit, K> + TestOverriddenSecurityNoAuthT, + Omit, K> > >; }; @@ -1792,21 +1794,21 @@ export function createClient({ options ); - const testOverriddenSecurityT: ReplaceRequestParams< - TestOverriddenSecurityT, - RequestParams + const testOverriddenSecurityNoAuthT: ReplaceRequestParams< + TestOverriddenSecurityNoAuthT, + RequestParams > = { method: \\"get\\", headers: () => ({}), - response_decoder: testOverriddenSecurityDefaultDecoder(), + response_decoder: testOverriddenSecurityNoAuthDefaultDecoder(), url: ({}) => \`\${basePath}/test-with-overridden-security-no-auth\`, query: () => withoutUndefinedValues({}) }; - const testOverriddenSecurity: TypeofApiCall = createFetchRequestForApi( - testOverriddenSecurityT, + const testOverriddenSecurityNoAuth: TypeofApiCall = createFetchRequestForApi( + testOverriddenSecurityNoAuthT, options ); @@ -1844,7 +1846,9 @@ export function createClient({ ), testHeaderOptional: (withDefaults || identity)(testHeaderOptional), testOverriddenSecurity: (withDefaults || identity)(testOverriddenSecurity), - testOverriddenSecurity: (withDefaults || identity)(testOverriddenSecurity) + testOverriddenSecurityNoAuth: (withDefaults || identity)( + testOverriddenSecurityNoAuth + ) }; } " @@ -2939,8 +2943,8 @@ import { testHeaderOptionalDefaultDecoder, TestOverriddenSecurityT, testOverriddenSecurityDefaultDecoder, - TestOverriddenSecurityT, - testOverriddenSecurityDefaultDecoder + TestOverriddenSecurityNoAuthT, + testOverriddenSecurityNoAuthDefaultDecoder } from \\"./requestTypes\\"; // This is a placeholder for undefined when dealing with object keys @@ -2971,7 +2975,7 @@ export type ApiOperation = TypeofApiCall & TypeofApiCall & TypeofApiCall & TypeofApiCall & - TypeofApiCall; + TypeofApiCall; export type ParamKeys = keyof (TypeofApiParams & TypeofApiParams & @@ -2995,7 +2999,7 @@ export type ParamKeys = keyof (TypeofApiParams & TypeofApiParams & TypeofApiParams & TypeofApiParams & - TypeofApiParams); + TypeofApiParams); /** * Defines an adapter for TypeofApiCall which omit one or more parameters in the signature @@ -3041,7 +3045,7 @@ export type WithDefaultsT< | TestHeaderWithSchemaRefT | TestHeaderOptionalT | TestOverriddenSecurityT - | TestOverriddenSecurityT, + | TestOverriddenSecurityNoAuthT, K >; @@ -3107,7 +3111,9 @@ export type Client< readonly testOverriddenSecurity: TypeofApiCall; - readonly testOverriddenSecurity: TypeofApiCall; + readonly testOverriddenSecurityNoAuth: TypeofApiCall< + TestOverriddenSecurityNoAuthT + >; } : { readonly testAuthBearer: TypeofApiCall< @@ -3264,10 +3270,10 @@ export type Client< > >; - readonly testOverriddenSecurity: TypeofApiCall< + readonly testOverriddenSecurityNoAuth: TypeofApiCall< ReplaceRequestParams< - TestOverriddenSecurityT, - Omit, K> + TestOverriddenSecurityNoAuthT, + Omit, K> > >; }; @@ -3818,21 +3824,21 @@ export function createClient({ options ); - const testOverriddenSecurityT: ReplaceRequestParams< - TestOverriddenSecurityT, - RequestParams + const testOverriddenSecurityNoAuthT: ReplaceRequestParams< + TestOverriddenSecurityNoAuthT, + RequestParams > = { method: \\"get\\", headers: () => ({}), - response_decoder: testOverriddenSecurityDefaultDecoder(), + response_decoder: testOverriddenSecurityNoAuthDefaultDecoder(), url: ({}) => \`\${basePath}/test-with-overridden-security-no-auth\`, query: () => withoutUndefinedValues({}) }; - const testOverriddenSecurity: TypeofApiCall = createFetchRequestForApi( - testOverriddenSecurityT, + const testOverriddenSecurityNoAuth: TypeofApiCall = createFetchRequestForApi( + testOverriddenSecurityNoAuthT, options ); @@ -3871,7 +3877,9 @@ export function createClient({ ), testHeaderOptional: (withDefaults || identity)(testHeaderOptional), testOverriddenSecurity: (withDefaults || identity)(testOverriddenSecurity), - testOverriddenSecurity: (withDefaults || identity)(testOverriddenSecurity) + testOverriddenSecurityNoAuth: (withDefaults || identity)( + testOverriddenSecurityNoAuth + ) }; } " From 89534f7e02115fd954a9254836b11cd04ad2c678 Mon Sep 17 00:00:00 2001 From: arcogabbo Date: Mon, 30 Jun 2025 09:25:53 +0200 Subject: [PATCH 6/8] removed leftover console.log --- src/commands/gen-api-models/__tests__/parse.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/commands/gen-api-models/__tests__/parse.test.ts b/src/commands/gen-api-models/__tests__/parse.test.ts index 9a45791..1a3f8cc 100644 --- a/src/commands/gen-api-models/__tests__/parse.test.ts +++ b/src/commands/gen-api-models/__tests__/parse.test.ts @@ -328,7 +328,6 @@ describe.each` "undefined" )("get"); - console.log(parsed); expect(parsed).toEqual( expect.objectContaining({ method: "get", From 9d3f0b19f574ce162a747b2b662887d5e5b03ad2 Mon Sep 17 00:00:00 2001 From: arcogabbo Date: Mon, 30 Jun 2025 10:14:44 +0200 Subject: [PATCH 7/8] fix: use custom-token strategy for related unit tests --- src/commands/gen-api-models/__tests__/parse.test.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/commands/gen-api-models/__tests__/parse.test.ts b/src/commands/gen-api-models/__tests__/parse.test.ts index 1a3f8cc..f2c19bd 100644 --- a/src/commands/gen-api-models/__tests__/parse.test.ts +++ b/src/commands/gen-api-models/__tests__/parse.test.ts @@ -277,10 +277,9 @@ describe.each` [ //global security defined { - authScheme: "bearer", - headerName: "Authorization", + authScheme: "none", in: "header", - name: "bearerToken", + name: "custom-token", tokenType: "apiKey", type: "string" } @@ -316,10 +315,9 @@ describe.each` [ //global security defined { - authScheme: "bearer", - headerName: "Authorization", + authScheme: "none", in: "header", - name: "bearerToken", + name: "custom-token", tokenType: "apiKey", type: "string" } From d92a0a5efa5a808068092cede790b14e6887b587 Mon Sep 17 00:00:00 2001 From: arcogabbo Date: Mon, 30 Jun 2025 10:39:06 +0200 Subject: [PATCH 8/8] added related e2e tests --- e2e/src/__tests__/test-api-v3/client.test.ts | 24 ++++++++++++++++++++ e2e/src/__tests__/test-api/client.test.ts | 24 ++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/e2e/src/__tests__/test-api-v3/client.test.ts b/e2e/src/__tests__/test-api-v3/client.test.ts index 2acb210..83d8350 100644 --- a/e2e/src/__tests__/test-api-v3/client.test.ts +++ b/e2e/src/__tests__/test-api-v3/client.test.ts @@ -386,4 +386,28 @@ describeSuite("Http client generated from Test API spec", () => { await closeServer(server); }); + + it("should override global security for operation level strategy", async () => { + const client = createClient({ + baseUrl: `http://localhost:${mockPort}`, + fetchApi: (nodeFetch as any) as typeof fetch, + basePath: "" + }); + + const result = await client.testOverriddenSecurity({ bearerToken: "abc" }); + expect(isRight(result)).toBeTruthy(); + // @ts-expect-error + client.putTestParameterWithBodyReference({}); + }); + + it("should override global security for operation level strategy with no auth", async () => { + const client = createClient({ + baseUrl: `http://localhost:${mockPort}`, + fetchApi: (nodeFetch as any) as typeof fetch, + basePath: "" + }); + + const result = await client.testOverriddenSecurityNoAuth({}); + expect(isRight(result)).toBeTruthy(); + }); }); diff --git a/e2e/src/__tests__/test-api/client.test.ts b/e2e/src/__tests__/test-api/client.test.ts index 338462d..297e187 100644 --- a/e2e/src/__tests__/test-api/client.test.ts +++ b/e2e/src/__tests__/test-api/client.test.ts @@ -388,4 +388,28 @@ describeSuite("Http client generated from Test API spec", () => { await closeServer(server); }); + + it("should override global security for operation level strategy", async () => { + const client = createClient({ + baseUrl: `http://localhost:${mockPort}`, + fetchApi: (nodeFetch as any) as typeof fetch, + basePath: "" + }); + + const result = await client.testOverriddenSecurity({ bearerToken: "abc" }); + expect(isRight(result)).toBeTruthy(); + // @ts-expect-error + client.putTestParameterWithBodyReference({}); + }); + + it("should override global security for operation level strategy with no auth", async () => { + const client = createClient({ + baseUrl: `http://localhost:${mockPort}`, + fetchApi: (nodeFetch as any) as typeof fetch, + basePath: "" + }); + + const result = await client.testOverriddenSecurityNoAuth({}); + expect(isRight(result)).toBeTruthy(); + }); });