Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
6 changes: 3 additions & 3 deletions broker/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,12 +101,12 @@ Configuration is provided via environment variables:
| `CLIENT_DELAY` | Delay duration for outgoing ISO18626 messages | `0ms` |
| `SHUTDOWN_DELAY` | Delay duration for graceful shutdown (in-flight connections) | `15s` |
| `MAX_MESSAGE_SIZE` | Max accepted ISO18626 message size | `100KB` |
| `HOLDINGS_ADAPTER` | Holdings lookup method: `mock`, `sru` or `consortia` | `mock` |
| `HOLDINGS_ADAPTER` | Holdings lookup method: `mock`, `sru` or `consortium` | `mock` |
| `HOLDINGS_SRU_URL` | Comma separated list of URLs when `HOLDINGS_ADAPTER` is `sru` | `http://localhost:8081/sru` |
| `HOLDINGS_ISXN_LOOKUP` | Whether to use ISBN/ISSN lookup for `sru` method | `false` |
| `HOLDINGS_FORMAT` | Parser for SRU holdings: `reservoir`, `marc`, `opac` or `MARC-21plus-1` | `reservoir` |
| `CONSORTIA_SYMBOL` | Designates peer for which configuration is used for consortia. At this time, it is | (empty value) |
| | used when `HOLDINGS_ADAPTER` = `consortia`. | |
| `CONSORTIUM_SYMBOL` | Designates peer for which configuration is used for consortium. At this time, it is | (empty value) |
| | used when `HOLDINGS_ADAPTER` = `consortium`. | |
| `DIRECTORY_ADAPTER` | Directory lookup method: `mock` or `api` | `mock` |
| `DIRECTORY_API_URL` | Comma separated list of URLs when `DIRECTORY_ADAPTER` is `api` | `http://localhost:8081/directory/entries` |
| `AVAILABILITY_ADAPTER` | Availability adapter: `mock` , `zoom`, `metaproxy`. | `zoom` |
Expand Down
4 changes: 2 additions & 2 deletions broker/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ var HOLDINGS_ADAPTER = utils.GetEnv("HOLDINGS_ADAPTER", "mock")
var HOLDINGS_SRU_URL = common.GetEnvWithDeprecated("HOLDINGS_SRU_URL", "SRU_URL", "http://localhost:8081/sru")
var HOLDINGS_ISXN_LOOKUP, _ = utils.GetEnvBool("HOLDINGS_ISXN_LOOKUP", false)
var HOLDINGS_FORMAT = utils.GetEnv("HOLDINGS_FORMAT", "reservoir")
var CONSORTIA_SYMBOL = utils.GetEnv("CONSORTIA_SYMBOL", "")
var CONSORTIUM_SYMBOL = utils.GetEnv("CONSORTIUM_SYMBOL", "")
Comment thread
adamdickmeiss marked this conversation as resolved.
Comment thread
adamdickmeiss marked this conversation as resolved.
var DIRECTORY_ADAPTER = utils.GetEnv("DIRECTORY_ADAPTER", "mock")
var AVAILABILITY_ADAPTER = utils.GetEnv("AVAILABILITY_ADAPTER", "zoom")
var DIRECTORY_API_URL = utils.GetEnv("DIRECTORY_API_URL", "http://localhost:8081/directory/entries")
Expand Down Expand Up @@ -181,7 +181,7 @@ func Init(ctx context.Context) (Context, error) {
prActionService := prservice.CreatePatronRequestActionService(prRepo, eventBus, &iso18626Handler, lmsCreator)
prMessageHandler.SetAutoActionRunner(prActionService)
iso18626Client := client.CreateIso18626Client(eventBus, illRepo, prMessageHandler, MAX_MESSAGE_SIZE, delay)
supplierLocator := service.CreateSupplierLocator(eventBus, illRepo, dirAdapter, holdingsAdapter, availabilityCreator, CONSORTIA_SYMBOL)
supplierLocator := service.CreateSupplierLocator(eventBus, illRepo, dirAdapter, holdingsAdapter, availabilityCreator, CONSORTIUM_SYMBOL)
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)
Expand Down
2 changes: 1 addition & 1 deletion broker/holdings/adapter_mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ type MockAvailabilityAdapter struct {
Holdings []Holding
}

