Skip to content

Commit e0e93f5

Browse files
committed
Add missing audit events
1 parent 4f0477a commit e0e93f5

7 files changed

Lines changed: 143 additions & 11 deletions

File tree

broker/patron_request/service/action.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -714,9 +714,9 @@ func (a *PatronRequestActionService) cancelLocalBorrowingRequest(ctx common.Exte
714714
result := events.EventResult{}
715715
status, eventResult, httpStatus := a.sendSupplyingAgencyMessage(ctx, pr, &result,
716716
iso18626.MessageInfo{
717-
ReasonForMessage: iso18626.TypeReasonForMessageCancelResponse,
717+
ReasonForMessage: iso18626.TypeReasonForMessageStatusChange,
718718
},
719-
iso18626.StatusInfo{Status: iso18626.TypeStatusWillSupply},
719+
iso18626.StatusInfo{Status: iso18626.TypeStatusCancelled},
720720
nil)
721721
return a.checkSupplyingResponse(status, eventResult, &result, httpStatus, pr)
722722
}

broker/patron_request/service/action_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2647,8 +2647,8 @@ func TestHandleInvokeBorrowerActionCancelLocalSupply(t *testing.T) {
26472647
assert.NotNil(t, resultData)
26482648
assert.Equal(t, BorrowerStateCancelled, mockPrRepo.savedPr.State)
26492649
assert.NotNil(t, mockIso18626Handler.lastSupplyingAgencyMessage)
2650-
assert.Equal(t, iso18626.TypeReasonForMessageCancelResponse, mockIso18626Handler.lastSupplyingAgencyMessage.MessageInfo.ReasonForMessage)
2651-
assert.Equal(t, iso18626.TypeStatusWillSupply, mockIso18626Handler.lastSupplyingAgencyMessage.StatusInfo.Status)
2650+
assert.Equal(t, iso18626.TypeReasonForMessageStatusChange, mockIso18626Handler.lastSupplyingAgencyMessage.MessageInfo.ReasonForMessage)
2651+
assert.Equal(t, iso18626.TypeStatusCancelled, mockIso18626Handler.lastSupplyingAgencyMessage.StatusInfo.Status)
26522652
assert.False(t, mockIso18626Handler.lastSupplyingAgencyMessage.Header.Timestamp.IsZero())
26532653
assert.Nil(t, mockIso18626Handler.lastSupplyingAgencyMessage.MessageInfo.AnswerYesNo)
26542654
}

broker/patron_request/service/message-handler.go

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,10 @@ func (m *PatronRequestMessageHandler) handleSupplyingAgencyMessage(ctx common.Ex
181181
return m.handleSupplyingAgencyMessageWithParent(ctx, sam, pr, nil)
182182
}
183183

