diff --git a/src/backend/database/routes/operations-routes.ts b/src/backend/database/routes/operations-routes.ts index 09ca3f553..56034d08c 100644 --- a/src/backend/database/routes/operations-routes.ts +++ b/src/backend/database/routes/operations-routes.ts @@ -622,6 +622,7 @@ router.get("/signals", authenticateJWT, async (req, res) => { userId: authReq.userId, actionId: signal.read, hostIds, + freshMs: signal.refreshSec ? signal.refreshSec * 1000 : undefined, origin: originOf(authReq), }), }); diff --git a/src/backend/operations/resources.ts b/src/backend/operations/resources.ts index 0e5340f2c..52a390d55 100644 --- a/src/backend/operations/resources.ts +++ b/src/backend/operations/resources.ts @@ -78,6 +78,8 @@ interface ReadOptions { actionId: string; hostIds?: number[]; refresh?: boolean; + /** How long a cached answer stays fresh. Defaults to RESOURCE_FRESH_MS. */ + freshMs?: number; input?: Record; origin: ActorOrigin; } @@ -155,7 +157,9 @@ export async function readAcrossHosts( const key = cacheKey(options.userId, options.actionId, hostId); const cached = cache.get(key); - const fresh = cached && Date.now() - cached.fetchedAt < RESOURCE_FRESH_MS; + const fresh = + cached && + Date.now() - cached.fetchedAt < (options.freshMs ?? RESOURCE_FRESH_MS); if (isOffline(hostId)) { reads.push({ diff --git a/src/backend/tests/database/routes/operations-routes.test.ts b/src/backend/tests/database/routes/operations-routes.test.ts index 158302370..a56511803 100644 --- a/src/backend/tests/database/routes/operations-routes.test.ts +++ b/src/backend/tests/database/routes/operations-routes.test.ts @@ -275,3 +275,27 @@ describe("GET /operations/catalog", () => { expect(response.body.actions).toEqual([{ id: "demo.restart" }]); }); }); + +describe("GET /operations/signals", () => { + it("keeps a signal's answer for its refreshSec", async () => { + const { buildCatalog } = await import("../../../operations/catalog.js"); + const { readAcrossHosts } = + await import("../../../operations/resources.js"); + vi.mocked(buildCatalog).mockResolvedValueOnce({ + actions: [], + events: [], + resources: [], + signals: [ + { id: "a", read: "demo.a", scope: "host", refreshSec: 600 }, + { id: "b", read: "demo.b", scope: "host" }, + ], + } as never); + await request(app).get("/operations/signals").expect(200); + expect(vi.mocked(readAcrossHosts)).toHaveBeenCalledWith( + expect.objectContaining({ actionId: "demo.a", freshMs: 600_000 }), + ); + expect(vi.mocked(readAcrossHosts)).toHaveBeenCalledWith( + expect.objectContaining({ actionId: "demo.b", freshMs: undefined }), + ); + }); +});