func NewMockAvailabilityAdapter(config directory.AvailabilityConfig) (LookupAdapter, error) {
func NewMockAvailabilityAdapter(config directory.HoldingsConfig) (LookupAdapter, error) {
if config.Zoom != nil && config.Zoom.Options != nil {
options := *config.Zoom.Options
// For testing purposes, we can use the presence of "adapter-error" in options to trigger an error response
Expand Down
2 changes: 1 addition & 1 deletion broker/holdings/create_holdings.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func CreateHoldingsLookupShared(cfg map[string]any) (LookupAdapter, error) {
if !ok {
return nil, fmt.Errorf("missing value for %s", HoldingsAdapter)
}
if holdingsAdapterVal == "consortia" {
if holdingsAdapterVal == "consortium" {
// consortia must be determined per-peer, so we can't create a single adapter for all peers
Comment thread
adamdickmeiss marked this conversation as resolved.
Outdated
return nil, nil
}
Expand Down
4 changes: 2 additions & 2 deletions broker/holdings/creator_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ func getParser(config *directory.ParserConfig) (HoldingsParser, error) {

func (c *AvailabilityCreatorImpl) GetAdapter(peer ill_db.Peer) (LookupAdapter, error) {
entry := peer.CustomData
config := entry.AvailabilityConfig
config := entry.HoldingsConfig
if config == nil {
return nil, nil // No availability adapter for this peer
return nil, nil // No holdings adapter for this peer
}
if c.mode == AvailabilityAdapterMock {
return NewMockAvailabilityAdapter(*config)
Expand Down
16 changes: 8 additions & 8 deletions broker/holdings/creator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func TestGetAdapterBadParser(t *testing.T) {
creator := NewAvailabilityCreator(AvailabilityAdapterZoom, "")
peer := ill_db.Peer{
CustomData: directory.Entry{
AvailabilityConfig: &directory.AvailabilityConfig{
HoldingsConfig: &directory.HoldingsConfig{
Zoom: &directory.ZoomConfig{
Address: "a",
},
Expand All @@ -76,7 +76,7 @@ func TestGetAdapterOtherWithConfig(t *testing.T) {
creator := NewAvailabilityCreator("other", "")
peer := ill_db.Peer{
CustomData: directory.Entry{
AvailabilityConfig: &directory.AvailabilityConfig{
HoldingsConfig: &directory.HoldingsConfig{
Zoom: &directory.ZoomConfig{
Address: "a",
},
Expand All @@ -92,7 +92,7 @@ func TestGetAdapterMissingProperties(t *testing.T) {
creator := NewAvailabilityCreator("zoom", "")
peer := ill_db.Peer{
CustomData: directory.Entry{
AvailabilityConfig: &directory.AvailabilityConfig{},
HoldingsConfig: &directory.HoldingsConfig{},
},
}
_, err := creator.GetAdapter(peer)
Expand All @@ -103,7 +103,7 @@ func TestGetAdapterMissingProperties(t *testing.T) {
func TestGetAdapterMock(t *testing.T) {
peer := ill_db.Peer{
CustomData: directory.Entry{
AvailabilityConfig: &directory.AvailabilityConfig{
HoldingsConfig: &directory.HoldingsConfig{
Zoom: &directory.ZoomConfig{
Address: "a",
},
Expand All @@ -119,7 +119,7 @@ func TestGetAdapterMock(t *testing.T) {
func TestGetAdapterZoom(t *testing.T) {
peer := ill_db.Peer{
CustomData: directory.Entry{
AvailabilityConfig: &directory.AvailabilityConfig{
HoldingsConfig: &directory.HoldingsConfig{
Zoom: &directory.ZoomConfig{
Address: "a",
},
Expand All @@ -141,7 +141,7 @@ func TestGetAdapterZoom(t *testing.T) {
func TestGetAdapterMetaproxy(t *testing.T) {
peer := ill_db.Peer{
CustomData: directory.Entry{
AvailabilityConfig: &directory.AvailabilityConfig{
HoldingsConfig: &directory.HoldingsConfig{
Zoom: &directory.ZoomConfig{
Address: "a",
},
Expand All @@ -157,7 +157,7 @@ func TestGetAdapterMetaproxy(t *testing.T) {
func TestGetAdapterMetaproxyMissingProxy(t *testing.T) {
peer := ill_db.Peer{
CustomData: directory.Entry{
AvailabilityConfig: &directory.AvailabilityConfig{
HoldingsConfig: &directory.HoldingsConfig{
Zoom: &directory.ZoomConfig{
Address: "a",
},
Expand All @@ -173,7 +173,7 @@ func TestGetAdapterMetaproxyMissingProxy(t *testing.T) {
func TestGetAdapterSRU(t *testing.T) {
peer := ill_db.Peer{
CustomData: directory.Entry{
AvailabilityConfig: &directory.AvailabilityConfig{
HoldingsConfig: &directory.HoldingsConfig{
Sru: &directory.SruConfig{
Address: "a",
},
Expand Down
2 changes: 1 addition & 1 deletion broker/holdings/gvi_holdings_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,7 @@ func TestGviHoldings(t *testing.T) {
qtype := directory.Cql
peer := ill_db.Peer{
CustomData: directory.Entry{
AvailabilityConfig: &directory.AvailabilityConfig{
HoldingsConfig: &directory.HoldingsConfig{
Zoom: &directory.ZoomConfig{
Address: server.URL,
Options: &map[string]string{
Expand Down
18 changes: 9 additions & 9 deletions broker/service/supplierlocator.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,17 @@ type SupplierLocator struct {
dirAdapter adapter.DirectoryLookupAdapter
holdingsAdapter holdings.LookupAdapter
availabilityCreator holdings.AvailabilityCreator
consortiaSymbol string
consortiumSymbol string
}

func CreateSupplierLocator(eventBus events.EventBus, illRepo ill_db.IllRepo, dirAdapter adapter.DirectoryLookupAdapter, holdingsAdapter holdings.LookupAdapter, availabilityCreator holdings.AvailabilityCreator, consortiaSymbol string) SupplierLocator {
func CreateSupplierLocator(eventBus events.EventBus, illRepo ill_db.IllRepo, dirAdapter adapter.DirectoryLookupAdapter, holdingsAdapter holdings.LookupAdapter, availabilityCreator holdings.AvailabilityCreator, consortiumSymbol string) SupplierLocator {
return SupplierLocator{
eventBus: eventBus,
illRepo: illRepo,
dirAdapter: dirAdapter,
holdingsAdapter: holdingsAdapter,
availabilityCreator: availabilityCreator,
consortiaSymbol: consortiaSymbol,
consortiumSymbol: consortiumSymbol,
}
}

Expand Down Expand Up @@ -78,17 +78,17 @@ func createHoldingsParams(illTransactionData ill_db.IllTransactionData) holdings

// 3 cases to consider for getting the adapter:
// 1. If holdingsAdapter is set from the start (for example for testing), use it directly
// 2. If consortiaSymbol is set, lookup the peer for the consortia and use its availability adapter
// 3. Otherwise, use the availability adapter for the requesting peer
func (s *SupplierLocator) getAdapterForConsortia(ctx common.ExtendedContext, requestPeer ill_db.Peer) (holdings.LookupAdapter, error) {
// 2. If consortiumSymbol is set, lookup the peer for the consortium and use its holdings adapter
// 3. Otherwise, use the holdings adapter for the requesting peer
func (s *SupplierLocator) getConsortialAdapter(ctx common.ExtendedContext, requestPeer ill_db.Peer) (holdings.LookupAdapter, error) {
lookupAdapter := s.holdingsAdapter
if lookupAdapter != nil {
return lookupAdapter, nil
}
if s.consortiaSymbol == "" {
if s.consortiumSymbol == "" {
return s.availabilityCreator.GetAdapter(requestPeer)
}
peer, err := s.illRepo.GetPeerBySymbol(ctx, s.consortiaSymbol)
peer, err := s.illRepo.GetPeerBySymbol(ctx, s.consortiumSymbol)
if err != nil {
return nil, err
}
Expand All @@ -110,7 +110,7 @@ func (s *SupplierLocator) locateSuppliers(ctx common.ExtendedContext, event even
if err != nil {
return events.LogErrorAndReturnResult(ctx, "failed to read requester peer", err)
}
lookupAdapter, err := s.getAdapterForConsortia(ctx, requester)
lookupAdapter, err := s.getConsortialAdapter(ctx, requester)
if err != nil {
return events.LogErrorAndReturnResult(ctx, "failed to get holdings adapter for locating suppliers", err)
}
Expand Down
148 changes: 148 additions & 0 deletions broker/test/holdings/holdings_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package holdings

import (
"bytes"
"context"
"net/http"
"os"
"strconv"
"testing"
"time"

"github.com/indexdata/crosslink/broker/adapter"
"github.com/indexdata/crosslink/broker/app"
"github.com/indexdata/crosslink/broker/common"
"github.com/indexdata/crosslink/broker/events"
"github.com/indexdata/crosslink/broker/holdings"
"github.com/indexdata/crosslink/broker/ill_db"
apptest "github.com/indexdata/crosslink/broker/test/apputils"
test "github.com/indexdata/crosslink/broker/test/utils"
"github.com/indexdata/go-utils/utils"
"github.com/jackc/pgx/v5/pgtype"
"github.com/stretchr/testify/assert"
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/modules/postgres"
"github.com/testcontainers/testcontainers-go/wait"
)

var eventBus events.EventBus
var illRepo ill_db.IllRepo
var eventRepo events.EventRepo

func TestMain(m *testing.M) {
ill_db.PeerRefreshInterval = 0 //force refresh for every test
ctx := context.Background()
app.DB_PROVISION = true
pgContainer, err := postgres.Run(ctx, "postgres",
postgres.WithDatabase("crosslink"),
postgres.WithUsername("crosslink"),
postgres.WithPassword("crosslink"),
testcontainers.WithWaitStrategy(
wait.ForLog("database system is ready to accept connections").
WithOccurrence(2).WithStartupTimeout(5*time.Second)),
)
test.Expect(err, "failed to start db container")

connStr, err := pgContainer.ConnectionString(ctx, "sslmode=disable")
test.Expect(err, "failed to get conn string")

mockPort := utils.Must(test.GetFreePort())
app.HTTP_PORT = utils.Must(test.GetFreePort())
test.Expect(os.Setenv("PEER_URL", "http://localhost:"+strconv.Itoa(app.HTTP_PORT)+"/iso18626"), "failed to set peer URL")
app.AVAILABILITY_ADAPTER = holdings.AvailabilityAdapterZoom
app.DIRECTORY_ADAPTER = "api"
app.DIRECTORY_API_URL = "http://localhost:" + strconv.Itoa(mockPort) + "/directory/entries"
app.HOLDINGS_ADAPTER = "consortium"

apptest.StartMockApp(mockPort)
app.ConnectionString = connStr
app.MigrationsFolder = "file://../../migrations"
adapter.MOCK_PEER_URL = "http://localhost:" + strconv.Itoa(mockPort) + "/iso18626"

ctx, cancel := context.WithCancel(context.Background())
defer cancel()
eventBus, illRepo, eventRepo, _ = apptest.StartApp(ctx)
test.WaitForServiceUp(app.HTTP_PORT)

code := m.Run()

test.Expect(pgContainer.Terminate(ctx), "failed to stop db container")
os.Exit(code)
}

func getPgText(value string) pgtype.Text {
return pgtype.Text{
String: value,
Valid: true,
}
}

func TestRequestRequesterNotFound(t *testing.T) {
appCtx := common.CreateExtCtxWithArgs(context.Background(), nil)
reqId := "eacf8b17-e89a-4d70-8576-e49077f8c4e1"
data, _ := os.ReadFile("request-1.xml")
req, _ := http.NewRequest("POST", adapter.MOCK_PEER_URL, bytes.NewReader(data))
Comment thread
adamdickmeiss marked this conversation as resolved.
Outdated
req.Header.Add("Content-Type", "application/xml")
client := &http.Client{}
res, err := client.Do(req)
// oddly here there is no transaction and no returned error
assert.NoError(t, err, "failed to send request to mock")
assert.Equal(t, http.StatusOK, res.StatusCode, "handler returned wrong status code")
_, err = illRepo.GetIllTransactionByRequesterRequestId(appCtx, getPgText(reqId))
assert.Error(t, err, "does not expect to find transaction")
}

func TestRequestRequestNoHoldingsConfig(t *testing.T) {
appCtx := common.CreateExtCtxWithArgs(context.Background(), nil)
reqId := "479931e1-3e94-467c-a04e-272ac8fcc154"
data, _ := os.ReadFile("request-2.xml")
req, _ := http.NewRequest("POST", adapter.MOCK_PEER_URL, bytes.NewReader(data))
req.Header.Add("Content-Type", "application/xml")
client := &http.Client{}
res, err := client.Do(req)
assert.NoError(t, err, "failed to send request to mock")
assert.Equal(t, http.StatusOK, res.StatusCode, "handler returned wrong status code")

var illTrans ill_db.IllTransaction
test.WaitForPredicateToBeTrue(func() bool {
illTrans, err = illRepo.GetIllTransactionByRequesterRequestId(appCtx, getPgText(reqId))
if err != nil {
t.Errorf("failed to find ill transaction by requester request id %v", reqId)
}
return illTrans.LastSupplierStatus.String == "" &&
illTrans.LastRequesterAction.String == "Request"
})
assert.Equal(t, "", illTrans.LastSupplierStatus.String)
assert.Equal(t, "Request", illTrans.LastRequesterAction.String)
exp := "NOTICE, request-received = SUCCESS\n" +
"TASK, locate-suppliers = ERROR, error=no holdings adapter available for locating suppliers\n" +
"TASK, message-requester = ERROR, error=failed to send ISO18626 message\n"
apptest.EventsCompareString(appCtx, eventRepo, t, illTrans.ID, exp)
}

func TestRequestRequestWithHoldingsConfig(t *testing.T) {
appCtx := common.CreateExtCtxWithArgs(context.Background(), nil)
reqId := "d2ce73de-2545-4ef3-be16-bff17932579a"
data, _ := os.ReadFile("request-3.xml")
req, _ := http.NewRequest("POST", adapter.MOCK_PEER_URL, bytes.NewReader(data))
req.Header.Add("Content-Type", "application/xml")
client := &http.Client{}
res, err := client.Do(req)
assert.NoError(t, err, "failed to send request to mock")
assert.Equal(t, http.StatusOK, res.StatusCode, "handler returned wrong status code")
var illTrans ill_db.IllTransaction
test.WaitForPredicateToBeTrue(func() bool {
illTrans, err = illRepo.GetIllTransactionByRequesterRequestId(appCtx, getPgText(reqId))
if err != nil {
t.Errorf("failed to find ill transaction by requester request id %v", reqId)
}
return illTrans.LastSupplierStatus.String == "" &&
illTrans.LastRequesterAction.String == "Request"
})
assert.Equal(t, "", illTrans.LastSupplierStatus.String)
assert.Equal(t, "Request", illTrans.LastRequesterAction.String)
exp := "NOTICE, request-received = SUCCESS\n" +
"TASK, locate-suppliers = ERROR, error=failed to locate holdings for query 'rec.id = \"LOANED\"'\n" +
"TASK, message-requester = ERROR, error=failed to send ISO18626 message\n"
apptest.EventsCompareString(appCtx, eventRepo, t, illTrans.ID, exp)
}
50 changes: 50 additions & 0 deletions broker/test/holdings/request-1.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<ISO18626Message
xmlns="http://illtransactions.org/2013/iso18626"
xmlns:ill="http://illtransactions.org/2013/iso18626"
ill:version="1.2"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://illtransactions.org/2013/iso18626 http://illtransactions.org/schemas/ISO-18626-v1_2.xsd">
<request>
<header>
<supplyingAgencyId>
<agencyIdType>ISIL</agencyIdType>
<agencyIdValue>BROKER</agencyIdValue>
</supplyingAgencyId>
<requestingAgencyId>
<agencyIdType>ISIL</agencyIdType>
<agencyIdValue>REQ</agencyIdValue>
</requestingAgencyId>
<multipleItemRequestId></multipleItemRequestId>
<timestamp>2024-11-28T10:25:11.136Z</timestamp>
<requestingAgencyRequestId>eacf8b17-e89a-4d70-8576-e49077f8c4e1</requestingAgencyRequestId>
</header>
<bibliographicInfo>
<supplierUniqueRecordId>LOANED</supplierUniqueRecordId>
<title>Lord of the Rings</title>
<author>JRR Tolkien</author>
<bibliographicItemId>
<bibliographicItemIdentifier>1983</bibliographicItemIdentifier>
<bibliographicItemIdentifierCode>ISBN</bibliographicItemIdentifierCode>
</bibliographicItemId>
</bibliographicInfo>
<publicationInfo>
<publicationDate>1954</publicationDate>
</publicationInfo>
<serviceInfo>
<requestType>New</requestType>
<requestSubType>PatronRequest</requestSubType>
<serviceType>Loan</serviceType>
</serviceInfo>
<supplierInfo></supplierInfo>
<requestedDeliveryInfo>
<address>
<physicalAddress>
<line1>The Prancing Pony Inn, Bree</line1>
</physicalAddress>
</address>
</requestedDeliveryInfo>
<patronInfo>
<patronId>123</patronId>
</patronInfo>
</request>
</ISO18626Message>
Loading
Loading