import { closeElectronApp, expect, getStableWindow, test } from './fixtures/electron'; test.describe('Plugin Marketplace', () => { test('opens the global catalog, searches, and reads detail through fixed Main routes', async ({ launchElectronApp }) => { const app = await launchElectronApp({ skipSetup: true }); try { const page = await getStableWindow(app); await page.getByTestId('ai-module-option-programming').click(); await app.evaluate(({ ipcMain }) => { const requests: string[] = []; (globalThis as typeof globalThis & { __marketplaceE2E?: { requests: string[] } }).__marketplaceE2E = { requests }; ipcMain.removeHandler('hostapi:fetch'); ipcMain.handle('hostapi:fetch', async (_event, request: { path?: string }) => { const requestPath = request.path ?? ''; requests.push(requestPath); const item = { pluginId: 'makelore.notes', title: '灵感笔记', summary: '整理项目灵感。', category: '效率', tags: ['笔记'], providerDisplayName: 'MakeLore', runtimeKind: 'skill_only', runtimeStatus: 'enabled', acquisition: 'free', usageBilling: 'included', includedOperationCount: 1, meteredOperationCount: 0, stableVersion: '1.0.0', betaVersion: null, }; if (requestPath.startsWith('/api/coding/plugin-marketplace/catalog')) { return { ok: true, data: { status: 200, ok: true, json: { items: [item], nextCursor: null, total: 1, catalogGeneration: 1, etag: '"plugins-1-tp-none"', pricingVersionId: null, stale: false, fetchedAt: 1, } } }; } if (requestPath === '/api/coding/plugin-marketplace/plugins/makelore.notes') { return { ok: true, data: { status: 200, ok: true, json: { ...item, descriptionMarkdown: '详细介绍', permissions: ['读取项目名称'], operations: [], stableRelease: { releaseId: 'release-1', pluginId: 'makelore.notes', version: '1.0.0' }, betaRelease: null, etag: '"plugins-1-tp-none"', pricingVersionId: null, stale: false, fetchedAt: 1, } } }; } return { ok: false, error: { message: `Unexpected Host API request: ${requestPath}` } }; }); }); await page.getByTestId('sidebar-nav-plugin-marketplace').click(); await expect(page.getByTestId('plugin-marketplace-page')).toBeVisible(); await expect(page.getByRole('heading', { name: '灵感笔记' })).toBeVisible(); await page.getByRole('searchbox', { name: '搜索插件' }).fill('笔记'); await page.getByRole('button', { name: '搜索', exact: true }).click(); await page.getByRole('button', { name: '查看详情' }).click(); await expect(page.getByRole('heading', { name: '灵感笔记' }).last()).toBeVisible(); await expect(page.getByText('详细介绍')).toBeVisible(); const requests = await app.evaluate(() => ( (globalThis as typeof globalThis & { __marketplaceE2E?: { requests: string[] } }).__marketplaceE2E?.requests ?? [] )); expect(requests).toContain('/api/coding/plugin-marketplace/catalog?limit=24'); expect(requests).toContain('/api/coding/plugin-marketplace/catalog?query=%E7%AC%94%E8%AE%B0&limit=24'); expect(requests).toContain('/api/coding/plugin-marketplace/plugins/makelore.notes'); const marketplaceRequests = requests.filter((requestPath) => requestPath.startsWith('/api/coding/plugin-marketplace/')); expect(JSON.stringify(marketplaceRequests)).not.toMatch(/account|admission|release_id|path=/i); } finally { await closeElectronApp(app); } }); test('keeps Beta explicit and exposes bounded unavailable, disabled, and device-delete states', async ({ launchElectronApp }) => { const app = await launchElectronApp({ skipSetup: true }); try { const page = await getStableWindow(app); await expect(page.getByTestId('ai-module-option-programming')).toBeVisible(); await app.evaluate(({ ipcMain }) => { let installedChannel: 'stable' | 'beta' | null = 'stable'; const installedVersion = '1.0.0'; const requests: string[] = []; const library = { items: [ { pluginId: 'makelore.notes', title: '灵感笔记', summary: '整理项目灵感。', category: '效率', acquisition: 'free', acquisitionMode: 'user_acquired', catalogStatus: 'active', runtimeStatus: 'enabled', acquiredAt: '2026-08-28T00:00:00Z', removedAt: null, stableVersion: '1.0.0', betaVersion: '2.0.0-beta.1', }, { pluginId: 'makelore.paused', title: '暂停插件', summary: '暂时暂停。', category: '效率', acquisition: 'free', acquisitionMode: 'user_acquired', catalogStatus: 'active', runtimeStatus: 'suspended', acquiredAt: '2026-08-28T00:00:00Z', removedAt: null, stableVersion: '1.0.0', betaVersion: null, }, { pluginId: 'makelore.unavailable', title: '不可用插件', summary: '服务不可用。', category: '效率', acquisition: 'free', acquisitionMode: 'user_acquired', catalogStatus: 'active', runtimeStatus: 'enabled', acquiredAt: '2026-08-28T00:00:00Z', removedAt: null, stableVersion: '1.0.0', betaVersion: null, }, ], total: 3, stale: false, fetchedAt: 1, }; const projection = () => ({ library, installations: [ ...(installedChannel ? [{ status: 'installed', pluginId: 'makelore.notes', releaseId: installedChannel === 'beta' ? 'beta-2' : 'stable-1', version: installedChannel === 'beta' ? '2.0.0-beta.1' : installedVersion, channel: installedChannel, }] : []), { status: 'unavailable', pluginId: 'makelore.unavailable', reason: 'plugin_backend_unavailable' }, ], }); const result = (json: unknown) => ({ ok: true, data: { status: 200, ok: true, json } }); (globalThis as typeof globalThis & { __marketplaceE2E?: { requests: string[] } }).__marketplaceE2E = { requests }; ipcMain.removeHandler('hostapi:fetch'); ipcMain.handle('hostapi:fetch', async (_event, request: { path?: string; method?: string }) => { const requestPath = request.path ?? ''; const method = (request.method ?? 'GET').toUpperCase(); requests.push(`${method} ${requestPath}`); if (requestPath === '/api/auth/session/sync') { const response = result({ success: true, session: { accessToken: 'marketplace-e2e-token', tokenType: 'Bearer', expiresAt: Date.now() + 3_600_000, lastActiveAt: Date.now(), canRefresh: false, }, }); return response; } if (requestPath === '/api/auth/me') return result({ success: true, moduleAccess: { programming: true } }); if (requestPath === '/api/coding/plugin-marketplace/library') return result(projection()); if (requestPath === '/api/coding/plugin-marketplace/install/makelore.notes/beta' && method === 'POST') { installedChannel = 'beta'; return result({ status: 'installed', pluginId: 'makelore.notes', releaseId: 'beta-2', version: '2.0.0-beta.1', channel: 'beta' }); } if (requestPath === '/api/coding/plugin-marketplace/install/makelore.notes' && method === 'DELETE') { installedChannel = null; return result({ status: 'removed', pluginId: 'makelore.notes', releaseId: 'beta-2', version: '2.0.0-beta.1', channel: 'beta' }); } return { ok: false, error: { message: `Unexpected Host API request: ${method} ${requestPath}` } }; }); }); await page.addInitScript(({ key, value }) => { localStorage.setItem(key, value); }, { key: 'niancode-auth', value: JSON.stringify({ state: { authBase: 'https://biz.nianxx.cn/auth/', clientId: 'app', accessToken: 'marketplace-e2e-token', tokenType: 'Bearer', expiresAt: Date.now() + 3_600_000, lastActiveAt: Date.now(), canRefresh: false, legacyRefreshToken: null, user: { username: 'marketplace-e2e', userId: 'marketplace-e2e-user', tenantId: null, deptId: null, authorities: [] }, moduleAccess: { programming: true, design: true, learning: true, robot: true }, }, version: 2, }), }); await page.reload({ waitUntil: 'domcontentloaded' }); await expect(page.getByTestId('ai-module-option-programming')).toBeVisible(); const programmingOption = page.getByTestId('ai-module-option-programming'); await programmingOption.click(); await expect(page.getByTestId('main-layout')).toBeVisible(); await page.keyboard.press('Escape'); await page.getByTestId('sidebar-nav-my-plugins').click(); await expect(page.getByTestId('my-plugins-page')).toBeVisible(); await expect.poll(async () => app.evaluate(() => ( ((globalThis as typeof globalThis & { __marketplaceE2E?: { requests: string[] } }).__marketplaceE2E?.requests ?? []) .filter((requestPath) => requestPath.includes('/api/auth') || requestPath.includes('/plugin-marketplace')) ))).toEqual(expect.arrayContaining([ 'GET /api/auth/me', 'GET /api/coding/plugin-marketplace/library', ])); await expect(page.getByRole('heading', { name: '暂停插件' })).toBeVisible(); await expect(page.getByText('运行已暂停')).toBeVisible(); await expect(page.getByRole('button', { name: '下载暂停插件' })).toBeDisabled(); await expect(page.getByText('Marketplace 服务暂不可用,设备版本未替换。')).toBeVisible(); await page.getByRole('button', { name: '安装 Beta灵感笔记' }).click(); await expect(page.getByText('当前频道:Beta')).toBeVisible(); await expect(page.getByRole('button', { name: '安装 Beta灵感笔记' })).toHaveCount(0); await page.getByRole('button', { name: '删除设备上的灵感笔记' }).click(); const notesCard = page.locator('article').filter({ has: page.getByRole('heading', { name: '灵感笔记' }) }); await expect(notesCard.getByText('尚未下载到设备')).toBeVisible(); await expect(notesCard.getByRole('button', { name: '下载灵感笔记' })).toBeVisible(); const requests = await app.evaluate(() => ( (globalThis as typeof globalThis & { __marketplaceE2E?: { requests: string[] } }).__marketplaceE2E?.requests ?? [] )); expect(requests).toContain('POST /api/coding/plugin-marketplace/install/makelore.notes/beta'); expect(requests).toContain('DELETE /api/coding/plugin-marketplace/install/makelore.notes'); } finally { await closeElectronApp(app); } }); });