import { redisTest } from "@internal/testcontainers"; import { subDays } from "date-fns"; import Redis from "ioredis"; import { describe, expect, vi } from "vitest"; import { DevPresence } from "~/presenters/v3/DevPresence.server"; vi.setConfig({ testTimeout: 30_000 }); let seq = 0; function ids() { seq += 1; return { userId: `user_${seq}`, projectId: `proj_${seq}` }; } const recentKey = (userId: string, projectId: string) => `dev-recent:${userId}:${projectId}`; describe("DevPresence — recency ZSET", () => { redisTest( "getRecentBranchIds returns an empty map when nothing has pinged", async ({ redisOptions }) => { const presence = new DevPresence(redisOptions); const { userId, projectId } = ids(); const result = await presence.getRecentBranchIds(userId, projectId); expect(result.size).toBe(0); } ); redisTest("a ping records the branch as recently active", async ({ redisOptions }) => { const presence = new DevPresence(redisOptions); const { userId, projectId } = ids(); await presence.setConnected({ userId, projectId, environmentId: "env_a", ttl: 60 }); const result = await presence.getRecentBranchIds(userId, projectId); expect([...result.keys()]).toEqual(["env_a"]); expect(result.get("env_a")).toBeInstanceOf(Date); }); redisTest("debounces to at most one ZADD per env per minute", async ({ redisOptions }) => { const presence = new DevPresence(redisOptions); const redis = new Redis(redisOptions); const { userId, projectId } = ids(); // First ping records env_a. await presence.setConnected({ userId, projectId, environmentId: "env_a", ttl: 60 }); // Simulate the entry being removed (e.g. another reader pruned it) while the // 60s debounce touch key is still live. await redis.zrem(recentKey(userId, projectId), "env_a"); // A second ping within the debounce window must NOT re-add it. await presence.setConnected({ userId, projectId, environmentId: "env_a", ttl: 60 }); const result = await presence.getRecentBranchIds(userId, projectId); expect(result.has("env_a")).toBe(false); await redis.quit(); }); redisTest("does not return entries older than the recency window", async ({ redisOptions }) => { const presence = new DevPresence(redisOptions); const redis = new Redis(redisOptions); const { userId, projectId } = ids(); const key = recentKey(userId, projectId); const fourDaysAgo = subDays(Date.now(), 4).getTime(); const oneHourAgo = Date.now() - 60 * 60 * 1000; await redis.zadd(key, fourDaysAgo, "env_stale", oneHourAgo, "env_fresh"); const result = await presence.getRecentBranchIds(userId, projectId); expect([...result.keys()]).toEqual(["env_fresh"]); await redis.quit(); }); redisTest("physically prunes stale entries on the next ping", async ({ redisOptions }) => { const presence = new DevPresence(redisOptions); const redis = new Redis(redisOptions); const { userId, projectId } = ids(); const key = recentKey(userId, projectId); await redis.zadd(key, subDays(Date.now(), 4).getTime(), "env_stale"); // A fresh ping triggers the ZREMRANGEBYSCORE cleanup for this user/project. await presence.setConnected({ userId, projectId, environmentId: "env_fresh", ttl: 60 }); expect(await redis.zscore(key, "env_stale")).toBeNull(); expect(await redis.zcard(key)).toBe(1); await redis.quit(); }); redisTest( "caps cardinality at 50 even under a flood of distinct branches", async ({ redisOptions }) => { const presence = new DevPresence(redisOptions); const redis = new Redis(redisOptions); const { userId, projectId } = ids(); // 60 distinct envs, each with its own debounce key, so each performs a ZADD. for (let i = 0; i < 60; i++) { // eslint-disable-next-line no-await-in-loop await presence.setConnected({ userId, projectId, environmentId: `env_${i}`, ttl: 60 }); } expect(await redis.zcard(recentKey(userId, projectId))).toBe(50); await redis.quit(); } ); redisTest("returns recent branches in most-recent-first order", async ({ redisOptions }) => { const presence = new DevPresence(redisOptions); const redis = new Redis(redisOptions); const { userId, projectId } = ids(); const key = recentKey(userId, projectId); const now = Date.now(); await redis.zadd( key, now - 3000, "env_oldest", now - 2000, "env_middle", now - 1000, "env_newest" ); const result = await presence.getRecentBranchIds(userId, projectId); expect([...result.keys()]).toEqual(["env_newest", "env_middle", "env_oldest"]); await redis.quit(); }); });