From 854e82a27090e2b626eae1dd2fa27bc604262505 Mon Sep 17 00:00:00 2001 From: azizbekxm Date: Thu, 9 Nov 2023 16:30:42 +0500 Subject: [PATCH] [MODAUD-174] - Consume piece change events and implement endpoints --- descriptors/ModuleDescriptor-template.json | 23 +++- .../folio/dao/acquisition/PieceEventsDao.java | 33 +++++ .../acquisition/impl/OrderEventsDaoImpl.java | 8 +- .../impl/OrderLineEventsDaoImpl.java | 6 +- .../acquisition/impl/PieceEventsDaoImpl.java | 128 ++++++++++++++++++ .../rest/impl/AuditDataAcquisitionImpl.java | 26 +++- .../acquisition/PieceAuditEventsService.java | 33 +++++ .../impl/PieceAuditEventsServiceImpl.java | 54 ++++++++ .../org/folio/util/AcquisitionEventType.java | 3 +- ...stants.java => AuditEventDBConstants.java} | 8 +- .../PieceEventConsumersVerticle.java | 29 ++++ .../consumers/PieceEventsHandler.java | 59 ++++++++ .../create_acquisition_pieces_log_table.sql | 11 ++ .../src/test/java/org/folio/TestSuite.java | 39 ++++-- .../org/folio/dao/PieceEventsDaoTest.java | 99 ++++++++++++++ .../impl/AuditDataAcquisitionAPITest.java | 6 +- .../rest/impl/PieceEventsHandlerMockTest.java | 94 +++++++++++++ .../services/OrderAuditEventsServiceTest.java | 43 ++---- .../OrderLineAuditEventsServiceTest.java | 33 +---- .../services/PieceAuditEventsServiceTest.java | 40 ++++++ .../java/org/folio/utils/EntityUtils.java | 60 ++++++++ ramls/acquisition-events.raml | 37 +++++ ramls/piece_audit_event.json | 40 ++++++ ramls/piece_audit_event_collection.json | 25 ++++ 24 files changed, 848 insertions(+), 89 deletions(-) create mode 100644 mod-audit-server/src/main/java/org/folio/dao/acquisition/PieceEventsDao.java create mode 100644 mod-audit-server/src/main/java/org/folio/dao/acquisition/impl/PieceEventsDaoImpl.java create mode 100644 mod-audit-server/src/main/java/org/folio/services/acquisition/PieceAuditEventsService.java create mode 100644 mod-audit-server/src/main/java/org/folio/services/acquisition/impl/PieceAuditEventsServiceImpl.java rename mod-audit-server/src/main/java/org/folio/util/{OrderAuditEventDBConstants.java => AuditEventDBConstants.java} (83%) create mode 100644 mod-audit-server/src/main/java/org/folio/verticle/acquisition/PieceEventConsumersVerticle.java create mode 100644 mod-audit-server/src/main/java/org/folio/verticle/acquisition/consumers/PieceEventsHandler.java create mode 100644 mod-audit-server/src/main/resources/templates/db_scripts/acquisition/create_acquisition_pieces_log_table.sql create mode 100644 mod-audit-server/src/test/java/org/folio/dao/PieceEventsDaoTest.java create mode 100644 mod-audit-server/src/test/java/org/folio/rest/impl/PieceEventsHandlerMockTest.java create mode 100644 mod-audit-server/src/test/java/org/folio/services/PieceAuditEventsServiceTest.java create mode 100644 mod-audit-server/src/test/java/org/folio/utils/EntityUtils.java create mode 100644 ramls/piece_audit_event.json create mode 100644 ramls/piece_audit_event_collection.json diff --git a/descriptors/ModuleDescriptor-template.json b/descriptors/ModuleDescriptor-template.json index e7bfceb7..8c6c835b 100644 --- a/descriptors/ModuleDescriptor-template.json +++ b/descriptors/ModuleDescriptor-template.json @@ -121,6 +121,21 @@ } ] }, + { + "id": "acquisition-piece-events", + "version": "1.0", + "handlers": [ + { + "methods": [ + "GET" + ], + "pathPattern": "/audit-data/acquisition/piece/{id}", + "permissionsRequired": [ + "acquisition.piece.events.get" + ] + } + ] + }, { "id": "circulation-logs", "version": "1.2", @@ -223,6 +238,11 @@ "displayName": "Acquisition order-line events - get order-line change events", "description": "Get order-line change events" }, + { + "permissionName": "acquisition.piece.events.get", + "displayName": "Acquisition piece events - get piece change events", + "description": "Get piece change events" + }, { "permissionName": "audit.all", "displayName": "Audit - all permissions", @@ -235,7 +255,8 @@ "audit.item.delete", "circulation-logs.collection.get", "acquisition.order.events.get", - "acquisition.order-line.events.get" + "acquisition.order-line.events.get", + "acquisition.piece.events.get" ] } ], diff --git a/mod-audit-server/src/main/java/org/folio/dao/acquisition/PieceEventsDao.java b/mod-audit-server/src/main/java/org/folio/dao/acquisition/PieceEventsDao.java new file mode 100644 index 00000000..8824901f --- /dev/null +++ b/mod-audit-server/src/main/java/org/folio/dao/acquisition/PieceEventsDao.java @@ -0,0 +1,33 @@ +package org.folio.dao.acquisition; + +import io.vertx.core.Future; +import io.vertx.sqlclient.Row; +import io.vertx.sqlclient.RowSet; +import org.folio.rest.jaxrs.model.PieceAuditEvent; +import org.folio.rest.jaxrs.model.PieceAuditEventCollection; + +public interface PieceEventsDao { + + /** + * Saves pieceAuditEvent entity to DB + * + * @param pieceAuditEvent pieceAuditEvent entity to save + * @param tenantId tenant id + * @return future with created row + */ + Future> save(PieceAuditEvent pieceAuditEvent, String tenantId); + + /** + * Searches for piece audit events by id + * + * @param pieceId piece id + * @param sortBy sort by + * @param sortOrder sort order + * @param limit limit + * @param offset offset + * @param tenantId tenant id + * @return future with PieceAuditEventCollection + */ + Future getAuditEventsByPieceId(String pieceId, String sortBy, String sortOrder, int limit, int offset, String tenantId); + +} diff --git a/mod-audit-server/src/main/java/org/folio/dao/acquisition/impl/OrderEventsDaoImpl.java b/mod-audit-server/src/main/java/org/folio/dao/acquisition/impl/OrderEventsDaoImpl.java index 8dfb02e6..71690f0a 100644 --- a/mod-audit-server/src/main/java/org/folio/dao/acquisition/impl/OrderEventsDaoImpl.java +++ b/mod-audit-server/src/main/java/org/folio/dao/acquisition/impl/OrderEventsDaoImpl.java @@ -22,19 +22,19 @@ import static java.lang.String.format; import static org.folio.rest.persist.PostgresClient.convertToPsqlStandard; -import static org.folio.util.OrderAuditEventDBConstants.*; +import static org.folio.util.AuditEventDBConstants.*; @Repository public class OrderEventsDaoImpl implements OrderEventsDao { private static final Logger LOGGER = LogManager.getLogger(); - public static final String TABLE_NAME = "acquisition_order_log"; + private static final String TABLE_NAME = "acquisition_order_log"; - public static final String GET_BY_ORDER_ID_SQL = "SELECT id, action, order_id, user_id, event_date, action_date, modified_content_snapshot," + + private static final String GET_BY_ORDER_ID_SQL = "SELECT id, action, order_id, user_id, event_date, action_date, modified_content_snapshot," + " (SELECT count(*) AS total_records FROM %s WHERE order_id = $1) FROM %s WHERE order_id = $1 %s LIMIT $2 OFFSET $3"; - public static final String INSERT_SQL = "INSERT INTO %s (id, action, order_id, user_id, event_date, action_date, modified_content_snapshot)" + + private static final String INSERT_SQL = "INSERT INTO %s (id, action, order_id, user_id, event_date, action_date, modified_content_snapshot)" + " VALUES ($1, $2, $3, $4, $5, $6, $7)"; @Autowired diff --git a/mod-audit-server/src/main/java/org/folio/dao/acquisition/impl/OrderLineEventsDaoImpl.java b/mod-audit-server/src/main/java/org/folio/dao/acquisition/impl/OrderLineEventsDaoImpl.java index 26d50718..88f1f1ee 100644 --- a/mod-audit-server/src/main/java/org/folio/dao/acquisition/impl/OrderLineEventsDaoImpl.java +++ b/mod-audit-server/src/main/java/org/folio/dao/acquisition/impl/OrderLineEventsDaoImpl.java @@ -22,16 +22,16 @@ import static java.lang.String.format; import static org.folio.rest.persist.PostgresClient.convertToPsqlStandard; -import static org.folio.util.OrderAuditEventDBConstants.*; +import static org.folio.util.AuditEventDBConstants.*; @Repository public class OrderLineEventsDaoImpl implements OrderLineEventsDao { private static final Logger LOGGER = LogManager.getLogger(); - public static final String TABLE_NAME = "acquisition_order_line_log"; + private static final String TABLE_NAME = "acquisition_order_line_log"; - public static final String GET_BY_ORDER_LINE_ID_SQL = "SELECT id, action, order_id, order_line_id, user_id, event_date, action_date, modified_content_snapshot," + + private static final String GET_BY_ORDER_LINE_ID_SQL = "SELECT id, action, order_id, order_line_id, user_id, event_date, action_date, modified_content_snapshot," + " (SELECT count(*) AS total_records FROM %s WHERE order_line_id = $1) " + " FROM %s WHERE order_line_id = $1 %s LIMIT $2 OFFSET $3"; diff --git a/mod-audit-server/src/main/java/org/folio/dao/acquisition/impl/PieceEventsDaoImpl.java b/mod-audit-server/src/main/java/org/folio/dao/acquisition/impl/PieceEventsDaoImpl.java new file mode 100644 index 00000000..083169cc --- /dev/null +++ b/mod-audit-server/src/main/java/org/folio/dao/acquisition/impl/PieceEventsDaoImpl.java @@ -0,0 +1,128 @@ +package org.folio.dao.acquisition.impl; + +import static java.lang.String.format; +import static org.folio.rest.persist.PostgresClient.convertToPsqlStandard; +import static org.folio.util.AuditEventDBConstants.ACTION_DATE_FIELD; +import static org.folio.util.AuditEventDBConstants.ACTION_FIELD; +import static org.folio.util.AuditEventDBConstants.EVENT_DATE_FIELD; +import static org.folio.util.AuditEventDBConstants.ID_FIELD; +import static org.folio.util.AuditEventDBConstants.MODIFIED_CONTENT_FIELD; +import static org.folio.util.AuditEventDBConstants.ORDER_BY_PATTERN; +import static org.folio.util.AuditEventDBConstants.PIECE_ID_FIELD; +import static org.folio.util.AuditEventDBConstants.TOTAL_RECORDS_FIELD; +import static org.folio.util.AuditEventDBConstants.USER_ID_FIELD; + +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.util.Date; +import java.util.UUID; + +import io.vertx.core.Future; +import io.vertx.core.Promise; +import io.vertx.core.json.JsonObject; +import io.vertx.sqlclient.Row; +import io.vertx.sqlclient.RowSet; +import io.vertx.sqlclient.Tuple; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.folio.dao.acquisition.PieceEventsDao; +import org.folio.rest.jaxrs.model.PieceAuditEvent; +import org.folio.rest.jaxrs.model.PieceAuditEventCollection; +import org.folio.util.PostgresClientFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Repository; + +@Repository +public class PieceEventsDaoImpl implements PieceEventsDao { + private static final Logger LOGGER = LogManager.getLogger(); + private static final String TABLE_NAME = "acquisition_piece_log"; + private static final String GET_BY_PIECE_ID_SQL = "SELECT id, action, piece_id, user_id, event_date, action_date, modified_content_snapshot," + + " (SELECT count(*) AS total_records FROM %s WHERE piece_id = $1) FROM %s WHERE piece_id = $1 %s LIMIT $2 OFFSET $3"; + private static final String INSERT_SQL = "INSERT INTO %s (id, action, piece_id, user_id, event_date, action_date, modified_content_snapshot)" + + " VALUES ($1, $2, $3, $4, $5, $6, $7)"; + + @Autowired + private final PostgresClientFactory pgClientFactory; + + public PieceEventsDaoImpl(PostgresClientFactory pgClientFactory) { + this.pgClientFactory = pgClientFactory; + } + + @Override + public Future> save(PieceAuditEvent pieceAuditEvent, String tenantId) { + LOGGER.debug("save:: Trying to save Piece AuditEvent with tenant id : {}", tenantId); + Promise> promise = Promise.promise(); + + String logTable = formatDBTableName(tenantId, TABLE_NAME); + String query = format(INSERT_SQL, logTable); + + makeSaveCall(promise, query, pieceAuditEvent, tenantId); + LOGGER.info("save:: Saved Piece AuditEvent with tenant id : {}", tenantId); + return promise.future(); + } + + @Override + public Future getAuditEventsByPieceId(String pieceId, String sortBy, String sortOrder, int limit, int offset, String tenantId) { + LOGGER.debug("getAuditEventsByOrderId:: Trying to retrieve AuditEvent with piece id : {}", pieceId); + Promise> promise = Promise.promise(); + try { + String logTable = formatDBTableName(tenantId, TABLE_NAME); + String query = format(GET_BY_PIECE_ID_SQL, logTable, logTable, format(ORDER_BY_PATTERN, sortBy, sortOrder)); + Tuple queryParams = Tuple.of(UUID.fromString(pieceId), limit, offset); + + pgClientFactory.createInstance(tenantId).selectRead(query, queryParams, promise); + } catch (Exception e) { + LOGGER.warn("Error getting piece audit events by piece id: {}", pieceId, e); + promise.fail(e); + } + LOGGER.info("getAuditEventsByOrderId:: Retrieved AuditEvent with piece id : {}", pieceId); + return promise.future().map(rowSet -> rowSet.rowCount() == 0 ? new PieceAuditEventCollection().withTotalItems(0) : + mapRowToListOfPieceEvent(rowSet)); + } + + private PieceAuditEventCollection mapRowToListOfPieceEvent(RowSet rowSet) { + LOGGER.debug("mapRowToListOfOrderEvent:: Mapping row to List of Piece Events"); + PieceAuditEventCollection pieceAuditEventCollection = new PieceAuditEventCollection(); + rowSet.iterator().forEachRemaining(row -> { + pieceAuditEventCollection.getPieceAuditEvents().add(mapRowToPieceEvent(row)); + pieceAuditEventCollection.setTotalItems(row.getInteger(TOTAL_RECORDS_FIELD)); + }); + LOGGER.info("mapRowToListOfOrderEvent:: Mapped row to List of Piece Events"); + return pieceAuditEventCollection; + } + + private PieceAuditEvent mapRowToPieceEvent(Row row) { + LOGGER.debug("mapRowToPieceEvent:: Mapping row to Order Event"); + return new PieceAuditEvent() + .withId(row.getValue(ID_FIELD).toString()) + .withAction(row.get(PieceAuditEvent.Action.class, ACTION_FIELD)) + .withPieceId(row.getValue(PIECE_ID_FIELD).toString()) + .withUserId(row.getValue(USER_ID_FIELD).toString()) + .withEventDate(Date.from(row.getLocalDateTime(EVENT_DATE_FIELD).toInstant(ZoneOffset.UTC))) + .withActionDate(Date.from(row.getLocalDateTime(ACTION_DATE_FIELD).toInstant(ZoneOffset.UTC))) + .withPieceSnapshot(JsonObject.mapFrom(row.getValue(MODIFIED_CONTENT_FIELD))); + } + + private void makeSaveCall(Promise> promise, String query, PieceAuditEvent pieceAuditEvent, String tenantId) { + LOGGER.debug("makeSaveCall:: Making save call with query : {} and tenant id : {}", query, tenantId); + try { + pgClientFactory.createInstance(tenantId).execute(query, Tuple.of(pieceAuditEvent.getId(), + pieceAuditEvent.getActionDate(), + pieceAuditEvent.getPieceId(), + pieceAuditEvent.getUserId(), + LocalDateTime.ofInstant(pieceAuditEvent.getEventDate().toInstant(), ZoneOffset.UTC), + LocalDateTime.ofInstant(pieceAuditEvent.getActionDate().toInstant(), ZoneOffset.UTC), + JsonObject.mapFrom(pieceAuditEvent.getPieceSnapshot())), + promise); + } catch (Exception e) { + LOGGER.error("Failed to save record with id: {} for order id: {} in to table {}", + pieceAuditEvent.getId(), pieceAuditEvent.getPieceId(), TABLE_NAME, e); + promise.fail(e); + } + } + + private String formatDBTableName(String tenantId, String table) { + LOGGER.debug("formatDBTableName:: Formatting DB Table Name with tenant id : {}", tenantId); + return format("%s.%s", convertToPsqlStandard(tenantId), table); + } +} diff --git a/mod-audit-server/src/main/java/org/folio/rest/impl/AuditDataAcquisitionImpl.java b/mod-audit-server/src/main/java/org/folio/rest/impl/AuditDataAcquisitionImpl.java index e651c739..ac119419 100644 --- a/mod-audit-server/src/main/java/org/folio/rest/impl/AuditDataAcquisitionImpl.java +++ b/mod-audit-server/src/main/java/org/folio/rest/impl/AuditDataAcquisitionImpl.java @@ -9,10 +9,12 @@ import org.apache.logging.log4j.Logger; import org.folio.rest.jaxrs.model.AuditDataAcquisitionOrderIdGetSortOrder; import org.folio.rest.jaxrs.model.AuditDataAcquisitionOrderLineIdGetSortOrder; +import org.folio.rest.jaxrs.model.AuditDataAcquisitionPieceIdGetSortOrder; import org.folio.rest.jaxrs.resource.AuditDataAcquisition; import org.folio.rest.tools.utils.TenantTool; import org.folio.services.acquisition.OrderAuditEventsService; import org.folio.services.acquisition.OrderLineAuditEventsService; +import org.folio.services.acquisition.PieceAuditEventsService; import org.folio.spring.SpringContextUtil; import org.folio.util.ErrorUtils; import org.springframework.beans.factory.annotation.Autowired; @@ -28,9 +30,10 @@ public class AuditDataAcquisitionImpl implements AuditDataAcquisition { @Autowired private OrderAuditEventsService orderAuditEventsService; - @Autowired private OrderLineAuditEventsService orderLineAuditEventsService; + @Autowired + private PieceAuditEventsService pieceAuditEventsService; public AuditDataAcquisitionImpl() { SpringContextUtil.autowireDependencies(this, Vertx.currentContext()); @@ -78,11 +81,30 @@ public void getAuditDataAcquisitionOrderLineById(String orderLineId, String sort }); } + @Override + public void getAuditDataAcquisitionPieceById(String pieceId, String sortBy, AuditDataAcquisitionPieceIdGetSortOrder sortOrder, + int limit, int offset, Map okapiHeaders, Handler> asyncResultHandler, Context vertxContext) { + LOGGER.debug("getAuditDataAcquisitionOrderById:: Retrieving Audit Data Acquisition Piece By Id : {}", pieceId); + String tenantId = TenantTool.tenantId(okapiHeaders); + + vertxContext.runOnContext(c -> { + try { + pieceAuditEventsService.getAuditEventsByPieceId(pieceId, sortBy, sortOrder.name(), limit, offset, tenantId) + .map(GetAuditDataAcquisitionPieceByIdResponse::respond200WithApplicationJson) + .map(Response.class::cast) + .otherwise(this::mapExceptionToResponse) + .onComplete(asyncResultHandler); + } catch (Exception e) { + LOGGER.error("Failed to get piece audit events by piece id: {}", pieceId, e); + asyncResultHandler.handle(Future.succeededFuture(mapExceptionToResponse(e))); + } + }); + } + private Response mapExceptionToResponse(Throwable throwable) { LOGGER.debug("mapExceptionToResponse:: Mapping Exception :{} to Response", throwable.getMessage(), throwable); return GetAuditDataAcquisitionOrderByIdResponse .respond500WithApplicationJson(ErrorUtils.buildErrors(GENERIC_ERROR_CODE.getCode(), throwable)); } - } diff --git a/mod-audit-server/src/main/java/org/folio/services/acquisition/PieceAuditEventsService.java b/mod-audit-server/src/main/java/org/folio/services/acquisition/PieceAuditEventsService.java new file mode 100644 index 00000000..1165ffab --- /dev/null +++ b/mod-audit-server/src/main/java/org/folio/services/acquisition/PieceAuditEventsService.java @@ -0,0 +1,33 @@ +package org.folio.services.acquisition; + +import io.vertx.core.Future; +import io.vertx.sqlclient.Row; +import io.vertx.sqlclient.RowSet; +import org.folio.rest.jaxrs.model.PieceAuditEvent; +import org.folio.rest.jaxrs.model.PieceAuditEventCollection; + +public interface PieceAuditEventsService { + + /** + * Saves Piece Audit Event + * + * @param pieceAuditEvent pieceAuditEvent + * @param tenantId id of tenant + * @return + */ + Future> savePieceAuditEvent(PieceAuditEvent pieceAuditEvent, String tenantId); + + /** + * Searches for piece audit events by piece id + * + * @param pieceId piece id + * @param sortBy sort by + * @param sortOrder sort order + * @param limit limit + * @param offset offset + * @return future with PieceAuditEventCollection + */ + Future getAuditEventsByPieceId(String pieceId, String sortBy, String sortOrder, + int limit, int offset, String tenantId); + +} diff --git a/mod-audit-server/src/main/java/org/folio/services/acquisition/impl/PieceAuditEventsServiceImpl.java b/mod-audit-server/src/main/java/org/folio/services/acquisition/impl/PieceAuditEventsServiceImpl.java new file mode 100644 index 00000000..65ca5480 --- /dev/null +++ b/mod-audit-server/src/main/java/org/folio/services/acquisition/impl/PieceAuditEventsServiceImpl.java @@ -0,0 +1,54 @@ +package org.folio.services.acquisition.impl; + +import io.vertx.core.Future; +import io.vertx.pgclient.PgException; +import io.vertx.sqlclient.Row; +import io.vertx.sqlclient.RowSet; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.folio.dao.acquisition.PieceEventsDao; +import org.folio.kafka.exception.DuplicateEventException; +import org.folio.rest.jaxrs.model.PieceAuditEvent; +import org.folio.rest.jaxrs.model.PieceAuditEventCollection; +import org.folio.services.acquisition.PieceAuditEventsService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class PieceAuditEventsServiceImpl implements PieceAuditEventsService { + private static final Logger LOGGER = LogManager.getLogger(); + private static final String UNIQUE_CONSTRAINT_VIOLATION_CODE = "23505"; + private PieceEventsDao pieceEventsDao; + + @Autowired + public PieceAuditEventsServiceImpl(PieceEventsDao pieceEventsDao) { + this.pieceEventsDao = pieceEventsDao; + } + + @Override + public Future> savePieceAuditEvent(PieceAuditEvent pieceAuditEvent, String tenantId) { + LOGGER.debug("savePieceAuditEvent:: Trying to save piece audit event with id={} for tenantId={}", pieceAuditEvent.getPieceId(), tenantId); + return pieceEventsDao.save(pieceAuditEvent, tenantId) + .recover(throwable -> handleFailures(throwable, pieceAuditEvent.getId())); + } + + @Override + public Future getAuditEventsByPieceId(String pieceId, String sortBy, String sortOrder, int limit, int offset, String tenantId) { + LOGGER.debug("getAuditEventsByOrderLineId:: Trying to retrieve audit events for piece Id : {} and tenant Id : {}", pieceId, tenantId); + return pieceEventsDao.getAuditEventsByPieceId(pieceId, sortBy, sortOrder, limit, offset, tenantId); + } + + private Future handleFailures(Throwable throwable, String id) { + LOGGER.debug("handleFailures:: Handling Failures with id={}", id); + return (throwable instanceof PgException && ((PgException) throwable).getCode().equals(UNIQUE_CONSTRAINT_VIOLATION_CODE)) ? + Future.failedFuture(new DuplicateEventException(String.format("Event with id=%s is already processed.", id))) : + Future.failedFuture(throwable); + } + + private Future handleFailuress(Throwable throwable, String id) { + LOGGER.debug("handleFailures:: Handling Failures with Id : {}", id); + return (throwable instanceof PgException && ((PgException) throwable).getCode().equals(UNIQUE_CONSTRAINT_VIOLATION_CODE)) ? + Future.failedFuture(new DuplicateEventException(String.format("Event with Id=%s is already processed.", id))) : + Future.failedFuture(throwable); + } +} diff --git a/mod-audit-server/src/main/java/org/folio/util/AcquisitionEventType.java b/mod-audit-server/src/main/java/org/folio/util/AcquisitionEventType.java index e2c9a6ec..3ecb182e 100644 --- a/mod-audit-server/src/main/java/org/folio/util/AcquisitionEventType.java +++ b/mod-audit-server/src/main/java/org/folio/util/AcquisitionEventType.java @@ -2,7 +2,8 @@ public enum AcquisitionEventType { ACQ_ORDER_CHANGED("ACQ_ORDER_CHANGED"), - ACQ_ORDER_LINE_CHANGED("ACQ_ORDER_LINE_CHANGED"); + ACQ_ORDER_LINE_CHANGED("ACQ_ORDER_LINE_CHANGED"), + ACQ_PIECE_CHANGED("ACQ_PIECE_CHANGED"); private final String topicName; diff --git a/mod-audit-server/src/main/java/org/folio/util/OrderAuditEventDBConstants.java b/mod-audit-server/src/main/java/org/folio/util/AuditEventDBConstants.java similarity index 83% rename from mod-audit-server/src/main/java/org/folio/util/OrderAuditEventDBConstants.java rename to mod-audit-server/src/main/java/org/folio/util/AuditEventDBConstants.java index 77f10d14..5278b55c 100644 --- a/mod-audit-server/src/main/java/org/folio/util/OrderAuditEventDBConstants.java +++ b/mod-audit-server/src/main/java/org/folio/util/AuditEventDBConstants.java @@ -1,10 +1,8 @@ package org.folio.util; -import org.apache.commons.lang3.StringUtils; +public class AuditEventDBConstants { -public class OrderAuditEventDBConstants { - - private OrderAuditEventDBConstants() {} + private AuditEventDBConstants() {} public static final String ID_FIELD = "id"; @@ -14,6 +12,8 @@ private OrderAuditEventDBConstants() {} public static final String ORDER_LINE_ID_FIELD = "order_line_id"; + public static final String PIECE_ID_FIELD = "order_id"; + public static final String USER_ID_FIELD = "user_id"; public static final String EVENT_DATE_FIELD = "event_date"; diff --git a/mod-audit-server/src/main/java/org/folio/verticle/acquisition/PieceEventConsumersVerticle.java b/mod-audit-server/src/main/java/org/folio/verticle/acquisition/PieceEventConsumersVerticle.java new file mode 100644 index 00000000..cc720acd --- /dev/null +++ b/mod-audit-server/src/main/java/org/folio/verticle/acquisition/PieceEventConsumersVerticle.java @@ -0,0 +1,29 @@ +package org.folio.verticle.acquisition; + +import java.util.List; + +import org.folio.kafka.AsyncRecordHandler; +import org.folio.kafka.KafkaConfig; +import org.folio.util.AcquisitionEventType; +import org.folio.verticle.AbstractConsumersVerticle; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class PieceEventConsumersVerticle extends AbstractConsumersVerticle { + + @Autowired + private KafkaConfig kafkaConfig; + @Autowired + private AsyncRecordHandler orderLineEventsHandler; + + @Override + public List getEvents() { + return List.of(AcquisitionEventType.ACQ_PIECE_CHANGED.getTopicName()); + } + + @Override + public AsyncRecordHandler getHandler() { + return orderLineEventsHandler; + } +} diff --git a/mod-audit-server/src/main/java/org/folio/verticle/acquisition/consumers/PieceEventsHandler.java b/mod-audit-server/src/main/java/org/folio/verticle/acquisition/consumers/PieceEventsHandler.java new file mode 100644 index 00000000..923ebdf9 --- /dev/null +++ b/mod-audit-server/src/main/java/org/folio/verticle/acquisition/consumers/PieceEventsHandler.java @@ -0,0 +1,59 @@ +package org.folio.verticle.acquisition.consumers; + +import java.util.List; + +import io.vertx.core.Future; +import io.vertx.core.Promise; +import io.vertx.core.Vertx; +import io.vertx.core.json.JsonObject; +import io.vertx.kafka.client.consumer.KafkaConsumerRecord; +import io.vertx.kafka.client.producer.KafkaHeader; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.folio.kafka.AsyncRecordHandler; +import org.folio.kafka.KafkaHeaderUtils; +import org.folio.kafka.exception.DuplicateEventException; +import org.folio.rest.jaxrs.model.PieceAuditEvent; +import org.folio.rest.util.OkapiConnectionParams; +import org.folio.services.acquisition.PieceAuditEventsService; +import org.springframework.stereotype.Component; + +@Component +public class PieceEventsHandler implements AsyncRecordHandler { + + private static final Logger LOGGER = LogManager.getLogger(); + + private final PieceAuditEventsService pieceAuditEventsService; + private final Vertx vertx; + + public PieceEventsHandler(PieceAuditEventsService pieceAuditEventsService, Vertx vertx) { + this.pieceAuditEventsService = pieceAuditEventsService; + this.vertx = vertx; + } + + @Override + public Future handle(KafkaConsumerRecord kafkaConsumerRecord) { + Promise result = Promise.promise(); + List kafkaHeaders = kafkaConsumerRecord.headers(); + OkapiConnectionParams okapiConnectionParams = new OkapiConnectionParams(KafkaHeaderUtils.kafkaHeadersToMap(kafkaHeaders), vertx); + PieceAuditEvent event = new JsonObject(kafkaConsumerRecord.value()).mapTo(PieceAuditEvent.class); + LOGGER.info("handle:: Starting processing of Piece audit event with id: {} for piece id: {}", event.getId(), event.getPieceId()); + + pieceAuditEventsService.savePieceAuditEvent(event, okapiConnectionParams.getTenantId()) + .onSuccess(ar -> { + LOGGER.info("handle:: Piece audit event with id: {} has been processed for piece id: {}", event.getId(), event.getPieceId()); + result.complete(event.getId()); + }) + .onFailure(e -> { + if (e instanceof DuplicateEventException) { + LOGGER.info("handle:: Duplicate Piece audit event with id: {} for piece id: {} received, skipped processing", event.getId(), event.getPieceId()); + result.complete(event.getId()); + } else { + LOGGER.error("Processing of Piece audit event with id: {} for piece id: {} has been failed", event.getId(), event.getPieceId(), e); + result.fail(e); + } + }); + + return result.future(); + } +} diff --git a/mod-audit-server/src/main/resources/templates/db_scripts/acquisition/create_acquisition_pieces_log_table.sql b/mod-audit-server/src/main/resources/templates/db_scripts/acquisition/create_acquisition_pieces_log_table.sql new file mode 100644 index 00000000..cf82a593 --- /dev/null +++ b/mod-audit-server/src/main/resources/templates/db_scripts/acquisition/create_acquisition_pieces_log_table.sql @@ -0,0 +1,11 @@ +CREATE TABLE IF NOT EXISTS acquisition_piece_log ( + id uuid PRIMARY KEY, + action text NOT NULL, + piece_id uuid NOT NULL, + user_id uuid NOT NULL, + event_date timestamp NOT NULL, + action_date timestamp NOT NULL, + modified_content_snapshot jsonb +); + +CREATE INDEX IF NOT EXISTS piece_id_index ON acquisition_piece_log USING BTREE (piece_id); diff --git a/mod-audit-server/src/test/java/org/folio/TestSuite.java b/mod-audit-server/src/test/java/org/folio/TestSuite.java index b76663c3..797a0916 100644 --- a/mod-audit-server/src/test/java/org/folio/TestSuite.java +++ b/mod-audit-server/src/test/java/org/folio/TestSuite.java @@ -1,5 +1,8 @@ package org.folio; +import static net.mguenther.kafka.junit.EmbeddedKafkaCluster.provisionWith; +import static net.mguenther.kafka.junit.EmbeddedKafkaClusterConfig.defaultClusterConfig; + import java.util.Locale; import java.util.Objects; import java.util.concurrent.CompletableFuture; @@ -7,6 +10,10 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import io.restassured.RestAssured; +import io.vertx.core.DeploymentOptions; +import io.vertx.core.Vertx; +import io.vertx.core.json.JsonObject; import net.mguenther.kafka.junit.EmbeddedKafkaCluster; import org.folio.builder.service.CheckInRecordBuilderTest; import org.folio.builder.service.CheckOutRecordBuilderTest; @@ -18,25 +25,24 @@ import org.folio.builder.service.RequestRecordBuilderTest; import org.folio.dao.OrderEventsDaoTest; import org.folio.dao.OrderLineEventsDaoTest; +import org.folio.dao.PieceEventsDaoTest; import org.folio.postgres.testing.PostgresTesterContainer; import org.folio.rest.RestVerticle; -import org.folio.rest.impl.*; import org.folio.rest.impl.AuditDataAcquisitionAPITest; +import org.folio.rest.impl.AuditDataImplApiTest; +import org.folio.rest.impl.AuditHandlersImplApiTest; +import org.folio.rest.impl.CirculationLogsImplApiTest; +import org.folio.rest.impl.OrderEventsHandlerMockTest; +import org.folio.rest.impl.OrderLineEventsHandlerMockTest; +import org.folio.rest.impl.PieceEventsHandlerMockTest; import org.folio.rest.persist.PostgresClient; import org.folio.services.OrderAuditEventsServiceTest; import org.folio.services.OrderLineAuditEventsServiceTest; +import org.folio.services.PieceAuditEventsServiceTest; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Nested; -import io.restassured.RestAssured; -import io.vertx.core.DeploymentOptions; -import io.vertx.core.Vertx; -import io.vertx.core.json.JsonObject; - -import static net.mguenther.kafka.junit.EmbeddedKafkaCluster.provisionWith; -import static net.mguenther.kafka.junit.EmbeddedKafkaClusterConfig.defaultClusterConfig; - public class TestSuite { private static final String KAFKA_HOST = "KAFKA_HOST"; private static final String KAFKA_PORT = "KAFKA_PORT"; @@ -98,7 +104,7 @@ private static void startVerticle(DeploymentOptions options) CompletableFuture deploymentComplete = new CompletableFuture<>(); vertx.deployVerticle(RestVerticle.class.getName(), options, res -> { - if(res.succeeded()) { + if (res.succeeded()) { deploymentComplete.complete(res.result()); } else { @@ -176,6 +182,18 @@ class OrderLineAuditEventsServiceNestedTest extends OrderLineAuditEventsServiceT class OrderLineEventsDaoNestedTest extends OrderLineEventsDaoTest { } + @Nested + class PieceEventsDaoNestedTest extends PieceEventsDaoTest { + } + + @Nested + class PieceAuditEventsServiceNestedTest extends PieceAuditEventsServiceTest { + } + + @Nested + class PieceEventsHandlerMockNestedTest extends PieceEventsHandlerMockTest { + } + @Nested class AuditDataImplApiTestNested extends AuditDataImplApiTest { } @@ -184,5 +202,4 @@ class AuditDataImplApiTestNested extends AuditDataImplApiTest { class CirculationLogsImplApiTestNested extends CirculationLogsImplApiTest { } - } diff --git a/mod-audit-server/src/test/java/org/folio/dao/PieceEventsDaoTest.java b/mod-audit-server/src/test/java/org/folio/dao/PieceEventsDaoTest.java new file mode 100644 index 00000000..efb23f89 --- /dev/null +++ b/mod-audit-server/src/test/java/org/folio/dao/PieceEventsDaoTest.java @@ -0,0 +1,99 @@ +package org.folio.dao; + +import static org.folio.utils.EntityUtils.TENANT_ID; +import static org.folio.utils.EntityUtils.createPieceAuditEvent; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Date; +import java.util.List; +import java.util.UUID; + +import io.vertx.core.Future; +import io.vertx.core.Vertx; +import io.vertx.core.json.JsonObject; +import io.vertx.pgclient.PgException; +import io.vertx.sqlclient.Row; +import io.vertx.sqlclient.RowSet; +import org.folio.dao.acquisition.impl.PieceEventsDaoImpl; +import org.folio.rest.jaxrs.model.PieceAuditEvent; +import org.folio.rest.jaxrs.model.PieceAuditEventCollection; +import org.folio.util.PostgresClientFactory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.MockitoAnnotations; +import org.mockito.Spy; + +public class PieceEventsDaoTest { + + @Spy + PostgresClientFactory postgresClientFactory = new PostgresClientFactory(Vertx.vertx()); + @InjectMocks + PieceEventsDaoImpl pieceEventsDao = new PieceEventsDaoImpl(postgresClientFactory); + + @BeforeEach + public void setUp() { + MockitoAnnotations.openMocks(this); + pieceEventsDao = new PieceEventsDaoImpl(postgresClientFactory); + } + + @Test + void shouldCreateEventProcessed() { + JsonObject jsonObject = new JsonObject(); + jsonObject.put("name", "Test product 123"); + + PieceAuditEvent pieceAuditEvent = new PieceAuditEvent() + .withId(UUID.randomUUID().toString()) + .withEventDate(new Date()) + .withPieceId(UUID.randomUUID().toString()) + .withActionDate(new Date()) + .withAction(PieceAuditEvent.Action.CREATE) + .withPieceSnapshot(jsonObject); + + Future> saveFuture = pieceEventsDao.save(pieceAuditEvent, TENANT_ID); + saveFuture.onComplete(ar -> { + assertTrue(ar.succeeded()); + }); + } + + @Test + void shouldThrowConstrainViolation() { + JsonObject jsonObject = new JsonObject(); + jsonObject.put("name", "Test product 123"); + + PieceAuditEvent pieceAuditEvent = new PieceAuditEvent() + .withId(UUID.randomUUID().toString()) + .withEventDate(new Date()) + .withPieceId(UUID.randomUUID().toString()) + .withActionDate(new Date()) + .withAction(PieceAuditEvent.Action.CREATE) + .withPieceSnapshot(jsonObject); + + Future> saveFuture = pieceEventsDao.save(pieceAuditEvent, TENANT_ID); + saveFuture.onComplete(ar -> { + Future> reSaveFuture = pieceEventsDao.save(pieceAuditEvent, TENANT_ID); + reSaveFuture.onComplete(re -> { + assertTrue(re.failed()); + assertTrue(re.cause() instanceof PgException); + assertEquals("ERROR: duplicate key value violates unique constraint \"acquisition_piece_log_pkey\" (23505)", re.cause().getMessage()); + }); + }); + } + + @Test + void shouldGetCreatedEvent() { + String id = UUID.randomUUID().toString(); + var pieceAuditEvent = createPieceAuditEvent(id); + + pieceEventsDao.save(pieceAuditEvent, TENANT_ID); + + Future saveFuture = pieceEventsDao.getAuditEventsByPieceId(id, "action_date", "desc", 1, 1, TENANT_ID); + saveFuture.onComplete(ar -> { + PieceAuditEventCollection pieceAuditEventCollection = ar.result(); + List pieceAuditEventList = pieceAuditEventCollection.getPieceAuditEvents(); + assertEquals(pieceAuditEventList.get(0).getId(), id); + assertEquals(PieceAuditEvent.Action.CREATE.value(), pieceAuditEventList.get(0).getAction().value()); + }); + } +} diff --git a/mod-audit-server/src/test/java/org/folio/rest/impl/AuditDataAcquisitionAPITest.java b/mod-audit-server/src/test/java/org/folio/rest/impl/AuditDataAcquisitionAPITest.java index ac7fde01..a7005250 100644 --- a/mod-audit-server/src/test/java/org/folio/rest/impl/AuditDataAcquisitionAPITest.java +++ b/mod-audit-server/src/test/java/org/folio/rest/impl/AuditDataAcquisitionAPITest.java @@ -19,6 +19,8 @@ import java.util.UUID; import static io.restassured.RestAssured.given; +import static org.folio.utils.EntityUtils.ORDER_ID; +import static org.folio.utils.EntityUtils.ORDER_LINE_ID; import static org.hamcrest.Matchers.*; public class AuditDataAcquisitionAPITest extends ApiTestBase { @@ -37,10 +39,6 @@ public class AuditDataAcquisitionAPITest extends ApiTestBase { private static final String TENANT_ID = "modaudittest"; - public static final String ORDER_ID = "a21fc51c-d46b-439b-8c79-9b2be41b79a6"; - - public static final String ORDER_LINE_ID = "a22fc51c-d46b-439b-8c79-9b2be41b79a6"; - @Spy private PostgresClientFactory postgresClientFactory = new PostgresClientFactory(Vertx.vertx()); diff --git a/mod-audit-server/src/test/java/org/folio/rest/impl/PieceEventsHandlerMockTest.java b/mod-audit-server/src/test/java/org/folio/rest/impl/PieceEventsHandlerMockTest.java new file mode 100644 index 00000000..a3c5787e --- /dev/null +++ b/mod-audit-server/src/test/java/org/folio/rest/impl/PieceEventsHandlerMockTest.java @@ -0,0 +1,94 @@ +package org.folio.rest.impl; + + +import static org.folio.kafka.KafkaTopicNameHelper.getDefaultNameSpace; +import static org.folio.utils.EntityUtils.createPieceAuditEvent; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.nio.charset.StandardCharsets; +import java.util.UUID; + +import io.vertx.core.Future; +import io.vertx.core.Vertx; +import io.vertx.core.json.Json; +import io.vertx.kafka.client.consumer.KafkaConsumerRecord; +import io.vertx.kafka.client.consumer.impl.KafkaConsumerRecordImpl; +import org.apache.kafka.clients.consumer.ConsumerRecord; +import org.apache.kafka.common.header.internals.RecordHeader; +import org.folio.dao.acquisition.impl.PieceEventsDaoImpl; +import org.folio.kafka.KafkaTopicNameHelper; +import org.folio.rest.jaxrs.model.PieceAuditEvent; +import org.folio.rest.util.OkapiConnectionParams; +import org.folio.services.acquisition.PieceAuditEventsService; +import org.folio.services.acquisition.impl.PieceAuditEventsServiceImpl; +import org.folio.util.PostgresClientFactory; +import org.folio.verticle.acquisition.consumers.PieceEventsHandler; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.Spy; + +public class PieceEventsHandlerMockTest { + private static final String TENANT_ID = "diku"; + protected static final String TOKEN = "token"; + protected static final String KAFKA_EVN = "folio"; + public static final String OKAPI_TOKEN_HEADER = "x-okapi-token"; + public static final String OKAPI_URL_HEADER = "x-okapi-url"; + + @Spy + private Vertx vertx = Vertx.vertx(); + @Spy + private PostgresClientFactory postgresClientFactory = new PostgresClientFactory(Vertx.vertx()); + + @Mock + PieceEventsDaoImpl pieceEventsDao; + @Mock + PieceAuditEventsService pieceAuditEventsService; + + private PieceEventsHandler pieceEventsHandler; + + @BeforeEach + public void setUp() throws Exception { + MockitoAnnotations.openMocks(this).close(); + pieceEventsDao = new PieceEventsDaoImpl(postgresClientFactory); + pieceAuditEventsService = new PieceAuditEventsServiceImpl(pieceEventsDao); + pieceEventsHandler = new PieceEventsHandler(pieceAuditEventsService, vertx); + } + + @Test + void shouldProcessEvent() { + var pieceAuditEvent = createPieceAuditEvent(UUID.randomUUID().toString()); + + KafkaConsumerRecord kafkaConsumerRecord = buildKafkaConsumerRecord(pieceAuditEvent); + + Future saveFuture = pieceEventsHandler.handle(kafkaConsumerRecord); + saveFuture.onComplete(are -> { + assertTrue(are.succeeded()); + }); + } + + @Test + void shouldNotProcessEvent() { + var pieceAuditEvent = createPieceAuditEvent(UUID.randomUUID().toString()); + + KafkaConsumerRecord kafkaConsumerRecord = buildKafkaConsumerRecord(pieceAuditEvent); + Future save = pieceEventsHandler.handle(kafkaConsumerRecord); + assertTrue(save.failed()); + } + + private KafkaConsumerRecord buildKafkaConsumerRecord(PieceAuditEvent event) { + String topic = KafkaTopicNameHelper.formatTopicName(KAFKA_EVN, getDefaultNameSpace(), TENANT_ID, event.getAction().name()); + ConsumerRecord consumerRecord = buildConsumerRecord(topic, event); + return new KafkaConsumerRecordImpl<>(consumerRecord); + } + + protected ConsumerRecord buildConsumerRecord(String topic, PieceAuditEvent event) { + ConsumerRecord consumer = new ConsumerRecord<>("folio", 0, 0, topic, Json.encode(event)); + consumer.headers().add(new RecordHeader(OkapiConnectionParams.OKAPI_TENANT_HEADER, TENANT_ID.getBytes(StandardCharsets.UTF_8))); + consumer.headers().add(new RecordHeader(OKAPI_URL_HEADER, ("https://localhost:" + 8080).getBytes(StandardCharsets.UTF_8))); + consumer.headers().add(new RecordHeader(OKAPI_TOKEN_HEADER, TOKEN.getBytes(StandardCharsets.UTF_8))); + return consumer; + } + +} diff --git a/mod-audit-server/src/test/java/org/folio/services/OrderAuditEventsServiceTest.java b/mod-audit-server/src/test/java/org/folio/services/OrderAuditEventsServiceTest.java index 89cc275f..2bd3904c 100644 --- a/mod-audit-server/src/test/java/org/folio/services/OrderAuditEventsServiceTest.java +++ b/mod-audit-server/src/test/java/org/folio/services/OrderAuditEventsServiceTest.java @@ -1,8 +1,15 @@ package org.folio.services; +import static org.folio.utils.EntityUtils.TENANT_ID; +import static org.folio.utils.EntityUtils.createOrderAuditEvent; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import java.util.UUID; + import io.vertx.core.Future; import io.vertx.core.Vertx; -import io.vertx.core.json.JsonObject; import io.vertx.sqlclient.Row; import io.vertx.sqlclient.RowSet; import org.folio.dao.acquisition.OrderEventsDao; @@ -16,17 +23,8 @@ import org.mockito.Mock; import org.mockito.Spy; -import java.util.Date; -import java.util.List; -import java.util.UUID; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - public class OrderAuditEventsServiceTest { - private static final String TENANT_ID = "diku"; - @Spy private PostgresClientFactory postgresClientFactory = new PostgresClientFactory(Vertx.vertx()); @Mock @@ -37,17 +35,7 @@ public class OrderAuditEventsServiceTest { @Test void shouldCallDaoForSuccessfulCase() { - JsonObject jsonObject = new JsonObject(); - jsonObject.put("name","Test Product"); - - OrderAuditEvent orderAuditEvent = new OrderAuditEvent() - .withId(UUID.randomUUID().toString()) - .withAction(OrderAuditEvent.Action.CREATE) - .withOrderId(UUID.randomUUID().toString()) - .withUserId(UUID.randomUUID().toString()) - .withEventDate(new Date()) - .withActionDate(new Date()) - .withOrderSnapshot(jsonObject); + var orderAuditEvent = createOrderAuditEvent(UUID.randomUUID().toString()); Future> saveFuture = orderAuditEventService.saveOrderAuditEvent(orderAuditEvent, TENANT_ID); saveFuture.onComplete(ar -> { @@ -57,18 +45,8 @@ void shouldCallDaoForSuccessfulCase() { @Test void shouldGetDto() { - JsonObject jsonObject = new JsonObject(); - jsonObject.put("name","Test Product"); - String id = UUID.randomUUID().toString(); - OrderAuditEvent orderAuditEvent = new OrderAuditEvent() - .withId(id) - .withAction(OrderAuditEvent.Action.CREATE) - .withOrderId(UUID.randomUUID().toString()) - .withUserId(UUID.randomUUID().toString()) - .withEventDate(new Date()) - .withActionDate(new Date()) - .withOrderSnapshot(jsonObject); + var orderAuditEvent = createOrderAuditEvent(id); orderAuditEventService.saveOrderAuditEvent(orderAuditEvent, TENANT_ID); @@ -79,7 +57,6 @@ void shouldGetDto() { assertEquals(orderAuditEventList.get(0).getId(), id); assertEquals(OrderAuditEvent.Action.CREATE.value(), orderAuditEventList.get(0).getAction().value()); - }); } diff --git a/mod-audit-server/src/test/java/org/folio/services/OrderLineAuditEventsServiceTest.java b/mod-audit-server/src/test/java/org/folio/services/OrderLineAuditEventsServiceTest.java index 3d0523d6..272c9816 100644 --- a/mod-audit-server/src/test/java/org/folio/services/OrderLineAuditEventsServiceTest.java +++ b/mod-audit-server/src/test/java/org/folio/services/OrderLineAuditEventsServiceTest.java @@ -2,7 +2,6 @@ import io.vertx.core.Future; import io.vertx.core.Vertx; -import io.vertx.core.json.JsonObject; import io.vertx.sqlclient.Row; import io.vertx.sqlclient.RowSet; import org.folio.dao.acquisition.OrderLineEventsDao; @@ -16,34 +15,27 @@ import org.mockito.Mock; import org.mockito.Spy; -import java.util.Date; import java.util.List; import java.util.UUID; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.folio.utils.EntityUtils.TENANT_ID; +import static org.folio.utils.EntityUtils.createOrderLineAuditEvent; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; public class OrderLineAuditEventsServiceTest { - private static final String TENANT_ID = "diku"; - @Spy private PostgresClientFactory postgresClientFactory = new PostgresClientFactory(Vertx.vertx()); @Mock OrderLineEventsDao orderLineEventsDao = new OrderLineEventsDaoImpl(postgresClientFactory); - @InjectMocks OrderLineAuditEventsServiceImpl orderLineAuditEventService = new OrderLineAuditEventsServiceImpl(orderLineEventsDao); @Test public void shouldCallDaoForSuccessfulCase() { - OrderLineAuditEvent orderLineAuditEvent = new OrderLineAuditEvent() - .withId(UUID.randomUUID().toString()) - .withAction(OrderLineAuditEvent.Action.CREATE) - .withOrderId(UUID.randomUUID().toString()) - .withUserId(UUID.randomUUID().toString()) - .withEventDate(new Date()) - .withActionDate(new Date()); + var orderLineAuditEvent = createOrderLineAuditEvent(UUID.randomUUID().toString()); + Future> saveFuture = orderLineAuditEventService.saveOrderLineAuditEvent(orderLineAuditEvent, TENANT_ID); saveFuture.onComplete(ar -> { @@ -53,19 +45,8 @@ public void shouldCallDaoForSuccessfulCase() { @Test void shouldGetOrderLineDto() { - JsonObject jsonObject = new JsonObject(); - jsonObject.put("name", "Test Product"); - String id = UUID.randomUUID().toString(); - OrderLineAuditEvent orderLineAuditEvent = new OrderLineAuditEvent() - .withId(id) - .withAction(OrderLineAuditEvent.Action.CREATE) - .withOrderId(UUID.randomUUID().toString()) - .withOrderLineId(UUID.randomUUID().toString()) - .withUserId(UUID.randomUUID().toString()) - .withEventDate(new Date()) - .withActionDate(new Date()) - .withOrderLineSnapshot(jsonObject); + var orderLineAuditEvent = createOrderLineAuditEvent(id); orderLineAuditEventService.saveOrderLineAuditEvent(orderLineAuditEvent, TENANT_ID); diff --git a/mod-audit-server/src/test/java/org/folio/services/PieceAuditEventsServiceTest.java b/mod-audit-server/src/test/java/org/folio/services/PieceAuditEventsServiceTest.java new file mode 100644 index 00000000..df2af446 --- /dev/null +++ b/mod-audit-server/src/test/java/org/folio/services/PieceAuditEventsServiceTest.java @@ -0,0 +1,40 @@ +package org.folio.services; + +import static org.folio.utils.EntityUtils.TENANT_ID; +import static org.folio.utils.EntityUtils.createPieceAuditEvent; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.UUID; + +import io.vertx.core.Future; +import io.vertx.core.Vertx; +import io.vertx.sqlclient.Row; +import io.vertx.sqlclient.RowSet; +import org.folio.dao.acquisition.PieceEventsDao; +import org.folio.dao.acquisition.impl.PieceEventsDaoImpl; +import org.folio.services.acquisition.impl.PieceAuditEventsServiceImpl; +import org.folio.util.PostgresClientFactory; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; + +public class PieceAuditEventsServiceTest { + + @Spy + private PostgresClientFactory postgresClientFactory = new PostgresClientFactory(Vertx.vertx()); + @Mock + PieceEventsDao pieceEventsDao = new PieceEventsDaoImpl(postgresClientFactory); + @InjectMocks + PieceAuditEventsServiceImpl pieceAuditEventsService = new PieceAuditEventsServiceImpl(pieceEventsDao); + + @Test + void shouldCallDaoSuccessfully() { + var pieceAuditEvent = createPieceAuditEvent(UUID.randomUUID().toString()); + + Future> saveFuture = pieceAuditEventsService.savePieceAuditEvent(pieceAuditEvent, TENANT_ID); + saveFuture.onComplete(ar -> { + assertTrue(ar.succeeded()); + }); + } +} diff --git a/mod-audit-server/src/test/java/org/folio/utils/EntityUtils.java b/mod-audit-server/src/test/java/org/folio/utils/EntityUtils.java new file mode 100644 index 00000000..7fe5648b --- /dev/null +++ b/mod-audit-server/src/test/java/org/folio/utils/EntityUtils.java @@ -0,0 +1,60 @@ +package org.folio.utils; + +import java.util.Date; +import java.util.UUID; + +import io.vertx.core.json.JsonObject; +import org.folio.rest.jaxrs.model.OrderAuditEvent; +import org.folio.rest.jaxrs.model.OrderLineAuditEvent; +import org.folio.rest.jaxrs.model.PieceAuditEvent; + +public class EntityUtils { + + public static String TENANT_ID = "diku"; + public static String PIECE_ID = "2cd4adc4-f287-49b6-a9c6-9eacdc4868e7"; + public static String ORDER_ID = "a21fc51c-d46b-439b-8c79-9b2be41b79a6"; + public static String ORDER_LINE_ID = "a22fc51c-d46b-439b-8c79-9b2be41b79a6"; + + public static OrderAuditEvent createOrderAuditEvent(String id) { + JsonObject jsonObject = new JsonObject(); + jsonObject.put("name","Test Product 123 "); + + return new OrderAuditEvent() + .withId(UUID.randomUUID().toString()) + .withAction(OrderAuditEvent.Action.CREATE) + .withOrderId(ORDER_ID) + .withUserId(UUID.randomUUID().toString()) + .withEventDate(new Date()) + .withActionDate(new Date()) + .withOrderSnapshot(jsonObject); + } + + public static OrderLineAuditEvent createOrderLineAuditEvent(String id) { + JsonObject jsonObject = new JsonObject(); + jsonObject.put("name", "Test Product"); + + return new OrderLineAuditEvent() + .withId(id) + .withAction(OrderLineAuditEvent.Action.CREATE) + .withOrderId(ORDER_ID) + .withOrderLineId(ORDER_LINE_ID) + .withUserId(UUID.randomUUID().toString()) + .withEventDate(new Date()) + .withActionDate(new Date()) + .withOrderLineSnapshot(jsonObject); + } + + public static PieceAuditEvent createPieceAuditEvent(String id) { + JsonObject jsonObject = new JsonObject(); + jsonObject.put("name", "Test Product"); + + return new PieceAuditEvent() + .withId(id) + .withAction(PieceAuditEvent.Action.CREATE) + .withPieceId(PIECE_ID) + .withUserId(UUID.randomUUID().toString()) + .withEventDate(new Date()) + .withActionDate(new Date()) + .withPieceSnapshot(jsonObject); + } +} diff --git a/ramls/acquisition-events.raml b/ramls/acquisition-events.raml index 3f9ce2b5..19b1ec4b 100644 --- a/ramls/acquisition-events.raml +++ b/ramls/acquisition-events.raml @@ -15,6 +15,8 @@ types: order-audit-event: !include order_audit_event.json order-audit-event-collection: !include order_audit_event_collection.json order-line-audit-event-collection: !include order_line_audit_event_collection.json + piece-audit-event: !include piece_audit_event.json + piece-audit-event-collection: !include piece_audit_event_collection.json traits: searchable: !include raml-util/traits/searchable.raml @@ -88,3 +90,38 @@ traits: body: application/json: type: errors + + /piece/{id}: + get: + description: Get list of piece events by piece_id + is: [ + pageable, + validate + ] + queryParameters: + sortBy: + description: "sorting by field: actionDate" + type: string + default: action_date + sortOrder: + description: "sort order: asc or desc" + enum: [asc, desc] + type: string + default: desc + limit: + default: 2147483647 + offset: + default: 0 + responses: + 200: + body: + application/json: + type: piece-audit-event-collection + 500: + description: "Internal server error" + body: + application/json: + type: errors + example: + strict: false + value: !include raml-util/examples/errors.sample diff --git a/ramls/piece_audit_event.json b/ramls/piece_audit_event.json new file mode 100644 index 00000000..a1d5141e --- /dev/null +++ b/ramls/piece_audit_event.json @@ -0,0 +1,40 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Piece audit event", + "type": "object", + "properties": { + "id": { + "description": "UUID of the event", + "$ref": "common/uuid.json" + }, + "action": { + "description": "Action for piece (Create, Edit or Delete)", + "type": "string", + "$ref": "event_action.json" + }, + "pieceId": { + "description": "UUID of the piece", + "$ref": "common/uuid.json" + }, + "userId": { + "description": "UUID of the user who performed the action", + "$ref": "common/uuid.json" + }, + "eventDate": { + "description": "Date time when event triggered", + "format": "date-time", + "type": "string" + }, + "actionDate": { + "description": "Date time when piece action occurred", + "format": "date-time", + "type": "string" + }, + "pieceSnapshot": { + "description": "Full snapshot of the piece", + "type": "object", + "javaType": "java.lang.Object" + } + }, + "additionalProperties": false +} diff --git a/ramls/piece_audit_event_collection.json b/ramls/piece_audit_event_collection.json new file mode 100644 index 00000000..6183d57d --- /dev/null +++ b/ramls/piece_audit_event_collection.json @@ -0,0 +1,25 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Collection of pieceLineAuditEvents", + "type": "object", + "additionalProperties": false, + "properties": { + "pieceAuditEvents": { + "description": "List of pieceAuditEvents", + "type": "array", + "id": "pieceAuditEventsList", + "items": { + "type": "object", + "$ref": "piece_audit_event.json" + } + }, + "totalItems": { + "description": "total records", + "type": "integer" + } + }, + "required": [ + "pieceAuditEvents", + "totalItems" + ] +}