From 6343358ba3680d2ca4c8a05a7283b7c732700077 Mon Sep 17 00:00:00 2001 From: Ilya Taratukhin Date: Tue, 4 Feb 2025 18:19:24 +0100 Subject: [PATCH] chore: refactor Java SDK tests to use more low level mocks --- .../fingerprint/api/FingerprintApiTest.java | 298 +++++++++++++++--- .../update_event_multiple_fields_request.json | 7 - .../mocks/update_event_one_field_request.json | 3 - 3 files changed, 262 insertions(+), 46 deletions(-) delete mode 100644 sdk/src/test/resources/mocks/update_event_multiple_fields_request.json delete mode 100644 sdk/src/test/resources/mocks/update_event_one_field_request.json diff --git a/sdk/src/test/java/com/fingerprint/api/FingerprintApiTest.java b/sdk/src/test/java/com/fingerprint/api/FingerprintApiTest.java index 9b97b110..4e05782d 100644 --- a/sdk/src/test/java/com/fingerprint/api/FingerprintApiTest.java +++ b/sdk/src/test/java/com/fingerprint/api/FingerprintApiTest.java @@ -5,18 +5,26 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.fingerprint.model.*; +import com.fingerprint.sdk.ApiClient; import com.fingerprint.sdk.ApiException; +import com.fingerprint.sdk.ApiResponse; +import com.fingerprint.sdk.Pair; +import jakarta.ws.rs.core.GenericType; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; import java.io.IOException; import java.io.InputStream; +import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.when; +import static org.mockito.ArgumentMatchers.*; /** * API tests for FingerprintApi @@ -49,19 +57,20 @@ private InputStream getFileAsIOStream(final String fileName) { return ioStream; } + private void validateIntegrationInfo(List queryParams) { + List iiValues = queryParams.stream() + .filter(pair -> "ii".equals(pair.getName())) + .map(Pair::getValue) + .collect(Collectors.toList()); + assertEquals(1, iiValues.size()); + assertEquals(FingerprintApi.INTEGRATION_INFO, iiValues.get(0)); + } + @BeforeAll - public void before() throws ApiException, IOException { - api = Mockito.mock(FingerprintApi.class); - when(api.getEvent(MOCK_REQUEST_ID)).thenReturn(fetchMockWithEventResponse("mocks/get_event_200.json")); - when(api.getEvent(MOCK_REQUEST_WITH_EXTRA_FIELDS_ID)).thenReturn(fetchMockWithEventResponse("mocks/get_event_200_extra_fields.json")); - when(api.getEvent(MOCK_REQUEST_WITH_ALL_FAILED_SIGNALS)).thenReturn(fetchMockWithEventResponse("mocks/get_event_200_all_errors.json")); - when(api.getEvent(MOCK_REQUEST_BOTD_FAILED)).thenReturn(fetchMockWithEventResponse("mocks/get_event_200_botd_failed_error.json")); - when(api.getEvent(MOCK_REQUEST_BOTD_MANY_REQUEST)).thenReturn(fetchMockWithEventResponse("mocks/get_event_200_too_many_requests_error.json")); - when(api.getEvent(MOCK_REQUEST_IDENTIFICATION_FAILED)).thenReturn(fetchMockWithEventResponse("mocks/get_event_200_identification_failed_error.json")); - when(api.getEvent(MOCK_REQUEST_IDENTIFICATION_MANY_REQUEST)).thenReturn(fetchMockWithEventResponse("mocks/get_event_200_too_many_requests_error.json")); - - when(api.getVisits(MOCK_VISITOR_ID, MOCK_VISITOR_REQUEST_ID, null, 50, "1683900801733.Ogvu1j", null)).thenReturn(fetchMockVisit()); - when(api.getRelatedVisitors(MOCK_VISITOR_ID)).thenReturn(fetchMockVisitWithRelatedVisitorsResponse("mocks/related-visitors/get_related_visitors_200.json")); + public void before() { + ApiClient realApiClient = new ApiClient(); + ApiClient apiClient = Mockito.spy(realApiClient); + api = new FingerprintApi(apiClient); } private static ObjectMapper getMapper() { @@ -71,26 +80,89 @@ private static ObjectMapper getMapper() { return mapper; } - private EventsGetResponse fetchMockWithEventResponse(String fileName) throws IOException { - return MAPPER.readValue(getFileAsIOStream(fileName), EventsGetResponse.class); + @FunctionalInterface + public interface ApiAnswerFunction { + ApiResponse apply(InvocationOnMock invocation) throws ApiException, IOException; } - private RelatedVisitorsResponse fetchMockVisitWithRelatedVisitorsResponse(String fileName) throws IOException { - return MAPPER.readValue(getFileAsIOStream(fileName), RelatedVisitorsResponse.class); + private void addMock(String operation, String path, ApiAnswerFunction answerFunction) throws ApiException { + ApiClient apiClient = api.getApiClient(); + String operationName = "FingerprintApi." + operation; + String httpMethod; + switch (operation) { + case "getEvent": + path = "/events/" + path; + httpMethod = "GET"; + break; + case "updateEvent": + path = "/events/" + path; + httpMethod = "PUT"; + break; + case "getVisits": + path = "/visitors/" + path; + httpMethod = "GET"; + break; + case "deleteVisitorData": + path = "/visitors/" + path; + httpMethod = "DELETE"; + break; + case "getRelatedVisitors": + path = "/related-visitors"; + httpMethod = "GET"; + break; + case "searchEvents": + path = "/events/search"; + httpMethod = "GET"; + break; + default: + throw new IllegalArgumentException("Unknown operation: " + operation); + } + Mockito.doAnswer(invocation -> { + validateIntegrationInfo(invocation.getArgument(3)); + + return answerFunction.apply(invocation); + }).when(apiClient).invokeAPI( + eq(operationName), // operation, for example "FingerprintApi.getEvent" + eq(path), // path + eq(httpMethod), // HTTP-method + any(), // queryParams + argThat(body -> { + if (httpMethod.equals("PUT")) { + return body != null; + } else { + return body == null; + } + }), + any(), // headerParams + any(), // cookieParams + any(), // formParams + any(), // accept + any(), // contentType + any(), // authNames + any(), // returnType + eq(false) // isBodyNullable + ); } - private ErrorResponse fetchMockErrorResponse(String fileName) throws IOException { - return MAPPER.readValue(getFileAsIOStream(fileName), ErrorResponse.class); + ApiResponse mockFileToResponse(int statusCode, InvocationOnMock invocation, String path) throws IOException { + GenericType returnType = invocation.getArgument(11); + return new ApiResponse<>(statusCode, null, path != null ? MAPPER.readValue(getFileAsIOStream(path), returnType.getRawType()) : null); } private T fetchMock(String filename, Class type) throws IOException { return MAPPER.readValue(getFileAsIOStream(filename), type); } - private VisitorsGetResponse fetchMockVisit() throws IOException { - ObjectMapper mapper = new ObjectMapper().registerModule(new JavaTimeModule()); - - return mapper.readValue(getFileAsIOStream("mocks/get_visitors_200_limit_500.json"), VisitorsGetResponse.class); + public static boolean listContainsPair(List pairs, String key, String value) { + if (pairs == null) { + return false; + } + for (Pair pair : pairs) { + if (key.equals(pair.getName()) && value.equals(pair.getValue())) { + return true; + } + } + return false; } /** @@ -101,6 +173,10 @@ private VisitorsGetResponse fetchMockVisit() throws IOException { */ @Test public void getEventTest() throws ApiException { + addMock("getEvent", MOCK_REQUEST_ID, invocation -> { + return mockFileToResponse(200, invocation, "mocks/get_event_200.json"); + }); + EventsGetResponse response = api.getEvent(MOCK_REQUEST_ID); assertNotNull(response.getProducts()); assertNotNull(response.getProducts().getIdentification()); @@ -131,20 +207,125 @@ public void getEventTest() throws ApiException { } @Test - public void updateOneFieldEventRequest() throws IOException { - EventsUpdateRequest request = fetchMock("mocks/update_event_one_field_request.json", EventsUpdateRequest.class); - assertDoesNotThrow(() -> api.updateEvent(MOCK_REQUEST_ID, request)); + public void updateEventLinkedIdRequest() throws ApiException { + final String LINKED_ID = "myLinkedId"; + EventsUpdateRequest request = new EventsUpdateRequest(); + request.setLinkedId(LINKED_ID); + + addMock("updateEvent", MOCK_REQUEST_ID, invocation -> { + List queryParams = invocation.getArgument(3); + assertEquals(1, queryParams.size()); + + EventsUpdateRequest body = invocation.getArgument(4); + assertEquals(LINKED_ID, body.getLinkedId()); + assertNull(body.getTag()); + assertNull(body.getSuspect()); + return mockFileToResponse(200, invocation, null); + }); + api.updateEvent(MOCK_REQUEST_ID, request); + } + + @Test + public void updateEventTagRequest() throws ApiException { + final Map TAG = new HashMap<>(); + TAG.put("stringKey", "value"); + TAG.put("booleanPositiveKey", true); + TAG.put("booleanNegativeKey", false); + TAG.put("numberKey", 123); + TAG.put("arrayStringKey", new String[]{"value1", "value2"}); + TAG.put("arrayIntKey", new int[]{1, 2, 7}); + TAG.put("arrayEmptyKey", new int[]{}); + TAG.put("mapKey", new HashMap() {{ + put("key1", "value1"); + put("key2", 2); + }}); + EventsUpdateRequest request = new EventsUpdateRequest(); + request.setTag(TAG); + + addMock("updateEvent", MOCK_REQUEST_ID, invocation -> { + List queryParams = invocation.getArgument(3); + assertEquals(1, queryParams.size()); + + EventsUpdateRequest body = invocation.getArgument(4); + assertNull(body.getLinkedId()); + assertEquals(TAG, body.getTag()); + assertNull(body.getSuspect()); + return mockFileToResponse(200, invocation, null); + }); + api.updateEvent(MOCK_REQUEST_ID, request); + } + + @Test + public void updateEventSuspectPositiveRequest() throws ApiException { + EventsUpdateRequest request = new EventsUpdateRequest(); + request.setSuspect(true); + + addMock("updateEvent", MOCK_REQUEST_ID, invocation -> { + List queryParams = invocation.getArgument(3); + assertEquals(1, queryParams.size()); + + EventsUpdateRequest body = invocation.getArgument(4); + assertNull(body.getLinkedId()); + assertNull(body.getTag()); + assertTrue(body.getSuspect()); + return mockFileToResponse(200, invocation, null); + }); + api.updateEvent(MOCK_REQUEST_ID, request); + } + + @Test + public void updateEventSuspectNegativeRequest() throws ApiException { + EventsUpdateRequest request = new EventsUpdateRequest(); + request.setSuspect(false); + + addMock("updateEvent", MOCK_REQUEST_ID, invocation -> { + List queryParams = invocation.getArgument(3); + assertEquals(1, queryParams.size()); + + EventsUpdateRequest body = invocation.getArgument(4); + assertNull(body.getLinkedId()); + assertNull(body.getTag()); + assertFalse(body.getSuspect()); + return mockFileToResponse(200, invocation, null); + }); + api.updateEvent(MOCK_REQUEST_ID, request); } @Test - public void updateMultipleFieldsEventRequest() throws IOException { - EventsUpdateRequest request = fetchMock("mocks/update_event_multiple_fields_request.json", EventsUpdateRequest.class); - assertDoesNotThrow(() -> api.updateEvent(MOCK_REQUEST_ID, request)); + public void updateMultipleFieldsEventRequest() throws ApiException { + final String LINKED_ID = "myLinkedId"; + final Map TAG = new HashMap<>(); + TAG.put("stringKey", "value"); + TAG.put("booleanKey", true); + TAG.put("numberKey", 123); + TAG.put("arrayStringKey", new String[]{"value1", "value2"}); + EventsUpdateRequest request = new EventsUpdateRequest(); + request.setLinkedId(LINKED_ID); + request.setTag(TAG); + request.setSuspect(true); + + addMock("updateEvent", MOCK_REQUEST_ID, invocation -> { + List queryParams = invocation.getArgument(3); + assertEquals(1, queryParams.size()); + + EventsUpdateRequest body = invocation.getArgument(4); + assertEquals(LINKED_ID, body.getLinkedId()); + assertEquals(TAG, body.getTag()); + assertTrue(body.getSuspect()); + return mockFileToResponse(200, invocation, null); + }); + api.updateEvent(MOCK_REQUEST_ID, request); } @Test - public void deleteVisitorDataTest() { - assertDoesNotThrow(() -> api.deleteVisitorData(MOCK_VISITOR_ID)); + public void deleteVisitorDataTest() throws ApiException { + addMock("deleteVisitorData", MOCK_VISITOR_ID, invocation -> { + List queryParams = invocation.getArgument(3); + assertEquals(1, queryParams.size()); + + return mockFileToResponse(200, invocation, null); + }); + api.deleteVisitorData(MOCK_VISITOR_ID); } /** @@ -156,6 +337,9 @@ public void deleteVisitorDataTest() { */ @Test public void getEventWithExtraFieldsTest() throws ApiException { + addMock("getEvent", MOCK_REQUEST_WITH_EXTRA_FIELDS_ID, + invocation -> mockFileToResponse(200, invocation, "mocks/get_event_200_extra_fields.json") + ); EventsGetResponse response = api.getEvent(MOCK_REQUEST_WITH_EXTRA_FIELDS_ID); Products products = response.getProducts(); assertNotNull(products); @@ -166,6 +350,10 @@ public void getEventWithExtraFieldsTest() throws ApiException { @Test public void getEventWithAllFailedSignalsTest() throws ApiException { + addMock("getEvent", MOCK_REQUEST_WITH_ALL_FAILED_SIGNALS, + invocation -> mockFileToResponse(200, invocation, "mocks/get_event_200_all_errors.json") + ); + EventsGetResponse response = api.getEvent(MOCK_REQUEST_WITH_ALL_FAILED_SIGNALS); Products products = response.getProducts(); @@ -197,6 +385,10 @@ public void getEventWithAllFailedSignalsTest() throws ApiException { @Test public void getEventBotdFailedErrorTest() throws ApiException { + addMock("getEvent", MOCK_REQUEST_BOTD_FAILED, + invocation -> mockFileToResponse(200, invocation, "mocks/get_event_200_botd_failed_error.json") + ); + EventsGetResponse response = api.getEvent(MOCK_REQUEST_BOTD_FAILED); Products products = response.getProducts(); assertNotNull(products); @@ -211,6 +403,10 @@ public void getEventBotdFailedErrorTest() throws ApiException { @Test public void getEventBotdManyRequestsErrorTest() throws ApiException { + addMock("getEvent", MOCK_REQUEST_BOTD_MANY_REQUEST, + invocation -> mockFileToResponse(200, invocation, "mocks/get_event_200_too_many_requests_error.json") + ); + EventsGetResponse response = api.getEvent(MOCK_REQUEST_BOTD_MANY_REQUEST); Products products = response.getProducts(); assertNotNull(products); @@ -221,6 +417,10 @@ public void getEventBotdManyRequestsErrorTest() throws ApiException { @Test public void getEventIdentificationFailedErrorTest() throws ApiException { + addMock("getEvent", MOCK_REQUEST_IDENTIFICATION_FAILED, + invocation -> mockFileToResponse(200, invocation, "mocks/get_event_200_identification_failed_error.json") + ); + EventsGetResponse response = api.getEvent(MOCK_REQUEST_IDENTIFICATION_FAILED); Products products = response.getProducts(); assertNotNull(products); @@ -233,6 +433,10 @@ public void getEventIdentificationFailedErrorTest() throws ApiException { @Test public void getEventIdentificationManyRequestsErrorTest() throws ApiException { + addMock("getEvent", MOCK_REQUEST_IDENTIFICATION_MANY_REQUEST, + invocation -> mockFileToResponse(200, invocation, "mocks/get_event_200_too_many_requests_error.json") + ); + EventsGetResponse response = api.getEvent(MOCK_REQUEST_IDENTIFICATION_MANY_REQUEST); Products products = response.getProducts(); assertNotNull(products); @@ -249,7 +453,19 @@ public void getEventIdentificationManyRequestsErrorTest() throws ApiException { */ @Test public void getVisitsTest() throws ApiException { - VisitorsGetResponse response = api.getVisits(MOCK_VISITOR_ID, MOCK_VISITOR_REQUEST_ID, null, 50, "1683900801733.Ogvu1j", null); + final String PAGINATION_KEY = "1683900801733.Ogvu1j"; + final int LIMIT = 50; + addMock("getVisits", MOCK_VISITOR_ID, invocation -> { + List queryParams = invocation.getArgument(3); + + assertEquals(4, queryParams.size()); + assertTrue(listContainsPair(queryParams, "request_id", MOCK_VISITOR_REQUEST_ID)); + assertTrue(listContainsPair(queryParams, "limit", String.valueOf(LIMIT))); + assertTrue(listContainsPair(queryParams, "paginationKey", PAGINATION_KEY)); + + return mockFileToResponse(200, invocation, "mocks/get_visitors_200_limit_500.json"); + }); + VisitorsGetResponse response = api.getVisits(MOCK_VISITOR_ID, MOCK_VISITOR_REQUEST_ID, null, LIMIT, PAGINATION_KEY, null); assertEquals(response.getVisitorId(), MOCK_VISITOR_ID); } @@ -264,18 +480,28 @@ public void webhookTest() throws Exception { ObjectMapper mapper = new ObjectMapper().registerModule(new JavaTimeModule()); mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); - Webhook visit = mapper.readValue(getFileAsIOStream("mocks/webhook.json"), Webhook.class); + Webhook visit = mapper.readValue(getFileAsIOStream("mocks/webhook.json"), Webhook.class); assertEquals(MOCK_WEBHOOK_VISITOR_ID, visit.getVisitorId()); assertEquals(MOCK_WEBHOOK_REQUEST_ID, visit.getRequestId()); } @Test - public void relatedVisitorsTest() throws Exception { + public void relatedVisitorsTest() throws ApiException { + addMock("getRelatedVisitors", null, invocation -> { + List queryParams = invocation.getArgument(3); + assertEquals(2, queryParams.size()); + assertTrue(listContainsPair(queryParams, "visitor_id", MOCK_VISITOR_ID)); + + return mockFileToResponse(200, invocation, "mocks/related-visitors/get_related_visitors_200.json"); + }); + RelatedVisitorsResponse response = api.getRelatedVisitors(MOCK_VISITOR_ID); - RelatedVisitorsResponse mock = fetchMockVisitWithRelatedVisitorsResponse("mocks/related-visitors/get_related_visitors_200.json"); + List relatedVisitorsList = response.getRelatedVisitors(); - assertEquals(response, mock); + assertEquals(relatedVisitorsList.size(), 2); + assertEquals(relatedVisitorsList.get(0).getVisitorId(), "NtCUJGceWX9RpvSbhvOm"); + assertEquals(relatedVisitorsList.get(1).getVisitorId(), "25ee02iZwGxeyT0jMNkZ"); } } diff --git a/sdk/src/test/resources/mocks/update_event_multiple_fields_request.json b/sdk/src/test/resources/mocks/update_event_multiple_fields_request.json deleted file mode 100644 index f85d2e75..00000000 --- a/sdk/src/test/resources/mocks/update_event_multiple_fields_request.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "linkedId": "myNewLinkedId", - "tag": { - "myTag": "myNewValue" - }, - "suspect": true -} diff --git a/sdk/src/test/resources/mocks/update_event_one_field_request.json b/sdk/src/test/resources/mocks/update_event_one_field_request.json deleted file mode 100644 index 0ebd1549..00000000 --- a/sdk/src/test/resources/mocks/update_event_one_field_request.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "linkedId": "myNewLinkedId" -}