Skip to content

Commit 11efbd0

Browse files
authored
fix(java): Fix java wire tests for sdks with oauth (#11335)
* updates * update java version * Automated update of seed files --------- Co-authored-by: jsklan <[email protected]>
1 parent ce199a5 commit 11efbd0

File tree

257 files changed

+4633
-444
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

257 files changed

+4633
-444
lines changed

generators/java-v2/ast/src/ast/Type.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -395,7 +395,7 @@ export class Type extends AstNode {
395395
}
396396

397397
private writeIterable({ writer, iterable }: { writer: Writer; iterable: Iterable }): void {
398-
writer.writeNode(OptionalClassReference);
398+
writer.writeNode(IterableClassReference);
399399
writer.write("<");
400400
iterable.value.write(writer);
401401
writer.write(">");
@@ -462,3 +462,8 @@ export const InputStreamClassReference = new ClassReference({
462462
name: "InputStream",
463463
packageName: "java.io"
464464
});
465+
466+
export const IterableClassReference = new ClassReference({
467+
name: "Iterable",
468+
packageName: "java.lang"
469+
});

generators/java-v2/ast/src/ast/TypeLiteral.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -673,7 +673,9 @@ export class TypeLiteral extends AstNode {
673673
}
674674

675675
if (values.length === 0) {
676-
writer.write(`new ${classReference.name}<`);
676+
writer.write("new ");
677+
writer.writeNode(classReference);
678+
writer.write("<");
677679
writer.writeNode(iterable.valueType);
678680
writer.write(">()");
679681
return;

generators/java-v2/dynamic-snippets/src/EndpointSnippetGenerator.ts

Lines changed: 95 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -141,16 +141,106 @@ export class EndpointSnippetGenerator {
141141
endpoint: FernIr.dynamic.Endpoint;
142142
snippet: FernIr.dynamic.EndpointSnippetRequest;
143143
}): java.CodeBlock {
144+
// For OAuth APIs, use withCredentials() instead of builder()
145+
const isOAuth = endpoint.auth?.type === "oauth";
146+
144147
return java.codeblock((writer) => {
145148
writer.writeNode(this.context.getRootClientClassReference());
146149
writer.write(` ${CLIENT_VAR_NAME} = `);
147-
writer.writeNode(
148-
java.TypeLiteral.builder({
149-
classReference: this.context.getRootClientClassReference(),
150-
parameters: this.getRootClientBuilderArgs({ endpoint, snippet })
151-
})
150+
151+
if (isOAuth && snippet.auth?.type === "oauth") {
152+
// OAuth: use withCredentials(clientId, clientSecret) pattern
153+
const oauthValues = snippet.auth as FernIr.dynamic.OAuthValues;
154+
writer.writeNode(this.context.getRootClientClassReference());
155+
writer.write(`.withCredentials("${oauthValues.clientId}", "${oauthValues.clientSecret}")`);
156+
writer.writeNewLineIfLastLineNot();
157+
writer.indent();
158+
159+
// Add remaining builder args (url, environment, headers, etc.)
160+
const otherArgs = this.getRootClientBuilderArgsExcludingAuth({ endpoint, snippet });
161+
for (const arg of otherArgs) {
162+
writer.write(`.${arg.name}(`);
163+
if (!arg.value.shouldWriteInLine()) {
164+
writer.newLine();
165+
}
166+
writer.writeNode(arg.value);
167+
if (!arg.value.shouldWriteInLine()) {
168+
writer.newLine();
169+
}
170+
writer.writeLine(")");
171+
}
172+
173+
writer.writeLine(".build()");
174+
writer.dedent();
175+
} else {
176+
// Standard builder() pattern
177+
writer.writeNode(
178+
java.TypeLiteral.builder({
179+
classReference: this.context.getRootClientClassReference(),
180+
parameters: this.getRootClientBuilderArgs({ endpoint, snippet })
181+
})
182+
);
183+
}
184+
});
185+
}
186+
187+
private getRootClientBuilderArgsExcludingAuth({
188+
endpoint,
189+
snippet
190+
}: {
191+
endpoint: FernIr.dynamic.Endpoint;
192+
snippet: FernIr.dynamic.EndpointSnippetRequest;
193+
}): java.BuilderParameter[] {
194+
const builderArgs: java.BuilderParameter[] = [];
195+
196+
// Skip auth - it's handled by withCredentials()
197+
198+
const baseUrlArg = this.getRootClientBaseUrlArg({
199+
baseUrl: snippet.baseURL,
200+
environment: snippet.environment
201+
});
202+
if (baseUrlArg != null) {
203+
builderArgs.push(baseUrlArg);
204+
}
205+
this.context.errors.scope(Scope.Headers);
206+
if (this.context.ir.headers != null && snippet.headers != null) {
207+
builderArgs.push(
208+
...this.getRootClientHeaderArgs({ headers: this.context.ir.headers, values: snippet.headers })
152209
);
210+
}
211+
this.context.errors.unscope();
212+
213+
const usedVariables = new Set<string>();
214+
const allPathParams = [...(this.context.ir.pathParameters ?? []), ...(endpoint.request.pathParameters ?? [])];
215+
216+
allPathParams.forEach((param) => {
217+
if (param.variable != null) {
218+
usedVariables.add(param.variable);
219+
}
153220
});
221+
222+
if (this.context.ir.variables != null && this.context.ir.variables.length > 0) {
223+
for (const variable of this.context.ir.variables) {
224+
if (usedVariables.has(variable.id)) {
225+
const variableName = variable.name.camelCase.unsafeName;
226+
builderArgs.push({
227+
name: variableName,
228+
value: java.TypeLiteral.string(`YOUR_${variable.name.screamingSnakeCase.unsafeName}`)
229+
});
230+
}
231+
}
232+
}
233+
234+
this.context.errors.scope(Scope.PathParameters);
235+
if (this.context.ir.pathParameters != null && this.context.ir.pathParameters.length > 0) {
236+
const apiPathParams = this.context.ir.pathParameters.filter((param) => param.variable == null);
237+
if (apiPathParams.length > 0) {
238+
builderArgs.push(...this.getPathParameters({ namedParameters: apiPathParams, snippet }));
239+
}
240+
}
241+
this.context.errors.unscope();
242+
243+
return builderArgs;
154244
}
155245

156246
private buildFullCodeBlock({ body, options }: { body: java.CodeBlock; options: Options }): java.AstNode {

generators/java-v2/sdk/src/sdk-wire-tests/SdkWireTestGenerator.ts

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -484,35 +484,11 @@ export class SdkWireTestGenerator {
484484
let transformedSnippet = snippet;
485485

486486
const isStreamingEndpoint = endpoint.response?.body?.type === "streaming";
487-
const methodCamel = endpoint.name.camelCase.safeName;
488-
const methodPascal = endpoint.name.pascalCase.safeName;
489-
const requestPascal = methodPascal + "Request";
490-
const streamRequestPascal = methodPascal + "StreamRequest";
491487

492488
if (isStreamingEndpoint) {
493-
// For streaming endpoints, ensure method name ends with "Stream"
494-
// Only add suffix if it doesn't already end with "Stream"
495-
if (!methodCamel.endsWith("Stream")) {
496-
const nonStreamingMethodPattern = new RegExp(`\\.${methodCamel}\\s*\\(`, "g");
497-
transformedSnippet = transformedSnippet.replace(nonStreamingMethodPattern, `.${methodCamel}Stream(`);
498-
}
499-
500-
// For request types, only add "Stream" suffix if not already present
501-
if (!methodPascal.endsWith("Stream")) {
502-
const nonStreamingRequestPattern = new RegExp(`\\b${requestPascal}\\b`, "g");
503-
transformedSnippet = transformedSnippet.replace(nonStreamingRequestPattern, streamRequestPascal);
504-
}
505-
506489
// For streaming endpoints, replace Optional<ResponseType> with Iterable<ResponseType>
490+
// The Java SDK generator uses Iterable for streaming responses, not Optional
507491
transformedSnippet = transformedSnippet.replace(/Optional</g, "Iterable<");
508-
} else {
509-
// For non-streaming endpoints, ensure method name does NOT end with "Stream"
510-
// Remove "Stream" suffix if present
511-
const streamingMethodPattern = new RegExp(`\\.${methodCamel}Stream\\s*\\(`, "g");
512-
transformedSnippet = transformedSnippet.replace(streamingMethodPattern, `.${methodCamel}(`);
513-
514-
const streamingRequestPattern = new RegExp(`\\b${streamRequestPascal}\\b`, "g");
515-
transformedSnippet = transformedSnippet.replace(streamingRequestPattern, requestPascal);
516492
}
517493

518494
if (endpoint.name.originalName === "listUsernames") {

generators/java-v2/sdk/src/sdk-wire-tests/builders/TestClassBuilder.ts

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -69,17 +69,26 @@ export class TestClassBuilder {
6969
// Note: For OAuth APIs, each test method enqueues its own OAuth token response
7070
// because the token is fetched lazily on first API call
7171

72-
writer.writeLine(`client = ${clientClassName}.builder()`);
73-
writer.indent();
74-
75-
this.generateEnvironmentConfiguration(writer);
72+
// For OAuth APIs, use withCredentials() instead of builder()
73+
const isOAuth = this.isOAuthApi();
74+
if (isOAuth) {
75+
writer.writeLine(`client = ${clientClassName}.withCredentials("test-client-id", "test-client-secret")`);
76+
writer.indent();
77+
this.generateEnvironmentConfiguration(writer);
78+
writer.writeLine(".build();");
79+
} else {
80+
writer.writeLine(`client = ${clientClassName}.builder()`);
81+
writer.indent();
82+
83+
this.generateEnvironmentConfiguration(writer);
84+
85+
const authConfig = this.getAuthClientBuilderCalls();
86+
if (authConfig) {
87+
writer.writeLine(authConfig);
88+
}
7689

77-
const authConfig = this.getAuthClientBuilderCalls();
78-
if (authConfig) {
79-
writer.writeLine(authConfig);
90+
writer.writeLine(".build();");
8091
}
81-
82-
writer.writeLine(".build();");
8392
writer.dedent();
8493
writer.dedent();
8594
writer.writeLine("}");

generators/java-v2/sdk/src/sdk-wire-tests/builders/TestMethodBuilder.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,16 @@ export class TestMethodBuilder {
9696
writer.writeLine("Assertions.assertNotNull(request);");
9797
writer.writeLine(`Assertions.assertEquals("${endpoint.method}", request.getMethod());`);
9898

99+
// For OAuth APIs, validate that the Authorization header contains the Bearer token
100+
if (isOAuth) {
101+
writer.writeLine("");
102+
writer.writeLine("// Validate OAuth Authorization header");
103+
writer.writeLine(
104+
'Assertions.assertEquals("Bearer test-token", request.getHeader("Authorization"), ' +
105+
'"OAuth Authorization header should contain Bearer token from OAuth flow");'
106+
);
107+
}
108+
99109
this.headerValidator.generateHeaderValidation(writer, testExample);
100110

101111
if (expectedRequestJson !== undefined && expectedRequestJson !== null) {

generators/java/sdk/versions.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
# yaml-language-server: $schema=../../../fern-versions-yml.schema.json
2+
3+
- version: 3.27.3
4+
changelogEntry:
5+
- summary: |
6+
Fix streaming endpoint return types to use Iterable instead of Optional, fix missing ArrayList import for empty list literals, and fix OAuth wire tests to use withCredentials() pattern.
7+
type: fix
8+
createdAt: "2025-12-18"
9+
irVersion: 61
10+
111
- version: 3.27.2
212
changelogEntry:
313
- summary: |

seed/java-sdk/accept-header/accept-header/.fern/metadata.json

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

seed/java-sdk/alias/.fern/metadata.json

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

seed/java-sdk/any-auth/.fern/metadata.json

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)