From ad3886ac6bd99ba9a98a93dd91c529ffff15fe77 Mon Sep 17 00:00:00 2001 From: Janis Saldabols Date: Thu, 9 Oct 2025 10:19:31 +0300 Subject: [PATCH 01/10] CROSSLINK-173 Add patron request API and DB --- broker/patron_request/db/rprepo.go | 56 ++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 broker/patron_request/db/rprepo.go diff --git a/broker/patron_request/db/rprepo.go b/broker/patron_request/db/rprepo.go new file mode 100644 index 00000000..468e54f5 --- /dev/null +++ b/broker/patron_request/db/rprepo.go @@ -0,0 +1,56 @@ +package pr_db + +import ( + "github.com/indexdata/crosslink/broker/common" + "github.com/indexdata/crosslink/broker/repo" +) + +type PrRepo interface { + repo.Transactional[PrRepo] + GetPatronRequestById(ctx common.ExtendedContext, id string) (PatronRequest, error) + ListPatronRequests(ctx common.ExtendedContext) ([]PatronRequest, error) + SavePatronRequest(ctx common.ExtendedContext, params SavePatronRequestParams) (PatronRequest, error) + DeletePatronRequest(ctx common.ExtendedContext, id string) error +} + +type PgPrRepo struct { + repo.PgBaseRepo[PrRepo] + queries Queries +} + +// delegate transaction handling to Base +func (r *PgPrRepo) WithTxFunc(ctx common.ExtendedContext, fn func(PrRepo) error) error { + return r.PgBaseRepo.WithTxFunc(ctx, r, fn) +} + +// DerivedRepo +func (r *PgPrRepo) CreateWithPgBaseRepo(base *repo.PgBaseRepo[PrRepo]) PrRepo { + rpRepo := new(PgPrRepo) + rpRepo.PgBaseRepo = *base + return rpRepo +} + +func (r *PgPrRepo) GetPatronRequestById(ctx common.ExtendedContext, id string) (PatronRequest, error) { + row, err := r.queries.GetPatronRequestById(ctx, r.GetConnOrTx(), id) + return row.PatronRequest, err +} + +func (r *PgPrRepo) ListPatronRequests(ctx common.ExtendedContext) ([]PatronRequest, error) { + rows, err := r.queries.ListPatronRequests(ctx, r.GetConnOrTx()) + var list []PatronRequest + if err == nil { + for _, r := range rows { + list = append(list, r.PatronRequest) + } + } + return list, err +} + +func (r *PgPrRepo) SavePatronRequest(ctx common.ExtendedContext, params SavePatronRequestParams) (PatronRequest, error) { + row, err := r.queries.SavePatronRequest(ctx, r.GetConnOrTx(), params) + return row.PatronRequest, err +} + +func (r *PgPrRepo) DeletePatronRequest(ctx common.ExtendedContext, id string) error { + return r.queries.DeletePatronRequest(ctx, r.GetConnOrTx(), id) +} From 4affb1a9720696d1e4c32466ada6612988365495 Mon Sep 17 00:00:00 2001 From: Janis Saldabols Date: Fri, 10 Oct 2025 10:22:32 +0300 Subject: [PATCH 02/10] CROSSLINK-173 Fix naming --- broker/patron_request/db/rprepo.go | 56 ------------------------------ 1 file changed, 56 deletions(-) delete mode 100644 broker/patron_request/db/rprepo.go diff --git a/broker/patron_request/db/rprepo.go b/broker/patron_request/db/rprepo.go deleted file mode 100644 index 468e54f5..00000000 --- a/broker/patron_request/db/rprepo.go +++ /dev/null @@ -1,56 +0,0 @@ -package pr_db - -import ( - "github.com/indexdata/crosslink/broker/common" - "github.com/indexdata/crosslink/broker/repo" -) - -type PrRepo interface { - repo.Transactional[PrRepo] - GetPatronRequestById(ctx common.ExtendedContext, id string) (PatronRequest, error) - ListPatronRequests(ctx common.ExtendedContext) ([]PatronRequest, error) - SavePatronRequest(ctx common.ExtendedContext, params SavePatronRequestParams) (PatronRequest, error) - DeletePatronRequest(ctx common.ExtendedContext, id string) error -} - -type PgPrRepo struct { - repo.PgBaseRepo[PrRepo] - queries Queries -} - -// delegate transaction handling to Base -func (r *PgPrRepo) WithTxFunc(ctx common.ExtendedContext, fn func(PrRepo) error) error { - return r.PgBaseRepo.WithTxFunc(ctx, r, fn) -} - -// DerivedRepo -func (r *PgPrRepo) CreateWithPgBaseRepo(base *repo.PgBaseRepo[PrRepo]) PrRepo { - rpRepo := new(PgPrRepo) - rpRepo.PgBaseRepo = *base - return rpRepo -} - -func (r *PgPrRepo) GetPatronRequestById(ctx common.ExtendedContext, id string) (PatronRequest, error) { - row, err := r.queries.GetPatronRequestById(ctx, r.GetConnOrTx(), id) - return row.PatronRequest, err -} - -func (r *PgPrRepo) ListPatronRequests(ctx common.ExtendedContext) ([]PatronRequest, error) { - rows, err := r.queries.ListPatronRequests(ctx, r.GetConnOrTx()) - var list []PatronRequest - if err == nil { - for _, r := range rows { - list = append(list, r.PatronRequest) - } - } - return list, err -} - -func (r *PgPrRepo) SavePatronRequest(ctx common.ExtendedContext, params SavePatronRequestParams) (PatronRequest, error) { - row, err := r.queries.SavePatronRequest(ctx, r.GetConnOrTx(), params) - return row.PatronRequest, err -} - -func (r *PgPrRepo) DeletePatronRequest(ctx common.ExtendedContext, id string) error { - return r.queries.DeletePatronRequest(ctx, r.GetConnOrTx(), id) -} From 2334817735a2091e7d72ec8dcff3b741f3ffbfd4 Mon Sep 17 00:00:00 2001 From: Janis Saldabols Date: Thu, 23 Oct 2025 07:42:58 +0300 Subject: [PATCH 03/10] Allow patron request events --- broker/events/eventbus.go | 44 +++++++++++++------ broker/events/eventmodels.go | 7 +++ broker/handler/iso18626-handler.go | 4 +- .../013_allow_patron_request_events.down.sql | 3 ++ .../013_allow_patron_request_events.up.sql | 6 +++ broker/service/workflow.go | 28 ++++++------ broker/service/workflow_test.go | 4 +- broker/sqlc/event_query.sql | 7 +-- broker/sqlc/event_schema.sql | 1 + broker/test/client/client_test.go | 8 ++-- broker/test/events/eventbus_test.go | 12 ++--- 11 files changed, 79 insertions(+), 45 deletions(-) create mode 100644 broker/migrations/013_allow_patron_request_events.down.sql create mode 100644 broker/migrations/013_allow_patron_request_events.up.sql diff --git a/broker/events/eventbus.go b/broker/events/eventbus.go index 10012a55..cb490517 100644 --- a/broker/events/eventbus.go +++ b/broker/events/eventbus.go @@ -21,10 +21,10 @@ const EB_COMP = "event_bus" type EventBus interface { Start(ctx common.ExtendedContext) error - CreateTask(illTransactionID string, eventName EventName, data EventData, parentId *string) (string, error) - CreateTaskBroadcast(illTransactionID string, eventName EventName, data EventData, parentId *string) (string, error) - CreateNotice(illTransactionID string, eventName EventName, data EventData, status EventStatus) (string, error) - CreateNoticeBroadcast(illTransactionID string, eventName EventName, data EventData, status EventStatus) (string, error) + CreateTask(illTransactionID string, eventName EventName, data EventData, eventClass EventClass, parentId *string) (string, error) + CreateTaskBroadcast(illTransactionID string, eventName EventName, data EventData, eventClass EventClass, parentId *string) (string, error) + CreateNotice(illTransactionID string, eventName EventName, data EventData, status EventStatus, eventClass EventClass) (string, error) + CreateNoticeBroadcast(illTransactionID string, eventName EventName, data EventData, status EventStatus, eventClass EventClass) (string, error) BeginTask(eventId string) (Event, error) CompleteTask(eventId string, result *EventResult, status EventStatus) (Event, error) HandleEventCreated(eventName EventName, f func(ctx common.ExtendedContext, event Event)) @@ -183,16 +183,23 @@ func triggerHandlers(eventCtx common.ExtendedContext, event Event, handlersMap m eventCtx.Logger().Debug("all handlers finished", "eventName", event.EventName, "signal", signal) } -func (p *PostgresEventBus) CreateTask(illTransactionID string, eventName EventName, data EventData, parentId *string) (string, error) { - return p.createTask(illTransactionID, eventName, data, parentId, false) +func (p *PostgresEventBus) CreateTask(classId string, eventName EventName, data EventData, eventClass EventClass, parentId *string) (string, error) { + return p.createTask(classId, eventName, data, eventClass, parentId, false) } -func (p *PostgresEventBus) CreateTaskBroadcast(illTransactionID string, eventName EventName, data EventData, parentId *string) (string, error) { - return p.createTask(illTransactionID, eventName, data, parentId, true) +func (p *PostgresEventBus) CreateTaskBroadcast(illTransactionID string, eventName EventName, data EventData, eventClass EventClass, parentId *string) (string, error) { + return p.createTask(illTransactionID, eventName, data, eventClass, parentId, true) } -func (p *PostgresEventBus) createTask(illTransactionID string, eventName EventName, data EventData, parentId *string, broadcast bool) (string, error) { +func (p *PostgresEventBus) createTask(classId string, eventName EventName, data EventData, eventClass EventClass, parentId *string, broadcast bool) (string, error) { id := uuid.New().String() + illTransactionID := "" + patronRequestID := "" + if eventClass == EventClassPatronRequest { + illTransactionID = classId + } else { + patronRequestID = classId + } return id, p.repo.WithTxFunc(p.ctx, func(eventRepo EventRepo) error { event, err := eventRepo.SaveEvent(p.ctx, SaveEventParams{ ID: id, @@ -205,6 +212,7 @@ func (p *PostgresEventBus) createTask(illTransactionID string, eventName EventNa ParentID: getPgText(parentId), LastSignal: string(SignalTaskCreated), Broadcast: broadcast, + PatronRequestID: patronRequestID, }) if err != nil && event.ParentID.Valid { return err @@ -215,16 +223,23 @@ func (p *PostgresEventBus) createTask(illTransactionID string, eventName EventNa }) } -func (p *PostgresEventBus) CreateNotice(illTransactionID string, eventName EventName, data EventData, status EventStatus) (string, error) { - return p.createNotice(illTransactionID, eventName, data, status, false) +func (p *PostgresEventBus) CreateNotice(classId string, eventName EventName, data EventData, status EventStatus, eventClass EventClass) (string, error) { + return p.createNotice(classId, eventName, data, status, eventClass, false) } -func (p *PostgresEventBus) CreateNoticeBroadcast(illTransactionID string, eventName EventName, data EventData, status EventStatus) (string, error) { - return p.createNotice(illTransactionID, eventName, data, status, true) +func (p *PostgresEventBus) CreateNoticeBroadcast(classId string, eventName EventName, data EventData, status EventStatus, eventClass EventClass) (string, error) { + return p.createNotice(classId, eventName, data, status, eventClass, true) } -func (p *PostgresEventBus) createNotice(illTransactionID string, eventName EventName, data EventData, status EventStatus, broadcast bool) (string, error) { +func (p *PostgresEventBus) createNotice(classId string, eventName EventName, data EventData, status EventStatus, eventClass EventClass, broadcast bool) (string, error) { id := uuid.New().String() + illTransactionID := "" + patronRequestID := "" + if eventClass == EventClassPatronRequest { + illTransactionID = classId + } else { + patronRequestID = classId + } return id, p.repo.WithTxFunc(p.ctx, func(eventRepo EventRepo) error { event, err := eventRepo.SaveEvent(p.ctx, SaveEventParams{ ID: id, @@ -236,6 +251,7 @@ func (p *PostgresEventBus) createNotice(illTransactionID string, eventName Event EventData: data, LastSignal: string(SignalNoticeCreated), Broadcast: broadcast, + PatronRequestID: patronRequestID, }) if err != nil { return err diff --git a/broker/events/eventmodels.go b/broker/events/eventmodels.go index cf32fb28..b70809d5 100644 --- a/broker/events/eventmodels.go +++ b/broker/events/eventmodels.go @@ -22,6 +22,13 @@ const ( EventTypeNotice EventType = "NOTICE" ) +type EventClass string + +const ( + EventClassPatronRequest EventClass = "PATRON_REQUEST" + EventClassIllTransaction EventClass = "ILL_TRANSACTION" +) + type EventName string const ( diff --git a/broker/handler/iso18626-handler.go b/broker/handler/iso18626-handler.go index 7b9bb1af..b480543e 100644 --- a/broker/handler/iso18626-handler.go +++ b/broker/handler/iso18626-handler.go @@ -711,7 +711,7 @@ func handleSupplyingAgencyErrorWithNotice(ctx common.ExtendedContext, w http.Res }, }, } - _, err := eventBus.CreateNotice(illTransId, events.EventNameSupplierMsgReceived, eventData, events.EventStatusProblem) + _, err := eventBus.CreateNotice(illTransId, events.EventNameSupplierMsgReceived, eventData, events.EventStatusProblem, events.EventClassIllTransaction) if err != nil { ctx.Logger().Error(InternalFailedToCreateNotice, "error", err, "transactionId", illTransId) http.Error(w, PublicFailedToProcessReqMsg, http.StatusInternalServerError) @@ -721,7 +721,7 @@ func handleSupplyingAgencyErrorWithNotice(ctx common.ExtendedContext, w http.Res } func createNoticeAndCheckDBError(ctx common.ExtendedContext, w http.ResponseWriter, eventBus events.EventBus, illTransId string, eventName events.EventName, eventData events.EventData, eventStatus events.EventStatus) string { - id, err := eventBus.CreateNotice(illTransId, eventName, eventData, eventStatus) + id, err := eventBus.CreateNotice(illTransId, eventName, eventData, eventStatus, events.EventClassIllTransaction) if err != nil { ctx.Logger().Error(InternalFailedToCreateNotice, "error", err, "transactionId", illTransId) http.Error(w, PublicFailedToProcessReqMsg, http.StatusInternalServerError) diff --git a/broker/migrations/013_allow_patron_request_events.down.sql b/broker/migrations/013_allow_patron_request_events.down.sql new file mode 100644 index 00000000..8080d980 --- /dev/null +++ b/broker/migrations/013_allow_patron_request_events.down.sql @@ -0,0 +1,3 @@ +ALTER TABLE event DROP COLUMN patron_request_id; + +DROP INDEX IF EXISTS event_patron_request_id_idx; \ No newline at end of file diff --git a/broker/migrations/013_allow_patron_request_events.up.sql b/broker/migrations/013_allow_patron_request_events.up.sql new file mode 100644 index 00000000..b3290e29 --- /dev/null +++ b/broker/migrations/013_allow_patron_request_events.up.sql @@ -0,0 +1,6 @@ +ALTER TABLE event DROP CONSTRAINT event_ill_transaction_id_fkey; + +ALTER TABLE event ADD COLUMN patron_request_id VARCHAR NOT NULL DEFAULT ''; + +CREATE INDEX IF NOT EXISTS event_ill_transaction_id_idx ON event (ill_transaction_id); +CREATE INDEX IF NOT EXISTS event_patron_request_id_idx ON event (patron_request_id); \ No newline at end of file diff --git a/broker/service/workflow.go b/broker/service/workflow.go index 83e6fa1b..00279557 100644 --- a/broker/service/workflow.go +++ b/broker/service/workflow.go @@ -38,7 +38,7 @@ func CreateWorkflowManager(eventBus events.EventBus, illRepo ill_db.IllRepo, con func (w *WorkflowManager) RequestReceived(ctx common.ExtendedContext, event events.Event) { ctx = ctx.WithArgs(ctx.LoggerArgs().WithComponent(WF_COMP)) common.Must(ctx, func() (string, error) { - return w.eventBus.CreateTask(event.IllTransactionID, events.EventNameLocateSuppliers, events.EventData{}, &event.ID) + return w.eventBus.CreateTask(event.IllTransactionID, events.EventNameLocateSuppliers, events.EventData{}, events.EventClassIllTransaction, &event.ID) }, "") } @@ -46,9 +46,9 @@ func (w *WorkflowManager) OnLocateSupplierComplete(ctx common.ExtendedContext, e ctx = ctx.WithArgs(ctx.LoggerArgs().WithComponent(WF_COMP)) common.Must(ctx, func() (string, error) { if event.EventStatus == events.EventStatusSuccess { - return w.eventBus.CreateTask(event.IllTransactionID, events.EventNameSelectSupplier, events.EventData{}, &event.ID) + return w.eventBus.CreateTask(event.IllTransactionID, events.EventNameSelectSupplier, events.EventData{}, events.EventClassIllTransaction, &event.ID) } else { - return w.eventBus.CreateTask(event.IllTransactionID, events.EventNameMessageRequester, events.EventData{}, &event.ID) + return w.eventBus.CreateTask(event.IllTransactionID, events.EventNameMessageRequester, events.EventData{}, events.EventClassIllTransaction, &event.ID) } }, "") } @@ -63,14 +63,14 @@ func (w *WorkflowManager) OnSelectSupplierComplete(ctx common.ExtendedContext, e return "", fmt.Errorf("failed to process supplier selected event, no requester") } if requester.BrokerMode == string(common.BrokerModeTransparent) || requester.BrokerMode == string(common.BrokerModeTranslucent) { - id, err := w.eventBus.CreateTask(event.IllTransactionID, events.EventNameMessageRequester, events.EventData{}, &event.ID) + id, err := w.eventBus.CreateTask(event.IllTransactionID, events.EventNameMessageRequester, events.EventData{}, events.EventClassIllTransaction, &event.ID) if err != nil { return id, err } } if local, ok := event.ResultData.CustomData["localSupplier"].(bool); ok { if !local { - return w.eventBus.CreateTask(event.IllTransactionID, events.EventNameMessageSupplier, events.EventData{}, &event.ID) + return w.eventBus.CreateTask(event.IllTransactionID, events.EventNameMessageSupplier, events.EventData{}, events.EventClassIllTransaction, &event.ID) } else { return "", nil } @@ -78,7 +78,7 @@ func (w *WorkflowManager) OnSelectSupplierComplete(ctx common.ExtendedContext, e return "", fmt.Errorf("failed to detect local supplier from event result data") } } else { - return w.eventBus.CreateTask(event.IllTransactionID, events.EventNameMessageRequester, events.EventData{}, &event.ID) + return w.eventBus.CreateTask(event.IllTransactionID, events.EventNameMessageRequester, events.EventData{}, events.EventClassIllTransaction, &event.ID) } }, "") } @@ -95,14 +95,14 @@ func (w *WorkflowManager) SupplierMessageReceived(ctx common.ExtendedContext, ev if w.handleAndCheckCancelResponse(ctx, *event.EventData.IncomingMessage.SupplyingAgencyMessage, event.IllTransactionID) { common.Must(ctx, func() (string, error) { return w.eventBus.CreateTask(event.IllTransactionID, events.EventNameMessageRequester, - events.EventData{CommonEventData: events.CommonEventData{IncomingMessage: event.EventData.IncomingMessage}, CustomData: map[string]any{common.DO_NOT_SEND: !w.shouldForwardMessage(ctx, event)}}, &event.ID) + events.EventData{CommonEventData: events.CommonEventData{IncomingMessage: event.EventData.IncomingMessage}, CustomData: map[string]any{common.DO_NOT_SEND: !w.shouldForwardMessage(ctx, event)}}, events.EventClassIllTransaction, &event.ID) }, "") } else { common.Must(ctx, func() (string, error) { - return w.eventBus.CreateTaskBroadcast(event.IllTransactionID, events.EventNameConfirmSupplierMsg, events.EventData{}, &event.ID) + return w.eventBus.CreateTaskBroadcast(event.IllTransactionID, events.EventNameConfirmSupplierMsg, events.EventData{}, events.EventClassIllTransaction, &event.ID) }, "") common.Must(ctx, func() (string, error) { // This will also send unfilled message if no more suppliers - return w.eventBus.CreateTask(event.IllTransactionID, events.EventNameSelectSupplier, events.EventData{}, &event.ID) + return w.eventBus.CreateTask(event.IllTransactionID, events.EventNameSelectSupplier, events.EventData{}, events.EventClassIllTransaction, &event.ID) }, "") } } @@ -112,7 +112,7 @@ func (w *WorkflowManager) RequesterMessageReceived(ctx common.ExtendedContext, e if event.EventStatus == events.EventStatusSuccess { common.Must(ctx, func() (string, error) { return w.eventBus.CreateTask(event.IllTransactionID, events.EventNameMessageSupplier, - events.EventData{CommonEventData: events.CommonEventData{IncomingMessage: event.EventData.IncomingMessage}, CustomData: map[string]any{common.DO_NOT_SEND: !w.shouldForwardMessage(ctx, event)}}, &event.ID) + events.EventData{CommonEventData: events.CommonEventData{IncomingMessage: event.EventData.IncomingMessage}, CustomData: map[string]any{common.DO_NOT_SEND: !w.shouldForwardMessage(ctx, event)}}, events.EventClassIllTransaction, &event.ID) }, "") } } @@ -127,12 +127,12 @@ func (w *WorkflowManager) OnMessageSupplierComplete(ctx common.ExtendedContext, if event.EventData.IncomingMessage != nil && event.EventData.IncomingMessage.RequestingAgencyMessage != nil { // action message was send by requester so we must relay the confirmation common.Must(ctx, func() (string, error) { - return w.eventBus.CreateTaskBroadcast(event.IllTransactionID, events.EventNameConfirmRequesterMsg, events.EventData{}, &event.ID) + return w.eventBus.CreateTaskBroadcast(event.IllTransactionID, events.EventNameConfirmRequesterMsg, events.EventData{}, events.EventClassIllTransaction, &event.ID) }, "") } else if event.EventStatus != events.EventStatusSuccess { // if the last requester action was Request and messaging supplier failed, we try next supplier common.Must(ctx, func() (string, error) { - return w.eventBus.CreateTask(event.IllTransactionID, events.EventNameSelectSupplier, events.EventData{}, &event.ID) + return w.eventBus.CreateTask(event.IllTransactionID, events.EventNameSelectSupplier, events.EventData{}, events.EventClassIllTransaction, &event.ID) }, "") } } @@ -170,11 +170,11 @@ func (w *WorkflowManager) OnMessageRequesterComplete(ctx common.ExtendedContext, if event.EventData.IncomingMessage != nil && event.EventData.IncomingMessage.SupplyingAgencyMessage != nil { // action message was send by supplier so we must relay the confirmation common.Must(ctx, func() (string, error) { - return w.eventBus.CreateTaskBroadcast(event.IllTransactionID, events.EventNameConfirmSupplierMsg, events.EventData{}, &event.ID) + return w.eventBus.CreateTaskBroadcast(event.IllTransactionID, events.EventNameConfirmSupplierMsg, events.EventData{}, events.EventClassIllTransaction, &event.ID) }, "") if event.EventData.IncomingMessage.SupplyingAgencyMessage.StatusInfo.Status == iso18626.TypeStatusUnfilled { common.Must(ctx, func() (string, error) { - return w.eventBus.CreateTask(event.IllTransactionID, events.EventNameSelectSupplier, events.EventData{}, &event.ID) + return w.eventBus.CreateTask(event.IllTransactionID, events.EventNameSelectSupplier, events.EventData{}, events.EventClassIllTransaction, &event.ID) }, "") } if cancelSuccessful(*event.EventData.IncomingMessage.SupplyingAgencyMessage) { diff --git a/broker/service/workflow_test.go b/broker/service/workflow_test.go index c0d407b8..3ec0f0d0 100644 --- a/broker/service/workflow_test.go +++ b/broker/service/workflow_test.go @@ -304,11 +304,11 @@ type MockEventBus struct { BroadcastCreated int } -func (r *MockEventBus) CreateTask(illTransactionID string, eventName events.EventName, data events.EventData, parentId *string) (string, error) { +func (r *MockEventBus) CreateTask(illTransactionID string, eventName events.EventName, data events.EventData, eventClass events.EventClass, parentId *string) (string, error) { r.TasksCreated++ return "id1", nil } -func (r *MockEventBus) CreateTaskBroadcast(illTransactionID string, eventName events.EventName, data events.EventData, parentId *string) (string, error) { +func (r *MockEventBus) CreateTaskBroadcast(illTransactionID string, eventName events.EventName, data events.EventData, eventClass events.EventClass, parentId *string) (string, error) { r.BroadcastCreated++ return "id2", nil } diff --git a/broker/sqlc/event_query.sql b/broker/sqlc/event_query.sql index ee5cfde2..d7791006 100644 --- a/broker/sqlc/event_query.sql +++ b/broker/sqlc/event_query.sql @@ -42,9 +42,9 @@ ORDER BY timestamp; -- name: SaveEvent :one INSERT INTO event ( - id, timestamp, ill_transaction_id, parent_id, event_type, event_name, event_status, event_data, result_data, last_signal, broadcast + id, timestamp, ill_transaction_id, parent_id, event_type, event_name, event_status, event_data, result_data, last_signal, broadcast, patron_request_id ) VALUES ( - $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11 + $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12 ) ON CONFLICT (id) DO UPDATE SET timestamp = EXCLUDED.timestamp, @@ -56,7 +56,8 @@ ON CONFLICT (id) DO UPDATE event_data = EXCLUDED.event_data, result_data = EXCLUDED.result_data, last_signal = EXCLUDED.last_signal, - broadcast = EXCLUDED.broadcast + broadcast = EXCLUDED.broadcast, + patron_request_id = EXCLUDED.patron_request_id RETURNING sqlc.embed(event); -- name: DeleteEvent :exec diff --git a/broker/sqlc/event_schema.sql b/broker/sqlc/event_schema.sql index 4206e890..587ecf4e 100644 --- a/broker/sqlc/event_schema.sql +++ b/broker/sqlc/event_schema.sql @@ -18,6 +18,7 @@ CREATE TABLE event result_data jsonb, last_signal VARCHAR NOT NULL, broadcast BOOLEAN NOT NULL DEFAULT FALSE, + patron_request_id VARCHAR NOT NULL, FOREIGN KEY (ill_transaction_id) REFERENCES ill_transaction (id), FOREIGN KEY (event_name) REFERENCES event_config (event_name) ); diff --git a/broker/test/client/client_test.go b/broker/test/client/client_test.go index dc293be4..9f4c1662 100644 --- a/broker/test/client/client_test.go +++ b/broker/test/client/client_test.go @@ -584,7 +584,7 @@ func TestRequestLocallyAvailable(t *testing.T) { } selSup, err = illRepo.SaveLocatedSupplier(appCtx, ill_db.SaveLocatedSupplierParams(selSup)) assert.NoError(t, err) - _, err = eventBus.CreateNotice(illTrans.ID, events.EventNameRequesterMsgReceived, events.EventData{}, events.EventStatusSuccess) + _, err = eventBus.CreateNotice(illTrans.ID, events.EventNameRequesterMsgReceived, events.EventData{}, events.EventStatusSuccess, events.EventClassIllTransaction) assert.NoError(t, err) _, err = eventBus.CreateNotice(illTrans.ID, events.EventNameSupplierMsgReceived, events.EventData{ CommonEventData: events.CommonEventData{ @@ -596,7 +596,7 @@ func TestRequestLocallyAvailable(t *testing.T) { }, }, }, - }, events.EventStatusSuccess) + }, events.EventStatusSuccess, events.EventClassIllTransaction) assert.NoError(t, err) assert.Equal(t, "NOTICE, request-received = SUCCESS\n"+ @@ -649,7 +649,7 @@ func TestRequestLocallyAvailable(t *testing.T) { } selSup, err = illRepo.SaveLocatedSupplier(appCtx, ill_db.SaveLocatedSupplierParams(selSup)) assert.NoError(t, err) - _, err = eventBus.CreateNotice(illTrans.ID, events.EventNameRequesterMsgReceived, events.EventData{}, events.EventStatusSuccess) + _, err = eventBus.CreateNotice(illTrans.ID, events.EventNameRequesterMsgReceived, events.EventData{}, events.EventStatusSuccess, events.EventClassIllTransaction) assert.NoError(t, err) _, err = eventBus.CreateNotice(illTrans.ID, events.EventNameSupplierMsgReceived, events.EventData{ CommonEventData: events.CommonEventData{ @@ -661,7 +661,7 @@ func TestRequestLocallyAvailable(t *testing.T) { }, }, }, - }, events.EventStatusSuccess) + }, events.EventStatusSuccess, events.EventClassIllTransaction) assert.NoError(t, err) assert.Equal(t, "NOTICE, request-received = SUCCESS\n"+ diff --git a/broker/test/events/eventbus_test.go b/broker/test/events/eventbus_test.go index 880b5418..fdc54561 100644 --- a/broker/test/events/eventbus_test.go +++ b/broker/test/events/eventbus_test.go @@ -94,7 +94,7 @@ func TestMultipleEventHandlers(t *testing.T) { for i := 0; i < noEvents; i++ { illId := apptest.GetIllTransId(t, illRepo) - _, err := eventBus.CreateTask(illId, events.EventNameRequestReceived, events.EventData{}, nil) + _, err := eventBus.CreateTask(illId, events.EventNameRequestReceived, events.EventData{}, events.EventClassIllTransaction, nil) assert.NoError(t, err, "Task should be created without errors") } @@ -151,7 +151,7 @@ func TestBroadcastEventHandlers(t *testing.T) { for i := 0; i < noEvents; i++ { illId := apptest.GetIllTransId(t, illRepo) - _, err := eventBus.CreateTaskBroadcast(illId, events.EventNameConfirmRequesterMsg, events.EventData{}, nil) + _, err := eventBus.CreateTaskBroadcast(illId, events.EventNameConfirmRequesterMsg, events.EventData{}, events.EventClassIllTransaction, nil) assert.NoError(t, err, "Task should be created without errors") } @@ -188,7 +188,7 @@ func TestCreateTask(t *testing.T) { }) illId := apptest.GetIllTransId(t, illRepo) - _, err := eventBus.CreateTask(illId, events.EventNameRequestReceived, events.EventData{}, nil) + _, err := eventBus.CreateTask(illId, events.EventNameRequestReceived, events.EventData{}, events.EventClassIllTransaction, nil) if err != nil { t.Errorf("Task should be created without errors: %s", err) } @@ -248,7 +248,7 @@ func TestCreateNotice(t *testing.T) { illId := apptest.GetIllTransId(t, illRepo) - _, err := eventBus.CreateNotice(illId, events.EventNameSupplierMsgReceived, events.EventData{}, events.EventStatusSuccess) + _, err := eventBus.CreateNotice(illId, events.EventNameSupplierMsgReceived, events.EventData{}, events.EventStatusSuccess, events.EventClassIllTransaction) if err != nil { t.Errorf("Task should be created without errors: %s", err) } @@ -284,7 +284,7 @@ func TestBeginAndCompleteTask(t *testing.T) { illId := apptest.GetIllTransId(t, illRepo) - _, err := eventBus.CreateTask(illId, events.EventNameRequestReceived, events.EventData{}, nil) + _, err := eventBus.CreateTask(illId, events.EventNameRequestReceived, events.EventData{}, events.EventClassIllTransaction, nil) if err != nil { t.Errorf("Task should be created without errors: %s", err) } @@ -426,7 +426,7 @@ func TestReconnectListener(t *testing.T) { illId := apptest.GetIllTransId(t, illRepo) - _, err := eventBus.CreateNotice(illId, events.EventNameSupplierMsgReceived, events.EventData{}, events.EventStatusSuccess) + _, err := eventBus.CreateNotice(illId, events.EventNameSupplierMsgReceived, events.EventData{}, events.EventStatusSuccess, events.EventClassIllTransaction) if err != nil { t.Errorf("Task should be created without errors: %s", err) } From c322e048407ee1db08cf844c6137182f22aeca17 Mon Sep 17 00:00:00 2001 From: Janis Saldabols Date: Thu, 6 Nov 2025 13:41:50 +0200 Subject: [PATCH 04/10] CROSSLINK-181 Add borrowing side actions and lender message handling --- broker/app/app.go | 44 +-- broker/client/client.go | 37 ++- broker/client/client_test.go | 35 +-- broker/events/eventbus.go | 36 ++- broker/events/eventmodels.go | 3 + broker/handler/iso18626-handler.go | 22 +- .../013_allow_patron_request_events.down.sql | 2 +- .../013_allow_patron_request_events.up.sql | 13 +- broker/patron_request/api/api-handler.go | 117 +++++++- broker/patron_request/api/api-handler_test.go | 30 +- broker/patron_request/oapi/open-api.yaml | 101 ++++++- broker/patron_request/service/action.go | 260 ++++++++++++++++++ .../patron_request/service/message-handler.go | 176 ++++++++++++ broker/test/apputils/apputils.go | 1 + broker/test/events/eventbus_test.go | 1 + .../patron_request/api/api-handler_test.go | 4 +- 16 files changed, 790 insertions(+), 92 deletions(-) create mode 100644 broker/patron_request/service/action.go create mode 100644 broker/patron_request/service/message-handler.go diff --git a/broker/app/app.go b/broker/app/app.go index c7b92f54..0f4f0596 100644 --- a/broker/app/app.go +++ b/broker/app/app.go @@ -7,6 +7,7 @@ import ( prapi "github.com/indexdata/crosslink/broker/patron_request/api" pr_db "github.com/indexdata/crosslink/broker/patron_request/db" proapi "github.com/indexdata/crosslink/broker/patron_request/oapi" + prservice "github.com/indexdata/crosslink/broker/patron_request/service" "log/slog" "math" "net/http" @@ -75,11 +76,12 @@ var ServeMux *http.ServeMux var appCtx = common.CreateExtCtxWithLogArgsAndHandler(context.Background(), nil, configLog()) type Context struct { - EventBus events.EventBus - IllRepo ill_db.IllRepo - EventRepo events.EventRepo - DirAdapter adapter.DirectoryLookupAdapter - PrRepo pr_db.PrRepo + EventBus events.EventBus + IllRepo ill_db.IllRepo + EventRepo events.EventRepo + DirAdapter adapter.DirectoryLookupAdapter + PrRepo pr_db.PrRepo + PrApiHandler prapi.PatronRequestApiHandler } func configLog() slog.Handler { @@ -148,22 +150,27 @@ func Init(ctx context.Context) (Context, error) { eventBus := CreateEventBus(eventRepo) illRepo := CreateIllRepo(pool) prRepo := CreatePrRepo(pool) - iso18626Client := client.CreateIso18626Client(eventBus, illRepo, MAX_MESSAGE_SIZE, delay) - iso18626Handler := handler.CreateIso18626Handler(eventBus, eventRepo) + prMessageHandler := prservice.CreatePatronRequestMessageHandler(prRepo, eventRepo, illRepo, eventBus) + iso18626Client := client.CreateIso18626Client(eventBus, illRepo, prMessageHandler, MAX_MESSAGE_SIZE, delay) + iso18626Handler := handler.CreateIso18626Handler(eventBus, eventRepo, illRepo, dirAdapter) supplierLocator := service.CreateSupplierLocator(eventBus, illRepo, dirAdapter, holdingsAdapter) workflowManager := service.CreateWorkflowManager(eventBus, illRepo, service.WorkflowConfig{}) - AddDefaultHandlers(eventBus, iso18626Client, supplierLocator, workflowManager, iso18626Handler) + prActionService := prservice.CreatePatronRequestAction(prRepo, illRepo, eventBus, iso18626Handler) + prApiHandler := prapi.NewApiHandler(prRepo, eventBus) + + AddDefaultHandlers(eventBus, iso18626Client, supplierLocator, workflowManager, iso18626Handler, prActionService, prApiHandler, prMessageHandler) err = StartEventBus(ctx, eventBus) if err != nil { return Context{}, err } return Context{ - EventBus: eventBus, - IllRepo: illRepo, - EventRepo: eventRepo, - DirAdapter: dirAdapter, - PrRepo: prRepo, + EventBus: eventBus, + IllRepo: illRepo, + EventRepo: eventRepo, + DirAdapter: dirAdapter, + PrRepo: prRepo, + PrApiHandler: prApiHandler, }, nil } @@ -194,8 +201,8 @@ func StartServer(ctx Context) error { apiHandler := api.NewApiHandler(ctx.EventRepo, ctx.IllRepo, TENANT_TO_SYMBOL, API_PAGE_SIZE) oapi.HandlerFromMuxWithBaseURL(&apiHandler, ServeMux, "/broker") } - prApiHandler := prapi.NewApiHandler(ctx.PrRepo) - proapi.HandlerFromMux(&prApiHandler, ServeMux) + + proapi.HandlerFromMux(&ctx.PrApiHandler, ServeMux) signatureHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Server", vcs.GetSignature()) ServeMux.ServeHTTP(w, r) @@ -264,7 +271,8 @@ func CreateEventBus(eventRepo events.EventRepo) events.EventBus { } func AddDefaultHandlers(eventBus events.EventBus, iso18626Client client.Iso18626Client, - supplierLocator service.SupplierLocator, workflowManager service.WorkflowManager, iso18626Handler handler.Iso18626Handler) { + supplierLocator service.SupplierLocator, workflowManager service.WorkflowManager, iso18626Handler handler.Iso18626Handler, + prActionService prservice.PatronRequestActionService, prApiHandler prapi.PatronRequestApiHandler, prMessageHandler prservice.PatronRequestMessageHandler) { eventBus.HandleEventCreated(events.EventNameMessageSupplier, iso18626Client.MessageSupplier) eventBus.HandleEventCreated(events.EventNameMessageRequester, iso18626Client.MessageRequester) eventBus.HandleEventCreated(events.EventNameConfirmRequesterMsg, iso18626Handler.ConfirmRequesterMsg) @@ -280,6 +288,10 @@ func AddDefaultHandlers(eventBus events.EventBus, iso18626Client client.Iso18626 eventBus.HandleTaskCompleted(events.EventNameSelectSupplier, workflowManager.OnSelectSupplierComplete) eventBus.HandleTaskCompleted(events.EventNameMessageSupplier, workflowManager.OnMessageSupplierComplete) eventBus.HandleTaskCompleted(events.EventNameMessageRequester, workflowManager.OnMessageRequesterComplete) + + eventBus.HandleEventCreated(events.EventNameInvokeAction, prActionService.InvokeAction) + eventBus.HandleTaskCompleted(events.EventNameInvokeAction, prApiHandler.ConfirmActionProcess) + eventBus.HandleEventCreated(events.EventNamePatronRequestMessage, prMessageHandler.PatronRequestMessage) } func StartEventBus(ctx context.Context, eventBus events.EventBus) error { err := eventBus.Start(common.CreateExtCtxWithArgs(ctx, nil)) diff --git a/broker/client/client.go b/broker/client/client.go index 4fe914b4..fc905fda 100644 --- a/broker/client/client.go +++ b/broker/client/client.go @@ -3,6 +3,7 @@ package client import ( "errors" "fmt" + prservice "github.com/indexdata/crosslink/broker/patron_request/service" "net/http" "strings" "time" @@ -43,11 +44,12 @@ var appendReturnInfo, _ = utils.GetEnvBool("RETURN_INFO", true) var prependVendor, _ = utils.GetEnvBool("VENDOR_INFO", true) type Iso18626Client struct { - eventBus events.EventBus - illRepo ill_db.IllRepo - client *http.Client - maxMsgSize int - sendDelay time.Duration + eventBus events.EventBus + illRepo ill_db.IllRepo + prMessageHandler prservice.PatronRequestMessageHandler + client *http.Client + maxMsgSize int + sendDelay time.Duration } type transactionContext struct { @@ -67,13 +69,14 @@ type messageTarget struct { problemDetails *string } -func CreateIso18626Client(eventBus events.EventBus, illRepo ill_db.IllRepo, maxMsgSize int, delay time.Duration) Iso18626Client { +func CreateIso18626Client(eventBus events.EventBus, illRepo ill_db.IllRepo, prMessageHandler prservice.PatronRequestMessageHandler, maxMsgSize int, delay time.Duration) Iso18626Client { return Iso18626Client{ - eventBus: eventBus, - illRepo: illRepo, - client: http.DefaultClient, - maxMsgSize: maxMsgSize, - sendDelay: delay, + eventBus: eventBus, + illRepo: illRepo, + prMessageHandler: prMessageHandler, + client: http.DefaultClient, + maxMsgSize: maxMsgSize, + sendDelay: delay, } } @@ -437,6 +440,14 @@ func (c *Iso18626Client) checkConfirmationError(ctx common.ExtendedContext, resp return status } +func (c *Iso18626Client) SendIllMessage(ctx common.ExtendedContext, peer *ill_db.Peer, msg *iso18626.ISO18626Message) (*iso18626.ISO18626Message, error) { + if strings.Contains(peer.Name, "local") { // TODO + return c.prMessageHandler.HandleMessage(ctx, msg) + } else { + return c.SendHttpPost(peer, msg) + } +} + func (c *Iso18626Client) SendHttpPost(peer *ill_db.Peer, msg *iso18626.ISO18626Message) (*iso18626.ISO18626Message, error) { httpClient := httpclient.NewClient(). WithMaxSize(int64(c.maxMsgSize)). @@ -694,7 +705,7 @@ func (c *Iso18626Client) sendAndUpdateStatus(ctx common.ExtendedContext, trCtx t resData.CustomData = map[string]any{common.DO_NOT_SEND: true} resData.OutgoingMessage = nil } else { - response, err := c.SendHttpPost(trCtx.requester, message) + response, err := c.SendIllMessage(ctx, trCtx.requester, message) if response != nil { resData.IncomingMessage = response } @@ -789,7 +800,7 @@ func (c *Iso18626Client) sendAndUpdateSupplier(ctx common.ExtendedContext, trCtx resData := events.EventResult{} resData.OutgoingMessage = message if !isDoNotSend(trCtx.event) { - response, err := c.SendHttpPost(trCtx.selectedPeer, message) + response, err := c.SendIllMessage(ctx, trCtx.selectedPeer, message) if response != nil { resData.IncomingMessage = response } diff --git a/broker/client/client_test.go b/broker/client/client_test.go index 87b96039..151e5f5a 100644 --- a/broker/client/client_test.go +++ b/broker/client/client_test.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "encoding/xml" + prservice "github.com/indexdata/crosslink/broker/patron_request/service" "net/http" "net/http/httptest" "testing" @@ -82,7 +83,7 @@ func TestSendHttpPost(t *testing.T) { server := httptest.NewServer(handler) defer server.Close() - var client = CreateIso18626Client(new(events.PostgresEventBus), new(ill_db.PgIllRepo), 0, 0*time.Second) + var client = CreateIso18626Client(new(events.PostgresEventBus), new(ill_db.PgIllRepo), *new(prservice.PatronRequestMessageHandler), 0, 0*time.Second) msg := &iso18626.ISO18626Message{} peer := ill_db.Peer{ @@ -364,7 +365,7 @@ func TestPopulateVendorInNote(t *testing.T) { func TestReadTransactionContextSuccess(t *testing.T) { appCtx := common.CreateExtCtxWithArgs(context.Background(), nil) - client := CreateIso18626Client(new(events.PostgresEventBus), new(mocks.MockIllRepositorySuccess), 1, 0*time.Second) + client := CreateIso18626Client(new(events.PostgresEventBus), new(mocks.MockIllRepositorySuccess), *new(prservice.PatronRequestMessageHandler), 1, 0*time.Second) event := events.Event{IllTransactionID: "1"} trCtx, err := client.readTransactionContext(appCtx, event, true) assert.NoError(t, err) @@ -377,7 +378,7 @@ func TestReadTransactionContextSuccess(t *testing.T) { func TestReadTransactionContextError(t *testing.T) { appCtx := common.CreateExtCtxWithArgs(context.Background(), nil) - client := CreateIso18626Client(new(events.PostgresEventBus), new(mocks.MockIllRepositoryError), 1, 0*time.Second) + client := CreateIso18626Client(new(events.PostgresEventBus), new(mocks.MockIllRepositoryError), *new(prservice.PatronRequestMessageHandler), 1, 0*time.Second) event := events.Event{IllTransactionID: "1"} trCtx, err := client.readTransactionContext(appCtx, event, true) assert.Equal(t, FailedToReadTransaction+": DB error", err.Error()) @@ -457,7 +458,7 @@ func (r *MockIllRepositorySkippedSup) GetLocatedSupplierByIllTransactionAndSymbo func TestDetermineMessageTarget_handleSkippedSupplierNotification_BrokerModeTransparent(t *testing.T) { appCtx := common.CreateExtCtxWithArgs(context.Background(), nil) - client := CreateIso18626Client(new(events.PostgresEventBus), new(MockIllRepositorySkippedSup), 1, 0*time.Second) + client := CreateIso18626Client(new(events.PostgresEventBus), new(MockIllRepositorySkippedSup), *new(prservice.PatronRequestMessageHandler), 1, 0*time.Second) event := createSupplyingAgencyMessageEvent(true) trCtx := createTransactionContext(event, nil, nil, common.BrokerModeTransparent) @@ -469,7 +470,7 @@ func TestDetermineMessageTarget_handleSkippedSupplierNotification_BrokerModeTran func TestDetermineMessageTarget_handleSkippedSupplierNotification_BrokerModeOpaque(t *testing.T) { appCtx := common.CreateExtCtxWithArgs(context.Background(), nil) - client := CreateIso18626Client(new(events.PostgresEventBus), new(MockIllRepositorySkippedSup), 1, 0*time.Second) + client := CreateIso18626Client(new(events.PostgresEventBus), new(MockIllRepositorySkippedSup), *new(prservice.PatronRequestMessageHandler), 1, 0*time.Second) event := createSupplyingAgencyMessageEvent(true) trCtx := createTransactionContext(event, nil, nil, common.BrokerModeOpaque) @@ -481,7 +482,7 @@ func TestDetermineMessageTarget_handleSkippedSupplierNotification_BrokerModeOpaq func TestDetermineMessageTarget_handleSkippedSupplierNotification_Error(t *testing.T) { appCtx := common.CreateExtCtxWithArgs(context.Background(), nil) - client := CreateIso18626Client(new(events.PostgresEventBus), new(mocks.MockIllRepositorySuccess), 1, 0*time.Second) + client := CreateIso18626Client(new(events.PostgresEventBus), new(mocks.MockIllRepositorySuccess), *new(prservice.PatronRequestMessageHandler), 1, 0*time.Second) event := createSupplyingAgencyMessageEvent(true) trCtx := createTransactionContext(event, nil, nil, common.BrokerModeOpaque) @@ -492,7 +493,7 @@ func TestDetermineMessageTarget_handleSkippedSupplierNotification_Error(t *testi func TestDetermineMessageTarget_handleNoSelectedSupplier_Unfilled(t *testing.T) { appCtx := common.CreateExtCtxWithArgs(context.Background(), nil) - client := CreateIso18626Client(new(events.PostgresEventBus), new(mocks.MockIllRepositorySuccess), 1, 0*time.Second) + client := CreateIso18626Client(new(events.PostgresEventBus), new(mocks.MockIllRepositorySuccess), *new(prservice.PatronRequestMessageHandler), 1, 0*time.Second) event := createSupplyingAgencyMessageEvent(false) trCtx := createTransactionContext(event, nil, nil, common.BrokerModeOpaque) @@ -505,7 +506,7 @@ func TestDetermineMessageTarget_handleNoSelectedSupplier_Unfilled(t *testing.T) func TestDetermineMessageTargetWithSupplier_handleSkippedSupplierNotification_BrokerModeTransparent(t *testing.T) { appCtx := common.CreateExtCtxWithArgs(context.Background(), nil) - client := CreateIso18626Client(new(events.PostgresEventBus), new(MockIllRepositorySkippedSup), 1, 0*time.Second) + client := CreateIso18626Client(new(events.PostgresEventBus), new(MockIllRepositorySkippedSup), *new(prservice.PatronRequestMessageHandler), 1, 0*time.Second) event := createSupplyingAgencyMessageEvent(true) trCtx := createTransactionContext(event, &ill_db.LocatedSupplier{SupplierSymbol: "isil:sup2"}, &ill_db.Peer{}, common.BrokerModeTransparent) @@ -517,7 +518,7 @@ func TestDetermineMessageTargetWithSupplier_handleSkippedSupplierNotification_Br func TestDetermineMessageTargetWithSupplier_handleSkippedSupplierNotification_BrokerModeOpaque(t *testing.T) { appCtx := common.CreateExtCtxWithArgs(context.Background(), nil) - client := CreateIso18626Client(new(events.PostgresEventBus), new(MockIllRepositorySkippedSup), 1, 0*time.Second) + client := CreateIso18626Client(new(events.PostgresEventBus), new(MockIllRepositorySkippedSup), *new(prservice.PatronRequestMessageHandler), 1, 0*time.Second) event := createSupplyingAgencyMessageEvent(true) trCtx := createTransactionContext(event, &ill_db.LocatedSupplier{SupplierSymbol: "isil:sup2"}, &ill_db.Peer{}, common.BrokerModeOpaque) @@ -529,7 +530,7 @@ func TestDetermineMessageTargetWithSupplier_handleSkippedSupplierNotification_Br func TestDetermineMessageTargetWithSupplier_handleSkippedSupplierNotification_Error(t *testing.T) { appCtx := common.CreateExtCtxWithArgs(context.Background(), nil) - client := CreateIso18626Client(new(events.PostgresEventBus), new(mocks.MockIllRepositorySuccess), 1, 0*time.Second) + client := CreateIso18626Client(new(events.PostgresEventBus), new(mocks.MockIllRepositorySuccess), *new(prservice.PatronRequestMessageHandler), 1, 0*time.Second) event := createSupplyingAgencyMessageEvent(true) trCtx := createTransactionContext(event, &ill_db.LocatedSupplier{SupplierSymbol: "isil:sup2"}, &ill_db.Peer{}, common.BrokerModeOpaque) @@ -540,7 +541,7 @@ func TestDetermineMessageTargetWithSupplier_handleSkippedSupplierNotification_Er func TestDetermineMessageTarget_handleSelectedSupplier_StatusLoaned(t *testing.T) { appCtx := common.CreateExtCtxWithArgs(context.Background(), nil) - client := CreateIso18626Client(new(events.PostgresEventBus), new(MockIllRepositorySkippedSup), 1, 0*time.Second) + client := CreateIso18626Client(new(events.PostgresEventBus), new(MockIllRepositorySkippedSup), *new(prservice.PatronRequestMessageHandler), 1, 0*time.Second) event := createSupplyingAgencyMessageEvent(true) sup := &ill_db.LocatedSupplier{SupplierSymbol: "isil:sup1", LastStatus: getPgText(string(iso18626.TypeStatusLoaned))} supPeer := &ill_db.Peer{} @@ -557,7 +558,7 @@ func TestDetermineMessageTarget_handleSelectedSupplier_StatusLoaned(t *testing.T func TestDetermineMessageTarget_handleSelectedSupplier_StatusInvalid(t *testing.T) { appCtx := common.CreateExtCtxWithArgs(context.Background(), nil) - client := CreateIso18626Client(new(events.PostgresEventBus), new(MockIllRepositorySkippedSup), 1, 0*time.Second) + client := CreateIso18626Client(new(events.PostgresEventBus), new(MockIllRepositorySkippedSup), *new(prservice.PatronRequestMessageHandler), 1, 0*time.Second) event := createSupplyingAgencyMessageEvent(true) sup := &ill_db.LocatedSupplier{SupplierSymbol: "isil:sup1", LastStatus: getPgText("invalid")} supPeer := &ill_db.Peer{} @@ -570,7 +571,7 @@ func TestDetermineMessageTarget_handleSelectedSupplier_StatusInvalid(t *testing. func TestDetermineMessageTarget_handleSelectedSupplier_NoStatus(t *testing.T) { appCtx := common.CreateExtCtxWithArgs(context.Background(), nil) - client := CreateIso18626Client(new(events.PostgresEventBus), new(MockIllRepositorySkippedSup), 1, 0*time.Second) + client := CreateIso18626Client(new(events.PostgresEventBus), new(MockIllRepositorySkippedSup), *new(prservice.PatronRequestMessageHandler), 1, 0*time.Second) event := createSupplyingAgencyMessageEvent(true) sup := &ill_db.LocatedSupplier{SupplierSymbol: "isil:sup1"} supPeer := &ill_db.Peer{} @@ -587,7 +588,7 @@ func TestDetermineMessageTarget_handleSelectedSupplier_NoStatus(t *testing.T) { func TestDetermineMessageTarget_handleSelectedSupplier_NoStatus_NoMessage_BrokerModeOpaque(t *testing.T) { appCtx := common.CreateExtCtxWithArgs(context.Background(), nil) - client := CreateIso18626Client(new(events.PostgresEventBus), new(MockIllRepositorySkippedSup), 1, 0*time.Second) + client := CreateIso18626Client(new(events.PostgresEventBus), new(MockIllRepositorySkippedSup), *new(prservice.PatronRequestMessageHandler), 1, 0*time.Second) event := createSupplyingAgencyMessageEvent(true) event.EventData.IncomingMessage = nil sup := &ill_db.LocatedSupplier{SupplierSymbol: "isil:sup1"} @@ -604,7 +605,7 @@ func TestDetermineMessageTarget_handleSelectedSupplier_NoStatus_NoMessage_Broker } func TestDetermineMessageTarget_handleSelectedSupplier_NoStatus_NoMessage_BrokerModeTransparent(t *testing.T) { appCtx := common.CreateExtCtxWithArgs(context.Background(), nil) - client := CreateIso18626Client(new(events.PostgresEventBus), new(MockIllRepositorySkippedSup), 1, 0*time.Second) + client := CreateIso18626Client(new(events.PostgresEventBus), new(MockIllRepositorySkippedSup), *new(prservice.PatronRequestMessageHandler), 1, 0*time.Second) event := createSupplyingAgencyMessageEvent(true) event.EventData.IncomingMessage = nil sup := &ill_db.LocatedSupplier{SupplierSymbol: "isil:sup1"} @@ -677,7 +678,7 @@ func TestSendAndUpdateStatus_DontSend(t *testing.T) { event := createSupplyingAgencyMessageEvent(true) event.EventData.CustomData = map[string]any{common.DO_NOT_SEND: true} trCtx := createTransactionContext(event, nil, nil, common.BrokerModeTransparent) - client := CreateIso18626Client(new(events.PostgresEventBus), new(MockIllRepositorySkippedSup), 1, 0*time.Second) + client := CreateIso18626Client(new(events.PostgresEventBus), new(MockIllRepositorySkippedSup), *new(prservice.PatronRequestMessageHandler), 1, 0*time.Second) status, resData := client.sendAndUpdateStatus(appCtx, trCtx, event.EventData.IncomingMessage) @@ -762,7 +763,7 @@ func TestSendAndUpdateSupplier_DontSend(t *testing.T) { Vendor: string(common.VendorAlma), } trCtx := createTransactionContext(event, sup, supPeer, common.BrokerModeTransparent) - client := CreateIso18626Client(new(events.PostgresEventBus), new(MockIllRepositorySkippedSup), 1, 0*time.Second) + client := CreateIso18626Client(new(events.PostgresEventBus), new(MockIllRepositorySkippedSup), *new(prservice.PatronRequestMessageHandler), 1, 0*time.Second) status, resData := client.sendAndUpdateSupplier(appCtx, trCtx, event.EventData.IncomingMessage, "Received") diff --git a/broker/events/eventbus.go b/broker/events/eventbus.go index cb490517..622912aa 100644 --- a/broker/events/eventbus.go +++ b/broker/events/eventbus.go @@ -18,13 +18,15 @@ import ( const EVENT_BUS_CHANNEL = "crosslink_channel" const EB_COMP = "event_bus" +const DEFAULT_ILL_TRANSACTION_ID = "00000000-0000-0000-0000-000000000001" +const DEFAULT_PATRON_REQUEST_ID = "00000000-0000-0000-0000-000000000002" type EventBus interface { Start(ctx common.ExtendedContext) error - CreateTask(illTransactionID string, eventName EventName, data EventData, eventClass EventClass, parentId *string) (string, error) - CreateTaskBroadcast(illTransactionID string, eventName EventName, data EventData, eventClass EventClass, parentId *string) (string, error) - CreateNotice(illTransactionID string, eventName EventName, data EventData, status EventStatus, eventClass EventClass) (string, error) - CreateNoticeBroadcast(illTransactionID string, eventName EventName, data EventData, status EventStatus, eventClass EventClass) (string, error) + CreateTask(id string, eventName EventName, data EventData, eventClass EventClass, parentId *string) (string, error) + CreateTaskBroadcast(id string, eventName EventName, data EventData, eventClass EventClass, parentId *string) (string, error) + CreateNotice(id string, eventName EventName, data EventData, status EventStatus, eventClass EventClass) (string, error) + CreateNoticeBroadcast(id string, eventName EventName, data EventData, status EventStatus, eventClass EventClass) (string, error) BeginTask(eventId string) (Event, error) CompleteTask(eventId string, result *EventResult, status EventStatus) (Event, error) HandleEventCreated(eventName EventName, f func(ctx common.ExtendedContext, event Event)) @@ -130,7 +132,7 @@ func (p *PostgresEventBus) Start(ctx common.ExtendedContext) error { if err != nil { ctx.Logger().Error("failed to unmarshal notification", "error", err, "payload", notification.Payload) } - p.handleNotify(notifyData) + go p.handleNotify(notifyData) } }() return nil @@ -193,13 +195,7 @@ func (p *PostgresEventBus) CreateTaskBroadcast(illTransactionID string, eventNam func (p *PostgresEventBus) createTask(classId string, eventName EventName, data EventData, eventClass EventClass, parentId *string, broadcast bool) (string, error) { id := uuid.New().String() - illTransactionID := "" - patronRequestID := "" - if eventClass == EventClassPatronRequest { - illTransactionID = classId - } else { - patronRequestID = classId - } + illTransactionID, patronRequestID := getIllTransactionAndPatronRequestId(classId, eventClass) return id, p.repo.WithTxFunc(p.ctx, func(eventRepo EventRepo) error { event, err := eventRepo.SaveEvent(p.ctx, SaveEventParams{ ID: id, @@ -233,13 +229,7 @@ func (p *PostgresEventBus) CreateNoticeBroadcast(classId string, eventName Event func (p *PostgresEventBus) createNotice(classId string, eventName EventName, data EventData, status EventStatus, eventClass EventClass, broadcast bool) (string, error) { id := uuid.New().String() - illTransactionID := "" - patronRequestID := "" - if eventClass == EventClassPatronRequest { - illTransactionID = classId - } else { - patronRequestID = classId - } + illTransactionID, patronRequestID := getIllTransactionAndPatronRequestId(classId, eventClass) return id, p.repo.WithTxFunc(p.ctx, func(eventRepo EventRepo) error { event, err := eventRepo.SaveEvent(p.ctx, SaveEventParams{ ID: id, @@ -421,3 +411,11 @@ func getPgText(value *string) pgtype.Text { String: stringValue, } } + +func getIllTransactionAndPatronRequestId(classId string, eventClass EventClass) (string, string) { + if eventClass == EventClassPatronRequest { + return DEFAULT_ILL_TRANSACTION_ID, classId + } else { + return classId, DEFAULT_PATRON_REQUEST_ID + } +} diff --git a/broker/events/eventmodels.go b/broker/events/eventmodels.go index b70809d5..a4921167 100644 --- a/broker/events/eventmodels.go +++ b/broker/events/eventmodels.go @@ -42,6 +42,8 @@ const ( EventNameMessageSupplier EventName = "message-supplier" EventNameConfirmRequesterMsg EventName = "confirm-requester-msg" EventNameConfirmSupplierMsg EventName = "confirm-supplier-msg" + EventNameInvokeAction EventName = "invoke-action" + EventNamePatronRequestMessage EventName = "patron-request-message" ) type Signal string @@ -65,6 +67,7 @@ type CommonEventData struct { HttpFailure *httpclient.HttpError `json:"httpFailure,omitempty"` EventError *EventError `json:"eventError,omitempty"` Note string `json:"note,omitempty"` + Action *string `json:"action,omitempty"` } type EventError struct { diff --git a/broker/handler/iso18626-handler.go b/broker/handler/iso18626-handler.go index b480543e..3d1c4801 100644 --- a/broker/handler/iso18626-handler.go +++ b/broker/handler/iso18626-handler.go @@ -60,14 +60,18 @@ var ErrRetryNotPossible = errors.New(string(RetryNotPossible)) var waitingReqs = map[string]RequestWait{} type Iso18626Handler struct { - eventBus events.EventBus - eventRepo events.EventRepo + eventBus events.EventBus + eventRepo events.EventRepo + illRepo ill_db.IllRepo + dirAdapter adapter.DirectoryLookupAdapter } -func CreateIso18626Handler(eventBus events.EventBus, eventRepo events.EventRepo) Iso18626Handler { +func CreateIso18626Handler(eventBus events.EventBus, eventRepo events.EventRepo, illRepo ill_db.IllRepo, dirAdapter adapter.DirectoryLookupAdapter) Iso18626Handler { return Iso18626Handler{ - eventBus: eventBus, - eventRepo: eventRepo, + eventBus: eventBus, + eventRepo: eventRepo, + illRepo: illRepo, + dirAdapter: dirAdapter, } } @@ -203,6 +207,10 @@ func handleRetryRequest(ctx common.ExtendedContext, request *iso18626.Request, r return id, err } +func (h *Iso18626Handler) HandleRequest(ctx common.ExtendedContext, illMessage *iso18626.ISO18626Message, w http.ResponseWriter) { + handleRequest(ctx, illMessage, w, h.illRepo, h.eventBus, h.dirAdapter) +} + func handleRequest(ctx common.ExtendedContext, illMessage *iso18626.ISO18626Message, w http.ResponseWriter, repo ill_db.IllRepo, eventBus events.EventBus, dirAdapter adapter.DirectoryLookupAdapter) { request := illMessage.Request if request.Header.RequestingAgencyRequestId == "" { @@ -335,6 +343,10 @@ func createConfirmationHeader(inHeader *iso18626.Header, messageStatus iso18626. return header } +func (h *Iso18626Handler) HandleRequestingAgencyMessage(ctx common.ExtendedContext, illMessage *iso18626.ISO18626Message, w http.ResponseWriter) { + handleRequestingAgencyMessage(ctx, illMessage, w, h.illRepo, h.eventBus) +} + func handleRequestingAgencyMessage(ctx common.ExtendedContext, illMessage *iso18626.ISO18626Message, w http.ResponseWriter, repo ill_db.IllRepo, eventBus events.EventBus) { var requestingRequestId = illMessage.RequestingAgencyMessage.Header.RequestingAgencyRequestId if requestingRequestId == "" { diff --git a/broker/migrations/013_allow_patron_request_events.down.sql b/broker/migrations/013_allow_patron_request_events.down.sql index 8080d980..e4abe9f5 100644 --- a/broker/migrations/013_allow_patron_request_events.down.sql +++ b/broker/migrations/013_allow_patron_request_events.down.sql @@ -1,3 +1,3 @@ ALTER TABLE event DROP COLUMN patron_request_id; -DROP INDEX IF EXISTS event_patron_request_id_idx; \ No newline at end of file +DELETE FROM event_config WHERE event_name ='invoke-action'; \ No newline at end of file diff --git a/broker/migrations/013_allow_patron_request_events.up.sql b/broker/migrations/013_allow_patron_request_events.up.sql index b3290e29..bdf42c03 100644 --- a/broker/migrations/013_allow_patron_request_events.up.sql +++ b/broker/migrations/013_allow_patron_request_events.up.sql @@ -1,6 +1,11 @@ -ALTER TABLE event DROP CONSTRAINT event_ill_transaction_id_fkey; -ALTER TABLE event ADD COLUMN patron_request_id VARCHAR NOT NULL DEFAULT ''; +INSERT INTO ill_transaction (id, timestamp, ill_transaction_data) VALUES ('00000000-0000-0000-0000-000000000001', now(), '{}'); +INSERT INTO patron_request (id, state, side) VALUES ('00000000-0000-0000-0000-000000000002', 'NEW', 'borrowing'); -CREATE INDEX IF NOT EXISTS event_ill_transaction_id_idx ON event (ill_transaction_id); -CREATE INDEX IF NOT EXISTS event_patron_request_id_idx ON event (patron_request_id); \ No newline at end of file +ALTER TABLE event ADD COLUMN patron_request_id VARCHAR NOT NULL DEFAULT '00000000-0000-0000-0000-000000000002', + ADD FOREIGN KEY (patron_request_id) REFERENCES patron_request(id); + +INSERT INTO event_config (event_name, event_type, retry_count) +VALUES ('invoke-action', 'TASK', 1); +INSERT INTO event_config (event_name, event_type, retry_count) +VALUES ('patron-request-message', 'TASK', 1); \ No newline at end of file diff --git a/broker/patron_request/api/api-handler.go b/broker/patron_request/api/api-handler.go index 34387b92..09c1c6a0 100644 --- a/broker/patron_request/api/api-handler.go +++ b/broker/patron_request/api/api-handler.go @@ -6,20 +6,27 @@ import ( "errors" "github.com/google/uuid" "github.com/indexdata/crosslink/broker/common" + "github.com/indexdata/crosslink/broker/events" pr_db "github.com/indexdata/crosslink/broker/patron_request/db" proapi "github.com/indexdata/crosslink/broker/patron_request/oapi" + prservice "github.com/indexdata/crosslink/broker/patron_request/service" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgtype" "net/http" + "sync" ) +var waitingReqs = map[string]RequestWait{} + type PatronRequestApiHandler struct { - prRepo pr_db.PrRepo + prRepo pr_db.PrRepo + eventBus events.EventBus } -func NewApiHandler(prRepo pr_db.PrRepo) PatronRequestApiHandler { +func NewApiHandler(prRepo pr_db.PrRepo, eventBus events.EventBus) PatronRequestApiHandler { return PatronRequestApiHandler{ - prRepo: prRepo, + prRepo: prRepo, + eventBus: eventBus, } } @@ -50,11 +57,26 @@ func (a *PatronRequestApiHandler) PostPatronRequests(w http.ResponseWriter, r *h addInternalError(ctx, w, err) return } + if newPr.State != prservice.BorrowerStateNew { + addBadRequestError(ctx, w, errors.New("only "+prservice.BorrowerStateNew+" state is allowed")) + return + } + if newPr.Side != prservice.SideBorrowing { + addBadRequestError(ctx, w, errors.New("only "+prservice.SideBorrowing+" side is allowed")) + return + } + pr, err := a.prRepo.SavePatronRequest(ctx, (pr_db.SavePatronRequestParams)(toDbPatronRequest(newPr))) if err != nil { addInternalError(ctx, w, err) return } + + _, err = a.eventBus.CreateTask(pr.ID, events.EventNameInvokeAction, events.EventData{CommonEventData: events.CommonEventData{Action: &prservice.ActionValidate}}, events.EventClassPatronRequest, nil) + if err != nil { + addInternalError(ctx, w, err) + return + } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) _ = json.NewEncoder(w).Encode(toApiPatronRequest(pr)) @@ -131,6 +153,80 @@ func (a *PatronRequestApiHandler) PutPatronRequestsId(w http.ResponseWriter, r * writeJsonResponse(w, toApiPatronRequest(pr)) } +func (a *PatronRequestApiHandler) GetPatronRequestsIdActions(w http.ResponseWriter, r *http.Request, id string) { + ctx := common.CreateExtCtxWithArgs(context.Background(), &common.LoggerArgs{ + Other: map[string]string{"method": "GetPatronRequestsIdActions", "id": id}, + }) + pr, err := a.prRepo.GetPatronRequestById(ctx, id) + if err != nil { + if errors.Is(err, pgx.ErrNoRows) { + addNotFoundError(w) + return + } else { + addInternalError(ctx, w, err) + return + } + } + writeJsonResponse(w, prservice.GetBorrowerActionsByState(pr.State)) +} + +func (a *PatronRequestApiHandler) PostPatronRequestsIdAction(w http.ResponseWriter, r *http.Request, id string) { + ctx := common.CreateExtCtxWithArgs(context.Background(), &common.LoggerArgs{ + Other: map[string]string{"method": "GetPatronRequestsIdActions", "id": id}, + }) + var action proapi.ExecuteAction + err := json.NewDecoder(r.Body).Decode(&action) + if err != nil { + addInternalError(ctx, w, err) + return + } + pr, err := a.prRepo.GetPatronRequestById(ctx, id) + if err != nil { + if errors.Is(err, pgx.ErrNoRows) { + addNotFoundError(w) + return + } else { + addInternalError(ctx, w, err) + return + } + } + if !prservice.IsBorrowerActionAvailable(pr.State, action.Action) { + addBadRequestError(ctx, w, errors.New("Action "+action.Action+" is not allowed for patron request "+id)) + return + } + + data := events.EventData{CommonEventData: events.CommonEventData{Action: &action.Action}} + if action.ActionParams != nil { + data.CustomData = *action.ActionParams + } + eventId, err := a.eventBus.CreateTask(pr.ID, events.EventNameInvokeAction, data, events.EventClassPatronRequest, nil) + if err != nil { + addInternalError(ctx, w, err) + return + } + + var wg sync.WaitGroup + wg.Add(1) + waitingReqs[eventId] = RequestWait{ + w: &w, + wg: &wg, + } + wg.Wait() +} + +func (a *PatronRequestApiHandler) ConfirmActionProcess(ctx common.ExtendedContext, event events.Event) { + if waitingRequest, ok := waitingReqs[event.ID]; ok { + result := proapi.ActionResult{ + ActionResult: string(event.EventStatus), + } + if event.ResultData.Note != "" { + result.Message = &event.ResultData.Note + } + writeJsonResponse(*waitingRequest.w, result) + waitingRequest.wg.Done() + } +} + func writeJsonResponse(w http.ResponseWriter, resp any) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) @@ -147,6 +243,16 @@ func addInternalError(ctx common.ExtendedContext, w http.ResponseWriter, err err _ = json.NewEncoder(w).Encode(resp) } +func addBadRequestError(ctx common.ExtendedContext, w http.ResponseWriter, err error) { + resp := proapi.Error{ + Error: err.Error(), + } + ctx.Logger().Error("error serving api request", "error", err.Error()) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusBadRequest) + _ = json.NewEncoder(w).Encode(resp) +} + func addNotFoundError(w http.ResponseWriter) { resp := proapi.Error{ Error: "not found", @@ -219,3 +325,8 @@ func getDbText(value *string) pgtype.Text { String: *value, } } + +type RequestWait struct { + w *http.ResponseWriter + wg *sync.WaitGroup +} diff --git a/broker/patron_request/api/api-handler_test.go b/broker/patron_request/api/api-handler_test.go index 5c46b166..063c928c 100644 --- a/broker/patron_request/api/api-handler_test.go +++ b/broker/patron_request/api/api-handler_test.go @@ -5,6 +5,7 @@ import ( "encoding/json" "errors" "github.com/indexdata/crosslink/broker/common" + "github.com/indexdata/crosslink/broker/events" pr_db "github.com/indexdata/crosslink/broker/patron_request/db" proapi "github.com/indexdata/crosslink/broker/patron_request/oapi" "github.com/jackc/pgx/v5" @@ -15,6 +16,8 @@ import ( "testing" ) +var mockEventBus = new(MockEventBus) + func TestGetId(t *testing.T) { assert.True(t, getId("") != "") assert.Equal(t, "id1", getId("id1")) @@ -31,7 +34,7 @@ func TestGetDbText(t *testing.T) { } func TestGetPatronRequests(t *testing.T) { - handler := NewApiHandler(new(PrRepoError)) + handler := NewApiHandler(new(PrRepoError), mockEventBus) req, _ := http.NewRequest("GET", "/", nil) rr := httptest.NewRecorder() handler.GetPatronRequests(rr, req) @@ -42,7 +45,7 @@ func TestGetPatronRequests(t *testing.T) { } func TestPostPatronRequests(t *testing.T) { - handler := NewApiHandler(new(PrRepoError)) + handler := NewApiHandler(new(PrRepoError), mockEventBus) toCreate := proapi.PatronRequest{ID: "1"} jsonBytes, err := json.Marshal(toCreate) assert.NoError(t, err, "failed to marshal patron request") @@ -56,7 +59,7 @@ func TestPostPatronRequests(t *testing.T) { } func TestPostPatronRequestsInvalidJson(t *testing.T) { - handler := NewApiHandler(new(PrRepoError)) + handler := NewApiHandler(new(PrRepoError), mockEventBus) req, _ := http.NewRequest("POST", "/", bytes.NewBuffer([]byte("a\": v\""))) rr := httptest.NewRecorder() handler.PostPatronRequests(rr, req) @@ -67,7 +70,7 @@ func TestPostPatronRequestsInvalidJson(t *testing.T) { } func TestDeletePatronRequestsIdNotFound(t *testing.T) { - handler := NewApiHandler(new(PrRepoError)) + handler := NewApiHandler(new(PrRepoError), mockEventBus) req, _ := http.NewRequest("POST", "/", nil) rr := httptest.NewRecorder() handler.DeletePatronRequestsId(rr, req, "2") @@ -78,7 +81,7 @@ func TestDeletePatronRequestsIdNotFound(t *testing.T) { } func TestDeletePatronRequestsId(t *testing.T) { - handler := NewApiHandler(new(PrRepoError)) + handler := NewApiHandler(new(PrRepoError), mockEventBus) req, _ := http.NewRequest("POST", "/", nil) rr := httptest.NewRecorder() handler.DeletePatronRequestsId(rr, req, "3") @@ -89,7 +92,7 @@ func TestDeletePatronRequestsId(t *testing.T) { } func TestGetPatronRequestsIdNotFound(t *testing.T) { - handler := NewApiHandler(new(PrRepoError)) + handler := NewApiHandler(new(PrRepoError), mockEventBus) req, _ := http.NewRequest("POST", "/", nil) rr := httptest.NewRecorder() handler.GetPatronRequestsId(rr, req, "2") @@ -100,7 +103,7 @@ func TestGetPatronRequestsIdNotFound(t *testing.T) { } func TestGetPatronRequestsId(t *testing.T) { - handler := NewApiHandler(new(PrRepoError)) + handler := NewApiHandler(new(PrRepoError), mockEventBus) req, _ := http.NewRequest("POST", "/", nil) rr := httptest.NewRecorder() handler.GetPatronRequestsId(rr, req, "1") @@ -111,7 +114,7 @@ func TestGetPatronRequestsId(t *testing.T) { } func TestPutPatronRequestsIdNotFound(t *testing.T) { - handler := NewApiHandler(new(PrRepoError)) + handler := NewApiHandler(new(PrRepoError), mockEventBus) toCreate := proapi.PatronRequest{ID: "2"} jsonBytes, err := json.Marshal(toCreate) assert.NoError(t, err, "failed to marshal patron request") @@ -125,7 +128,7 @@ func TestPutPatronRequestsIdNotFound(t *testing.T) { } func TestPutPatronRequestsId(t *testing.T) { - handler := NewApiHandler(new(PrRepoError)) + handler := NewApiHandler(new(PrRepoError), mockEventBus) toCreate := proapi.PatronRequest{ID: "1"} jsonBytes, err := json.Marshal(toCreate) assert.NoError(t, err, "failed to marshal patron request") @@ -139,7 +142,7 @@ func TestPutPatronRequestsId(t *testing.T) { } func TestPutPatronRequestsIdSaveError(t *testing.T) { - handler := NewApiHandler(new(PrRepoError)) + handler := NewApiHandler(new(PrRepoError), mockEventBus) toCreate := proapi.PatronRequest{ID: "3"} jsonBytes, err := json.Marshal(toCreate) assert.NoError(t, err, "failed to marshal patron request") @@ -153,7 +156,7 @@ func TestPutPatronRequestsIdSaveError(t *testing.T) { } func TestPutPatronRequestsIdInvalidJson(t *testing.T) { - handler := NewApiHandler(new(PrRepoError)) + handler := NewApiHandler(new(PrRepoError), mockEventBus) req, _ := http.NewRequest("POST", "/", bytes.NewBuffer([]byte("a\":v\""))) rr := httptest.NewRecorder() handler.PutPatronRequestsId(rr, req, "3") @@ -190,3 +193,8 @@ func (r *PrRepoError) SavePatronRequest(ctx common.ExtendedContext, params pr_db func (r *PrRepoError) DeletePatronRequest(ctx common.ExtendedContext, id string) error { return errors.New("DB error") } + +type MockEventBus struct { + mock.Mock + events.EventBus +} diff --git a/broker/patron_request/oapi/open-api.yaml b/broker/patron_request/oapi/open-api.yaml index 4ba66aa7..ab440a7b 100644 --- a/broker/patron_request/oapi/open-api.yaml +++ b/broker/patron_request/oapi/open-api.yaml @@ -48,6 +48,30 @@ components: required: - error + ExecuteAction: + type: object + properties: + action: + type: string + description: Action to execute + actionParams: + type: object + description: Action parameters + required: + - action + + ActionResult: + type: object + properties: + actionResult: + type: string + description: Action result + message: + type: string + description: Action message + required: + - actionResult + paths: /patron_requests: get: @@ -177,4 +201,79 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/Error' \ No newline at end of file + $ref: '#/components/schemas/Error' + + /patron_requests/{id}/actions: + get: + summary: Get valid actions for patron request in current state + parameters: + - in: path + name: id + schema: + type: string + required: true + description: ID of the patron request + responses: + '200': + description: List of available actions + content: + application/json: + schema: + type: array + items: + type: string + '404': + description: Patron request not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '500': + description: There was internal server error looking up patron request + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + /patron_requests/{id}/action: + post: + summary: Execute action for given patron request + parameters: + - in: path + name: id + schema: + type: string + required: true + description: ID of the patron request + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ExecuteAction' + responses: + '200': + description: List of available actions + content: + application/json: + schema: + $ref: '#/components/schemas/ActionResult' + + '400': + description: Invalid request + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: Patron request not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '500': + description: There was internal server error looking up patron request + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + diff --git a/broker/patron_request/service/action.go b/broker/patron_request/service/action.go new file mode 100644 index 00000000..304f14d6 --- /dev/null +++ b/broker/patron_request/service/action.go @@ -0,0 +1,260 @@ +package prservice + +import ( + "encoding/xml" + "errors" + "github.com/indexdata/crosslink/broker/common" + "github.com/indexdata/crosslink/broker/events" + "github.com/indexdata/crosslink/broker/handler" + "github.com/indexdata/crosslink/broker/ill_db" + pr_db "github.com/indexdata/crosslink/broker/patron_request/db" + "github.com/indexdata/crosslink/iso18626" + "net/http" + "slices" + "strings" +) + +const COMP = "pr_action_service" + +var SideBorrowing = "borrowing" +var SideLanding = "landing" + +var BorrowerStateNew = "NEW" +var BorrowerStateValidated = "VALIDATED" +var BorrowerStateSent = "SENT" +var BorrowerStateSupplierLocated = "SUPPLIER_LOCATED" +var BorrowerStateConditionPending = "CONDITION_PENDING" +var BorrowerStateWillSupply = "WILL_SUPPLY" +var BorrowerStateShipped = "SHIPPED" +var BorrowerStateReceived = "RECEIVED" +var BorrowerStateCheckedOut = "CHECKED_OUT" +var BorrowerStateCheckedIn = "CHECKED_IN" +var BorrowerStateShippedReturned = "SHIPPED_RETURNED" +var BorrowerStateCancelPending = "CANCEL_PENDING" +var BorrowerStateCompleted = "COMPLETED" +var BorrowerStateCancelled = "CANCELLED" +var BorrowerStateUnfilled = "UNFILLED" + +var ActionValidate = "validate" +var ActionSendRequest = "send-request" +var ActionCancelRequest = "cancel-request" +var ActionAcceptCondition = "accept-condition" +var ActionRejectCondition = "reject-condition" +var ActionReceive = "receive" +var ActionCheckOut = "check-out" +var ActionCheckIn = "check-in" +var ActionShipReturn = "ship-return" + +var BorrowerStateActionMapping = map[string][]string{ + BorrowerStateNew: {ActionValidate}, + BorrowerStateValidated: {ActionSendRequest}, + BorrowerStateSupplierLocated: {ActionCancelRequest}, + BorrowerStateConditionPending: {ActionAcceptCondition, ActionRejectCondition}, + BorrowerStateWillSupply: {ActionCancelRequest}, + BorrowerStateShipped: {ActionReceive}, + BorrowerStateReceived: {ActionCheckOut}, + BorrowerStateCheckedOut: {ActionCheckIn}, + BorrowerStateCheckedIn: {ActionShipReturn}, +} + +type PatronRequestActionService struct { + prRepo pr_db.PrRepo + illRepo ill_db.IllRepo + eventBus events.EventBus + iso18626Handler handler.Iso18626Handler +} + +func CreatePatronRequestAction(prRepo pr_db.PrRepo, illRepo ill_db.IllRepo, eventBus events.EventBus, iso18626Handler handler.Iso18626Handler) PatronRequestActionService { + return PatronRequestActionService{ + prRepo: prRepo, + illRepo: illRepo, + eventBus: eventBus, + iso18626Handler: iso18626Handler, + } +} +func GetBorrowerActionsByState(state string) []string { + if actions, ok := BorrowerStateActionMapping[state]; ok { + return actions + } else { + return []string{} + } +} + +func IsBorrowerActionAvailable(state string, action string) bool { + return slices.Contains(GetBorrowerActionsByState(state), action) +} + +func (a *PatronRequestActionService) InvokeAction(ctx common.ExtendedContext, event events.Event) { + ctx = ctx.WithArgs(ctx.LoggerArgs().WithComponent(COMP)) + _, _ = a.eventBus.ProcessTask(ctx, event, a.handleInvokeAction) +} + +func (a *PatronRequestActionService) handleInvokeAction(ctx common.ExtendedContext, event events.Event) (events.EventStatus, *events.EventResult) { + if event.EventData.Action == nil { + return events.LogErrorAndReturnResult(ctx, "action not specified", errors.New("action not specified")) + } + action := *event.EventData.Action + pr, err := a.prRepo.GetPatronRequestById(ctx, event.PatronRequestID) + if err != nil { + return events.LogErrorAndReturnResult(ctx, "failed to read patron request", err) + } + if !IsBorrowerActionAvailable(pr.State, action) { + return events.LogErrorAndReturnResult(ctx, "state "+pr.State+" does not support action "+action, errors.New("invalid action")) + } + + switch action { + case ActionValidate: + return a.validateBorrowingRequest(ctx, pr) + case ActionSendRequest: + return a.sendBorrowingRequest(ctx, pr) + case ActionReceive: + return a.receiveBorrowingRequest(ctx, pr) + case ActionCheckOut: + return a.checkoutBorrowingRequest(ctx, pr) + case ActionCheckIn: + return a.checkinBorrowingRequest(ctx, pr) + case ActionShipReturn: + return a.shipReturnBorrowingRequest(ctx, pr) + default: + return events.LogErrorAndReturnResult(ctx, "action "+action+" is not implemented yet", errors.New("invalid action")) + } +} +func (a *PatronRequestActionService) updateStateAndReturnResult(ctx common.ExtendedContext, pr pr_db.PatronRequest, state string, result *events.EventResult) (events.EventStatus, *events.EventResult) { + pr.State = state + pr, err := a.prRepo.SavePatronRequest(ctx, pr_db.SavePatronRequestParams(pr)) + if err != nil { + return events.LogErrorAndReturnResult(ctx, "failed to update patron request", err) + } + return events.EventStatusSuccess, result +} + +func (a *PatronRequestActionService) validateBorrowingRequest(ctx common.ExtendedContext, pr pr_db.PatronRequest) (events.EventStatus, *events.EventResult) { + // TODO do validation + + return a.updateStateAndReturnResult(ctx, pr, BorrowerStateValidated, nil) +} + +func (a *PatronRequestActionService) sendBorrowingRequest(ctx common.ExtendedContext, pr pr_db.PatronRequest) (events.EventStatus, *events.EventResult) { + result := events.EventResult{} + if !pr.BorrowingPeerID.Valid { + result.Note = "missing borrowing peer id" + return events.EventStatusProblem, &result + } + borrowerSymbols, err := a.illRepo.GetSymbolsByPeerId(ctx, pr.BorrowingPeerID.String) + if err != nil { + result.Note = err.Error() + return events.EventStatusProblem, &result + } + borrowerSymbol := strings.SplitN(borrowerSymbols[0].SymbolValue, ":", 2) + var illMessage = iso18626.ISO18626Message{ + Request: &iso18626.Request{ + Header: iso18626.Header{ + RequestingAgencyId: iso18626.TypeAgencyId{ + AgencyIdType: iso18626.TypeSchemeValuePair{ + Text: borrowerSymbol[0], + }, + AgencyIdValue: borrowerSymbol[1], + }, + RequestingAgencyRequestId: pr.ID, + }, + PatronInfo: &iso18626.PatronInfo{PatronId: pr.Requester.String}, + BibliographicInfo: iso18626.BibliographicInfo{ + SupplierUniqueRecordId: "WILLSUPPLY_LOANED", + }, + }, + } + w := NewResponseCaptureWriter() + a.iso18626Handler.HandleRequest(ctx, &illMessage, w) + result.OutgoingMessage = &illMessage + result.IncomingMessage = w.IllMessage + if w.StatusCode != http.StatusOK || w.IllMessage == nil || w.IllMessage.RequestConfirmation == nil || + w.IllMessage.RequestConfirmation.ConfirmationHeader.MessageStatus != iso18626.TypeMessageStatusOK { + return events.EventStatusProblem, &result + } + return a.updateStateAndReturnResult(ctx, pr, BorrowerStateSent, &result) +} + +func (a *PatronRequestActionService) receiveBorrowingRequest(ctx common.ExtendedContext, pr pr_db.PatronRequest) (events.EventStatus, *events.EventResult) { + // TODO Make NCIP calls + return a.updateStateAndReturnResult(ctx, pr, BorrowerStateReceived, nil) +} + +func (a *PatronRequestActionService) checkoutBorrowingRequest(ctx common.ExtendedContext, pr pr_db.PatronRequest) (events.EventStatus, *events.EventResult) { + // TODO Make NCIP calls + return a.updateStateAndReturnResult(ctx, pr, BorrowerStateCheckedOut, nil) +} + +func (a *PatronRequestActionService) checkinBorrowingRequest(ctx common.ExtendedContext, pr pr_db.PatronRequest) (events.EventStatus, *events.EventResult) { + // TODO Make NCIP calls + return a.updateStateAndReturnResult(ctx, pr, BorrowerStateCheckedIn, nil) +} + +func (a *PatronRequestActionService) shipReturnBorrowingRequest(ctx common.ExtendedContext, pr pr_db.PatronRequest) (events.EventStatus, *events.EventResult) { + result := events.EventResult{} + if !pr.BorrowingPeerID.Valid { + result.Note = "missing borrowing peer id" + return events.EventStatusProblem, &result + } + borrowerSymbols, err := a.illRepo.GetSymbolsByPeerId(ctx, pr.BorrowingPeerID.String) + if err != nil { + result.Note = err.Error() + return events.EventStatusProblem, &result + } + lenderSymbols, err := a.illRepo.GetSymbolsByPeerId(ctx, pr.LendingPeerID.String) + if err != nil { + result.Note = err.Error() + return events.EventStatusProblem, &result + } + borrowerSymbol := strings.SplitN(borrowerSymbols[0].SymbolValue, ":", 2) + lenderSymbol := strings.SplitN(lenderSymbols[0].SymbolValue, ":", 2) + var illMessage = iso18626.ISO18626Message{ + RequestingAgencyMessage: &iso18626.RequestingAgencyMessage{ + Header: iso18626.Header{ + RequestingAgencyId: iso18626.TypeAgencyId{ + AgencyIdType: iso18626.TypeSchemeValuePair{ + Text: borrowerSymbol[0], + }, + AgencyIdValue: borrowerSymbol[1], + }, + SupplyingAgencyId: iso18626.TypeAgencyId{ + AgencyIdType: iso18626.TypeSchemeValuePair{ + Text: lenderSymbol[0], + }, + AgencyIdValue: lenderSymbol[1], + }, + RequestingAgencyRequestId: pr.ID, + }, + Action: iso18626.TypeActionShippedReturn, + }, + } + w := NewResponseCaptureWriter() + a.iso18626Handler.HandleRequestingAgencyMessage(ctx, &illMessage, w) + result.OutgoingMessage = &illMessage + result.IncomingMessage = w.IllMessage + if w.StatusCode != http.StatusOK || w.IllMessage == nil || w.IllMessage.RequestingAgencyMessageConfirmation == nil || + w.IllMessage.RequestingAgencyMessageConfirmation.ConfirmationHeader.MessageStatus != iso18626.TypeMessageStatusOK { + return events.EventStatusProblem, &result + } + return a.updateStateAndReturnResult(ctx, pr, BorrowerStateShippedReturned, nil) +} + +type ResponseCaptureWriter struct { + IllMessage *iso18626.ISO18626Message + StatusCode int +} + +func NewResponseCaptureWriter() *ResponseCaptureWriter { + return &ResponseCaptureWriter{ + StatusCode: http.StatusOK, + } +} +func (rcw *ResponseCaptureWriter) Write(b []byte) (int, error) { + err := xml.Unmarshal(b, &rcw.IllMessage) + return 1, err +} +func (rcw *ResponseCaptureWriter) WriteHeader(code int) { + rcw.StatusCode = code +} +func (rcw *ResponseCaptureWriter) Header() http.Header { + return http.Header{} +} diff --git a/broker/patron_request/service/message-handler.go b/broker/patron_request/service/message-handler.go new file mode 100644 index 00000000..4bac68a9 --- /dev/null +++ b/broker/patron_request/service/message-handler.go @@ -0,0 +1,176 @@ +package prservice + +import ( + "errors" + "github.com/indexdata/crosslink/broker/common" + "github.com/indexdata/crosslink/broker/events" + "github.com/indexdata/crosslink/broker/ill_db" + pr_db "github.com/indexdata/crosslink/broker/patron_request/db" + "github.com/indexdata/crosslink/iso18626" + "github.com/jackc/pgx/v5/pgtype" + "strings" + "sync" +) + +const COMP_MESSAGE = "pr_massage_handler" +const RESHARE_ADD_LOAN_CONDITION = "#ReShareAddLoanCondition#" + +var waitings = map[string]*sync.WaitGroup{} + +type PatronRequestMessageHandler struct { + prRepo pr_db.PrRepo + eventRepo events.EventRepo + illRep ill_db.IllRepo + eventBus events.EventBus +} + +func CreatePatronRequestMessageHandler(prRepo pr_db.PrRepo, eventRepo events.EventRepo, illRep ill_db.IllRepo, eventBus events.EventBus) PatronRequestMessageHandler { + return PatronRequestMessageHandler{ + prRepo: prRepo, + eventRepo: eventRepo, + illRep: illRep, + eventBus: eventBus, + } +} + +func (m *PatronRequestMessageHandler) HandleMessage(ctx common.ExtendedContext, msg *iso18626.ISO18626Message) (*iso18626.ISO18626Message, error) { + if msg == nil { + return nil, errors.New("cannot process nil message") + } + + requestId := getPatronRequestId(*msg) + pr, err := m.prRepo.GetPatronRequestById(ctx, requestId) + if err != nil { + return nil, err + } + + eventId, err := m.eventBus.CreateTask(pr.ID, events.EventNamePatronRequestMessage, events.EventData{CommonEventData: events.CommonEventData{IncomingMessage: msg}}, events.EventClassPatronRequest, nil) + if err != nil { + return nil, err + } + + var wg sync.WaitGroup + wg.Add(1) + waitings[eventId] = &wg + wg.Wait() + + event, err := m.eventRepo.GetEvent(ctx, eventId) + if err != nil { + return nil, err + } + return event.ResultData.OutgoingMessage, nil +} + +func (m *PatronRequestMessageHandler) PatronRequestMessage(ctx common.ExtendedContext, event events.Event) { + ctx = ctx.WithArgs(ctx.LoggerArgs().WithComponent(COMP_MESSAGE)) + _, _ = m.eventBus.ProcessTask(ctx, event, m.handlePatronRequestMessage) + if waiting, ok := waitings[event.ID]; ok { + waiting.Done() + } +} + +func (m *PatronRequestMessageHandler) handlePatronRequestMessage(ctx common.ExtendedContext, event events.Event) (events.EventStatus, *events.EventResult) { + if event.EventData.IncomingMessage.SupplyingAgencyMessage != nil { + return m.handleSupplyingAgencyMessage(ctx, *event.EventData.IncomingMessage.SupplyingAgencyMessage) + } else if event.EventData.IncomingMessage.RequestingAgencyMessage != nil { + return events.EventStatusError, &events.EventResult{CommonEventData: events.CommonEventData{Note: "requesting agency message handling is not implemented yet"}} + } else if event.EventData.IncomingMessage.Request != nil { + return events.EventStatusError, &events.EventResult{CommonEventData: events.CommonEventData{Note: "request handling is not implemented yet"}} + } else { + return events.EventStatusError, &events.EventResult{CommonEventData: events.CommonEventData{Note: "cannot process message without content"}} + } +} + +func getPatronRequestId(msg iso18626.ISO18626Message) string { + if msg.SupplyingAgencyMessage != nil { + return msg.SupplyingAgencyMessage.Header.RequestingAgencyRequestId + } else if msg.RequestingAgencyMessage != nil { + return msg.RequestingAgencyMessage.Header.SupplyingAgencyRequestId + } else if msg.Request != nil { + return msg.Request.Header.RequestingAgencyRequestId + } else { + return "" + } +} + +func (m *PatronRequestMessageHandler) handleSupplyingAgencyMessage(ctx common.ExtendedContext, sam iso18626.SupplyingAgencyMessage) (events.EventStatus, *events.EventResult) { + pr, err := m.prRepo.GetPatronRequestById(ctx, sam.Header.RequestingAgencyRequestId) + if err != nil { + return createSAMResponse(sam, iso18626.TypeMessageStatusERROR, &iso18626.ErrorData{ + ErrorType: iso18626.TypeErrorTypeUnrecognisedDataValue, + ErrorValue: "could not find patron request", + }) + } + // TODO handle notifications + switch sam.StatusInfo.Status { + case iso18626.TypeStatusExpectToSupply: + pr.State = BorrowerStateSupplierLocated + supSymbol := sam.Header.SupplyingAgencyId.AgencyIdType.Text + ":" + sam.Header.SupplyingAgencyId.AgencyIdValue + supplier, err := m.illRep.GetPeerBySymbol(ctx, supSymbol) + if err != nil { + return createSAMResponse(sam, iso18626.TypeMessageStatusERROR, &iso18626.ErrorData{ + ErrorType: iso18626.TypeErrorTypeUnrecognisedDataValue, + ErrorValue: "could not find supplier", + }) + } + pr.LendingPeerID = pgtype.Text{ + String: supplier.ID, + Valid: true, + } + return m.updatePatronRequestAndCreateSamResponse(ctx, pr, sam) + case iso18626.TypeStatusWillSupply: + if strings.Contains(sam.MessageInfo.Note, RESHARE_ADD_LOAN_CONDITION) { + pr.State = BorrowerStateConditionPending + // TODO Save conditions + } else { + pr.State = BorrowerStateWillSupply + } + // TODO should we check if supplier is set ? and search if not + return m.updatePatronRequestAndCreateSamResponse(ctx, pr, sam) + case iso18626.TypeStatusLoaned: + pr.State = BorrowerStateShipped + return m.updatePatronRequestAndCreateSamResponse(ctx, pr, sam) + case iso18626.TypeStatusLoanCompleted, iso18626.TypeStatusCopyCompleted: + pr.State = BorrowerStateCompleted + return m.updatePatronRequestAndCreateSamResponse(ctx, pr, sam) + case iso18626.TypeStatusUnfilled: + pr.State = BorrowerStateUnfilled + return m.updatePatronRequestAndCreateSamResponse(ctx, pr, sam) + } + return createSAMResponse(sam, iso18626.TypeMessageStatusERROR, &iso18626.ErrorData{ + ErrorType: iso18626.TypeErrorTypeBadlyFormedMessage, + ErrorValue: "status change no allowed", + }) +} + +func (m *PatronRequestMessageHandler) updatePatronRequestAndCreateSamResponse(ctx common.ExtendedContext, pr pr_db.PatronRequest, sam iso18626.SupplyingAgencyMessage) (events.EventStatus, *events.EventResult) { + _, err := m.prRepo.SavePatronRequest(ctx, pr_db.SavePatronRequestParams(pr)) + if err != nil { + return createSAMResponse(sam, iso18626.TypeMessageStatusERROR, &iso18626.ErrorData{ + ErrorType: iso18626.TypeErrorTypeUnrecognisedDataValue, + ErrorValue: err.Error(), + }) + } + return createSAMResponse(sam, iso18626.TypeMessageStatusOK, nil) +} + +func createSAMResponse(sam iso18626.SupplyingAgencyMessage, messageStatus iso18626.TypeMessageStatus, errorData *iso18626.ErrorData) (events.EventStatus, *events.EventResult) { + eventStatus := events.EventStatusSuccess + if messageStatus != iso18626.TypeMessageStatusOK { + eventStatus = events.EventStatusProblem + } + return eventStatus, &events.EventResult{CommonEventData: events.CommonEventData{ + OutgoingMessage: &iso18626.ISO18626Message{ + SupplyingAgencyMessageConfirmation: &iso18626.SupplyingAgencyMessageConfirmation{ + ConfirmationHeader: iso18626.ConfirmationHeader{ + SupplyingAgencyId: &sam.Header.SupplyingAgencyId, + RequestingAgencyId: &sam.Header.RequestingAgencyId, + RequestingAgencyRequestId: sam.Header.RequestingAgencyRequestId, + MessageStatus: messageStatus, + }, + ReasonForMessage: &sam.MessageInfo.ReasonForMessage, + ErrorData: errorData, + }, + }, + }} +} diff --git a/broker/test/apputils/apputils.go b/broker/test/apputils/apputils.go index 4fc0b6d4..140eaabc 100644 --- a/broker/test/apputils/apputils.go +++ b/broker/test/apputils/apputils.go @@ -71,6 +71,7 @@ func GetEventIdWithData(t *testing.T, eventRepo events.EventRepo, illId string, EventStatus: status, EventData: data, LastSignal: string(events.SignalTaskCreated), + PatronRequestID: events.DEFAULT_PATRON_REQUEST_ID, }) if err != nil { diff --git a/broker/test/events/eventbus_test.go b/broker/test/events/eventbus_test.go index fdc54561..32d6d4fc 100644 --- a/broker/test/events/eventbus_test.go +++ b/broker/test/events/eventbus_test.go @@ -216,6 +216,7 @@ func TestTransactionRollback(t *testing.T) { EventName: events.EventNameMessageRequester, EventStatus: events.EventStatusNew, EventData: events.EventData{}, + PatronRequestID: events.DEFAULT_PATRON_REQUEST_ID, }) if err != nil { t.Error("Should not be error") diff --git a/broker/test/patron_request/api/api-handler_test.go b/broker/test/patron_request/api/api-handler_test.go index f01ff07f..466fc97a 100644 --- a/broker/test/patron_request/api/api-handler_test.go +++ b/broker/test/patron_request/api/api-handler_test.go @@ -96,8 +96,8 @@ func TestCrud(t *testing.T) { err = json.Unmarshal(respBytes, &foundPrs) assert.NoError(t, err, "failed to unmarshal patron request") - assert.Len(t, foundPrs, 1) - assert.Equal(t, newPr.ID, foundPrs[0].ID) + assert.Len(t, foundPrs, 2) + assert.Equal(t, newPr.ID, foundPrs[1].ID) // GET by id thisPrPath := basePath + "/" + newPr.ID From 9b8cb7c03222522c8b418268d3407f980bf1e11a Mon Sep 17 00:00:00 2001 From: Janis Saldabols Date: Thu, 6 Nov 2025 16:57:01 +0200 Subject: [PATCH 05/10] CROSSLINK-181 Clean patron request API --- broker/patron_request/api/api-handler.go | 18 +++------ broker/patron_request/oapi/open-api.yaml | 38 ++++++++++++++++++- .../patron_request/api/api-handler_test.go | 13 +++---- 3 files changed, 47 insertions(+), 22 deletions(-) diff --git a/broker/patron_request/api/api-handler.go b/broker/patron_request/api/api-handler.go index 09c1c6a0..bf2fedac 100644 --- a/broker/patron_request/api/api-handler.go +++ b/broker/patron_request/api/api-handler.go @@ -51,20 +51,12 @@ func (a *PatronRequestApiHandler) PostPatronRequests(w http.ResponseWriter, r *h ctx := common.CreateExtCtxWithArgs(context.Background(), &common.LoggerArgs{ Other: map[string]string{"method": "PostPatronRequests"}, }) - var newPr proapi.PatronRequest + var newPr proapi.CreatePatronRequest err := json.NewDecoder(r.Body).Decode(&newPr) if err != nil { addInternalError(ctx, w, err) return } - if newPr.State != prservice.BorrowerStateNew { - addBadRequestError(ctx, w, errors.New("only "+prservice.BorrowerStateNew+" state is allowed")) - return - } - if newPr.Side != prservice.SideBorrowing { - addBadRequestError(ctx, w, errors.New("only "+prservice.SideBorrowing+" side is allowed")) - return - } pr, err := a.prRepo.SavePatronRequest(ctx, (pr_db.SavePatronRequestParams)(toDbPatronRequest(newPr))) if err != nil { @@ -126,7 +118,7 @@ func (a *PatronRequestApiHandler) PutPatronRequestsId(w http.ResponseWriter, r * ctx := common.CreateExtCtxWithArgs(context.Background(), &common.LoggerArgs{ Other: map[string]string{"method": "GetPatronRequestsId", "id": id}, }) - var updatePr proapi.PatronRequest + var updatePr proapi.UpdatePatronRequest err := json.NewDecoder(r.Body).Decode(&updatePr) if err != nil { addInternalError(ctx, w, err) @@ -292,7 +284,7 @@ func toStringFromBytes(bytes []byte) *string { return value } -func toDbPatronRequest(request proapi.PatronRequest) pr_db.PatronRequest { +func toDbPatronRequest(request proapi.CreatePatronRequest) pr_db.PatronRequest { var illRequest []byte if request.IllRequest != nil { illRequest = []byte(*request.IllRequest) @@ -300,8 +292,8 @@ func toDbPatronRequest(request proapi.PatronRequest) pr_db.PatronRequest { return pr_db.PatronRequest{ ID: getId(request.ID), Timestamp: pgtype.Timestamp{Valid: true, Time: request.Timestamp}, - State: request.State, - Side: request.Side, + State: prservice.BorrowerStateNew, + Side: prservice.SideBorrowing, Requester: getDbText(request.Requester), BorrowingPeerID: getDbText(request.BorrowingPeerId), LendingPeerID: getDbText(request.LendingPeerId), diff --git a/broker/patron_request/oapi/open-api.yaml b/broker/patron_request/oapi/open-api.yaml index ab440a7b..17084a6a 100644 --- a/broker/patron_request/oapi/open-api.yaml +++ b/broker/patron_request/oapi/open-api.yaml @@ -39,6 +39,40 @@ components: - Timestamp - State - Side + + CreatePatronRequest: + type: object + properties: + ID: + type: string + description: Unique identifier of the patron request + Timestamp: + type: string + format: date-time + description: Timestamp of the patron reuqest + IllRequest: + type: string + description: JSON of ISO18626 request + Requester: + type: string + description: User who requested item + BorrowingPeerId: + type: string + description: Borrowing peer id + LendingPeerId: + type: string + description: Lending peer id + required: + - ID + - Timestamp + + UpdatePatronRequest: + type: object + properties: + Requester: + type: string + description: User who requested item + Error: type: object properties: @@ -103,7 +137,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/PatronRequest' + $ref: '#/components/schemas/CreatePatronRequest' responses: '201': description: Patron request created successfully @@ -162,7 +196,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/PatronRequest' + $ref: '#/components/schemas/UpdatePatronRequest' responses: '200': description: Patron request updated successfully diff --git a/broker/test/patron_request/api/api-handler_test.go b/broker/test/patron_request/api/api-handler_test.go index 466fc97a..e7f06f1e 100644 --- a/broker/test/patron_request/api/api-handler_test.go +++ b/broker/test/patron_request/api/api-handler_test.go @@ -7,6 +7,7 @@ import ( "github.com/google/uuid" "github.com/indexdata/crosslink/broker/app" proapi "github.com/indexdata/crosslink/broker/patron_request/oapi" + prservice "github.com/indexdata/crosslink/broker/patron_request/service" apptest "github.com/indexdata/crosslink/broker/test/apputils" test "github.com/indexdata/crosslink/broker/test/utils" "github.com/indexdata/go-utils/utils" @@ -62,10 +63,8 @@ func TestCrud(t *testing.T) { borrowingId := "b1" requester := "r1" illMessage := "{\"request\": {}}" - newPr := proapi.PatronRequest{ + newPr := proapi.CreatePatronRequest{ ID: uuid.NewString(), - State: "new", - Side: "landing", Timestamp: time.Now(), LendingPeerId: &landingId, BorrowingPeerId: &borrowingId, @@ -82,8 +81,8 @@ func TestCrud(t *testing.T) { assert.NoError(t, err, "failed to unmarshal patron request") assert.Equal(t, newPr.ID, foundPr.ID) - assert.Equal(t, newPr.State, foundPr.State) - assert.Equal(t, newPr.Side, foundPr.Side) + assert.True(t, foundPr.State != "") + assert.Equal(t, prservice.SideBorrowing, foundPr.Side) assert.Equal(t, newPr.Timestamp.YearDay(), foundPr.Timestamp.YearDay()) assert.Equal(t, *newPr.LendingPeerId, *foundPr.LendingPeerId) assert.Equal(t, *newPr.BorrowingPeerId, *foundPr.BorrowingPeerId) @@ -126,8 +125,8 @@ func TestCrud(t *testing.T) { err = json.Unmarshal(respBytes, &foundPr) assert.NoError(t, err, "failed to unmarshal patron request") assert.Equal(t, newPr.ID, foundPr.ID) - assert.Equal(t, newPr.State, foundPr.State) - assert.Equal(t, newPr.Side, foundPr.Side) + assert.True(t, foundPr.State != "ACCEPTED") + assert.Equal(t, prservice.SideBorrowing, foundPr.Side) assert.Equal(t, newPr.Timestamp.YearDay(), foundPr.Timestamp.YearDay()) assert.Equal(t, "l1", *foundPr.LendingPeerId) assert.Equal(t, "b1", *foundPr.BorrowingPeerId) From 688ddb8af5e063e7a3d15cc5c147bbda0e185340 Mon Sep 17 00:00:00 2001 From: Janis Saldabols Date: Fri, 14 Nov 2025 10:49:01 +0200 Subject: [PATCH 06/10] CROSSLINK-181 Add unit tests --- broker/app/app.go | 2 +- broker/client/client.go | 2 +- broker/client/client_test.go | 33 ++ broker/handler/iso18626-handler.go | 5 + broker/patron_request/service/action.go | 94 +++- broker/patron_request/service/action_test.go | 409 ++++++++++++++++++ .../patron_request/service/message-handler.go | 3 + .../service/message-handler_test.go | 269 ++++++++++++ .../patron_request/api/api-handler_test.go | 22 +- 9 files changed, 815 insertions(+), 24 deletions(-) create mode 100644 broker/patron_request/service/action_test.go create mode 100644 broker/patron_request/service/message-handler_test.go diff --git a/broker/app/app.go b/broker/app/app.go index 0f4f0596..407bc9f6 100644 --- a/broker/app/app.go +++ b/broker/app/app.go @@ -156,7 +156,7 @@ func Init(ctx context.Context) (Context, error) { iso18626Handler := handler.CreateIso18626Handler(eventBus, eventRepo, illRepo, dirAdapter) supplierLocator := service.CreateSupplierLocator(eventBus, illRepo, dirAdapter, holdingsAdapter) workflowManager := service.CreateWorkflowManager(eventBus, illRepo, service.WorkflowConfig{}) - prActionService := prservice.CreatePatronRequestAction(prRepo, illRepo, eventBus, iso18626Handler) + prActionService := prservice.CreatePatronRequestAction(prRepo, illRepo, eventBus, &iso18626Handler) prApiHandler := prapi.NewApiHandler(prRepo, eventBus) AddDefaultHandlers(eventBus, iso18626Client, supplierLocator, workflowManager, iso18626Handler, prActionService, prApiHandler, prMessageHandler) diff --git a/broker/client/client.go b/broker/client/client.go index fc905fda..f742bddb 100644 --- a/broker/client/client.go +++ b/broker/client/client.go @@ -441,7 +441,7 @@ func (c *Iso18626Client) checkConfirmationError(ctx common.ExtendedContext, resp } func (c *Iso18626Client) SendIllMessage(ctx common.ExtendedContext, peer *ill_db.Peer, msg *iso18626.ISO18626Message) (*iso18626.ISO18626Message, error) { - if strings.Contains(peer.Name, "local") { // TODO + if strings.Contains(peer.Name, "local") { // TODO Implement real check of local peer return c.prMessageHandler.HandleMessage(ctx, msg) } else { return c.SendHttpPost(peer, msg) diff --git a/broker/client/client_test.go b/broker/client/client_test.go index 151e5f5a..3a58593b 100644 --- a/broker/client/client_test.go +++ b/broker/client/client_test.go @@ -4,7 +4,10 @@ import ( "context" "encoding/json" "encoding/xml" + "errors" + pr_db "github.com/indexdata/crosslink/broker/patron_request/db" prservice "github.com/indexdata/crosslink/broker/patron_request/service" + "github.com/stretchr/testify/mock" "net/http" "net/http/httptest" "testing" @@ -846,3 +849,33 @@ func TestUpdateSupplierNote(t *testing.T) { updateSupplierNote(trCtx, &sam) assert.Equal(t, "Supplier: SUP1, Original note 2", sam.MessageInfo.Note) } + +func TestSendIllMessage(t *testing.T) { + mockPrMessageHandler := prservice.CreatePatronRequestMessageHandler(new(MockPrRepo), new(events.PgEventRepo), new(ill_db.PgIllRepo), new(events.PostgresEventBus)) + appCtx := common.CreateExtCtxWithArgs(context.Background(), nil) + + client := CreateIso18626Client(new(events.PostgresEventBus), new(MockIllRepositorySkippedSup), mockPrMessageHandler, 1, 0*time.Second) + sam := iso18626.ISO18626Message{ + SupplyingAgencyMessage: &iso18626.SupplyingAgencyMessage{ + Header: iso18626.Header{ + RequestingAgencyRequestId: "req-1", + }, + }, + } + + // To local peer + _, err := client.SendIllMessage(appCtx, &ill_db.Peer{Name: "local peer"}, &sam) + assert.Equal(t, "searching pr with id=req-1", err.Error()) + + _, err = client.SendIllMessage(appCtx, &ill_db.Peer{Name: "random peer"}, &sam) + assert.Equal(t, "Post \"\": unsupported protocol scheme \"\"", err.Error()) +} + +type MockPrRepo struct { + mock.Mock + pr_db.PgPrRepo +} + +func (r *MockPrRepo) GetPatronRequestById(ctx common.ExtendedContext, id string) (pr_db.PatronRequest, error) { + return pr_db.PatronRequest{}, errors.New("searching pr with id=" + id) +} diff --git a/broker/handler/iso18626-handler.go b/broker/handler/iso18626-handler.go index 3d1c4801..c3c0a393 100644 --- a/broker/handler/iso18626-handler.go +++ b/broker/handler/iso18626-handler.go @@ -59,6 +59,11 @@ var ErrRetryNotPossible = errors.New(string(RetryNotPossible)) var waitingReqs = map[string]RequestWait{} +type Iso18626HandlerInterface interface { + HandleRequest(ctx common.ExtendedContext, illMessage *iso18626.ISO18626Message, w http.ResponseWriter) + HandleRequestingAgencyMessage(ctx common.ExtendedContext, illMessage *iso18626.ISO18626Message, w http.ResponseWriter) +} + type Iso18626Handler struct { eventBus events.EventBus eventRepo events.EventRepo diff --git a/broker/patron_request/service/action.go b/broker/patron_request/service/action.go index 304f14d6..209e1096 100644 --- a/broker/patron_request/service/action.go +++ b/broker/patron_request/service/action.go @@ -61,10 +61,10 @@ type PatronRequestActionService struct { prRepo pr_db.PrRepo illRepo ill_db.IllRepo eventBus events.EventBus - iso18626Handler handler.Iso18626Handler + iso18626Handler handler.Iso18626HandlerInterface } -func CreatePatronRequestAction(prRepo pr_db.PrRepo, illRepo ill_db.IllRepo, eventBus events.EventBus, iso18626Handler handler.Iso18626Handler) PatronRequestActionService { +func CreatePatronRequestAction(prRepo pr_db.PrRepo, illRepo ill_db.IllRepo, eventBus events.EventBus, iso18626Handler handler.Iso18626HandlerInterface) PatronRequestActionService { return PatronRequestActionService{ prRepo: prRepo, illRepo: illRepo, @@ -115,6 +115,12 @@ func (a *PatronRequestActionService) handleInvokeAction(ctx common.ExtendedConte return a.checkinBorrowingRequest(ctx, pr) case ActionShipReturn: return a.shipReturnBorrowingRequest(ctx, pr) + case ActionCancelRequest: + return a.cancelBorrowingRequest(ctx, pr) + case ActionAcceptCondition: + return a.acceptConditionBorrowingRequest(ctx, pr) + case ActionRejectCondition: + return a.rejectConditionBorrowingRequest(ctx, pr) default: return events.LogErrorAndReturnResult(ctx, "action "+action+" is not implemented yet", errors.New("invalid action")) } @@ -137,13 +143,14 @@ func (a *PatronRequestActionService) validateBorrowingRequest(ctx common.Extende func (a *PatronRequestActionService) sendBorrowingRequest(ctx common.ExtendedContext, pr pr_db.PatronRequest) (events.EventStatus, *events.EventResult) { result := events.EventResult{} if !pr.BorrowingPeerID.Valid { - result.Note = "missing borrowing peer id" - return events.EventStatusProblem, &result + return events.LogErrorAndReturnResult(ctx, "missing borrowing peer id", nil) } borrowerSymbols, err := a.illRepo.GetSymbolsByPeerId(ctx, pr.BorrowingPeerID.String) if err != nil { - result.Note = err.Error() - return events.EventStatusProblem, &result + return events.LogErrorAndReturnResult(ctx, "cannot fetch borrowing peer symbols", err) + } + if borrowerSymbols == nil { + return events.LogErrorAndReturnResult(ctx, "missing borrowing peer symbols", err) } borrowerSymbol := strings.SplitN(borrowerSymbols[0].SymbolValue, ":", 2) var illMessage = iso18626.ISO18626Message{ @@ -175,8 +182,16 @@ func (a *PatronRequestActionService) sendBorrowingRequest(ctx common.ExtendedCon } func (a *PatronRequestActionService) receiveBorrowingRequest(ctx common.ExtendedContext, pr pr_db.PatronRequest) (events.EventStatus, *events.EventResult) { - // TODO Make NCIP calls - return a.updateStateAndReturnResult(ctx, pr, BorrowerStateReceived, nil) + result := events.EventResult{} + status, eventResult, httpStatus := a.sendRequestingAgencyMessage(ctx, pr, &result, iso18626.TypeActionReceived) + if httpStatus == nil { + return status, eventResult + } + if *httpStatus != http.StatusOK || result.IncomingMessage == nil || result.IncomingMessage.RequestingAgencyMessageConfirmation == nil || + result.IncomingMessage.RequestingAgencyMessageConfirmation.ConfirmationHeader.MessageStatus != iso18626.TypeMessageStatusOK { + return events.EventStatusProblem, &result + } + return a.updateStateAndReturnResult(ctx, pr, BorrowerStateReceived, &result) } func (a *PatronRequestActionService) checkoutBorrowingRequest(ctx common.ExtendedContext, pr pr_db.PatronRequest) (events.EventStatus, *events.EventResult) { @@ -191,19 +206,43 @@ func (a *PatronRequestActionService) checkinBorrowingRequest(ctx common.Extended func (a *PatronRequestActionService) shipReturnBorrowingRequest(ctx common.ExtendedContext, pr pr_db.PatronRequest) (events.EventStatus, *events.EventResult) { result := events.EventResult{} - if !pr.BorrowingPeerID.Valid { - result.Note = "missing borrowing peer id" + status, eventResult, httpStatus := a.sendRequestingAgencyMessage(ctx, pr, &result, iso18626.TypeActionShippedReturn) + if httpStatus == nil { + return status, eventResult + } + if *httpStatus != http.StatusOK || result.IncomingMessage == nil || result.IncomingMessage.RequestingAgencyMessageConfirmation == nil || + result.IncomingMessage.RequestingAgencyMessageConfirmation.ConfirmationHeader.MessageStatus != iso18626.TypeMessageStatusOK { return events.EventStatusProblem, &result } + return a.updateStateAndReturnResult(ctx, pr, BorrowerStateShippedReturned, &result) +} + +func (a *PatronRequestActionService) sendRequestingAgencyMessage(ctx common.ExtendedContext, pr pr_db.PatronRequest, result *events.EventResult, action iso18626.TypeAction) (events.EventStatus, *events.EventResult, *int) { + if !pr.BorrowingPeerID.Valid { + status, eventResult := events.LogErrorAndReturnResult(ctx, "missing borrowing peer id", nil) + return status, eventResult, nil + } borrowerSymbols, err := a.illRepo.GetSymbolsByPeerId(ctx, pr.BorrowingPeerID.String) if err != nil { - result.Note = err.Error() - return events.EventStatusProblem, &result + status, eventResult := events.LogErrorAndReturnResult(ctx, "cannot fetch borrowing peer symbols", err) + return status, eventResult, nil + } + if borrowerSymbols == nil { + status, eventResult := events.LogErrorAndReturnResult(ctx, "missing borrowing peer symbols", err) + return status, eventResult, nil + } + if !pr.LendingPeerID.Valid { + status, eventResult := events.LogErrorAndReturnResult(ctx, "missing lending peer id", nil) + return status, eventResult, nil } lenderSymbols, err := a.illRepo.GetSymbolsByPeerId(ctx, pr.LendingPeerID.String) if err != nil { - result.Note = err.Error() - return events.EventStatusProblem, &result + status, eventResult := events.LogErrorAndReturnResult(ctx, "cannot fetch lending peer symbols", err) + return status, eventResult, nil + } + if lenderSymbols == nil { + status, eventResult := events.LogErrorAndReturnResult(ctx, "missing lending peer symbols", err) + return status, eventResult, nil } borrowerSymbol := strings.SplitN(borrowerSymbols[0].SymbolValue, ":", 2) lenderSymbol := strings.SplitN(lenderSymbols[0].SymbolValue, ":", 2) @@ -224,18 +263,37 @@ func (a *PatronRequestActionService) shipReturnBorrowingRequest(ctx common.Exten }, RequestingAgencyRequestId: pr.ID, }, - Action: iso18626.TypeActionShippedReturn, + Action: action, }, } w := NewResponseCaptureWriter() a.iso18626Handler.HandleRequestingAgencyMessage(ctx, &illMessage, w) result.OutgoingMessage = &illMessage result.IncomingMessage = w.IllMessage - if w.StatusCode != http.StatusOK || w.IllMessage == nil || w.IllMessage.RequestingAgencyMessageConfirmation == nil || - w.IllMessage.RequestingAgencyMessageConfirmation.ConfirmationHeader.MessageStatus != iso18626.TypeMessageStatusOK { + return "", nil, &w.StatusCode +} + +func (a *PatronRequestActionService) cancelBorrowingRequest(ctx common.ExtendedContext, pr pr_db.PatronRequest) (events.EventStatus, *events.EventResult) { + result := events.EventResult{} + status, eventResult, httpStatus := a.sendRequestingAgencyMessage(ctx, pr, &result, iso18626.TypeActionCancel) + if httpStatus == nil { + return status, eventResult + } + if *httpStatus != http.StatusOK || result.IncomingMessage == nil || result.IncomingMessage.RequestingAgencyMessageConfirmation == nil || + result.IncomingMessage.RequestingAgencyMessageConfirmation.ConfirmationHeader.MessageStatus != iso18626.TypeMessageStatusOK { return events.EventStatusProblem, &result } - return a.updateStateAndReturnResult(ctx, pr, BorrowerStateShippedReturned, nil) + return a.updateStateAndReturnResult(ctx, pr, BorrowerStateCancelPending, &result) +} + +func (a *PatronRequestActionService) acceptConditionBorrowingRequest(ctx common.ExtendedContext, pr pr_db.PatronRequest) (events.EventStatus, *events.EventResult) { + // TODO Make NCIP calls + return a.updateStateAndReturnResult(ctx, pr, BorrowerStateWillSupply, nil) +} + +func (a *PatronRequestActionService) rejectConditionBorrowingRequest(ctx common.ExtendedContext, pr pr_db.PatronRequest) (events.EventStatus, *events.EventResult) { + // TODO Make NCIP calls + return a.updateStateAndReturnResult(ctx, pr, BorrowerStateCancelPending, nil) } type ResponseCaptureWriter struct { diff --git a/broker/patron_request/service/action_test.go b/broker/patron_request/service/action_test.go new file mode 100644 index 00000000..92a8a60e --- /dev/null +++ b/broker/patron_request/service/action_test.go @@ -0,0 +1,409 @@ +package prservice + +import ( + "context" + "encoding/xml" + "errors" + "github.com/indexdata/crosslink/broker/common" + "github.com/indexdata/crosslink/broker/events" + "github.com/indexdata/crosslink/broker/handler" + "github.com/indexdata/crosslink/broker/ill_db" + pr_db "github.com/indexdata/crosslink/broker/patron_request/db" + "github.com/indexdata/crosslink/iso18626" + "github.com/jackc/pgx/v5/pgtype" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "net/http" + "strings" + "testing" +) + +var appCtx = common.CreateExtCtxWithArgs(context.Background(), nil) +var patronRequestId = "pr1" + +func TestGetBorrowerActionsByState(t *testing.T) { + assert.Equal(t, []string{ActionValidate}, GetBorrowerActionsByState(BorrowerStateNew)) + assert.Equal(t, []string{}, GetBorrowerActionsByState(BorrowerStateCompleted)) +} + +func TestIsBorrowerActionAvailable(t *testing.T) { + assert.True(t, IsBorrowerActionAvailable(BorrowerStateNew, ActionValidate)) + assert.False(t, IsBorrowerActionAvailable(BorrowerStateNew, ActionCheckOut)) + assert.False(t, IsBorrowerActionAvailable(BorrowerStateCompleted, ActionValidate)) +} +func TestInvokeAction(t *testing.T) { + mockEventBus := new(MockEventBus) + prAction := CreatePatronRequestAction(*new(pr_db.PrRepo), *new(ill_db.IllRepo), mockEventBus, new(handler.Iso18626Handler)) + event := events.Event{ + ID: "action-1", + } + mockEventBus.On("ProcessTask", event.ID).Return(event, nil) + + prAction.InvokeAction(appCtx, event) + + mockEventBus.AssertNumberOfCalls(t, "ProcessTask", 1) +} + +func TestHandleInvokeActionNotSpecifiedAction(t *testing.T) { + prAction := CreatePatronRequestAction(*new(pr_db.PrRepo), *new(ill_db.IllRepo), *new(events.EventBus), new(handler.Iso18626Handler)) + + status, resultData := prAction.handleInvokeAction(appCtx, events.Event{}) + + assert.Equal(t, events.EventStatusError, status) + assert.Equal(t, "action not specified", resultData.EventError.Message) +} + +func TestHandleInvokeActionNoPR(t *testing.T) { + mockPrRepo := new(MockPrRepo) + prAction := CreatePatronRequestAction(mockPrRepo, *new(ill_db.IllRepo), *new(events.EventBus), new(handler.Iso18626Handler)) + mockPrRepo.On("GetPatronRequestById", patronRequestId).Return(pr_db.PatronRequest{}, errors.New("not fund")) + + status, resultData := prAction.handleInvokeAction(appCtx, events.Event{PatronRequestID: patronRequestId, EventData: events.EventData{CommonEventData: events.CommonEventData{Action: &ActionValidate}}}) + + assert.Equal(t, events.EventStatusError, status) + assert.Equal(t, "failed to read patron request", resultData.EventError.Message) +} + +func TestHandleInvokeActionWhichIsNotAllowed(t *testing.T) { + mockPrRepo := new(MockPrRepo) + prAction := CreatePatronRequestAction(mockPrRepo, *new(ill_db.IllRepo), *new(events.EventBus), new(handler.Iso18626Handler)) + mockPrRepo.On("GetPatronRequestById", patronRequestId).Return(pr_db.PatronRequest{State: BorrowerStateValidated}, nil) + + status, resultData := prAction.handleInvokeAction(appCtx, events.Event{PatronRequestID: patronRequestId, EventData: events.EventData{CommonEventData: events.CommonEventData{Action: &ActionValidate}}}) + + assert.Equal(t, events.EventStatusError, status) + assert.Equal(t, "state VALIDATED does not support action validate", resultData.EventError.Message) +} + +func TestHandleInvokeActionValidate(t *testing.T) { + mockPrRepo := new(MockPrRepo) + prAction := CreatePatronRequestAction(mockPrRepo, *new(ill_db.IllRepo), *new(events.EventBus), new(handler.Iso18626Handler)) + mockPrRepo.On("GetPatronRequestById", patronRequestId).Return(pr_db.PatronRequest{State: BorrowerStateNew}, nil) + + status, resultData := prAction.handleInvokeAction(appCtx, events.Event{PatronRequestID: patronRequestId, EventData: events.EventData{CommonEventData: events.CommonEventData{Action: &ActionValidate}}}) + + assert.Equal(t, events.EventStatusSuccess, status) + assert.Nil(t, resultData) + assert.Equal(t, BorrowerStateValidated, mockPrRepo.savedPr.State) +} + +func TestHandleInvokeActionSendRequest(t *testing.T) { + mockPrRepo := new(MockPrRepo) + mockIso18626Handler := new(MockIso18626Handler) + prAction := CreatePatronRequestAction(mockPrRepo, new(MockIllRepo), *new(events.EventBus), mockIso18626Handler) + mockPrRepo.On("GetPatronRequestById", patronRequestId).Return(pr_db.PatronRequest{State: BorrowerStateValidated, BorrowingPeerID: pgtype.Text{Valid: true, String: "peer1"}}, nil) + + status, resultData := prAction.handleInvokeAction(appCtx, events.Event{PatronRequestID: patronRequestId, EventData: events.EventData{CommonEventData: events.CommonEventData{Action: &ActionSendRequest}}}) + + assert.Equal(t, events.EventStatusSuccess, status) + assert.Equal(t, iso18626.TypeMessageStatusOK, resultData.IncomingMessage.RequestConfirmation.ConfirmationHeader.MessageStatus) + assert.Equal(t, BorrowerStateSent, mockPrRepo.savedPr.State) +} + +func TestHandleInvokeActionReceive(t *testing.T) { + mockPrRepo := new(MockPrRepo) + mockIso18626Handler := new(MockIso18626Handler) + prAction := CreatePatronRequestAction(mockPrRepo, new(MockIllRepo), *new(events.EventBus), mockIso18626Handler) + mockPrRepo.On("GetPatronRequestById", patronRequestId).Return(pr_db.PatronRequest{State: BorrowerStateShipped, BorrowingPeerID: pgtype.Text{Valid: true, String: "peer1"}, LendingPeerID: pgtype.Text{Valid: true, String: "peer1"}}, nil) + + status, resultData := prAction.handleInvokeAction(appCtx, events.Event{PatronRequestID: patronRequestId, EventData: events.EventData{CommonEventData: events.CommonEventData{Action: &ActionReceive}}}) + + assert.Equal(t, events.EventStatusSuccess, status) + assert.Equal(t, iso18626.TypeMessageStatusOK, resultData.IncomingMessage.RequestingAgencyMessageConfirmation.ConfirmationHeader.MessageStatus) + assert.Equal(t, BorrowerStateReceived, mockPrRepo.savedPr.State) +} + +func TestHandleInvokeActionCheckOut(t *testing.T) { + mockPrRepo := new(MockPrRepo) + prAction := CreatePatronRequestAction(mockPrRepo, *new(ill_db.IllRepo), *new(events.EventBus), new(handler.Iso18626Handler)) + mockPrRepo.On("GetPatronRequestById", patronRequestId).Return(pr_db.PatronRequest{State: BorrowerStateReceived}, nil) + + status, resultData := prAction.handleInvokeAction(appCtx, events.Event{PatronRequestID: patronRequestId, EventData: events.EventData{CommonEventData: events.CommonEventData{Action: &ActionCheckOut}}}) + + assert.Equal(t, events.EventStatusSuccess, status) + assert.Nil(t, resultData) + assert.Equal(t, BorrowerStateCheckedOut, mockPrRepo.savedPr.State) +} + +func TestHandleInvokeActionCheckIn(t *testing.T) { + mockPrRepo := new(MockPrRepo) + prAction := CreatePatronRequestAction(mockPrRepo, *new(ill_db.IllRepo), *new(events.EventBus), new(handler.Iso18626Handler)) + mockPrRepo.On("GetPatronRequestById", patronRequestId).Return(pr_db.PatronRequest{State: BorrowerStateCheckedOut}, nil) + + status, resultData := prAction.handleInvokeAction(appCtx, events.Event{PatronRequestID: patronRequestId, EventData: events.EventData{CommonEventData: events.CommonEventData{Action: &ActionCheckIn}}}) + + assert.Equal(t, events.EventStatusSuccess, status) + assert.Nil(t, resultData) + assert.Equal(t, BorrowerStateCheckedIn, mockPrRepo.savedPr.State) +} + +func TestHandleInvokeActionShipReturn(t *testing.T) { + mockPrRepo := new(MockPrRepo) + mockIso18626Handler := new(MockIso18626Handler) + prAction := CreatePatronRequestAction(mockPrRepo, new(MockIllRepo), *new(events.EventBus), mockIso18626Handler) + mockPrRepo.On("GetPatronRequestById", patronRequestId).Return(pr_db.PatronRequest{State: BorrowerStateCheckedIn, BorrowingPeerID: pgtype.Text{Valid: true, String: "peer1"}, LendingPeerID: pgtype.Text{Valid: true, String: "peer1"}}, nil) + + status, resultData := prAction.handleInvokeAction(appCtx, events.Event{PatronRequestID: patronRequestId, EventData: events.EventData{CommonEventData: events.CommonEventData{Action: &ActionShipReturn}}}) + + assert.Equal(t, events.EventStatusSuccess, status) + assert.Equal(t, iso18626.TypeMessageStatusOK, resultData.IncomingMessage.RequestingAgencyMessageConfirmation.ConfirmationHeader.MessageStatus) + assert.Equal(t, BorrowerStateShippedReturned, mockPrRepo.savedPr.State) +} + +func TestHandleInvokeActionCancelRequest(t *testing.T) { + mockPrRepo := new(MockPrRepo) + mockIso18626Handler := new(MockIso18626Handler) + prAction := CreatePatronRequestAction(mockPrRepo, new(MockIllRepo), *new(events.EventBus), mockIso18626Handler) + mockPrRepo.On("GetPatronRequestById", patronRequestId).Return(pr_db.PatronRequest{State: BorrowerStateWillSupply, BorrowingPeerID: pgtype.Text{Valid: true, String: "peer1"}, LendingPeerID: pgtype.Text{Valid: true, String: "peer1"}}, nil) + + status, resultData := prAction.handleInvokeAction(appCtx, events.Event{PatronRequestID: patronRequestId, EventData: events.EventData{CommonEventData: events.CommonEventData{Action: &ActionCancelRequest}}}) + + assert.Equal(t, events.EventStatusSuccess, status) + assert.Equal(t, iso18626.TypeMessageStatusOK, resultData.IncomingMessage.RequestingAgencyMessageConfirmation.ConfirmationHeader.MessageStatus) + assert.Equal(t, BorrowerStateCancelPending, mockPrRepo.savedPr.State) +} + +func TestHandleInvokeActionAcceptCondition(t *testing.T) { + mockPrRepo := new(MockPrRepo) + mockIso18626Handler := new(MockIso18626Handler) + prAction := CreatePatronRequestAction(mockPrRepo, new(MockIllRepo), *new(events.EventBus), mockIso18626Handler) + mockPrRepo.On("GetPatronRequestById", patronRequestId).Return(pr_db.PatronRequest{State: BorrowerStateConditionPending, BorrowingPeerID: pgtype.Text{Valid: true, String: "peer1"}, LendingPeerID: pgtype.Text{Valid: true, String: "peer1"}}, nil) + + status, resultData := prAction.handleInvokeAction(appCtx, events.Event{PatronRequestID: patronRequestId, EventData: events.EventData{CommonEventData: events.CommonEventData{Action: &ActionAcceptCondition}}}) + + assert.Equal(t, events.EventStatusSuccess, status) + assert.Nil(t, resultData) + assert.Equal(t, BorrowerStateWillSupply, mockPrRepo.savedPr.State) +} + +func TestHandleInvokeActionRejectCondition(t *testing.T) { + mockPrRepo := new(MockPrRepo) + mockIso18626Handler := new(MockIso18626Handler) + prAction := CreatePatronRequestAction(mockPrRepo, new(MockIllRepo), *new(events.EventBus), mockIso18626Handler) + mockPrRepo.On("GetPatronRequestById", patronRequestId).Return(pr_db.PatronRequest{State: BorrowerStateConditionPending, BorrowingPeerID: pgtype.Text{Valid: true, String: "peer1"}, LendingPeerID: pgtype.Text{Valid: true, String: "peer1"}}, nil) + + status, resultData := prAction.handleInvokeAction(appCtx, events.Event{PatronRequestID: patronRequestId, EventData: events.EventData{CommonEventData: events.CommonEventData{Action: &ActionRejectCondition}}}) + + assert.Equal(t, events.EventStatusSuccess, status) + assert.Nil(t, resultData) + assert.Equal(t, BorrowerStateCancelPending, mockPrRepo.savedPr.State) +} + +func TestSendBorrowingRequestNoRequester(t *testing.T) { + mockPrRepo := new(MockPrRepo) + mockIso18626Handler := new(MockIso18626Handler) + prAction := CreatePatronRequestAction(mockPrRepo, new(MockIllRepo), *new(events.EventBus), mockIso18626Handler) + + status, resultData := prAction.sendBorrowingRequest(appCtx, pr_db.PatronRequest{State: BorrowerStateValidated}) + + assert.Equal(t, events.EventStatusError, status) + assert.Equal(t, "missing borrowing peer id", resultData.EventError.Message) +} + +func TestSendBorrowingRequestSymbolSearchError(t *testing.T) { + mockPrRepo := new(MockPrRepo) + mockIso18626Handler := new(MockIso18626Handler) + prAction := CreatePatronRequestAction(mockPrRepo, new(MockIllRepo), *new(events.EventBus), mockIso18626Handler) + + status, resultData := prAction.sendBorrowingRequest(appCtx, pr_db.PatronRequest{State: BorrowerStateValidated, BorrowingPeerID: pgtype.Text{Valid: true, String: "error"}}) + + assert.Equal(t, events.EventStatusError, status) + assert.Equal(t, "cannot fetch borrowing peer symbols", resultData.EventError.Message) +} + +func TestSendBorrowingRequestSymbolMissing(t *testing.T) { + mockPrRepo := new(MockPrRepo) + mockIso18626Handler := new(MockIso18626Handler) + prAction := CreatePatronRequestAction(mockPrRepo, new(MockIllRepo), *new(events.EventBus), mockIso18626Handler) + + status, resultData := prAction.sendBorrowingRequest(appCtx, pr_db.PatronRequest{State: BorrowerStateValidated, BorrowingPeerID: pgtype.Text{Valid: true, String: "missing"}}) + + assert.Equal(t, events.EventStatusError, status) + assert.Equal(t, "missing borrowing peer symbols", resultData.EventError.Message) +} + +func TestSendBorrowingRequestFailedProcess(t *testing.T) { + mockPrRepo := new(MockPrRepo) + mockIso18626Handler := new(MockIso18626Handler) + prAction := CreatePatronRequestAction(mockPrRepo, new(MockIllRepo), *new(events.EventBus), mockIso18626Handler) + + status, resultData := prAction.sendBorrowingRequest(appCtx, pr_db.PatronRequest{ID: "error", State: BorrowerStateValidated, BorrowingPeerID: pgtype.Text{Valid: true, String: "pr123"}}) + + assert.Equal(t, events.EventStatusProblem, status) + assert.Equal(t, iso18626.TypeMessageStatusERROR, resultData.IncomingMessage.RequestConfirmation.ConfirmationHeader.MessageStatus) +} + +func TestShipReturnBorrowingRequestNoBorrowingId(t *testing.T) { + mockPrRepo := new(MockPrRepo) + mockIso18626Handler := new(MockIso18626Handler) + prAction := CreatePatronRequestAction(mockPrRepo, new(MockIllRepo), *new(events.EventBus), mockIso18626Handler) + + status, resultData := prAction.shipReturnBorrowingRequest(appCtx, pr_db.PatronRequest{ID: "1", State: BorrowerStateValidated}) + + assert.Equal(t, events.EventStatusError, status) + assert.Equal(t, "missing borrowing peer id", resultData.EventError.Message) +} +func TestShipReturnBorrowingRequestErrorBorrowing(t *testing.T) { + mockPrRepo := new(MockPrRepo) + mockIso18626Handler := new(MockIso18626Handler) + prAction := CreatePatronRequestAction(mockPrRepo, new(MockIllRepo), *new(events.EventBus), mockIso18626Handler) + + status, resultData := prAction.shipReturnBorrowingRequest(appCtx, pr_db.PatronRequest{ID: "1", State: BorrowerStateValidated, BorrowingPeerID: pgtype.Text{Valid: true, String: "error"}}) + + assert.Equal(t, events.EventStatusError, status) + assert.Equal(t, "cannot fetch borrowing peer symbols", resultData.EventError.Message) +} +func TestShipReturnBorrowingRequestMissingBorrowing(t *testing.T) { + mockPrRepo := new(MockPrRepo) + mockIso18626Handler := new(MockIso18626Handler) + prAction := CreatePatronRequestAction(mockPrRepo, new(MockIllRepo), *new(events.EventBus), mockIso18626Handler) + + status, resultData := prAction.shipReturnBorrowingRequest(appCtx, pr_db.PatronRequest{ID: "1", State: BorrowerStateValidated, BorrowingPeerID: pgtype.Text{Valid: true, String: "missing"}}) + + assert.Equal(t, events.EventStatusError, status) + assert.Equal(t, "missing borrowing peer symbols", resultData.EventError.Message) +} +func TestShipReturnBorrowingRequestNoLendingId(t *testing.T) { + mockPrRepo := new(MockPrRepo) + mockIso18626Handler := new(MockIso18626Handler) + prAction := CreatePatronRequestAction(mockPrRepo, new(MockIllRepo), *new(events.EventBus), mockIso18626Handler) + + status, resultData := prAction.shipReturnBorrowingRequest(appCtx, pr_db.PatronRequest{ID: "1", State: BorrowerStateValidated, BorrowingPeerID: pgtype.Text{Valid: true, String: "pr123"}}) + + assert.Equal(t, events.EventStatusError, status) + assert.Equal(t, "missing lending peer id", resultData.EventError.Message) +} +func TestShipReturnBorrowingRequestLendingError(t *testing.T) { + mockPrRepo := new(MockPrRepo) + mockIso18626Handler := new(MockIso18626Handler) + prAction := CreatePatronRequestAction(mockPrRepo, new(MockIllRepo), *new(events.EventBus), mockIso18626Handler) + + status, resultData := prAction.shipReturnBorrowingRequest(appCtx, pr_db.PatronRequest{ID: "1", State: BorrowerStateValidated, BorrowingPeerID: pgtype.Text{Valid: true, String: "pr123"}, LendingPeerID: pgtype.Text{Valid: true, String: "error"}}) + + assert.Equal(t, events.EventStatusError, status) + assert.Equal(t, "cannot fetch lending peer symbols", resultData.EventError.Message) +} +func TestShipReturnBorrowingRequestLendingMissing(t *testing.T) { + mockPrRepo := new(MockPrRepo) + mockIso18626Handler := new(MockIso18626Handler) + prAction := CreatePatronRequestAction(mockPrRepo, new(MockIllRepo), *new(events.EventBus), mockIso18626Handler) + + status, resultData := prAction.shipReturnBorrowingRequest(appCtx, pr_db.PatronRequest{ID: "1", State: BorrowerStateValidated, BorrowingPeerID: pgtype.Text{Valid: true, String: "pr123"}, LendingPeerID: pgtype.Text{Valid: true, String: "missing"}}) + + assert.Equal(t, events.EventStatusError, status) + assert.Equal(t, "missing lending peer symbols", resultData.EventError.Message) +} +func TestShipReturnBorrowingRequestProblemProcessing(t *testing.T) { + mockPrRepo := new(MockPrRepo) + mockIso18626Handler := new(MockIso18626Handler) + prAction := CreatePatronRequestAction(mockPrRepo, new(MockIllRepo), *new(events.EventBus), mockIso18626Handler) + + status, resultData := prAction.shipReturnBorrowingRequest(appCtx, pr_db.PatronRequest{ID: "error", State: BorrowerStateValidated, BorrowingPeerID: pgtype.Text{Valid: true, String: "pr123"}, LendingPeerID: pgtype.Text{Valid: true, String: "pr321"}}) + + assert.Equal(t, events.EventStatusProblem, status) + assert.Equal(t, iso18626.TypeMessageStatusERROR, resultData.IncomingMessage.RequestingAgencyMessageConfirmation.ConfirmationHeader.MessageStatus) +} + +type MockEventBus struct { + mock.Mock + events.EventBus +} + +func (m *MockEventBus) ProcessTask(ctx common.ExtendedContext, event events.Event, h func(common.ExtendedContext, events.Event) (events.EventStatus, *events.EventResult)) (events.Event, error) { + args := m.Called(event.ID) + return args.Get(0).(events.Event), args.Error(1) +} + +func (m *MockEventBus) CreateTask(id string, eventName events.EventName, data events.EventData, eventClass events.EventClass, parentId *string) (string, error) { + if id == "error" { + return "", errors.New("event bus error") + } + return id, nil +} + +type MockPrRepo struct { + mock.Mock + pr_db.PgPrRepo + savedPr pr_db.PatronRequest +} + +func (r *MockPrRepo) GetPatronRequestById(ctx common.ExtendedContext, id string) (pr_db.PatronRequest, error) { + args := r.Called(id) + return args.Get(0).(pr_db.PatronRequest), args.Error(1) +} + +func (r *MockPrRepo) SavePatronRequest(ctx common.ExtendedContext, params pr_db.SavePatronRequestParams) (pr_db.PatronRequest, error) { + if strings.Contains(params.ID, "error") { + return pr_db.PatronRequest{}, errors.New("db error") + } + r.savedPr = pr_db.PatronRequest(params) + return pr_db.PatronRequest(params), nil +} + +type MockIso18626Handler struct { + mock.Mock + handler.Iso18626Handler +} + +func (h *MockIso18626Handler) HandleRequest(ctx common.ExtendedContext, illMessage *iso18626.ISO18626Message, w http.ResponseWriter) { + status := iso18626.TypeMessageStatusOK + if illMessage.Request.Header.RequestingAgencyRequestId == "error" { + status = iso18626.TypeMessageStatusERROR + } + var resmsg = &iso18626.ISO18626Message{ + RequestConfirmation: &iso18626.RequestConfirmation{ + ConfirmationHeader: iso18626.ConfirmationHeader{ + MessageStatus: status, + }, + }, + } + output, err := xml.MarshalIndent(resmsg, " ", " ") + if err != nil { + ctx.Logger().Error("failed to produce response", "error", err, "body", string(output)) + return + } + w.Header().Set("Content-Type", "application/xml") + w.WriteHeader(http.StatusOK) + w.Write(output) +} +func (h *MockIso18626Handler) HandleRequestingAgencyMessage(ctx common.ExtendedContext, illMessage *iso18626.ISO18626Message, w http.ResponseWriter) { + status := iso18626.TypeMessageStatusOK + if illMessage.RequestingAgencyMessage.Header.RequestingAgencyRequestId == "error" { + status = iso18626.TypeMessageStatusERROR + } + var resmsg = &iso18626.ISO18626Message{ + RequestingAgencyMessageConfirmation: &iso18626.RequestingAgencyMessageConfirmation{ + ConfirmationHeader: iso18626.ConfirmationHeader{ + MessageStatus: status, + }, + }, + } + output, err := xml.MarshalIndent(resmsg, " ", " ") + if err != nil { + ctx.Logger().Error("failed to produce response", "error", err, "body", string(output)) + return + } + w.Header().Set("Content-Type", "application/xml") + w.WriteHeader(http.StatusOK) + w.Write(output) +} + +type MockIllRepo struct { + mock.Mock + ill_db.PgIllRepo +} + +func (r *MockIllRepo) GetSymbolsByPeerId(ctx common.ExtendedContext, peerId string) ([]ill_db.Symbol, error) { + if peerId == "error" { + return []ill_db.Symbol{}, errors.New("db error") + } + if peerId == "missing" { + return nil, nil + } + return []ill_db.Symbol{{SymbolValue: "ISIL:PEER1", PeerID: peerId}}, nil +} + +func (r *MockIllRepo) GetPeerBySymbol(ctx common.ExtendedContext, symbol string) (ill_db.Peer, error) { + args := r.Called(symbol) + return args.Get(0).(ill_db.Peer), args.Error(1) +} diff --git a/broker/patron_request/service/message-handler.go b/broker/patron_request/service/message-handler.go index 4bac68a9..718aa358 100644 --- a/broker/patron_request/service/message-handler.go +++ b/broker/patron_request/service/message-handler.go @@ -136,6 +136,9 @@ func (m *PatronRequestMessageHandler) handleSupplyingAgencyMessage(ctx common.Ex case iso18626.TypeStatusUnfilled: pr.State = BorrowerStateUnfilled return m.updatePatronRequestAndCreateSamResponse(ctx, pr, sam) + case iso18626.TypeStatusCancelled: + pr.State = BorrowerStateCancelled + return m.updatePatronRequestAndCreateSamResponse(ctx, pr, sam) } return createSAMResponse(sam, iso18626.TypeMessageStatusERROR, &iso18626.ErrorData{ ErrorType: iso18626.TypeErrorTypeBadlyFormedMessage, diff --git a/broker/patron_request/service/message-handler_test.go b/broker/patron_request/service/message-handler_test.go new file mode 100644 index 00000000..ece3719e --- /dev/null +++ b/broker/patron_request/service/message-handler_test.go @@ -0,0 +1,269 @@ +package prservice + +import ( + "errors" + "github.com/indexdata/crosslink/broker/events" + "github.com/indexdata/crosslink/broker/ill_db" + pr_db "github.com/indexdata/crosslink/broker/patron_request/db" + "github.com/indexdata/crosslink/iso18626" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestGetPatronRequestId(t *testing.T) { + msg := iso18626.ISO18626Message{ + Request: &iso18626.Request{ + Header: iso18626.Header{ + RequestingAgencyRequestId: "req-id-1", + SupplyingAgencyRequestId: "sam-id-1", + }, + }, + } + assert.Equal(t, "req-id-1", getPatronRequestId(msg)) + + msg = iso18626.ISO18626Message{ + RequestingAgencyMessage: &iso18626.RequestingAgencyMessage{ + Header: iso18626.Header{ + RequestingAgencyRequestId: "ram-id-1", + SupplyingAgencyRequestId: "sam-id-1", + }, + }, + } + assert.Equal(t, "sam-id-1", getPatronRequestId(msg)) + + msg = iso18626.ISO18626Message{ + SupplyingAgencyMessage: &iso18626.SupplyingAgencyMessage{ + Header: iso18626.Header{ + RequestingAgencyRequestId: "ram-id-1", + SupplyingAgencyRequestId: "sam-id-1", + }, + }, + } + assert.Equal(t, "ram-id-1", getPatronRequestId(msg)) +} + +func TestHandleMessageNoMessage(t *testing.T) { + handler := CreatePatronRequestMessageHandler(*new(pr_db.PrRepo), *new(events.EventRepo), *new(ill_db.IllRepo), *new(events.EventBus)) + + mes, err := handler.HandleMessage(appCtx, nil) + + assert.Nil(t, mes) + assert.Equal(t, "cannot process nil message", err.Error()) +} + +func TestHandleMessageFetchPRError(t *testing.T) { + mockPrRepo := new(MockPrRepo) + mockPrRepo.On("GetPatronRequestById", patronRequestId).Return(pr_db.PatronRequest{}, errors.New("db error")) + + handler := CreatePatronRequestMessageHandler(mockPrRepo, *new(events.EventRepo), *new(ill_db.IllRepo), *new(events.EventBus)) + + mes, err := handler.HandleMessage(appCtx, &iso18626.ISO18626Message{ + SupplyingAgencyMessage: &iso18626.SupplyingAgencyMessage{ + Header: iso18626.Header{ + RequestingAgencyRequestId: patronRequestId, + }, + }, + }) + + assert.Nil(t, mes) + assert.Equal(t, "db error", err.Error()) +} + +func TestHandleMessageFetchEventError(t *testing.T) { + mockPrRepo := new(MockPrRepo) + mockEventBus := new(MockEventBus) + mockPrRepo.On("GetPatronRequestById", patronRequestId).Return(pr_db.PatronRequest{ID: "error"}, nil) + + handler := CreatePatronRequestMessageHandler(mockPrRepo, *new(events.EventRepo), *new(ill_db.IllRepo), mockEventBus) + + mes, err := handler.HandleMessage(appCtx, &iso18626.ISO18626Message{ + SupplyingAgencyMessage: &iso18626.SupplyingAgencyMessage{ + Header: iso18626.Header{ + RequestingAgencyRequestId: patronRequestId, + }, + }, + }) + + assert.Nil(t, mes) + assert.Equal(t, "event bus error", err.Error()) +} + +func TestHandlePatronRequestMessage(t *testing.T) { + mockPrRepo := new(MockPrRepo) + handler := CreatePatronRequestMessageHandler(mockPrRepo, *new(events.EventRepo), *new(ill_db.IllRepo), *new(events.EventBus)) + + status, result := handler.handlePatronRequestMessage(appCtx, events.Event{EventData: events.EventData{CommonEventData: events.CommonEventData{IncomingMessage: &iso18626.ISO18626Message{}}}}) + assert.Equal(t, events.EventStatusError, status) + assert.Equal(t, "cannot process message without content", result.Note) + + status, result = handler.handlePatronRequestMessage(appCtx, events.Event{EventData: events.EventData{CommonEventData: events.CommonEventData{IncomingMessage: &iso18626.ISO18626Message{Request: &iso18626.Request{}}}}}) + assert.Equal(t, events.EventStatusError, status) + assert.Equal(t, "request handling is not implemented yet", result.Note) + + status, result = handler.handlePatronRequestMessage(appCtx, events.Event{EventData: events.EventData{CommonEventData: events.CommonEventData{IncomingMessage: &iso18626.ISO18626Message{RequestingAgencyMessage: &iso18626.RequestingAgencyMessage{}}}}}) + assert.Equal(t, events.EventStatusError, status) + assert.Equal(t, "requesting agency message handling is not implemented yet", result.Note) + + mockPrRepo.On("GetPatronRequestById", patronRequestId).Return(pr_db.PatronRequest{}, errors.New("db error")) + status, result = handler.handlePatronRequestMessage(appCtx, events.Event{EventData: events.EventData{CommonEventData: events.CommonEventData{IncomingMessage: &iso18626.ISO18626Message{SupplyingAgencyMessage: &iso18626.SupplyingAgencyMessage{Header: iso18626.Header{RequestingAgencyRequestId: patronRequestId}}}}}}) + assert.Equal(t, events.EventStatusProblem, status) + assert.Equal(t, "could not find patron request", result.OutgoingMessage.SupplyingAgencyMessageConfirmation.ErrorData.ErrorValue) +} + +func TestHandleSupplyingAgencyMessageNoSupplier(t *testing.T) { + mockPrRepo := new(MockPrRepo) + mockPrRepo.On("GetPatronRequestById", patronRequestId).Return(pr_db.PatronRequest{}, nil) + mockIllRepo := new(MockIllRepo) + mockIllRepo.On("GetPeerBySymbol", "ISIL:SUP1").Return(ill_db.Peer{}, errors.New("db error")) + handler := CreatePatronRequestMessageHandler(mockPrRepo, *new(events.EventRepo), mockIllRepo, *new(events.EventBus)) + + status, result := handler.handleSupplyingAgencyMessage(appCtx, iso18626.SupplyingAgencyMessage{ + Header: iso18626.Header{ + SupplyingAgencyId: iso18626.TypeAgencyId{ + AgencyIdType: iso18626.TypeSchemeValuePair{Text: "ISIL"}, + AgencyIdValue: "SUP1", + }, + RequestingAgencyRequestId: patronRequestId, + }, + StatusInfo: iso18626.StatusInfo{Status: iso18626.TypeStatusExpectToSupply}, + }) + assert.Equal(t, events.EventStatusProblem, status) + assert.Equal(t, "could not find supplier", result.OutgoingMessage.SupplyingAgencyMessageConfirmation.ErrorData.ErrorValue) +} + +func TestHandleSupplyingAgencyMessageExpectToSupply(t *testing.T) { + mockPrRepo := new(MockPrRepo) + mockPrRepo.On("GetPatronRequestById", patronRequestId).Return(pr_db.PatronRequest{}, nil) + mockIllRepo := new(MockIllRepo) + mockIllRepo.On("GetPeerBySymbol", "ISIL:SUP1").Return(ill_db.Peer{ID: "peer1"}, nil) + handler := CreatePatronRequestMessageHandler(mockPrRepo, *new(events.EventRepo), mockIllRepo, *new(events.EventBus)) + + status, result := handler.handleSupplyingAgencyMessage(appCtx, iso18626.SupplyingAgencyMessage{ + Header: iso18626.Header{ + SupplyingAgencyId: iso18626.TypeAgencyId{ + AgencyIdType: iso18626.TypeSchemeValuePair{Text: "ISIL"}, + AgencyIdValue: "SUP1", + }, + RequestingAgencyRequestId: patronRequestId, + }, + StatusInfo: iso18626.StatusInfo{Status: iso18626.TypeStatusExpectToSupply}, + }) + assert.Equal(t, events.EventStatusSuccess, status) + assert.Equal(t, iso18626.TypeMessageStatusOK, result.OutgoingMessage.SupplyingAgencyMessageConfirmation.ConfirmationHeader.MessageStatus) + assert.Equal(t, BorrowerStateSupplierLocated, mockPrRepo.savedPr.State) +} + +func TestHandleSupplyingAgencyMessageWillSupply(t *testing.T) { + mockPrRepo := new(MockPrRepo) + mockPrRepo.On("GetPatronRequestById", patronRequestId).Return(pr_db.PatronRequest{}, nil) + handler := CreatePatronRequestMessageHandler(mockPrRepo, *new(events.EventRepo), *new(ill_db.IllRepo), *new(events.EventBus)) + + status, result := handler.handleSupplyingAgencyMessage(appCtx, iso18626.SupplyingAgencyMessage{ + Header: iso18626.Header{ + RequestingAgencyRequestId: patronRequestId, + }, + StatusInfo: iso18626.StatusInfo{Status: iso18626.TypeStatusWillSupply}, + }) + assert.Equal(t, events.EventStatusSuccess, status) + assert.Equal(t, iso18626.TypeMessageStatusOK, result.OutgoingMessage.SupplyingAgencyMessageConfirmation.ConfirmationHeader.MessageStatus) + assert.Equal(t, BorrowerStateWillSupply, mockPrRepo.savedPr.State) +} + +func TestHandleSupplyingAgencyMessageWillSupplyCondition(t *testing.T) { + mockPrRepo := new(MockPrRepo) + mockPrRepo.On("GetPatronRequestById", patronRequestId).Return(pr_db.PatronRequest{}, nil) + handler := CreatePatronRequestMessageHandler(mockPrRepo, *new(events.EventRepo), *new(ill_db.IllRepo), *new(events.EventBus)) + + status, result := handler.handleSupplyingAgencyMessage(appCtx, iso18626.SupplyingAgencyMessage{ + Header: iso18626.Header{ + RequestingAgencyRequestId: patronRequestId, + }, + StatusInfo: iso18626.StatusInfo{Status: iso18626.TypeStatusWillSupply}, + MessageInfo: iso18626.MessageInfo{ + Note: RESHARE_ADD_LOAN_CONDITION + " some comment", + }, + }) + assert.Equal(t, events.EventStatusSuccess, status) + assert.Equal(t, iso18626.TypeMessageStatusOK, result.OutgoingMessage.SupplyingAgencyMessageConfirmation.ConfirmationHeader.MessageStatus) + assert.Equal(t, BorrowerStateConditionPending, mockPrRepo.savedPr.State) +} + +func TestHandleSupplyingAgencyMessageLoaned(t *testing.T) { + mockPrRepo := new(MockPrRepo) + mockPrRepo.On("GetPatronRequestById", patronRequestId).Return(pr_db.PatronRequest{}, nil) + handler := CreatePatronRequestMessageHandler(mockPrRepo, *new(events.EventRepo), *new(ill_db.IllRepo), *new(events.EventBus)) + + status, result := handler.handleSupplyingAgencyMessage(appCtx, iso18626.SupplyingAgencyMessage{ + Header: iso18626.Header{ + RequestingAgencyRequestId: patronRequestId, + }, + StatusInfo: iso18626.StatusInfo{Status: iso18626.TypeStatusLoaned}, + }) + assert.Equal(t, events.EventStatusSuccess, status) + assert.Equal(t, iso18626.TypeMessageStatusOK, result.OutgoingMessage.SupplyingAgencyMessageConfirmation.ConfirmationHeader.MessageStatus) + assert.Equal(t, BorrowerStateShipped, mockPrRepo.savedPr.State) +} + +func TestHandleSupplyingAgencyMessageLoanCompleted(t *testing.T) { + mockPrRepo := new(MockPrRepo) + mockPrRepo.On("GetPatronRequestById", patronRequestId).Return(pr_db.PatronRequest{}, nil) + handler := CreatePatronRequestMessageHandler(mockPrRepo, *new(events.EventRepo), *new(ill_db.IllRepo), *new(events.EventBus)) + + status, result := handler.handleSupplyingAgencyMessage(appCtx, iso18626.SupplyingAgencyMessage{ + Header: iso18626.Header{ + RequestingAgencyRequestId: patronRequestId, + }, + StatusInfo: iso18626.StatusInfo{Status: iso18626.TypeStatusLoanCompleted}, + }) + assert.Equal(t, events.EventStatusSuccess, status) + assert.Equal(t, iso18626.TypeMessageStatusOK, result.OutgoingMessage.SupplyingAgencyMessageConfirmation.ConfirmationHeader.MessageStatus) + assert.Equal(t, BorrowerStateCompleted, mockPrRepo.savedPr.State) +} + +func TestHandleSupplyingAgencyMessageUnfilled(t *testing.T) { + mockPrRepo := new(MockPrRepo) + mockPrRepo.On("GetPatronRequestById", patronRequestId).Return(pr_db.PatronRequest{}, nil) + handler := CreatePatronRequestMessageHandler(mockPrRepo, *new(events.EventRepo), *new(ill_db.IllRepo), *new(events.EventBus)) + + status, result := handler.handleSupplyingAgencyMessage(appCtx, iso18626.SupplyingAgencyMessage{ + Header: iso18626.Header{ + RequestingAgencyRequestId: patronRequestId, + }, + StatusInfo: iso18626.StatusInfo{Status: iso18626.TypeStatusUnfilled}, + }) + assert.Equal(t, events.EventStatusSuccess, status) + assert.Equal(t, iso18626.TypeMessageStatusOK, result.OutgoingMessage.SupplyingAgencyMessageConfirmation.ConfirmationHeader.MessageStatus) + assert.Equal(t, BorrowerStateUnfilled, mockPrRepo.savedPr.State) +} + +func TestHandleSupplyingAgencyMessageCancelled(t *testing.T) { + mockPrRepo := new(MockPrRepo) + mockPrRepo.On("GetPatronRequestById", patronRequestId).Return(pr_db.PatronRequest{}, nil) + handler := CreatePatronRequestMessageHandler(mockPrRepo, *new(events.EventRepo), *new(ill_db.IllRepo), *new(events.EventBus)) + + status, result := handler.handleSupplyingAgencyMessage(appCtx, iso18626.SupplyingAgencyMessage{ + Header: iso18626.Header{ + RequestingAgencyRequestId: patronRequestId, + }, + StatusInfo: iso18626.StatusInfo{Status: iso18626.TypeStatusCancelled}, + }) + assert.Equal(t, events.EventStatusSuccess, status) + assert.Equal(t, iso18626.TypeMessageStatusOK, result.OutgoingMessage.SupplyingAgencyMessageConfirmation.ConfirmationHeader.MessageStatus) + assert.Equal(t, BorrowerStateCancelled, mockPrRepo.savedPr.State) +} + +func TestHandleSupplyingAgencyMessageNoImplemented(t *testing.T) { + mockPrRepo := new(MockPrRepo) + mockPrRepo.On("GetPatronRequestById", patronRequestId).Return(pr_db.PatronRequest{}, nil) + handler := CreatePatronRequestMessageHandler(mockPrRepo, *new(events.EventRepo), *new(ill_db.IllRepo), *new(events.EventBus)) + + status, result := handler.handleSupplyingAgencyMessage(appCtx, iso18626.SupplyingAgencyMessage{ + Header: iso18626.Header{ + RequestingAgencyRequestId: patronRequestId, + }, + StatusInfo: iso18626.StatusInfo{Status: iso18626.TypeStatusEmpty}, + }) + assert.Equal(t, events.EventStatusProblem, status) + assert.Equal(t, iso18626.TypeMessageStatusERROR, result.OutgoingMessage.SupplyingAgencyMessageConfirmation.ConfirmationHeader.MessageStatus) + assert.Equal(t, "status change no allowed", result.OutgoingMessage.SupplyingAgencyMessageConfirmation.ErrorData.ErrorValue) +} diff --git a/broker/test/patron_request/api/api-handler_test.go b/broker/test/patron_request/api/api-handler_test.go index e7f06f1e..c3fcc58d 100644 --- a/broker/test/patron_request/api/api-handler_test.go +++ b/broker/test/patron_request/api/api-handler_test.go @@ -133,11 +133,25 @@ func TestCrud(t *testing.T) { assert.Equal(t, *updatedPr.Requester, *foundPr.Requester) // Only requester can be updated now assert.Equal(t, *newPr.IllRequest, *foundPr.IllRequest) - // DELETE patron request - httpRequest(t, "DELETE", thisPrPath, []byte{}, 204) + // GET actions by PR id + respBytes = httpRequest(t, "GET", thisPrPath+"/actions", []byte{}, 200) + assert.Equal(t, "[\"send-request\"]\n", string(respBytes)) - // GET patron request which is deleted - httpRequest(t, "DELETE", thisPrPath, []byte{}, 404) + // POST execute action + action := proapi.ExecuteAction{ + Action: "send-request", + } + actionBytes, err := json.Marshal(action) + assert.NoError(t, err, "failed to marshal patron request action") + respBytes = httpRequest(t, "POST", thisPrPath+"/action", actionBytes, 200) + assert.Equal(t, "{\"actionResult\":\"PROBLEM\",\"message\":\"could not find borrower symbols\"}\n", string(respBytes)) + + // TODO Do we really want to delete from DB or just add DELETED status ? + //// DELETE patron request + //httpRequest(t, "DELETE", thisPrPath, []byte{}, 204) + // + //// GET patron request which is deleted + //httpRequest(t, "DELETE", thisPrPath, []byte{}, 404) } func httpRequest(t *testing.T, method string, uriPath string, reqbytes []byte, expectStatus int) []byte { From 84d594fd451266fc0e3cca554cd97431920feb80 Mon Sep 17 00:00:00 2001 From: Janis Saldabols Date: Thu, 20 Nov 2025 13:42:07 +0200 Subject: [PATCH 07/10] CROSSLINK-181 Add unit tests --- broker/test/patron_request/api/api-handler_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/broker/test/patron_request/api/api-handler_test.go b/broker/test/patron_request/api/api-handler_test.go index c3fcc58d..217deb76 100644 --- a/broker/test/patron_request/api/api-handler_test.go +++ b/broker/test/patron_request/api/api-handler_test.go @@ -144,7 +144,7 @@ func TestCrud(t *testing.T) { actionBytes, err := json.Marshal(action) assert.NoError(t, err, "failed to marshal patron request action") respBytes = httpRequest(t, "POST", thisPrPath+"/action", actionBytes, 200) - assert.Equal(t, "{\"actionResult\":\"PROBLEM\",\"message\":\"could not find borrower symbols\"}\n", string(respBytes)) + assert.Equal(t, "{\"actionResult\":\"ERROR\"}\n", string(respBytes)) // TODO Do we really want to delete from DB or just add DELETED status ? //// DELETE patron request From c342298d319dccfc30b183a90a63ee300a520eec Mon Sep 17 00:00:00 2001 From: Janis Saldabols Date: Thu, 20 Nov 2025 14:59:51 +0200 Subject: [PATCH 08/10] CROSSLINK-181 Rename --- broker/app/app.go | 2 +- broker/events/eventbus.go | 24 ++++----- broker/events/eventmodels.go | 6 +-- broker/handler/iso18626-handler.go | 4 +- broker/patron_request/api/api-handler.go | 4 +- broker/patron_request/service/action.go | 2 +- broker/patron_request/service/action_test.go | 50 +++++++++---------- .../patron_request/service/message-handler.go | 2 +- broker/service/workflow.go | 28 +++++------ broker/service/workflow_test.go | 4 +- broker/test/client/client_test.go | 8 +-- broker/test/events/eventbus_test.go | 12 ++--- 12 files changed, 73 insertions(+), 73 deletions(-) diff --git a/broker/app/app.go b/broker/app/app.go index 407bc9f6..3dc62f56 100644 --- a/broker/app/app.go +++ b/broker/app/app.go @@ -156,7 +156,7 @@ func Init(ctx context.Context) (Context, error) { iso18626Handler := handler.CreateIso18626Handler(eventBus, eventRepo, illRepo, dirAdapter) supplierLocator := service.CreateSupplierLocator(eventBus, illRepo, dirAdapter, holdingsAdapter) workflowManager := service.CreateWorkflowManager(eventBus, illRepo, service.WorkflowConfig{}) - prActionService := prservice.CreatePatronRequestAction(prRepo, illRepo, eventBus, &iso18626Handler) + prActionService := prservice.CreatePatronRequestActionService(prRepo, illRepo, eventBus, &iso18626Handler) prApiHandler := prapi.NewApiHandler(prRepo, eventBus) AddDefaultHandlers(eventBus, iso18626Client, supplierLocator, workflowManager, iso18626Handler, prActionService, prApiHandler, prMessageHandler) diff --git a/broker/events/eventbus.go b/broker/events/eventbus.go index 622912aa..38bd2820 100644 --- a/broker/events/eventbus.go +++ b/broker/events/eventbus.go @@ -23,10 +23,10 @@ const DEFAULT_PATRON_REQUEST_ID = "00000000-0000-0000-0000-000000000002" type EventBus interface { Start(ctx common.ExtendedContext) error - CreateTask(id string, eventName EventName, data EventData, eventClass EventClass, parentId *string) (string, error) - CreateTaskBroadcast(id string, eventName EventName, data EventData, eventClass EventClass, parentId *string) (string, error) - CreateNotice(id string, eventName EventName, data EventData, status EventStatus, eventClass EventClass) (string, error) - CreateNoticeBroadcast(id string, eventName EventName, data EventData, status EventStatus, eventClass EventClass) (string, error) + CreateTask(id string, eventName EventName, data EventData, eventClass EventDomain, parentId *string) (string, error) + CreateTaskBroadcast(id string, eventName EventName, data EventData, eventClass EventDomain, parentId *string) (string, error) + CreateNotice(id string, eventName EventName, data EventData, status EventStatus, eventClass EventDomain) (string, error) + CreateNoticeBroadcast(id string, eventName EventName, data EventData, status EventStatus, eventClass EventDomain) (string, error) BeginTask(eventId string) (Event, error) CompleteTask(eventId string, result *EventResult, status EventStatus) (Event, error) HandleEventCreated(eventName EventName, f func(ctx common.ExtendedContext, event Event)) @@ -185,15 +185,15 @@ func triggerHandlers(eventCtx common.ExtendedContext, event Event, handlersMap m eventCtx.Logger().Debug("all handlers finished", "eventName", event.EventName, "signal", signal) } -func (p *PostgresEventBus) CreateTask(classId string, eventName EventName, data EventData, eventClass EventClass, parentId *string) (string, error) { +func (p *PostgresEventBus) CreateTask(classId string, eventName EventName, data EventData, eventClass EventDomain, parentId *string) (string, error) { return p.createTask(classId, eventName, data, eventClass, parentId, false) } -func (p *PostgresEventBus) CreateTaskBroadcast(illTransactionID string, eventName EventName, data EventData, eventClass EventClass, parentId *string) (string, error) { +func (p *PostgresEventBus) CreateTaskBroadcast(illTransactionID string, eventName EventName, data EventData, eventClass EventDomain, parentId *string) (string, error) { return p.createTask(illTransactionID, eventName, data, eventClass, parentId, true) } -func (p *PostgresEventBus) createTask(classId string, eventName EventName, data EventData, eventClass EventClass, parentId *string, broadcast bool) (string, error) { +func (p *PostgresEventBus) createTask(classId string, eventName EventName, data EventData, eventClass EventDomain, parentId *string, broadcast bool) (string, error) { id := uuid.New().String() illTransactionID, patronRequestID := getIllTransactionAndPatronRequestId(classId, eventClass) return id, p.repo.WithTxFunc(p.ctx, func(eventRepo EventRepo) error { @@ -219,15 +219,15 @@ func (p *PostgresEventBus) createTask(classId string, eventName EventName, data }) } -func (p *PostgresEventBus) CreateNotice(classId string, eventName EventName, data EventData, status EventStatus, eventClass EventClass) (string, error) { +func (p *PostgresEventBus) CreateNotice(classId string, eventName EventName, data EventData, status EventStatus, eventClass EventDomain) (string, error) { return p.createNotice(classId, eventName, data, status, eventClass, false) } -func (p *PostgresEventBus) CreateNoticeBroadcast(classId string, eventName EventName, data EventData, status EventStatus, eventClass EventClass) (string, error) { +func (p *PostgresEventBus) CreateNoticeBroadcast(classId string, eventName EventName, data EventData, status EventStatus, eventClass EventDomain) (string, error) { return p.createNotice(classId, eventName, data, status, eventClass, true) } -func (p *PostgresEventBus) createNotice(classId string, eventName EventName, data EventData, status EventStatus, eventClass EventClass, broadcast bool) (string, error) { +func (p *PostgresEventBus) createNotice(classId string, eventName EventName, data EventData, status EventStatus, eventClass EventDomain, broadcast bool) (string, error) { id := uuid.New().String() illTransactionID, patronRequestID := getIllTransactionAndPatronRequestId(classId, eventClass) return id, p.repo.WithTxFunc(p.ctx, func(eventRepo EventRepo) error { @@ -412,8 +412,8 @@ func getPgText(value *string) pgtype.Text { } } -func getIllTransactionAndPatronRequestId(classId string, eventClass EventClass) (string, string) { - if eventClass == EventClassPatronRequest { +func getIllTransactionAndPatronRequestId(classId string, eventClass EventDomain) (string, string) { + if eventClass == EventDomainPatronRequest { return DEFAULT_ILL_TRANSACTION_ID, classId } else { return classId, DEFAULT_PATRON_REQUEST_ID diff --git a/broker/events/eventmodels.go b/broker/events/eventmodels.go index a4921167..2332eb3c 100644 --- a/broker/events/eventmodels.go +++ b/broker/events/eventmodels.go @@ -22,11 +22,11 @@ const ( EventTypeNotice EventType = "NOTICE" ) -type EventClass string +type EventDomain string const ( - EventClassPatronRequest EventClass = "PATRON_REQUEST" - EventClassIllTransaction EventClass = "ILL_TRANSACTION" + EventDomainPatronRequest EventDomain = "PATRON_REQUEST" + EventDomainIllTransaction EventDomain = "ILL_TRANSACTION" ) type EventName string diff --git a/broker/handler/iso18626-handler.go b/broker/handler/iso18626-handler.go index c3c0a393..d17cfae8 100644 --- a/broker/handler/iso18626-handler.go +++ b/broker/handler/iso18626-handler.go @@ -728,7 +728,7 @@ func handleSupplyingAgencyErrorWithNotice(ctx common.ExtendedContext, w http.Res }, }, } - _, err := eventBus.CreateNotice(illTransId, events.EventNameSupplierMsgReceived, eventData, events.EventStatusProblem, events.EventClassIllTransaction) + _, err := eventBus.CreateNotice(illTransId, events.EventNameSupplierMsgReceived, eventData, events.EventStatusProblem, events.EventDomainIllTransaction) if err != nil { ctx.Logger().Error(InternalFailedToCreateNotice, "error", err, "transactionId", illTransId) http.Error(w, PublicFailedToProcessReqMsg, http.StatusInternalServerError) @@ -738,7 +738,7 @@ func handleSupplyingAgencyErrorWithNotice(ctx common.ExtendedContext, w http.Res } func createNoticeAndCheckDBError(ctx common.ExtendedContext, w http.ResponseWriter, eventBus events.EventBus, illTransId string, eventName events.EventName, eventData events.EventData, eventStatus events.EventStatus) string { - id, err := eventBus.CreateNotice(illTransId, eventName, eventData, eventStatus, events.EventClassIllTransaction) + id, err := eventBus.CreateNotice(illTransId, eventName, eventData, eventStatus, events.EventDomainIllTransaction) if err != nil { ctx.Logger().Error(InternalFailedToCreateNotice, "error", err, "transactionId", illTransId) http.Error(w, PublicFailedToProcessReqMsg, http.StatusInternalServerError) diff --git a/broker/patron_request/api/api-handler.go b/broker/patron_request/api/api-handler.go index bf2fedac..8e699f71 100644 --- a/broker/patron_request/api/api-handler.go +++ b/broker/patron_request/api/api-handler.go @@ -64,7 +64,7 @@ func (a *PatronRequestApiHandler) PostPatronRequests(w http.ResponseWriter, r *h return } - _, err = a.eventBus.CreateTask(pr.ID, events.EventNameInvokeAction, events.EventData{CommonEventData: events.CommonEventData{Action: &prservice.ActionValidate}}, events.EventClassPatronRequest, nil) + _, err = a.eventBus.CreateTask(pr.ID, events.EventNameInvokeAction, events.EventData{CommonEventData: events.CommonEventData{Action: &prservice.ActionValidate}}, events.EventDomainPatronRequest, nil) if err != nil { addInternalError(ctx, w, err) return @@ -191,7 +191,7 @@ func (a *PatronRequestApiHandler) PostPatronRequestsIdAction(w http.ResponseWrit if action.ActionParams != nil { data.CustomData = *action.ActionParams } - eventId, err := a.eventBus.CreateTask(pr.ID, events.EventNameInvokeAction, data, events.EventClassPatronRequest, nil) + eventId, err := a.eventBus.CreateTask(pr.ID, events.EventNameInvokeAction, data, events.EventDomainPatronRequest, nil) if err != nil { addInternalError(ctx, w, err) return diff --git a/broker/patron_request/service/action.go b/broker/patron_request/service/action.go index 209e1096..929b5795 100644 --- a/broker/patron_request/service/action.go +++ b/broker/patron_request/service/action.go @@ -64,7 +64,7 @@ type PatronRequestActionService struct { iso18626Handler handler.Iso18626HandlerInterface } -func CreatePatronRequestAction(prRepo pr_db.PrRepo, illRepo ill_db.IllRepo, eventBus events.EventBus, iso18626Handler handler.Iso18626HandlerInterface) PatronRequestActionService { +func CreatePatronRequestActionService(prRepo pr_db.PrRepo, illRepo ill_db.IllRepo, eventBus events.EventBus, iso18626Handler handler.Iso18626HandlerInterface) PatronRequestActionService { return PatronRequestActionService{ prRepo: prRepo, illRepo: illRepo, diff --git a/broker/patron_request/service/action_test.go b/broker/patron_request/service/action_test.go index 92a8a60e..f450eb02 100644 --- a/broker/patron_request/service/action_test.go +++ b/broker/patron_request/service/action_test.go @@ -33,7 +33,7 @@ func TestIsBorrowerActionAvailable(t *testing.T) { } func TestInvokeAction(t *testing.T) { mockEventBus := new(MockEventBus) - prAction := CreatePatronRequestAction(*new(pr_db.PrRepo), *new(ill_db.IllRepo), mockEventBus, new(handler.Iso18626Handler)) + prAction := CreatePatronRequestActionService(*new(pr_db.PrRepo), *new(ill_db.IllRepo), mockEventBus, new(handler.Iso18626Handler)) event := events.Event{ ID: "action-1", } @@ -45,7 +45,7 @@ func TestInvokeAction(t *testing.T) { } func TestHandleInvokeActionNotSpecifiedAction(t *testing.T) { - prAction := CreatePatronRequestAction(*new(pr_db.PrRepo), *new(ill_db.IllRepo), *new(events.EventBus), new(handler.Iso18626Handler)) + prAction := CreatePatronRequestActionService(*new(pr_db.PrRepo), *new(ill_db.IllRepo), *new(events.EventBus), new(handler.Iso18626Handler)) status, resultData := prAction.handleInvokeAction(appCtx, events.Event{}) @@ -55,7 +55,7 @@ func TestHandleInvokeActionNotSpecifiedAction(t *testing.T) { func TestHandleInvokeActionNoPR(t *testing.T) { mockPrRepo := new(MockPrRepo) - prAction := CreatePatronRequestAction(mockPrRepo, *new(ill_db.IllRepo), *new(events.EventBus), new(handler.Iso18626Handler)) + prAction := CreatePatronRequestActionService(mockPrRepo, *new(ill_db.IllRepo), *new(events.EventBus), new(handler.Iso18626Handler)) mockPrRepo.On("GetPatronRequestById", patronRequestId).Return(pr_db.PatronRequest{}, errors.New("not fund")) status, resultData := prAction.handleInvokeAction(appCtx, events.Event{PatronRequestID: patronRequestId, EventData: events.EventData{CommonEventData: events.CommonEventData{Action: &ActionValidate}}}) @@ -66,7 +66,7 @@ func TestHandleInvokeActionNoPR(t *testing.T) { func TestHandleInvokeActionWhichIsNotAllowed(t *testing.T) { mockPrRepo := new(MockPrRepo) - prAction := CreatePatronRequestAction(mockPrRepo, *new(ill_db.IllRepo), *new(events.EventBus), new(handler.Iso18626Handler)) + prAction := CreatePatronRequestActionService(mockPrRepo, *new(ill_db.IllRepo), *new(events.EventBus), new(handler.Iso18626Handler)) mockPrRepo.On("GetPatronRequestById", patronRequestId).Return(pr_db.PatronRequest{State: BorrowerStateValidated}, nil) status, resultData := prAction.handleInvokeAction(appCtx, events.Event{PatronRequestID: patronRequestId, EventData: events.EventData{CommonEventData: events.CommonEventData{Action: &ActionValidate}}}) @@ -77,7 +77,7 @@ func TestHandleInvokeActionWhichIsNotAllowed(t *testing.T) { func TestHandleInvokeActionValidate(t *testing.T) { mockPrRepo := new(MockPrRepo) - prAction := CreatePatronRequestAction(mockPrRepo, *new(ill_db.IllRepo), *new(events.EventBus), new(handler.Iso18626Handler)) + prAction := CreatePatronRequestActionService(mockPrRepo, *new(ill_db.IllRepo), *new(events.EventBus), new(handler.Iso18626Handler)) mockPrRepo.On("GetPatronRequestById", patronRequestId).Return(pr_db.PatronRequest{State: BorrowerStateNew}, nil) status, resultData := prAction.handleInvokeAction(appCtx, events.Event{PatronRequestID: patronRequestId, EventData: events.EventData{CommonEventData: events.CommonEventData{Action: &ActionValidate}}}) @@ -90,7 +90,7 @@ func TestHandleInvokeActionValidate(t *testing.T) { func TestHandleInvokeActionSendRequest(t *testing.T) { mockPrRepo := new(MockPrRepo) mockIso18626Handler := new(MockIso18626Handler) - prAction := CreatePatronRequestAction(mockPrRepo, new(MockIllRepo), *new(events.EventBus), mockIso18626Handler) + prAction := CreatePatronRequestActionService(mockPrRepo, new(MockIllRepo), *new(events.EventBus), mockIso18626Handler) mockPrRepo.On("GetPatronRequestById", patronRequestId).Return(pr_db.PatronRequest{State: BorrowerStateValidated, BorrowingPeerID: pgtype.Text{Valid: true, String: "peer1"}}, nil) status, resultData := prAction.handleInvokeAction(appCtx, events.Event{PatronRequestID: patronRequestId, EventData: events.EventData{CommonEventData: events.CommonEventData{Action: &ActionSendRequest}}}) @@ -103,7 +103,7 @@ func TestHandleInvokeActionSendRequest(t *testing.T) { func TestHandleInvokeActionReceive(t *testing.T) { mockPrRepo := new(MockPrRepo) mockIso18626Handler := new(MockIso18626Handler) - prAction := CreatePatronRequestAction(mockPrRepo, new(MockIllRepo), *new(events.EventBus), mockIso18626Handler) + prAction := CreatePatronRequestActionService(mockPrRepo, new(MockIllRepo), *new(events.EventBus), mockIso18626Handler) mockPrRepo.On("GetPatronRequestById", patronRequestId).Return(pr_db.PatronRequest{State: BorrowerStateShipped, BorrowingPeerID: pgtype.Text{Valid: true, String: "peer1"}, LendingPeerID: pgtype.Text{Valid: true, String: "peer1"}}, nil) status, resultData := prAction.handleInvokeAction(appCtx, events.Event{PatronRequestID: patronRequestId, EventData: events.EventData{CommonEventData: events.CommonEventData{Action: &ActionReceive}}}) @@ -115,7 +115,7 @@ func TestHandleInvokeActionReceive(t *testing.T) { func TestHandleInvokeActionCheckOut(t *testing.T) { mockPrRepo := new(MockPrRepo) - prAction := CreatePatronRequestAction(mockPrRepo, *new(ill_db.IllRepo), *new(events.EventBus), new(handler.Iso18626Handler)) + prAction := CreatePatronRequestActionService(mockPrRepo, *new(ill_db.IllRepo), *new(events.EventBus), new(handler.Iso18626Handler)) mockPrRepo.On("GetPatronRequestById", patronRequestId).Return(pr_db.PatronRequest{State: BorrowerStateReceived}, nil) status, resultData := prAction.handleInvokeAction(appCtx, events.Event{PatronRequestID: patronRequestId, EventData: events.EventData{CommonEventData: events.CommonEventData{Action: &ActionCheckOut}}}) @@ -127,7 +127,7 @@ func TestHandleInvokeActionCheckOut(t *testing.T) { func TestHandleInvokeActionCheckIn(t *testing.T) { mockPrRepo := new(MockPrRepo) - prAction := CreatePatronRequestAction(mockPrRepo, *new(ill_db.IllRepo), *new(events.EventBus), new(handler.Iso18626Handler)) + prAction := CreatePatronRequestActionService(mockPrRepo, *new(ill_db.IllRepo), *new(events.EventBus), new(handler.Iso18626Handler)) mockPrRepo.On("GetPatronRequestById", patronRequestId).Return(pr_db.PatronRequest{State: BorrowerStateCheckedOut}, nil) status, resultData := prAction.handleInvokeAction(appCtx, events.Event{PatronRequestID: patronRequestId, EventData: events.EventData{CommonEventData: events.CommonEventData{Action: &ActionCheckIn}}}) @@ -140,7 +140,7 @@ func TestHandleInvokeActionCheckIn(t *testing.T) { func TestHandleInvokeActionShipReturn(t *testing.T) { mockPrRepo := new(MockPrRepo) mockIso18626Handler := new(MockIso18626Handler) - prAction := CreatePatronRequestAction(mockPrRepo, new(MockIllRepo), *new(events.EventBus), mockIso18626Handler) + prAction := CreatePatronRequestActionService(mockPrRepo, new(MockIllRepo), *new(events.EventBus), mockIso18626Handler) mockPrRepo.On("GetPatronRequestById", patronRequestId).Return(pr_db.PatronRequest{State: BorrowerStateCheckedIn, BorrowingPeerID: pgtype.Text{Valid: true, String: "peer1"}, LendingPeerID: pgtype.Text{Valid: true, String: "peer1"}}, nil) status, resultData := prAction.handleInvokeAction(appCtx, events.Event{PatronRequestID: patronRequestId, EventData: events.EventData{CommonEventData: events.CommonEventData{Action: &ActionShipReturn}}}) @@ -153,7 +153,7 @@ func TestHandleInvokeActionShipReturn(t *testing.T) { func TestHandleInvokeActionCancelRequest(t *testing.T) { mockPrRepo := new(MockPrRepo) mockIso18626Handler := new(MockIso18626Handler) - prAction := CreatePatronRequestAction(mockPrRepo, new(MockIllRepo), *new(events.EventBus), mockIso18626Handler) + prAction := CreatePatronRequestActionService(mockPrRepo, new(MockIllRepo), *new(events.EventBus), mockIso18626Handler) mockPrRepo.On("GetPatronRequestById", patronRequestId).Return(pr_db.PatronRequest{State: BorrowerStateWillSupply, BorrowingPeerID: pgtype.Text{Valid: true, String: "peer1"}, LendingPeerID: pgtype.Text{Valid: true, String: "peer1"}}, nil) status, resultData := prAction.handleInvokeAction(appCtx, events.Event{PatronRequestID: patronRequestId, EventData: events.EventData{CommonEventData: events.CommonEventData{Action: &ActionCancelRequest}}}) @@ -166,7 +166,7 @@ func TestHandleInvokeActionCancelRequest(t *testing.T) { func TestHandleInvokeActionAcceptCondition(t *testing.T) { mockPrRepo := new(MockPrRepo) mockIso18626Handler := new(MockIso18626Handler) - prAction := CreatePatronRequestAction(mockPrRepo, new(MockIllRepo), *new(events.EventBus), mockIso18626Handler) + prAction := CreatePatronRequestActionService(mockPrRepo, new(MockIllRepo), *new(events.EventBus), mockIso18626Handler) mockPrRepo.On("GetPatronRequestById", patronRequestId).Return(pr_db.PatronRequest{State: BorrowerStateConditionPending, BorrowingPeerID: pgtype.Text{Valid: true, String: "peer1"}, LendingPeerID: pgtype.Text{Valid: true, String: "peer1"}}, nil) status, resultData := prAction.handleInvokeAction(appCtx, events.Event{PatronRequestID: patronRequestId, EventData: events.EventData{CommonEventData: events.CommonEventData{Action: &ActionAcceptCondition}}}) @@ -179,7 +179,7 @@ func TestHandleInvokeActionAcceptCondition(t *testing.T) { func TestHandleInvokeActionRejectCondition(t *testing.T) { mockPrRepo := new(MockPrRepo) mockIso18626Handler := new(MockIso18626Handler) - prAction := CreatePatronRequestAction(mockPrRepo, new(MockIllRepo), *new(events.EventBus), mockIso18626Handler) + prAction := CreatePatronRequestActionService(mockPrRepo, new(MockIllRepo), *new(events.EventBus), mockIso18626Handler) mockPrRepo.On("GetPatronRequestById", patronRequestId).Return(pr_db.PatronRequest{State: BorrowerStateConditionPending, BorrowingPeerID: pgtype.Text{Valid: true, String: "peer1"}, LendingPeerID: pgtype.Text{Valid: true, String: "peer1"}}, nil) status, resultData := prAction.handleInvokeAction(appCtx, events.Event{PatronRequestID: patronRequestId, EventData: events.EventData{CommonEventData: events.CommonEventData{Action: &ActionRejectCondition}}}) @@ -192,7 +192,7 @@ func TestHandleInvokeActionRejectCondition(t *testing.T) { func TestSendBorrowingRequestNoRequester(t *testing.T) { mockPrRepo := new(MockPrRepo) mockIso18626Handler := new(MockIso18626Handler) - prAction := CreatePatronRequestAction(mockPrRepo, new(MockIllRepo), *new(events.EventBus), mockIso18626Handler) + prAction := CreatePatronRequestActionService(mockPrRepo, new(MockIllRepo), *new(events.EventBus), mockIso18626Handler) status, resultData := prAction.sendBorrowingRequest(appCtx, pr_db.PatronRequest{State: BorrowerStateValidated}) @@ -203,7 +203,7 @@ func TestSendBorrowingRequestNoRequester(t *testing.T) { func TestSendBorrowingRequestSymbolSearchError(t *testing.T) { mockPrRepo := new(MockPrRepo) mockIso18626Handler := new(MockIso18626Handler) - prAction := CreatePatronRequestAction(mockPrRepo, new(MockIllRepo), *new(events.EventBus), mockIso18626Handler) + prAction := CreatePatronRequestActionService(mockPrRepo, new(MockIllRepo), *new(events.EventBus), mockIso18626Handler) status, resultData := prAction.sendBorrowingRequest(appCtx, pr_db.PatronRequest{State: BorrowerStateValidated, BorrowingPeerID: pgtype.Text{Valid: true, String: "error"}}) @@ -214,7 +214,7 @@ func TestSendBorrowingRequestSymbolSearchError(t *testing.T) { func TestSendBorrowingRequestSymbolMissing(t *testing.T) { mockPrRepo := new(MockPrRepo) mockIso18626Handler := new(MockIso18626Handler) - prAction := CreatePatronRequestAction(mockPrRepo, new(MockIllRepo), *new(events.EventBus), mockIso18626Handler) + prAction := CreatePatronRequestActionService(mockPrRepo, new(MockIllRepo), *new(events.EventBus), mockIso18626Handler) status, resultData := prAction.sendBorrowingRequest(appCtx, pr_db.PatronRequest{State: BorrowerStateValidated, BorrowingPeerID: pgtype.Text{Valid: true, String: "missing"}}) @@ -225,7 +225,7 @@ func TestSendBorrowingRequestSymbolMissing(t *testing.T) { func TestSendBorrowingRequestFailedProcess(t *testing.T) { mockPrRepo := new(MockPrRepo) mockIso18626Handler := new(MockIso18626Handler) - prAction := CreatePatronRequestAction(mockPrRepo, new(MockIllRepo), *new(events.EventBus), mockIso18626Handler) + prAction := CreatePatronRequestActionService(mockPrRepo, new(MockIllRepo), *new(events.EventBus), mockIso18626Handler) status, resultData := prAction.sendBorrowingRequest(appCtx, pr_db.PatronRequest{ID: "error", State: BorrowerStateValidated, BorrowingPeerID: pgtype.Text{Valid: true, String: "pr123"}}) @@ -236,7 +236,7 @@ func TestSendBorrowingRequestFailedProcess(t *testing.T) { func TestShipReturnBorrowingRequestNoBorrowingId(t *testing.T) { mockPrRepo := new(MockPrRepo) mockIso18626Handler := new(MockIso18626Handler) - prAction := CreatePatronRequestAction(mockPrRepo, new(MockIllRepo), *new(events.EventBus), mockIso18626Handler) + prAction := CreatePatronRequestActionService(mockPrRepo, new(MockIllRepo), *new(events.EventBus), mockIso18626Handler) status, resultData := prAction.shipReturnBorrowingRequest(appCtx, pr_db.PatronRequest{ID: "1", State: BorrowerStateValidated}) @@ -246,7 +246,7 @@ func TestShipReturnBorrowingRequestNoBorrowingId(t *testing.T) { func TestShipReturnBorrowingRequestErrorBorrowing(t *testing.T) { mockPrRepo := new(MockPrRepo) mockIso18626Handler := new(MockIso18626Handler) - prAction := CreatePatronRequestAction(mockPrRepo, new(MockIllRepo), *new(events.EventBus), mockIso18626Handler) + prAction := CreatePatronRequestActionService(mockPrRepo, new(MockIllRepo), *new(events.EventBus), mockIso18626Handler) status, resultData := prAction.shipReturnBorrowingRequest(appCtx, pr_db.PatronRequest{ID: "1", State: BorrowerStateValidated, BorrowingPeerID: pgtype.Text{Valid: true, String: "error"}}) @@ -256,7 +256,7 @@ func TestShipReturnBorrowingRequestErrorBorrowing(t *testing.T) { func TestShipReturnBorrowingRequestMissingBorrowing(t *testing.T) { mockPrRepo := new(MockPrRepo) mockIso18626Handler := new(MockIso18626Handler) - prAction := CreatePatronRequestAction(mockPrRepo, new(MockIllRepo), *new(events.EventBus), mockIso18626Handler) + prAction := CreatePatronRequestActionService(mockPrRepo, new(MockIllRepo), *new(events.EventBus), mockIso18626Handler) status, resultData := prAction.shipReturnBorrowingRequest(appCtx, pr_db.PatronRequest{ID: "1", State: BorrowerStateValidated, BorrowingPeerID: pgtype.Text{Valid: true, String: "missing"}}) @@ -266,7 +266,7 @@ func TestShipReturnBorrowingRequestMissingBorrowing(t *testing.T) { func TestShipReturnBorrowingRequestNoLendingId(t *testing.T) { mockPrRepo := new(MockPrRepo) mockIso18626Handler := new(MockIso18626Handler) - prAction := CreatePatronRequestAction(mockPrRepo, new(MockIllRepo), *new(events.EventBus), mockIso18626Handler) + prAction := CreatePatronRequestActionService(mockPrRepo, new(MockIllRepo), *new(events.EventBus), mockIso18626Handler) status, resultData := prAction.shipReturnBorrowingRequest(appCtx, pr_db.PatronRequest{ID: "1", State: BorrowerStateValidated, BorrowingPeerID: pgtype.Text{Valid: true, String: "pr123"}}) @@ -276,7 +276,7 @@ func TestShipReturnBorrowingRequestNoLendingId(t *testing.T) { func TestShipReturnBorrowingRequestLendingError(t *testing.T) { mockPrRepo := new(MockPrRepo) mockIso18626Handler := new(MockIso18626Handler) - prAction := CreatePatronRequestAction(mockPrRepo, new(MockIllRepo), *new(events.EventBus), mockIso18626Handler) + prAction := CreatePatronRequestActionService(mockPrRepo, new(MockIllRepo), *new(events.EventBus), mockIso18626Handler) status, resultData := prAction.shipReturnBorrowingRequest(appCtx, pr_db.PatronRequest{ID: "1", State: BorrowerStateValidated, BorrowingPeerID: pgtype.Text{Valid: true, String: "pr123"}, LendingPeerID: pgtype.Text{Valid: true, String: "error"}}) @@ -286,7 +286,7 @@ func TestShipReturnBorrowingRequestLendingError(t *testing.T) { func TestShipReturnBorrowingRequestLendingMissing(t *testing.T) { mockPrRepo := new(MockPrRepo) mockIso18626Handler := new(MockIso18626Handler) - prAction := CreatePatronRequestAction(mockPrRepo, new(MockIllRepo), *new(events.EventBus), mockIso18626Handler) + prAction := CreatePatronRequestActionService(mockPrRepo, new(MockIllRepo), *new(events.EventBus), mockIso18626Handler) status, resultData := prAction.shipReturnBorrowingRequest(appCtx, pr_db.PatronRequest{ID: "1", State: BorrowerStateValidated, BorrowingPeerID: pgtype.Text{Valid: true, String: "pr123"}, LendingPeerID: pgtype.Text{Valid: true, String: "missing"}}) @@ -296,7 +296,7 @@ func TestShipReturnBorrowingRequestLendingMissing(t *testing.T) { func TestShipReturnBorrowingRequestProblemProcessing(t *testing.T) { mockPrRepo := new(MockPrRepo) mockIso18626Handler := new(MockIso18626Handler) - prAction := CreatePatronRequestAction(mockPrRepo, new(MockIllRepo), *new(events.EventBus), mockIso18626Handler) + prAction := CreatePatronRequestActionService(mockPrRepo, new(MockIllRepo), *new(events.EventBus), mockIso18626Handler) status, resultData := prAction.shipReturnBorrowingRequest(appCtx, pr_db.PatronRequest{ID: "error", State: BorrowerStateValidated, BorrowingPeerID: pgtype.Text{Valid: true, String: "pr123"}, LendingPeerID: pgtype.Text{Valid: true, String: "pr321"}}) @@ -314,7 +314,7 @@ func (m *MockEventBus) ProcessTask(ctx common.ExtendedContext, event events.Even return args.Get(0).(events.Event), args.Error(1) } -func (m *MockEventBus) CreateTask(id string, eventName events.EventName, data events.EventData, eventClass events.EventClass, parentId *string) (string, error) { +func (m *MockEventBus) CreateTask(id string, eventName events.EventName, data events.EventData, eventClass events.EventDomain, parentId *string) (string, error) { if id == "error" { return "", errors.New("event bus error") } diff --git a/broker/patron_request/service/message-handler.go b/broker/patron_request/service/message-handler.go index 718aa358..51db547f 100644 --- a/broker/patron_request/service/message-handler.go +++ b/broker/patron_request/service/message-handler.go @@ -44,7 +44,7 @@ func (m *PatronRequestMessageHandler) HandleMessage(ctx common.ExtendedContext, return nil, err } - eventId, err := m.eventBus.CreateTask(pr.ID, events.EventNamePatronRequestMessage, events.EventData{CommonEventData: events.CommonEventData{IncomingMessage: msg}}, events.EventClassPatronRequest, nil) + eventId, err := m.eventBus.CreateTask(pr.ID, events.EventNamePatronRequestMessage, events.EventData{CommonEventData: events.CommonEventData{IncomingMessage: msg}}, events.EventDomainPatronRequest, nil) if err != nil { return nil, err } diff --git a/broker/service/workflow.go b/broker/service/workflow.go index 00279557..a5afdd62 100644 --- a/broker/service/workflow.go +++ b/broker/service/workflow.go @@ -38,7 +38,7 @@ func CreateWorkflowManager(eventBus events.EventBus, illRepo ill_db.IllRepo, con func (w *WorkflowManager) RequestReceived(ctx common.ExtendedContext, event events.Event) { ctx = ctx.WithArgs(ctx.LoggerArgs().WithComponent(WF_COMP)) common.Must(ctx, func() (string, error) { - return w.eventBus.CreateTask(event.IllTransactionID, events.EventNameLocateSuppliers, events.EventData{}, events.EventClassIllTransaction, &event.ID) + return w.eventBus.CreateTask(event.IllTransactionID, events.EventNameLocateSuppliers, events.EventData{}, events.EventDomainIllTransaction, &event.ID) }, "") } @@ -46,9 +46,9 @@ func (w *WorkflowManager) OnLocateSupplierComplete(ctx common.ExtendedContext, e ctx = ctx.WithArgs(ctx.LoggerArgs().WithComponent(WF_COMP)) common.Must(ctx, func() (string, error) { if event.EventStatus == events.EventStatusSuccess { - return w.eventBus.CreateTask(event.IllTransactionID, events.EventNameSelectSupplier, events.EventData{}, events.EventClassIllTransaction, &event.ID) + return w.eventBus.CreateTask(event.IllTransactionID, events.EventNameSelectSupplier, events.EventData{}, events.EventDomainIllTransaction, &event.ID) } else { - return w.eventBus.CreateTask(event.IllTransactionID, events.EventNameMessageRequester, events.EventData{}, events.EventClassIllTransaction, &event.ID) + return w.eventBus.CreateTask(event.IllTransactionID, events.EventNameMessageRequester, events.EventData{}, events.EventDomainIllTransaction, &event.ID) } }, "") } @@ -63,14 +63,14 @@ func (w *WorkflowManager) OnSelectSupplierComplete(ctx common.ExtendedContext, e return "", fmt.Errorf("failed to process supplier selected event, no requester") } if requester.BrokerMode == string(common.BrokerModeTransparent) || requester.BrokerMode == string(common.BrokerModeTranslucent) { - id, err := w.eventBus.CreateTask(event.IllTransactionID, events.EventNameMessageRequester, events.EventData{}, events.EventClassIllTransaction, &event.ID) + id, err := w.eventBus.CreateTask(event.IllTransactionID, events.EventNameMessageRequester, events.EventData{}, events.EventDomainIllTransaction, &event.ID) if err != nil { return id, err } } if local, ok := event.ResultData.CustomData["localSupplier"].(bool); ok { if !local { - return w.eventBus.CreateTask(event.IllTransactionID, events.EventNameMessageSupplier, events.EventData{}, events.EventClassIllTransaction, &event.ID) + return w.eventBus.CreateTask(event.IllTransactionID, events.EventNameMessageSupplier, events.EventData{}, events.EventDomainIllTransaction, &event.ID) } else { return "", nil } @@ -78,7 +78,7 @@ func (w *WorkflowManager) OnSelectSupplierComplete(ctx common.ExtendedContext, e return "", fmt.Errorf("failed to detect local supplier from event result data") } } else { - return w.eventBus.CreateTask(event.IllTransactionID, events.EventNameMessageRequester, events.EventData{}, events.EventClassIllTransaction, &event.ID) + return w.eventBus.CreateTask(event.IllTransactionID, events.EventNameMessageRequester, events.EventData{}, events.EventDomainIllTransaction, &event.ID) } }, "") } @@ -95,14 +95,14 @@ func (w *WorkflowManager) SupplierMessageReceived(ctx common.ExtendedContext, ev if w.handleAndCheckCancelResponse(ctx, *event.EventData.IncomingMessage.SupplyingAgencyMessage, event.IllTransactionID) { common.Must(ctx, func() (string, error) { return w.eventBus.CreateTask(event.IllTransactionID, events.EventNameMessageRequester, - events.EventData{CommonEventData: events.CommonEventData{IncomingMessage: event.EventData.IncomingMessage}, CustomData: map[string]any{common.DO_NOT_SEND: !w.shouldForwardMessage(ctx, event)}}, events.EventClassIllTransaction, &event.ID) + events.EventData{CommonEventData: events.CommonEventData{IncomingMessage: event.EventData.IncomingMessage}, CustomData: map[string]any{common.DO_NOT_SEND: !w.shouldForwardMessage(ctx, event)}}, events.EventDomainIllTransaction, &event.ID) }, "") } else { common.Must(ctx, func() (string, error) { - return w.eventBus.CreateTaskBroadcast(event.IllTransactionID, events.EventNameConfirmSupplierMsg, events.EventData{}, events.EventClassIllTransaction, &event.ID) + return w.eventBus.CreateTaskBroadcast(event.IllTransactionID, events.EventNameConfirmSupplierMsg, events.EventData{}, events.EventDomainIllTransaction, &event.ID) }, "") common.Must(ctx, func() (string, error) { // This will also send unfilled message if no more suppliers - return w.eventBus.CreateTask(event.IllTransactionID, events.EventNameSelectSupplier, events.EventData{}, events.EventClassIllTransaction, &event.ID) + return w.eventBus.CreateTask(event.IllTransactionID, events.EventNameSelectSupplier, events.EventData{}, events.EventDomainIllTransaction, &event.ID) }, "") } } @@ -112,7 +112,7 @@ func (w *WorkflowManager) RequesterMessageReceived(ctx common.ExtendedContext, e if event.EventStatus == events.EventStatusSuccess { common.Must(ctx, func() (string, error) { return w.eventBus.CreateTask(event.IllTransactionID, events.EventNameMessageSupplier, - events.EventData{CommonEventData: events.CommonEventData{IncomingMessage: event.EventData.IncomingMessage}, CustomData: map[string]any{common.DO_NOT_SEND: !w.shouldForwardMessage(ctx, event)}}, events.EventClassIllTransaction, &event.ID) + events.EventData{CommonEventData: events.CommonEventData{IncomingMessage: event.EventData.IncomingMessage}, CustomData: map[string]any{common.DO_NOT_SEND: !w.shouldForwardMessage(ctx, event)}}, events.EventDomainIllTransaction, &event.ID) }, "") } } @@ -127,12 +127,12 @@ func (w *WorkflowManager) OnMessageSupplierComplete(ctx common.ExtendedContext, if event.EventData.IncomingMessage != nil && event.EventData.IncomingMessage.RequestingAgencyMessage != nil { // action message was send by requester so we must relay the confirmation common.Must(ctx, func() (string, error) { - return w.eventBus.CreateTaskBroadcast(event.IllTransactionID, events.EventNameConfirmRequesterMsg, events.EventData{}, events.EventClassIllTransaction, &event.ID) + return w.eventBus.CreateTaskBroadcast(event.IllTransactionID, events.EventNameConfirmRequesterMsg, events.EventData{}, events.EventDomainIllTransaction, &event.ID) }, "") } else if event.EventStatus != events.EventStatusSuccess { // if the last requester action was Request and messaging supplier failed, we try next supplier common.Must(ctx, func() (string, error) { - return w.eventBus.CreateTask(event.IllTransactionID, events.EventNameSelectSupplier, events.EventData{}, events.EventClassIllTransaction, &event.ID) + return w.eventBus.CreateTask(event.IllTransactionID, events.EventNameSelectSupplier, events.EventData{}, events.EventDomainIllTransaction, &event.ID) }, "") } } @@ -170,11 +170,11 @@ func (w *WorkflowManager) OnMessageRequesterComplete(ctx common.ExtendedContext, if event.EventData.IncomingMessage != nil && event.EventData.IncomingMessage.SupplyingAgencyMessage != nil { // action message was send by supplier so we must relay the confirmation common.Must(ctx, func() (string, error) { - return w.eventBus.CreateTaskBroadcast(event.IllTransactionID, events.EventNameConfirmSupplierMsg, events.EventData{}, events.EventClassIllTransaction, &event.ID) + return w.eventBus.CreateTaskBroadcast(event.IllTransactionID, events.EventNameConfirmSupplierMsg, events.EventData{}, events.EventDomainIllTransaction, &event.ID) }, "") if event.EventData.IncomingMessage.SupplyingAgencyMessage.StatusInfo.Status == iso18626.TypeStatusUnfilled { common.Must(ctx, func() (string, error) { - return w.eventBus.CreateTask(event.IllTransactionID, events.EventNameSelectSupplier, events.EventData{}, events.EventClassIllTransaction, &event.ID) + return w.eventBus.CreateTask(event.IllTransactionID, events.EventNameSelectSupplier, events.EventData{}, events.EventDomainIllTransaction, &event.ID) }, "") } if cancelSuccessful(*event.EventData.IncomingMessage.SupplyingAgencyMessage) { diff --git a/broker/service/workflow_test.go b/broker/service/workflow_test.go index 3ec0f0d0..4a424a1b 100644 --- a/broker/service/workflow_test.go +++ b/broker/service/workflow_test.go @@ -304,11 +304,11 @@ type MockEventBus struct { BroadcastCreated int } -func (r *MockEventBus) CreateTask(illTransactionID string, eventName events.EventName, data events.EventData, eventClass events.EventClass, parentId *string) (string, error) { +func (r *MockEventBus) CreateTask(illTransactionID string, eventName events.EventName, data events.EventData, eventClass events.EventDomain, parentId *string) (string, error) { r.TasksCreated++ return "id1", nil } -func (r *MockEventBus) CreateTaskBroadcast(illTransactionID string, eventName events.EventName, data events.EventData, eventClass events.EventClass, parentId *string) (string, error) { +func (r *MockEventBus) CreateTaskBroadcast(illTransactionID string, eventName events.EventName, data events.EventData, eventClass events.EventDomain, parentId *string) (string, error) { r.BroadcastCreated++ return "id2", nil } diff --git a/broker/test/client/client_test.go b/broker/test/client/client_test.go index 9f4c1662..5883b538 100644 --- a/broker/test/client/client_test.go +++ b/broker/test/client/client_test.go @@ -584,7 +584,7 @@ func TestRequestLocallyAvailable(t *testing.T) { } selSup, err = illRepo.SaveLocatedSupplier(appCtx, ill_db.SaveLocatedSupplierParams(selSup)) assert.NoError(t, err) - _, err = eventBus.CreateNotice(illTrans.ID, events.EventNameRequesterMsgReceived, events.EventData{}, events.EventStatusSuccess, events.EventClassIllTransaction) + _, err = eventBus.CreateNotice(illTrans.ID, events.EventNameRequesterMsgReceived, events.EventData{}, events.EventStatusSuccess, events.EventDomainIllTransaction) assert.NoError(t, err) _, err = eventBus.CreateNotice(illTrans.ID, events.EventNameSupplierMsgReceived, events.EventData{ CommonEventData: events.CommonEventData{ @@ -596,7 +596,7 @@ func TestRequestLocallyAvailable(t *testing.T) { }, }, }, - }, events.EventStatusSuccess, events.EventClassIllTransaction) + }, events.EventStatusSuccess, events.EventDomainIllTransaction) assert.NoError(t, err) assert.Equal(t, "NOTICE, request-received = SUCCESS\n"+ @@ -649,7 +649,7 @@ func TestRequestLocallyAvailable(t *testing.T) { } selSup, err = illRepo.SaveLocatedSupplier(appCtx, ill_db.SaveLocatedSupplierParams(selSup)) assert.NoError(t, err) - _, err = eventBus.CreateNotice(illTrans.ID, events.EventNameRequesterMsgReceived, events.EventData{}, events.EventStatusSuccess, events.EventClassIllTransaction) + _, err = eventBus.CreateNotice(illTrans.ID, events.EventNameRequesterMsgReceived, events.EventData{}, events.EventStatusSuccess, events.EventDomainIllTransaction) assert.NoError(t, err) _, err = eventBus.CreateNotice(illTrans.ID, events.EventNameSupplierMsgReceived, events.EventData{ CommonEventData: events.CommonEventData{ @@ -661,7 +661,7 @@ func TestRequestLocallyAvailable(t *testing.T) { }, }, }, - }, events.EventStatusSuccess, events.EventClassIllTransaction) + }, events.EventStatusSuccess, events.EventDomainIllTransaction) assert.NoError(t, err) assert.Equal(t, "NOTICE, request-received = SUCCESS\n"+ diff --git a/broker/test/events/eventbus_test.go b/broker/test/events/eventbus_test.go index 32d6d4fc..9c1facea 100644 --- a/broker/test/events/eventbus_test.go +++ b/broker/test/events/eventbus_test.go @@ -94,7 +94,7 @@ func TestMultipleEventHandlers(t *testing.T) { for i := 0; i < noEvents; i++ { illId := apptest.GetIllTransId(t, illRepo) - _, err := eventBus.CreateTask(illId, events.EventNameRequestReceived, events.EventData{}, events.EventClassIllTransaction, nil) + _, err := eventBus.CreateTask(illId, events.EventNameRequestReceived, events.EventData{}, events.EventDomainIllTransaction, nil) assert.NoError(t, err, "Task should be created without errors") } @@ -151,7 +151,7 @@ func TestBroadcastEventHandlers(t *testing.T) { for i := 0; i < noEvents; i++ { illId := apptest.GetIllTransId(t, illRepo) - _, err := eventBus.CreateTaskBroadcast(illId, events.EventNameConfirmRequesterMsg, events.EventData{}, events.EventClassIllTransaction, nil) + _, err := eventBus.CreateTaskBroadcast(illId, events.EventNameConfirmRequesterMsg, events.EventData{}, events.EventDomainIllTransaction, nil) assert.NoError(t, err, "Task should be created without errors") } @@ -188,7 +188,7 @@ func TestCreateTask(t *testing.T) { }) illId := apptest.GetIllTransId(t, illRepo) - _, err := eventBus.CreateTask(illId, events.EventNameRequestReceived, events.EventData{}, events.EventClassIllTransaction, nil) + _, err := eventBus.CreateTask(illId, events.EventNameRequestReceived, events.EventData{}, events.EventDomainIllTransaction, nil) if err != nil { t.Errorf("Task should be created without errors: %s", err) } @@ -249,7 +249,7 @@ func TestCreateNotice(t *testing.T) { illId := apptest.GetIllTransId(t, illRepo) - _, err := eventBus.CreateNotice(illId, events.EventNameSupplierMsgReceived, events.EventData{}, events.EventStatusSuccess, events.EventClassIllTransaction) + _, err := eventBus.CreateNotice(illId, events.EventNameSupplierMsgReceived, events.EventData{}, events.EventStatusSuccess, events.EventDomainIllTransaction) if err != nil { t.Errorf("Task should be created without errors: %s", err) } @@ -285,7 +285,7 @@ func TestBeginAndCompleteTask(t *testing.T) { illId := apptest.GetIllTransId(t, illRepo) - _, err := eventBus.CreateTask(illId, events.EventNameRequestReceived, events.EventData{}, events.EventClassIllTransaction, nil) + _, err := eventBus.CreateTask(illId, events.EventNameRequestReceived, events.EventData{}, events.EventDomainIllTransaction, nil) if err != nil { t.Errorf("Task should be created without errors: %s", err) } @@ -427,7 +427,7 @@ func TestReconnectListener(t *testing.T) { illId := apptest.GetIllTransId(t, illRepo) - _, err := eventBus.CreateNotice(illId, events.EventNameSupplierMsgReceived, events.EventData{}, events.EventStatusSuccess, events.EventClassIllTransaction) + _, err := eventBus.CreateNotice(illId, events.EventNameSupplierMsgReceived, events.EventData{}, events.EventStatusSuccess, events.EventDomainIllTransaction) if err != nil { t.Errorf("Task should be created without errors: %s", err) } From 2afa6fb47bbb65fa2965a6ac201e1363145233e1 Mon Sep 17 00:00:00 2001 From: Janis Saldabols Date: Mon, 24 Nov 2025 16:33:49 +0200 Subject: [PATCH 09/10] CROSSLINK-181 Create notice and not task --- broker/app/app.go | 1 - broker/docker-compose.yml | 2 +- broker/events/eventbus.go | 37 +++++----- .../013_allow_patron_request_events.up.sql | 2 +- broker/patron_request/service/action_test.go | 7 ++ .../patron_request/service/message-handler.go | 70 ++++++++----------- .../service/message-handler_test.go | 67 +++++++++++------- 7 files changed, 96 insertions(+), 90 deletions(-) diff --git a/broker/app/app.go b/broker/app/app.go index 3dc62f56..cb89e134 100644 --- a/broker/app/app.go +++ b/broker/app/app.go @@ -291,7 +291,6 @@ func AddDefaultHandlers(eventBus events.EventBus, iso18626Client client.Iso18626 eventBus.HandleEventCreated(events.EventNameInvokeAction, prActionService.InvokeAction) eventBus.HandleTaskCompleted(events.EventNameInvokeAction, prApiHandler.ConfirmActionProcess) - eventBus.HandleEventCreated(events.EventNamePatronRequestMessage, prMessageHandler.PatronRequestMessage) } func StartEventBus(ctx context.Context, eventBus events.EventBus) error { err := eventBus.Start(common.CreateExtCtxWithArgs(ctx, nil)) diff --git a/broker/docker-compose.yml b/broker/docker-compose.yml index 63dd7cfe..774e98ac 100644 --- a/broker/docker-compose.yml +++ b/broker/docker-compose.yml @@ -11,4 +11,4 @@ services: ports: - "25432:5432" volumes: - - ./data:/var/lib/postgresql/data \ No newline at end of file + - ./pg_data:/var/lib/postgresql \ No newline at end of file diff --git a/broker/events/eventbus.go b/broker/events/eventbus.go index 38bd2820..6c2e2ab9 100644 --- a/broker/events/eventbus.go +++ b/broker/events/eventbus.go @@ -23,10 +23,10 @@ const DEFAULT_PATRON_REQUEST_ID = "00000000-0000-0000-0000-000000000002" type EventBus interface { Start(ctx common.ExtendedContext) error - CreateTask(id string, eventName EventName, data EventData, eventClass EventDomain, parentId *string) (string, error) - CreateTaskBroadcast(id string, eventName EventName, data EventData, eventClass EventDomain, parentId *string) (string, error) - CreateNotice(id string, eventName EventName, data EventData, status EventStatus, eventClass EventDomain) (string, error) - CreateNoticeBroadcast(id string, eventName EventName, data EventData, status EventStatus, eventClass EventDomain) (string, error) + CreateTask(id string, eventName EventName, data EventData, eventDomain EventDomain, parentId *string) (string, error) + CreateTaskBroadcast(id string, eventName EventName, data EventData, eventDomain EventDomain, parentId *string) (string, error) + CreateNotice(id string, eventName EventName, data EventData, status EventStatus, eventDomain EventDomain) (string, error) + CreateNoticeBroadcast(id string, eventName EventName, data EventData, status EventStatus, eventDomain EventDomain) (string, error) BeginTask(eventId string) (Event, error) CompleteTask(eventId string, result *EventResult, status EventStatus) (Event, error) HandleEventCreated(eventName EventName, f func(ctx common.ExtendedContext, event Event)) @@ -132,6 +132,7 @@ func (p *PostgresEventBus) Start(ctx common.ExtendedContext) error { if err != nil { ctx.Logger().Error("failed to unmarshal notification", "error", err, "payload", notification.Payload) } + // TODO We could run this method in separate go routine go p.handleNotify(notifyData) } }() @@ -185,17 +186,17 @@ func triggerHandlers(eventCtx common.ExtendedContext, event Event, handlersMap m eventCtx.Logger().Debug("all handlers finished", "eventName", event.EventName, "signal", signal) } -func (p *PostgresEventBus) CreateTask(classId string, eventName EventName, data EventData, eventClass EventDomain, parentId *string) (string, error) { - return p.createTask(classId, eventName, data, eventClass, parentId, false) +func (p *PostgresEventBus) CreateTask(classId string, eventName EventName, data EventData, eventDomain EventDomain, parentId *string) (string, error) { + return p.createTask(classId, eventName, data, eventDomain, parentId, false) } -func (p *PostgresEventBus) CreateTaskBroadcast(illTransactionID string, eventName EventName, data EventData, eventClass EventDomain, parentId *string) (string, error) { - return p.createTask(illTransactionID, eventName, data, eventClass, parentId, true) +func (p *PostgresEventBus) CreateTaskBroadcast(illTransactionID string, eventName EventName, data EventData, eventDomain EventDomain, parentId *string) (string, error) { + return p.createTask(illTransactionID, eventName, data, eventDomain, parentId, true) } -func (p *PostgresEventBus) createTask(classId string, eventName EventName, data EventData, eventClass EventDomain, parentId *string, broadcast bool) (string, error) { +func (p *PostgresEventBus) createTask(classId string, eventName EventName, data EventData, eventDomain EventDomain, parentId *string, broadcast bool) (string, error) { id := uuid.New().String() - illTransactionID, patronRequestID := getIllTransactionAndPatronRequestId(classId, eventClass) + illTransactionID, patronRequestID := getIllTransactionAndPatronRequestId(classId, eventDomain) return id, p.repo.WithTxFunc(p.ctx, func(eventRepo EventRepo) error { event, err := eventRepo.SaveEvent(p.ctx, SaveEventParams{ ID: id, @@ -219,17 +220,17 @@ func (p *PostgresEventBus) createTask(classId string, eventName EventName, data }) } -func (p *PostgresEventBus) CreateNotice(classId string, eventName EventName, data EventData, status EventStatus, eventClass EventDomain) (string, error) { - return p.createNotice(classId, eventName, data, status, eventClass, false) +func (p *PostgresEventBus) CreateNotice(classId string, eventName EventName, data EventData, status EventStatus, eventDomain EventDomain) (string, error) { + return p.createNotice(classId, eventName, data, status, eventDomain, false) } -func (p *PostgresEventBus) CreateNoticeBroadcast(classId string, eventName EventName, data EventData, status EventStatus, eventClass EventDomain) (string, error) { - return p.createNotice(classId, eventName, data, status, eventClass, true) +func (p *PostgresEventBus) CreateNoticeBroadcast(classId string, eventName EventName, data EventData, status EventStatus, eventDomain EventDomain) (string, error) { + return p.createNotice(classId, eventName, data, status, eventDomain, true) } -func (p *PostgresEventBus) createNotice(classId string, eventName EventName, data EventData, status EventStatus, eventClass EventDomain, broadcast bool) (string, error) { +func (p *PostgresEventBus) createNotice(classId string, eventName EventName, data EventData, status EventStatus, eventDomain EventDomain, broadcast bool) (string, error) { id := uuid.New().String() - illTransactionID, patronRequestID := getIllTransactionAndPatronRequestId(classId, eventClass) + illTransactionID, patronRequestID := getIllTransactionAndPatronRequestId(classId, eventDomain) return id, p.repo.WithTxFunc(p.ctx, func(eventRepo EventRepo) error { event, err := eventRepo.SaveEvent(p.ctx, SaveEventParams{ ID: id, @@ -412,8 +413,8 @@ func getPgText(value *string) pgtype.Text { } } -func getIllTransactionAndPatronRequestId(classId string, eventClass EventDomain) (string, string) { - if eventClass == EventDomainPatronRequest { +func getIllTransactionAndPatronRequestId(classId string, eventDomain EventDomain) (string, string) { + if eventDomain == EventDomainPatronRequest { return DEFAULT_ILL_TRANSACTION_ID, classId } else { return classId, DEFAULT_PATRON_REQUEST_ID diff --git a/broker/migrations/013_allow_patron_request_events.up.sql b/broker/migrations/013_allow_patron_request_events.up.sql index bdf42c03..72934ca9 100644 --- a/broker/migrations/013_allow_patron_request_events.up.sql +++ b/broker/migrations/013_allow_patron_request_events.up.sql @@ -8,4 +8,4 @@ ALTER TABLE event ADD COLUMN patron_request_id VARCHAR NOT NULL DEFAULT '000000 INSERT INTO event_config (event_name, event_type, retry_count) VALUES ('invoke-action', 'TASK', 1); INSERT INTO event_config (event_name, event_type, retry_count) -VALUES ('patron-request-message', 'TASK', 1); \ No newline at end of file +VALUES ('patron-request-message', 'NOTICE', 1); \ No newline at end of file diff --git a/broker/patron_request/service/action_test.go b/broker/patron_request/service/action_test.go index f450eb02..fbbef859 100644 --- a/broker/patron_request/service/action_test.go +++ b/broker/patron_request/service/action_test.go @@ -321,6 +321,13 @@ func (m *MockEventBus) CreateTask(id string, eventName events.EventName, data ev return id, nil } +func (m *MockEventBus) CreateNotice(id string, eventName events.EventName, data events.EventData, status events.EventStatus, eventDomain events.EventDomain) (string, error) { + if id == "error" { + return "", errors.New("event bus error") + } + return id, nil +} + type MockPrRepo struct { mock.Mock pr_db.PgPrRepo diff --git a/broker/patron_request/service/message-handler.go b/broker/patron_request/service/message-handler.go index 51db547f..9e4677b7 100644 --- a/broker/patron_request/service/message-handler.go +++ b/broker/patron_request/service/message-handler.go @@ -9,14 +9,11 @@ import ( "github.com/indexdata/crosslink/iso18626" "github.com/jackc/pgx/v5/pgtype" "strings" - "sync" ) const COMP_MESSAGE = "pr_massage_handler" const RESHARE_ADD_LOAN_CONDITION = "#ReShareAddLoanCondition#" -var waitings = map[string]*sync.WaitGroup{} - type PatronRequestMessageHandler struct { prRepo pr_db.PrRepo eventRepo events.EventRepo @@ -43,41 +40,31 @@ func (m *PatronRequestMessageHandler) HandleMessage(ctx common.ExtendedContext, if err != nil { return nil, err } - - eventId, err := m.eventBus.CreateTask(pr.ID, events.EventNamePatronRequestMessage, events.EventData{CommonEventData: events.CommonEventData{IncomingMessage: msg}}, events.EventDomainPatronRequest, nil) + // Create notice with result + status, response, err := m.handlePatronRequestMessage(ctx, msg) + eventData := events.EventData{CommonEventData: events.CommonEventData{IncomingMessage: msg, OutgoingMessage: response}} if err != nil { - return nil, err + eventData.EventError = &events.EventError{ + Message: err.Error(), + } } - - var wg sync.WaitGroup - wg.Add(1) - waitings[eventId] = &wg - wg.Wait() - - event, err := m.eventRepo.GetEvent(ctx, eventId) + _, err = m.eventBus.CreateNotice(pr.ID, events.EventNamePatronRequestMessage, eventData, status, events.EventDomainPatronRequest) if err != nil { return nil, err } - return event.ResultData.OutgoingMessage, nil -} -func (m *PatronRequestMessageHandler) PatronRequestMessage(ctx common.ExtendedContext, event events.Event) { - ctx = ctx.WithArgs(ctx.LoggerArgs().WithComponent(COMP_MESSAGE)) - _, _ = m.eventBus.ProcessTask(ctx, event, m.handlePatronRequestMessage) - if waiting, ok := waitings[event.ID]; ok { - waiting.Done() - } + return response, err } -func (m *PatronRequestMessageHandler) handlePatronRequestMessage(ctx common.ExtendedContext, event events.Event) (events.EventStatus, *events.EventResult) { - if event.EventData.IncomingMessage.SupplyingAgencyMessage != nil { - return m.handleSupplyingAgencyMessage(ctx, *event.EventData.IncomingMessage.SupplyingAgencyMessage) - } else if event.EventData.IncomingMessage.RequestingAgencyMessage != nil { - return events.EventStatusError, &events.EventResult{CommonEventData: events.CommonEventData{Note: "requesting agency message handling is not implemented yet"}} - } else if event.EventData.IncomingMessage.Request != nil { - return events.EventStatusError, &events.EventResult{CommonEventData: events.CommonEventData{Note: "request handling is not implemented yet"}} +func (m *PatronRequestMessageHandler) handlePatronRequestMessage(ctx common.ExtendedContext, msg *iso18626.ISO18626Message) (events.EventStatus, *iso18626.ISO18626Message, error) { + if msg.SupplyingAgencyMessage != nil { + return m.handleSupplyingAgencyMessage(ctx, *msg.SupplyingAgencyMessage) + } else if msg.RequestingAgencyMessage != nil { + return events.EventStatusError, nil, errors.New("requesting agency message handling is not implemented yet") + } else if msg.Request != nil { + return events.EventStatusError, nil, errors.New("request handling is not implemented yet") } else { - return events.EventStatusError, &events.EventResult{CommonEventData: events.CommonEventData{Note: "cannot process message without content"}} + return events.EventStatusError, nil, errors.New("cannot process message without content") } } @@ -93,13 +80,13 @@ func getPatronRequestId(msg iso18626.ISO18626Message) string { } } -func (m *PatronRequestMessageHandler) handleSupplyingAgencyMessage(ctx common.ExtendedContext, sam iso18626.SupplyingAgencyMessage) (events.EventStatus, *events.EventResult) { +func (m *PatronRequestMessageHandler) handleSupplyingAgencyMessage(ctx common.ExtendedContext, sam iso18626.SupplyingAgencyMessage) (events.EventStatus, *iso18626.ISO18626Message, error) { pr, err := m.prRepo.GetPatronRequestById(ctx, sam.Header.RequestingAgencyRequestId) if err != nil { return createSAMResponse(sam, iso18626.TypeMessageStatusERROR, &iso18626.ErrorData{ ErrorType: iso18626.TypeErrorTypeUnrecognisedDataValue, - ErrorValue: "could not find patron request", - }) + ErrorValue: "could not find patron request: " + err.Error(), + }, err) } // TODO handle notifications switch sam.StatusInfo.Status { @@ -110,8 +97,8 @@ func (m *PatronRequestMessageHandler) handleSupplyingAgencyMessage(ctx common.Ex if err != nil { return createSAMResponse(sam, iso18626.TypeMessageStatusERROR, &iso18626.ErrorData{ ErrorType: iso18626.TypeErrorTypeUnrecognisedDataValue, - ErrorValue: "could not find supplier", - }) + ErrorValue: "could not find supplier: " + err.Error(), + }, err) } pr.LendingPeerID = pgtype.Text{ String: supplier.ID, @@ -143,27 +130,26 @@ func (m *PatronRequestMessageHandler) handleSupplyingAgencyMessage(ctx common.Ex return createSAMResponse(sam, iso18626.TypeMessageStatusERROR, &iso18626.ErrorData{ ErrorType: iso18626.TypeErrorTypeBadlyFormedMessage, ErrorValue: "status change no allowed", - }) + }, errors.New("status change no allowed")) } -func (m *PatronRequestMessageHandler) updatePatronRequestAndCreateSamResponse(ctx common.ExtendedContext, pr pr_db.PatronRequest, sam iso18626.SupplyingAgencyMessage) (events.EventStatus, *events.EventResult) { +func (m *PatronRequestMessageHandler) updatePatronRequestAndCreateSamResponse(ctx common.ExtendedContext, pr pr_db.PatronRequest, sam iso18626.SupplyingAgencyMessage) (events.EventStatus, *iso18626.ISO18626Message, error) { _, err := m.prRepo.SavePatronRequest(ctx, pr_db.SavePatronRequestParams(pr)) if err != nil { return createSAMResponse(sam, iso18626.TypeMessageStatusERROR, &iso18626.ErrorData{ ErrorType: iso18626.TypeErrorTypeUnrecognisedDataValue, ErrorValue: err.Error(), - }) + }, err) } - return createSAMResponse(sam, iso18626.TypeMessageStatusOK, nil) + return createSAMResponse(sam, iso18626.TypeMessageStatusOK, nil, nil) } -func createSAMResponse(sam iso18626.SupplyingAgencyMessage, messageStatus iso18626.TypeMessageStatus, errorData *iso18626.ErrorData) (events.EventStatus, *events.EventResult) { +func createSAMResponse(sam iso18626.SupplyingAgencyMessage, messageStatus iso18626.TypeMessageStatus, errorData *iso18626.ErrorData, err error) (events.EventStatus, *iso18626.ISO18626Message, error) { eventStatus := events.EventStatusSuccess if messageStatus != iso18626.TypeMessageStatusOK { eventStatus = events.EventStatusProblem } - return eventStatus, &events.EventResult{CommonEventData: events.CommonEventData{ - OutgoingMessage: &iso18626.ISO18626Message{ + return eventStatus, &iso18626.ISO18626Message{ SupplyingAgencyMessageConfirmation: &iso18626.SupplyingAgencyMessageConfirmation{ ConfirmationHeader: iso18626.ConfirmationHeader{ SupplyingAgencyId: &sam.Header.SupplyingAgencyId, @@ -175,5 +161,5 @@ func createSAMResponse(sam iso18626.SupplyingAgencyMessage, messageStatus iso186 ErrorData: errorData, }, }, - }} + err } diff --git a/broker/patron_request/service/message-handler_test.go b/broker/patron_request/service/message-handler_test.go index ece3719e..c1f7d6db 100644 --- a/broker/patron_request/service/message-handler_test.go +++ b/broker/patron_request/service/message-handler_test.go @@ -92,22 +92,26 @@ func TestHandlePatronRequestMessage(t *testing.T) { mockPrRepo := new(MockPrRepo) handler := CreatePatronRequestMessageHandler(mockPrRepo, *new(events.EventRepo), *new(ill_db.IllRepo), *new(events.EventBus)) - status, result := handler.handlePatronRequestMessage(appCtx, events.Event{EventData: events.EventData{CommonEventData: events.CommonEventData{IncomingMessage: &iso18626.ISO18626Message{}}}}) + status, resp, err := handler.handlePatronRequestMessage(appCtx, &iso18626.ISO18626Message{}) assert.Equal(t, events.EventStatusError, status) - assert.Equal(t, "cannot process message without content", result.Note) + assert.Nil(t, resp) + assert.Equal(t, "cannot process message without content", err.Error()) - status, result = handler.handlePatronRequestMessage(appCtx, events.Event{EventData: events.EventData{CommonEventData: events.CommonEventData{IncomingMessage: &iso18626.ISO18626Message{Request: &iso18626.Request{}}}}}) + status, resp, err = handler.handlePatronRequestMessage(appCtx, &iso18626.ISO18626Message{Request: &iso18626.Request{}}) assert.Equal(t, events.EventStatusError, status) - assert.Equal(t, "request handling is not implemented yet", result.Note) + assert.Nil(t, resp) + assert.Equal(t, "request handling is not implemented yet", err.Error()) - status, result = handler.handlePatronRequestMessage(appCtx, events.Event{EventData: events.EventData{CommonEventData: events.CommonEventData{IncomingMessage: &iso18626.ISO18626Message{RequestingAgencyMessage: &iso18626.RequestingAgencyMessage{}}}}}) + status, resp, err = handler.handlePatronRequestMessage(appCtx, &iso18626.ISO18626Message{RequestingAgencyMessage: &iso18626.RequestingAgencyMessage{}}) assert.Equal(t, events.EventStatusError, status) - assert.Equal(t, "requesting agency message handling is not implemented yet", result.Note) + assert.Nil(t, resp) + assert.Equal(t, "requesting agency message handling is not implemented yet", err.Error()) mockPrRepo.On("GetPatronRequestById", patronRequestId).Return(pr_db.PatronRequest{}, errors.New("db error")) - status, result = handler.handlePatronRequestMessage(appCtx, events.Event{EventData: events.EventData{CommonEventData: events.CommonEventData{IncomingMessage: &iso18626.ISO18626Message{SupplyingAgencyMessage: &iso18626.SupplyingAgencyMessage{Header: iso18626.Header{RequestingAgencyRequestId: patronRequestId}}}}}}) + status, resp, err = handler.handlePatronRequestMessage(appCtx, &iso18626.ISO18626Message{SupplyingAgencyMessage: &iso18626.SupplyingAgencyMessage{Header: iso18626.Header{RequestingAgencyRequestId: patronRequestId}}}) assert.Equal(t, events.EventStatusProblem, status) - assert.Equal(t, "could not find patron request", result.OutgoingMessage.SupplyingAgencyMessageConfirmation.ErrorData.ErrorValue) + assert.Equal(t, "db error", err.Error()) + assert.Equal(t, "could not find patron request: db error", resp.SupplyingAgencyMessageConfirmation.ErrorData.ErrorValue) } func TestHandleSupplyingAgencyMessageNoSupplier(t *testing.T) { @@ -117,7 +121,7 @@ func TestHandleSupplyingAgencyMessageNoSupplier(t *testing.T) { mockIllRepo.On("GetPeerBySymbol", "ISIL:SUP1").Return(ill_db.Peer{}, errors.New("db error")) handler := CreatePatronRequestMessageHandler(mockPrRepo, *new(events.EventRepo), mockIllRepo, *new(events.EventBus)) - status, result := handler.handleSupplyingAgencyMessage(appCtx, iso18626.SupplyingAgencyMessage{ + status, resp, err := handler.handleSupplyingAgencyMessage(appCtx, iso18626.SupplyingAgencyMessage{ Header: iso18626.Header{ SupplyingAgencyId: iso18626.TypeAgencyId{ AgencyIdType: iso18626.TypeSchemeValuePair{Text: "ISIL"}, @@ -128,7 +132,8 @@ func TestHandleSupplyingAgencyMessageNoSupplier(t *testing.T) { StatusInfo: iso18626.StatusInfo{Status: iso18626.TypeStatusExpectToSupply}, }) assert.Equal(t, events.EventStatusProblem, status) - assert.Equal(t, "could not find supplier", result.OutgoingMessage.SupplyingAgencyMessageConfirmation.ErrorData.ErrorValue) + assert.Equal(t, "db error", err.Error()) + assert.Equal(t, "could not find supplier: db error", resp.SupplyingAgencyMessageConfirmation.ErrorData.ErrorValue) } func TestHandleSupplyingAgencyMessageExpectToSupply(t *testing.T) { @@ -138,7 +143,7 @@ func TestHandleSupplyingAgencyMessageExpectToSupply(t *testing.T) { mockIllRepo.On("GetPeerBySymbol", "ISIL:SUP1").Return(ill_db.Peer{ID: "peer1"}, nil) handler := CreatePatronRequestMessageHandler(mockPrRepo, *new(events.EventRepo), mockIllRepo, *new(events.EventBus)) - status, result := handler.handleSupplyingAgencyMessage(appCtx, iso18626.SupplyingAgencyMessage{ + status, resp, err := handler.handleSupplyingAgencyMessage(appCtx, iso18626.SupplyingAgencyMessage{ Header: iso18626.Header{ SupplyingAgencyId: iso18626.TypeAgencyId{ AgencyIdType: iso18626.TypeSchemeValuePair{Text: "ISIL"}, @@ -149,7 +154,8 @@ func TestHandleSupplyingAgencyMessageExpectToSupply(t *testing.T) { StatusInfo: iso18626.StatusInfo{Status: iso18626.TypeStatusExpectToSupply}, }) assert.Equal(t, events.EventStatusSuccess, status) - assert.Equal(t, iso18626.TypeMessageStatusOK, result.OutgoingMessage.SupplyingAgencyMessageConfirmation.ConfirmationHeader.MessageStatus) + assert.NoError(t, err) + assert.Equal(t, iso18626.TypeMessageStatusOK, resp.SupplyingAgencyMessageConfirmation.ConfirmationHeader.MessageStatus) assert.Equal(t, BorrowerStateSupplierLocated, mockPrRepo.savedPr.State) } @@ -158,15 +164,16 @@ func TestHandleSupplyingAgencyMessageWillSupply(t *testing.T) { mockPrRepo.On("GetPatronRequestById", patronRequestId).Return(pr_db.PatronRequest{}, nil) handler := CreatePatronRequestMessageHandler(mockPrRepo, *new(events.EventRepo), *new(ill_db.IllRepo), *new(events.EventBus)) - status, result := handler.handleSupplyingAgencyMessage(appCtx, iso18626.SupplyingAgencyMessage{ + status, resp, err := handler.handleSupplyingAgencyMessage(appCtx, iso18626.SupplyingAgencyMessage{ Header: iso18626.Header{ RequestingAgencyRequestId: patronRequestId, }, StatusInfo: iso18626.StatusInfo{Status: iso18626.TypeStatusWillSupply}, }) assert.Equal(t, events.EventStatusSuccess, status) - assert.Equal(t, iso18626.TypeMessageStatusOK, result.OutgoingMessage.SupplyingAgencyMessageConfirmation.ConfirmationHeader.MessageStatus) + assert.Equal(t, iso18626.TypeMessageStatusOK, resp.SupplyingAgencyMessageConfirmation.ConfirmationHeader.MessageStatus) assert.Equal(t, BorrowerStateWillSupply, mockPrRepo.savedPr.State) + assert.NoError(t, err) } func TestHandleSupplyingAgencyMessageWillSupplyCondition(t *testing.T) { @@ -174,7 +181,7 @@ func TestHandleSupplyingAgencyMessageWillSupplyCondition(t *testing.T) { mockPrRepo.On("GetPatronRequestById", patronRequestId).Return(pr_db.PatronRequest{}, nil) handler := CreatePatronRequestMessageHandler(mockPrRepo, *new(events.EventRepo), *new(ill_db.IllRepo), *new(events.EventBus)) - status, result := handler.handleSupplyingAgencyMessage(appCtx, iso18626.SupplyingAgencyMessage{ + status, resp, err := handler.handleSupplyingAgencyMessage(appCtx, iso18626.SupplyingAgencyMessage{ Header: iso18626.Header{ RequestingAgencyRequestId: patronRequestId, }, @@ -184,8 +191,9 @@ func TestHandleSupplyingAgencyMessageWillSupplyCondition(t *testing.T) { }, }) assert.Equal(t, events.EventStatusSuccess, status) - assert.Equal(t, iso18626.TypeMessageStatusOK, result.OutgoingMessage.SupplyingAgencyMessageConfirmation.ConfirmationHeader.MessageStatus) + assert.Equal(t, iso18626.TypeMessageStatusOK, resp.SupplyingAgencyMessageConfirmation.ConfirmationHeader.MessageStatus) assert.Equal(t, BorrowerStateConditionPending, mockPrRepo.savedPr.State) + assert.NoError(t, err) } func TestHandleSupplyingAgencyMessageLoaned(t *testing.T) { @@ -193,15 +201,16 @@ func TestHandleSupplyingAgencyMessageLoaned(t *testing.T) { mockPrRepo.On("GetPatronRequestById", patronRequestId).Return(pr_db.PatronRequest{}, nil) handler := CreatePatronRequestMessageHandler(mockPrRepo, *new(events.EventRepo), *new(ill_db.IllRepo), *new(events.EventBus)) - status, result := handler.handleSupplyingAgencyMessage(appCtx, iso18626.SupplyingAgencyMessage{ + status, resp, err := handler.handleSupplyingAgencyMessage(appCtx, iso18626.SupplyingAgencyMessage{ Header: iso18626.Header{ RequestingAgencyRequestId: patronRequestId, }, StatusInfo: iso18626.StatusInfo{Status: iso18626.TypeStatusLoaned}, }) assert.Equal(t, events.EventStatusSuccess, status) - assert.Equal(t, iso18626.TypeMessageStatusOK, result.OutgoingMessage.SupplyingAgencyMessageConfirmation.ConfirmationHeader.MessageStatus) + assert.Equal(t, iso18626.TypeMessageStatusOK, resp.SupplyingAgencyMessageConfirmation.ConfirmationHeader.MessageStatus) assert.Equal(t, BorrowerStateShipped, mockPrRepo.savedPr.State) + assert.NoError(t, err) } func TestHandleSupplyingAgencyMessageLoanCompleted(t *testing.T) { @@ -209,15 +218,16 @@ func TestHandleSupplyingAgencyMessageLoanCompleted(t *testing.T) { mockPrRepo.On("GetPatronRequestById", patronRequestId).Return(pr_db.PatronRequest{}, nil) handler := CreatePatronRequestMessageHandler(mockPrRepo, *new(events.EventRepo), *new(ill_db.IllRepo), *new(events.EventBus)) - status, result := handler.handleSupplyingAgencyMessage(appCtx, iso18626.SupplyingAgencyMessage{ + status, resp, err := handler.handleSupplyingAgencyMessage(appCtx, iso18626.SupplyingAgencyMessage{ Header: iso18626.Header{ RequestingAgencyRequestId: patronRequestId, }, StatusInfo: iso18626.StatusInfo{Status: iso18626.TypeStatusLoanCompleted}, }) assert.Equal(t, events.EventStatusSuccess, status) - assert.Equal(t, iso18626.TypeMessageStatusOK, result.OutgoingMessage.SupplyingAgencyMessageConfirmation.ConfirmationHeader.MessageStatus) + assert.Equal(t, iso18626.TypeMessageStatusOK, resp.SupplyingAgencyMessageConfirmation.ConfirmationHeader.MessageStatus) assert.Equal(t, BorrowerStateCompleted, mockPrRepo.savedPr.State) + assert.NoError(t, err) } func TestHandleSupplyingAgencyMessageUnfilled(t *testing.T) { @@ -225,15 +235,16 @@ func TestHandleSupplyingAgencyMessageUnfilled(t *testing.T) { mockPrRepo.On("GetPatronRequestById", patronRequestId).Return(pr_db.PatronRequest{}, nil) handler := CreatePatronRequestMessageHandler(mockPrRepo, *new(events.EventRepo), *new(ill_db.IllRepo), *new(events.EventBus)) - status, result := handler.handleSupplyingAgencyMessage(appCtx, iso18626.SupplyingAgencyMessage{ + status, resp, err := handler.handleSupplyingAgencyMessage(appCtx, iso18626.SupplyingAgencyMessage{ Header: iso18626.Header{ RequestingAgencyRequestId: patronRequestId, }, StatusInfo: iso18626.StatusInfo{Status: iso18626.TypeStatusUnfilled}, }) assert.Equal(t, events.EventStatusSuccess, status) - assert.Equal(t, iso18626.TypeMessageStatusOK, result.OutgoingMessage.SupplyingAgencyMessageConfirmation.ConfirmationHeader.MessageStatus) + assert.Equal(t, iso18626.TypeMessageStatusOK, resp.SupplyingAgencyMessageConfirmation.ConfirmationHeader.MessageStatus) assert.Equal(t, BorrowerStateUnfilled, mockPrRepo.savedPr.State) + assert.NoError(t, err) } func TestHandleSupplyingAgencyMessageCancelled(t *testing.T) { @@ -241,15 +252,16 @@ func TestHandleSupplyingAgencyMessageCancelled(t *testing.T) { mockPrRepo.On("GetPatronRequestById", patronRequestId).Return(pr_db.PatronRequest{}, nil) handler := CreatePatronRequestMessageHandler(mockPrRepo, *new(events.EventRepo), *new(ill_db.IllRepo), *new(events.EventBus)) - status, result := handler.handleSupplyingAgencyMessage(appCtx, iso18626.SupplyingAgencyMessage{ + status, resp, err := handler.handleSupplyingAgencyMessage(appCtx, iso18626.SupplyingAgencyMessage{ Header: iso18626.Header{ RequestingAgencyRequestId: patronRequestId, }, StatusInfo: iso18626.StatusInfo{Status: iso18626.TypeStatusCancelled}, }) assert.Equal(t, events.EventStatusSuccess, status) - assert.Equal(t, iso18626.TypeMessageStatusOK, result.OutgoingMessage.SupplyingAgencyMessageConfirmation.ConfirmationHeader.MessageStatus) + assert.Equal(t, iso18626.TypeMessageStatusOK, resp.SupplyingAgencyMessageConfirmation.ConfirmationHeader.MessageStatus) assert.Equal(t, BorrowerStateCancelled, mockPrRepo.savedPr.State) + assert.NoError(t, err) } func TestHandleSupplyingAgencyMessageNoImplemented(t *testing.T) { @@ -257,13 +269,14 @@ func TestHandleSupplyingAgencyMessageNoImplemented(t *testing.T) { mockPrRepo.On("GetPatronRequestById", patronRequestId).Return(pr_db.PatronRequest{}, nil) handler := CreatePatronRequestMessageHandler(mockPrRepo, *new(events.EventRepo), *new(ill_db.IllRepo), *new(events.EventBus)) - status, result := handler.handleSupplyingAgencyMessage(appCtx, iso18626.SupplyingAgencyMessage{ + status, resp, err := handler.handleSupplyingAgencyMessage(appCtx, iso18626.SupplyingAgencyMessage{ Header: iso18626.Header{ RequestingAgencyRequestId: patronRequestId, }, StatusInfo: iso18626.StatusInfo{Status: iso18626.TypeStatusEmpty}, }) assert.Equal(t, events.EventStatusProblem, status) - assert.Equal(t, iso18626.TypeMessageStatusERROR, result.OutgoingMessage.SupplyingAgencyMessageConfirmation.ConfirmationHeader.MessageStatus) - assert.Equal(t, "status change no allowed", result.OutgoingMessage.SupplyingAgencyMessageConfirmation.ErrorData.ErrorValue) + assert.Equal(t, iso18626.TypeMessageStatusERROR, resp.SupplyingAgencyMessageConfirmation.ConfirmationHeader.MessageStatus) + assert.Equal(t, "status change no allowed", resp.SupplyingAgencyMessageConfirmation.ErrorData.ErrorValue) + assert.Equal(t, "status change no allowed", err.Error()) } From 70628ff910dddc8cf091b1cfa8201dfe96b4a545 Mon Sep 17 00:00:00 2001 From: Janis Saldabols Date: Tue, 25 Nov 2025 14:10:09 +0200 Subject: [PATCH 10/10] CROSSLINK-181 Fix PR comments --- broker/client/client.go | 6 +-- broker/client/client_test.go | 6 +-- .../patron_request/api/api-handler_test.go | 50 +++++++++++++++---- .../patron_request/service/action_test.go | 1 + 4 files changed, 47 insertions(+), 16 deletions(-) create mode 100644 broker/test/patron_request/service/action_test.go diff --git a/broker/client/client.go b/broker/client/client.go index f742bddb..3cf5cf95 100644 --- a/broker/client/client.go +++ b/broker/client/client.go @@ -440,7 +440,7 @@ func (c *Iso18626Client) checkConfirmationError(ctx common.ExtendedContext, resp return status } -func (c *Iso18626Client) SendIllMessage(ctx common.ExtendedContext, peer *ill_db.Peer, msg *iso18626.ISO18626Message) (*iso18626.ISO18626Message, error) { +func (c *Iso18626Client) HandleIllMessage(ctx common.ExtendedContext, peer *ill_db.Peer, msg *iso18626.ISO18626Message) (*iso18626.ISO18626Message, error) { if strings.Contains(peer.Name, "local") { // TODO Implement real check of local peer return c.prMessageHandler.HandleMessage(ctx, msg) } else { @@ -705,7 +705,7 @@ func (c *Iso18626Client) sendAndUpdateStatus(ctx common.ExtendedContext, trCtx t resData.CustomData = map[string]any{common.DO_NOT_SEND: true} resData.OutgoingMessage = nil } else { - response, err := c.SendIllMessage(ctx, trCtx.requester, message) + response, err := c.HandleIllMessage(ctx, trCtx.requester, message) if response != nil { resData.IncomingMessage = response } @@ -800,7 +800,7 @@ func (c *Iso18626Client) sendAndUpdateSupplier(ctx common.ExtendedContext, trCtx resData := events.EventResult{} resData.OutgoingMessage = message if !isDoNotSend(trCtx.event) { - response, err := c.SendIllMessage(ctx, trCtx.selectedPeer, message) + response, err := c.HandleIllMessage(ctx, trCtx.selectedPeer, message) if response != nil { resData.IncomingMessage = response } diff --git a/broker/client/client_test.go b/broker/client/client_test.go index 3a58593b..43a772ee 100644 --- a/broker/client/client_test.go +++ b/broker/client/client_test.go @@ -850,7 +850,7 @@ func TestUpdateSupplierNote(t *testing.T) { assert.Equal(t, "Supplier: SUP1, Original note 2", sam.MessageInfo.Note) } -func TestSendIllMessage(t *testing.T) { +func TestHandleIllMessage(t *testing.T) { mockPrMessageHandler := prservice.CreatePatronRequestMessageHandler(new(MockPrRepo), new(events.PgEventRepo), new(ill_db.PgIllRepo), new(events.PostgresEventBus)) appCtx := common.CreateExtCtxWithArgs(context.Background(), nil) @@ -864,10 +864,10 @@ func TestSendIllMessage(t *testing.T) { } // To local peer - _, err := client.SendIllMessage(appCtx, &ill_db.Peer{Name: "local peer"}, &sam) + _, err := client.HandleIllMessage(appCtx, &ill_db.Peer{Name: "local peer"}, &sam) assert.Equal(t, "searching pr with id=req-1", err.Error()) - _, err = client.SendIllMessage(appCtx, &ill_db.Peer{Name: "random peer"}, &sam) + _, err = client.HandleIllMessage(appCtx, &ill_db.Peer{Name: "random peer"}, &sam) assert.Equal(t, "Post \"\": unsupported protocol scheme \"\"", err.Error()) } diff --git a/broker/test/patron_request/api/api-handler_test.go b/broker/test/patron_request/api/api-handler_test.go index 217deb76..895920aa 100644 --- a/broker/test/patron_request/api/api-handler_test.go +++ b/broker/test/patron_request/api/api-handler_test.go @@ -5,11 +5,14 @@ import ( "context" "encoding/json" "github.com/google/uuid" + "github.com/indexdata/crosslink/broker/adapter" "github.com/indexdata/crosslink/broker/app" + "github.com/indexdata/crosslink/broker/ill_db" proapi "github.com/indexdata/crosslink/broker/patron_request/oapi" prservice "github.com/indexdata/crosslink/broker/patron_request/service" apptest "github.com/indexdata/crosslink/broker/test/apputils" test "github.com/indexdata/crosslink/broker/test/utils" + mockapp "github.com/indexdata/crosslink/illmock/app" "github.com/indexdata/go-utils/utils" "github.com/stretchr/testify/assert" "github.com/testcontainers/testcontainers-go" @@ -24,6 +27,7 @@ import ( ) var basePath = "/patron_requests" +var illRepo ill_db.IllRepo func TestMain(m *testing.M) { app.TENANT_TO_SYMBOL = "ISIL:DK-{tenant}" @@ -45,9 +49,20 @@ func TestMain(m *testing.M) { app.ConnectionString = connStr app.MigrationsFolder = "file://../../../migrations" app.HTTP_PORT = utils.Must(test.GetFreePort()) + mockPort := strconv.Itoa(utils.Must(test.GetFreePort())) + localAddress := "http://localhost:" + strconv.Itoa(app.HTTP_PORT) + "/iso18626" + test.Expect(os.Setenv("HTTP_PORT", mockPort), "failed to set mock client port") + test.Expect(os.Setenv("PEER_URL", localAddress), "failed to set peer URL") + + adapter.MOCK_CLIENT_URL = "http://localhost:" + mockPort + "/iso18626" + + go func() { + var mockApp mockapp.MockApp + test.Expect(mockApp.Run(), "failed to start illmock client") + }() ctx, cancel := context.WithCancel(context.Background()) - _, _, _ = apptest.StartApp(ctx) + _, illRepo, _ = apptest.StartApp(ctx) test.WaitForServiceUp(app.HTTP_PORT) defer cancel() @@ -58,16 +73,16 @@ func TestMain(m *testing.M) { } func TestCrud(t *testing.T) { + reqPeer := apptest.CreatePeer(t, illRepo, "localISIL:REQ"+uuid.NewString(), adapter.MOCK_CLIENT_URL) + supPeer := apptest.CreatePeer(t, illRepo, "ISIL:SUP1", adapter.MOCK_CLIENT_URL) // POST - landingId := "l1" - borrowingId := "b1" requester := "r1" illMessage := "{\"request\": {}}" newPr := proapi.CreatePatronRequest{ ID: uuid.NewString(), Timestamp: time.Now(), - LendingPeerId: &landingId, - BorrowingPeerId: &borrowingId, + LendingPeerId: &supPeer.ID, + BorrowingPeerId: &reqPeer.ID, Requester: &requester, IllRequest: &illMessage, } @@ -106,8 +121,8 @@ func TestCrud(t *testing.T) { assert.Equal(t, newPr.ID, foundPr.ID) // PUT update - landingId = "l2" - borrowingId = "b2" + landingId := "l2" + borrowingId := "b2" requester = "r2" updatedPr := proapi.PatronRequest{ ID: newPr.ID, @@ -128,8 +143,8 @@ func TestCrud(t *testing.T) { assert.True(t, foundPr.State != "ACCEPTED") assert.Equal(t, prservice.SideBorrowing, foundPr.Side) assert.Equal(t, newPr.Timestamp.YearDay(), foundPr.Timestamp.YearDay()) - assert.Equal(t, "l1", *foundPr.LendingPeerId) - assert.Equal(t, "b1", *foundPr.BorrowingPeerId) + assert.Equal(t, supPeer.ID, *foundPr.LendingPeerId) + assert.Equal(t, reqPeer.ID, *foundPr.BorrowingPeerId) assert.Equal(t, *updatedPr.Requester, *foundPr.Requester) // Only requester can be updated now assert.Equal(t, *newPr.IllRequest, *foundPr.IllRequest) @@ -144,7 +159,22 @@ func TestCrud(t *testing.T) { actionBytes, err := json.Marshal(action) assert.NoError(t, err, "failed to marshal patron request action") respBytes = httpRequest(t, "POST", thisPrPath+"/action", actionBytes, 200) - assert.Equal(t, "{\"actionResult\":\"ERROR\"}\n", string(respBytes)) + assert.Equal(t, "{\"actionResult\":\"SUCCESS\"}\n", string(respBytes)) + + // Wait till requester response processed + test.WaitForPredicateToBeTrue(func() bool { + respBytes = httpRequest(t, "GET", thisPrPath+"/actions", []byte{}, 200) + return string(respBytes) == "[\"receive\"]\n" + }) + + // POST blocking action + action = proapi.ExecuteAction{ + Action: "receive", + } + actionBytes, err = json.Marshal(action) + assert.NoError(t, err, "failed to marshal patron request action") + respBytes = httpRequest(t, "POST", thisPrPath+"/action", actionBytes, 200) + assert.Equal(t, "{\"actionResult\":\"SUCCESS\"}\n", string(respBytes)) // TODO Do we really want to delete from DB or just add DELETED status ? //// DELETE patron request diff --git a/broker/test/patron_request/service/action_test.go b/broker/test/patron_request/service/action_test.go new file mode 100644 index 00000000..6d43c336 --- /dev/null +++ b/broker/test/patron_request/service/action_test.go @@ -0,0 +1 @@ +package service