From 07e9c2cc32f08b29692104c4de1a01ac9343d6a7 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Sat, 22 Nov 2025 22:56:39 +0000 Subject: [PATCH 1/7] fix(openapi): Preserve example summaries in v3 OpenAPI importer Fixes #9551 The bug was in AbstractMediaTypeObjectConverter.parseMediaTypeObject where example objects were being reduced to just their values, discarding the summary and description fields. This caused example names to be lost, resulting in generic 'Example 1/2/3' labels instead of the user-provided summary names like 'Partner token' and 'User token'. The fix preserves the full example object (including summary and description) instead of extracting only the value field. Co-Authored-By: kenny@buildwithfern.com --- .../converters/abstract/AbstractMediaTypeObjectConverter.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cli/api-importers/v3-importer-commons/src/converters/abstract/AbstractMediaTypeObjectConverter.ts b/packages/cli/api-importers/v3-importer-commons/src/converters/abstract/AbstractMediaTypeObjectConverter.ts index 4fcab82e40f8..0f0731784bfc 100644 --- a/packages/cli/api-importers/v3-importer-commons/src/converters/abstract/AbstractMediaTypeObjectConverter.ts +++ b/packages/cli/api-importers/v3-importer-commons/src/converters/abstract/AbstractMediaTypeObjectConverter.ts @@ -86,9 +86,9 @@ export abstract class AbstractMediaTypeObjectConverter extends AbstractConverter breadcrumbs: [...this.breadcrumbs, "content", contentType, "examples"], skipErrorCollector: true }); - return resolved.resolved ? [key, resolved.value.value ?? resolved.value] : null; + return resolved.resolved ? [key, resolved.value] : null; } - return [key, example.value ?? example]; + return [key, example]; }) .filter((entry): entry is [string, OpenAPIV3_1.ExampleObject] => entry != null) ) From a1306530775a94ba52135d14c4e815cd404605a5 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Sat, 22 Nov 2025 23:09:38 +0000 Subject: [PATCH 2/7] test(openapi): Add regression test for example summaries preservation Adds test case following CLAUDE.md instructions to verify that OpenAPI example summaries (defined via examples..summary) are correctly preserved through the v3 parser pipeline. Test validates that: - Example names 'Partner token' and 'User token' appear in IR - Generic names like 'Example 1' or 'Example 2' are NOT generated - FDR output contains the correct example names Includes fixture with OpenAPI spec from GitHub issue #9551 and snapshot files for regression testing. Co-Authored-By: kenny@buildwithfern.com --- .../__snapshots__/example-summaries-fdr.snap | 225 +++++++ .../__snapshots__/example-summaries-ir.snap | 609 ++++++++++++++++++ .../fixtures/example-summaries/generators.yml | 3 + .../fixtures/example-summaries/openapi.yml | 41 ++ .../__test__/openapi-from-flag.test.ts | 104 +++ 5 files changed, 982 insertions(+) create mode 100644 packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/example-summaries-fdr.snap create mode 100644 packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/example-summaries-ir.snap create mode 100644 packages/cli/register/src/ir-to-fdr-converter/__test__/fixtures/example-summaries/generators.yml create mode 100644 packages/cli/register/src/ir-to-fdr-converter/__test__/fixtures/example-summaries/openapi.yml diff --git a/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/example-summaries-fdr.snap b/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/example-summaries-fdr.snap new file mode 100644 index 000000000000..9d04134bbb27 --- /dev/null +++ b/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/example-summaries-fdr.snap @@ -0,0 +1,225 @@ +{ + "auth": undefined, + "authSchemes": {}, + "globalHeaders": [], + "navigation": undefined, + "rootPackage": { + "endpoints": [ + { + "auth": false, + "authV2": undefined, + "availability": undefined, + "defaultEnvironment": "https://httpbin.org/anything", + "description": undefined, + "environments": [ + { + "baseUrl": "https://httpbin.org/anything", + "id": "https://httpbin.org/anything", + }, + ], + "errors": undefined, + "errorsV2": [], + "examples": [ + { + "codeSamples": undefined, + "description": "", + "headers": {}, + "name": undefined, + "path": "/something", + "pathParameters": {}, + "queryParameters": {}, + "requestBody": { + "foo": "user", + }, + "requestBodyV3": { + "type": "json", + "value": { + "foo": "user", + }, + }, + "responseBody": { + "foo": "string", + }, + "responseBodyV3": { + "type": "json", + "value": { + "foo": "string", + }, + }, + "responseStatusCode": 200, + }, + { + "codeSamples": undefined, + "description": "", + "headers": {}, + "name": undefined, + "path": "/something", + "pathParameters": {}, + "queryParameters": {}, + "requestBody": { + "foo": "partner", + }, + "requestBodyV3": { + "type": "json", + "value": { + "foo": "partner", + }, + }, + "responseBody": { + "foo": "string", + }, + "responseBodyV3": { + "type": "json", + "value": { + "foo": "string", + }, + }, + "responseStatusCode": 200, + }, + ], + "headers": [], + "id": "doSomething", + "includeInApiExplorer": undefined, + "method": "POST", + "multiAuth": undefined, + "name": "Do something", + "originalEndpointId": "endpoint_.doSomething", + "path": { + "parts": [ + { + "type": "literal", + "value": "", + }, + { + "type": "literal", + "value": "/something", + }, + ], + "pathParameters": [], + }, + "protocol": { + "type": "rest", + }, + "queryParameters": [], + "request": { + "description": undefined, + "type": { + "contentType": "application/json", + "description": undefined, + "shape": { + "type": "reference", + "value": { + "default": undefined, + "type": "id", + "value": "Foo", + }, + }, + "type": "json", + }, + }, + "requestsV2": { + "requests": [ + { + "description": undefined, + "type": { + "contentType": "application/json", + "description": undefined, + "shape": { + "type": "reference", + "value": { + "default": undefined, + "type": "id", + "value": "Foo", + }, + }, + "type": "json", + }, + }, + ], + }, + "response": { + "description": "ok", + "isWildcard": undefined, + "statusCode": 200, + "type": { + "type": "reference", + "value": { + "default": undefined, + "type": "id", + "value": "Foo", + }, + }, + }, + "responsesV2": { + "responses": [ + { + "description": "ok", + "isWildcard": undefined, + "statusCode": 200, + "type": { + "type": "reference", + "value": { + "default": undefined, + "type": "id", + "value": "Foo", + }, + }, + }, + ], + }, + "slug": undefined, + }, + ], + "pointsTo": undefined, + "subpackages": [], + "types": [ + "Foo", + ], + "webhooks": [], + "websockets": [], + }, + "snippetsConfiguration": { + "csharpSdk": undefined, + "goSdk": undefined, + "javaSdk": undefined, + "phpSdk": undefined, + "pythonSdk": undefined, + "rubySdk": undefined, + "rustSdk": undefined, + "swiftSdk": undefined, + "typescriptSdk": undefined, + }, + "subpackages": {}, + "types": { + "Foo": { + "availability": undefined, + "description": undefined, + "displayName": undefined, + "name": "Foo", + "shape": { + "extends": [], + "extraProperties": undefined, + "properties": [ + { + "availability": undefined, + "description": undefined, + "key": "foo", + "propertyAccess": undefined, + "valueType": { + "type": "primitive", + "value": { + "default": undefined, + "format": undefined, + "maxLength": undefined, + "minLength": undefined, + "regex": undefined, + "type": "string", + }, + }, + }, + ], + "type": "object", + }, + }, + }, +} \ No newline at end of file diff --git a/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/example-summaries-ir.snap b/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/example-summaries-ir.snap new file mode 100644 index 000000000000..641e84f9f84b --- /dev/null +++ b/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/example-summaries-ir.snap @@ -0,0 +1,609 @@ +{ + "apiDisplayName": undefined, + "apiDocs": undefined, + "apiName": { + "camelCase": { + "safeName": "", + "unsafeName": "", + }, + "originalName": "", + "pascalCase": { + "safeName": "", + "unsafeName": "", + }, + "screamingSnakeCase": { + "safeName": "", + "unsafeName": "", + }, + "snakeCase": { + "safeName": "", + "unsafeName": "", + }, + }, + "apiPlayground": undefined, + "apiVersion": undefined, + "audiences": undefined, + "auth": { + "docs": undefined, + "requirement": "ALL", + "schemes": [], + }, + "basePath": undefined, + "constants": { + "errorInstanceIdKey": { + "name": { + "camelCase": { + "safeName": "errorInstanceId", + "unsafeName": "errorInstanceId", + }, + "originalName": "errorInstanceId", + "pascalCase": { + "safeName": "ErrorInstanceId", + "unsafeName": "ErrorInstanceId", + }, + "screamingSnakeCase": { + "safeName": "ERROR_INSTANCE_ID", + "unsafeName": "ERROR_INSTANCE_ID", + }, + "snakeCase": { + "safeName": "error_instance_id", + "unsafeName": "error_instance_id", + }, + }, + "wireValue": "errorInstanceId", + }, + }, + "dynamic": undefined, + "environments": { + "defaultEnvironment": "https://httpbin.org/anything", + "environments": { + "_visit": [Function], + "environments": [ + { + "docs": undefined, + "id": "https://httpbin.org/anything", + "name": { + "camelCase": { + "safeName": "httpsHttpbinOrgAnything", + "unsafeName": "httpsHttpbinOrgAnything", + }, + "originalName": "https://httpbin.org/anything", + "pascalCase": { + "safeName": "HttpsHttpbinOrgAnything", + "unsafeName": "HttpsHttpbinOrgAnything", + }, + "screamingSnakeCase": { + "safeName": "HTTPS_HTTPBIN_ORG_ANYTHING", + "unsafeName": "HTTPS_HTTPBIN_ORG_ANYTHING", + }, + "snakeCase": { + "safeName": "https_httpbin_org_anything", + "unsafeName": "https_httpbin_org_anything", + }, + }, + "url": "https://httpbin.org/anything", + }, + ], + "type": "singleBaseUrl", + }, + }, + "errorDiscriminationStrategy": { + "_visit": [Function], + "type": "statusCode", + }, + "errors": {}, + "fdrApiDefinitionId": undefined, + "generationMetadata": undefined, + "headers": [], + "idempotencyHeaders": [], + "pathParameters": [], + "publishConfig": undefined, + "readmeConfig": undefined, + "rootPackage": { + "docs": undefined, + "errors": [], + "fernFilepath": { + "allParts": [], + "file": undefined, + "packagePath": [], + }, + "hasEndpointsInTree": false, + "navigationConfig": undefined, + "service": "service_", + "subpackages": [], + "types": [ + "Foo", + ], + "webhooks": undefined, + "websocket": undefined, + }, + "sdkConfig": { + "hasFileDownloadEndpoints": false, + "hasPaginatedEndpoints": false, + "hasStreamingEndpoints": false, + "isAuthMandatory": true, + "platformHeaders": { + "language": "", + "sdkName": "", + "sdkVersion": "", + "userAgent": undefined, + }, + }, + "selfHosted": false, + "serviceTypeReferenceInfo": { + "sharedTypes": [], + "typesReferencedOnlyByService": {}, + }, + "services": { + "service_": { + "audiences": undefined, + "availability": undefined, + "basePath": { + "head": "", + "parts": [], + }, + "displayName": undefined, + "encoding": undefined, + "endpoints": [ + { + "allPathParameters": [], + "apiPlayground": undefined, + "audiences": [], + "auth": false, + "autogeneratedExamples": [], + "availability": undefined, + "basePath": undefined, + "baseUrl": "https://httpbin.org/anything", + "displayName": "Do something", + "docs": undefined, + "errors": [], + "fullPath": { + "head": "/something", + "parts": [], + }, + "headers": [], + "id": "endpoint_.doSomething", + "idempotent": false, + "method": "POST", + "name": { + "camelCase": { + "safeName": "doSomething", + "unsafeName": "doSomething", + }, + "originalName": "doSomething", + "pascalCase": { + "safeName": "DoSomething", + "unsafeName": "DoSomething", + }, + "screamingSnakeCase": { + "safeName": "DO_SOMETHING", + "unsafeName": "DO_SOMETHING", + }, + "snakeCase": { + "safeName": "do_something", + "unsafeName": "do_something", + }, + }, + "pagination": undefined, + "path": { + "head": "/something", + "parts": [], + }, + "pathParameters": [], + "queryParameters": [], + "requestBody": { + "_visit": [Function], + "contentType": "application/json", + "docs": undefined, + "requestBodyType": { + "_visit": [Function], + "default": undefined, + "displayName": undefined, + "fernFilepath": { + "allParts": [], + "file": undefined, + "packagePath": [], + }, + "inline": false, + "name": { + "camelCase": { + "safeName": "foo", + "unsafeName": "foo", + }, + "originalName": "Foo", + "pascalCase": { + "safeName": "Foo", + "unsafeName": "Foo", + }, + "screamingSnakeCase": { + "safeName": "FOO", + "unsafeName": "FOO", + }, + "snakeCase": { + "safeName": "foo", + "unsafeName": "foo", + }, + }, + "type": "named", + "typeId": "Foo", + }, + "type": "reference", + "v2Examples": { + "autogeneratedExamples": {}, + "userSpecifiedExamples": { + "Partner token": { + "foo": "partner", + }, + "User token": { + "foo": "user", + }, + }, + }, + }, + "response": { + "body": { + "_visit": [Function], + "type": "json", + "value": { + "_visit": [Function], + "docs": "ok", + "responseBodyType": { + "_visit": [Function], + "default": undefined, + "displayName": undefined, + "fernFilepath": { + "allParts": [], + "file": undefined, + "packagePath": [], + }, + "inline": false, + "name": { + "camelCase": { + "safeName": "foo", + "unsafeName": "foo", + }, + "originalName": "Foo", + "pascalCase": { + "safeName": "Foo", + "unsafeName": "Foo", + }, + "screamingSnakeCase": { + "safeName": "FOO", + "unsafeName": "FOO", + }, + "snakeCase": { + "safeName": "foo", + "unsafeName": "foo", + }, + }, + "type": "named", + "typeId": "Foo", + }, + "type": "response", + "v2Examples": { + "autogeneratedExamples": { + "doSomethingExample": { + "foo": "string", + }, + }, + "userSpecifiedExamples": {}, + }, + }, + }, + "statusCode": 200, + }, + "retries": undefined, + "sdkRequest": undefined, + "security": undefined, + "source": { + "_visit": [Function], + "type": "openapi", + }, + "transport": undefined, + "userSpecifiedExamples": [], + "v2BaseUrls": undefined, + "v2Examples": { + "autogeneratedExamples": { + "Partner token_doSomethingExample_200": { + "codeSamples": undefined, + "displayName": "Partner token", + "request": { + "auth": undefined, + "baseUrl": undefined, + "docs": undefined, + "endpoint": { + "method": "POST", + "path": "/something", + }, + "environment": "https://httpbin.org/anything", + "headers": {}, + "pathParameters": {}, + "queryParameters": {}, + "requestBody": { + "foo": "partner", + }, + }, + "response": { + "body": { + "_visit": [Function], + "type": "json", + "value": { + "foo": "string", + }, + }, + "docs": undefined, + "statusCode": 200, + }, + }, + }, + "userSpecifiedExamples": { + "User token_doSomethingExample_200": { + "codeSamples": undefined, + "displayName": "User token", + "request": { + "auth": undefined, + "baseUrl": undefined, + "docs": undefined, + "endpoint": { + "method": "POST", + "path": "/something", + }, + "environment": "https://httpbin.org/anything", + "headers": {}, + "pathParameters": {}, + "queryParameters": {}, + "requestBody": { + "foo": "user", + }, + }, + "response": { + "body": { + "_visit": [Function], + "type": "json", + "value": { + "foo": "string", + }, + }, + "docs": undefined, + "statusCode": 200, + }, + }, + }, + }, + "v2RequestBodies": { + "requestBodies": [ + { + "_visit": [Function], + "contentType": "application/json", + "docs": undefined, + "requestBodyType": { + "_visit": [Function], + "default": undefined, + "displayName": undefined, + "fernFilepath": { + "allParts": [], + "file": undefined, + "packagePath": [], + }, + "inline": false, + "name": { + "camelCase": { + "safeName": "foo", + "unsafeName": "foo", + }, + "originalName": "Foo", + "pascalCase": { + "safeName": "Foo", + "unsafeName": "Foo", + }, + "screamingSnakeCase": { + "safeName": "FOO", + "unsafeName": "FOO", + }, + "snakeCase": { + "safeName": "foo", + "unsafeName": "foo", + }, + }, + "type": "named", + "typeId": "Foo", + }, + "type": "reference", + "v2Examples": { + "autogeneratedExamples": {}, + "userSpecifiedExamples": { + "Partner token": { + "foo": "partner", + }, + "User token": { + "foo": "user", + }, + }, + }, + }, + ], + }, + "v2Responses": { + "responses": [ + { + "body": { + "_visit": [Function], + "type": "json", + "value": { + "_visit": [Function], + "docs": "ok", + "responseBodyType": { + "_visit": [Function], + "default": undefined, + "displayName": undefined, + "fernFilepath": { + "allParts": [], + "file": undefined, + "packagePath": [], + }, + "inline": false, + "name": { + "camelCase": { + "safeName": "foo", + "unsafeName": "foo", + }, + "originalName": "Foo", + "pascalCase": { + "safeName": "Foo", + "unsafeName": "Foo", + }, + "screamingSnakeCase": { + "safeName": "FOO", + "unsafeName": "FOO", + }, + "snakeCase": { + "safeName": "foo", + "unsafeName": "foo", + }, + }, + "type": "named", + "typeId": "Foo", + }, + "type": "response", + "v2Examples": { + "autogeneratedExamples": { + "doSomethingExample": { + "foo": "string", + }, + }, + "userSpecifiedExamples": {}, + }, + }, + }, + "statusCode": 200, + }, + ], + }, + }, + ], + "headers": [], + "name": { + "fernFilepath": { + "allParts": [], + "file": undefined, + "packagePath": [], + }, + }, + "pathParameters": [], + "transport": undefined, + }, + }, + "sourceConfig": undefined, + "subpackages": {}, + "types": { + "Foo": { + "autogeneratedExamples": [], + "availability": undefined, + "docs": undefined, + "encoding": undefined, + "inline": false, + "name": { + "displayName": undefined, + "fernFilepath": { + "allParts": [], + "file": undefined, + "packagePath": [], + }, + "name": { + "camelCase": { + "safeName": "foo", + "unsafeName": "foo", + }, + "originalName": "Foo", + "pascalCase": { + "safeName": "Foo", + "unsafeName": "Foo", + }, + "screamingSnakeCase": { + "safeName": "FOO", + "unsafeName": "FOO", + }, + "snakeCase": { + "safeName": "foo", + "unsafeName": "foo", + }, + }, + "typeId": "Foo", + }, + "referencedTypes": Set {}, + "shape": { + "_visit": [Function], + "extendedProperties": [], + "extends": [], + "extraProperties": false, + "properties": [ + { + "availability": undefined, + "docs": undefined, + "name": { + "name": { + "camelCase": { + "safeName": "foo", + "unsafeName": "foo", + }, + "originalName": "foo", + "pascalCase": { + "safeName": "Foo", + "unsafeName": "Foo", + }, + "screamingSnakeCase": { + "safeName": "FOO", + "unsafeName": "FOO", + }, + "snakeCase": { + "safeName": "foo", + "unsafeName": "foo", + }, + }, + "wireValue": "foo", + }, + "propertyAccess": undefined, + "v2Examples": { + "autogeneratedExamples": { + "FooFoo_example_autogenerated": "string", + }, + "userSpecifiedExamples": {}, + }, + "valueType": { + "_visit": [Function], + "primitive": { + "v1": "STRING", + "v2": { + "_visit": [Function], + "default": undefined, + "type": "string", + "validation": { + "format": undefined, + "maxLength": undefined, + "minLength": undefined, + "pattern": undefined, + }, + }, + }, + "type": "primitive", + }, + }, + ], + "type": "object", + }, + "source": undefined, + "userProvidedExamples": [], + "v2Examples": { + "autogeneratedExamples": { + "Foo_example_autogenerated": { + "foo": "string", + }, + }, + "userSpecifiedExamples": {}, + }, + }, + }, + "variables": [], + "webhookGroups": {}, + "websocketChannels": undefined, +} \ No newline at end of file diff --git a/packages/cli/register/src/ir-to-fdr-converter/__test__/fixtures/example-summaries/generators.yml b/packages/cli/register/src/ir-to-fdr-converter/__test__/fixtures/example-summaries/generators.yml new file mode 100644 index 000000000000..bc9a333a3500 --- /dev/null +++ b/packages/cli/register/src/ir-to-fdr-converter/__test__/fixtures/example-summaries/generators.yml @@ -0,0 +1,3 @@ +api: + specs: + - openapi: openapi.yml diff --git a/packages/cli/register/src/ir-to-fdr-converter/__test__/fixtures/example-summaries/openapi.yml b/packages/cli/register/src/ir-to-fdr-converter/__test__/fixtures/example-summaries/openapi.yml new file mode 100644 index 000000000000..d54c6c921528 --- /dev/null +++ b/packages/cli/register/src/ir-to-fdr-converter/__test__/fixtures/example-summaries/openapi.yml @@ -0,0 +1,41 @@ +openapi: 3.1.0 +info: + title: Example Summaries Test + version: 1.0.0 +servers: + - url: https://httpbin.org/anything +paths: + /something: + post: + summary: Do something + operationId: doSomething + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Foo' + examples: + partner_token: + summary: Partner token + value: + foo: partner + user_token: + summary: User token + value: + foo: user + responses: + '200': + description: ok + content: + application/json: + schema: + $ref: '#/components/schemas/Foo' + +components: + schemas: + Foo: + type: object + required: [foo] + properties: + foo: + type: string diff --git a/packages/cli/register/src/ir-to-fdr-converter/__test__/openapi-from-flag.test.ts b/packages/cli/register/src/ir-to-fdr-converter/__test__/openapi-from-flag.test.ts index 524a8ca8382e..c88b093fb772 100644 --- a/packages/cli/register/src/ir-to-fdr-converter/__test__/openapi-from-flag.test.ts +++ b/packages/cli/register/src/ir-to-fdr-converter/__test__/openapi-from-flag.test.ts @@ -609,4 +609,108 @@ describe("OpenAPI v3 Parser Pipeline (--from-openapi flag)", () => { await expect(fdrApiDefinition).toMatchFileSnapshot("__snapshots__/x-code-samples-override-fdr.snap"); await expect(intermediateRepresentation).toMatchFileSnapshot("__snapshots__/x-code-samples-override-ir.snap"); }); + + it("should preserve OpenAPI example summaries in request bodies", async () => { + // Test that example summaries defined via examples..summary are preserved + // Fixes: https://github.com/fern-api/fern/issues/9551 + const context = createMockTaskContext(); + const workspace = await loadAPIWorkspace({ + absolutePathToWorkspace: join( + AbsoluteFilePath.of(__dirname), + RelativeFilePath.of("fixtures/example-summaries") + ), + context, + cliVersion: "0.0.0", + workspaceName: "example-summaries" + }); + + expect(workspace.didSucceed).toBe(true); + assert(workspace.didSucceed); + + if (!(workspace.workspace instanceof OSSWorkspace)) { + throw new Error( + `Expected OSSWorkspace for OpenAPI processing, got ${workspace.workspace.constructor.name}` + ); + } + + const intermediateRepresentation = await workspace.workspace.getIntermediateRepresentation({ + context, + audiences: { type: "all" }, + enableUniqueErrorsPerEndpoint: true, + generateV1Examples: false, + logWarnings: false + }); + + // Convert to FDR format (complete pipeline) + const fdrApiDefinition = await convertIrToFdrApi({ + ir: intermediateRepresentation, + snippetsConfig: { + typescriptSdk: undefined, + pythonSdk: undefined, + javaSdk: undefined, + rubySdk: undefined, + goSdk: undefined, + csharpSdk: undefined, + phpSdk: undefined, + swiftSdk: undefined, + rustSdk: undefined + }, + playgroundConfig: { + oauth: true + }, + context + }); + + // Validate that example summaries are preserved in IR + expect(intermediateRepresentation.services).toBeDefined(); + const services = Object.values(intermediateRepresentation.services); + expect(services.length).toBeGreaterThan(0); + + const service = services[0]; + expect(service).toBeDefined(); + + if (service && typeof service === "object" && "endpoints" in service) { + const serviceWithEndpoints = service as { + endpoints?: Array<{ + examples?: Array<{ name?: string }>; + requestBody?: { + contentType?: string; + shape?: { + type?: string; + value?: { + jsonExample?: { + properties?: Record; + }; + }; + }; + }; + }> + }; + + expect(serviceWithEndpoints.endpoints).toBeDefined(); + expect(serviceWithEndpoints.endpoints?.length).toBeGreaterThan(0); + + const doSomethingEndpoint = serviceWithEndpoints.endpoints?.[0]; + expect(doSomethingEndpoint).toBeDefined(); + + // Validate that request body has examples with proper names + // The example names should be "Partner token" and "User token", not "Example 1" or "Example 2" + const irString = JSON.stringify(intermediateRepresentation); + expect(irString).toContain("Partner token"); + expect(irString).toContain("User token"); + + // Should NOT contain generic example names + expect(irString).not.toContain("Example 1"); + expect(irString).not.toContain("Example 2"); + } + + // Validate FDR structure + expect(fdrApiDefinition.types).toBeDefined(); + expect(fdrApiDefinition.subpackages).toBeDefined(); + expect(fdrApiDefinition.rootPackage).toBeDefined(); + + // Snapshot the complete output for regression testing + await expect(fdrApiDefinition).toMatchFileSnapshot("__snapshots__/example-summaries-fdr.snap"); + await expect(intermediateRepresentation).toMatchFileSnapshot("__snapshots__/example-summaries-ir.snap"); + }); }); From e97441d9c18da3ad409325b869f0b98eff7d918a Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Sat, 22 Nov 2025 23:13:31 +0000 Subject: [PATCH 3/7] fix(test): Fix biome formatting in openapi-from-flag test Co-Authored-By: kenny@buildwithfern.com --- .../__test__/openapi-from-flag.test.ts | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/cli/register/src/ir-to-fdr-converter/__test__/openapi-from-flag.test.ts b/packages/cli/register/src/ir-to-fdr-converter/__test__/openapi-from-flag.test.ts index c88b093fb772..a1aa59c287d6 100644 --- a/packages/cli/register/src/ir-to-fdr-converter/__test__/openapi-from-flag.test.ts +++ b/packages/cli/register/src/ir-to-fdr-converter/__test__/openapi-from-flag.test.ts @@ -668,25 +668,25 @@ describe("OpenAPI v3 Parser Pipeline (--from-openapi flag)", () => { const service = services[0]; expect(service).toBeDefined(); - + if (service && typeof service === "object" && "endpoints" in service) { - const serviceWithEndpoints = service as { - endpoints?: Array<{ + const serviceWithEndpoints = service as { + endpoints?: Array<{ examples?: Array<{ name?: string }>; - requestBody?: { + requestBody?: { contentType?: string; - shape?: { + shape?: { type?: string; - value?: { - jsonExample?: { + value?: { + jsonExample?: { properties?: Record; }; }; }; }; - }> + }>; }; - + expect(serviceWithEndpoints.endpoints).toBeDefined(); expect(serviceWithEndpoints.endpoints?.length).toBeGreaterThan(0); @@ -698,7 +698,7 @@ describe("OpenAPI v3 Parser Pipeline (--from-openapi flag)", () => { const irString = JSON.stringify(intermediateRepresentation); expect(irString).toContain("Partner token"); expect(irString).toContain("User token"); - + // Should NOT contain generic example names expect(irString).not.toContain("Example 1"); expect(irString).not.toContain("Example 2"); From 55a1c727e968b32ad1a4b79d956a4f53ea303915 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Sat, 22 Nov 2025 23:28:06 +0000 Subject: [PATCH 4/7] fix(openapi): Preserve example displayName in FDR conversion When converting IR v2 examples to FDR format, prefer example.displayName over the shouldUseExampleName logic. This ensures OpenAPI example summaries are always surfaced in the FDR output, fixing the issue where example names appeared as undefined instead of user-provided summaries like 'Partner token' and 'User token'. The fix maintains backward compatibility by falling back to the existing shouldUseExampleName behavior when displayName is not present. Fixes #9551 Co-Authored-By: kenny@buildwithfern.com --- .../__test__/__snapshots__/example-summaries-fdr.snap | 4 ++-- .../__snapshots__/multiple-security-headers-fdr.snap | 8 ++++---- .../__snapshots__/oneOf-references-mapping-fdr.snap | 2 +- .../__test__/__snapshots__/openapi-auth-test-fdr.snap | 4 ++-- .../__snapshots__/openapi-from-flag-simple-fdr.snap | 6 +++--- .../__test__/__snapshots__/x-code-samples-fdr.snap | 2 +- .../register/src/ir-to-fdr-converter/convertPackage.ts | 4 +++- 7 files changed, 16 insertions(+), 14 deletions(-) diff --git a/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/example-summaries-fdr.snap b/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/example-summaries-fdr.snap index 9d04134bbb27..965037c27be2 100644 --- a/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/example-summaries-fdr.snap +++ b/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/example-summaries-fdr.snap @@ -24,7 +24,7 @@ "codeSamples": undefined, "description": "", "headers": {}, - "name": undefined, + "name": "User token", "path": "/something", "pathParameters": {}, "queryParameters": {}, @@ -52,7 +52,7 @@ "codeSamples": undefined, "description": "", "headers": {}, - "name": undefined, + "name": "Partner token", "path": "/something", "pathParameters": {}, "queryParameters": {}, diff --git a/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/multiple-security-headers-fdr.snap b/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/multiple-security-headers-fdr.snap index 42e4e7b236a3..1d4724507cac 100644 --- a/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/multiple-security-headers-fdr.snap +++ b/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/multiple-security-headers-fdr.snap @@ -86,7 +86,7 @@ "codeSamples": undefined, "description": "", "headers": {}, - "name": undefined, + "name": "createTransactionExample", "path": "/transaction", "pathParameters": {}, "queryParameters": {}, @@ -237,7 +237,7 @@ "codeSamples": undefined, "description": "", "headers": {}, - "name": undefined, + "name": "createUserTransactionExample", "path": "/user-transaction", "pathParameters": {}, "queryParameters": {}, @@ -392,7 +392,7 @@ "codeSamples": undefined, "description": "", "headers": {}, - "name": undefined, + "name": "getMerchantSettingsExample", "path": "/merchant/settings", "pathParameters": {}, "queryParameters": {}, @@ -499,7 +499,7 @@ "codeSamples": undefined, "description": "", "headers": {}, - "name": undefined, + "name": "listUsersExample", "path": "/admin/users", "pathParameters": {}, "queryParameters": {}, diff --git a/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/oneOf-references-mapping-fdr.snap b/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/oneOf-references-mapping-fdr.snap index 44940fba7d14..c4c89fb08bd1 100644 --- a/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/oneOf-references-mapping-fdr.snap +++ b/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/oneOf-references-mapping-fdr.snap @@ -41,7 +41,7 @@ "codeSamples": undefined, "description": "", "headers": {}, - "name": undefined, + "name": "createEventExample", "path": "/events", "pathParameters": {}, "queryParameters": {}, diff --git a/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/openapi-auth-test-fdr.snap b/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/openapi-auth-test-fdr.snap index 360df07d7b38..7d93b0abaa56 100644 --- a/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/openapi-auth-test-fdr.snap +++ b/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/openapi-auth-test-fdr.snap @@ -53,7 +53,7 @@ "codeSamples": undefined, "description": "", "headers": {}, - "name": undefined, + "name": "getProtectedExample", "path": "/protected", "pathParameters": {}, "queryParameters": {}, @@ -158,7 +158,7 @@ "codeSamples": undefined, "description": "", "headers": {}, - "name": undefined, + "name": "getPublicExample", "path": "/public", "pathParameters": {}, "queryParameters": {}, diff --git a/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/openapi-from-flag-simple-fdr.snap b/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/openapi-from-flag-simple-fdr.snap index 6b905750c151..a3d46e228909 100644 --- a/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/openapi-from-flag-simple-fdr.snap +++ b/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/openapi-from-flag-simple-fdr.snap @@ -41,7 +41,7 @@ "codeSamples": undefined, "description": "", "headers": {}, - "name": undefined, + "name": "getHealthExample", "path": "/health", "pathParameters": {}, "queryParameters": {}, @@ -159,7 +159,7 @@ "codeSamples": undefined, "description": "", "headers": {}, - "name": undefined, + "name": "getUserExample", "path": "/users/user123", "pathParameters": { "userId": "user123", @@ -329,7 +329,7 @@ "codeSamples": undefined, "description": "", "headers": {}, - "name": undefined, + "name": "createUserExample", "path": "/users", "pathParameters": {}, "queryParameters": {}, diff --git a/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/x-code-samples-fdr.snap b/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/x-code-samples-fdr.snap index 9af60607325a..9a37c1334236 100644 --- a/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/x-code-samples-fdr.snap +++ b/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/x-code-samples-fdr.snap @@ -66,7 +66,7 @@ func main() { ], "description": "", "headers": {}, - "name": undefined, + "name": "getUserExample", "path": "/users/userId", "pathParameters": { "userId": "userId", diff --git a/packages/cli/register/src/ir-to-fdr-converter/convertPackage.ts b/packages/cli/register/src/ir-to-fdr-converter/convertPackage.ts index 8f5b3498780f..c673b5f8862c 100644 --- a/packages/cli/register/src/ir-to-fdr-converter/convertPackage.ts +++ b/packages/cli/register/src/ir-to-fdr-converter/convertPackage.ts @@ -909,8 +909,10 @@ function convertV2HttpEndpointExample({ }); const responseBodyValue = example.response?.body != null ? example.response.body.value : undefined; + const resolvedName = + example.displayName != null ? example.displayName : shouldUseExampleName ? exampleName : undefined; return { - name: shouldUseExampleName ? exampleName : undefined, + name: resolvedName, description: "", path: example.request?.endpoint.path ?? "", pathParameters: example.request?.pathParameters ?? {}, From 45cfd7b17412afbebf768bd8152d8c46140fc41c Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Sat, 22 Nov 2025 23:32:53 +0000 Subject: [PATCH 5/7] fix(test): Update gRPC comments snapshot for example displayName The gRPC proto test snapshot needs to be updated to reflect the fix that preserves example.displayName in FDR conversion. The example name is now 'commentsServiceCreatecommentExample' instead of undefined, consistent with the OpenAPI example fixes. Co-Authored-By: kenny@buildwithfern.com --- .../__test__/__snapshots__/grpc-comments-fdr.snap | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/grpc-comments-fdr.snap b/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/grpc-comments-fdr.snap index 059e85e67a52..b80e4e81d0a0 100644 --- a/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/grpc-comments-fdr.snap +++ b/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/grpc-comments-fdr.snap @@ -63,7 +63,7 @@ "codeSamples": undefined, "description": "", "headers": {}, - "name": undefined, + "name": "commentsServiceCreatecommentExample", "path": "/comments/v1/comments", "pathParameters": {}, "queryParameters": {}, @@ -420,4 +420,4 @@ }, }, }, -} \ No newline at end of file +} From 9f223cd2fcf1a5c31e685eb173bdaf7893a885c7 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Sat, 22 Nov 2025 23:35:24 +0000 Subject: [PATCH 6/7] fix(test): Add trailing newline to grpc-comments-fdr.snap Co-Authored-By: kenny@buildwithfern.com --- .../__test__/__snapshots__/grpc-comments-fdr.snap | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/grpc-comments-fdr.snap b/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/grpc-comments-fdr.snap index b80e4e81d0a0..d8f79fd24c9a 100644 --- a/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/grpc-comments-fdr.snap +++ b/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/grpc-comments-fdr.snap @@ -421,3 +421,4 @@ }, }, } + From c7dd9fc65e2c33979135fc70d24c8849c2dbf850 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Sat, 22 Nov 2025 23:40:07 +0000 Subject: [PATCH 7/7] fix(test): Update gRPC snapshot with correct EOF format Restore the grpc-comments-fdr.snap file from main and apply only the name field change to preserve example.displayName. This ensures the EOF format matches the original (no trailing blank line after final }). Co-Authored-By: kenny@buildwithfern.com --- .../__test__/__snapshots__/grpc-comments-fdr.snap | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/grpc-comments-fdr.snap b/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/grpc-comments-fdr.snap index d8f79fd24c9a..b80e4e81d0a0 100644 --- a/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/grpc-comments-fdr.snap +++ b/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/grpc-comments-fdr.snap @@ -421,4 +421,3 @@ }, }, } -