diff --git a/src/app/api/upgrade-config/__tests__/route.test.ts b/src/app/api/upgrade-config/__tests__/route.test.ts index 73d359c..5a3aaf9 100644 --- a/src/app/api/upgrade-config/__tests__/route.test.ts +++ b/src/app/api/upgrade-config/__tests__/route.test.ts @@ -69,33 +69,15 @@ describe('GET /api/upgrade-config', () => { }); describe('path traversal prevention', () => { - it('returns 400 when network contains ".."', async () => { - const res = await GET(createRequest({ network: '..', upgradeId: 'test' })); - expect(res.status).toBe(400); - const body = await res.json(); - expect(body.error).toMatch(/invalid network/i); - }); - - it('returns 400 when network contains "/"', async () => { - const res = await GET(createRequest({ network: 'foo/bar', upgradeId: 'test' })); - expect(res.status).toBe(400); - const body = await res.json(); - expect(body.error).toMatch(/invalid network/i); - }); - - it('returns 400 when network contains spaces', async () => { - const res = await GET(createRequest({ network: 'main net', upgradeId: 'test' })); - expect(res.status).toBe(400); - const body = await res.json(); - expect(body.error).toMatch(/invalid network/i); - }); - - it('returns 400 when network contains "." (single dot)', async () => { - const res = await GET(createRequest({ network: '.', upgradeId: 'test' })); - expect(res.status).toBe(400); - const body = await res.json(); - expect(body.error).toMatch(/invalid network/i); - }); + it.each(['..', 'foo/bar', 'main net', '.'])( + 'returns 400 when network is "%s"', + async network => { + const res = await GET(createRequest({ network, upgradeId: 'test' })); + expect(res.status).toBe(400); + const body = await res.json(); + expect(body.error).toMatch(/invalid network/i); + } + ); it('returns 400 when upgradeId contains path separators', async () => { const res = await GET(createRequest({ network: 'mainnet', upgradeId: 'foo/bar' })); expect(res.status).toBe(400); @@ -132,6 +114,24 @@ describe('GET /api/upgrade-config', () => { expect(body.configOptions).toEqual([ { fileName: 'config1', displayName: 'Config1', ledgerId: 0 }, ]); + expect(console.warn).toHaveBeenCalledWith( + 'Failed to parse config1.json, using default ledgerId: 0' + ); + }); + + it('returns the default config option when reading fails', async () => { + const error = new Error('Read failed'); + mockReadFile.mockRejectedValue(error); + const res = await GET(createRequest({ network: 'mainnet', upgradeId: 'test' })); + expect(res.status).toBe(200); + const body = await res.json(); + expect(body.configOptions).toEqual([ + { fileName: 'config1', displayName: 'Config1', ledgerId: 0 }, + ]); + expect(console.warn).toHaveBeenCalledWith( + 'Error reading config1.json, using default ledgerId: 0', + error + ); }); it('returns 200 with empty array on ENOENT', async () => { diff --git a/src/app/api/upgrade-config/route.ts b/src/app/api/upgrade-config/route.ts index 9c13b14..ebd72cd 100644 --- a/src/app/api/upgrade-config/route.ts +++ b/src/app/api/upgrade-config/route.ts @@ -64,28 +64,21 @@ export async function GET(req: NextRequest) { const displayName = toDisplayName(baseName); const filePath = path.join(validationsPath, configFile); + let ledgerId = 0; try { const configContent = await fs.readFile(filePath, 'utf-8'); const parsedConfig = parseFromString(configContent); if (parsedConfig.result.success && 'config' in parsedConfig) { - return { - fileName: baseName, - displayName, - ledgerId: parsedConfig.config.ledgerId, - }; + ledgerId = parsedConfig.config.ledgerId; + } else { + console.warn(`Failed to parse ${configFile}, using default ledgerId: 0`); } - - console.warn(`Failed to parse ${configFile}, using default ledgerId: 0`); } catch (error) { console.warn(`Error reading ${configFile}, using default ledgerId: 0`, error); } - return { - fileName: baseName, - displayName, - ledgerId: 0, - }; + return { fileName: baseName, displayName, ledgerId }; }) ); diff --git a/src/app/api/upgrades/__tests__/route.test.ts b/src/app/api/upgrades/__tests__/route.test.ts index a223c86..707e1b1 100644 --- a/src/app/api/upgrades/__tests__/route.test.ts +++ b/src/app/api/upgrades/__tests__/route.test.ts @@ -22,21 +22,6 @@ function createRequest(params: Record): NextRequest { describe('GET /api/upgrades', () => { beforeEach(() => { jest.clearAllMocks(); - mockGetUpgradeOptions.mockImplementation((network: NetworkType) => { - if (network === NetworkType.Zeronet) { - return [ - { - id: '2025-08-01-upgrade-qux', - name: 'Upgrade Qux', - description: 'Zeronet upgrade', - date: '2025-08-01', - network: NetworkType.Zeronet, - status: TaskStatus.ReadyToSign, - }, - ]; - } - return []; - }); }); afterEach(() => { @@ -82,6 +67,7 @@ describe('GET /api/upgrades', () => { }); const res = GET(createRequest({ readyToSign: 'true' })); + expect(res.status).toBe(200); const body = await res.json(); expect(body.map((t: Upgrade) => t.id)).toEqual([ @@ -90,13 +76,13 @@ describe('GET /api/upgrades', () => { ]); }); - it('returns only ready-to-sign tasks tagged with the fetched network', async () => { + it('returns only ready-to-sign tasks', async () => { const readyTask: Upgrade = { id: '2025-08-01-upgrade-ready', name: 'Ready', description: '', date: '2025-08-01', - network: NetworkType.Sepolia, + network: NetworkType.Mainnet, status: TaskStatus.ReadyToSign, }; mockGetUpgradeOptions.mockImplementation((network: NetworkType) => @@ -110,23 +96,6 @@ describe('GET /api/upgrades', () => { const res = GET(createRequest({ readyToSign: 'true' })); - expect(await res.json()).toEqual([{ ...readyTask, network: NetworkType.Mainnet }]); - }); - - it('includes zeronet when aggregating ready-to-sign tasks', async () => { - const res = GET(createRequest({ readyToSign: 'true' })); - - expect(res.status).toBe(200); - - const body = await res.json(); - expect(body).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - id: '2025-08-01-upgrade-qux', - network: NetworkType.Zeronet, - status: TaskStatus.ReadyToSign, - }), - ]) - ); + expect(await res.json()).toEqual([readyTask]); }); }); diff --git a/src/app/api/upgrades/route.ts b/src/app/api/upgrades/route.ts index b2b90e9..59d6533 100644 --- a/src/app/api/upgrades/route.ts +++ b/src/app/api/upgrades/route.ts @@ -12,9 +12,7 @@ export function GET(req: NextRequest) { const allReadyToSignTasks = availableNetworks.flatMap(network => { const upgrades = getUpgradeOptions(network); - return upgrades - .filter(upgrade => upgrade.status === TaskStatus.ReadyToSign) - .map(upgrade => ({ ...upgrade, network })); + return upgrades.filter(upgrade => upgrade.status === TaskStatus.ReadyToSign); }); // Sort by id (newest first) across all networks; per-network lists are sorted independently.