Skip to content

Commit d48889b

Browse files
committed
FINERACT-2380: move retrofit client based test to feign
1 parent 5784cb7 commit d48889b

File tree

78 files changed

+4926
-5055
lines changed

Some content is hidden

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

78 files changed

+4926
-5055
lines changed

fineract-client-feign/src/main/java/org/apache/fineract/client/feign/FineractFeignClient.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
import org.apache.fineract.client.feign.services.EntityFieldConfigurationApi;
6262
import org.apache.fineract.client.feign.services.ExternalAssetOwnerLoanProductAttributesApi;
6363
import org.apache.fineract.client.feign.services.ExternalAssetOwnersApi;
64+
import org.apache.fineract.client.feign.services.ExternalAssetOwnersApiExtension;
6465
import org.apache.fineract.client.feign.services.ExternalEventConfigurationApi;
6566
import org.apache.fineract.client.feign.services.ExternalServicesApi;
6667
import org.apache.fineract.client.feign.services.FetchAuthenticatedUserDetailsApi;
@@ -95,6 +96,7 @@
9596
import org.apache.fineract.client.feign.services.LoanCollateralApi;
9697
import org.apache.fineract.client.feign.services.LoanCollateralManagementApi;
9798
import org.apache.fineract.client.feign.services.LoanDisbursementDetailsApi;
99+
import org.apache.fineract.client.feign.services.LoanDisbursementDetailsApiExtension;
98100
import org.apache.fineract.client.feign.services.LoanInterestPauseApi;
99101
import org.apache.fineract.client.feign.services.LoanProductsApi;
100102
import org.apache.fineract.client.feign.services.LoanReschedulingApi;
@@ -392,6 +394,10 @@ public ExternalAssetOwnersApi externalAssetOwners() {
392394
return create(ExternalAssetOwnersApi.class);
393395
}
394396

397+
public ExternalAssetOwnersApiExtension externalAssetOwnersExtension() {
398+
return create(ExternalAssetOwnersApiExtension.class);
399+
}
400+
395401
public ExternalEventConfigurationApi externalEventConfiguration() {
396402
return create(ExternalEventConfigurationApi.class);
397403
}
@@ -528,6 +534,10 @@ public LoanDisbursementDetailsApi loanDisbursementDetails() {
528534
return create(LoanDisbursementDetailsApi.class);
529535
}
530536

537+
public LoanDisbursementDetailsApiExtension loanDisbursementDetailsExtension() {
538+
return create(LoanDisbursementDetailsApiExtension.class);
539+
}
540+
531541
public LoanInterestPauseApi loanInterestPause() {
532542
return create(LoanInterestPauseApi.class);
533543
}

