From a276c75ded03addd8cffd602587c37447ace2e3a Mon Sep 17 00:00:00 2001 From: TheUnderScorer Date: Mon, 3 Feb 2025 09:38:14 +0000 Subject: [PATCH 1/4] feat: update OpenAPI schema --- .changeset/forty-seas-prove.md | 5 + .changeset/good-experts-watch.md | 5 + .changeset/orange-poets-drive.md | 5 + .changeset/pre.json | 8 + README.md | 3 + docs/FingerprintApi.md | 97 ++++++++++++ docs/SearchEventsResponse.md | 15 ++ docs/SearchEventsResponseEventsInner.md | 14 ++ res/fingerprint-server-api.yaml | 144 ++++++++++++++++++ .../com/fingerprint/api/FingerprintApi.java | 99 ++++++++++++ .../model/SearchEventsResponse.java | 140 +++++++++++++++++ .../SearchEventsResponseEventsInner.java | 99 ++++++++++++ 12 files changed, 634 insertions(+) create mode 100644 .changeset/forty-seas-prove.md create mode 100644 .changeset/good-experts-watch.md create mode 100644 .changeset/orange-poets-drive.md create mode 100644 .changeset/pre.json create mode 100644 docs/SearchEventsResponse.md create mode 100644 docs/SearchEventsResponseEventsInner.md create mode 100644 sdk/src/main/java/com/fingerprint/model/SearchEventsResponse.java create mode 100644 sdk/src/main/java/com/fingerprint/model/SearchEventsResponseEventsInner.java diff --git a/.changeset/forty-seas-prove.md b/.changeset/forty-seas-prove.md new file mode 100644 index 0000000..4f10093 --- /dev/null +++ b/.changeset/forty-seas-prove.md @@ -0,0 +1,5 @@ +--- +'fingerprint-pro-server-api-java-sdk': minor +--- + +Add `relay` detection method to the VPN Detection Smart Signal diff --git a/.changeset/good-experts-watch.md b/.changeset/good-experts-watch.md new file mode 100644 index 0000000..2e24306 --- /dev/null +++ b/.changeset/good-experts-watch.md @@ -0,0 +1,5 @@ +--- +'fingerprint-pro-server-api-java-sdk': minor +--- + +**events-search**: Add a new `events/search` API endpoint. Allow users to search for identification events matching one or more search criteria, for example, visitor ID, IP address, bot detection result, etc. diff --git a/.changeset/orange-poets-drive.md b/.changeset/orange-poets-drive.md new file mode 100644 index 0000000..c297c6f --- /dev/null +++ b/.changeset/orange-poets-drive.md @@ -0,0 +1,5 @@ +--- +'fingerprint-pro-server-api-java-sdk': minor +--- + +**events**: Add a `suspect` field to the `identification` product schema \ No newline at end of file diff --git a/.changeset/pre.json b/.changeset/pre.json new file mode 100644 index 0000000..66e6693 --- /dev/null +++ b/.changeset/pre.json @@ -0,0 +1,8 @@ +{ + "mode": "pre", + "tag": "develop", + "initialVersions": { + "fingerprint-pro-server-api-java-sdk": "7.2.0" + }, + "changesets": [] +} diff --git a/README.md b/README.md index 4d92a45..10d66f8 100644 --- a/README.md +++ b/README.md @@ -277,6 +277,7 @@ Class | Method | HTTP request | Description *FingerprintApi* | [**getEvent**](docs/FingerprintApi.md#getEvent) | **GET** /events/{request_id} | Get event by request ID *FingerprintApi* | [**getRelatedVisitors**](docs/FingerprintApi.md#getRelatedVisitors) | **GET** /related-visitors | Get Related Visitors *FingerprintApi* | [**getVisits**](docs/FingerprintApi.md#getVisits) | **GET** /visitors/{visitor_id} | Get visits by visitor ID +*FingerprintApi* | [**searchEvents**](docs/FingerprintApi.md#searchEvents) | **GET** /events/search | Get events via search *FingerprintApi* | [**updateEvent**](docs/FingerprintApi.md#updateEvent) | **PUT** /events/{request_id} | Update an event with a given request ID *FingerprintApi* | [**webhookTrace**](docs/FingerprintApi.md#webhookTrace) | **TRACE** /webhook | @@ -351,6 +352,8 @@ Class | Method | HTTP request | Description - [RelatedVisitorsResponse](docs/RelatedVisitorsResponse.md) - [RemoteControl](docs/RemoteControl.md) - [RootApps](docs/RootApps.md) + - [SearchEventsResponse](docs/SearchEventsResponse.md) + - [SearchEventsResponseEventsInner](docs/SearchEventsResponseEventsInner.md) - [SuspectScore](docs/SuspectScore.md) - [Tampering](docs/Tampering.md) - [Tor](docs/Tor.md) diff --git a/docs/FingerprintApi.md b/docs/FingerprintApi.md index 2445459..6096d4d 100644 --- a/docs/FingerprintApi.md +++ b/docs/FingerprintApi.md @@ -8,6 +8,7 @@ All URIs are relative to *https://api.fpjs.io* | [**getEvent**](FingerprintApi.md#getEvent) | **GET** /events/{request_id} | Get event by request ID | | [**getRelatedVisitors**](FingerprintApi.md#getRelatedVisitors) | **GET** /related-visitors | Get Related Visitors | | [**getVisits**](FingerprintApi.md#getVisits) | **GET** /visitors/{visitor_id} | Get visits by visitor ID | +| [**searchEvents**](FingerprintApi.md#searchEvents) | **GET** /events/search | Get events via search | | [**updateEvent**](FingerprintApi.md#updateEvent) | **PUT** /events/{request_id} | Update an event with a given request ID | | [**webhookTrace**](FingerprintApi.md#webhookTrace) | **TRACE** /webhook | | @@ -376,6 +377,102 @@ public class FingerprintApiExample { | **429** | Too Many Requests. The request is throttled. | * Retry-After - Indicates how many seconds you should wait before attempting the next request.
| +## searchEvents + +> SearchEventsResponse searchEvents(limit, visitorId, bot, ipAddress, linkedId, start, end, reverse, suspect) + +Get events via search + +Search for identification events, including Smart Signals, using multiple filtering criteria. If you don't provide `start` or `end` parameters, the default search range is the last 7 days. + +Please note that events include mobile signals (e.g. `rootApps`) even if the request originated from a non-mobile platform. We recommend you **ignore** mobile signals for such requests. + + +### Example + +```java +package main; + +import com.fingerprint.api.FingerprintApi; +import com.fingerprint.model.EventsGetResponse; +import com.fingerprint.model.EventsUpdateRequest; +import com.fingerprint.model.VisitorsGetResponse; +import com.fingerprint.sdk.ApiClient; +import com.fingerprint.sdk.ApiException; +import com.fingerprint.sdk.Configuration; +import com.fingerprint.sdk.Region; + +public class FingerprintApiExample { + // Fingerprint Pro Secret API Key + private static final String FPJS_API_SECRET = "Fingerprint Pro Secret API Key"; + public static void main(String... args) { + // Create a new api client instance from Configuration with your Fingerprint Pro Server API Key and your Fingerprint Pro Server API Region. + /* + You can specify a region on getDefaultApiClient function's second parameter + If you leave the second parameter empty, then Region.GLOBAL will be used as a default region + Options for regions are: + Region.GLOBAL + Region.EUROPE + Region.ASIA + */ + ApiClient client = Configuration.getDefaultApiClient(FPJS_API_SECRET, Region.EUROPE); + FingerprintApi api = new FingerprintApi(client); + Integer limit = 10; // Integer | Limit the number of events returned. + String visitorId = "visitorId_example"; // String | Unique [visitor identifier](https://dev.fingerprint.com/reference/get-function#visitorid) issued by Fingerprint Pro. Filter for events matching this `visitor_id`. + String bot = "all"; // String | Filter events by the bot detection result, specifically: - events where any kind of bot was detected. - events where a good bot was detected. - events where a bad bot was detected. - events where no bot was detected. + String ipAddress = "ipAddress_example"; // String | Filter events by IP address range. The range can be as specific as a single IP (/32 for IPv4 or /128 for IPv6) All ip_address filters must use CIDR notation, for example, 10.0.0.0/24, 192.168.0.1/32 + String linkedId = "linkedId_example"; // String | Filter events by your custom identifier. You can use [linked IDs](https://dev.fingerprint.com/reference/get-function#linkedid) to associate identification requests with your own identifier, for example, session ID, purchase ID, or transaction ID. You can then use this `linked_id` parameter to retrieve all events associated with your custom identifier. + Long start = 56L; // Long | Filter events with a timestamp greater than the start time, in Unix time (milliseconds). + Long end = 56L; // Long | Filter events with a timestamp smaller than the end time, in Unix time (milliseconds). + Boolean reverse = true; // Boolean | Sort events in reverse timestamp order. + Boolean suspect = true; // Boolean | Filter events previously tagged as suspicious via the [Update API](https://dev.fingerprint.com/reference/updateevent). + try { + SearchEventsResponse result = apiInstance.searchEvents(limit, visitorId, bot, ipAddress, linkedId, start, end, reverse, suspect); + System.out.println(result); + } catch (ApiException e) { + System.err.println("Exception when calling FingerprintApi.searchEvents:" + e.getMessage()); + } + } +} +``` + + +### Parameters + + +| Name | Type | Description | Notes | +|------------- | ------------- | ------------- | -------------| +| **limit** | **Integer**| Limit the number of events returned. | | +| **visitorId** | **String**| Unique [visitor identifier](https://dev.fingerprint.com/reference/get-function#visitorid) issued by Fingerprint Pro. Filter for events matching this `visitor_id`. | [optional] | +| **bot** | **String**| Filter events by the bot detection result, specifically: - events where any kind of bot was detected. - events where a good bot was detected. - events where a bad bot was detected. - events where no bot was detected. | [optional] [enum: all, good, bad, none] | +| **ipAddress** | **String**| Filter events by IP address range. The range can be as specific as a single IP (/32 for IPv4 or /128 for IPv6) All ip_address filters must use CIDR notation, for example, 10.0.0.0/24, 192.168.0.1/32 | [optional] | +| **linkedId** | **String**| Filter events by your custom identifier. You can use [linked IDs](https://dev.fingerprint.com/reference/get-function#linkedid) to associate identification requests with your own identifier, for example, session ID, purchase ID, or transaction ID. You can then use this `linked_id` parameter to retrieve all events associated with your custom identifier. | [optional] | +| **start** | **Long**| Filter events with a timestamp greater than the start time, in Unix time (milliseconds). | [optional] | +| **end** | **Long**| Filter events with a timestamp smaller than the end time, in Unix time (milliseconds). | [optional] | +| **reverse** | **Boolean**| Sort events in reverse timestamp order. | [optional] | +| **suspect** | **Boolean**| Filter events previously tagged as suspicious via the [Update API](https://dev.fingerprint.com/reference/updateevent). | [optional] | + +### Return type + +[**SearchEventsResponse**](SearchEventsResponse.md) + +### Authorization + +[ApiKeyHeader](../README.md#ApiKeyHeader), [ApiKeyQuery](../README.md#ApiKeyQuery) + +### HTTP request headers + +- **Content-Type**: Not defined +- **Accept**: application/json + +### HTTP response details +| Status code | Description | Response headers | +|-------------|-------------|------------------| +| **200** | Events matching the filter(s). | - | +| **400** | Bad request. One or more supplied search parameters are invalid, or a required parameter is missing. | - | +| **403** | Forbidden. Access to this API is denied. | - | + + ## updateEvent > updateEvent(requestId, eventsUpdateRequest) diff --git a/docs/SearchEventsResponse.md b/docs/SearchEventsResponse.md new file mode 100644 index 0000000..3ec2a5f --- /dev/null +++ b/docs/SearchEventsResponse.md @@ -0,0 +1,15 @@ + + +# SearchEventsResponse + +Contains a list of all identification events matching the specified search criteria. + +## Properties + +| Name | Type | Description | Notes | +|------------ | ------------- | ------------- | -------------| +|**events** | [**List<SearchEventsResponseEventsInner>**](SearchEventsResponseEventsInner.md) | | [optional] | +|**paginationKey** | **String** | | [optional] | + + + diff --git a/docs/SearchEventsResponseEventsInner.md b/docs/SearchEventsResponseEventsInner.md new file mode 100644 index 0000000..4b469d6 --- /dev/null +++ b/docs/SearchEventsResponseEventsInner.md @@ -0,0 +1,14 @@ + + +# SearchEventsResponseEventsInner + +Device intelligence results for the identification event. + +## Properties + +| Name | Type | Description | Notes | +|------------ | ------------- | ------------- | -------------| +|**products** | [**Products**](Products.md) | | | + + + diff --git a/res/fingerprint-server-api.yaml b/res/fingerprint-server-api.yaml index af3cc9b..fd40920 100644 --- a/res/fingerprint-server-api.yaml +++ b/res/fingerprint-server-api.yaml @@ -142,6 +142,131 @@ paths: application/json: schema: $ref: '#/components/schemas/ErrorResponse' + /events/search: + get: + tags: + - Fingerprint + operationId: searchEvents + summary: Get events via search + description: > + Search for identification events, including Smart Signals, using + multiple filtering criteria. If you don't provide `start` or `end` + parameters, the default search range is the last 7 days. + + + Please note that events include mobile signals (e.g. `rootApps`) even if + the request originated from a non-mobile platform. We recommend you + **ignore** mobile signals for such requests. + parameters: + - name: limit + in: query + required: true + schema: + type: integer + format: int32 + minimum: 1 + example: 10 + description: | + Limit the number of events returned. + - name: visitor_id + in: query + schema: + type: string + description: > + Unique [visitor + identifier](https://dev.fingerprint.com/reference/get-function#visitorid) + issued by Fingerprint Pro. + + Filter for events matching this `visitor_id`. + - name: bot + in: query + schema: + type: string + enum: + - all + - good + - bad + - none + description: | + Filter events by the bot detection result, specifically: + - events where any kind of bot was detected. + - events where a good bot was detected. + - events where a bad bot was detected. + - events where no bot was detected. + - name: ip_address + in: query + schema: + type: string + description: > + Filter events by IP address range. The range can be as specific as a + single IP (/32 for IPv4 or /128 for IPv6) + + All ip_address filters must use CIDR notation, for example, + 10.0.0.0/24, 192.168.0.1/32 + - name: linked_id + in: query + schema: + type: string + description: > + Filter events by your custom identifier. + + + You can use [linked + IDs](https://dev.fingerprint.com/reference/get-function#linkedid) to + associate identification requests with your own identifier, for + example, session ID, purchase ID, or transaction ID. You can then + use this `linked_id` parameter to retrieve all events associated + with your custom identifier. + - name: start + in: query + schema: + type: integer + format: int64 + description: > + Filter events with a timestamp greater than the start time, in Unix + time (milliseconds). + - name: end + in: query + schema: + type: integer + format: int64 + description: > + Filter events with a timestamp smaller than the end time, in Unix + time (milliseconds). + - name: reverse + in: query + schema: + type: boolean + description: | + Sort events in reverse timestamp order. + - name: suspect + in: query + schema: + type: boolean + description: > + Filter events previously tagged as suspicious via the [Update + API](https://dev.fingerprint.com/reference/updateevent). + responses: + '200': + description: Events matching the filter(s). + content: + application/json: + schema: + $ref: '#/components/schemas/SearchEventsResponse' + '400': + description: >- + Bad request. One or more supplied search parameters are invalid, or + a required parameter is missing. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '403': + description: Forbidden. Access to this API is denied. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' /visitors/{visitor_id}: get: tags: @@ -1874,6 +1999,25 @@ components: suspect: type: boolean description: Suspect flag indicating observed suspicious or fraudulent event + SearchEventsResponse: + type: object + description: >- + Contains a list of all identification events matching the specified + search criteria. + additionalProperties: false + properties: + events: + type: array + items: + type: object + description: Device intelligence results for the identification event. + required: + - products + properties: + products: + $ref: '#/components/schemas/Products' + paginationKey: + type: string Visit: type: object additionalProperties: false diff --git a/sdk/src/main/java/com/fingerprint/api/FingerprintApi.java b/sdk/src/main/java/com/fingerprint/api/FingerprintApi.java index b07791f..fa6fe71 100644 --- a/sdk/src/main/java/com/fingerprint/api/FingerprintApi.java +++ b/sdk/src/main/java/com/fingerprint/api/FingerprintApi.java @@ -14,6 +14,7 @@ import com.fingerprint.model.EventsGetResponse; import com.fingerprint.model.EventsUpdateRequest; import com.fingerprint.model.RelatedVisitorsResponse; +import com.fingerprint.model.SearchEventsResponse; import com.fingerprint.model.VisitorsGetResponse; import com.fingerprint.model.Webhook; @@ -379,6 +380,104 @@ public ApiResponse getVisitsWithHttpInfo(String visitorId, localVarHeaderParams, localVarCookieParams, localVarFormParams, localVarAccept, localVarContentType, localVarAuthNames, localVarReturnType, false); } + /** + * Get events via search + * Search for identification events, including Smart Signals, using multiple filtering criteria. If you don't provide `start` or `end` parameters, the default search range is the last 7 days. Please note that events include mobile signals (e.g. `rootApps`) even if the request originated from a non-mobile platform. We recommend you **ignore** mobile signals for such requests. + * @param limit Limit the number of events returned. (required) + * @param visitorId Unique [visitor identifier](https://dev.fingerprint.com/reference/get-function#visitorid) issued by Fingerprint Pro. Filter for events matching this `visitor_id`. (optional) + * @param bot Filter events by the bot detection result, specifically: - events where any kind of bot was detected. - events where a good bot was detected. - events where a bad bot was detected. - events where no bot was detected. (optional) + * @param ipAddress Filter events by IP address range. The range can be as specific as a single IP (/32 for IPv4 or /128 for IPv6) All ip_address filters must use CIDR notation, for example, 10.0.0.0/24, 192.168.0.1/32 (optional) + * @param linkedId Filter events by your custom identifier. You can use [linked IDs](https://dev.fingerprint.com/reference/get-function#linkedid) to associate identification requests with your own identifier, for example, session ID, purchase ID, or transaction ID. You can then use this `linked_id` parameter to retrieve all events associated with your custom identifier. (optional) + * @param start Filter events with a timestamp greater than the start time, in Unix time (milliseconds). (optional) + * @param end Filter events with a timestamp smaller than the end time, in Unix time (milliseconds). (optional) + * @param reverse Sort events in reverse timestamp order. (optional) + * @param suspect Filter events previously tagged as suspicious via the [Update API](https://dev.fingerprint.com/reference/updateevent). (optional) + * @return SearchEventsResponse + * @throws ApiException if fails to make API call + * @http.response.details + + + + + +
Status Code Description Response Headers
200 Events matching the filter(s). -
400 Bad request. One or more supplied search parameters are invalid, or a required parameter is missing. -
403 Forbidden. Access to this API is denied. -
+ */ + public SearchEventsResponse searchEvents(Integer limit, String visitorId, String bot, String ipAddress, String linkedId, Long start, Long end, Boolean reverse, Boolean suspect) throws ApiException { + return searchEventsWithHttpInfo(limit, visitorId, bot, ipAddress, linkedId, start, end, reverse, suspect).getData(); + } + + /** + * Get events via search + * Search for identification events, including Smart Signals, using multiple filtering criteria. If you don't provide `start` or `end` parameters, the default search range is the last 7 days. Please note that events include mobile signals (e.g. `rootApps`) even if the request originated from a non-mobile platform. We recommend you **ignore** mobile signals for such requests. + * @param limit Limit the number of events returned. (required) + * @param visitorId Unique [visitor identifier](https://dev.fingerprint.com/reference/get-function#visitorid) issued by Fingerprint Pro. Filter for events matching this `visitor_id`. (optional) + * @param bot Filter events by the bot detection result, specifically: - events where any kind of bot was detected. - events where a good bot was detected. - events where a bad bot was detected. - events where no bot was detected. (optional) + * @param ipAddress Filter events by IP address range. The range can be as specific as a single IP (/32 for IPv4 or /128 for IPv6) All ip_address filters must use CIDR notation, for example, 10.0.0.0/24, 192.168.0.1/32 (optional) + * @param linkedId Filter events by your custom identifier. You can use [linked IDs](https://dev.fingerprint.com/reference/get-function#linkedid) to associate identification requests with your own identifier, for example, session ID, purchase ID, or transaction ID. You can then use this `linked_id` parameter to retrieve all events associated with your custom identifier. (optional) + * @param start Filter events with a timestamp greater than the start time, in Unix time (milliseconds). (optional) + * @param end Filter events with a timestamp smaller than the end time, in Unix time (milliseconds). (optional) + * @param reverse Sort events in reverse timestamp order. (optional) + * @param suspect Filter events previously tagged as suspicious via the [Update API](https://dev.fingerprint.com/reference/updateevent). (optional) + * @return ApiResponse<SearchEventsResponse> + * @throws ApiException if fails to make API call + * @http.response.details + + + + + +
Status Code Description Response Headers
200 Events matching the filter(s). -
400 Bad request. One or more supplied search parameters are invalid, or a required parameter is missing. -
403 Forbidden. Access to this API is denied. -
+ */ + public ApiResponse searchEventsWithHttpInfo(Integer limit, String visitorId, String bot, String ipAddress, String linkedId, Long start, Long end, Boolean reverse, Boolean suspect) throws ApiException { + Object localVarPostBody = null; + + // verify the required parameter 'limit' is set + if (limit == null) { + throw new ApiException(400, "Missing the required parameter 'limit' when calling searchEvents"); + } + + // create path and map variables + String localVarPath = "/events/search"; + + // query params + List localVarQueryParams = new ArrayList(); + Map localVarHeaderParams = new HashMap(); + Map localVarCookieParams = new HashMap(); + Map localVarFormParams = new HashMap(); + + localVarQueryParams.add(new Pair("ii", INTEGRATION_INFO)); + + localVarQueryParams.addAll(apiClient.parameterToPairs("", "limit", limit)); + localVarQueryParams.addAll(apiClient.parameterToPairs("", "visitor_id", visitorId)); + localVarQueryParams.addAll(apiClient.parameterToPairs("", "bot", bot)); + localVarQueryParams.addAll(apiClient.parameterToPairs("", "ip_address", ipAddress)); + localVarQueryParams.addAll(apiClient.parameterToPairs("", "linked_id", linkedId)); + localVarQueryParams.addAll(apiClient.parameterToPairs("", "start", start)); + localVarQueryParams.addAll(apiClient.parameterToPairs("", "end", end)); + localVarQueryParams.addAll(apiClient.parameterToPairs("", "reverse", reverse)); + localVarQueryParams.addAll(apiClient.parameterToPairs("", "suspect", suspect)); + + + + + final String[] localVarAccepts = { + "application/json" + }; + final String localVarAccept = apiClient.selectHeaderAccept(localVarAccepts); + + final String[] localVarContentTypes = { + + }; + final String localVarContentType = apiClient.selectHeaderContentType(localVarContentTypes); + + String[] localVarAuthNames = new String[] { "ApiKeyHeader", "ApiKeyQuery" }; + + GenericType localVarReturnType = new GenericType() {}; + + return apiClient.invokeAPI("FingerprintApi.searchEvents", localVarPath, "GET", localVarQueryParams, localVarPostBody, + localVarHeaderParams, localVarCookieParams, localVarFormParams, localVarAccept, localVarContentType, + localVarAuthNames, localVarReturnType, false); + } /** * Update an event with a given request ID * Change information in existing events specified by `requestId` or *flag suspicious events*. When an event is created, it is assigned `linkedId` and `tag` submitted through the JS agent parameters. This information might not be available on the client so the Server API allows for updating the attributes after the fact. **Warning** It's not possible to update events older than 10 days. diff --git a/sdk/src/main/java/com/fingerprint/model/SearchEventsResponse.java b/sdk/src/main/java/com/fingerprint/model/SearchEventsResponse.java new file mode 100644 index 0000000..87f3b12 --- /dev/null +++ b/sdk/src/main/java/com/fingerprint/model/SearchEventsResponse.java @@ -0,0 +1,140 @@ +package com.fingerprint.model; + +import java.util.Objects; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.annotation.JsonValue; +import com.fingerprint.model.SearchEventsResponseEventsInner; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fingerprint.sdk.JSON; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * Contains a list of all identification events matching the specified search criteria. + */ + +@Schema(description = "Contains a list of all identification events matching the specified search criteria.") +@JsonPropertyOrder({ + SearchEventsResponse.JSON_PROPERTY_EVENTS, + SearchEventsResponse.JSON_PROPERTY_PAGINATION_KEY +}) +@jakarta.annotation.Generated(value = "org.openapitools.codegen.languages.JavaClientCodegen", comments = "Generator version: 7.7.0") +public class SearchEventsResponse { + public static final String JSON_PROPERTY_EVENTS = "events"; + private List events = null; + + public static final String JSON_PROPERTY_PAGINATION_KEY = "paginationKey"; + private String paginationKey; + + public SearchEventsResponse() { + } + + public SearchEventsResponse events(List events) { + this.events = events; + return this; + } + + public SearchEventsResponse addSearchEventsResponseEventsInnerItem(SearchEventsResponseEventsInner eventsItem) { + if (this.events == null) { + this.events = new ArrayList<>(); + } + this.events.add(eventsItem); + return this; + } + + /** + * Get events + * @return events + **/ + @jakarta.annotation.Nullable + @Schema(description = "") + @JsonProperty(JSON_PROPERTY_EVENTS) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + + public List getEvents() { + return events; + } + + + @JsonProperty(JSON_PROPERTY_EVENTS) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setEvents(List events) { + this.events = events; + } + + + public SearchEventsResponse paginationKey(String paginationKey) { + this.paginationKey = paginationKey; + return this; + } + + /** + * Get paginationKey + * @return paginationKey + **/ + @jakarta.annotation.Nullable + @Schema(description = "") + @JsonProperty(JSON_PROPERTY_PAGINATION_KEY) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + + public String getPaginationKey() { + return paginationKey; + } + + + @JsonProperty(JSON_PROPERTY_PAGINATION_KEY) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setPaginationKey(String paginationKey) { + this.paginationKey = paginationKey; + } + + + /** + * Return true if this SearchEventsResponse object is equal to o. + */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + SearchEventsResponse searchEventsResponse = (SearchEventsResponse) o; + return Objects.equals(this.events, searchEventsResponse.events) && + Objects.equals(this.paginationKey, searchEventsResponse.paginationKey); + } + + @Override + public int hashCode() { + return Objects.hash(events, paginationKey); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class SearchEventsResponse {\n"); + sb.append(" events: ").append(toIndentedString(events)).append("\n"); + sb.append(" paginationKey: ").append(toIndentedString(paginationKey)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces + * (except the first line). + */ + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } + +} + diff --git a/sdk/src/main/java/com/fingerprint/model/SearchEventsResponseEventsInner.java b/sdk/src/main/java/com/fingerprint/model/SearchEventsResponseEventsInner.java new file mode 100644 index 0000000..919fd7d --- /dev/null +++ b/sdk/src/main/java/com/fingerprint/model/SearchEventsResponseEventsInner.java @@ -0,0 +1,99 @@ +package com.fingerprint.model; + +import java.util.Objects; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.annotation.JsonValue; +import com.fingerprint.model.Products; +import java.util.Arrays; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fingerprint.sdk.JSON; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * Device intelligence results for the identification event. + */ + +@Schema(description = "Device intelligence results for the identification event.") +@JsonPropertyOrder({ + SearchEventsResponseEventsInner.JSON_PROPERTY_PRODUCTS +}) +@JsonTypeName("SearchEventsResponse_events_inner") +@jakarta.annotation.Generated(value = "org.openapitools.codegen.languages.JavaClientCodegen", comments = "Generator version: 7.7.0") +public class SearchEventsResponseEventsInner { + public static final String JSON_PROPERTY_PRODUCTS = "products"; + private Products products; + + public SearchEventsResponseEventsInner() { + } + + public SearchEventsResponseEventsInner products(Products products) { + this.products = products; + return this; + } + + /** + * Get products + * @return products + **/ + @jakarta.annotation.Nonnull + @Schema(required = true, description = "") + @JsonProperty(JSON_PROPERTY_PRODUCTS) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + + public Products getProducts() { + return products; + } + + + @JsonProperty(JSON_PROPERTY_PRODUCTS) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public void setProducts(Products products) { + this.products = products; + } + + + /** + * Return true if this SearchEventsResponse_events_inner object is equal to o. + */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + SearchEventsResponseEventsInner searchEventsResponseEventsInner = (SearchEventsResponseEventsInner) o; + return Objects.equals(this.products, searchEventsResponseEventsInner.products); + } + + @Override + public int hashCode() { + return Objects.hash(products); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class SearchEventsResponseEventsInner {\n"); + sb.append(" products: ").append(toIndentedString(products)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces + * (except the first line). + */ + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } + +} + From de93e58520bd0c29e76b82e30a4422889f036d1f Mon Sep 17 00:00:00 2001 From: Ilya Taratukhin Date: Tue, 4 Feb 2025 18:17:39 +0100 Subject: [PATCH 2/4] chore: remove unrelated changelog records --- .changeset/forty-seas-prove.md | 5 ----- .changeset/orange-poets-drive.md | 5 ----- 2 files changed, 10 deletions(-) delete mode 100644 .changeset/forty-seas-prove.md delete mode 100644 .changeset/orange-poets-drive.md diff --git a/.changeset/forty-seas-prove.md b/.changeset/forty-seas-prove.md deleted file mode 100644 index 4f10093..0000000 --- a/.changeset/forty-seas-prove.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'fingerprint-pro-server-api-java-sdk': minor ---- - -Add `relay` detection method to the VPN Detection Smart Signal diff --git a/.changeset/orange-poets-drive.md b/.changeset/orange-poets-drive.md deleted file mode 100644 index c297c6f..0000000 --- a/.changeset/orange-poets-drive.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'fingerprint-pro-server-api-java-sdk': minor ---- - -**events**: Add a `suspect` field to the `identification` product schema \ No newline at end of file From 6343358ba3680d2ca4c8a05a7283b7c732700077 Mon Sep 17 00:00:00 2001 From: Ilya Taratukhin Date: Tue, 4 Feb 2025 18:19:24 +0100 Subject: [PATCH 3/4] 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 9b97b11..4e05782 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 f85d2e7..0000000 --- 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 0ebd154..0000000 --- a/sdk/src/test/resources/mocks/update_event_one_field_request.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "linkedId": "myNewLinkedId" -} From d4c2fed4d99349b515288f522d237e6ff6bb751a Mon Sep 17 00:00:00 2001 From: Ilya Taratukhin Date: Tue, 4 Feb 2025 18:55:38 +0100 Subject: [PATCH 4/4] test: add tests for `searchEvents` method --- .../fingerprint/api/FingerprintApiTest.java | 136 +++++++++++++++++- 1 file changed, 129 insertions(+), 7 deletions(-) diff --git a/sdk/src/test/java/com/fingerprint/api/FingerprintApiTest.java b/sdk/src/test/java/com/fingerprint/api/FingerprintApiTest.java index 4e05782..f670f39 100644 --- a/sdk/src/test/java/com/fingerprint/api/FingerprintApiTest.java +++ b/sdk/src/test/java/com/fingerprint/api/FingerprintApiTest.java @@ -1,6 +1,7 @@ package com.fingerprint.api; import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; @@ -18,6 +19,7 @@ import java.io.IOException; import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -119,8 +121,12 @@ private void addMock(String operation, String path, ApiAnswerFunction ans } Mockito.doAnswer(invocation -> { validateIntegrationInfo(invocation.getArgument(3)); - - return answerFunction.apply(invocation); + ApiResponse result = answerFunction.apply(invocation); + if (result.getStatusCode() == 200) { + return result; + } else { + throw new ApiException(result.getStatusCode(), result.getHeaders(), result.getData().toString()); + } }).when(apiClient).invokeAPI( eq(operationName), // operation, for example "FingerprintApi.getEvent" eq(path), // path @@ -146,11 +152,11 @@ private void addMock(String operation, String path, ApiAnswerFunction ans 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); + if (statusCode == 200) { + return new ApiResponse<>(statusCode, null, path != null ? MAPPER.readValue(getFileAsIOStream(path), returnType.getRawType()) : null); + } else { + return new ApiResponse<>(statusCode, null, new String(getFileAsIOStream(path).readAllBytes(), StandardCharsets.UTF_8)); + } } public static boolean listContainsPair(List pairs, String key, String value) { @@ -504,4 +510,120 @@ public void relatedVisitorsTest() throws ApiException { assertEquals(relatedVisitorsList.get(1).getVisitorId(), "25ee02iZwGxeyT0jMNkZ"); } + @Test + public void searchEventsMinimumParamsTest() throws ApiException { + int LIMIT = 1; + addMock("searchEvents", null, invocation -> { + List queryParams = invocation.getArgument(3); + assertEquals(2, queryParams.size()); + assertTrue(listContainsPair(queryParams, "limit", String.valueOf(LIMIT))); + + return mockFileToResponse(200, invocation, "mocks/get_event_search_200.json"); + }); + + SearchEventsResponse response = api.searchEvents(LIMIT, null, null, null, null, null, null, null, null); + List events = response.getEvents(); + + assertEquals(events.size(), 1); + Products products = events.get(0).getProducts(); + + assertNotNull(products); + assertNotNull(products.getIdentification()); + assertNotNull(products.getIdentification().getData()); + assertEquals("Ibk1527CUFmcnjLwIs4A9", products.getIdentification().getData().getVisitorId()); + + assertFalse(products.getClonedApp().getData().getResult()); + assertFalse(products.getEmulator().getData().getResult()); + assertFalse(products.getFrida().getData().getResult()); + assertFalse(products.getJailbroken().getData().getResult()); + assertFalse(products.getIpBlocklist().getData().getResult()); + assertFalse(products.getProxy().getData().getResult()); + assertFalse(products.getTampering().getData().getResult()); + assertFalse(products.getTor().getData().getResult()); + assertFalse(products.getVpn().getData().getResult()); + assertFalse(products.getVirtualMachine().getData().getResult()); + assertFalse(products.getHighActivity().getData().getResult()); + assertFalse(products.getLocationSpoofing().getData().getResult()); + assertEquals(0, products.getFactoryReset().getData().getTimestamp()); + ProductRawDeviceAttributes signalResponseRawDeviceAttributes = products.getRawDeviceAttributes(); + assertEquals(127, signalResponseRawDeviceAttributes.getData().get("architecture").getValue()); + assertEquals(35.73832903057337, signalResponseRawDeviceAttributes.getData().get("audio").getValue()); + Map canvasAttribute = (Map) products.getRawDeviceAttributes().getData().get("canvas").getValue(); + assertEquals(true, canvasAttribute.get("Winding")); + assertEquals("4dce9d6017c3e0c052a77252f29f2b1c", canvasAttribute.get("Geometry")); + assertEquals("p3", signalResponseRawDeviceAttributes.getData().get("colorGamut").getValue()); + assertEquals(true, signalResponseRawDeviceAttributes.getData().get("cookiesEnabled").getValue()); + } + + @Test + public void searchEventsMaximumParamsTest() throws ApiException { + final int LIMIT = 1; + final String BOT = "good"; + final String IP_ADDRESS = "192.168.0.1/32"; + final String LINKED_ID = "some_id"; + final Long START = 1582299576511L; + final Long END = 1582299576513L; + final Boolean REVERSE = true; + final Boolean SUSPECT = false; + + addMock("searchEvents", null, invocation -> { + List queryParams = invocation.getArgument(3); + assertEquals(10, queryParams.size()); + assertTrue(listContainsPair(queryParams, "limit", String.valueOf(LIMIT))); + assertTrue(listContainsPair(queryParams, "visitor_id", MOCK_VISITOR_ID)); + assertTrue(listContainsPair(queryParams, "bot", BOT)); + assertTrue(listContainsPair(queryParams, "ip_address", IP_ADDRESS)); + assertTrue(listContainsPair(queryParams, "linked_id", LINKED_ID)); + assertTrue(listContainsPair(queryParams, "start", String.valueOf(START))); + assertTrue(listContainsPair(queryParams, "end", String.valueOf(END))); + assertTrue(listContainsPair(queryParams, "reverse", String.valueOf(REVERSE))); + assertTrue(listContainsPair(queryParams, "suspect", String.valueOf(SUSPECT))); + + return mockFileToResponse(200, invocation, "mocks/get_event_search_200.json"); + }); + + SearchEventsResponse response = api.searchEvents(LIMIT, MOCK_VISITOR_ID, BOT, IP_ADDRESS, LINKED_ID, START, END, REVERSE, SUSPECT); + List events = response.getEvents(); + assertEquals(events.size(), 1); + } + + @Test + public void searchEvents400ErrorTest() throws ApiException, JsonProcessingException { + int LIMIT = 1; + addMock("searchEvents", null, invocation -> { + List queryParams = invocation.getArgument(3); + assertEquals(2, queryParams.size()); + assertTrue(listContainsPair(queryParams, "limit", String.valueOf(LIMIT))); + + return mockFileToResponse(400, invocation, "mocks/errors/400_ip_address_invalid.json"); + }); + + ApiException exception = assertThrows(ApiException.class, + () -> api.searchEvents(LIMIT, null, null, null, null, null, null, null, null)); + + assertEquals(400, exception.getCode()); + ErrorResponse response = MAPPER.readValue(exception.getResponseBody(), ErrorResponse.class); + assertEquals(ErrorCode.REQUEST_CANNOT_BE_PARSED, response.getError().getCode()); + assertEquals("invalid ip address", response.getError().getMessage()); + } + + @Test + public void searchEvents403ErrorTest() throws ApiException, JsonProcessingException { + int LIMIT = 1; + addMock("searchEvents", null, invocation -> { + List queryParams = invocation.getArgument(3); + assertEquals(2, queryParams.size()); + assertTrue(listContainsPair(queryParams, "limit", String.valueOf(LIMIT))); + + return mockFileToResponse(403, invocation, "mocks/errors/403_feature_not_enabled.json"); + }); + + ApiException exception = assertThrows(ApiException.class, + () -> api.searchEvents(LIMIT, null, null, null, null, null, null, null, null)); + + assertEquals(403, exception.getCode()); + ErrorResponse response = MAPPER.readValue(exception.getResponseBody(), ErrorResponse.class); + assertEquals(ErrorCode.FEATURE_NOT_ENABLED, response.getError().getCode()); + assertEquals("feature not enabled", response.getError().getMessage()); + } }