diff --git a/packages/services/service-cluster-redis/src/redis.contract.test.ts b/packages/services/service-cluster-redis/src/redis.contract.test.ts index 5b955a43de..481614adc8 100644 --- a/packages/services/service-cluster-redis/src/redis.contract.test.ts +++ b/packages/services/service-cluster-redis/src/redis.contract.test.ts @@ -3,12 +3,87 @@ /** * Driver contract tests for the Redis cluster driver, run against * `ioredis-mock` so they execute without a real Redis instance in CI. + * There is no live-Redis path in this file: every suite below runs on the + * mock. (An earlier version of this header promised `RUN_REAL_REDIS=1` + + * `REDIS_URL` and "conditional `describe.skipIf` blocks at the bottom" — + * no such blocks were ever here. Scanning the whole package, not just + * `src/`: `RUN_REAL_REDIS` and `skipIf` occur nowhere outside that + * sentence, so the escape hatch never existed. `REDIS_URL` does occur, at + * `README.md:42` — but as the env var a caller feeds to + * `createRedisClient()`, which is unrelated to any test path.) * - * The same suites can be invoked against a live Redis by setting - * `RUN_REAL_REDIS=1` and providing `REDIS_URL` — see the conditional - * `describe.skipIf` blocks at the bottom. + * ## The double is one major version behind the client it doubles + * + * This package depends on `ioredis@^6`, while `ioredis-mock@8.13.1` + * declares `peerDependencies: { ioredis: "^5" }` — so a resolving + * `pnpm install` prints an unmet-peer warning for it (a frozen re-link + * prints nothing). That gap is real, and it is + * declared here rather than closed, because it was measured to be inert + * on the surface these suites actually drive. Measured against ioredis + * 5.11.1 (newest release satisfying the mock's `^5` peer) and 6.0.0 (the + * version resolved in this workspace): + * + * - Every Redis command issued by this package's `src/*.ts` — get, set, + * del, incr, incrby, pttl, watch, unwatch, exec, publish, subscribe, + * unsubscribe, quit, eval — carries all of its v5 overloads verbatim + * into v6's `RedisCommander.d.ts`. `set` is a strict superset there + * (v6 adds the IFEQ/IFNE/IFDEQ/IFDNE tokens); nothing used here was + * removed or re-shaped. + * - `multi()` is deliberately not in that list: it is declared on + * `Transaction` (`transaction.d.ts`) and appears in + * `RedisCommander.d.ts` in neither version. Measured separately, it + * DID change — all four overloads went from returning + * `ChainableCommander` to `ChainableCommander` parameterised by a + * mapping. It is inert here because the parameter defaults to "resp2" + * and a client built without `replyMapping` reaches it as such: the + * class defaults to "legacy" and extends `Transaction` at "resp2". + * So this package's `multi()` resolves to the non-RESP3 instantiation, + * and `exec()`'s own declaration is byte-identical across the pair. + * - v6's one substantive change reachable from the surface these suites + * drive is RESP3 reply mapping, and it is opt-in: the class is + * declared with a `ReplyMapping` parameter defaulting to "legacy", + * `ChainableCommander` defaults to "resp2", and `duplicate()` with no + * override inherits the caller's mapping. This package never passes + * `replyMapping`, so every reply shape these suites see is the v5 one. + * ⚠️ Scoped deliberately: v6 also changes connection defaults that are + * NOT opt-in — `protocol: 3` (no such option in v5) and `keepAlive` + * 0 -> 30000. Those are reached in production through + * `createRedisClient()`, which no suite here calls (see below), so + * they are changed-but-unexercised rather than absent. + * - The three `RedisOptions` keys client.ts sets — lazyConnect, + * maxRetriesPerRequest, enableAutoPipelining — are declared + * identically in both versions. + * + * That named set is the whole basis for the claim; it is not a statement + * about ioredis 5 vs 6 in general. If the `ioredis` or `ioredis-mock` + * range in package.json moves — OR if the version either one RESOLVES to + * moves under an unchanged caret range, which a lockfile bump alone will + * do — this paragraph expires and the diff has to be re-taken. + * + * ## What these suites therefore do NOT certify + * + * They reach the mock only through the injected `client` and the + * `client.duplicate()` the pub/sub adapter makes. They never call + * `createRedisClient()`, so `new Redis(url, options)` — this package's + * only contact with ioredis's constructor and connection surface, and the + * area v6 changed most — is exercised by nothing in this file. That is + * why the version gap is inert here, and it is not a reason to trust the + * double: the mock is simply never asked to stand in for the surface on + * which the two majors differ. */ +// `ioredis-mock` publishes no type declarations of its own — no `types` +// field in its manifest and no `.d.ts` in the tarball — so this import +// raises TS7016 ("could not find a declaration file ... implicitly has an +// 'any' type") and the directive below is what silences it. Note the +// second cost, beyond the missing types: with the module untyped +// `RedisMock` is `any`, so every `client:` argument constructed from it +// satisfies ioredis's `Redis` type without ever being checked against it. +// That is the other reason a v5-vs-v6 divergence could not surface here. +// `@types/ioredis-mock` exists and would type this seam, but it only +// *asserts* `new(): ioredis.Redis` rather than describing the mock, so +// adopting it would trade an honest `any` for an unearned certainty — +// that trade has not been made, deliberately. // @ts-expect-error — ioredis-mock has no published types import RedisMock from 'ioredis-mock'; import { describe, expect, it, vi } from 'vitest';