diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d6dcca1..270ccf5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,6 +4,15 @@ on: branches: [master] pull_request: branches: [master] + workflow_dispatch: + inputs: + enable_destructive_solrcloud_tests: + description: "Run opt-in SPLITSHARD and MIGRATE checks on the disposable two-node cluster" + required: false + default: false + type: boolean +jobs: + test: jobs: test: strategy: @@ -45,7 +54,7 @@ jobs: --volume "$RUNNER_TEMP/solr-backups:/var/solr/backups" \ --publish 8983:8983 \ --env SOLR_JAVA_MEM="-Xms512m -Xmx512m" \ - --env SOLR_OPTS="-Dsolr.allowPaths=/var/solr/backups" \ + --env SOLR_OPTS="-Dsolr.allowPaths=/var/solr/backups -Dsolr.disable.shardsWhitelist=true -Dsolr.disable.allowUrls=true -Dsolr.security.allow.urls.enabled=false" \ "solr:${{ matrix.solr-version }}" \ solr-precreate integration - name: Wait for Solr and integration core @@ -67,6 +76,7 @@ jobs: env: SOLR_BASE_URL: http://localhost:8983/solr SOLR_CORE: integration + SOLR_VERSION: "${{ matrix.solr-version }}" SOLR_BACKUP_LOCATION: /var/solr/backups - name: Print Solr logs if: always() @@ -124,7 +134,7 @@ jobs: --volume "$RUNNER_TEMP/solr-backups:/var/solr/backups" \ --publish 8983:8983 \ --env SOLR_JAVA_MEM="-Xms512m -Xmx512m" \ - --env SOLR_OPTS="-Dsolr.allowPaths=/var/solr/backups" \ + --env SOLR_OPTS="-Dsolr.allowPaths=/var/solr/backups -Dsolr.disable.shardsWhitelist=true -Dsolr.disable.allowUrls=true -Dsolr.security.allow.urls.enabled=false" \ --entrypoint /bin/bash \ "solr:${{ matrix.solr-version }}" \ -lc 'mkdir -p /var/solr/data && cp /tmp/security.json /var/solr/data/security.json && chmod 666 /var/solr/data/security.json && exec docker-entrypoint.sh solr-precreate integration' @@ -149,6 +159,7 @@ jobs: env: SOLR_BASE_URL: http://localhost:8983/solr SOLR_CORE: integration + SOLR_VERSION: "${{ matrix.solr-version }}" SOLR_SECURITY: "true" SOLR_USERNAME: integration-admin SOLR_PASSWORD: integration-password @@ -158,6 +169,8 @@ jobs: run: docker logs solr-secure || true cloud-integration: + env: + SOLR_DESTRUCTIVE_OPT_IN: "${{ github.event_name == 'workflow_dispatch' && inputs.enable_destructive_solrcloud_tests == true && 'true' || 'false' }}" strategy: fail-fast: false matrix: diff --git a/docs/administration.md b/docs/administration.md index 4647f56..a97f5f3 100644 --- a/docs/administration.md +++ b/docs/administration.md @@ -161,3 +161,18 @@ The alias and cluster-status integration checks exercise alias read-back, collec The V1 parameter spellings are intentional: this client calls `/solr/admin/collections`, not the Solr 10 V2 `/api/collections` resources. The V2 API maps `split.key` to `splitKey` and `target.collection` to `target`; those V2 names must not be sent through this V1 client. Reference guides: [Solr 8.1 Collections API](https://archive.apache.org/dist/lucene/solr/ref-guide/apache-solr-ref-guide-8.1.pdf), [Solr 9.2 Collection Management](https://solr.apache.org/guide/solr/9_2/deployment-guide/collection-management.html), [Solr 9.10 Shard Management](https://solr.apache.org/guide/solr/9_10/deployment-guide/shard-management.html), and [Solr 10.0 Collections API](https://solr.apache.org/guide/solr/10_0/configuration-guide/collections-api.html). + +## Opt-in SPLITSHARD and MIGRATE verification + +These operations are intentionally excluded from normal push/pull-request CI. The opt-in suite uses only collections created by the fixture with unique `integration_*` names. It refuses to run unless all of the following are true: + +- `SOLR_CLOUD=true` and `SOLR_MULTINODE=true`. +- `SOLR_DESTRUCTIVE_OPT_IN=true` is explicitly set. +- `SOLR_BASE_URL` resolves to localhost / loopback; remote Solr endpoints are skipped. +- The disposable cluster reports at least two live nodes; the split test additionally confirms its new collection starts with one shard and the migration test confirms the target collection is empty. + +Run it from the GitHub Actions **CI** workflow using **Run workflow** and enable `enable_destructive_solrcloud_tests`. This provisions the workflow's ephemeral two-node SolrCloud cluster and runs the opt-in tests for the pinned Solr version matrix. Leaving the input disabled keeps both tests skipped, including on ordinary pull requests. + +The tests use asynchronous request IDs and poll `REQUESTSTATUS` for up to 180 seconds, then remove completed status records. They verify active child-shard state and post-split document visibility, and verify that every routed document is searchable in the migration target. They do not claim to test concurrent writes, rollback of a partially completed operation, or recovery after a cluster/node failure. + +If a request times out or fails, do not rerun against a reused cluster. Preserve the CI job logs, inspect `CLUSTERSTATUS` and `REQUESTSTATUS`, and discard the ephemeral cluster. Fixture cleanup deletes only the collections it created; if Solr still has an operation in flight and refuses deletion, destroy the ephemeral cluster rather than attempting cleanup against a shared server. The suite is an opt-in verification harness, not permission to point these destructive actions at a user-owned cluster. A workflow that is skipped or has not been manually dispatched is not runtime evidence; record successful Solr versions individually before updating compatibility status. diff --git a/docs/api-coverage.md b/docs/api-coverage.md index 31ff5fb..e9c8a1a 100644 --- a/docs/api-coverage.md +++ b/docs/api-coverage.md @@ -26,7 +26,7 @@ A generic request method does not mean that every endpoint has a typed wrapper, | Security | `security(area, command)` | Security-enabled standalone matrix covers anonymous 401 responses for both authentication and authorization, authenticated reads, authentication property mutation/read-back, and malformed JSON rejection on Solr 8.11.4/9.11.0/10.1.0. Unit tests verify Basic/Bearer credentials and Authorization headers are not exposed through client HTTP errors. See Issues #86–#88. | | System | `systemInfo()`, `systemProperties()`, `systemThreads()`, `systemLogging()` | System info, non-empty JVM/process properties, a non-empty thread dump, and logger metadata are exercised on pinned Solr 8.11.4/9.11.0/10.1.0. Optional fields remain version-dependent; response models preserve unknown fields. See Issues #89 and #91. |\n| Metrics | `metrics()` with typed group/registry/prefix/regex filters; exported `SolrMetricsJsonResponse`, `SolrMetricsRegistry`, and open `SolrMetricValue`; JSON/Prometheus response union | JVM response-format coverage remains distinct (observed JSON on Solr 8/9 and Prometheus exposition on 10.1). A `node` group integration check runs on all pinned versions, asserting a non-empty JSON registry on Solr 8/9 and Prometheus samples on 10.1; registry, prefix, and regex filters are asserted against JSON responses on Solr 8/9 (Solr 10.1 supports Prometheus/OpenMetrics, not the JSON writer). Consumer fixtures verify the extensible registry/value envelope; plugin-specific metrics remain open and unenumerated. See Issues #90, #91, and #236. | | Backup and restore | `replication("backup" | "restore" | "restorestatus" | "deletebackup")`, `collections("BACKUP" | "RESTORE" | "LISTBACKUP" | "DELETEBACKUP" | "REQUESTSTATUS" | "DELETESTATUS")` | Typed core and SolrCloud backup/restore parameters and response models, including async status polling. Full core restore and SolrCloud collection backup/restore cycles are integration-tested on Solr 8.11.4/9.11.0/10.1.0 using local filesystem repositories and shared SolrCloud storage. See [backup and restore docs](backup-restore.md). | -| Replication | `replication(command, params)`, `replicationFile(params)`; exported command-specific parameter and response models | V1 ReplicationHandler commands for index version, details, file list, leader/follower control (Solr 10 fetchindex/abortfetch; Solr 8/9 snappull/abort), core backup/restore/status/delete, and raw file-content streaming. Integration coverage includes core backup/restore cycles on Solr 8.11.4/9.11.0/10.1.0. An end-to-end leader/follower fetch cycle is not yet certified. See Issue #95 and [replication docs](replication.md). | +| Replication | `replication(command, params)`, `replicationFile(params)`; exported command-specific parameter and response models | V1 ReplicationHandler commands for index version, details, file list, leader/follower control (Solr 10 fetchindex/abortfetch; Solr 8/9 snappull/abort), core backup/restore/status/delete, and raw file-content streaming. Integration coverage includes core backup/restore cycles and a disposable two-core leader/follower fetch/read-back cycle on Solr 8.11.4/9.11.0/10.1.0. The test uses `snappull` on Solr 8/9 and `fetchindex` on Solr 10; abort and polling lifecycle behavior remains unit-only. See Issue #95 and [replication docs](replication.md). | | Streaming Expressions | `stream()` | Sends `expr` with `wt=json` and parses the complete JSON response through the shared transport. It is not an incremental tuple-stream parser: no chunk-boundary protocol or memory-bound/backpressure guarantee is claimed. SolrCloud search is integration-tested on pinned Solr 8.11.4/9.11.0/10.1.0; unit tests cover JSON parsing, caller cancellation during body consumption, and cleanup after body-stream errors. See [Streaming Expressions](streaming.md) and Issue #93. | | Export | `export()`, `exportStream()`; exported `SolrExportParams`, `SolrExportSort`, and `SolrExportFieldType` | Export requests require typed `fl` and `sort` parameters, support repeated filter queries, structured or raw sort expressions, batch sizing, JSON/JavaBin writers, and an explicit custom-parameter escape hatch. The raw Response preserves backpressure-aware streaming; `exportStream()` exposes its byte body directly. Sort fields must be single-valued docValues fields (int, long, float, double, or string); field lists additionally support date and boolean and may be multi-valued. Solr 10.1 `includeStoredFields` support is exposed but requires version-specific verification. Existing raw Export response tests pass on pinned Solr 8.11.4/9.11.0/10.1.0; typed parameter serialization and streamed chunk consumption have unit coverage. See [Export docs](export.md) and Issues #92–#94. | | Advanced / Raw | `requestRaw(path, options)` with typed method/query/header/body/control options and a raw Fetch `Response` | Relative paths and same-origin absolute URLs use the shared transport. Cross-origin URLs, embedded credentials, and URL fragments are rejected before Fetch, preventing configured credentials from being sent to unrelated hosts. Generic request()/requestRaw() transport behavior has unit coverage only; arbitrary custom endpoint behavior remains unverified until endpoint-specific integration tests exist. See [raw requests](raw-requests.md) and Issue #98. | diff --git a/docs/api-inventory.json b/docs/api-inventory.json index 6ef9b53..55abdf1 100644 --- a/docs/api-inventory.json +++ b/docs/api-inventory.json @@ -84,12 +84,13 @@ "testFiles": [ "test/admin.test.ts", "test/integration/solr-collections.test.ts", + "test/integration/solrcloud-split-migrate-optin.test.ts", "test/collections-types.ts", "test/backup-restore.test.ts", "test/backup-restore-api-types.ts", "test/integration/solrcloud-backup-restore.test.ts" ], - "notes": "SolrCloud CREATE/LIST/DELETE, alias lifecycle, implicit-router shard lifecycle, named ADDREPLICA/DELETEREPLICA lifecycle, replica property add/delete and BALANCESHARDUNIQUE responses, plus multi-node MOVEREPLICA and REBALANCELEADERS leader reassignment are tested on pinned Solr 8.11.4, 9.11.0, and 10.1.0 images (CI #177, #192, #209, and PR #142 CI #224). MOVEREPLICA verifies the named replica becomes active on a different live node. REBALANCELEADERS assigns preferredLeader to an active non-leader and verifies it becomes shard leader. Snapshot, backup, and cluster-state response models remain unverified. Additional operation evidence: Typed SolrCloud collection backup/restore actions and asynchronous REQUESTSTATUS/DELETESTATUS tracking. Full backup, list, restore, query read-back, and delete cycle is integration-tested on Solr 8.11.4, 9.11.0, and 10.1.0 with a shared local filesystem volume mounted on both SolrCloud nodes. S3 and other external repositories are not certified. LISTALIASES defaults to SolrCollectionAliasesResponse; CREATEALIAS/DELETEALIAS default to SolrCollectionAliasResponse; CLUSTERSTATUS defaults to SolrClusterStatusResponse with extensible collection/shard/replica models. Filtered cluster status and unobserved version-specific fields remain optional/open. CREATESHARD/DELETESHARD now default to SolrCollectionShardOperationResponse and remain integration-tested on all pinned versions. SPLITSHARD has a typed V1 split.key contract (shard or routing-key mode), but remains runtime-unverified because it is long-running, hash-router-specific, and has disk/routing implications. MIGRATE is added with V1 collection/split.key/target.collection/forward.timeout/async parameters and the same response envelope; it remains runtime-unverified because it is destructive and requires a controlled compositeId source/target pair. Do not treat these typed requests as integration certification. RELOAD now has exact action-specific serialization and isolated collection integration coverage on pinned Solr 8.11.4/9.11.0/10.1.0. MODIFYCOLLECTION, ADDROLE, and REMOVEROLE now have dedicated serialization and consumer type fixtures; MODIFYCOLLECTION remains runtime-unverified because it mutates collection policy, while role actions remain runtime-unverified because they mutate cluster-wide node role assignments. Named response contracts now cover every action literal: LIST uses SolrCollectionListResponse; CREATE/DELETE/RELOAD/MODIFYCOLLECTION use SolrCollectionAdminResponse; replica lifecycle, movement, balancing, and property actions use SolrCollectionReplicaResponse; ADDROLE/REMOVEROLE use SolrCollectionRoleResponse. CollectionAdminResponse selects the default contract from the action literal while explicit caller generics remain supported. These are extensible response envelopes, not a claim that all action-specific payload fields or all actions are runtime-certified." + "notes": "SolrCloud CREATE/LIST/DELETE, alias lifecycle, implicit-router shard lifecycle, named ADDREPLICA/DELETEREPLICA lifecycle, replica property add/delete and BALANCESHARDUNIQUE responses, plus multi-node MOVEREPLICA and REBALANCELEADERS leader reassignment are tested on pinned Solr 8.11.4, 9.11.0, and 10.1.0 images (CI #177, #192, #209, and PR #142 CI #224). MOVEREPLICA verifies the named replica becomes active on a different live node. REBALANCELEADERS assigns preferredLeader to an active non-leader and verifies it becomes shard leader. Snapshot, backup, and cluster-state response models remain unverified. Additional operation evidence: Typed SolrCloud collection backup/restore actions and asynchronous REQUESTSTATUS/DELETESTATUS tracking. Full backup, list, restore, query read-back, and delete cycle is integration-tested on Solr 8.11.4, 9.11.0, and 10.1.0 with a shared local filesystem volume mounted on both SolrCloud nodes. S3 and other external repositories are not certified. LISTALIASES defaults to SolrCollectionAliasesResponse; CREATEALIAS/DELETEALIAS default to SolrCollectionAliasResponse; CLUSTERSTATUS defaults to SolrClusterStatusResponse with extensible collection/shard/replica models. Filtered cluster status and unobserved version-specific fields remain optional/open. CREATESHARD/DELETESHARD now default to SolrCollectionShardOperationResponse and remain integration-tested on all pinned versions. SPLITSHARD has a typed V1 split.key contract (shard or routing-key mode). MIGRATE uses V1 collection/split.key/target.collection/forward.timeout/async parameters and the same response envelope. Both now have opt-in asynchronous integration checks that require SOLR_DESTRUCTIVE_OPT_IN=true, a loopback endpoint, SOLR_CLOUD=true, SOLR_MULTINODE=true, and disposable collections; the manual workflow provisions its own two-node cluster. The tests verify child-shard state and document read-back after split, and target document read-back after migration. The opt-in workflow has not yet been executed, so both operations remain runtime-unverified. Abort, partial-failure recovery, and concurrent-write behavior are not certified. RELOAD now has exact action-specific serialization and isolated collection integration coverage on pinned Solr 8.11.4/9.11.0/10.1.0. MODIFYCOLLECTION, ADDROLE, and REMOVEROLE now have dedicated serialization and consumer type fixtures; MODIFYCOLLECTION remains runtime-unverified because it mutates collection policy, while role actions remain runtime-unverified because they mutate cluster-wide node role assignments. Named response contracts now cover every action literal: LIST uses SolrCollectionListResponse; CREATE/DELETE/RELOAD/MODIFYCOLLECTION use SolrCollectionAdminResponse; replica lifecycle, movement, balancing, and property actions use SolrCollectionReplicaResponse; ADDROLE/REMOVEROLE use SolrCollectionRoleResponse. CollectionAdminResponse selects the default contract from the action literal while explicit caller generics remain supported. These are extensible response envelopes, not a claim that all action-specific payload fields or all actions are runtime-certified." }, { "id": "update.commit", @@ -396,11 +397,12 @@ "test/replication.test.ts", "test/replication-api-types.ts", "test/integration/solr-replication.test.ts", + "test/integration/solr-replication-lifecycle.test.ts", "test/backup-restore.test.ts", "test/backup-restore-api-types.ts", "test/integration/solr-core-backup-restore.test.ts" ], - "notes": "Typed V1 ReplicationHandler commands for enablereplication, disablereplication, indexversion, fetchindex, abortfetch, snappull, abort, enablepoll, disablepoll, details, filelist, backup, restore, restorestatus, and deletebackup; replicationFile() returns raw filecontent streams. Live integration coverage on Solr 8.11.4, 9.11.0, and 10.1.0 covers indexversion, details, filelist, and filecontent. enablereplication/disablereplication are integration-tested on a configured leader; fetchindex/abortfetch (Solr 10), snappull/abort (Solr 8/9), enablepoll, and disablepoll have unit serialization coverage, but a multi-core leader/follower fetch cycle is not yet certified. Core-level backup, restore, restorestatus, and deletebackup are integration-tested on all three pinned versions. Filelist uses the generation returned by newer Solr indexversion responses and falls back to the legacy indexversion parameter when that field is absent. Additional operation evidence: Core-level ReplicationHandler backup, restore, restorestatus, and deletebackup are typed. Backup defaults to the core dataDir; restore status is polled with restorestatus. Full backup/restore/read-back/delete cycle is integration-tested on Solr 8.11.4, 9.11.0, and 10.1.0. Custom filesystem locations must be permitted by server allowPaths." + "notes": "Typed V1 ReplicationHandler commands for enablereplication, disablereplication, indexversion, fetchindex, abortfetch, snappull, abort, enablepoll, disablepoll, details, filelist, backup, restore, restorestatus, and deletebackup; replicationFile() returns raw filecontent streams. Live integration coverage on Solr 8.11.4, 9.11.0, and 10.1.0 covers indexversion, details, filelist, filecontent, leader enable/disable, and a disposable two-core leader/follower fetch with follower query read-back. The integration job supplies SOLR_VERSION so the test uses fetchindex on Solr 10 and snappull on Solr 8/9. abortfetch/abort, enablepoll, and disablepoll retain unit serialization coverage only; an in-progress abort lifecycle is not certified. Core-level backup, restore, restorestatus, and deletebackup are integration-tested on all three pinned versions. Filelist uses the generation returned by newer Solr indexversion responses and falls back to the legacy indexversion parameter when that field is absent. Additional operation evidence: Core-level ReplicationHandler backup, restore, restorestatus, and deletebackup are typed. Backup defaults to the core dataDir; restore status is polled with restorestatus. Full backup/restore/read-back/delete cycle is integration-tested on Solr 8.11.4, 9.11.0, and 10.1.0. Custom filesystem locations must be permitted by server allowPaths." }, { "id": "replication.file", diff --git a/docs/collections-api-coverage.md b/docs/collections-api-coverage.md index 60e3b5c..fa27607 100644 --- a/docs/collections-api-coverage.md +++ b/docs/collections-api-coverage.md @@ -23,8 +23,8 @@ This matrix is deliberately action-specific. The public client infers a named re | `ADDREPLICAPROP` | `AddReplicaPropertyParams` | `SolrCollectionReplicaResponse` | Serialization and compile fixtures | Adds replica property; all three pinned versions | | `DELETEREPLICAPROP` | `DeleteReplicaPropertyParams` | `SolrCollectionReplicaResponse` | Serialization and compile fixtures | Removes replica property; all three pinned versions | | `CREATESHARD` | `CreateShardParams` | `SolrCollectionShardOperationResponse` | Exact serialization and consumer type fixture | Creates a shard on a disposable implicit collection and reads it through CLUSTERSTATUS; all three pinned versions | -| `DELETESHARD` | `DeleteShardParams` | `SolrCollectionShardOperationResponse` | Exact serialization and consumer type fixture | Deletes only the shard created by the test and verifies absence; all three pinned versions |\n| `MIGRATE` | `MigrateCollectionParams` (`collection`, V1 `split.key`, V1 `target.collection`, optional `forward.timeout`, `async`) | `SolrCollectionShardOperationResponse` | Exact serialization and consumer type fixture | Not integration-tested: data migration is destructive/long-running and requires a controlled compositeId source/target pair | -| `SPLITSHARD` | `SplitShardParams` (`shard` or V1 `split.key`; `ranges`, `numSubShards`, `splitMethod`, `async`) | `SolrCollectionShardOperationResponse` | Exact serialization and consumer type fixture | Not integration-tested: long-running, hash-router-only operation with disk and routing implications; unsuitable for shared CI collections | +| `DELETESHARD` | `DeleteShardParams` | `SolrCollectionShardOperationResponse` | Exact serialization and consumer type fixture | Deletes only the shard created by the test and verifies absence; all three pinned versions |\n| `MIGRATE` | `MigrateCollectionParams` (`collection`, V1 `split.key`, V1 `target.collection`, optional `forward.timeout`, `async`) | `SolrCollectionShardOperationResponse` | Exact serialization and consumer type fixture | Opt-in only: disposable compositeId source/empty target; async completion plus target document read-back. Not yet executed against pinned versions; remains runtime-unverified | +| `SPLITSHARD` | `SplitShardParams` (`shard` or V1 `split.key`; `ranges`, `numSubShards`, `splitMethod`, `async`) | `SolrCollectionShardOperationResponse` | Exact serialization and consumer type fixture | Opt-in only: disposable one-shard compositeId collection; async completion, active child-shard state, and document read-back. Not yet executed against pinned versions; remains runtime-unverified | | `ADDROLE` | `CollectionRoleParams` | `SolrCollectionRoleResponse` | Exact serialization and consumer type fixture | Not integration-tested: changes cluster-wide node role assignment; requires a dedicated isolated cluster policy fixture | | `REMOVEROLE` | `CollectionRoleParams` | `SolrCollectionRoleResponse` | Exact serialization and consumer type fixture | Not integration-tested: changes cluster-wide node role assignment; requires a dedicated isolated cluster policy fixture | | `BACKUP` | `CollectionBackupParams` | `SolrCollectionBackupResponse` | Named response type and compile-time API declarations | Async collection backup with shared storage; all three pinned versions | @@ -44,13 +44,14 @@ The public `CollectionAdminResponse` conditional type maps each action litera - Compile-time consumer examples: `test/admin-api-types.ts`, `test/collections-types.ts`. - Unit serialization: `test/admin.test.ts`. - Collection, alias, shard, replica, and cluster-status runtime checks: `test/integration/solr-collections.test.ts`. +- Destructive, opt-in SPLITSHARD and MIGRATE verification: `test/integration/solrcloud-split-migrate-optin.test.ts` (manual workflow dispatch only). - Backup/restore and async status runtime checks: `test/integration/solrcloud-backup-restore.test.ts`. - Pinned server matrix: `test/integration/fixtures.ts` and the SolrCloud jobs in the CI workflow. ## Gaps and scope boundaries 1. **Cluster status response typing:** `SolrClusterStatusResponse` now models the stable live-node, alias, collection, shard, and replica envelope. Unknown and version-specific fields remain open; it is not a closed schema for every SolrCloud response variation. -2. **Shard split and migration:** `SPLITSHARD` and `MIGRATE` have typed V1 parameter and response contracts plus serialization/type fixtures, but no shared-CI runtime test. Both remain explicitly unverified because they can be long-running and affect routing/data placement. `MIGRATE` additionally requires a compositeId router and a quiescent target collection. +2. **Shard split and migration:** `SPLITSHARD` and `MIGRATE` have typed V1 parameter and response contracts plus serialization/type fixtures, but no normal-CI runtime test. A manually dispatched, loopback-only two-node workflow now contains opt-in tests that poll asynchronous completion and verify cluster/data state; until that workflow passes for a pinned version, both actions remain explicitly runtime-unverified because they can be long-running and affect routing/data placement. `MIGRATE` additionally requires a compositeId router and a quiescent target collection. 3. **Collection role operations:** `ADDROLE` and `REMOVEROLE` share `CollectionRoleParams` and now have exact serialization and consumer type fixtures. They remain runtime-unverified because they mutate cluster-wide node role assignments. 4. **Collection `RELOAD` and `MODIFYCOLLECTION`:** `RELOAD` now has an isolated integration assertion on the pinned matrix. `MODIFYCOLLECTION` has serialization/type coverage but remains runtime-unverified because it mutates collection policy and behavior varies with SolrCloud configuration. 5. **Response contract boundaries:** `SolrCollectionAdminResponse`, `SolrCollectionReplicaResponse`, and `SolrCollectionRoleResponse` are intentionally extensible envelopes; they do not claim undocumented action-specific payload fields. `SolrCollectionListResponse` types the stable `collections` list. These contracts describe the client-side JSON shape, not blanket runtime certification for every action or Solr version. diff --git a/docs/compatibility.md b/docs/compatibility.md index f255131..f12d501 100644 --- a/docs/compatibility.md +++ b/docs/compatibility.md @@ -38,7 +38,7 @@ The 8.11, 9.11, and 10.1 guides describe the API families below. The Replica Man | `security()` — authentication and authorization handlers | Documented | Documented | Documented | Tested: authenticated BasicAuth/RuleBasedAuthorization reads, anonymous 401 on both handlers, authentication `set-property` read-back, and malformed JSON rejection on pinned 8.11.4 / 9.11.0 / 10.1.0 images. Unit tests verify Basic/Bearer credentials are not exposed by client HTTP errors | | `stream()` — `/stream` | Documented | Documented | Documented | Tested: Streaming Expressions search against a disposable SolrCloud collection on pinned 8.11.4 / 9.11.0 / 10.1.0 images (CI #145) | | `export()` — `/export` | Documented | Documented | Documented | Tested: raw response with docValues sort field on pinned 8.11.4 / 9.11.0 / 10.1.0 images | -| `replication()` / `replicationFile()` — `/solr/{core}/replication` | Documented | Documented | Documented | Tested: indexversion, details, filelist (generation on newer versions / legacy indexversion on older versions), streamed filecontent, enablereplication/disablereplication, core backup, restorestatus, restore, and deletebackup on pinned 8.11.4 / 9.11.0 / 10.1.0 images. fetchindex/abortfetch (Solr 10), snappull/abort (Solr 8/9), enablepoll, and disablepoll have unit serialization coverage; an end-to-end multi-core leader/follower fetch cycle is not yet certified | +| `replication()` / `replicationFile()` — `/solr/{core}/replication` | Documented | Documented | Documented | Tested: indexversion, details, filelist (generation on newer versions / legacy indexversion on older versions), streamed filecontent, enablereplication/disablereplication, core backup, restorestatus, restore, deletebackup, and a disposable two-core leader/follower fetch with follower query read-back on pinned 8.11.4 / 9.11.0 / 10.1.0 images. The fetch uses snappull on Solr 8/9 and fetchindex on Solr 10. Abort and polling lifecycle behavior remains unit-only | | Core and collection backup/restore | `replication("backup" | "restore" | "restorestatus" | "deletebackup")`, `collections("BACKUP" | "RESTORE" | "LISTBACKUP" | "DELETEBACKUP" | "REQUESTSTATUS" | "DELETESTATUS")` | Documented | Documented | Documented | Tested: full core backup/restore/read-back and SolrCloud collection backup/list/restore/read-back/delete with asynchronous status polling on pinned 8.11.4 / 9.11.0 / 10.1.0 images using local filesystem repositories and shared SolrCloud storage | | `request()` / `requestRaw()` — generic caller-defined transport | Depends on the selected endpoint | Depends on the selected endpoint | Depends on the selected endpoint | Generic transport behavior has unit tests only; no endpoint-independent live Solr integration claim is made. Each custom endpoint remains unverified until tested individually | diff --git a/docs/config.md b/docs/config.md index ad65901..e1b53f6 100644 --- a/docs/config.md +++ b/docs/config.md @@ -17,7 +17,7 @@ await solr.config({ }); ``` -Typed command families include set/unset properties, request handlers, search components, query response writers, init params, query parsers, value-source parsers, transformers, update processors, query converters, listeners, and expressible components. `SolrRequestHandlerConfig` exposes typed `defaults`, `appends`, and `invariants` through `SolrConfigRequestHandlerParams`; common request parameters such as `q`, `fq`, `fl`, `rows`, `start`, `sort`, `wt`, `echoParams`, and time limits receive value-aware autocomplete, while the parameter map remains open for Solr extensions. Read models distinguish request handlers, search components, response writers, listeners, and init-params sets; response writers expose `contentType`/`charset`, and listeners expose standard event names with custom plugin event names still accepted. Cache settings are configured through `set-property` (for example, `query.filterCache.size`), not through a separate cache command. Search-component option maps and other plugin-defined component settings remain open; `customOptions` is the explicit flattened escape hatch for plugin-specific command properties. The library does not claim to validate every plugin's schema. Check the documentation for the target Solr version before applying configuration changes. +Typed command families include set/unset properties, request handlers, search components, query response writers, init params, query parsers, value-source parsers, transformers, update processors, query converters, listeners, and expressible components. `SolrRequestHandlerConfig` exposes typed `handleSelect` plus `defaults`, `appends`, and `invariants` through `SolrConfigRequestHandlerParams`; common request parameters such as `q`, `fq`, `fl`, `rows`, `start`, `sort`, `wt`, `echoParams`, and time limits receive value-aware autocomplete, while the parameter map remains open for Solr extensions. Read models distinguish request handlers, search components, response writers, listeners, and init-params sets; response writers expose `contentType`/`charset`, and listeners expose standard event names with custom plugin event names still accepted. Cache settings are configured through `set-property` (for example, `query.filterCache.size`), not through a separate cache command. Search-component option maps and other plugin-defined component settings remain open; `customOptions` is the explicit flattened escape hatch for plugin-specific command properties. The library does not claim to validate every plugin's schema. Check the documentation for the target Solr version before applying configuration changes. ## Common properties and response types diff --git a/docs/endpoint-inventory.md b/docs/endpoint-inventory.md index 35c74ec..235df19 100644 --- a/docs/endpoint-inventory.md +++ b/docs/endpoint-inventory.md @@ -12,7 +12,7 @@ This inventory defines the boundary of the typed client API after review against | Schema API `/schema` | `schema()` | Typed schema command models; integration coverage includes field, field-type, dynamic-field, copy-field, and schema read operations. | | Config API `/config` | `config()`, `getConfig()`, `getConfigOverlay()` | Typed POST command models and typed GET effective-config/overlay responses. Integration coverage includes user-property mutation/read-back; broader component mutation coverage is tracked separately in [Issue #75](https://github.com/vsimko/node-solr-lambda/issues/75). | | CoreAdmin V1 `/solr/admin/cores` | `coreAdmin()` | Action-specific parameter models include STATUS, CREATE, RELOAD, UNLOAD, RENAME, SWAP, MERGEINDEXES, SPLIT, REQUESTSTATUS, and REQUESTRECOVERY. Integration coverage includes isolated CREATE, STATUS discovery, RELOAD, and cleanup UNLOAD; async status/recovery actions have serialization coverage only. UPGRADEINDEX is intentionally excluded from the cross-version union because it is not part of the pinned Solr 8.11.4 baseline and is an in-place, non-SolrCloud operation. | -| Collections V1 `/solr/admin/collections` | `collections()` | Action-specific input models cover the current action union. Backup/restore and async operations have named response models; other actions use caller-supplied generic response shapes. Per-action evidence and unverified operations are tracked in the [Collections action coverage matrix](collections-api-coverage.md). Other unmodelled actions include MIGRATE, FORCELEADER, and selected cluster/node management commands. | +| Collections V1 `/solr/admin/collections` | `collections()` | Action-specific input models cover the current action union. Backup/restore and async operations have named response models; other actions use caller-supplied generic response shapes. Per-action evidence and unverified operations are tracked in the [Collections action coverage matrix](collections-api-coverage.md). SPLITSHARD and MIGRATE have opt-in, loopback-only runtime checks in the manually dispatched workflow; they remain unverified until that workflow passes. Other unmodelled actions include FORCELEADER and selected cluster/node management commands. | | Security API `/solr/admin/authentication`, `/solr/admin/authorization` | `security()` | Authentication and authorization requests; dedicated security-enabled integration coverage is available on all three pinned versions. | | Ping `/admin/ping` | `ping()` | Typed client method; response status tested on all three pinned versions. | | System information `/solr/admin/info/system` | `systemInfo()` | Typed client method; representative response integration-tested on all three pinned versions. | diff --git a/docs/replication.md b/docs/replication.md index 375730c..b0d2a5b 100644 --- a/docs/replication.md +++ b/docs/replication.md @@ -46,7 +46,7 @@ The `indexversion` response includes the current `generation` on newer Solr vers // Solr 10 uses fetchindex/abortfetch. Solr 8 and 9 use the legacy snappull/abort names. // leaderUrl may be omitted when configured on the follower. await client.replication("fetchindex", { - leaderUrl: "http://leader:8983/solr/catalog/replication", + leaderUrl: "http://leader:8983/solr/catalog", compression: "internal", httpConnTimeout: 5_000, httpReadTimeout: 10_000 @@ -59,7 +59,7 @@ await client.replication("enablepoll"); await client.replication("abortfetch"); ``` -`fetchindex` accepts the documented follower connection and compression settings. For Basic authentication between the follower and its leader, configure the ReplicationHandler's server-side follower settings; do not put credentials into URLs or query parameters. +`fetchindex` accepts the documented follower connection and compression settings. Solr 9+ expects `leaderUrl` as the core URL without the `/replication` suffix; Solr 8.11 expects the full handler URL. For Basic authentication between the follower and its leader, configure the ReplicationHandler's server-side follower settings; do not put credentials into URLs or query parameters. ## Control a leader @@ -103,4 +103,7 @@ Supported directory types are `file` (Lucene index files), `cf` (configuration f - The client currently exposes the V1 handler path. Solr 10.1 also documents V2 equivalents for selected file and index-version operations; no dedicated V2 wrapper is provided. - The handler may not be configured for user-managed leader/follower replication on every core. The server must have the appropriate ReplicationHandler configuration before control commands can be used. +- The pinned integration matrix creates disposable leader and follower cores, commits a unique leader document, triggers `snappull` on Solr 8/9 or `fetchindex` on Solr 10, and verifies that the document becomes searchable on the follower. This verifies a complete fetch/read-back cycle, not polling or abort of an in-progress transfer. +- `abort` (Solr 8/9) and `abortfetch` (Solr 10), plus `enablepoll`/`disablepoll`, have request-serialization unit coverage; cancellation of a live transfer remains unverified. + See the [Solr 8.11](https://solr.apache.org/__root/docs.solr.apache.org/guide/8_11/deployment-guide/user-managed-index-replication.html), [Solr 9.11](https://solr.apache.org/guide/solr/9_11/deployment-guide/user-managed-index-replication.html), and [Solr 10.1](https://solr.apache.org/guide/solr/10_1/deployment-guide/user-managed-index-replication.html) guides for server configuration and command semantics. diff --git a/src/types.ts b/src/types.ts index 249a2fd..28807a7 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1102,6 +1102,8 @@ export interface SolrConfigPlugin { customOptions?: Readonly>; } export interface SolrRequestHandlerConfig extends SolrConfigPlugin { + /** Allow the handler to be selected through the legacy `qt` request parameter. */ + handleSelect?: boolean; defaults?: SolrConfigRequestHandlerParams; appends?: SolrConfigRequestHandlerParams; invariants?: SolrConfigRequestHandlerParams; diff --git a/test/admin-api-types.ts b/test/admin-api-types.ts index 24cb2cb..ce7814c 100644 --- a/test/admin-api-types.ts +++ b/test/admin-api-types.ts @@ -192,6 +192,7 @@ const invalidConfigValue: SolrConfigCommand = { "set-property": { "query.maxBool // @ts-expect-error unset-property only accepts known common Config API property names. const invalidUnsetProperty: SolrConfigCommand = { "unset-property": "query.notARealSetting" }; void [invalidConfigProperty, invalidConfigValue, invalidUnsetProperty]; +const handleSelectHandler: SolrConfigCommand = { "add-requesthandler": { name: "/replication", class: "solr.ReplicationHandler", handleSelect: true } }; const addHandler: SolrConfigCommand = { "add-requesthandler": { name: "/select", @@ -214,6 +215,7 @@ const addInitParams: SolrConfigCommand = { "add-initparams": { name: "shared-search-params", path: ["/select", "/query"], defaults: { rows: 20, wt: "json" } } }; void client.config(setProperty); +void client.config(handleSelectHandler); void client.config(addHandler); void client.config(addListener); void client.config(addInitParams); diff --git a/test/integration/all.test.ts b/test/integration/all.test.ts index 36f2f2f..be2a43a 100644 --- a/test/integration/all.test.ts +++ b/test/integration/all.test.ts @@ -1,4 +1,5 @@ import "./solr-collections.test.js"; +import "./solrcloud-split-migrate-optin.test.js"; import "./solr-config-security.test.js"; import "./solr-config-operations.test.js"; import "./solr-core-lifecycle.test.js"; @@ -15,6 +16,7 @@ import "./solr-cursor.test.js"; import "./solr-schema.test.js"; import "./solr-security.test.js"; import "./solr-replication.test.js"; +import "./solr-replication-lifecycle.test.js"; import "./solr-core-backup-restore.test.js"; import "./solrcloud-backup-restore.test.js"; import "./solr-smoke.test.js"; diff --git a/test/integration/fixtures.ts b/test/integration/fixtures.ts index 0b8d7dd..2bceb86 100644 --- a/test/integration/fixtures.ts +++ b/test/integration/fixtures.ts @@ -234,9 +234,11 @@ export function integrationTest( export function cloudIntegrationTest( name: string, run: (fixture: SolrCloudIntegrationFixture) => Promise, - requiresMultipleNodes = false + requiresMultipleNodes = false, + requiresDestructiveOptIn = false ): void { - test(name, { skip: !baseUrl || !cloudEnabled || (requiresMultipleNodes && process.env.SOLR_MULTINODE !== "true") }, async () => { + const localEndpoint = baseUrl ? ["localhost", "127.0.0.1", "::1"].includes(new URL(baseUrl).hostname) : false; + test(name, { skip: !baseUrl || !cloudEnabled || (requiresMultipleNodes && process.env.SOLR_MULTINODE !== "true") || (requiresDestructiveOptIn && (process.env.SOLR_DESTRUCTIVE_OPT_IN !== "true" || !localEndpoint)) }, async () => { const client = new SolrClient({ baseUrl: baseUrl!, timeoutMs: 30_000 }); const createdCollections = new Set(); const createdAliases = new Set(); diff --git a/test/integration/solr-replication-lifecycle.test.ts b/test/integration/solr-replication-lifecycle.test.ts new file mode 100644 index 0000000..23ae073 --- /dev/null +++ b/test/integration/solr-replication-lifecycle.test.ts @@ -0,0 +1,76 @@ +import assert from "node:assert/strict"; +import { SolrClient } from "../../src/index.js"; +import { integrationTest } from "./fixtures.js"; + +integrationTest("ReplicationHandler fetches a committed leader index into an isolated follower core", async ({ + createCore, + createId +}) => { + const leaderCore = await createCore("replication_lifecycle_leader"); + const followerCore = await createCore("replication_lifecycle_follower"); + const baseUrl = process.env.SOLR_BASE_URL!; + const username = process.env.SOLR_USERNAME; + const password = process.env.SOLR_PASSWORD; + const makeClient = (core: string) => new SolrClient({ + baseUrl, + core, + timeoutMs: 30_000, + ...(username && password ? { auth: { type: "basic" as const, username, password } } : {}) + }); + const leader = makeClient(leaderCore); + const follower = makeClient(followerCore); + const solrMajor = Number(process.env.SOLR_VERSION?.split(".")[0] ?? "0"); + // Solr 8 expects the handler URL; Solr 9+ appends /replication internally. + const leaderUrl = `${baseUrl.replace(/\/$/, "")}/${leaderCore}${solrMajor === 8 ? "/replication" : ""}`; + const documentId = createId("replication_lifecycle"); + + const leaderConfig = await leader.config({ + "update-requesthandler": { + name: "/replication", + class: "solr.ReplicationHandler", + handleSelect: true, + customOptions: { leader: { replicateAfter: "commit" } } + } + }); + assert.equal(leaderConfig.responseHeader?.status, 0); + const followerConfig = await follower.config({ + "update-requesthandler": { + name: "/replication", + class: "solr.ReplicationHandler", + handleSelect: true, + customOptions: { follower: { leaderUrl, ...(username && password ? { httpBasicAuthUser: username, httpBasicAuthPassword: password } : {}) } } + } + }); + assert.equal(followerConfig.responseHeader?.status, 0); + + await leader.add([{ id: documentId, title: "leader follower lifecycle" }]); + await leader.commit({ waitSearcher: true }); + + const leaderVersion = await leader.replication("indexversion"); + assert.equal(leaderVersion.responseHeader?.status, 0); + const leaderGeneration = leaderVersion.generation ?? Number(leaderVersion.indexversion); + assert.ok(Number.isFinite(leaderGeneration) && leaderGeneration > 0, "leader must publish a replicable index"); + + const solrMajor = process.env.SOLR_VERSION?.split(".")[0]; + const fetchCommand = solrMajor === "10" ? "fetchindex" : "snappull"; + const fetchResponse = await follower.replication(fetchCommand, { leaderUrl }); + assert.equal(fetchResponse.responseHeader?.status, 0, `${fetchCommand} should complete successfully`); + + // Replication copies Lucene index files asynchronously; assert the resulting + // searchable state rather than treating the command's HTTP response as proof. + let found = false; + for (let attempt = 0; attempt < 60; attempt += 1) { + const result = await follower.query<{ response?: { numFound?: number } }>({ query: `id:"${documentId}"`, fields: ["id"], limit: 1 }); + if ((result.response?.numFound ?? 0) === 1) { + found = true; + break; + } + await new Promise(resolve => setTimeout(resolve, 500)); + } + assert.ok(found, "follower should expose the leader document after the fetch completes"); + + const followerVersion = await follower.replication("indexversion"); + assert.equal(followerVersion.responseHeader?.status, 0); + const followerGeneration = followerVersion.generation ?? Number(followerVersion.indexversion); + assert.ok(Number.isFinite(followerGeneration) && followerGeneration > 0, "follower should publish its fetched index"); +}); diff --git a/test/integration/solrcloud-split-migrate-optin.test.ts b/test/integration/solrcloud-split-migrate-optin.test.ts new file mode 100644 index 0000000..84c683b --- /dev/null +++ b/test/integration/solrcloud-split-migrate-optin.test.ts @@ -0,0 +1,103 @@ +import assert from "node:assert/strict"; +import { randomUUID } from "node:crypto"; +import { cloudIntegrationTest } from "./fixtures.js"; + +const routeKey = "tenant!"; + +async function waitForCollectionRequest(client: import("../../src/index.js").SolrClient, requestId: string): Promise { + for (let attempt = 0; attempt < 180; attempt += 1) { + const result = await client.collections("REQUESTSTATUS", { requestid: requestId }); + const state = result.status?.state; + if (state === "completed") { + await client.collections("DELETESTATUS", { requestid: requestId }); + return; + } + if (state === "failed" || state === "notfound") { + throw new Error(`Collection request ${requestId} ended in ${state}: ${JSON.stringify(result)}`); + } + await new Promise(resolve => setTimeout(resolve, 1_000)); + } + throw new Error(`Collection request ${requestId} did not complete within 180 seconds`); +} + +cloudIntegrationTest( + "SPLITSHARD splits an isolated compositeId collection and preserves routed documents", + async ({ client, createCollection, clientForCollection }) => { + const collection = await createCollection("integration_splitshard_optin"); + const statusBefore = await client.collections("CLUSTERSTATUS", { collection }); + const liveNodes = statusBefore.cluster?.live_nodes ?? []; + assert.ok(liveNodes.length >= 2, "opt-in verification requires at least two live SolrCloud nodes"); + const beforeShards = statusBefore.cluster?.collections?.[collection]?.shards ?? {}; + assert.equal(Object.keys(beforeShards).length, 1, "new disposable collection must start with exactly one shard"); + + const source = clientForCollection(collection); + const ids = [1, 2, 3].map(index => `${routeKey}split-${index}`); + await source.add(ids.map(id => ({ id, title: "splitshard opt-in verification" }))); + await source.commit({ waitSearcher: true }); + + const splitRequestId = `split-${randomUUID()}`; + const split = await client.collections("SPLITSHARD", { + collection, + "split.key": routeKey, + async: splitRequestId + }); + assert.equal(split.responseHeader?.status, 0, "SPLITSHARD should be accepted"); + await waitForCollectionRequest(client, splitRequestId); + + const statusAfter = await client.collections("CLUSTERSTATUS", { collection }); + const shardsAfter = statusAfter.cluster?.collections?.[collection]?.shards ?? {}; + assert.ok(Object.keys(shardsAfter).length >= 2, "cluster state should expose the split child shards"); + assert.ok( + Object.values(shardsAfter).filter(shard => shard.state === "active").length >= 2, + "at least two child shards should be active" + ); + const result = await source.query<{ response?: { numFound?: number } }>({ query: "*:*", fields: ["id"], limit: 10 }); + assert.equal(result.response?.numFound, ids.length, "all documents should remain searchable after the split"); + }, + true, + true +); + +cloudIntegrationTest( + "MIGRATE moves an isolated compositeId routing prefix into a disposable target collection", + async ({ client, createCollection, clientForCollection }) => { + const sourceName = await createCollection("integration_migrate_source_optin"); + const targetName = await createCollection("integration_migrate_target_optin"); + const status = await client.collections("CLUSTERSTATUS"); + assert.ok((status.cluster?.live_nodes?.length ?? 0) >= 2, "opt-in verification requires at least two live SolrCloud nodes"); + const source = clientForCollection(sourceName); + const target = clientForCollection(targetName); + const ids = [1, 2, 3].map(index => `${routeKey}migrate-${index}`); + + await source.add(ids.map(id => ({ id, title: "migration opt-in verification" }))); + await source.commit({ waitSearcher: true }); + + const targetBefore = await target.query<{ response?: { numFound?: number } }>({ query: "*:*", fields: ["id"], limit: 10 }); + assert.equal(targetBefore.response?.numFound, 0, "migration target must be empty before mutation"); + + const migrationRequestId = `migrate-${randomUUID()}`; + const migrated = await client.collections("MIGRATE", { + collection: sourceName, + "split.key": routeKey, + "target.collection": targetName, + "forward.timeout": 120, + async: migrationRequestId + }); + assert.equal(migrated.responseHeader?.status, 0, "MIGRATE should be accepted"); + await waitForCollectionRequest(client, migrationRequestId); + + let targetCount = 0; + for (let attempt = 0; attempt < 60; attempt += 1) { + const result = await target.query<{ response?: { numFound?: number } }>({ query: "*:*", fields: ["id"], limit: 10 }); + targetCount = result.response?.numFound ?? 0; + if (targetCount === ids.length) break; + await new Promise(resolve => setTimeout(resolve, 1_000)); + } + assert.equal(targetCount, ids.length, "target collection should contain every migrated routing-key document"); + const targetReadBack = await target.query<{ response?: { docs?: Array<{ id: string }> } }>({ query: "*:*", fields: ["id"], limit: 10 }); + const foundIds = new Set(targetReadBack.response?.docs?.map(document => String(document.id)) ?? []); + for (const id of ids) assert.ok(foundIds.has(id), `target collection should contain migrated document ${id}`); + }, + true, + true +); diff --git a/test/replication-api-types.ts b/test/replication-api-types.ts index d5fb558..138d1e6 100644 --- a/test/replication-api-types.ts +++ b/test/replication-api-types.ts @@ -6,13 +6,13 @@ void client.replication("indexversion"); void client.replication("details"); void client.replication("filelist", { generation: 12 }); void client.replication("filelist", { indexversion: 12 }); -void client.replication("fetchindex", { leaderUrl: "http://leader:8983/solr/catalog/replication", compression: "internal" }); +void client.replication("fetchindex", { leaderUrl: "http://leader:8983/solr/catalog", compression: "internal" }); void client.replication("enablereplication"); void client.replication("disablereplication"); void client.replication("enablepoll"); void client.replication("disablepoll"); void client.replication("abortfetch"); -void client.replication("snappull", { leaderUrl: "http://leader:8983/solr/catalog/replication" }); +void client.replication("snappull", { leaderUrl: "http://leader:8983/solr/catalog" }); void client.replication("abort"); void client.replicationFile({ directoryType: "cf", filePath: "managed-schema", checksum: true }); diff --git a/test/replication.test.ts b/test/replication.test.ts index 1165174..9046b8e 100644 --- a/test/replication.test.ts +++ b/test/replication.test.ts @@ -14,7 +14,7 @@ test("ReplicationHandler commands use the configured core and JSON response form await client.replication("indexversion"); await client.replication("fetchindex", { - leaderUrl: "http://leader:8983/solr/catalog/replication", + leaderUrl: "http://leader:8983/solr/catalog", compression: "internal", httpConnTimeout: 5000, customParams: { maxWriteMBPerSec: 20 } @@ -27,7 +27,7 @@ test("ReplicationHandler commands use the configured core and JSON response form assert.equal(calls[0]!.url.searchParams.get("command"), "indexversion"); assert.equal(calls[0]!.url.searchParams.get("wt"), "json"); assert.equal(calls[1]!.url.searchParams.get("command"), "fetchindex"); - assert.equal(calls[1]!.url.searchParams.get("leaderUrl"), "http://leader:8983/solr/catalog/replication"); + assert.equal(calls[1]!.url.searchParams.get("leaderUrl"), "http://leader:8983/solr/catalog"); assert.equal(calls[1]!.url.searchParams.get("compression"), "internal"); assert.equal(calls[1]!.url.searchParams.get("httpConnTimeout"), "5000"); assert.equal(calls[1]!.url.searchParams.get("maxWriteMBPerSec"), "20"); @@ -54,7 +54,7 @@ test("ReplicationHandler supports read, poll, and replication lifecycle commands await client.replication("enablepoll"); await client.replication("disablepoll"); await client.replication("abortfetch"); - await client.replication("snappull", { leaderUrl: "http://leader:8983/solr/catalog/replication" }); + await client.replication("snappull", { leaderUrl: "http://leader:8983/solr/catalog" }); await client.replication("abort"); assert.deepEqual(commands, [