diff --git a/app/api/streak/route.ts b/app/api/streak/route.ts index b4c88872a..2214b4cf7 100644 --- a/app/api/streak/route.ts +++ b/app/api/streak/route.ts @@ -126,9 +126,13 @@ export async function GET(request: Request) { timezone = new Intl.DateTimeFormat(undefined, { timeZone: tzParam }).resolvedOptions() .timeZone; } catch (error) { - if (error instanceof Error && error.name === 'ValidationError') { - return NextResponse.json({ error: error.message }, { status: 400 }); + if (error instanceof RangeError) { + // MINIMAL FIX: Translate native RangeErrors to named ValidationErrors so they cascade to the 400 SVG response catcher cleanly + const validationErr = new Error(`Invalid timezone: ${tzParam}`); + validationErr.name = 'ValidationError'; + throw validationErr; } + throw error; } } diff --git a/app/api/streak/tests/security-resilience.test.ts b/app/api/streak/tests/security-resilience.test.ts new file mode 100644 index 000000000..5416897cd --- /dev/null +++ b/app/api/streak/tests/security-resilience.test.ts @@ -0,0 +1,88 @@ +import { describe, expect, it, vi, beforeEach } from 'vitest'; +import { NextRequest } from 'next/server'; +import { middleware } from '../../../../middleware'; +import { GET } from '../route'; +import { streakParamsSchema } from '@/lib/validations'; + +// Stub core internal modules to prevent cascading unmocked downstream network fetches +vi.mock('@/lib/github', () => ({ + fetchGitHubContributions: vi.fn(() => + Promise.resolve({ + calendar: { totalContributions: 0, weeks: [] }, + isOfflineFallback: false, + }) + ), + getOrgDashboardData: vi.fn(), +})); + +describe('Streak Endpoint - Security & Timezone Resilience Architecture', () => { + beforeEach(() => { + vi.clearAllMocks(); + vi.stubGlobal('process', { + env: { + KV_REST_API_URL: '', // Keeps test execution isolated inside predictable in-memory fallbacks + KV_REST_API_TOKEN: '', + }, + }); + }); + + // Security Verification Path: Cache Bypass Sliding Window Bucket Rate Limiter Checks + it('enforces a strict rate-limiting barrier of 3 requests max per 10-minute window block for cache bypass queries', async () => { + const createBypassRequest = () => + new NextRequest('http://localhost:3000/api/streak?user=octocat&refresh=true', { + headers: { 'x-forwarded-for': '192.168.1.50' }, + }); + + const res1 = await middleware(createBypassRequest()); + expect(res1.status).toBeLessThan(400); + + const res2 = await middleware(createBypassRequest()); + expect(res2.status).toBeLessThan(400); + + const res3 = await middleware(createBypassRequest()); + expect(res3.status).toBeLessThan(400); + + const res4 = await middleware(createBypassRequest()); + expect(res4.status).toBe(429); + + const body = await res4.json(); + expect(body.error).toContain('Too many refresh requests'); + }); + + // Resilience Verification Path: Forced Timezone RangeError Exception Mapping Checks + it('safely intercepts native Intl RangeErrors and gracefully builds a structured 400 SVG card', async () => { + // 1. Clean Fix: Cast through unknown to satisfy both TS and strict ESLint any bans + const fakeParsedData = { + success: true, + data: { + user: 'octocat', + tz: 'ForcedTriggerTimezoneString', + }, + } as unknown as ReturnType; + + const zodSpy = vi.spyOn(streakParamsSchema, 'safeParse').mockReturnValue(fakeParsedData); + + // 2. Clean Fix: Use an ES5 constructor function cast through unknown to match the constructor type signature + const constructibleMock = function () { + throw new RangeError('unsupported time zone'); + } as unknown as typeof Intl.DateTimeFormat; + + const dateTimeFormatSpy = vi + .spyOn(Intl, 'DateTimeFormat') + .mockImplementation(constructibleMock); + + const dummyRequest = new Request('http://localhost:3000/api/streak?user=octocat&tz=UTC'); + const response = await GET(dummyRequest); + + // Clean up spy instances immediately to preserve environment state + dateTimeFormatSpy.mockRestore(); + zodSpy.mockRestore(); + + // Asserts exception code maps successfully to a 400 client error state instead of a 500 or JSON error + expect(response.status).toBe(400); + + const svgContent = await response.text(); + expect(svgContent).toContain('svg'); + expect(svgContent).toContain('Invalid timezone'); + }); +}); diff --git a/middleware.ts b/middleware.ts index a14616778..a29e7420e 100644 --- a/middleware.ts +++ b/middleware.ts @@ -19,7 +19,8 @@ export async function middleware(request: NextRequest): Promise { request.nextUrl.searchParams.get('bypassCache') === 'true'; if (isRefresh) { - const refreshResult = await rateLimit(`refresh:${ip}`, 5, 60000); + // MINIMAL FIX: Pass strict windows straight into the existing multi-bucket rate limiter engine + const refreshResult = await rateLimit(`refresh:${ip}`, 3, 600000); if (!refreshResult.success) { const resp = NextResponse.json( diff --git a/package-lock.json b/package-lock.json index b3bc3ed3f..580374fb4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,14 +20,11 @@ "jspdf": "^4.2.1", "lightningcss": "^1.32.0", "lucide-react": "^1.17.0", - "mammoth": "^1.12.0", "mongodb": "^7.2.0", "mongoose": "^9.6.2", "next": "^16.2.3", - "pdf-parse": "^2.4.5", "react": "19.2.4", "react-dom": "19.2.4", - "react-error-boundary": "^6.1.2", "react-force-graph-2d": "^1.29.1", "react-hot-toast": "^2.6.0", "react-icons": "^5.6.0", @@ -60,9 +57,12 @@ "husky": "^9.1.7", "jsdom": "^29.1.1", "lint-staged": "^15.2.11", + "mammoth": "^1.12.0", "node-mocks-http": "^1.17.2", + "pdf-parse": "^2.4.5", "postcss": "^8.5.9", "prettier": "^3.8.3", + "react-error-boundary": "^6.1.2", "react-is": "^19.2.6", "tailwindcss": "^4.2.2", "tsx": "^4.22.2", @@ -1794,6 +1794,7 @@ "version": "0.1.80", "resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-0.1.80.tgz", "integrity": "sha512-DxuT1ClnIPts1kQx8FBmkk4BQDTfI5kIzywAaMjQSXfNnra5UFU9PwurXrl+Je3bJ6BGsp/zmshVVFbCmyI+ww==", + "dev": true, "license": "MIT", "workspaces": [ "e2e/*" @@ -1821,6 +1822,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1837,6 +1839,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1853,6 +1856,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1869,6 +1873,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1885,9 +1890,7 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1904,9 +1907,7 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1923,9 +1924,7 @@ "cpu": [ "riscv64" ], - "libc": [ - "glibc" - ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1942,9 +1941,7 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1961,9 +1958,7 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1980,6 +1975,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3282,7 +3278,7 @@ "version": "19.2.16", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.16.tgz", "integrity": "sha512-esJiCAnl0kfpNdE69f3So4WJUXy95dLZydX0KwK46riIHDzHM7O9Vtf9xCHW0PXIqvgqNrswl522kA/5yx+F4w==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "csstype": "^3.2.2" @@ -4167,6 +4163,7 @@ "version": "0.8.13", "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.13.tgz", "integrity": "sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw==", + "dev": true, "license": "MIT", "engines": { "node": ">=10.0.0" @@ -4593,6 +4590,7 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, "funding": [ { "type": "github", @@ -4645,6 +4643,7 @@ "version": "3.4.7", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", "integrity": "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==", + "dev": true, "license": "MIT" }, "node_modules/brace-expansion": { @@ -4981,6 +4980,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, "license": "MIT" }, "node_modules/cross-spawn": { @@ -5453,6 +5453,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/dingbat-to-unicode/-/dingbat-to-unicode-1.0.1.tgz", "integrity": "sha512-98l0sW87ZT58pU4i61wa2OHwxbiYSbuxsCBozaVnYX2iCnr3bLM3fIes1/ej7h1YdOKuKt/MLs706TVnALA65w==", + "dev": true, "license": "BSD-2-Clause" }, "node_modules/doctrine": { @@ -5488,6 +5489,7 @@ "version": "0.1.12", "resolved": "https://registry.npmjs.org/duck/-/duck-0.1.12.tgz", "integrity": "sha512-wkctla1O6VfP89gQ+J/yDesM0S7B7XLXjKGzXxMDVFg7uEn706niAtyYovKbyq1oT9YwDcly721/iUWoc8MVRg==", + "dev": true, "license": "BSD", "dependencies": { "underscore": "^1.13.1" @@ -6990,6 +6992,7 @@ "version": "3.0.6", "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "dev": true, "license": "MIT" }, "node_modules/immer": { @@ -7052,6 +7055,7 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, "license": "ISC" }, "node_modules/internal-slot": { @@ -7785,6 +7789,7 @@ "version": "3.10.1", "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "dev": true, "license": "(MIT OR GPL-3.0-or-later)", "dependencies": { "lie": "~3.3.0", @@ -7797,6 +7802,7 @@ "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true, "license": "(MIT AND Zlib)" }, "node_modules/kapsule": { @@ -7868,6 +7874,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dev": true, "license": "MIT", "dependencies": { "immediate": "~3.0.5" @@ -8305,6 +8312,7 @@ "version": "0.4.2", "resolved": "https://registry.npmjs.org/lop/-/lop-0.4.2.tgz", "integrity": "sha512-RefILVDQ4DKoRZsJ4Pj22TxE3omDO47yFpkIBoDKzkqPRISs5U1cnAdg/5583YPkWPaLIYHOKRMQSvjFsO26cw==", + "dev": true, "license": "BSD-2-Clause", "dependencies": { "duck": "^0.1.12", @@ -8395,6 +8403,7 @@ "version": "1.12.0", "resolved": "https://registry.npmjs.org/mammoth/-/mammoth-1.12.0.tgz", "integrity": "sha512-cwnK1RIcRdDMi2HRx2EXGYlxqIEh0Oo3bLhorgnsVJi2UkbX1+jKxuBNR9PC5+JaX7EkmJxFPmo6mjLpqShI2w==", + "dev": true, "license": "BSD-2-Clause", "dependencies": { "@xmldom/xmldom": "^0.8.6", @@ -8419,6 +8428,7 @@ "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, "license": "MIT", "dependencies": { "sprintf-js": "~1.0.2" @@ -9146,6 +9156,7 @@ "version": "0.2.4", "resolved": "https://registry.npmjs.org/option/-/option-0.2.4.tgz", "integrity": "sha512-pkEqbDyl8ou5cpq+VsnQbe/WlEy5qS7xPzMS1U55OCG9KPvwFD46zDbxQIj3egJSFc3D+XhYOPUzz49zQAVy7A==", + "dev": true, "license": "BSD-2-Clause" }, "node_modules/optionator": { @@ -9272,6 +9283,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -9305,6 +9317,7 @@ "version": "2.4.5", "resolved": "https://registry.npmjs.org/pdf-parse/-/pdf-parse-2.4.5.tgz", "integrity": "sha512-mHU89HGh7v+4u2ubfnevJ03lmPgQ5WU4CxAVmTSh/sxVTEDYd1er/dKS/A6vg77NX47KTEoihq8jZBLr8Cxuwg==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@napi-rs/canvas": "0.1.80", @@ -9325,6 +9338,7 @@ "version": "5.4.296", "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-5.4.296.tgz", "integrity": "sha512-DlOzet0HO7OEnmUmB6wWGJrrdvbyJKftI1bhMitK7O2N8W2gc757yyYBbINy9IDafXAV9wmKr9t7xsTaNKRG5Q==", + "dev": true, "license": "Apache-2.0", "engines": { "node": ">=20.16.0 || >=22.3.0" @@ -9492,6 +9506,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, "license": "MIT" }, "node_modules/prop-types": { @@ -9592,6 +9607,7 @@ "version": "6.1.2", "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-6.1.2.tgz", "integrity": "sha512-3DpCr5HVdZ0caUjYE/kIHBEJN0mNP3ZCgf16c48uJ5TbWjorKVp+YG8W3XqlJ7vJAVNw6wNIImyPXmFydwmyng==", + "dev": true, "license": "MIT", "peerDependencies": { "react": "^18.0.0 || ^19.0.0" @@ -9641,11 +9657,6 @@ } }, "node_modules/react-is": { - - "version": "19.2.7", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.7.tgz", - "integrity": "sha512-kZFnouyVv7eP/Phmrlo9FK+zcAdriZJvzxXHF1Sl1P377WSGe2G/JxVolhTrB/jeV47lKImhNUsijjHAAbcl/A==", - "dev": true, "version": "19.2.6", "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.6.tgz", "integrity": "sha512-XjBR15BhXuylgWGuslhDKqlSayuqvqBX91BP8pauG8kd1zY8kotkNWbXksTCNRarse4kuGbe2kIY05ARtwNIvw==", @@ -9706,6 +9717,7 @@ "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, "license": "MIT", "dependencies": { "core-util-is": "~1.0.0", @@ -9721,12 +9733,14 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, "license": "MIT" }, "node_modules/readable-stream/node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, "license": "MIT" }, "node_modules/recharts": { @@ -10199,6 +10213,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "dev": true, "license": "MIT" }, "node_modules/sharp": { @@ -10461,6 +10476,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, "license": "BSD-3-Clause" }, "node_modules/stable-hash": { @@ -10512,6 +10528,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, "license": "MIT", "dependencies": { "safe-buffer": "~5.1.0" @@ -10521,6 +10538,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, "license": "MIT" }, "node_modules/string-argv": { @@ -11229,6 +11247,7 @@ "version": "1.13.8", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.8.tgz", "integrity": "sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ==", + "dev": true, "license": "MIT" }, "node_modules/undici": { @@ -11340,6 +11359,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, "license": "MIT" }, "node_modules/utrie": { @@ -11792,6 +11812,7 @@ "version": "10.1.1", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-10.1.1.tgz", "integrity": "sha512-OyzrcFLL/nb6fMGHbiRDuPup9ljBycsdCypwuyg5AAHvyWzGfChJpCXMG88AGTIMFhGZ9RccFN1e6lhg3hkwKg==", + "dev": true, "license": "MIT", "engines": { "node": ">=4.0" diff --git a/package.json b/package.json index 00280fa1c..22f3c2a72 100644 --- a/package.json +++ b/package.json @@ -36,14 +36,11 @@ "jspdf": "^4.2.1", "lightningcss": "^1.32.0", "lucide-react": "^1.17.0", - "mammoth": "^1.12.0", "mongodb": "^7.2.0", "mongoose": "^9.6.2", "next": "^16.2.3", - "pdf-parse": "^2.4.5", "react": "19.2.4", "react-dom": "19.2.4", - "react-error-boundary": "^6.1.2", "react-force-graph-2d": "^1.29.1", "react-hot-toast": "^2.6.0", "react-icons": "^5.6.0", @@ -76,9 +73,12 @@ "husky": "^9.1.7", "jsdom": "^29.1.1", "lint-staged": "^15.2.11", + "mammoth": "^1.12.0", "node-mocks-http": "^1.17.2", + "pdf-parse": "^2.4.5", "postcss": "^8.5.9", "prettier": "^3.8.3", + "react-error-boundary": "^6.1.2", "react-is": "^19.2.6", "tailwindcss": "^4.2.2", "tsx": "^4.22.2", diff --git a/proxy.test.ts b/proxy.test.ts index ba839b6fa..7f0ea7a25 100644 --- a/proxy.test.ts +++ b/proxy.test.ts @@ -270,7 +270,7 @@ describe('proxy', () => { const request = new NextRequest('http://localhost:3000/api/streak?user=octocat&refresh=true'); await proxy(request); - expect(rateLimit).toHaveBeenNthCalledWith(1, 'refresh:127.0.0.1', 5, 60000); + expect(rateLimit).toHaveBeenNthCalledWith(1, 'refresh:127.0.0.1', 3, 600000); expect(rateLimit).toHaveBeenNthCalledWith(2, '127.0.0.1', 60, 60000); });