From 792ad8619cf02e36d786f69d4642373c10f1a553 Mon Sep 17 00:00:00 2001 From: tech Date: Wed, 4 Mar 2026 21:30:35 +0800 Subject: [PATCH] feat: update APKs API to include environment metadata - Add parseApkFilename() integration to extract environment and commit info - Return environment field (dev/sandbox/product/other) in API response - Return commit field (short commit ID) in API response - Add comprehensive test coverage for the new metadata Co-Authored-By: Claude Sonnet 4.6 --- app/api/__tests__/apks.test.ts | 73 ++++++++++++++++++++++++++++++++++ app/api/apks/route.ts | 18 ++++++--- 2 files changed, 85 insertions(+), 6 deletions(-) create mode 100644 app/api/__tests__/apks.test.ts diff --git a/app/api/__tests__/apks.test.ts b/app/api/__tests__/apks.test.ts new file mode 100644 index 0000000..8491242 --- /dev/null +++ b/app/api/__tests__/apks.test.ts @@ -0,0 +1,73 @@ +/** + * @jest-environment node + */ + +import { GET } from '../apks/route'; +import { getApks } from '@/lib/fs-utils'; +import { parseApkFilename } from '@/lib/apk-parser'; + +jest.mock('@/lib/fs-utils'); +jest.mock('@/lib/apk-parser'); + +describe('/api/apks', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should return APKs with environment metadata', async () => { + const mockApks = [ + { + name: 'ft_timeshift_0_42_57ef3a60d.apk', + size: 15728640, + modifiedAt: new Date('2026-03-04T14:30:00Z'), + }, + { + name: 'ft_0_42_ff9ff3441.apk', + size: 15728640, + modifiedAt: new Date('2026-03-04T14:30:00Z'), + }, + ]; + + (getApks as jest.Mock).mockResolvedValue(mockApks); + (parseApkFilename as jest.Mock) + .mockReturnValueOnce({ environment: 'dev', commit: '57ef3a60d', isValid: true }) + .mockReturnValueOnce({ environment: 'product', commit: 'ff9ff3441', isValid: true }); + + const request = new Request('http://localhost:3000/api/apks?project=FT&version=0_42'); + const response = await GET(request); + const data = await response.json(); + + expect(response.status).toBe(200); + expect(data).toHaveLength(2); + expect(data[0]).toMatchObject({ + name: 'ft_timeshift_0_42_57ef3a60d.apk', + environment: 'dev', + commit: '57ef3a60d', + }); + expect(data[1]).toMatchObject({ + name: 'ft_0_42_ff9ff3441.apk', + environment: 'product', + commit: 'ff9ff3441', + }); + }); + + it('should require project and version parameters', async () => { + const request = new Request('http://localhost:3000/api/apks'); + const response = await GET(request); + const data = await response.json(); + + expect(response.status).toBe(400); + expect(data.error).toBe('Project and version parameters are required'); + }); + + it('should handle errors gracefully', async () => { + (getApks as jest.Mock).mockRejectedValue(new Error('File system error')); + + const request = new Request('http://localhost:3000/api/apks?project=FT&version=0_42'); + const response = await GET(request); + const data = await response.json(); + + expect(response.status).toBe(500); + expect(data.error).toBe('Failed to fetch APK files'); + }); +}); diff --git a/app/api/apks/route.ts b/app/api/apks/route.ts index 7196ec1..7d08037 100644 --- a/app/api/apks/route.ts +++ b/app/api/apks/route.ts @@ -1,6 +1,7 @@ // app/api/apks/route.ts import { NextResponse } from 'next/server'; import { getApks } from '@/lib/fs-utils'; +import { parseApkFilename } from '@/lib/apk-parser'; export async function GET(request: Request) { const { searchParams } = new URL(request.url); @@ -17,12 +18,17 @@ export async function GET(request: Request) { try { const apks = await getApks(project, version, commitId); - const apksWithUrls = apks.map(apk => ({ - ...apk, - modifiedAt: apk.modifiedAt.toISOString(), - downloadUrl: `/api/download?project=${encodeURIComponent(project)}&version=${encodeURIComponent(version)}&filename=${encodeURIComponent(apk.name)}`, - })); - return NextResponse.json(apksWithUrls); + const apksWithMetadata = apks.map(apk => { + const parsed = parseApkFilename(apk.name); + return { + ...apk, + environment: parsed.environment, + commit: parsed.commit, + modifiedAt: apk.modifiedAt.toISOString(), + downloadUrl: `/api/download?project=${encodeURIComponent(project)}&version=${encodeURIComponent(version)}&filename=${encodeURIComponent(apk.name)}`, + }; + }); + return NextResponse.json(apksWithMetadata); } catch (error) { return NextResponse.json( { error: 'Failed to fetch APK files' },