From 4d5160811828832cd3288f6c016d59055dfd4fd5 Mon Sep 17 00:00:00 2001 From: Silo Server Migration Date: Mon, 25 May 2026 18:12:45 -0400 Subject: [PATCH] test(requests): document viewer-keyed cache isolation and invalidation cascade --- web/src/hooks/queries/useRequests.test.tsx | 28 ++++++++++++++++++++++ web/src/hooks/queries/useRequests.ts | 3 +++ 2 files changed, 31 insertions(+) diff --git a/web/src/hooks/queries/useRequests.test.tsx b/web/src/hooks/queries/useRequests.test.tsx index 5767e33a..a59213b6 100644 --- a/web/src/hooks/queries/useRequests.test.tsx +++ b/web/src/hooks/queries/useRequests.test.tsx @@ -2,6 +2,7 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; import { renderToStaticMarkup } from "react-dom/server"; import type { ReactNode } from "react"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { requestKeys } from "./keys"; const mocks = vi.hoisted(() => ({ useQuery: vi.fn(), @@ -108,3 +109,30 @@ describe("useRequestSearch", () => { expect(options.enabled).toBe(true); }); }); + +describe("requestKeys.all invalidation", () => { + it("invalidates entries under requestKeys.search() when invalidating requestKeys.all", async () => { + const client = new QueryClient(); + client.setQueryData(requestKeys.search("all", "dune", 1, "profile-1"), { sentinel: true }); + + expect(client.getQueryData(requestKeys.search("all", "dune", 1, "profile-1"))).toEqual({ + sentinel: true, + }); + + await client.invalidateQueries({ queryKey: requestKeys.all }); + + const state = client.getQueryState(requestKeys.search("all", "dune", 1, "profile-1")); + expect(state?.isInvalidated).toBe(true); + }); +}); + +describe("viewer-scoped cache isolation", () => { + it("does not return profile-1 results when keyed by profile-2", () => { + const client = new QueryClient(); + client.setQueryData(requestKeys.search("all", "dune", 1, "profile-1"), { + results: [{ tmdb_id: 1 }], + }); + + expect(client.getQueryData(requestKeys.search("all", "dune", 1, "profile-2"))).toBeUndefined(); + }); +}); diff --git a/web/src/hooks/queries/useRequests.ts b/web/src/hooks/queries/useRequests.ts index 4ba1bab9..a7b9ea14 100644 --- a/web/src/hooks/queries/useRequests.ts +++ b/web/src/hooks/queries/useRequests.ts @@ -53,6 +53,9 @@ function buildListQuery(params: RequestListParams = {}) { } function invalidateRequestSurfaces(queryClient: ReturnType) { + // requestKeys.all = ["requests"], so invalidating it cascades to nested keys, + // including requestKeys.search(...). Policy mutations rely on this to refresh + // viewer-scoped search results when request eligibility changes. queryClient.invalidateQueries({ queryKey: requestKeys.all }); queryClient.invalidateQueries({ queryKey: adminKeys.requestsRoot() }); }