diff --git a/descriptors/ModuleDescriptor-template.json b/descriptors/ModuleDescriptor-template.json index a3bb56c7..2c5c5775 100644 --- a/descriptors/ModuleDescriptor-template.json +++ b/descriptors/ModuleDescriptor-template.json @@ -276,6 +276,13 @@ "permissionsRequired": [ "audit.inventory.instance.collection.get" ] + }, + { + "methods": ["GET"], + "pathPattern": "/audit-data/inventory/holdings/{entityId}", + "permissionsRequired": [ + "audit.inventory.holdings.collection.get" + ] } ] }, @@ -407,8 +414,13 @@ }, { "permissionName": "audit.inventory.instance.collection.get", - "displayName": "Inventory Audit - get events", - "description": "Get inventory audit events" + "displayName": "Inventory Audit - get instance events", + "description": "Get inventory instance audit events" + }, + { + "permissionName": "audit.inventory.holdings.collection.get", + "displayName": "Inventory Audit - get holdings events", + "description": "Get inventory holdings audit events" }, { "permissionName": "audit.all", @@ -428,7 +440,8 @@ "acquisition.invoice.events.get", "acquisition.invoice-line.events.get", "acquisition.organization.events.get", - "audit.inventory.instance.collection.get" + "audit.inventory.instance.collection.get", + "audit.inventory.holdings.collection.get" ] } ], diff --git a/mod-audit-server/src/main/java/org/folio/dao/inventory/impl/HoldingsEventDao.java b/mod-audit-server/src/main/java/org/folio/dao/inventory/impl/HoldingsEventDao.java index b5dfe5f7..412f5c5e 100644 --- a/mod-audit-server/src/main/java/org/folio/dao/inventory/impl/HoldingsEventDao.java +++ b/mod-audit-server/src/main/java/org/folio/dao/inventory/impl/HoldingsEventDao.java @@ -7,7 +7,7 @@ @Repository public class HoldingsEventDao extends InventoryEventDaoImpl { - protected HoldingsEventDao(PostgresClientFactory pgClientFactory) { + public HoldingsEventDao(PostgresClientFactory pgClientFactory) { super(pgClientFactory); } diff --git a/mod-audit-server/src/main/java/org/folio/dao/inventory/impl/ItemEventDao.java b/mod-audit-server/src/main/java/org/folio/dao/inventory/impl/ItemEventDao.java index 637ad20e..b4153dbb 100644 --- a/mod-audit-server/src/main/java/org/folio/dao/inventory/impl/ItemEventDao.java +++ b/mod-audit-server/src/main/java/org/folio/dao/inventory/impl/ItemEventDao.java @@ -7,7 +7,7 @@ @Repository public class ItemEventDao extends InventoryEventDaoImpl { - protected ItemEventDao(PostgresClientFactory pgClientFactory) { + public ItemEventDao(PostgresClientFactory pgClientFactory) { super(pgClientFactory); } diff --git a/mod-audit-server/src/main/java/org/folio/rest/impl/InventoryAuditImpl.java b/mod-audit-server/src/main/java/org/folio/rest/impl/InventoryAuditImpl.java index cc0d1686..e931948d 100644 --- a/mod-audit-server/src/main/java/org/folio/rest/impl/InventoryAuditImpl.java +++ b/mod-audit-server/src/main/java/org/folio/rest/impl/InventoryAuditImpl.java @@ -19,7 +19,6 @@ import org.folio.rest.tools.utils.TenantTool; import org.folio.services.inventory.InventoryEventService; import org.folio.spring.SpringContextUtil; -import org.folio.util.ErrorUtils; import org.folio.util.inventory.InventoryResourceType; import org.springframework.beans.factory.annotation.Autowired; @@ -56,6 +55,28 @@ public void getAuditDataInventoryInstanceByEntityId(String entityId, String even } } + @Override + public void getAuditDataInventoryHoldingsByEntityId(String entityId, String eventTs, Map okapiHeaders, + Handler> asyncResultHandler, + Context vertxContext) { + LOGGER.debug( + "getAuditDataInventoryHoldingsByEntityId:: Retrieving Audit Data Inventory Holdings by [entityId: {}, eventTs: {}]", + entityId, eventTs); + var tenantId = TenantTool.tenantId(okapiHeaders); + try { + service.getEvents(InventoryResourceType.HOLDINGS, entityId, eventTs, tenantId) + .map(AuditDataInventory.GetAuditDataInventoryHoldingsByEntityIdResponse::respond200WithApplicationJson) + .map(Response.class::cast) + .otherwise(this::mapExceptionToResponse) + .onComplete(asyncResultHandler); + } catch (Exception e) { + LOGGER.error( + "getAuditDataInventoryHoldingsByEntityId:: Error retrieving Audit Data Inventory Holdings by [entityId: {}, eventDate: {}]", + entityId, eventTs, e); + asyncResultHandler.handle(Future.succeededFuture(mapExceptionToResponse(e))); + } + } + private Response mapExceptionToResponse(Throwable throwable) { LOGGER.debug("mapExceptionToResponse:: Mapping Exception :{} to Response", throwable.getMessage(), throwable); if (throwable instanceof ValidationException) { diff --git a/mod-audit-server/src/test/java/org/folio/rest/impl/InventoryAuditApiTest.java b/mod-audit-server/src/test/java/org/folio/rest/impl/InventoryAuditApiTest.java index 9f63418a..2cbde29a 100644 --- a/mod-audit-server/src/test/java/org/folio/rest/impl/InventoryAuditApiTest.java +++ b/mod-audit-server/src/test/java/org/folio/rest/impl/InventoryAuditApiTest.java @@ -12,21 +12,33 @@ import java.sql.Timestamp; import java.time.Instant; import java.time.LocalDateTime; +import java.util.EnumMap; +import java.util.Map; import java.util.UUID; +import java.util.stream.Stream; +import lombok.SneakyThrows; import org.folio.CopilotGenerated; import org.folio.HttpStatus; import org.folio.dao.configuration.SettingDao; import org.folio.dao.configuration.SettingEntity; import org.folio.dao.configuration.SettingValueType; import org.folio.dao.inventory.InventoryAuditEntity; +import org.folio.dao.inventory.impl.HoldingsEventDao; import org.folio.dao.inventory.impl.InstanceEventDao; +import org.folio.dao.inventory.impl.InventoryEventDaoImpl; import org.folio.util.PostgresClientFactory; +import org.folio.util.inventory.InventoryResourceType; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.mockito.InjectMocks; import org.mockito.Spy; +import org.mockito.junit.jupiter.MockitoExtension; @CopilotGenerated(partiallyGenerated = true) +@ExtendWith(MockitoExtension.class) public class InventoryAuditApiTest extends ApiTestBase { private static final String TENANT_ID = "modaudittest"; @@ -36,21 +48,31 @@ public class InventoryAuditApiTest extends ApiTestBase { private static final Header CONTENT_TYPE_HEADER = new Header("Content-Type", "application/json"); private static final Headers HEADERS = new Headers(TENANT_HEADER, PERMS_HEADER, CONTENT_TYPE_HEADER); private static final String INVENTORY_INSTANCE_AUDIT_PATH = "/audit-data/inventory/instance/"; + private static final String INVENTORY_HOLDINGS_AUDIT_PATH = "/audit-data/inventory/holdings/"; + @InjectMocks InstanceEventDao instanceEventDao; @InjectMocks + HoldingsEventDao holdingsEventDao; + @InjectMocks SettingDao settingDao; @Spy private PostgresClientFactory postgresClientFactory = new PostgresClientFactory(Vertx.vertx()); + private Map resourceToDaoMap; + @BeforeEach public void setUp() { - instanceEventDao = new InstanceEventDao(postgresClientFactory); - settingDao = new SettingDao(postgresClientFactory); + resourceToDaoMap = new EnumMap<>(InventoryResourceType.class); + resourceToDaoMap.put(InventoryResourceType.INSTANCE, instanceEventDao); + resourceToDaoMap.put(InventoryResourceType.HOLDINGS, holdingsEventDao); } - @Test - void shouldReturnInventoryEventsOnGetByEntityId() { + @SneakyThrows + @ParameterizedTest + @MethodSource("provideResourceTypeAndPath") + void shouldReturnInventoryEventsOnGetByEntityId(InventoryResourceType resourceType, String apiPath) { + var dao = resourceToDaoMap.get(resourceType); var jsonObject = new JsonObject(); jsonObject.put("name", "Test Product"); @@ -63,17 +85,20 @@ void shouldReturnInventoryEventsOnGetByEntityId() { jsonObject.getMap() ); - instanceEventDao.save(inventoryAuditEntity, TENANT_ID); + dao.save(inventoryAuditEntity, TENANT_ID).toCompletionStage().toCompletableFuture().get(); given().headers(HEADERS) - .get(INVENTORY_INSTANCE_AUDIT_PATH + ENTITY_ID) + .get(apiPath + ENTITY_ID) .then().log().all() .statusCode(HttpStatus.HTTP_OK.toInt()) .body(containsString(ENTITY_ID)); } - @Test - void shouldReturnPaginatedInventoryEvents() { + @SneakyThrows + @ParameterizedTest + @MethodSource("provideResourceTypeAndPath") + void shouldReturnPaginatedInventoryEvents(InventoryResourceType resourceType, String apiPath) { + var dao = resourceToDaoMap.get(resourceType); // Update the INVENTORY_RECORDS_PAGE_SIZE setting to 3 var settingEntity = SettingEntity.builder() .id(INVENTORY_RECORDS_PAGE_SIZE.getSettingId()) @@ -83,7 +108,7 @@ void shouldReturnPaginatedInventoryEvents() { .groupId(INVENTORY_RECORDS_PAGE_SIZE.getGroup().getId()) .updatedDate(LocalDateTime.now()) .build(); - settingDao.update(settingEntity.getId(), settingEntity, TENANT_ID).result(); + settingDao.update(settingEntity.getId(), settingEntity, TENANT_ID).toCompletionStage().toCompletableFuture().get(); // Create InventoryAuditEntity entities with a year date difference for (int i = 0; i < 5; i++) { @@ -99,12 +124,12 @@ void shouldReturnPaginatedInventoryEvents() { jsonObject.getMap() ); - instanceEventDao.save(inventoryAuditEntity, TENANT_ID).result(); + dao.save(inventoryAuditEntity, TENANT_ID).toCompletionStage().toCompletableFuture().get(); } // Query the API once var firstResponse = given().headers(HEADERS) - .get(INVENTORY_INSTANCE_AUDIT_PATH + ENTITY_ID) + .get(apiPath + ENTITY_ID) .then().log().all() .statusCode(HttpStatus.HTTP_OK.toInt()) .assertThat() @@ -116,27 +141,43 @@ void shouldReturnPaginatedInventoryEvents() { // Query the API again, passing the last date from the previous response given().headers(HEADERS) - .get(INVENTORY_INSTANCE_AUDIT_PATH + ENTITY_ID + "?eventTs=" + lastDate) + .get(apiPath + ENTITY_ID + "?eventTs=" + lastDate) .then().log().all() .statusCode(HttpStatus.HTTP_OK.toInt()) .body("inventoryAuditItems", hasSize(2)); } - @Test - void shouldReturnEmptyListForInvalidEntityId() { + @ParameterizedTest + @MethodSource("providePath") + void shouldReturnEmptyListForInvalidEntityId(String apiPath) { given().headers(HEADERS) - .get(INVENTORY_INSTANCE_AUDIT_PATH + UUID.randomUUID()) + .get(apiPath + UUID.randomUUID()) .then().log().all() .statusCode(HttpStatus.HTTP_OK.toInt()) .body("inventoryAuditItems", hasSize(0)); } - @Test - void shouldReturn400ForInvalidEntityId() { + @ParameterizedTest + @MethodSource("providePath") + void shouldReturn400ForInvalidEntityId(String apiPath) { given().headers(HEADERS) - .get(INVENTORY_INSTANCE_AUDIT_PATH + "invalid-id") + .get(apiPath + "invalid-id") .then().log().all() .statusCode(HttpStatus.HTTP_BAD_REQUEST.toInt()) .body("errors[0].message", containsString("Invalid UUID string")); } + + private static Stream provideResourceTypeAndPath() { + return Stream.of( + Arguments.of(InventoryResourceType.INSTANCE, INVENTORY_INSTANCE_AUDIT_PATH), + Arguments.of(InventoryResourceType.HOLDINGS, INVENTORY_HOLDINGS_AUDIT_PATH) + ); + } + + private static Stream providePath() { + return Stream.of( + Arguments.of(INVENTORY_INSTANCE_AUDIT_PATH), + Arguments.of(INVENTORY_HOLDINGS_AUDIT_PATH) + ); + } } \ No newline at end of file diff --git a/ramls/inventory-audit.raml b/ramls/inventory-audit.raml index 455a169f..8d32d50e 100644 --- a/ramls/inventory-audit.raml +++ b/ramls/inventory-audit.raml @@ -36,4 +36,24 @@ traits: example: strict: false value: !include raml-util/examples/errors.sample + /holdings/{entityId}: + get: + description: Get list of holdings events + is: [ + validate, + seek + ] + responses: + 200: + body: + application/json: + type: inventory-audit-collection + 500: + description: "Internal server error" + body: + application/json: + type: errors + example: + strict: false + value: !include raml-util/examples/errors.sample