user-all-limit-window.test.ts 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. /**
  2. * user-all-limit-window tests
  3. *
  4. * Verify getUserAllLimitUsage correctly uses getTimeRangeForPeriodWithMode for daily window,
  5. * respecting user.dailyResetMode configuration.
  6. *
  7. * Rolling mode: past 24 hours window
  8. * Fixed mode: since reset time window
  9. */
  10. import { beforeEach, describe, expect, it, vi } from "vitest";
  11. // Mock functions
  12. const getSessionMock = vi.fn();
  13. const findUserByIdMock = vi.fn();
  14. const getTimeRangeForPeriodMock = vi.fn();
  15. const getTimeRangeForPeriodWithModeMock = vi.fn();
  16. const sumUserCostInTimeRangeMock = vi.fn();
  17. const sumUserTotalCostMock = vi.fn();
  18. // Mock modules
  19. vi.mock("@/lib/auth", () => ({
  20. getSession: () => getSessionMock(),
  21. }));
  22. vi.mock("@/repository/user", () => ({
  23. findUserById: (...args: unknown[]) => findUserByIdMock(...args),
  24. }));
  25. vi.mock("@/lib/rate-limit/time-utils", () => ({
  26. getTimeRangeForPeriod: (...args: unknown[]) => getTimeRangeForPeriodMock(...args),
  27. getTimeRangeForPeriodWithMode: (...args: unknown[]) => getTimeRangeForPeriodWithModeMock(...args),
  28. }));
  29. vi.mock("@/repository/statistics", () => ({
  30. sumUserCostInTimeRange: (...args: unknown[]) => sumUserCostInTimeRangeMock(...args),
  31. sumUserTotalCost: (...args: unknown[]) => sumUserTotalCostMock(...args),
  32. }));
  33. vi.mock("@/lib/logger", () => ({
  34. logger: {
  35. trace: vi.fn(),
  36. debug: vi.fn(),
  37. info: vi.fn(),
  38. warn: vi.fn(),
  39. error: vi.fn(),
  40. },
  41. }));
  42. vi.mock("next-intl/server", () => ({
  43. getTranslations: vi.fn(() => async (key: string) => key),
  44. getLocale: vi.fn(() => "en"),
  45. }));
  46. vi.mock("next/cache", () => ({
  47. revalidatePath: vi.fn(),
  48. }));
  49. describe("getUserAllLimitUsage - daily window mode handling", () => {
  50. const now = new Date("2024-06-15T12:00:00.000Z");
  51. const past24h = new Date("2024-06-14T12:00:00.000Z");
  52. const fixedReset = new Date("2024-06-15T00:00:00.000Z");
  53. beforeEach(() => {
  54. vi.clearAllMocks();
  55. vi.useFakeTimers();
  56. vi.setSystemTime(now);
  57. // Default: admin session
  58. getSessionMock.mockResolvedValue({
  59. user: { id: 1, role: "admin" },
  60. key: { id: 1 },
  61. });
  62. // Default time range mocks
  63. getTimeRangeForPeriodMock.mockImplementation(async (period: string) => {
  64. switch (period) {
  65. case "5h":
  66. return { startTime: new Date(now.getTime() - 5 * 60 * 60 * 1000), endTime: now };
  67. case "weekly":
  68. return { startTime: new Date("2024-06-10T00:00:00.000Z"), endTime: now };
  69. case "monthly":
  70. return { startTime: new Date("2024-06-01T00:00:00.000Z"), endTime: now };
  71. default:
  72. return { startTime: fixedReset, endTime: now };
  73. }
  74. });
  75. getTimeRangeForPeriodWithModeMock.mockImplementation(
  76. async (period: string, _resetTime: string, mode: string) => {
  77. if (period === "daily" && mode === "rolling") {
  78. return { startTime: past24h, endTime: now };
  79. }
  80. // fixed mode
  81. return { startTime: fixedReset, endTime: now };
  82. }
  83. );
  84. // Default cost mocks
  85. sumUserCostInTimeRangeMock.mockResolvedValue(1.0);
  86. sumUserTotalCostMock.mockResolvedValue(10.0);
  87. });
  88. afterEach(() => {
  89. vi.useRealTimers();
  90. });
  91. it("should use rolling mode (past 24h) when user.dailyResetMode is rolling", async () => {
  92. // User with rolling mode
  93. findUserByIdMock.mockResolvedValue({
  94. id: 1,
  95. name: "Test User",
  96. dailyResetMode: "rolling",
  97. dailyResetTime: "00:00",
  98. dailyQuota: 10,
  99. limit5hUsd: null,
  100. limitWeeklyUsd: null,
  101. limitMonthlyUsd: null,
  102. limitTotalUsd: null,
  103. });
  104. const { getUserAllLimitUsage } = await import("@/actions/users");
  105. const result = await getUserAllLimitUsage(1);
  106. expect(result.ok).toBe(true);
  107. // Verify getTimeRangeForPeriodWithMode was called with rolling mode
  108. expect(getTimeRangeForPeriodWithModeMock).toHaveBeenCalledWith("daily", "00:00", "rolling");
  109. });
  110. it("should use fixed mode when user.dailyResetMode is fixed", async () => {
  111. // User with fixed mode
  112. findUserByIdMock.mockResolvedValue({
  113. id: 1,
  114. name: "Test User",
  115. dailyResetMode: "fixed",
  116. dailyResetTime: "18:00",
  117. dailyQuota: 10,
  118. limit5hUsd: null,
  119. limitWeeklyUsd: null,
  120. limitMonthlyUsd: null,
  121. limitTotalUsd: null,
  122. });
  123. const { getUserAllLimitUsage } = await import("@/actions/users");
  124. const result = await getUserAllLimitUsage(1);
  125. expect(result.ok).toBe(true);
  126. // Verify getTimeRangeForPeriodWithMode was called with fixed mode and custom reset time
  127. expect(getTimeRangeForPeriodWithModeMock).toHaveBeenCalledWith("daily", "18:00", "fixed");
  128. });
  129. it("should default to fixed mode when dailyResetMode is not set", async () => {
  130. // User without explicit dailyResetMode (defaults to fixed)
  131. findUserByIdMock.mockResolvedValue({
  132. id: 1,
  133. name: "Test User",
  134. dailyResetMode: undefined, // or null
  135. dailyResetTime: "00:00",
  136. dailyQuota: 10,
  137. limit5hUsd: null,
  138. limitWeeklyUsd: null,
  139. limitMonthlyUsd: null,
  140. limitTotalUsd: null,
  141. });
  142. const { getUserAllLimitUsage } = await import("@/actions/users");
  143. const result = await getUserAllLimitUsage(1);
  144. expect(result.ok).toBe(true);
  145. // Should default to fixed mode
  146. expect(getTimeRangeForPeriodWithModeMock).toHaveBeenCalledWith("daily", "00:00", "fixed");
  147. });
  148. it("should pass correct dailyResetTime from user config", async () => {
  149. findUserByIdMock.mockResolvedValue({
  150. id: 1,
  151. name: "Test User",
  152. dailyResetMode: "fixed",
  153. dailyResetTime: "09:30", // Custom reset time
  154. dailyQuota: 10,
  155. limit5hUsd: null,
  156. limitWeeklyUsd: null,
  157. limitMonthlyUsd: null,
  158. limitTotalUsd: null,
  159. });
  160. const { getUserAllLimitUsage } = await import("@/actions/users");
  161. await getUserAllLimitUsage(1);
  162. // Verify custom reset time is passed
  163. expect(getTimeRangeForPeriodWithModeMock).toHaveBeenCalledWith("daily", "09:30", "fixed");
  164. });
  165. it("should default to 00:00 when dailyResetTime is not set", async () => {
  166. findUserByIdMock.mockResolvedValue({
  167. id: 1,
  168. name: "Test User",
  169. dailyResetMode: "fixed",
  170. dailyResetTime: undefined, // or null
  171. dailyQuota: 10,
  172. limit5hUsd: null,
  173. limitWeeklyUsd: null,
  174. limitMonthlyUsd: null,
  175. limitTotalUsd: null,
  176. });
  177. const { getUserAllLimitUsage } = await import("@/actions/users");
  178. await getUserAllLimitUsage(1);
  179. // Should default to "00:00"
  180. expect(getTimeRangeForPeriodWithModeMock).toHaveBeenCalledWith("daily", "00:00", "fixed");
  181. });
  182. it("should NOT use getTimeRangeForPeriod for daily (consistency with getUserLimitUsage)", async () => {
  183. findUserByIdMock.mockResolvedValue({
  184. id: 1,
  185. name: "Test User",
  186. dailyResetMode: "rolling",
  187. dailyResetTime: "00:00",
  188. dailyQuota: 10,
  189. limit5hUsd: null,
  190. limitWeeklyUsd: null,
  191. limitMonthlyUsd: null,
  192. limitTotalUsd: null,
  193. });
  194. const { getUserAllLimitUsage } = await import("@/actions/users");
  195. await getUserAllLimitUsage(1);
  196. // getTimeRangeForPeriod should only be called for 5h, weekly, monthly - NOT daily
  197. const dailyCalls = getTimeRangeForPeriodMock.mock.calls.filter((call) => call[0] === "daily");
  198. expect(dailyCalls).toHaveLength(0);
  199. });
  200. it("should still use getTimeRangeForPeriod for non-daily periods", async () => {
  201. findUserByIdMock.mockResolvedValue({
  202. id: 1,
  203. name: "Test User",
  204. dailyResetMode: "fixed",
  205. dailyResetTime: "00:00",
  206. dailyQuota: 10,
  207. limit5hUsd: 5,
  208. limitWeeklyUsd: 50,
  209. limitMonthlyUsd: 200,
  210. limitTotalUsd: null,
  211. });
  212. const { getUserAllLimitUsage } = await import("@/actions/users");
  213. await getUserAllLimitUsage(1);
  214. // Verify other periods still use getTimeRangeForPeriod
  215. expect(getTimeRangeForPeriodMock).toHaveBeenCalledWith("5h");
  216. expect(getTimeRangeForPeriodMock).toHaveBeenCalledWith("weekly");
  217. expect(getTimeRangeForPeriodMock).toHaveBeenCalledWith("monthly");
  218. });
  219. });
  220. describe("getUserAllLimitUsage - consistency with key-quota.ts", () => {
  221. const now = new Date("2024-06-15T12:00:00.000Z");
  222. beforeEach(() => {
  223. vi.clearAllMocks();
  224. vi.useFakeTimers();
  225. vi.setSystemTime(now);
  226. getSessionMock.mockResolvedValue({
  227. user: { id: 1, role: "admin" },
  228. key: { id: 1 },
  229. });
  230. getTimeRangeForPeriodMock.mockImplementation(async (period: string) => {
  231. switch (period) {
  232. case "5h":
  233. return { startTime: new Date(now.getTime() - 5 * 60 * 60 * 1000), endTime: now };
  234. case "weekly":
  235. return { startTime: new Date("2024-06-10T00:00:00.000Z"), endTime: now };
  236. case "monthly":
  237. return { startTime: new Date("2024-06-01T00:00:00.000Z"), endTime: now };
  238. default:
  239. return { startTime: new Date("2024-06-15T00:00:00.000Z"), endTime: now };
  240. }
  241. });
  242. getTimeRangeForPeriodWithModeMock.mockResolvedValue({
  243. startTime: new Date("2024-06-15T00:00:00.000Z"),
  244. endTime: now,
  245. });
  246. sumUserCostInTimeRangeMock.mockResolvedValue(1.0);
  247. sumUserTotalCostMock.mockResolvedValue(10.0);
  248. });
  249. afterEach(() => {
  250. vi.useRealTimers();
  251. });
  252. it("should match key-quota.ts pattern: getTimeRangeForPeriodWithMode for daily", async () => {
  253. // key-quota.ts line 91-95:
  254. // const keyDailyTimeRange = await getTimeRangeForPeriodWithMode(
  255. // "daily",
  256. // keyRow.dailyResetTime ?? "00:00",
  257. // (keyRow.dailyResetMode as DailyResetMode | undefined) ?? "fixed"
  258. // );
  259. findUserByIdMock.mockResolvedValue({
  260. id: 1,
  261. name: "Test User",
  262. dailyResetMode: "rolling",
  263. dailyResetTime: "12:00",
  264. dailyQuota: 10,
  265. limit5hUsd: null,
  266. limitWeeklyUsd: null,
  267. limitMonthlyUsd: null,
  268. limitTotalUsd: null,
  269. });
  270. const { getUserAllLimitUsage } = await import("@/actions/users");
  271. await getUserAllLimitUsage(1);
  272. // Should use getTimeRangeForPeriodWithMode matching key-quota.ts pattern
  273. expect(getTimeRangeForPeriodWithModeMock).toHaveBeenCalledWith("daily", "12:00", "rolling");
  274. });
  275. });