From 6e0fe547208bb4c242279b27740399f3704121eb Mon Sep 17 00:00:00 2001 From: erio Date: Sat, 14 Mar 2026 01:26:34 +0800 Subject: [PATCH] =?UTF-8?q?test:=20=E8=A1=A5=E5=85=85=20platform-style=20?= =?UTF-8?q?=E5=92=8C=20listSubscriptions=20=E5=8D=95=E5=85=83=E6=B5=8B?= =?UTF-8?q?=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - platform-style: getPlatformStyle/PlatformBadge/PlatformIcon 共 17 个测试 - listSubscriptions: URL 拼接、响应解析、错误处理共 4 个测试 --- src/__tests__/lib/platform-style.test.ts | 70 ++++++++++++++++ .../sub2api/client-listSubscriptions.test.ts | 79 +++++++++++++++++++ 2 files changed, 149 insertions(+) create mode 100644 src/__tests__/lib/platform-style.test.ts create mode 100644 src/__tests__/lib/sub2api/client-listSubscriptions.test.ts diff --git a/src/__tests__/lib/platform-style.test.ts b/src/__tests__/lib/platform-style.test.ts new file mode 100644 index 0000000..33d7d5d --- /dev/null +++ b/src/__tests__/lib/platform-style.test.ts @@ -0,0 +1,70 @@ +import { describe, it, expect } from 'vitest'; +import { renderToStaticMarkup } from 'react-dom/server'; + +import { getPlatformStyle, PlatformBadge, PlatformIcon } from '@/lib/platform-style'; + +describe('getPlatformStyle', () => { + const knownPlatforms = ['claude', 'anthropic', 'openai', 'codex', 'gemini', 'google', 'sora', 'antigravity']; + + it.each(knownPlatforms)('should return correct label and non-empty icon for "%s"', (platform) => { + const style = getPlatformStyle(platform); + // label should be the capitalised form, not empty + expect(style.label).toBeTruthy(); + expect(style.icon).toBeTruthy(); + }); + + it('anthropic and claude should share the same badge style', () => { + const claude = getPlatformStyle('claude'); + const anthropic = getPlatformStyle('anthropic'); + expect(claude.badge).toBe(anthropic.badge); + }); + + it('openai and codex should share the same badge style', () => { + const openai = getPlatformStyle('openai'); + const codex = getPlatformStyle('codex'); + expect(openai.badge).toBe(codex.badge); + }); + + it('gemini and google should share the same badge style', () => { + const gemini = getPlatformStyle('gemini'); + const google = getPlatformStyle('google'); + expect(gemini.badge).toBe(google.badge); + }); + + it('should be case-insensitive ("OpenAI" and "openai" return same result)', () => { + const upper = getPlatformStyle('OpenAI'); + const lower = getPlatformStyle('openai'); + expect(upper).toEqual(lower); + }); + + it('should return fallback grey style for unknown platform', () => { + const style = getPlatformStyle('unknownService'); + expect(style.badge).toContain('slate'); + expect(style.label).toBe('unknownService'); + expect(style.icon).toBe(''); + }); +}); + +describe('PlatformBadge', () => { + it('should render output containing the correct label text', () => { + const html = renderToStaticMarkup(PlatformBadge({ platform: 'claude' })); + expect(html).toContain('Claude'); + }); + + it('should render fallback label for unknown platform', () => { + const html = renderToStaticMarkup(PlatformBadge({ platform: 'myPlatform' })); + expect(html).toContain('myPlatform'); + }); +}); + +describe('PlatformIcon', () => { + it('should return non-null for known platforms', () => { + const icon = PlatformIcon({ platform: 'openai' }); + expect(icon).not.toBeNull(); + }); + + it('should return null for unknown platform (empty icon)', () => { + const icon = PlatformIcon({ platform: 'unknownPlatform' }); + expect(icon).toBeNull(); + }); +}); diff --git a/src/__tests__/lib/sub2api/client-listSubscriptions.test.ts b/src/__tests__/lib/sub2api/client-listSubscriptions.test.ts new file mode 100644 index 0000000..f0dbfa6 --- /dev/null +++ b/src/__tests__/lib/sub2api/client-listSubscriptions.test.ts @@ -0,0 +1,79 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +vi.mock('@/lib/config', () => ({ + getEnv: () => ({ + SUB2API_BASE_URL: 'https://test.sub2api.com', + SUB2API_ADMIN_API_KEY: 'admin-testkey123', + }), +})); + +import { listSubscriptions } from '@/lib/sub2api/client'; + +describe('listSubscriptions', () => { + beforeEach(() => { + vi.restoreAllMocks(); + }); + + it('should call correct URL with no query params when no params provided', async () => { + global.fetch = vi.fn().mockResolvedValue({ + ok: true, + json: () => Promise.resolve({ data: [], total: 0, page: 1, page_size: 50 }), + }) as typeof fetch; + + await listSubscriptions(); + + const [url] = (fetch as ReturnType).mock.calls[0]; + // URL should end with "subscriptions?" and have no params after the ? + expect(url).toBe('https://test.sub2api.com/api/v1/admin/subscriptions?'); + }); + + it('should build correct query params when all params provided', async () => { + global.fetch = vi.fn().mockResolvedValue({ + ok: true, + json: () => Promise.resolve({ data: [], total: 0, page: 2, page_size: 10 }), + }) as typeof fetch; + + await listSubscriptions({ + user_id: 42, + group_id: 5, + status: 'active', + page: 2, + page_size: 10, + }); + + const [url] = (fetch as ReturnType).mock.calls[0]; + const parsedUrl = new URL(url); + expect(parsedUrl.searchParams.get('user_id')).toBe('42'); + expect(parsedUrl.searchParams.get('group_id')).toBe('5'); + expect(parsedUrl.searchParams.get('status')).toBe('active'); + expect(parsedUrl.searchParams.get('page')).toBe('2'); + expect(parsedUrl.searchParams.get('page_size')).toBe('10'); + }); + + it('should parse normal response correctly', async () => { + const mockSubs = [ + { id: 1, user_id: 42, group_id: 5, status: 'active', expires_at: '2026-12-31' }, + ]; + + global.fetch = vi.fn().mockResolvedValue({ + ok: true, + json: () => Promise.resolve({ data: mockSubs, total: 1, page: 1, page_size: 50 }), + }) as typeof fetch; + + const result = await listSubscriptions({ user_id: 42 }); + + expect(result.subscriptions).toEqual(mockSubs); + expect(result.total).toBe(1); + expect(result.page).toBe(1); + expect(result.page_size).toBe(50); + }); + + it('should throw on HTTP error', async () => { + global.fetch = vi.fn().mockResolvedValue({ + ok: false, + status: 500, + }) as typeof fetch; + + await expect(listSubscriptions()).rejects.toThrow('Failed to list subscriptions: 500'); + }); +});