diff --git a/.secrets.baseline b/.secrets.baseline index d637c4c43..a4aaec714 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -296,5 +296,5 @@ } ] }, - "generated_at": "2024-10-07T15:02:23Z" + "generated_at": "2024-12-09T11:44:50Z" } diff --git a/live-ingester/pom.xml b/live-ingester/pom.xml index cedbe3713..b4716882f 100644 --- a/live-ingester/pom.xml +++ b/live-ingester/pom.xml @@ -130,6 +130,11 @@ lombok provided + + com.atlassian.oai + swagger-request-validator-core + test + diff --git a/live-ingester/src/integration-test/java/org/alfresco/hxi_connector/live_ingester/domain/usecase/e2e/repository/CreateRequestIntegrationTest.java b/live-ingester/src/integration-test/java/org/alfresco/hxi_connector/live_ingester/domain/usecase/e2e/repository/CreateRequestIntegrationTest.java index d90d0f598..5661e4264 100644 --- a/live-ingester/src/integration-test/java/org/alfresco/hxi_connector/live_ingester/domain/usecase/e2e/repository/CreateRequestIntegrationTest.java +++ b/live-ingester/src/integration-test/java/org/alfresco/hxi_connector/live_ingester/domain/usecase/e2e/repository/CreateRequestIntegrationTest.java @@ -30,6 +30,8 @@ import org.junit.jupiter.api.Test; import org.alfresco.hxi_connector.live_ingester.util.E2ETestBase; +import org.alfresco.hxi_connector.live_ingester.util.insight_api.HxInsightRequest; +import org.alfresco.hxi_connector.live_ingester.util.insight_api.RequestLoader; public class CreateRequestIntegrationTest extends E2ETestBase { @@ -87,41 +89,8 @@ void testCreateRequest() containerSupport.raiseRepoEvent(repoEvent); // then - String expectedBody = """ - [ - { - "objectId": "d71dd823-82c7-477c-8490-04cb0e826e65", - "sourceId" : "alfresco-dummy-source-id-0a63de491876", - "eventType": "create", - "sourceTimestamp": 1611227656423, - "properties": { - "cm:autoVersion": {"value": true}, - "createdAt": {"value": 1611227655695}, - "modifiedAt": {"value" : 1611227655695}, - "cm:versionType": {"value": "MAJOR"}, - "aspectsNames": {"value": ["cm:versionable", "cm:auditable"]}, - "cm:name": { - "value": "purchase-order-scan.doc", - "annotation" : "name" - }, - "type": {"value": "cm:content"}, - "createdBy": {"value": "admin"}, - "modifiedBy": {"value": "admin"}, - "cm:content": { - "file": { - "content-metadata": { - "size": 531152, - "name": "purchase-order-scan.doc", - "content-type": "application/msword" - } - } - }, - "ALLOW_ACCESS": {"value": ["GROUP_EVERYONE"]}, - "DENY_ACCESS": {"value": []} - } - } - ]"""; - containerSupport.expectHxIngestMessageReceived(expectedBody); + HxInsightRequest request = RequestLoader.load("/rest/hxinsight/requests/create-document.yml"); + containerSupport.expectHxIngestMessageReceived(request.body()); String expectedATSRequest = """ { diff --git a/live-ingester/src/integration-test/java/org/alfresco/hxi_connector/live_ingester/domain/usecase/e2e/repository/DeleteRequestIntegrationTest.java b/live-ingester/src/integration-test/java/org/alfresco/hxi_connector/live_ingester/domain/usecase/e2e/repository/DeleteRequestIntegrationTest.java index 73157c7b8..8324890e6 100644 --- a/live-ingester/src/integration-test/java/org/alfresco/hxi_connector/live_ingester/domain/usecase/e2e/repository/DeleteRequestIntegrationTest.java +++ b/live-ingester/src/integration-test/java/org/alfresco/hxi_connector/live_ingester/domain/usecase/e2e/repository/DeleteRequestIntegrationTest.java @@ -28,6 +28,8 @@ import org.junit.jupiter.api.Test; import org.alfresco.hxi_connector.live_ingester.util.E2ETestBase; +import org.alfresco.hxi_connector.live_ingester.util.insight_api.HxInsightRequest; +import org.alfresco.hxi_connector.live_ingester.util.insight_api.RequestLoader; @SuppressWarnings("PMD.JUnitTestsShouldIncludeAssert") class DeleteRequestIntegrationTest extends E2ETestBase @@ -59,15 +61,7 @@ void testDeleteRequest() containerSupport.raiseRepoEvent(repoEvent); // then - String expectedBody = """ - [ - { - "objectId": "d71dd823-82c7-477c-8490-04cb0e826e65", - "sourceId" : "alfresco-dummy-source-id-0a63de491876", - "eventType": "delete", - "sourceTimestamp": 1611656982995 - } - ]"""; - containerSupport.expectHxIngestMessageReceived(expectedBody); + HxInsightRequest request = RequestLoader.load("/rest/hxinsight/requests/delete-document.yml"); + containerSupport.expectHxIngestMessageReceived(request.body()); } } diff --git a/live-ingester/src/integration-test/java/org/alfresco/hxi_connector/live_ingester/domain/usecase/e2e/repository/OpenApiRequestValidationTest.java b/live-ingester/src/integration-test/java/org/alfresco/hxi_connector/live_ingester/domain/usecase/e2e/repository/OpenApiRequestValidationTest.java new file mode 100644 index 000000000..b2b96b1a1 --- /dev/null +++ b/live-ingester/src/integration-test/java/org/alfresco/hxi_connector/live_ingester/domain/usecase/e2e/repository/OpenApiRequestValidationTest.java @@ -0,0 +1,143 @@ +/* + * #%L + * Alfresco HX Insight Connector + * %% + * Copyright (C) 2023 - 2024 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ +package org.alfresco.hxi_connector.live_ingester.domain.usecase.e2e.repository; + +import static com.atlassian.oai.validator.schema.SchemaValidator.ADDITIONAL_PROPERTIES_KEY; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; + +import com.atlassian.oai.validator.OpenApiInteractionValidator; +import com.atlassian.oai.validator.model.Request; +import com.atlassian.oai.validator.model.SimpleRequest; +import com.atlassian.oai.validator.report.LevelResolver; +import com.atlassian.oai.validator.report.MessageResolver; +import com.atlassian.oai.validator.report.ValidationReport; +import com.atlassian.oai.validator.schema.SchemaValidator; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.swagger.parser.OpenAPIParser; +import io.swagger.v3.oas.models.media.Schema; +import io.swagger.v3.parser.core.models.ParseOptions; +import lombok.SneakyThrows; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import org.alfresco.hxi_connector.live_ingester.util.insight_api.HxInsightRequest; +import org.alfresco.hxi_connector.live_ingester.util.insight_api.RequestLoader; + +public class OpenApiRequestValidationTest +{ + + private static final String OPEN_API_SPECIFICATION_URL = "http://hxai-data-platform-dev-swagger-ui.s3-website-us-east-1.amazonaws.com/docs/insight-ingestion-api-swagger.json"; + private static OpenApiInteractionValidator openApiInteractionValidator; + private static Schema propertiesSchema; + private static SchemaValidator schemaValidator; + + @BeforeAll + static void setUp() + { + + openApiInteractionValidator = OpenApiInteractionValidator + .createForSpecificationUrl(OPEN_API_SPECIFICATION_URL) + .withLevelResolver(LevelResolver.create().withLevel(ADDITIONAL_PROPERTIES_KEY, ValidationReport.Level.IGNORE).build()) + .build(); + + // Introducing schemaValidator as a workaround for the issue with OpenApiInteractionValidator. + // OpenApiInteractionValidator does not allow validation for deeply nested properties parts like properties.file, properties.value etc. + schemaValidator = createSchemaValidator(OPEN_API_SPECIFICATION_URL); + propertiesSchema = new Schema().additionalProperties( + new Schema().oneOf(List.of( + new Schema().$ref("#/components/schemas/File"), + new Schema().$ref("#/components/schemas/Value")))); + } + + @Test + void testRequestToPresignedUrls() + { + HxInsightRequest hxInsightRequest = RequestLoader.load("/rest/hxinsight/requests/get-presigned-urls.yml"); + + Request request = makeRequest(hxInsightRequest); + + assertThat(openApiInteractionValidator.validateRequest(request).getMessages()).isEmpty(); + } + + @SneakyThrows + @Test + void testCreateRequestToIngestionEvents() + { + HxInsightRequest hxInsightRequest = RequestLoader.load("/rest/hxinsight/requests/create-document.yml"); + JsonNode propertiesNode = new ObjectMapper().readTree(hxInsightRequest.body()).get(0).get("properties"); + + Request request = makeRequest(hxInsightRequest); + + assertThat(openApiInteractionValidator.validateRequest(request).getMessages()).isEmpty(); + assertThat(schemaValidator.validate(propertiesNode.toString(), propertiesSchema, null).getMessages()).isEmpty(); + } + + @SneakyThrows + @Test + void testUpdateRequestToIngestionEvents() + { + HxInsightRequest hxInsightRequest = RequestLoader.load("/rest/hxinsight/requests/update-document.yml"); + JsonNode propertiesNode = new ObjectMapper().readTree(hxInsightRequest.body()).get(0).get("properties"); + + Request request = makeRequest(hxInsightRequest); + + assertThat(openApiInteractionValidator.validateRequest(request).getMessages()).isEmpty(); + assertThat(schemaValidator.validate(propertiesNode.toString(), propertiesSchema, null).getMessages()).isEmpty(); + } + + @Test + void testDeleteRequestToIngestionEvents() + { + HxInsightRequest hxInsightRequest = RequestLoader.load("/rest/hxinsight/requests/delete-document.yml"); + + Request request = makeRequest(hxInsightRequest); + + assertThat(openApiInteractionValidator.validateRequest(request).getMessages()).isEmpty(); + } + + private static SchemaValidator createSchemaValidator(final String api) + { + final ParseOptions parseOptions = new ParseOptions(); + parseOptions.setResolve(true); + return new SchemaValidator( + new OpenAPIParser().readLocation(api, null, parseOptions).getOpenAPI(), + new MessageResolver( + LevelResolver + .create() + .withLevel(ADDITIONAL_PROPERTIES_KEY, ValidationReport.Level.IGNORE) + .build())); + } + + private static Request makeRequest(HxInsightRequest hxInsightRequest) + { + SimpleRequest.Builder builder = SimpleRequest.Builder.post(hxInsightRequest.url()); + hxInsightRequest.headers().forEach(builder::withHeader); + return builder.withBody(hxInsightRequest.body()).build(); + } +} diff --git a/live-ingester/src/integration-test/java/org/alfresco/hxi_connector/live_ingester/domain/usecase/e2e/repository/UpdateRequestIntegrationTest.java b/live-ingester/src/integration-test/java/org/alfresco/hxi_connector/live_ingester/domain/usecase/e2e/repository/UpdateRequestIntegrationTest.java index 6b3aca860..bac3ee70b 100644 --- a/live-ingester/src/integration-test/java/org/alfresco/hxi_connector/live_ingester/domain/usecase/e2e/repository/UpdateRequestIntegrationTest.java +++ b/live-ingester/src/integration-test/java/org/alfresco/hxi_connector/live_ingester/domain/usecase/e2e/repository/UpdateRequestIntegrationTest.java @@ -30,6 +30,8 @@ import org.junit.jupiter.api.Test; import org.alfresco.hxi_connector.live_ingester.util.E2ETestBase; +import org.alfresco.hxi_connector.live_ingester.util.insight_api.HxInsightRequest; +import org.alfresco.hxi_connector.live_ingester.util.insight_api.RequestLoader; @SuppressWarnings("PMD.JUnitTestsShouldIncludeAssert") public class UpdateRequestIntegrationTest extends E2ETestBase @@ -102,56 +104,8 @@ void testUpdateRequest() containerSupport.raiseRepoEvent(repoEvent); // then - String expectedBody = """ - [ - { - "objectId": "d71dd823-82c7-477c-8490-04cb0e826e65", - "sourceId" : "alfresco-dummy-source-id-0a63de491876", - "eventType": "update", - "sourceTimestamp" : 1611656982995, - "properties": { - "cm:title": {"value": "Purchase Order"}, - "aspectsNames": {"value": ["cm:versionable", "cm:author", "cm:titled"]}, - "modifiedBy": {"value": "abeecher"}, - "createdAt" : { - "value" : 1611227655695 - }, - "modifiedAt" : { - "value" : 1611227655695 - }, - "cm:versionLabel" : { - "value" : "1.0" - }, - "createdBy" : { - "value" : "admin" - }, - "ALLOW_ACCESS" : { - "value" : [ "GROUP_EVERYONE" ] - }, - "cm:name" : { - "value" : "purchase-order-scan.pdf", - "annotation" : "name" - }, - "type" : { - "value" : "cm:content" - }, - "DENY_ACCESS" : { - "value" : [ ] - }, - "cm:content" : { - "file" : { - "content-metadata" : { - "size" : 531152, - "name" : "purchase-order-scan.pdf", - "content-type" : "application/pdf" - } - } - } - }, - "removedProperties": ["cm:versionType", "cm:description"] - } - ]"""; - containerSupport.expectHxIngestMessageReceived(expectedBody); + HxInsightRequest request = RequestLoader.load("/rest/hxinsight/requests/update-document.yml"); + containerSupport.expectHxIngestMessageReceived(request.body()); } @Test diff --git a/live-ingester/src/integration-test/java/org/alfresco/hxi_connector/live_ingester/util/insight_api/HxInsightRequest.java b/live-ingester/src/integration-test/java/org/alfresco/hxi_connector/live_ingester/util/insight_api/HxInsightRequest.java new file mode 100644 index 000000000..c87c73aa7 --- /dev/null +++ b/live-ingester/src/integration-test/java/org/alfresco/hxi_connector/live_ingester/util/insight_api/HxInsightRequest.java @@ -0,0 +1,31 @@ +/* + * #%L + * Alfresco HX Insight Connector + * %% + * Copyright (C) 2023 - 2024 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ +package org.alfresco.hxi_connector.live_ingester.util.insight_api; + +import java.util.Map; + +public record HxInsightRequest(String url, Map headers, String body) +{} diff --git a/live-ingester/src/integration-test/java/org/alfresco/hxi_connector/live_ingester/util/insight_api/RequestLoader.java b/live-ingester/src/integration-test/java/org/alfresco/hxi_connector/live_ingester/util/insight_api/RequestLoader.java new file mode 100644 index 000000000..30debfe13 --- /dev/null +++ b/live-ingester/src/integration-test/java/org/alfresco/hxi_connector/live_ingester/util/insight_api/RequestLoader.java @@ -0,0 +1,74 @@ +/* + * #%L + * Alfresco HX Insight Connector + * %% + * Copyright (C) 2023 - 2024 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ +package org.alfresco.hxi_connector.live_ingester.util.insight_api; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.util.HashMap; +import java.util.Map; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +import org.alfresco.hxi_connector.live_ingester.domain.exception.LiveIngesterRuntimeException; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class RequestLoader +{ + private final static ObjectMapper JSON_MAPPER = new ObjectMapper(); + private final static ObjectMapper YAML_MAPPER = new ObjectMapper(new YAMLFactory()); + + public static HxInsightRequest load(String path) + { + Map data; + try (InputStream stream = HxInsightRequest.class.getResourceAsStream(path)) + { + data = YAML_MAPPER.readValue(stream.readAllBytes(), HashMap.class); + } + catch (IOException e) + { + throw new UncheckedIOException(e); + } + Object body = data.get("body"); + String bodyAsString = null; + if (body != null) + { + try + { + bodyAsString = JSON_MAPPER.writeValueAsString(body); + } + catch (JsonProcessingException e) + { + throw new LiveIngesterRuntimeException(e); + } + } + return new HxInsightRequest((String) data.get("url"), (Map) data.get("headers"), bodyAsString); + } +} diff --git a/live-ingester/src/integration-test/resources/rest/hxinsight/requests/create-document.yml b/live-ingester/src/integration-test/resources/rest/hxinsight/requests/create-document.yml new file mode 100644 index 000000000..206e1cf68 --- /dev/null +++ b/live-ingester/src/integration-test/resources/rest/hxinsight/requests/create-document.yml @@ -0,0 +1,39 @@ +url: /v1/ingestion-events +headers: + authorization: string + content-type: application/json + hxp-environment: string + user-agent: string +body: [ + { + "objectId": "d71dd823-82c7-477c-8490-04cb0e826e65", + "sourceId" : "alfresco-dummy-source-id-0a63de491876", + "eventType": "create", + "sourceTimestamp": 1611227656423, + "properties": { + "cm:autoVersion": {"value": true}, + "createdAt": {"value": 1611227655695}, + "modifiedAt": {"value" : 1611227655695}, + "cm:versionType": {"value": "MAJOR"}, + "aspectsNames": {"value": ["cm:versionable", "cm:auditable"]}, + "cm:name": { + "value": "purchase-order-scan.doc", + "annotation" : "name" + }, + "type": {"value": "cm:content"}, + "createdBy": {"value": "admin"}, + "modifiedBy": {"value": "admin"}, + "cm:content": { + "file": { + "content-metadata": { + "size": 531152, + "name": "purchase-order-scan.doc", + "content-type": "application/msword" + } + } + }, + "ALLOW_ACCESS": {"value": ["GROUP_EVERYONE"]}, + "DENY_ACCESS": {"value": []} + } + } +] diff --git a/live-ingester/src/integration-test/resources/rest/hxinsight/requests/delete-document.yml b/live-ingester/src/integration-test/resources/rest/hxinsight/requests/delete-document.yml new file mode 100644 index 000000000..7d5d285ea --- /dev/null +++ b/live-ingester/src/integration-test/resources/rest/hxinsight/requests/delete-document.yml @@ -0,0 +1,14 @@ +url: /v1/ingestion-events +headers: + authorization: string + content-type: application/json + hxp-environment: string + user-agent: string +body: [ + { + "objectId": "d71dd823-82c7-477c-8490-04cb0e826e65", + "sourceId" : "alfresco-dummy-source-id-0a63de491876", + "eventType": "delete", + "sourceTimestamp": 1611656982995 + } +] diff --git a/live-ingester/src/integration-test/resources/rest/hxinsight/requests/get-presigned-urls.yml b/live-ingester/src/integration-test/resources/rest/hxinsight/requests/get-presigned-urls.yml new file mode 100644 index 000000000..e2a221118 --- /dev/null +++ b/live-ingester/src/integration-test/resources/rest/hxinsight/requests/get-presigned-urls.yml @@ -0,0 +1,7 @@ +url: /v1/presigned-urls +headers: + authorization: string + content-type: application/json + hxp-environment: string + user-agent: string + count: string diff --git a/live-ingester/src/integration-test/resources/rest/hxinsight/requests/update-document.yml b/live-ingester/src/integration-test/resources/rest/hxinsight/requests/update-document.yml new file mode 100644 index 000000000..d2fd8c838 --- /dev/null +++ b/live-ingester/src/integration-test/resources/rest/hxinsight/requests/update-document.yml @@ -0,0 +1,69 @@ +url: /v1/ingestion-events +headers: + authorization: string + content-type: application/json + hxp-environment: string + user-agent: string +body: [ + { + "objectId": "d71dd823-82c7-477c-8490-04cb0e826e65", + "sourceId": "alfresco-dummy-source-id-0a63de491876", + "eventType": "update", + "sourceTimestamp": 1611656982995, + "properties": { + "cm:title": { + "value": "Purchase Order" + }, + "aspectsNames": { + "value": [ + "cm:versionable", + "cm:author", + "cm:titled" + ] + }, + "modifiedBy": { + "value": "abeecher" + }, + "createdAt": { + "value": 1611227655695 + }, + "modifiedAt": { + "value": 1611227655695 + }, + "cm:versionLabel": { + "value": "1.0" + }, + "createdBy": { + "value": "admin" + }, + "ALLOW_ACCESS": { + "value": [ + "GROUP_EVERYONE" + ] + }, + "cm:name": { + "value": "purchase-order-scan.pdf", + "annotation": "name" + }, + "type": { + "value": "cm:content" + }, + "DENY_ACCESS": { + "value": [] + }, + "cm:content": { + "file": { + "content-metadata": { + "size": 531152, + "name": "purchase-order-scan.pdf", + "content-type": "application/pdf" + } + } + } + }, + "removedProperties": [ + "cm:versionType", + "cm:description" + ] + } +] diff --git a/pom.xml b/pom.xml index a27557746..fd516dc56 100644 --- a/pom.xml +++ b/pom.xml @@ -86,6 +86,7 @@ 1.0-alpha-14 1.6.3 + 2.44.1 3.3.1 3.7.1 @@ -294,6 +295,11 @@ mapstruct-processor ${mapstruct.version} + + com.atlassian.oai + swagger-request-validator-core + ${swagger-request-validator.version} +