fineract-client-feign/src/main/java/org/apache/fineract/client/feign/FineractFeignClientConfig.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import javax.net.ssl.SSLContext;
3333
import javax.net.ssl.TrustManager;
3434
import javax.net.ssl.X509TrustManager;
35+
import org.apache.fineract.client.feign.support.HeaderCapturingDecoder;
3536
import org.apache.hc.client5.http.config.RequestConfig;
3637
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
3738
import org.apache.hc.client5.http.impl.classic.HttpClients;
@@ -95,7 +96,8 @@ public <T> T createClient(Class<T> apiType) {
9596
Encoder multipartEncoder = new FineractMultipartEncoder(jacksonEncoder);
9697

9798
return Feign.builder().client(getOrCreateHttpClient()).encoder(multipartEncoder)
98-
.decoder(new JacksonDecoder(ObjectMapperFactory.getShared())).errorDecoder(new FineractErrorDecoder())
99+
.decoder(new HeaderCapturingDecoder(new JacksonDecoder(ObjectMapperFactory.getShared())))
100+
.errorDecoder(new FineractErrorDecoder())
99101
.options(new Request.Options(connectTimeout, TimeUnit.MILLISECONDS, readTimeout, TimeUnit.MILLISECONDS, true))
100102
.retryer(Retryer.NEVER_RETRY).requestInterceptor(new BasicAuthRequestInterceptor(username, password))
101103
.requestInterceptor(new TenantIdRequestInterceptor(tenantId)).logger(new Slf4jLogger(apiType))
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/**
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.fineract.client.feign.services;
20+
21+
import feign.Headers;
22+
import feign.Param;
23+
import feign.QueryMap;
24+
import feign.RequestLine;
25+
import java.util.Map;
26+
import org.apache.fineract.client.models.ExternalAssetOwnerRequest;
27+
import org.apache.fineract.client.models.PostInitiateTransferResponse;
28+
29+
public interface ExternalAssetOwnersApiExtension {
30+
31+
@RequestLine("POST /v1/external-asset-owners/transfers/{id}")
32+
@Headers("Content-Type: application/json")
33+
PostInitiateTransferResponse transferRequestWithIdWithBody(@Param("id") Long id, ExternalAssetOwnerRequest body,
34+
@QueryMap Map<String, Object> queryParams);
35+
36+
@RequestLine("POST /v1/external-asset-owners/transfers/external-id/{externalId}")
37+
@Headers("Content-Type: application/json")
38+
PostInitiateTransferResponse transferRequestWithId1WithBody(@Param("externalId") String externalId, ExternalAssetOwnerRequest body,
39+
@QueryMap Map<String, Object> queryParams);
40+
}

fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanProductsCustomApi.java renamed to fineract-client-feign/src/main/java/org/apache/fineract/client/feign/services/LoanDisbursementDetailsApiExtension.java

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,16 @@
1616
* specific language governing permissions and limitations
1717
* under the License.
1818
*/
19-
package org.apache.fineract.test.stepdef.loan;
19+
package org.apache.fineract.client.feign.services;
2020

21-
import org.apache.fineract.client.models.GetLoanProductsProductIdResponse;
22-
import retrofit2.Call;
23-
import retrofit2.http.GET;
21+
import feign.Param;
22+
import feign.RequestLine;
23+
import org.apache.fineract.client.models.PostAddAndDeleteDisbursementDetailRequest;
2424

25-
public interface LoanProductsCustomApi {
25+
public interface LoanDisbursementDetailsApiExtension {
26+
27+
@RequestLine("PUT /v1/loans/{loanId}/disbursements/editDisbursements")
28+
PostAddAndDeleteDisbursementDetailRequest addAndDeleteDisbursementDetail(@Param("loanId") Long loanId,
29+
PostAddAndDeleteDisbursementDetailRequest postAddAndDeleteDisbursementDetailRequest);
2630

27-
@GET("v1/loanproducts/{productId}")
28-
Call<GetLoanProductsProductIdResponse> retrieveLoanProductDetails(@retrofit2.http.Path("productId") Long productId,
29-
@retrofit2.http.Query("template") String isTemplate);
3031
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/**
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.fineract.client.feign.support;
20+
21+
import feign.Response;
22+
import java.util.Collection;
23+
import java.util.Collections;
24+
import java.util.Map;
25+
26+
/**
27+
* Thread-local holder for the last Feign response headers. This is primarily used by E2E tests that need to assert on
28+
* HTTP headers when using Feign clients that otherwise only expose the deserialized body.
29+
*/
30+
public final class FeignResponseHeaderHolder {
31+
32+
private static final ThreadLocal<Map<String, Collection<String>>> LAST_HEADERS = new ThreadLocal<>();
33+
34+
private FeignResponseHeaderHolder() {}
35+
36+
public static void store(Response response) {
37+
LAST_HEADERS.set(response != null ? response.headers() : null);
38+
}
39+
40+
public static Map<String, Collection<String>> getHeaders() {
41+
Map<String, Collection<String>> headers = LAST_HEADERS.get();
42+
return headers == null ? Collections.emptyMap() : headers;
43+
}
44+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/**
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.fineract.client.feign.support;
20+
21+
import feign.Response;
22+
import feign.codec.Decoder;
23+
import java.io.IOException;
24+
import java.lang.reflect.Type;
25+
26+
/**
27+
* Decorator that captures response headers into a thread-local holder before delegating decoding.
28+
*/
29+
public final class HeaderCapturingDecoder implements Decoder {
30+
31+
private final Decoder delegate;
32+
33+
public HeaderCapturingDecoder(Decoder delegate) {
34+
this.delegate = delegate;
35+
}
36+
37+
@Override
38+
public Object decode(Response response, Type type) throws IOException {
39+
FeignResponseHeaderHolder.store(response);
40+
return delegate.decode(response, type);
41+
}
42+
}

fineract-client-feign/src/main/java/org/apache/fineract/client/feign/util/CallFailedRuntimeException.java

Lines changed: 5 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,9 @@
1818
*/
1919
package org.apache.fineract.client.feign.util;
2020

21-
import com.fasterxml.jackson.databind.JsonNode;
22-
import com.fasterxml.jackson.databind.ObjectMapper;
23-
import feign.FeignException;
24-
import java.io.IOException;
25-
import java.nio.charset.StandardCharsets;
2621
import lombok.Getter;
2722
import lombok.extern.slf4j.Slf4j;
23+
import org.apache.fineract.client.feign.FeignException;
2824

2925
/**
3026
* Exception thrown by {@link FeignCalls} utility when Feign calls fail.
@@ -49,7 +45,7 @@ private static String createMessage(FeignException e) {
4945
sb.append(", request=").append(e.request().url());
5046
}
5147

52-
String contentString = e.contentUTF8();
48+
String contentString = e.responseBodyAsString();
5349
if (contentString != null && !contentString.isEmpty()) {
5450
sb.append(", errorBody=").append(contentString);
5551
}
@@ -58,34 +54,9 @@ private static String createMessage(FeignException e) {
5854
}
5955

6056
private static String extractDeveloperMessage(FeignException e) {
61-
try {
62-
byte[] content = e.content();
63-
if (content == null || content.length == 0) {
64-
return e.getMessage();
65-
}
66-
67-
String contentString = new String(content, StandardCharsets.UTF_8);
68-
ObjectMapper mapper = new ObjectMapper();
69-
JsonNode root = mapper.readTree(contentString);
70-
71-
if (root.has("developerMessage")) {
72-
return root.get("developerMessage").asText();
73-
}
74-
75-
if (root.has("errors")) {
76-
JsonNode errors = root.get("errors");
77-
if (errors.isArray() && errors.size() > 0) {
78-
JsonNode firstError = errors.get(0);
79-
if (firstError.has("developerMessage")) {
80-
return firstError.get("developerMessage").asText();
81-
}
82-
}
83-
}
84-
85-
return contentString;
86-
} catch (IOException ex) {
87-
log.warn("Failed to extract developer message from error response", ex);
88-
return e.getMessage();
57+
if (e.getDeveloperMessage() != null) {
58+
return e.getDeveloperMessage();
8959
}
60+
return e.getMessage();
9061
}
9162
}

fineract-client-feign/src/main/java/org/apache/fineract/client/feign/util/FeignCalls.java

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@
1818
*/
1919
package org.apache.fineract.client.feign.util;
2020

21-
import feign.FeignException;
2221
import java.util.function.Supplier;
22+
import org.apache.fineract.client.feign.FeignException;
2323

2424
/**
2525
* Extension methods for Feign calls. This class is recommended to be statically imported.
@@ -77,4 +77,41 @@ public static void executeVoid(Runnable feignCall) throws CallFailedRuntimeExcep
7777
public static <T> T execute(Supplier<T> feignCall) throws FeignException {
7878
return feignCall.get();
7979
}
80+
81+
/**
82+
* Execute a Feign call expecting failure (for negative tests). Returns the exception with error details.
83+
*
84+
* @param feignCall
85+
* the Feign call to execute
86+
* @return CallFailedRuntimeException containing status code and error message
87+
* @throws AssertionError
88+
* if the call succeeds when failure was expected
89+
*/
90+
public static CallFailedRuntimeException fail(Supplier<?> feignCall) {
91+
try {
92+
Object result = feignCall.get();
93+
throw new AssertionError("Expected call to fail, but it succeeded with result: " + result);
94+
} catch (FeignException e) {
95+
return new CallFailedRuntimeException(e);
96+
}
97+
}
98+
99+
/**
100+
* Execute a Feign call expecting failure with void return (for negative tests). Returns the exception with error
101+
* details.
102+
*
103+
* @param feignCall
104+
* the Feign call to execute
105+
* @return CallFailedRuntimeException containing status code and error message
106+
* @throws AssertionError
107+
* if the call succeeds when failure was expected
108+
*/
109+
public static CallFailedRuntimeException failVoid(Runnable feignCall) {
110+
try {
111+
feignCall.run();
112+
throw new AssertionError("Expected call to fail, but it succeeded");
113+
} catch (FeignException e) {
114+
return new CallFailedRuntimeException(e);
115+
}
116+
}
80117
}

0 commit comments

Comments
 (0)