184+
func isLocalSupply(pr pr_db.PatronRequest, supplierSymbol string) bool {
185+
return pr.RequesterSymbol.Valid && pr.RequesterSymbol.String == supplierSymbol
186+
}
187+
184188
func (m *PatronRequestMessageHandler) handleSupplyingAgencyMessageWithParent(ctx common.ExtendedContext, sam iso18626.SupplyingAgencyMessage, pr pr_db.PatronRequest, parentEventID *string) (events.EventStatus, *iso18626.ISO18626Message, error) {
185189
unsupportedReason := func() (events.EventStatus, *iso18626.ISO18626Message, error) {
186190
err := fmt.Errorf("unsupported reason for message: %s", sam.MessageInfo.ReasonForMessage)
@@ -227,13 +231,12 @@ func (m *PatronRequestMessageHandler) handleSupplyingAgencyMessageWithParent(ctx
227231
Valid: true,
228232
}
229233
}
230-
231234
eventName := MessageEvent("")
232235
var retryBibInfo *iso18626.BibliographicInfo
233236
switch sam.StatusInfo.Status {
234237
case iso18626.TypeStatusExpectToSupply:
235238
eventName = SupplierExpectToSupply
236-
if pr.RequesterSymbol.String == supSymbol {
239+
if isLocalSupply(pr, supSymbol) {
237240
eventName = SupplierExpectToSupplyLocal
238241
}
239242
case iso18626.TypeStatusWillSupply:
@@ -261,15 +264,23 @@ func (m *PatronRequestMessageHandler) handleSupplyingAgencyMessageWithParent(ctx
261264
eventName = SupplierLoaned
262265
case iso18626.TypeStatusLoanCompleted, iso18626.TypeStatusCopyCompleted:
263266
eventName = SupplierCompleted
267+
if isLocalSupply(pr, supSymbol) {
268+
eventName = SupplierCompletedLocal
269+
}
264270
case iso18626.TypeStatusUnfilled:
265271
eventName = SupplierUnfilled
272+
if isLocalSupply(pr, supSymbol) {
273+
eventName = SupplierUnfilledLocal
274+
}
266275
case iso18626.TypeStatusCancelled:
267-
// Cancellation transition is accepted only for cancel-response messages.
268276
if sam.MessageInfo.ReasonForMessage == iso18626.TypeReasonForMessageCancelResponse {
269277
if sam.MessageInfo.AnswerYesNo != nil && *sam.MessageInfo.AnswerYesNo == iso18626.TypeYesNoN {
270278
return contradictoryCancelResponse()
271279
}
272280
eventName = SupplierCancelAccepted
281+
} else if sam.MessageInfo.ReasonForMessage == iso18626.TypeReasonForMessageStatusChange &&
282+
isLocalSupply(pr, supSymbol) {
283+
eventName = SupplierCancelledLocal
273284
}
274285
case iso18626.TypeStatusRetryPossible:
275286
eventName = SupplierRetryConditional

broker/patron_request/service/message-handler_test.go

Lines changed: 107 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -736,22 +736,125 @@ func TestHandleSupplyingAgencyMessageCancelled(t *testing.T) {
736736
assert.NoError(t, err)
737737
}
738738

739-
func TestHandleSupplyingAgencyMessageCancelledStatusChangeRejected(t *testing.T) {
739+
func TestHandleSupplyingAgencyMessageCancelledStatusChangeAcceptedWithoutTransition(t *testing.T) {
740+
mockPrRepo := new(MockPrRepo)
741+
handler := CreatePatronRequestMessageHandler(mockPrRepo, *new(events.EventRepo), *new(ill_db.IllRepo), *new(events.EventBus))
742+
743+
status, resp, err := handler.handleSupplyingAgencyMessage(appCtx, iso18626.SupplyingAgencyMessage{
744+
Header: iso18626.Header{
745+
SupplyingAgencyId: iso18626.TypeAgencyId{
746+
AgencyIdType: iso18626.TypeSchemeValuePair{Text: "ISIL"},
747+
AgencyIdValue: "REQ",
748+
},
749+
RequestingAgencyRequestId: patronRequestId,
750+
},
751+
MessageInfo: iso18626.MessageInfo{
752+
ReasonForMessage: iso18626.TypeReasonForMessageStatusChange,
753+
},
754+
StatusInfo: iso18626.StatusInfo{Status: iso18626.TypeStatusCancelled},
755+
}, pr_db.PatronRequest{
756+
ID: patronRequestId,
757+
RequesterSymbol: pgtype.Text{Valid: true, String: "ISIL:REQ"},
758+
State: BorrowerStateLocalSupply,
759+
Side: SideBorrowing,
760+
})
761+
assert.Equal(t, events.EventStatusSuccess, status)
762+
assert.Equal(t, iso18626.TypeMessageStatusOK, resp.SupplyingAgencyMessageConfirmation.ConfirmationHeader.MessageStatus)
763+
assert.NoError(t, err)
764+
assert.Equal(t, patronRequestId, mockPrRepo.savedPr.ID)
765+
assert.Equal(t, BorrowerStateLocalSupply, mockPrRepo.savedPr.State)
766+
}
767+
768+
func TestHandleSupplyingAgencyMessageCancelledStatusChangeRejectedWhenNotLocalSupply(t *testing.T) {
740769
handler := CreatePatronRequestMessageHandler(new(MockPrRepo), *new(events.EventRepo), *new(ill_db.IllRepo), *new(events.EventBus))
741770

742771
status, resp, err := handler.handleSupplyingAgencyMessage(appCtx, iso18626.SupplyingAgencyMessage{
743772
Header: iso18626.Header{
773+
SupplyingAgencyId: iso18626.TypeAgencyId{
774+
AgencyIdType: iso18626.TypeSchemeValuePair{Text: "ISIL"},
775+
AgencyIdValue: "REQ",
776+
},
744777
RequestingAgencyRequestId: patronRequestId,
745778
},
746779
MessageInfo: iso18626.MessageInfo{
747780
ReasonForMessage: iso18626.TypeReasonForMessageStatusChange,
748781
},
749782
StatusInfo: iso18626.StatusInfo{Status: iso18626.TypeStatusCancelled},
750-
}, pr_db.PatronRequest{})
783+
}, pr_db.PatronRequest{
784+
RequesterSymbol: pgtype.Text{Valid: true, String: "ISIL:REQ"},
785+
State: BorrowerStateSent,
786+
Side: SideBorrowing,
787+
})
751788
assert.Equal(t, events.EventStatusProblem, status)
752789
assert.Equal(t, iso18626.TypeMessageStatusERROR, resp.SupplyingAgencyMessageConfirmation.ConfirmationHeader.MessageStatus)
753-
assert.Contains(t, resp.SupplyingAgencyMessageConfirmation.ErrorData.ErrorValue, "status change not allowed:")
754-
assert.Contains(t, err.Error(), "status change not allowed:")
790+
if assert.Error(t, err) {
791+
assert.Contains(t, err.Error(), "status change not allowed:")
792+
}
793+
}
794+
795+
func TestHandleSupplyingAgencyMessageLocalSupplyCompletedAcceptedWithoutTransition(t *testing.T) {
796+
for _, completedStatus := range []iso18626.TypeStatus{
797+
iso18626.TypeStatusLoanCompleted,
798+
iso18626.TypeStatusCopyCompleted,
799+
} {
800+
t.Run(string(completedStatus), func(t *testing.T) {
801+
mockPrRepo := new(MockPrRepo)
802+
handler := CreatePatronRequestMessageHandler(mockPrRepo, *new(events.EventRepo), *new(ill_db.IllRepo), *new(events.EventBus))
803+
804+
status, resp, err := handler.handleSupplyingAgencyMessage(appCtx, iso18626.SupplyingAgencyMessage{
805+
Header: iso18626.Header{
806+
SupplyingAgencyId: iso18626.TypeAgencyId{
807+
AgencyIdType: iso18626.TypeSchemeValuePair{Text: "ISIL"},
808+
AgencyIdValue: "REQ",
809+
},
810+
RequestingAgencyRequestId: patronRequestId,
811+
},
812+
MessageInfo: iso18626.MessageInfo{
813+
ReasonForMessage: iso18626.TypeReasonForMessageStatusChange,
814+
},
815+
StatusInfo: iso18626.StatusInfo{Status: completedStatus},
816+
}, pr_db.PatronRequest{
817+
ID: patronRequestId,
818+
RequesterSymbol: pgtype.Text{Valid: true, String: "ISIL:REQ"},
819+
State: BorrowerStateLocalSupply,
820+
Side: SideBorrowing,
821+
})
822+
assert.Equal(t, events.EventStatusSuccess, status)
823+
assert.Equal(t, iso18626.TypeMessageStatusOK, resp.SupplyingAgencyMessageConfirmation.ConfirmationHeader.MessageStatus)
824+
assert.NoError(t, err)
825+
assert.Equal(t, patronRequestId, mockPrRepo.savedPr.ID)
826+
assert.Equal(t, BorrowerStateLocalSupply, mockPrRepo.savedPr.State)
827+
})
828+
}
829+
}
830+
831+
func TestHandleSupplyingAgencyMessageLocalSupplyUnfilledAcceptedWithoutTransition(t *testing.T) {
832+
mockPrRepo := new(MockPrRepo)
833+
handler := CreatePatronRequestMessageHandler(mockPrRepo, *new(events.EventRepo), *new(ill_db.IllRepo), *new(events.EventBus))
834+
835+
status, resp, err := handler.handleSupplyingAgencyMessage(appCtx, iso18626.SupplyingAgencyMessage{
836+
Header: iso18626.Header{
837+
SupplyingAgencyId: iso18626.TypeAgencyId{
838+
AgencyIdType: iso18626.TypeSchemeValuePair{Text: "ISIL"},
839+
AgencyIdValue: "REQ",
840+
},
841+
RequestingAgencyRequestId: patronRequestId,
842+
},
843+
MessageInfo: iso18626.MessageInfo{
844+
ReasonForMessage: iso18626.TypeReasonForMessageStatusChange,
845+
},
846+
StatusInfo: iso18626.StatusInfo{Status: iso18626.TypeStatusUnfilled},
847+
}, pr_db.PatronRequest{
848+
ID: patronRequestId,
849+
RequesterSymbol: pgtype.Text{Valid: true, String: "ISIL:REQ"},
850+
State: BorrowerStateLocalSupply,
851+
Side: SideBorrowing,
852+
})
853+
assert.Equal(t, events.EventStatusSuccess, status)
854+
assert.Equal(t, iso18626.TypeMessageStatusOK, resp.SupplyingAgencyMessageConfirmation.ConfirmationHeader.MessageStatus)
855+
assert.NoError(t, err)
856+
assert.Equal(t, patronRequestId, mockPrRepo.savedPr.ID)
857+
assert.Equal(t, BorrowerStateLocalSupply, mockPrRepo.savedPr.State)
755858
}
756859

757860
func TestHandleSupplyingAgencyMessageCancelResponseCancelled(t *testing.T) {

broker/patron_request/service/statemodel_capabilities.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,10 @@ const (
9898
SupplierWillSupplyCond MessageEvent = "will-supply-conditional"
9999
SupplierLoaned MessageEvent = "loaned"
100100
SupplierCompleted MessageEvent = "completed"
101+
SupplierCompletedLocal MessageEvent = "completed-local"
101102
SupplierUnfilled MessageEvent = "unfilled"
103+
SupplierUnfilledLocal MessageEvent = "unfilled-local"
104+
SupplierCancelledLocal MessageEvent = "cancelled-local"
102105
SupplierCancelAccepted MessageEvent = "cancel-accepted"
103106
SupplierCancelRejected MessageEvent = "cancel-rejected"
104107
SupplierRetryConditional MessageEvent = "retry-conditional"
@@ -299,7 +302,10 @@ func supplierBuiltInMessageEvents() []string {
299302
string(SupplierWillSupplyCond),
300303
string(SupplierLoaned),
301304
string(SupplierCompleted),
305+
string(SupplierCompletedLocal),
302306
string(SupplierUnfilled),
307+
string(SupplierUnfilledLocal),
308+
string(SupplierCancelledLocal),
303309
string(SupplierCancelAccepted),
304310
string(SupplierCancelRejected),
305311
string(SupplierRetryConditional),

broker/patron_request/service/statemodel_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ func TestBuiltInStateModelCapabilities(t *testing.T) {
3131
}))
3232

3333
assert.True(t, slices.Contains(c.SupplierMessageEvents, string(SupplierWillSupply)))
34+
assert.True(t, slices.Contains(c.SupplierMessageEvents, string(SupplierCancelledLocal)))
35+
assert.True(t, slices.Contains(c.SupplierMessageEvents, string(SupplierCompletedLocal)))
36+
assert.True(t, slices.Contains(c.SupplierMessageEvents, string(SupplierUnfilledLocal)))
3437
assert.True(t, slices.Contains(c.RequesterMessageEvents, string(RequesterCancelRequest)))
3538
assert.True(t, slices.Contains(c.RequesterMessageEvents, string(RequesterReceived)))
3639
assert.True(t, slices.Contains(c.SupplierMessageEvents, string(SupplierCancelRejected)))

misc/state-models.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,15 @@ stateModels:
5656
primaryAction: fill-locally
5757
needsAttention: true
5858
editable: true
59+
# Audit-only events emitted by local-supply actions. These must not define
60+
# transitions; the corresponding actions own the state transitions.
61+
events:
62+
- name: completed-local
63+
desc: Local-supply completion recorded for audit
64+
- name: cancelled-local
65+
desc: Local-supply cancellation recorded for audit
66+
- name: unfilled-local
67+
desc: Local-supply failure recorded for audit
5968
actions:
6069
- name: fill-locally
6170
desc: Fill the request locally

0 commit comments

Comments
 (0)