import { Prisma } from '@prisma/client' const mockPositionFindMany = jest.fn() const mockSecurityFindMany = jest.fn() const mockExchangeRateFindMany = jest.fn() global.fetch = jest.fn() jest.mock('@/lib/prisma', () => ({ prisma: { position: { findMany: (...args: any[]) => mockPositionFindMany(...args), }, security: { findMany: (...args: any[]) => mockSecurityFindMany(...args), }, exchangeRate: { findMany: (...args: any[]) => mockExchangeRateFindMany(...args), }, }, })) import { GET } from '../src/app/api/dashboard/analytics/route' describe('Dashboard Analytics API - GET', () => { beforeEach(() => { jest.clearAllMocks() ;(global.fetch as jest.Mock).mockReset() }) it('无持仓返回空prices和quotes', async () => { mockPositionFindMany.mockResolvedValue([]) mockExchangeRateFindMany.mockResolvedValue([]) const req = new Request('http://localhost/api/dashboard/analytics') const res = await GET(req) expect(res.status).toBe(200) const data = await res.json() expect(data.prices).toEqual({}) expect(data.quotes).toEqual({}) }) it('计算持仓盈亏 - 盈利场景', async () => { const mockPositions = [ { id: 'pos-1', accountId: 'acc-1', symbol: 'AAPL', quantity: new Prisma.Decimal('10'), averageCost: new Prisma.Decimal('150'), currency: 'USD', account: { name: '美股', marketType: 'US' as const, baseCurrency: 'USD' }, }, ] const mockSecurities = [ { symbol: 'AAPL', name: 'Apple Inc', isCrypto: false }, ] mockPositionFindMany.mockResolvedValue(mockPositions) mockSecurityFindMany.mockResolvedValue(mockSecurities) mockExchangeRateFindMany.mockResolvedValue([]) ;(global.fetch as jest.Mock).mockResolvedValue({ ok: true, text: () => Promise.resolve('v_xxx="100~Apple Inc~AAPL~180.00~150.00~..."'), }) const req = new Request('http://localhost/api/dashboard/analytics') const res = await GET(req) expect(res.status).toBe(200) const data = await res.json() expect(data.positions).toHaveLength(1) expect(data.positions[0].pnl).toBe(300) expect(data.positions[0].pnlPercent).toBe(20) expect(data.positions[0].marketValue).toBe(1800) expect(data.positions[0].costBasis).toBe(1500) }) it('计算持仓盈亏 - 亏损场景', async () => { const mockPositions = [ { id: 'pos-1', accountId: 'acc-1', symbol: 'AAPL', quantity: new Prisma.Decimal('10'), averageCost: new Prisma.Decimal('200'), currency: 'USD', account: { name: '美股', marketType: 'US' as const, baseCurrency: 'USD' }, }, ] const mockSecurities = [{ symbol: 'AAPL', name: 'Apple Inc', isCrypto: false }] mockPositionFindMany.mockResolvedValue(mockPositions) mockSecurityFindMany.mockResolvedValue(mockSecurities) mockExchangeRateFindMany.mockResolvedValue([]) ;(global.fetch as jest.Mock).mockResolvedValue({ ok: true, text: () => Promise.resolve('v_xxx="100~Apple Inc~AAPL~150.00~200.00~..."'), }) const req = new Request('http://localhost/api/dashboard/analytics') const res = await GET(req) expect(res.status).toBe(200) const data = await res.json() expect(data.positions[0].pnl).toBe(-500) expect(data.positions[0].pnlPercent).toBe(-25) }) it('除以零防护 - 零成本基准', async () => { const mockPositions = [ { id: 'pos-1', accountId: 'acc-1', symbol: 'AAPL', quantity: new Prisma.Decimal('0'), averageCost: new Prisma.Decimal('0'), currency: 'USD', account: { name: '美股', marketType: 'US' as const, baseCurrency: 'USD' }, }, ] const mockSecurities = [{ symbol: 'AAPL', name: 'Apple Inc', isCrypto: false }] mockPositionFindMany.mockResolvedValue(mockPositions) mockSecurityFindMany.mockResolvedValue(mockSecurities) mockExchangeRateFindMany.mockResolvedValue([]) ;(global.fetch as jest.Mock).mockResolvedValue({ ok: true, text: () => Promise.resolve('v_xxx="100~Apple Inc~AAPL~150.00~0.00~..."'), }) const req = new Request('http://localhost/api/dashboard/analytics') const res = await GET(req) expect(res.status).toBe(200) const data = await res.json() expect(data.positions[0].pnlPercent).toBe(0) expect(data.summary.totalPnLPercent).toBe(0) }) it('腾讯行情API失败时降级到成本价', async () => { const mockPositions = [ { id: 'pos-1', accountId: 'acc-1', symbol: 'AAPL', quantity: new Prisma.Decimal('10'), averageCost: new Prisma.Decimal('150'), currency: 'USD', account: { name: '美股', marketType: 'US' as const, baseCurrency: 'USD' }, }, ] mockPositionFindMany.mockResolvedValue(mockPositions) mockSecurityFindMany.mockResolvedValue([{ symbol: 'AAPL', name: 'Apple Inc', isCrypto: false }]) mockExchangeRateFindMany.mockResolvedValue([]) ;(global.fetch as jest.Mock).mockRejectedValue(new Error('Network error')) const req = new Request('http://localhost/api/dashboard/analytics') const res = await GET(req) expect(res.status).toBe(200) const data = await res.json() expect(data.prices).toEqual({}) }) })