Skip to content

Commit

Permalink
feat(inventory-audit): Implement holdings audit endpoint (#192)
Browse files Browse the repository at this point in the history
* feat(inventory-audit): Implement holdings audit endpoint

Implements: MODAUD-213
  • Loading branch information
viacheslavkol authored Feb 11, 2025
1 parent 1c07fc8 commit 2a6d064
Show file tree
Hide file tree
Showing 6 changed files with 120 additions and 25 deletions.
19 changes: 16 additions & 3 deletions descriptors/ModuleDescriptor-template.json
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,13 @@
"permissionsRequired": [
"audit.inventory.instance.collection.get"
]
},
{
"methods": ["GET"],
"pathPattern": "/audit-data/inventory/holdings/{entityId}",
"permissionsRequired": [
"audit.inventory.holdings.collection.get"
]
}
]
},
Expand Down Expand Up @@ -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",
Expand 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"
]
}
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
@Repository
public class HoldingsEventDao extends InventoryEventDaoImpl {

protected HoldingsEventDao(PostgresClientFactory pgClientFactory) {
public HoldingsEventDao(PostgresClientFactory pgClientFactory) {
super(pgClientFactory);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
@Repository
public class ItemEventDao extends InventoryEventDaoImpl {

protected ItemEventDao(PostgresClientFactory pgClientFactory) {
public ItemEventDao(PostgresClientFactory pgClientFactory) {
super(pgClientFactory);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -56,6 +55,28 @@ public void getAuditDataInventoryInstanceByEntityId(String entityId, String even
}
}

@Override
public void getAuditDataInventoryHoldingsByEntityId(String entityId, String eventTs, Map<String, String> okapiHeaders,
Handler<AsyncResult<Response>> 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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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<InventoryResourceType, InventoryEventDaoImpl> 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");

Expand All @@ -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())
Expand All @@ -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++) {
Expand All @@ -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()
Expand All @@ -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<Arguments> provideResourceTypeAndPath() {
return Stream.of(
Arguments.of(InventoryResourceType.INSTANCE, INVENTORY_INSTANCE_AUDIT_PATH),
Arguments.of(InventoryResourceType.HOLDINGS, INVENTORY_HOLDINGS_AUDIT_PATH)
);
}

private static Stream<Arguments> providePath() {
return Stream.of(
Arguments.of(INVENTORY_INSTANCE_AUDIT_PATH),
Arguments.of(INVENTORY_HOLDINGS_AUDIT_PATH)
);
}
}
20 changes: 20 additions & 0 deletions ramls/inventory-audit.raml
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit 2a6d064

Please sign in to comment.