Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions broker/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,20 +190,17 @@ func Init(ctx context.Context) (Context, error) {
iso18626Handler := handler.CreateIso18626Handler(eventBus, eventRepo, illRepo, dirAdapter)
lmsCreator := lms.NewLmsCreator(illRepo, dirAdapter)
lookupAdapterCreator := catalog.NewLookupAdapterCreator(AVAILABILITY_ADAPTER, METAPROXY_URL)
prActionService := prservice.CreatePatronRequestActionService(prRepo, illRepo, eventBus, &iso18626Handler, lmsCreator, email.NewEmailService())
lookupAdapterFactory := service.NewLookupAdapterFactory(illRepo, dirAdapter, CONSORTIUM_SYMBOL, lookupAdapterEnv, lookupAdapterCreator)
prActionService := prservice.CreatePatronRequestActionService(prRepo, illRepo, eventBus, &iso18626Handler, lmsCreator, email.NewEmailService(), lookupAdapterFactory, dirAdapter)
prMessageHandler.SetAutoActionRunner(prActionService)
iso18626Client := client.CreateIso18626Client(eventBus, illRepo, prMessageHandler, MAX_MESSAGE_SIZE, delay)
lookupAdapterFactory := service.NewLookupAdapterFactory(illRepo, dirAdapter, CONSORTIUM_SYMBOL, lookupAdapterEnv, lookupAdapterCreator)
supplierLocator := service.CreateSupplierLocator(eventBus, illRepo, dirAdapter, lookupAdapterFactory)
workflowManager := service.CreateWorkflowManager(eventBus, illRepo, service.WorkflowConfig{})
tenantResolver := tenant.NewResolver().WithIllRepo(illRepo).WithLookupAdapter(dirAdapter).WithTenantToSymbol(TENANT_TO_SYMBOL)
apiHandler := api.NewApiHandler(eventRepo, illRepo, tenantResolver, API_PAGE_SIZE)
prApiHandler := prapi.NewPrApiHandler(prRepo, eventBus, eventRepo, tenantResolver, &iso18626Handler, API_PAGE_SIZE)
prApiHandler.SetAutoActionRunner(prActionService)
prApiHandler.SetActionTaskProcessor(prActionService)
prApiHandler.SetIllRepo(illRepo)
prApiHandler.SetDirectoryLookupAdapter(dirAdapter)
prApiHandler.SetLookupAdapterFactory(lookupAdapterFactory)
sseBroker := api.NewSseBroker(appCtx, tenantResolver)
psApiHandler := psapi.NewPsApiHandler(psRepo, prRepo, tenantResolver)

Expand Down
93 changes: 9 additions & 84 deletions broker/patron_request/api/api-handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,15 @@ import (
"github.com/go-playground/validator/v10"
"github.com/google/uuid"
"github.com/indexdata/cql-go/cqlbuilder"
"github.com/indexdata/crosslink/broker/adapter"
"github.com/indexdata/crosslink/broker/api"
"github.com/indexdata/crosslink/broker/catalog"
"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"
"github.com/indexdata/crosslink/broker/oapi"
pr_db "github.com/indexdata/crosslink/broker/patron_request/db"
"github.com/indexdata/crosslink/broker/patron_request/proapi"
prservice "github.com/indexdata/crosslink/broker/patron_request/service"
"github.com/indexdata/crosslink/broker/service"
"github.com/indexdata/crosslink/broker/tenant"
dirapi "github.com/indexdata/crosslink/directory/api"
"github.com/indexdata/crosslink/iso18626"
"github.com/indexdata/go-utils/utils"
"github.com/jackc/pgerrcode"
Expand All @@ -44,18 +39,15 @@ var brokerSymbol = utils.GetEnv("BROKER_SYMBOL", "ISIL:BROKER")
var errInvalidPatronRequest = errors.New("invalid patron request")

type PatronRequestApiHandler struct {
limitDefault int32
prRepo pr_db.PrRepo
eventBus events.EventBus
eventRepo events.EventRepo
actionMappingService prservice.ActionMappingService
autoActionRunner prservice.AutoActionRunner
actionTaskProcessor ActionTaskProcessor
tenantResolver *tenant.TenantResolver
notificationSender prservice.PatronRequestNotificationService
lookupAdapterFactory *service.LookupAdapterFactory
illRepo ill_db.IllRepo
directoryLookupAdapter adapter.DirectoryLookupAdapter
limitDefault int32
prRepo pr_db.PrRepo
eventBus events.EventBus
eventRepo events.EventRepo
actionMappingService prservice.ActionMappingService
autoActionRunner prservice.AutoActionRunner
actionTaskProcessor ActionTaskProcessor
tenantResolver *tenant.TenantResolver
notificationSender prservice.PatronRequestNotificationService
}

func NewPrApiHandler(prRepo pr_db.PrRepo, eventBus events.EventBus,
Expand All @@ -79,18 +71,6 @@ func (a *PatronRequestApiHandler) SetActionTaskProcessor(actionTaskProcessor Act
a.actionTaskProcessor = actionTaskProcessor
}

func (a *PatronRequestApiHandler) SetLookupAdapterFactory(lookupAdapterFactory *service.LookupAdapterFactory) {
a.lookupAdapterFactory = lookupAdapterFactory
}

func (a *PatronRequestApiHandler) SetIllRepo(illRepo ill_db.IllRepo) {
a.illRepo = illRepo
}

func (a *PatronRequestApiHandler) SetDirectoryLookupAdapter(directoryLookupAdapter adapter.DirectoryLookupAdapter) {
a.directoryLookupAdapter = directoryLookupAdapter
}

func decodeRequiredBody[T any](r *http.Request, dst *T) error {
if r.Body == nil || r.Body == http.NoBody {
return errors.New("body is required")
Expand Down Expand Up @@ -288,38 +268,6 @@ func AddOwnerRestriction(queryBuilder *cqlbuilder.QueryBuilder, symbol string, s
return queryBuilder, err
}

func (a *PatronRequestApiHandler) metadataUpdate(ctx common.ExtendedContext, illRequest *iso18626.Request, requesterPeer ill_db.Peer) error {
if a.lookupAdapterFactory == nil {
return nil
}
lookupAdapter, configPeer, err := a.lookupAdapterFactory.GetAdapterRequester(ctx, requesterPeer)
if err != nil {
return fmt.Errorf("failed to get lookup adapter: %w", err)
}
if lookupAdapter == nil {
return nil
}

mode := dirapi.None
if configPeer.CatalogConfig != nil && configPeer.CatalogConfig.MetadataUpdateMode != nil {
mode = *configPeer.CatalogConfig.MetadataUpdateMode
}
if mode == dirapi.None {
return nil
}
lookupParams := catalog.LookupParamsFromBibliographicInfo(illRequest.BibliographicInfo, illRequest.ServiceInfo)

lookupResult, err := lookupAdapter.Lookup(lookupParams)
if err != nil {
return fmt.Errorf("failed to perform lookup for patron request: %w", err)
}
metadata, err := lookupResult.GetMetadata()
if err != nil {
return fmt.Errorf("failed to get metadata for patron request: %w", err)
}
return catalog.MetadataRequestUpdate(&illRequest.BibliographicInfo, metadata, lookupParams, mode)
}

func (a *PatronRequestApiHandler) PostPatronRequests(w http.ResponseWriter, r *http.Request, params proapi.PostPatronRequestsParams) {
logParams := map[string]string{"method": "PostPatronRequests"}
ctx := common.CreateExtCtxWithArgs(r.Context(), &common.LoggerArgs{Other: logParams})
Expand Down Expand Up @@ -373,29 +321,6 @@ func (a *PatronRequestApiHandler) PostPatronRequests(w http.ResponseWriter, r *h
return
}

if a.illRepo != nil && a.directoryLookupAdapter != nil {
peers, _, peerErr := a.illRepo.GetCachedPeersBySymbols(ctx, []string{symbol}, a.directoryLookupAdapter)
if peerErr != nil {
api.AddInternalError(ctx, w, peerErr)
return
}
if len(peers) == 0 {
api.AddInternalError(ctx, w, fmt.Errorf("no peer found for requester symbol %q", symbol))
return
}
if len(peers) > 1 {
ctx.Logger().Warn("multiple peers found for requester symbol, using first peer", "symbol", symbol, "peerCount", len(peers))
}
requesterPeer := peers[0]
if requesterPeer.Vendor == string(dirapi.CrossLink) {
err := a.metadataUpdate(ctx, &illRequest, requesterPeer)
if err != nil {
api.AddInternalError(ctx, w, err)
return
}
}
}

dbreq := buildDbPatronRequest(&newPr, params.XOkapiTenant, creationTime, requesterReqId, illRequest, borrowerInitialState, stateModelName)
pr, err := a.prRepo.CreatePatronRequest(ctx, pr_db.CreatePatronRequestParams(dbreq))
if err != nil {
Expand Down
174 changes: 0 additions & 174 deletions broker/patron_request/api/api-handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,14 @@ import (

"github.com/google/uuid"
"github.com/indexdata/cql-go/pgcql"
"github.com/indexdata/crosslink/broker/catalog"
"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/broker/patron_request/proapi"
prservice "github.com/indexdata/crosslink/broker/patron_request/service"
"github.com/indexdata/crosslink/broker/service"
"github.com/indexdata/crosslink/broker/tenant"
"github.com/indexdata/crosslink/broker/test/mocks"
dirapi "github.com/indexdata/crosslink/directory/api"
"github.com/indexdata/crosslink/iso18626"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype"
Expand Down Expand Up @@ -1248,176 +1244,6 @@ func (m *MockActionTaskProcessorExclusiveError) ProcessInvokeActionTask(ctx comm
}, nil
}

// --- metadataUpdate tests ---

// mockLookupCreator controls what GetAdapter returns when no globalLookupAdapter is pre-set.
type mockLookupCreator struct {
adapter catalog.LookupAdapter
err error
}

func (m *mockLookupCreator) GetAdapter(peer ill_db.Peer) (catalog.LookupAdapter, error) {
return m.adapter, m.err
}

// peerWithMetadataMode builds a Peer whose CustomData carries the given MetadataUpdateMode.
// Pass nil to leave CatalogConfig absent entirely.
func peerWithMetadataMode(mode *dirapi.MetadataUpdateMode) ill_db.Peer {
var cc *dirapi.CatalogConfig
if mode != nil {
cc = &dirapi.CatalogConfig{MetadataUpdateMode: mode}
}
return ill_db.Peer{
CustomData: dirapi.Entry{Name: "test-peer", CatalogConfig: cc},
}
}

// lookupFactoryWithAdapter creates a LookupAdapterFactory that returns the given adapter directly.
func lookupFactoryWithAdapter(adapter catalog.LookupAdapter) *service.LookupAdapterFactory {
return service.NewLookupAdapterFactory(nil, nil, "", adapter, nil)
}

func TestMetadataUpdateNoFactory(t *testing.T) {
h := PatronRequestApiHandler{}
ctx := common.CreateExtCtxWithArgs(context.Background(), &common.LoggerArgs{})
err := h.metadataUpdate(ctx, &iso18626.Request{}, ill_db.Peer{})
assert.NoError(t, err)
}

func TestMetadataUpdateAdapterInitError(t *testing.T) {
creator := &mockLookupCreator{err: errors.New("adapter init failed")}
factory := service.NewLookupAdapterFactory(nil, nil, "", nil, creator)
h := PatronRequestApiHandler{}
h.SetLookupAdapterFactory(factory)
ctx := common.CreateExtCtxWithArgs(context.Background(), &common.LoggerArgs{})
err := h.metadataUpdate(ctx, &iso18626.Request{}, ill_db.Peer{})
assert.ErrorContains(t, err, "failed to get lookup adapter")
}

func TestMetadataUpdateNilLookupAdapter(t *testing.T) {
creator := &mockLookupCreator{} // returns nil adapter, nil error
factory := service.NewLookupAdapterFactory(nil, nil, "", nil, creator)
h := PatronRequestApiHandler{}
h.SetLookupAdapterFactory(factory)
ctx := common.CreateExtCtxWithArgs(context.Background(), &common.LoggerArgs{})
err := h.metadataUpdate(ctx, &iso18626.Request{}, ill_db.Peer{})
assert.NoError(t, err)
}

func TestMetadataUpdateNoCatalogConfig(t *testing.T) {
factory := lookupFactoryWithAdapter(&catalog.MockLookupAdapter{})
h := PatronRequestApiHandler{}
h.SetLookupAdapterFactory(factory)
ctx := common.CreateExtCtxWithArgs(context.Background(), &common.LoggerArgs{})
peer := peerWithMetadataMode(nil) // CatalogConfig absent → mode stays None
err := h.metadataUpdate(ctx, &iso18626.Request{}, peer)
assert.NoError(t, err)
}

func TestMetadataUpdateModeNone(t *testing.T) {
mode := dirapi.None
factory := lookupFactoryWithAdapter(&catalog.MockLookupAdapter{})
h := PatronRequestApiHandler{}
h.SetLookupAdapterFactory(factory)
ctx := common.CreateExtCtxWithArgs(context.Background(), &common.LoggerArgs{})
err := h.metadataUpdate(ctx, &iso18626.Request{}, peerWithMetadataMode(&mode))
assert.NoError(t, err)
}

func TestMetadataUpdateMetadataLookupError(t *testing.T) {
mode := dirapi.Merge
factory := lookupFactoryWithAdapter(&catalog.MockLookupAdapter{Err: errors.New("lookup failed")})
h := PatronRequestApiHandler{}
h.SetLookupAdapterFactory(factory)
ctx := common.CreateExtCtxWithArgs(context.Background(), &common.LoggerArgs{})
err := h.metadataUpdate(ctx, &iso18626.Request{}, peerWithMetadataMode(&mode))
assert.ErrorContains(t, err, "failed to perform lookup for patron request")
}

func TestMetadataUpdateMergePopulatesEmptyFields(t *testing.T) {
mode := dirapi.Merge
meta := catalog.Metadata{Title: "Catalog Title", Author: "Jane Doe"}
factory := lookupFactoryWithAdapter(&catalog.MockLookupAdapter{Metadata: meta})
h := PatronRequestApiHandler{}
h.SetLookupAdapterFactory(factory)
ctx := common.CreateExtCtxWithArgs(context.Background(), &common.LoggerArgs{})
req := &iso18626.Request{} // empty bib info
err := h.metadataUpdate(ctx, req, peerWithMetadataMode(&mode))
assert.NoError(t, err)
assert.Equal(t, "Catalog Title", req.BibliographicInfo.Title)
assert.Equal(t, "Jane Doe", req.BibliographicInfo.Author)
}

func TestMetadataUpdateMergePreservesExistingFields(t *testing.T) {
mode := dirapi.Merge
meta := catalog.Metadata{Title: "Catalog Title"}
factory := lookupFactoryWithAdapter(&catalog.MockLookupAdapter{Metadata: meta})
h := PatronRequestApiHandler{}
h.SetLookupAdapterFactory(factory)
ctx := common.CreateExtCtxWithArgs(context.Background(), &common.LoggerArgs{})
req := &iso18626.Request{
BibliographicInfo: iso18626.BibliographicInfo{Title: "Existing Title"},
}
err := h.metadataUpdate(ctx, req, peerWithMetadataMode(&mode))
assert.NoError(t, err)
assert.Equal(t, "Existing Title", req.BibliographicInfo.Title) // not overwritten
}

func TestMetadataUpdateAutoModeWithIdentifierReplaces(t *testing.T) {
mode := dirapi.Auto
meta := catalog.Metadata{Title: "Catalog Title", Author: "Catalog Author", Isbn: "1234567890"}
factory := lookupFactoryWithAdapter(&catalog.MockLookupAdapter{Metadata: meta})
h := PatronRequestApiHandler{}
h.SetLookupAdapterFactory(factory)
ctx := common.CreateExtCtxWithArgs(context.Background(), &common.LoggerArgs{})
req := &iso18626.Request{
BibliographicInfo: iso18626.BibliographicInfo{
Title: "Old Title",
SupplierUniqueRecordId: "record-123", // non-empty → Auto resolves to Replace
BibliographicItemId: []iso18626.BibliographicItemId{
{
BibliographicItemIdentifier: "0987654321",
BibliographicItemIdentifierCode: iso18626.TypeSchemeValuePair{Text: "ISBN"},
},
},
},
}
err := h.metadataUpdate(ctx, req, peerWithMetadataMode(&mode))
assert.NoError(t, err)
assert.Equal(t, "Catalog Title", req.BibliographicInfo.Title) // replaced
assert.Equal(t, "Catalog Author", req.BibliographicInfo.Author) // replaced
assert.Equal(t, "1234567890", req.BibliographicInfo.BibliographicItemId[0].BibliographicItemIdentifier) // replaced
assert.Equal(t, "ISBN", req.BibliographicInfo.BibliographicItemId[0].BibliographicItemIdentifierCode.Text) // replaced
}

func TestMetadataUpdateAutoModeWithoutIdentifierMerges(t *testing.T) {
mode := dirapi.Auto
meta := catalog.Metadata{Title: "Catalog Title", Author: "Catalog Author", Isbn: "1234567890", Issn: "4321-4321"}
factory := lookupFactoryWithAdapter(&catalog.MockLookupAdapter{Metadata: meta})
h := PatronRequestApiHandler{}
h.SetLookupAdapterFactory(factory)
ctx := common.CreateExtCtxWithArgs(context.Background(), &common.LoggerArgs{})
req := &iso18626.Request{
BibliographicInfo: iso18626.BibliographicInfo{
Title: "Patron Title", // no SupplierUniqueRecordId → Auto resolves to Merge
BibliographicItemId: []iso18626.BibliographicItemId{
{
BibliographicItemIdentifier: "0987654321",
BibliographicItemIdentifierCode: iso18626.TypeSchemeValuePair{Text: "ISBN"},
},
},
},
}
err := h.metadataUpdate(ctx, req, peerWithMetadataMode(&mode))
assert.NoError(t, err)
assert.Equal(t, "Patron Title", req.BibliographicInfo.Title) // preserved (Merge)
assert.Equal(t, "Catalog Author", req.BibliographicInfo.Author) // filled in (was empty)
assert.Equal(t, "0987654321", req.BibliographicInfo.BibliographicItemId[0].BibliographicItemIdentifier) // kept
assert.Equal(t, "ISBN", req.BibliographicInfo.BibliographicItemId[0].BibliographicItemIdentifierCode.Text) // kept
assert.Equal(t, "4321-4321", req.BibliographicInfo.BibliographicItemId[1].BibliographicItemIdentifier) // added (not present)
assert.Equal(t, "ISSN", req.BibliographicInfo.BibliographicItemId[1].BibliographicItemIdentifierCode.Text) // added (not present)
}

// --- PutPatronRequestsId tests ---

// illRepoNoTx returns pgx.ErrNoRows for GetIllTransactionByRequesterRequestId,
Expand Down
Loading