Skip to content

Conversation

@adamdickmeiss
Copy link
Contributor

@adamdickmeiss adamdickmeiss commented Jan 8, 2026

Copilot AI review requested due to automatic review settings January 8, 2026 13:36
@adamdickmeiss adamdickmeiss marked this pull request as draft January 8, 2026 13:36
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request implements NCIP support for supplier-side operations in the Crosslink ILL brokering service. The changes enable supplier libraries to validate their institutional patron accounts and perform LMS operations via NCIP when acting as lenders.

Key changes:

  • Refactored LmsCreator interface to accept string symbols instead of pgtype.Text for cleaner API design
  • Added InstitutionalPatron() method to LmsAdapter interface to support supplier-side validation
  • Consolidated ILL request parsing and LMS adapter creation to reduce code duplication in action handlers

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
broker/patron_request/service/action.go Refactored action handling to parse ILL requests and create LMS adapters early; added supplier-side NCIP calls for validate, will-supply, ship, and mark-received actions
broker/patron_request/service/action_test.go Updated tests for new method signatures; added edge case tests for missing LMS creator, invalid ILL requests, and missing symbols; removed duplicate/redundant tests
broker/lms/lms_creator.go Changed GetAdapter interface signature from pgtype.Text to string
broker/lms/lms_creator_impl.go Simplified implementation by removing symbol validation (now handled by callers)
broker/lms/lms_creator_test.go Updated tests to use string symbols instead of pgtype.Text
broker/lms/lms_adapter.go Added InstitutionalPatron() method to interface
broker/lms/lms_adapter_ncip.go Implemented InstitutionalPatron() with configurable institutional_patron field
broker/lms/lms_adapter_manual.go Added stub InstitutionalPatron() implementation returning empty string
go.work.sum Transitive dependency updates from Go workspace

Comment on lines +110 to +114
var illRequest iso18626.Request
err = json.Unmarshal(pr.IllRequest, &illRequest)
if err != nil {
return events.LogErrorAndReturnResult(ctx, "failed to parse ILL request", err)
}
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ILL request parsing at lines 110-114 happens unconditionally for all actions, but not all actions require the ILL request data. Actions like BorrowerActionCancelRequest, BorrowerActionAcceptCondition, BorrowerActionRejectCondition, LenderActionCannotSupply, LenderActionAddCondition, and LenderActionMarkCancelled don't use the illRequest parameter. This means these actions will fail if pr.IllRequest contains invalid JSON, even though they don't need it. Consider moving the ILL request parsing to only happen when needed, or ensure pr.IllRequest is always valid JSON even for actions that don't require it.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Premature optimization. The illRequest bytes are stored in the DB anyway, so this makes no difference for the user.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 10 comments.

return a.validateBorrowingRequest(ctx, pr, lmsAdapter)
case BorrowerActionSendRequest:
return a.sendBorrowingRequest(ctx, pr)
return a.sendBorrowingRequest(ctx, pr, illRequest)
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The sendBorrowingRequest action is not receiving the lmsAdapter parameter even though it's retrieved in handleBorrowingAction. While this action may not currently need it, the inconsistency in parameter passing could lead to confusion. Consider either passing the adapter to all actions or restructuring to only retrieve it when needed.

Copilot uses AI. Check for mistakes.
Comment on lines +483 to +487
// TODO set these values properly
itemId := illRequest.BibliographicInfo.SupplierUniqueRecordId
requestId := ""
borrowerBarcode := ""
externalReferenceValue := ""
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using empty strings as placeholders for required NCIP parameters (requestId, borrowerBarcode, externalReferenceValue) may cause the LMS CheckOutItem operation to fail or behave unexpectedly. These TODO comments indicate incomplete implementation that should be addressed before merging.

Suggested change
// TODO set these values properly
itemId := illRequest.BibliographicInfo.SupplierUniqueRecordId
requestId := ""
borrowerBarcode := ""
externalReferenceValue := ""
itemId := illRequest.BibliographicInfo.SupplierUniqueRecordId
// Use non-empty values for required LMS parameters; fall back to itemId as a stable surrogate.
requestId := itemId
borrowerBarcode := itemId
externalReferenceValue := itemId

Copilot uses AI. Check for mistakes.
Comment on lines +498 to +499
// TODO set these values properly
itemId := illRequest.BibliographicInfo.SupplierUniqueRecordId
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using an empty itemId placeholder may cause the LMS CheckInItem operation to fail. This TODO comment indicates incomplete implementation that should be addressed before merging.

Suggested change
// TODO set these values properly
itemId := illRequest.BibliographicInfo.SupplierUniqueRecordId
itemId := strings.TrimSpace(illRequest.BibliographicInfo.SupplierUniqueRecordId)
if itemId == "" {
return events.LogErrorAndReturnResult(ctx, "LMS CheckInItem failed", errors.New("missing supplier unique record id for item check-in"))
}

Copilot uses AI. Check for mistakes.
Comment on lines 85 to 94
func TestHandleInvokeActionNoLms(t *testing.T) {
mockPrRepo := new(MockPrRepo)
prAction := CreatePatronRequestActionService(mockPrRepo, *new(events.EventBus), new(handler.Iso18626Handler), nil)
mockPrRepo.On("GetPatronRequestById", patronRequestId).Return(pr_db.PatronRequest{RequesterSymbol: pgtype.Text{Valid: true, String: "ISIL:x"}, State: BorrowerStateNew, Side: SideBorrowing, Tenant: pgtype.Text{Valid: true, String: "testlib"}}, 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, "LMS creator not configured", resultData.EventError.Message)
}
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test expects the error "LMS creator not configured" but doesn't provide an IllRequest field in the PatronRequest. According to the new code logic, it would fail earlier at line 107-114 with "failed to parse ILL request" before reaching the LMS creator check. The test should include a valid IllRequest field.

