diff --git a/broker/.gitignore b/broker/.gitignore index 1a31990f..82d51129 100644 --- a/broker/.gitignore +++ b/broker/.gitignore @@ -1,2 +1,3 @@ +/archive /broker /data diff --git a/broker/Dockerfile b/broker/Dockerfile index 81ee8f66..db5c5d4e 100644 --- a/broker/Dockerfile +++ b/broker/Dockerfile @@ -33,6 +33,11 @@ RUN --mount=type=cache,sharing=shared,target=/root/.cache/go-build \ GOOS=linux \ go build -o /broker ./cmd/broker +RUN --mount=type=cache,sharing=shared,target=/root/.cache/go-build \ + CGO_ENABLED=0 \ + GOOS=linux \ + go build -o /archive ./cmd/archive + # create runtime user RUN adduser \ --disabled-password \ @@ -52,8 +57,9 @@ COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ COPY --from=build /etc/passwd /etc/passwd COPY --from=build /etc/group /etc/group -# copy the binary +# copy binaries COPY --from=build /broker . +COPY --from=build /archive . # copy migrations COPY --from=build /app/broker/migrations /migrations diff --git a/broker/Makefile b/broker/Makefile index bf7e3e86..122ac358 100644 --- a/broker/Makefile +++ b/broker/Makefile @@ -32,7 +32,7 @@ OAPI_GEN = $(OAPI_DIR)/openapi_gen.go .PHONY: all docker generate generate-sqlc generate-commit-id check run fmt fmt-check vet clean view-coverage -all: $(BINARY) +all: $(BINARY) archive docker: generate cd .. && $(DOCKER) build -t indexdata/$(MODULE):latest -f ./$(MODULE)/Dockerfile . @@ -57,6 +57,9 @@ $(COMMIT_ID): $(BINARY): $(COMMIT_ID) $(GEN_SCHEMA_OUT) $(SQL_GEN_OUT) $(OAPI_GEN) $(GOFILES) $(GO) build -v -o $(BINARY) ./$(MAIN_PACKAGE) +archive: $(COMMIT_ID) $(GEN_SCHEMA_OUT) $(SQL_GEN_OUT) $(OAPI_GEN) $(GOFILES) + $(GO) build -v -o archive ./cmd/archive + check: generate $(GO) test -v -cover -coverpkg=./... -coverprofile=$(COVERAGE) ./... @@ -79,7 +82,9 @@ check-coverage: check $(GO) run github.com/vladopajic/go-test-coverage/v2@latest --config=./.testcoverage.yaml clean: - rm -f $(BINARY) + $(GO) clean -testcache + $(GO) clean -cache + rm -f $(BINARY) archive rm -f $(COVERAGE) rm -f $(COMMIT_ID) rm -f $(SQL_GEN_OUT) diff --git a/broker/README.md b/broker/README.md index 45a14f46..a2afee5a 100644 --- a/broker/README.md +++ b/broker/README.md @@ -80,12 +80,17 @@ Configuration is provided via environment variables: # Build -Generate sources and compile the main program with: +Generate sources and compile the main programs with: ``` make ``` +This will build the following binaries: + +* `broker` — the main program for the ILL service +* `archive` — a utility for archiving old ILL transactions + You can also run included tests with: ``` @@ -100,7 +105,7 @@ go test -v -coverpkg=./.. -cover ./cmd/broker # Run locally -You can run the program locally with: +You can run the `broker` program locally with: ``` make run diff --git a/broker/api/api-handler.go b/broker/api/api-handler.go index 97d7408a..bbbdf11a 100644 --- a/broker/api/api-handler.go +++ b/broker/api/api-handler.go @@ -13,6 +13,7 @@ import ( "time" "github.com/indexdata/crosslink/broker/adapter" + "github.com/indexdata/crosslink/broker/service" "github.com/indexdata/go-utils/utils" @@ -34,6 +35,7 @@ var LOCATED_SUPPLIERS_PATH = "/located_suppliers" var PEERS_PATH = "/peers" var ILL_TRANSACTION_QUERY = "ill_transaction_id=" var LIMIT_DEFAULT int32 = 10 +var ARCHIVE_PROCESS_STARTED = "Archive process started" type ApiHandler struct { limitDefault int32 @@ -627,6 +629,21 @@ func (a *ApiHandler) GetLocatedSuppliers(w http.ResponseWriter, r *http.Request, writeJsonResponse(w, resp) } +func (a *ApiHandler) PostArchiveIllTransactions(w http.ResponseWriter, r *http.Request, params oapi.PostArchiveIllTransactionsParams) { + logParams := map[string]string{"method": "PostArchiveIllTransactions", "ArchiveDelay": params.ArchiveDelay, "ArchiveStatus": params.ArchiveStatus} + ctx := extctx.CreateExtCtxWithArgs(context.Background(), &extctx.LoggerArgs{ + Other: logParams, + }) + err := service.Archive(ctx, a.illRepo, params.ArchiveStatus, params.ArchiveDelay, true) + if err != nil { + addBadRequestError(ctx, w, err) + return + } + writeJsonResponse(w, oapi.StatusMessage{ + Status: ARCHIVE_PROCESS_STARTED, + }) +} + func writeJsonResponse(w http.ResponseWriter, resp any) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) diff --git a/broker/call_archive.sh b/broker/call_archive.sh new file mode 100755 index 00000000..960c2cf4 --- /dev/null +++ b/broker/call_archive.sh @@ -0,0 +1,13 @@ +ACTUAL_PORT="${HTTP_PORT:8081}" +URL="http://localhost:${ACTUAL_PORT}/archive_ill_transactions?archive_delay=240h&archive_status=LoanCompleted,CopyCompleted,Unfilled" + +RESPONSE=$(curl -X POST -s -o /dev/null -w "%{http_code}" "${URL}") + +if [ "$RESPONSE" -eq 200 ]; then + echo "Success! HTTP Status Code: ${RESPONSE}" +else + echo "Error! HTTP Status Code: ${RESPONSE}" + echo "Check server logs for more details." +fi + +echo "Script finished." \ No newline at end of file diff --git a/broker/cmd/archive/main.go b/broker/cmd/archive/main.go new file mode 100644 index 00000000..6d705452 --- /dev/null +++ b/broker/cmd/archive/main.go @@ -0,0 +1,40 @@ +package main + +import ( + "context" + "flag" + + "fmt" + "os" + + "github.com/indexdata/crosslink/broker/app" + extctx "github.com/indexdata/crosslink/broker/common" + "github.com/indexdata/crosslink/broker/service" +) + +func main() { + err := run() + if err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + os.Exit(1) + } +} + +func run() error { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + var statusList string + var duration string + flag.StringVar(&statusList, "statuses", "LoanCompleted,CopyCompleted,Unfilled", "comma separated list of statuses to archive") + flag.StringVar(&duration, "duration", "5d", "archive transactions older than this duration, for example 2d") + flag.Parse() + context, err := app.Init(ctx) + if err != nil { + return err + } + logParams := map[string]string{"method": "PostArchiveIllTransactions", "ArchiveDelay": duration, "ArchiveStatus": statusList} + ectx := extctx.CreateExtCtxWithArgs(ctx, &extctx.LoggerArgs{ + Other: logParams, + }) + return service.Archive(ectx, context.IllRepo, statusList, duration, false) +} diff --git a/broker/cmd/archive/main_test.go b/broker/cmd/archive/main_test.go new file mode 100644 index 00000000..7a4102a7 --- /dev/null +++ b/broker/cmd/archive/main_test.go @@ -0,0 +1,45 @@ +package main + +import ( + "context" + "os" + "testing" + "time" + + "github.com/indexdata/crosslink/broker/app" + test "github.com/indexdata/crosslink/broker/test/utils" + _ "github.com/lib/pq" // PostgreSQL driver + "github.com/stretchr/testify/assert" + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/modules/postgres" + "github.com/testcontainers/testcontainers-go/wait" +) + +func TestMain(m *testing.M) { + ctx := context.Background() + + 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") + app.ConnectionString = connStr + app.MigrationsFolder = "file://../../migrations" + + code := m.Run() + + test.Expect(pgContainer.Terminate(ctx), "failed to stop db container") + os.Exit(code) +} + +func TestMainOK(t *testing.T) { + err := run() + assert.NoError(t, err) +} diff --git a/broker/ill_db/illrepo.go b/broker/ill_db/illrepo.go index b79d305a..33c1ec26 100644 --- a/broker/ill_db/illrepo.go +++ b/broker/ill_db/illrepo.go @@ -48,6 +48,7 @@ type IllRepo interface { SaveBranchSymbol(ctx extctx.ExtendedContext, params SaveBranchSymbolParams) (BranchSymbol, error) GetBranchSymbolsByPeerId(ctx extctx.ExtendedContext, peerId string) ([]BranchSymbol, error) DeleteBranchSymbolByPeerId(ctx extctx.ExtendedContext, peerId string) error + CallArchiveIllTransactionByDateAndStatus(ctx extctx.ExtendedContext, toDate time.Time, statuses []string) error } type PgIllRepo struct { @@ -486,6 +487,11 @@ func (r *PgIllRepo) mapSymbolsAndFilterStale(ctx extctx.ExtendedContext, symbols return symbolToPeer, symbolsToFetch } +func (r *PgIllRepo) CallArchiveIllTransactionByDateAndStatus(ctx extctx.ExtendedContext, toDate time.Time, statuses []string) error { + _, err := r.queries.CallArchiveIllTransactionByDateAndStatus(ctx, r.GetConnOrTx(), CallArchiveIllTransactionByDateAndStatusParams{toDate, statuses}) + return err +} + func getSliceFromMapInOrder(symbolToPeer map[string]Peer, symbols []string) []Peer { peers := make([]Peer, 0, len(symbolToPeer)) // first add peers that match the original symbols diff --git a/broker/migrations/010_add_archive_table.down.sql b/broker/migrations/010_add_archive_table.down.sql new file mode 100644 index 00000000..990939d2 --- /dev/null +++ b/broker/migrations/010_add_archive_table.down.sql @@ -0,0 +1,2 @@ +DROP FUNCTION archive_ill_transaction_by_date_and_status; +DROP TABLE archived_ill_transactions; diff --git a/broker/migrations/010_add_archive_table.up.sql b/broker/migrations/010_add_archive_table.up.sql new file mode 100644 index 00000000..a51b5c2c --- /dev/null +++ b/broker/migrations/010_add_archive_table.up.sql @@ -0,0 +1,89 @@ +CREATE TABLE archived_ill_transactions +( + ill_transaction JSONB NOT NULL, + events JSONB NOT NULL, + located_suppliers JSONB NOT NULL +); + +CREATE OR REPLACE FUNCTION archive_ill_transaction_by_date_and_status( + t_cut_off_date TIMESTAMPTZ, + t_status_list TEXT[] +) + RETURNS INT AS $$ +DECLARE + v_deleted_ids TEXT[]; + v_deleted_count INT := 0; + lock_id BIGINT := 8372910465; + lock_acquired BOOLEAN; +BEGIN + SELECT pg_try_advisory_lock(lock_id) INTO lock_acquired; + + IF NOT lock_acquired THEN + RAISE NOTICE 'Function archive_ill_transaction_by_date_and_status() is already running. Exiting.'; + RETURN 0; + END IF; + + SELECT array_agg(id) INTO v_deleted_ids + FROM ill_transaction + WHERE timestamp <= t_cut_off_date + AND last_supplier_status = ANY(t_status_list); + + -- If no ill transactions match the criteria, exit early + IF v_deleted_ids IS NULL OR array_length(v_deleted_ids, 1) IS NULL THEN + RAISE NOTICE 'No ILL transactions found matching date % and statuses %', t_cut_off_date, t_status_list; + RETURN 0; + END IF; + + INSERT INTO archived_ill_transactions (ill_transaction, events, located_suppliers) + SELECT + row_to_json(t)::jsonb as transaction_json, + COALESCE(pe.events, '[]'::jsonb) as events_json, + COALESCE(pls.contacts, '[]'::jsonb) as located_suppliers_json + FROM + ill_transaction AS t + LEFT JOIN LATERAL ( + SELECT + e.ill_transaction_id, + jsonb_agg(row_to_json(e)) AS events + FROM + event AS e + WHERE + e.ill_transaction_id = t.id + GROUP BY + e.ill_transaction_id + ) AS pe ON pe.ill_transaction_id = t.id + LEFT JOIN LATERAL ( + SELECT + ls.ill_transaction_id, + jsonb_agg(row_to_json(ls)) AS contacts + FROM + located_supplier AS ls + WHERE + ls.ill_transaction_id = t.id + GROUP BY + ls.ill_transaction_id + ) AS pls ON pls.ill_transaction_id = t.id + WHERE t.id = ANY(v_deleted_ids); + + DELETE FROM located_supplier + WHERE ill_transaction_id = ANY(v_deleted_ids); + GET DIAGNOSTICS v_deleted_count = ROW_COUNT; + RAISE NOTICE 'Deleted % located_supplier rows.', v_deleted_count; + + DELETE FROM event + WHERE ill_transaction_id = ANY(v_deleted_ids); + GET DIAGNOSTICS v_deleted_count = ROW_COUNT; + RAISE NOTICE 'Deleted % event rows.', v_deleted_count; + + DELETE FROM ill_transaction + WHERE id = ANY(v_deleted_ids); + GET DIAGNOSTICS v_deleted_count = ROW_COUNT; + RAISE NOTICE 'Deleted % ill_transaction rows.', v_deleted_count; + + RETURN v_deleted_count; +EXCEPTION + WHEN OTHERS THEN + PERFORM pg_advisory_unlock(lock_id); + RAISE; +END; +$$ LANGUAGE plpgsql; \ No newline at end of file diff --git a/broker/oapi/open-api.yaml b/broker/oapi/open-api.yaml index 4d3d7bfb..772b6512 100644 --- a/broker/oapi/open-api.yaml +++ b/broker/oapi/open-api.yaml @@ -46,6 +46,20 @@ components: schema: type: integer format: int32 + ArchiveDelay: + name: archive_delay + in: query + description: How old ILL Transaction to archive, for example "48h" + required: true + schema: + type: string + ArchiveStatus: + name: archive_status + in: query + description: Comma separated list of statuses to archive, for example "LoanCompleted,CopyCompleted,Unfilled" + required: true + schema: + type: string Cql: name: cql in: query @@ -92,6 +106,14 @@ components: error: type: string description: Error message + StatusMessage: + type: object + properties: + status: + type: string + description: Process status + required: + - status IllTransactions: type: object required: @@ -686,3 +708,28 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' + /archive_ill_transactions: + post: + summary: Trigger ILL Transaction archive process + parameters: + - $ref: '#/components/parameters/ArchiveDelay' + - $ref: '#/components/parameters/ArchiveStatus' + responses: + '200': + description: Process started successfully + content: + application/json: + schema: + $ref: '#/components/schemas/StatusMessage' + '400': + description: Bad Request. Invalid query parameters. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '500': + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' diff --git a/broker/service/archive.go b/broker/service/archive.go new file mode 100644 index 00000000..877b829c --- /dev/null +++ b/broker/service/archive.go @@ -0,0 +1,39 @@ +package service + +import ( + "strconv" + "strings" + "time" + + extctx "github.com/indexdata/crosslink/broker/common" + "github.com/indexdata/crosslink/broker/ill_db" +) + +func ParseDurationWithDays(archiveDelay string) (time.Duration, error) { + if !strings.HasSuffix(archiveDelay, "d") { + return time.ParseDuration(archiveDelay) + } + days, err := strconv.Atoi(strings.TrimSuffix(archiveDelay, "d")) + if err != nil { + return 0, err + } + return time.Duration(days) * 24 * time.Hour, nil +} + +func Archive(ctx extctx.ExtendedContext, illRepo ill_db.IllRepo, statusList string, archiveDelay string, background bool) error { + delayInterval, err := ParseDurationWithDays(archiveDelay) + if err != nil { + return err + } + var fromTime = time.Now().Add(-delayInterval) + if !background { + return illRepo.CallArchiveIllTransactionByDateAndStatus(ctx, fromTime, strings.Split(statusList, ",")) + } + go func() { + err := illRepo.CallArchiveIllTransactionByDateAndStatus(ctx, fromTime, strings.Split(statusList, ",")) + if err != nil { + ctx.Logger().Error("failed to archive ill transactions", "error", err) + } + }() + return nil +} diff --git a/broker/sqlc/ill_query.sql b/broker/sqlc/ill_query.sql index a548ca76..74b4bcfe 100644 --- a/broker/sqlc/ill_query.sql +++ b/broker/sqlc/ill_query.sql @@ -226,3 +226,6 @@ WHERE peer_id = $1; DELETE FROM branch_symbol WHERE peer_id = $1; + +-- name: CallArchiveIllTransactionByDateAndStatus :one +SELECT archive_ill_transaction_by_date_and_status($1, $2); diff --git a/broker/test/api/api-handler_test.go b/broker/test/api/api-handler_test.go index 3a8e4f6b..c6158c10 100644 --- a/broker/test/api/api-handler_test.go +++ b/broker/test/api/api-handler_test.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "encoding/json" + "errors" "io" "net/http" "net/http/httptest" @@ -14,6 +15,8 @@ import ( "testing" "time" + "github.com/jackc/pgx/v5" + extctx "github.com/indexdata/crosslink/broker/common" "github.com/indexdata/crosslink/broker/vcs" "github.com/indexdata/crosslink/iso18626" @@ -708,6 +711,72 @@ func TestGetLocatedSuppliersDbError(t *testing.T) { } } +func TestPostArchiveIllTransactions(t *testing.T) { + // Loan Completed + minus5days := pgtype.Timestamp{ + Time: time.Now().Add(-(24 * 5 * time.Hour)), + Valid: true, + } + ctx := extctx.CreateExtCtxWithArgs(context.Background(), nil) + illId := apptest.GetIllTransId(t, illRepo) + illTr, err := illRepo.GetIllTransactionById(ctx, illId) + assert.Nil(t, err) + illTr.LastSupplierStatus = apptest.CreatePgText(string(iso18626.TypeStatusLoanCompleted)) + illTr.Timestamp = minus5days + _, err = illRepo.SaveIllTransaction(ctx, ill_db.SaveIllTransactionParams(illTr)) + assert.Nil(t, err) + peer := apptest.CreatePeer(t, illRepo, "ISIL:LOC_SUP", "") + apptest.CreateLocatedSupplier(t, illRepo, illId, peer.ID, "ISIL:LOC_SUP", string(iso18626.TypeStatusLoaned)) + apptest.GetEventId(t, eventRepo, illId, events.EventTypeTask, events.EventStatusSuccess, events.EventNameSelectSupplier) + + // Unfilled + illId2 := apptest.GetIllTransId(t, illRepo) + illTr, err = illRepo.GetIllTransactionById(ctx, illId2) + assert.Nil(t, err) + illTr.LastSupplierStatus = apptest.CreatePgText(string(iso18626.TypeStatusUnfilled)) + illTr.Timestamp = minus5days + _, err = illRepo.SaveIllTransaction(ctx, ill_db.SaveIllTransactionParams(illTr)) + assert.Nil(t, err) + + // Loaned + illId3 := apptest.GetIllTransId(t, illRepo) + illTr, err = illRepo.GetIllTransactionById(ctx, illId3) + assert.Nil(t, err) + illTr.LastSupplierStatus = apptest.CreatePgText(string(iso18626.TypeStatusLoaned)) + illTr.Timestamp = minus5days + _, err = illRepo.SaveIllTransaction(ctx, ill_db.SaveIllTransactionParams(illTr)) + assert.Nil(t, err) + + body := httpRequest(t, "POST", "/archive_ill_transactions?archive_delay=1d&archive_status=LoanCompleted,CopyCompleted,Unfilled", nil, "", http.StatusOK) + var resp oapi.StatusMessage + err = json.Unmarshal(body, &resp) + assert.NoError(t, err) + assert.Equal(t, "Archive process started", resp.Status) + + test.WaitForPredicateToBeTrue(func() bool { + _, err = illRepo.GetIllTransactionById(ctx, illId) + return errors.Is(err, pgx.ErrNoRows) + }) + + _, err = illRepo.GetIllTransactionById(ctx, illId) + assert.True(t, errors.Is(err, pgx.ErrNoRows)) + + _, err = illRepo.GetIllTransactionById(ctx, illId2) + assert.True(t, errors.Is(err, pgx.ErrNoRows)) + + illTr, err = illRepo.GetIllTransactionById(ctx, illId3) + assert.Nil(t, err) + assert.Equal(t, illId3, illTr.ID) +} + +func TestPostArchiveIllTransactionsBadRequest(t *testing.T) { + body := httpRequest(t, "POST", "/archive_ill_transactions?archive_delay=2x&archive_status=LoanCompleted,CopyCompleted,Unfilled", nil, "", http.StatusBadRequest) + var resp oapi.Error + err := json.Unmarshal(body, &resp) + assert.NoError(t, err) + assert.Equal(t, "time: unknown unit \"x\" in duration \"2x\"", *resp.Error) +} + func getPeers(t *testing.T) oapi.Peers { body := getResponseBody(t, "/peers") var respPeers oapi.Peers diff --git a/broker/test/mocks/mock_illrepo.go b/broker/test/mocks/mock_illrepo.go index b8e9d143..ed116543 100644 --- a/broker/test/mocks/mock_illrepo.go +++ b/broker/test/mocks/mock_illrepo.go @@ -2,6 +2,7 @@ package mocks import ( "errors" + "time" "github.com/google/uuid" "github.com/indexdata/crosslink/broker/adapter" @@ -187,6 +188,9 @@ func (r *MockIllRepositorySuccess) GetBranchSymbolsByPeerId(ctx extctx.ExtendedC func (r *MockIllRepositorySuccess) DeleteBranchSymbolByPeerId(ctx extctx.ExtendedContext, peerId string) error { return nil } +func (r *MockIllRepositorySuccess) CallArchiveIllTransactionByDateAndStatus(ctx extctx.ExtendedContext, toDate time.Time, statuses []string) error { + return errors.New("DB error") +} // Implement missing method for interface compliance func (r *MockIllRepositorySuccess) GetLocatedSupplierByIllTransactionAndSymbol(ctx extctx.ExtendedContext, illTransactionId string, symbol string) (ill_db.LocatedSupplier, error) { @@ -334,3 +338,7 @@ func (r *MockIllRepositoryError) GetBranchSymbolsByPeerId(ctx extctx.ExtendedCon func (r *MockIllRepositoryError) DeleteBranchSymbolByPeerId(ctx extctx.ExtendedContext, peerId string) error { return errors.New("DB error") } + +func (r *MockIllRepositoryError) CallArchiveIllTransactionByDateAndStatus(ctx extctx.ExtendedContext, toDate time.Time, statuses []string) error { + return errors.New("DB error") +} diff --git a/broker/test/service/archive_test.go b/broker/test/service/archive_test.go new file mode 100644 index 00000000..c26d8820 --- /dev/null +++ b/broker/test/service/archive_test.go @@ -0,0 +1,70 @@ +package service + +import ( + "context" + "testing" + "time" + + extctx "github.com/indexdata/crosslink/broker/common" + "github.com/indexdata/crosslink/broker/service" + "github.com/indexdata/crosslink/broker/test/mocks" + "github.com/stretchr/testify/assert" +) + +func TestArchiveInvalidDelay(t *testing.T) { + err := service.Archive(nil, nil, "LoanCompleted,CopyCompleted,Unfilled", "2x", false) + assert.Error(t, err) + assert.Equal(t, "time: unknown unit \"x\" in duration \"2x\"", err.Error()) +} + +func TestInvalidUnit(t *testing.T) { + _, err := service.ParseDurationWithDays("2x") + assert.Error(t, err) + assert.Equal(t, "time: unknown unit \"x\" in duration \"2x\"", err.Error()) +} + +func TestValidDelayDays(t *testing.T) { + duration, err := service.ParseDurationWithDays("5d") + assert.NoError(t, err) + assert.Equal(t, 5*24*time.Hour, duration) +} + +func TestInvalidDelayDays(t *testing.T) { + _, err := service.ParseDurationWithDays("ad") + assert.Error(t, err) + assert.Equal(t, "strconv.Atoi: parsing \"a\": invalid syntax", err.Error()) +} + +func TestValidDelayHours(t *testing.T) { + duration, err := service.ParseDurationWithDays("5h") + assert.NoError(t, err) + assert.Equal(t, 5*time.Hour, duration) +} + +func TestArchiveDbError(t *testing.T) { + illrepo := &mocks.MockIllRepositoryError{} + + logParams := map[string]string{"method": "PostArchiveIllTransactions", "ArchiveDelay": "5h", "ArchiveStatus": "LoanCompleted"} + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + ectx := extctx.CreateExtCtxWithArgs(ctx, &extctx.LoggerArgs{ + Other: logParams, + }) + err := service.Archive(ectx, illrepo, "LoanCompleted", "5h", false) + assert.Error(t, err) + assert.Equal(t, "DB error", err.Error()) +} + +func TestArchiveBackground(t *testing.T) { + illrepo := &mocks.MockIllRepositoryError{} + + logParams := map[string]string{"method": "PostArchiveIllTransactions", "ArchiveDelay": "5h", "ArchiveStatus": "LoanCompleted"} + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + ectx := extctx.CreateExtCtxWithArgs(ctx, &extctx.LoggerArgs{ + Other: logParams, + }) + err := service.Archive(ectx, illrepo, "LoanCompleted", "5h", true) + assert.NoError(t, err) +}