| #15 |
HTTP connection reuse |
A single AlternatorClient is configured to send requests through a proxy that counts client TCP connects/disconnects |
The client sends many sequential requests to the same target node |
The proxy observes one long-lived client connection, or at most a very small bounded number, rather than one connection per request |
Use proxy on_client_connect and on_client_disconnect hooks |
| #15 |
HTTP connection reuse after idle |
A single AlternatorClient has already completed at least one request through the proxy |
The client waits for a short idle period and then sends more requests |
The connection is reused if the idle period is within pool lifetime, or reconnects only once if pool expiry is expected |
Helps detect hidden reconnect churn |
| #15 |
HTTP connection reuse across discovery traffic |
Load balancing and discovery are enabled and all traffic goes through the proxy |
The client performs repeated operations while background /localnodes discovery also runs |
Request success is maintained without opening a new client connection for every GET or POST |
Distinguishes discovery GETs from operation POSTs |
| #45 |
LiveNodes no fallback on empty /localnodes |
LiveNodes has a routing scope with no fallback, already holds a non-empty node list, and a mocked node returns an empty /localnodes response |
A refresh cycle runs |
LiveNodes returns without changing the existing live node list |
The empty-with-fallback branch is already exercised by scope_fallback_test; this no-fallback early-return guard is not |
| #45 |
LiveNodes retry on failed /localnodes request |
LiveNodes has multiple candidate nodes and the first node fails all network requests |
A refresh cycle runs |
LiveNodes skips the failed node and continues trying remaining candidates in the same scope |
Pins the failure branch without relying on broader cluster behavior |
| #45 |
LiveNodes seed fallback |
All currently known live nodes fail during refresh, but seed nodes are still configured |
A refresh cycle runs |
LiveNodes retries using seed nodes before giving up |
Important recovery path |
| #45 |
LiveNodes no update on total failure |
LiveNodes already holds a non-empty known-good node list and all refresh attempts fail |
A refresh cycle runs |
The previous live node list remains unchanged |
Guards against accidental state wipeout |
| #46 |
Retry path within one request |
A client is configured with multiple live nodes and the first node fails |
The SDK retries the same logical operation |
The next attempt targets a different node rather than retrying the same failed node immediately |
Validates QueryPlan walking under retries |
| #46 |
QueryPlan round-robin node exhaustion |
A round-robin QueryPlan is backed by a small finite live node list |
next_node() is called repeatedly within the same plan until all nodes are consumed |
Each node is returned at most once and None is returned afterwards |
Affinity exhaustion already has unit coverage; round_robin_test covers cross-request fairness but not single-plan None-on-exhaustion |
| #42 |
Per-request override: combined matrix |
Client-level config sets request_compression and optimize_headers, and one operation overrides both settings together in all meaningful combinations |
Each customized operation is sent |
Each override affects only that one operation, wins over client defaults, and the next uncustomized call reverts to client defaults |
Current tests cover both features independently, but not their combined interceptor path |
| #42 |
HTTP content features + load balancing |
Load balancing is enabled and a request uses request_compression and/or optimize_headers |
Requests are routed across nodes and may be retried |
Routing still works correctly and the observed request body/headers still reflect the expected compression and header filtering behavior |
Covers the missing interaction between HTTP content features and routing without expanding the matrix too much |
| #42 |
Interceptor ordering: compression before signing |
A signed request with compression enabled is sent |
The request passes through the interceptor chain |
The signed request reflects the compressed body and updated headers, not a pre-compression payload |
Best verified with a focused interceptor-level or proxy-assisted test |
| #42 |
Interceptor ordering: URI rewrite before transmit |
A query plan provides an alternate node URI |
The request passes through signing and transmit interceptors |
The final transmitted URI targets the node selected by the query plan |
Verifies that routing hook fires at the right stage |
| #43 |
Affinity + retry within one request |
Affinity routing is enabled, the request qualifies for affinity, and the affinity-selected first node fails |
The SDK retries the same logical write |
The retry uses the next node from the same per-request affinity QueryPlan, rather than restarting selection or reusing the failed node |
Most important missing feature-combination test |
| #43 |
Affinity + scope fallback and recovery |
Affinity routing is enabled and the preferred routing scope later becomes unavailable, then recovers |
Qualifying writes continue throughout failure and recovery |
Routing stays deterministic within the currently live fallback set, then returns to the primary scope once it is available again |
Covers interaction between affinity and LiveNodes fallback/recovery |
| #44 |
PartitionKeyResolver transient retry |
DescribeTable fails with transient errors a few times before eventually succeeding |
Discovery is triggered for a table with no cached partition key |
The resolver retries with backoff and caches the discovered partition key on success |
Important correctness path, currently not unit-tested |
| #44 |
PartitionKeyResolver permanent failure cooldown |
DescribeTable fails with a permanent error such as resource not found or access denied |
Discovery is triggered repeatedly for the same table |
No further DescribeTable requests are made until the cooldown window expires |
Current unit coverage checks cooldown bookkeeping, but not the full request-level behavior |
| #44 |
PartitionKeyResolver in-progress deduplication |
Multiple concurrent tasks need partition key discovery for the same table |
Discovery is triggered concurrently from all of them |
Exactly one DescribeTable request is sent; other callers observe the shared result |
There is unit coverage for slot deduplication; add a request-level test that counts actual DescribeTable calls |
| #44 |
PartitionKeyResolver cancellation safety |
Partition key discovery has started and the runtime or owning task is dropped mid-flight |
The in-flight discovery is cancelled |
No stuck in-progress marker is left behind and future discovery for the same table works correctly |
Good resilience test; cancellation is intentional per the module docs |
| #47 |
Builder/config pass-through correctness |
An AlternatorBuilder sets a selection of DynamoDB builder options such as timeout, retry config, and region |
The config is built and used to construct a client |
The resulting inner client config preserves those option values |
Existing compatibility test checks method presence, not that values are forwarded correctly |
| #47 |
Invalid seed host handling |
Config includes malformed or unusual seed host values such as empty strings, missing ports, or non-URL characters |
LiveNodes or the client config is constructed |
Construction fails safely or ignores invalid entries in a well-defined and documented way |
Currently no test exercises this boundary |
| #47 |
Duplicate seed host handling |
Config includes the same seed host address multiple times |
Discovery starts and refreshes repeatedly |
Behavior stays stable and discovery does not multiply requests unnecessarily per duplicate |
Mostly an efficiency and correctness-of-dedup concern |
| #45 |
Concurrent discovery startup |
Multiple tasks share the same client or LiveNodes and access it simultaneously for the first time without any prior request |
Discovery startup is triggered concurrently from all tasks |
Discovery starts exactly once with no panic, no duplicate background tasks, and no data races |
Basic startup paths are already covered; this should stress the compare_exchange path directly |
| #49 |
Main API HTTPS smoke test |
A client is configured with an https:// endpoint and valid TLS trust for the test server |
A basic operation is sent |
The request succeeds over HTTPS without any Alternator-specific HTTP client customization |
Main API extensions should not interfere with standard SDK TLS handling |
| #49 |
LiveNodes HTTPS discovery from HTTPS seed |
LiveNodes is configured with HTTPS seed nodes and /localnodes is served over HTTPS |
A refresh cycle runs |
The discovered live nodes are updated successfully and preserve the https scheme |
Covers the driver-specific discovery path over TLS |
| #49 |
HTTPS routing after discovery refresh |
Load balancing is enabled, discovery runs over HTTPS, and multiple HTTPS nodes are available |
Requests are sent after discovery updates the live node list |
Routed requests target the discovered HTTPS node URIs successfully |
Verifies URI rewrite preserves HTTPS on routed requests |
AlternatorClientis configured to send requests through a proxy that counts client TCP connects/disconnectson_client_connectandon_client_disconnecthooksAlternatorClienthas already completed at least one request through the proxy/localnodesdiscovery also runsLiveNodesno fallback on empty/localnodesLiveNodeshas a routing scope with no fallback, already holds a non-empty node list, and a mocked node returns an empty/localnodesresponseLiveNodesreturns without changing the existing live node listscope_fallback_test; this no-fallback early-return guard is notLiveNodesretry on failed/localnodesrequestLiveNodeshas multiple candidate nodes and the first node fails all network requestsLiveNodesskips the failed node and continues trying remaining candidates in the same scopeLiveNodesseed fallbackLiveNodesretries using seed nodes before giving upLiveNodesno update on total failureLiveNodesalready holds a non-empty known-good node list and all refresh attempts failQueryPlanwalking under retriesQueryPlanround-robin node exhaustionQueryPlanis backed by a small finite live node listnext_node()is called repeatedly within the same plan until all nodes are consumedNoneis returned afterwardsround_robin_testcovers cross-request fairness but not single-planNone-on-exhaustionrequest_compressionandoptimize_headers, and one operation overrides both settings together in all meaningful combinationsrequest_compressionand/oroptimize_headersQueryPlan, rather than restarting selection or reusing the failed nodeLiveNodesfallback/recoveryPartitionKeyResolvertransient retryDescribeTablefails with transient errors a few times before eventually succeedingPartitionKeyResolverpermanent failure cooldownDescribeTablefails with a permanent error such as resource not found or access deniedDescribeTablerequests are made until the cooldown window expiresPartitionKeyResolverin-progress deduplicationDescribeTablerequest is sent; other callers observe the shared resultDescribeTablecallsPartitionKeyResolvercancellation safetyAlternatorBuildersets a selection of DynamoDB builder options such as timeout, retry config, and regionLiveNodesor the client config is constructedLiveNodesand access it simultaneously for the first time without any prior requestcompare_exchangepath directlyhttps://endpoint and valid TLS trust for the test serverLiveNodesHTTPS discovery from HTTPS seedLiveNodesis configured with HTTPS seed nodes and/localnodesis served over HTTPShttpsscheme