1import { WS_METHODS } from "@t3tools/contracts";
2import { afterEach, beforeEach, describe, expect, it, vi } from "vite-plus/test";
3
4import {
5 acknowledgeRpcRequest,
6 getSlowRpcAckRequests,
7 resetRequestLatencyStateForTests,
8 trackRpcRequestSent,
9 LONG_RUNNING_RPC_ACK_THRESHOLD_MS,
10 SLOW_RPC_ACK_THRESHOLD_MS,
11 MAX_TRACKED_RPC_ACK_REQUESTS,
12} from "./requestLatencyState";
13
14describe("requestLatencyState", () => {
15 beforeEach(() => {
16 vi.useFakeTimers();
17 resetRequestLatencyStateForTests();
18 });
19
20 afterEach(() => {
21 vi.useRealTimers();
22 });
23
24 it("marks unary requests as slow when the ack threshold is exceeded", () => {
25 trackRpcRequestSent("1", "server.getConfig");
26 vi.advanceTimersByTime(SLOW_RPC_ACK_THRESHOLD_MS - 1);
27 expect(getSlowRpcAckRequests()).toEqual([]);
28
29 vi.advanceTimersByTime(1);
30 expect(getSlowRpcAckRequests()).toMatchObject([
31 {
32 requestId: "1",
33 tag: "server.getConfig",
34 thresholdMs: SLOW_RPC_ACK_THRESHOLD_MS,
35 },
36 ]);
37 });
38
39 it("clears the slow request once the server acknowledges it", () => {
40 trackRpcRequestSent("1", "git.status");
41 vi.advanceTimersByTime(SLOW_RPC_ACK_THRESHOLD_MS);
42 expect(getSlowRpcAckRequests()).toHaveLength(1);
43
44 acknowledgeRpcRequest("1");
45 expect(getSlowRpcAckRequests()).toEqual([]);
46 });
47
48 it("ignores long-lived subscribe requests", () => {
49 trackRpcRequestSent("1", "subscribeServerConfig");
50 vi.advanceTimersByTime(SLOW_RPC_ACK_THRESHOLD_MS * 2);
51
52 expect(getSlowRpcAckRequests()).toEqual([]);
53 });
54
55 it("ignores usage summary requests", () => {
56 trackRpcRequestSent("1", WS_METHODS.serverGetUsageSummary);
57 vi.advanceTimersByTime(SLOW_RPC_ACK_THRESHOLD_MS * 2);
58
59 expect(getSlowRpcAckRequests()).toEqual([]);
60 });
61
62 it.each(Object.values(WS_METHODS).filter((method) => method.startsWith("pullRequests.")))(
63 "ignores pull request workspace request %s",
64 (method) => {
65 trackRpcRequestSent("1", method);
66 vi.advanceTimersByTime(SLOW_RPC_ACK_THRESHOLD_MS * 2);
67
68 expect(getSlowRpcAckRequests()).toEqual([]);
69 },
70 );
71
72 it("keeps ignoring untracked methods when a display tag is supplied", () => {
73 trackRpcRequestSent(
74 "1",
75 WS_METHODS.serverGetUsageSummary,
76 `${WS_METHODS.serverGetUsageSummary} · env-1`,
77 );
78 vi.advanceTimersByTime(SLOW_RPC_ACK_THRESHOLD_MS * 2);
79
80 expect(getSlowRpcAckRequests()).toEqual([]);
81 });
82
83 it("gives provider updates a longer threshold before warning", () => {
84 trackRpcRequestSent("1", WS_METHODS.serverUpdateProvider, "server.updateProvider · env-1");
85 vi.advanceTimersByTime(LONG_RUNNING_RPC_ACK_THRESHOLD_MS - 1);
86 expect(getSlowRpcAckRequests()).toEqual([]);
87
88 vi.advanceTimersByTime(1);
89 expect(getSlowRpcAckRequests()).toMatchObject([
90 {
91 requestId: "1",
92 tag: "server.updateProvider · env-1",
93 thresholdMs: LONG_RUNNING_RPC_ACK_THRESHOLD_MS,
94 },
95 ]);
96 });
97
98 it("evicts the oldest pending requests once the tracker reaches capacity", () => {
99 for (let index = 0; index < MAX_TRACKED_RPC_ACK_REQUESTS + 1; index += 1) {
100 trackRpcRequestSent(String(index), "server.getConfig");
101 }
102
103 vi.advanceTimersByTime(SLOW_RPC_ACK_THRESHOLD_MS);
104
105 const slowRequests = getSlowRpcAckRequests();
106 expect(slowRequests).toHaveLength(MAX_TRACKED_RPC_ACK_REQUESTS);
107 expect(slowRequests[0]?.requestId).toBe("1");
108 expect(slowRequests.at(-1)?.requestId).toBe(String(MAX_TRACKED_RPC_ACK_REQUESTS));
109 });
110});