Copilot uses AI. Check for mistakes.
if !a.actionMappingService.GetActionMapping(pr).IsActionAvailable(pr, action) {
return events.LogErrorAndReturnResult(ctx, "state "+string(pr.State)+" does not support action "+string(action), errors.New("invalid action"))
}
if a.lmsCreator == nil {
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The LMS creator validation happens for all actions and sides, but some actions like cancelBorrowingRequest, acceptConditionBorrowingRequest, and rejectConditionBorrowingRequest don't use the LMS adapter. This check should be moved to only apply when LMS operations are actually needed, or those actions should be handled before this validation.

Suggested change
if a.lmsCreator == nil {
requiresLMS := true
switch action {
case "cancelBorrowingRequest", "acceptConditionBorrowingRequest", "rejectConditionBorrowingRequest":
requiresLMS = false
}
if requiresLMS && a.lmsCreator == nil {

Copilot uses AI. Check for mistakes.
Comment on lines +129 to +132
lmsAdapter, err := a.lmsCreator.GetAdapter(ctx, pr.RequesterSymbol.String)
if err != nil {
return events.LogErrorAndReturnResult(ctx, "failed to create LMS adapter", err)
}
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The LMS adapter is retrieved for all borrowing actions, but BorrowerActionSendRequest doesn't use it. The lmsAdapter parameter should only be passed to actions that actually need it, or the GetAdapter call should be moved to the individual action handlers that require it.

Copilot uses AI. Check for mistakes.
Comment on lines +449 to +457
func (a *PatronRequestActionService) willSupplyLenderRequest(ctx common.ExtendedContext, pr pr_db.PatronRequest, lmsAdapter lms.LmsAdapter, illRequest iso18626.Request) (events.EventStatus, *events.EventResult) {
// TODO set these values properly
itemId := illRequest.BibliographicInfo.SupplierUniqueRecordId
requestId := ""
borrowerBarCode := ""
pickupLocation := ""
itemLocation := ""
err := lmsAdapter.RequestItem(requestId, itemId, borrowerBarCode, pickupLocation, itemLocation)
if err != nil {
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using empty strings as placeholders for required NCIP parameters (requestId, borrowerBarCode, pickupLocation, itemLocation) may cause the LMS RequestItem operation to fail or behave unexpectedly. These TODO comments indicate incomplete implementation that should be addressed before merging.

Suggested change
func (a *PatronRequestActionService) willSupplyLenderRequest(ctx common.ExtendedContext, pr pr_db.PatronRequest, lmsAdapter lms.LmsAdapter, illRequest iso18626.Request) (events.EventStatus, *events.EventResult) {
// TODO set these values properly
itemId := illRequest.BibliographicInfo.SupplierUniqueRecordId
requestId := ""
borrowerBarCode := ""
pickupLocation := ""
itemLocation := ""
err := lmsAdapter.RequestItem(requestId, itemId, borrowerBarCode, pickupLocation, itemLocation)
if err != nil {
type lmsRequestItemParams struct {
RequestID string
ItemID string
BorrowerBarcode string
PickupLocation string
ItemLocation string
}
// buildLmsRequestItemParams constructs the parameters required for the LMS
// RequestItem operation. It returns an error if the parameters cannot be
// safely derived yet.
func buildLmsRequestItemParams(pr pr_db.PatronRequest, illRequest iso18626.Request) (lmsRequestItemParams, error) {
params := lmsRequestItemParams{
ItemID: illRequest.BibliographicInfo.SupplierUniqueRecordId,
}
if params.ItemID == "" {
return params, errors.New("missing item ID for LMS RequestItem")
}
// TODO: Derive RequestID, BorrowerBarcode, PickupLocation and ItemLocation
// from the patron request and/or ISO18626 request once the data mapping
// is defined. For now, signal that this is not fully implemented instead
// of calling the LMS with placeholder values.
return params, errors.New("building LMS RequestItem parameters not fully implemented")
}
func (a *PatronRequestActionService) willSupplyLenderRequest(ctx common.ExtendedContext, pr pr_db.PatronRequest, lmsAdapter lms.LmsAdapter, illRequest iso18626.Request) (events.EventStatus, *events.EventResult) {
params, err := buildLmsRequestItemParams(pr, illRequest)
if err != nil {
return events.LogErrorAndReturnResult(ctx, "LMS RequestItem parameters invalid", err)
}
err = lmsAdapter.RequestItem(params.RequestID, params.ItemID, params.BorrowerBarcode, params.PickupLocation, params.ItemLocation)
if err != nil {

Copilot uses AI. Check for mistakes.
Comment on lines +441 to +445
institutionalPatron := lms.InstitutionalPatron()
_, err := lms.LookupUser(institutionalPatron)
if err != nil {
return events.LogErrorAndReturnResult(ctx, "LMS LookupUser failed", err)
}
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If InstitutionalPatron returns an empty string (as it does in LmsAdapterManual), this will call LookupUser with an empty string which may not be the intended behavior. Consider checking if institutionalPatron is empty and handling that case explicitly, or documenting that an empty string is valid for manual adapters.

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants