Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions CHANGES.ja.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
変更点
======

2.91 (2026-03-05)
-----------------

- `AuthleteApiJaxrsImpl` クラス
* `callGetApi()` と `callPostApi()` で非 2xx HTTP レスポンスが
`AuthleteApiException` をスローしない問題を修正。v2.88 で型付き
JAX-RS オーバーロードから非型付きオーバーロードへ切り替えた際の
エラーハンドリング契約を復元するため、明示的なステータスチェックを追加。

- 新しいテスト
* `AuthleteApiJaxrsImplTest` クラス
- GET、POST、DELETE のエラーハンドリングに関するリグレッションテストを追加。


2.90 (2025-12-29)
-----------------

Expand Down
14 changes: 14 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
CHANGES
=======

2.91 (2026-03-05)
-----------------

- `AuthleteApiJaxrsImpl` class
* Fixed non-2xx HTTP responses not throwing `AuthleteApiException` in
`callGetApi()` and `callPostApi()`. Added explicit status check to
restore the error-handling contract after the switch from typed to
untyped JAX-RS overloads in v2.88.

- New test
* `AuthleteApiJaxrsImplTest` class
- Added regression tests for GET, POST, and DELETE error handling.


2.90 (2025-12-29)
-----------------

Expand Down
18 changes: 16 additions & 2 deletions src/main/java/com/authlete/jaxrs/api/AuthleteApiJaxrsImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -430,9 +430,16 @@ protected <TResponse> TResponse callGetApi(
setCustomRequestHeaders(builder, options);

Response httpResponse = builder.get();

if (httpResponse.getStatusInfo().getFamily() != Response.Status.Family.SUCCESSFUL)
{
throw new WebApplicationException(httpResponse);
}

TResponse apiResponseObject = httpResponse.readEntity(responseClass);

if (apiResponseObject instanceof ApiResponse) {
if (apiResponseObject instanceof ApiResponse)
{
((ApiResponse) apiResponseObject).setResponseHeaders(httpResponse.getStringHeaders());
}

Expand Down Expand Up @@ -467,11 +474,18 @@ protected <TResponse> TResponse callPostApi(

Response httpResponse = builder.post(Entity.entity(request, JSON_UTF8_TYPE));

if (httpResponse.getStatusInfo().getFamily() != Response.Status.Family.SUCCESSFUL)
{
throw new WebApplicationException(httpResponse);
}

TResponse apiResponseObject = httpResponse.readEntity(responseClass);

if (apiResponseObject instanceof ApiResponse) {
if (apiResponseObject instanceof ApiResponse)
{
((ApiResponse) apiResponseObject).setResponseHeaders(httpResponse.getStringHeaders());
}

return apiResponseObject;
}

Expand Down
258 changes: 258 additions & 0 deletions src/test/java/com/authlete/jaxrs/api/AuthleteApiJaxrsImplTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
package com.authlete.jaxrs.api;


import com.authlete.common.api.AuthleteApi;
import com.authlete.common.api.AuthleteApiException;
import com.authlete.common.api.Options;
import com.authlete.common.conf.AuthleteConfiguration;
import com.authlete.common.dto.AuthorizationRequest;
import com.authlete.common.dto.AuthorizationResponse;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Invocation;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedHashMap;
import javax.ws.rs.core.Response;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;


/**
* Tests for {@link AuthleteApiJaxrsImpl} to verify that non-2xx HTTP
* responses from the Authlete API are correctly raised as
* {@link AuthleteApiException} with the appropriate status code.
*
* <p>
* The JAX-RS typed overloads ({@code builder.get(Class)},
* {@code builder.post(Entity, Class)}) throw
* {@code WebApplicationException} on non-2xx responses, whereas the
* untyped overloads ({@code builder.get()}, {@code builder.post(Entity)})
* return the {@code Response} silently. These tests ensure that the
* explicit status check is in place so that the error-handling contract
* is preserved regardless of which overload is used internally.
* </p>
*/
public class AuthleteApiJaxrsImplTest
{
private static class TestHarness
{
final Invocation.Builder builder;
final AuthleteApi api;

TestHarness()
{
AuthleteConfiguration configuration =
mock(AuthleteConfiguration.class);
when(configuration.getBaseUrl()).thenReturn("http://example.com");
when(configuration.getServiceApiKey()).thenReturn("key");
when(configuration.getServiceApiSecret()).thenReturn("secret");
when(configuration.getApiVersion()).thenReturn("V2");

Client client = mock(Client.class);
WebTarget webTarget = mock(WebTarget.class);
builder = mock(Invocation.Builder.class);

ClientBuilder clientBuilder = mock(ClientBuilder.class);
doReturn(client).when(clientBuilder).build();
doReturn(webTarget).when(client).target(anyString());
doReturn(webTarget).when(webTarget).path(anyString());
doReturn(webTarget).when(webTarget)
.queryParam(anyString(), any());
doReturn(builder).when(webTarget).request();
doReturn(builder).when(webTarget)
.request(any(MediaType.class));
doReturn(builder).when(builder).header(anyString(), any());

AuthleteApiImpl impl = new AuthleteApiImpl(configuration);
impl.setJaxRsClientBuilder(clientBuilder);
api = impl;
}
}


private static Response createErrorResponse(int statusCode)
{
Response response = mock(Response.class);
Response.StatusType statusType = mock(Response.StatusType.class);

doReturn(statusCode).when(statusType).getStatusCode();
doReturn("Error").when(statusType).getReasonPhrase();
doReturn(Response.Status.Family.familyOf(statusCode))
.when(statusType).getFamily();

doReturn(statusType).when(response).getStatusInfo();
doReturn(statusCode).when(response).getStatus();
doReturn(true).when(response).hasEntity();
doReturn("{\"resultCode\":\"error\"}")
.when(response).readEntity(String.class);
doReturn(new MultivaluedHashMap<String, String>())
.when(response).getStringHeaders();

return response;
}


private static Response createSuccessResponse()
{
Response response = mock(Response.class);
Response.StatusType statusType = mock(Response.StatusType.class);

doReturn(Response.Status.Family.SUCCESSFUL)
.when(statusType).getFamily();
doReturn(statusType).when(response).getStatusInfo();
doReturn(new MultivaluedHashMap<String, String>())
.when(response).getStringHeaders();

return response;
}


// ---------------------------------------------------------------
// POST: non-2xx should throw AuthleteApiException
// ---------------------------------------------------------------

@Test
public void testPostApiThrowsOn400()
{
TestHarness h = new TestHarness();
Response response = createErrorResponse(400);
doReturn(response).when(h.builder).post(any());

AuthleteApiException ex = assertThrows(
AuthleteApiException.class,
() -> h.api.authorization(
new AuthorizationRequest(), new Options()));

assertEquals(400, ex.getStatusCode());
}

@Test
public void testPostApiThrowsOn401()
{
TestHarness h = new TestHarness();
Response response = createErrorResponse(401);
doReturn(response).when(h.builder).post(any());

AuthleteApiException ex = assertThrows(
AuthleteApiException.class,
() -> h.api.authorization(
new AuthorizationRequest(), new Options()));

assertEquals(401, ex.getStatusCode());
}

@Test
public void testPostApiThrowsOn500()
{
TestHarness h = new TestHarness();
Response response = createErrorResponse(500);
doReturn(response).when(h.builder).post(any());

AuthleteApiException ex = assertThrows(
AuthleteApiException.class,
() -> h.api.authorization(
new AuthorizationRequest(), new Options()));

assertEquals(500, ex.getStatusCode());
}


// ---------------------------------------------------------------
// GET: non-2xx should throw AuthleteApiException
// ---------------------------------------------------------------

@Test
public void testGetApiThrowsOn400()
{
TestHarness h = new TestHarness();
Response response = createErrorResponse(400);
doReturn(response).when(h.builder).get();

AuthleteApiException ex = assertThrows(
AuthleteApiException.class,
() -> h.api.getServiceJwks(new Options()));

assertEquals(400, ex.getStatusCode());
}

@Test
public void testGetApiThrowsOn401()
{
TestHarness h = new TestHarness();
Response response = createErrorResponse(401);
doReturn(response).when(h.builder).get();

AuthleteApiException ex = assertThrows(
AuthleteApiException.class,
() -> h.api.getServiceJwks(new Options()));

assertEquals(401, ex.getStatusCode());
}

@Test
public void testGetApiThrowsOn500()
{
TestHarness h = new TestHarness();
Response response = createErrorResponse(500);
doReturn(response).when(h.builder).get();

AuthleteApiException ex = assertThrows(
AuthleteApiException.class,
() -> h.api.getServiceJwks(new Options()));

assertEquals(500, ex.getStatusCode());
}


// ---------------------------------------------------------------
// 2xx success paths
// ---------------------------------------------------------------

@Test
public void testPostApiSucceedsOn200()
{
TestHarness h = new TestHarness();
Response response = createSuccessResponse();
doReturn(new AuthorizationResponse())
.when(response).readEntity(AuthorizationResponse.class);
doReturn(response).when(h.builder).post(any());

AuthorizationResponse result = h.api.authorization(
new AuthorizationRequest(), new Options());

assertNotNull(result);
}

@Test
public void testGetApiSucceedsOn200()
{
TestHarness h = new TestHarness();
Response response = createSuccessResponse();
doReturn("{\"keys\":[]}").when(response).readEntity(String.class);
doReturn(response).when(h.builder).get();

String result = h.api.getServiceJwks(new Options());

assertNotNull(result);
}

@Test
public void testDeleteApiSucceeds()
{
TestHarness h = new TestHarness();
Response response = mock(Response.class);
doReturn(200).when(response).getStatus();
doReturn(response).when(h.builder).delete();

h.api.deleteClient("123", new Options());
}
}