From a4e9ededdaec2a8fe65cd8bdffb8209885c1111e Mon Sep 17 00:00:00 2001 From: Daniel Sutton Date: Fri, 21 Aug 2026 13:33:40 +0100 Subject: [PATCH] test(run-store): prove the completion TTL is re-applied, not merely uncleared --- .../run-store/src/redisSnapshotStore.test.ts | 52 +++++++++++++++---- 1 file changed, 43 insertions(+), 9 deletions(-) diff --git a/internal-packages/run-store/src/redisSnapshotStore.test.ts b/internal-packages/run-store/src/redisSnapshotStore.test.ts index 9c8903091..86e458796 100644 --- a/internal-packages/run-store/src/redisSnapshotStore.test.ts +++ b/internal-packages/run-store/src/redisSnapshotStore.test.ts @@ -392,7 +392,7 @@ describe("TTL rule", () => { expect(await raw.pttl(key)).toBe(-1); } } finally { - raw.disconnect(); + await raw.quit(); await store.quit(); } }); @@ -409,6 +409,13 @@ describe("TTL rule", () => { isTerminal: false, cycle: { kind: "new", completedWaitpoints: [{ id: "w_a", index: 0 }] }, }); + // Second cycle, so the terminal PEXPIRE loop runs past its first iteration. + await store.append({ + entry: entry({ id: "s1b" }), + kind: "transition", + isTerminal: false, + cycle: { kind: "new", completedWaitpoints: [{ id: "w_b", index: 0 }] }, + }); const r = await store.append({ entry: entry({ id: "s2", executionStatus: "FINISHED" }), kind: "transition", @@ -421,13 +428,14 @@ describe("TTL rule", () => { "snap:{run_1}:cur", "snap:{run_1}:seq", "snap:{run_1}:wp:1", + "snap:{run_1}:wp:2", ]) { const ttl = await raw.pttl(key); expect(ttl).toBeGreaterThan(0); expect(ttl).toBeLessThanOrEqual(60_000); } } finally { - raw.disconnect(); + await raw.quit(); await store.quit(); } } @@ -437,12 +445,37 @@ describe("TTL rule", () => { const store = new RedisSnapshotStore({ redisOptions, completedTtlMs: 60_000 }); const raw = createRedisClient(redisOptions); try { - await store.append({ entry: entry({ id: "s1" }), kind: "birth", isTerminal: false }); + await store.append({ + entry: entry({ id: "s1" }), + kind: "birth", + isTerminal: false, + cycle: { kind: "new", completedWaitpoints: [{ id: "w_a", index: 0 }] }, + }); + await store.append({ + entry: entry({ id: "s1b" }), + kind: "transition", + isTerminal: false, + cycle: { kind: "new", completedWaitpoints: [{ id: "w_b", index: 0 }] }, + }); await store.append({ entry: entry({ id: "s2", executionStatus: "FINISHED" }), kind: "transition", isTerminal: true, }); + + const keys = [ + "snap:{run_1}:e", + "snap:{run_1}:idx", + "snap:{run_1}:cur", + "snap:{run_1}:seq", + "snap:{run_1}:wp:1", + "snap:{run_1}:wp:2", + ]; + // Shrink first: a re-apply is then the only way the TTL can go back up. + for (const key of keys) { + await raw.pexpire(key, 5_000); + } + // A stale client appends a non-terminal, invalid row after FINISHED. const late = await store.append({ entry: entry({ id: "s3", error: "stale" }), @@ -450,12 +483,13 @@ describe("TTL rule", () => { isTerminal: false, }); expect(late).toMatchObject({ outcome: "written", ttl: "reapplied" }); - // Never a live TTL, and never cleared: the key stays bounded. - const ttl = await raw.pttl("snap:{run_1}:e"); - expect(ttl).toBeGreaterThan(0); - expect(ttl).toBeLessThanOrEqual(60_000); + for (const key of keys) { + const ttl = await raw.pttl(key); + expect(ttl).toBeGreaterThan(55_000); + expect(ttl).toBeLessThanOrEqual(60_000); + } } finally { - raw.disconnect(); + await raw.quit(); await store.quit(); } }); @@ -480,7 +514,7 @@ describe("TTL rule", () => { expect(after).toEqual({ outcome: "skippedNoKeyspace" }); expect(await raw.exists("snap:{run_1}:e")).toBe(0); } finally { - raw.disconnect(); + await raw.quit(); await store.quit(); } });