Compare commits

...

2 Commits

Author SHA1 Message Date
codegen-sh[bot] ff724f5b2b Fix test assertions and linting issues
- Changed test expectations from error constructors to error message strings
- Removed unused imports (AuthenticationError, InvalidRequestError)
- Replaced 'as any' with 'as unknown as Response' for better type safety
- This resolves test assertion failures and linting errors
2025-09-25 21:55:34 +00:00
codegen-sh[bot] 8d66288adf Fix OAuth userinfo endpoint 401 handling with empty response body
- Check response status before attempting to parse JSON
- Prevents 'Unexpected end of JSON input' error when OAuth provider
  returns 401 with empty body
- Add comprehensive tests for userInfo method error handling
- Fixes issue #10251
2025-09-25 21:28:10 +00:00
2 changed files with 113 additions and 1 deletions
+107
View File
@@ -0,0 +1,107 @@
import fetch from "./fetch";
import OAuthClient from "./oauth";
// Mock the fetch utility
jest.mock("./fetch");
const mockFetch = fetch as jest.MockedFunction<typeof fetch>;
// Create a concrete implementation of the abstract OAuthClient for testing
class TestOAuthClient extends OAuthClient {
constructor(clientId: string, clientSecret: string) {
super(clientId, clientSecret);
this.endpoints = {
authorize: "https://example.com/authorize",
token: "https://example.com/token",
userinfo: "https://example.com/userinfo",
};
}
}
describe("OAuthClient", () => {
let client: TestOAuthClient;
beforeEach(() => {
client = new TestOAuthClient("test-client-id", "test-client-secret");
jest.clearAllMocks();
});
describe("userInfo", () => {
it("should return user data for successful response", async () => {
const mockUserData = {
id: "123",
name: "Test User",
email: "test@example.com",
};
mockFetch.mockResolvedValue({
status: 200,
json: jest.fn().mockResolvedValue(mockUserData),
} as unknown as Response);
const result = await client.userInfo("valid-access-token");
expect(result).toEqual(mockUserData);
expect(mockFetch).toHaveBeenCalledWith("https://example.com/userinfo", {
method: "GET",
allowPrivateIPAddress: true,
headers: {
Authorization: "Bearer valid-access-token",
"Content-Type": "application/json",
},
});
});
it("should throw AuthenticationError for 401 response with empty body", async () => {
mockFetch.mockResolvedValue({
status: 401,
json: jest
.fn()
.mockRejectedValue(new Error("Unexpected end of JSON input")),
} as unknown as Response);
await expect(client.userInfo("invalid-access-token")).rejects.toThrow(
"Authentication required"
);
});
it("should throw AuthenticationError for 401 response with JSON body", async () => {
mockFetch.mockResolvedValue({
status: 401,
json: jest.fn().mockResolvedValue({ error: "unauthorized" }),
} as unknown as Response);
await expect(client.userInfo("invalid-access-token")).rejects.toThrow(
"Authentication required"
);
});
it("should throw AuthenticationError for other non-success status codes", async () => {
mockFetch.mockResolvedValue({
status: 403,
json: jest.fn().mockResolvedValue({ error: "forbidden" }),
} as unknown as Response);
await expect(client.userInfo("access-token")).rejects.toThrow(
"Authentication required"
);
});
it("should throw InvalidRequestError for network errors", async () => {
mockFetch.mockRejectedValue(new Error("Network error"));
await expect(client.userInfo("access-token")).rejects.toThrow(
"Request invalid"
);
});
it("should throw InvalidRequestError for JSON parsing errors on successful response", async () => {
mockFetch.mockResolvedValue({
status: 200,
json: jest.fn().mockRejectedValue(new Error("Invalid JSON")),
} as unknown as Response);
await expect(client.userInfo("access-token")).rejects.toThrow(
"Request invalid"
);
});
});
});
+6 -1
View File
@@ -30,7 +30,6 @@ export default abstract class OAuthClient {
"Content-Type": "application/json",
},
});
data = await response.json();
} catch (err) {
throw InvalidRequestError(err.message);
}
@@ -40,6 +39,12 @@ export default abstract class OAuthClient {
throw AuthenticationError();
}
try {
data = await response.json();
} catch (err) {
throw InvalidRequestError(err.message);
}
return data;
};