-
Notifications
You must be signed in to change notification settings - Fork 323
fix(go,csharp,python,ruby,php,rust): strip query params from WireMock urlPathTemplate #16693
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| - summary: | | ||
| Fix WireMock wire test generation for endpoints whose OpenAPI paths embed query | ||
| strings (e.g. Stainless overload dispatch pattern `?stainless_overload=methodName`). | ||
| Query parameters are now correctly stripped from WithPath and added as separate | ||
| WithParam calls, matching WireMock.Net's path-only matching behavior. | ||
| type: fix |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| - summary: | | ||
| Fix WireMock wire test generation for endpoints whose OpenAPI paths embed query | ||
| strings (e.g. Stainless overload dispatch pattern `?stainless_overload=methodName`). | ||
| Query parameters are now correctly stripped from the URL path and passed separately | ||
| in VerifyRequestCount, matching WireMock's path-only matching behavior. | ||
| type: fix |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -42,6 +42,8 @@ export interface WireMockMapping { | |
| via: string; | ||
| }; | ||
| }; | ||
| /** Original path template including any embedded query string, for lookup disambiguation. */ | ||
| fullPathTemplate?: string; | ||
| }; | ||
| postServeActions?: unknown[]; | ||
| } | ||
|
|
@@ -168,8 +170,10 @@ export class WireMock { | |
| example: FernIr.ExampleEndpointCall | undefined, | ||
| streamConditionProperty: string | undefined | ||
| ): WireMockMapping | null { | ||
| // Build URL path template | ||
| // Build URL path template (stripped of query params for WireMock matching) | ||
| const urlPathTemplate = this.buildUrlPathTemplate(endpoint); | ||
| // Preserve the original full path (including any embedded query string) for lookup disambiguation | ||
| const rawFullPath = this.buildRawFullPath(endpoint); | ||
|
|
||
| // Extract path parameters from example (root, service, and endpoint levels) | ||
| const pathParameters: Record<string, { equalTo: string }> = {}; | ||
|
|
@@ -188,8 +192,11 @@ export class WireMock { | |
| } | ||
| } | ||
|
|
||
| // Extract query parameters embedded in the path (e.g. ?stainless_overload=branchFrom) | ||
| const pathQueryParams = this.extractPathQueryParameters(endpoint); | ||
|
|
||
| // Extract query parameters from example | ||
| const queryParameters: Record<string, QueryParameterMatcher> = {}; | ||
| const queryParameters: Record<string, QueryParameterMatcher> = { ...pathQueryParams }; | ||
| for (const param of example?.queryParameters || []) { | ||
| const paramName = param.name != null ? getWireValue(param.name) : undefined; | ||
| if (!paramName) { | ||
|
|
@@ -380,7 +387,8 @@ export class WireMock { | |
| at: "2020-01-01T00:00:00.000Z", | ||
| via: "SYSTEM" | ||
| } | ||
| } | ||
| }, | ||
| fullPathTemplate: rawFullPath !== urlPathTemplate ? rawFullPath : undefined | ||
| } | ||
| }; | ||
|
|
||
|
|
@@ -413,9 +421,63 @@ export class WireMock { | |
| if (fragmentIndex !== -1) { | ||
| path = path.substring(0, fragmentIndex); | ||
| } | ||
| // Strip query string — query parameters embedded in the path (e.g. Stainless | ||
| // overload paths like "?stainless_overload=branchFrom") must be matched via | ||
| // the separate queryParameters field, not as part of urlPathTemplate. | ||
| const queryIndex = path.indexOf("?"); | ||
| if (queryIndex !== -1) { | ||
| path = path.substring(0, queryIndex); | ||
| } | ||
|
Comment on lines
+424
to
+430
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 Shared The Affected consumer code patterns (all unchanged by this PR)All four generators do: // getWireMockConfigContent() - uses urlPathTemplate which now lacks query string
const key = this.wiremockMappingKey({ requestMethod: mapping.request.method, requestUrlPathTemplate: mapping.request.urlPathTemplate });
// buildBasePath() - uses raw endpoint.fullPath which still has query string
let basePath = endpoint.fullPath.head + parts;
const mappingKey = this.wiremockMappingKey({ requestMethod: endpoint.method, requestUrlPathTemplate: basePath });Prompt for agentsWas this helpful? React with 👍 or 👎 to provide feedback. |
||
| return path; | ||
| } | ||
|
|
||
| /** | ||
| * Builds the raw full path template including any embedded query string. | ||
| * Used only for metadata / lookup disambiguation, not for WireMock matching. | ||
| */ | ||
| private buildRawFullPath(endpoint: FernIr.HttpEndpoint): string { | ||
| let path = endpoint.fullPath.head; | ||
| for (const part of endpoint.fullPath.parts || []) { | ||
| path += `{${part.pathParameter}}${part.tail}`; | ||
| } | ||
| if (!path.startsWith("/")) { | ||
| path = "/" + path; | ||
| } | ||
| const fragmentIndex = path.indexOf("#"); | ||
| if (fragmentIndex !== -1) { | ||
| path = path.substring(0, fragmentIndex); | ||
| } | ||
| return path; | ||
| } | ||
|
|
||
| /** | ||
| * Extracts query parameters that are embedded in the endpoint's full path. | ||
| * Some OpenAPI specs embed query strings directly in the path key | ||
| * (e.g. "/v2/namespaces/{namespace}?stainless_overload=branchFrom"). | ||
| * These need to be extracted and matched as WireMock queryParameters. | ||
| */ | ||
| private extractPathQueryParameters(endpoint: FernIr.HttpEndpoint): Record<string, QueryParameterMatcher> { | ||
| let path = endpoint.fullPath.head; | ||
| for (const part of endpoint.fullPath.parts || []) { | ||
| path += `{${part.pathParameter}}${part.tail}`; | ||
| } | ||
| const queryIndex = path.indexOf("?"); | ||
| if (queryIndex === -1) { | ||
| return {}; | ||
| } | ||
| const queryString = path.substring(queryIndex + 1); | ||
| const params: Record<string, QueryParameterMatcher> = {}; | ||
| for (const pair of queryString.split("&")) { | ||
| const eqIndex = pair.indexOf("="); | ||
| if (eqIndex !== -1) { | ||
| const key = decodeURIComponent(pair.substring(0, eqIndex)); | ||
| const value = decodeURIComponent(pair.substring(eqIndex + 1)); | ||
| params[key] = { equalTo: value }; | ||
| } | ||
| } | ||
| return params; | ||
| } | ||
|
|
||
| public exampleToQueryOrHeaderValue({ value }: { value: FernIr.ExampleTypeReference }): string | undefined { | ||
| if (typeof value.jsonExample === "string") { | ||
| const maybeDatetime = this.getDateTime(value); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚩 Fragment handling inconsistency between buildRawFullPath and Go v2 buildBasePath
The
buildRawFullPathmethod inpackages/commons/mock-utils/src/index.ts:446-449strips URL fragments (e.g.,#refresh) from paths, but the Go v2buildBasePathatgenerators/go-v2/sdk/src/wire-tests/WireTestGenerator.ts:816-818does NOT strip fragments when constructing the lookup key. For endpoints with fragments (like/oauth2/token#refresh),fullPathTemplatewould beundefined(sincebuildRawFullPathproduces the same asurlPathTemplateafter fragment stripping), so the stored key would be"POST - /oauth2/token". ButbuildBasePathwould produce"POST - /oauth2/token#refresh"as the lookup key. This is a pre-existing mismatch (it existed before this PR in the old code where fragments weren't stripped from the lookup key either), but it's worth noting that this PR doesn't fix it. If fragment-path endpoints exist in Go API specs, they'd still cause a fatal error at thethrow GeneratorError.internalErroron line 841.Was this helpful? React with 👍 or 👎 to provide feedback.