// @vitest-environment node import path from 'node:path'; import { describe, expect, it, vi } from 'vitest'; import { loadBundledCodingPluginDefinitionsSync } from '../../electron/coding-plugins/manifest'; import { isProjectWideCodingPluginId } from '../../shared/coding-plugins'; import { GameAudioPluginAdapter } from '../../electron/coding-plugins/adapters/game-audio'; import { CodingCapabilityRegistryImpl } from '../../electron/coding-plugins/registry'; import type { PluginPolicyClientState } from '../../electron/services/plugin-policy-client'; import type { AudioDeliveryResult } from '../../electron/services/game-audio-delivery'; const context = { conversationId: 'conversation', runId: 'run', resourceId: 'tool', requestId: 'trusted-id', localProjectId: 'local', projectPath: '/project', durableProjectId: 'durable', workerRole: 'parent' as const, effectiveSkillIds: ['game-audio'], pluginReleaseId: '00000000-0000-4000-8000-000000000505' }; function definition() { return loadBundledCodingPluginDefinitionsSync(path.resolve('resources/coding-plugins')) .find((item) => item.id === 'makelore.game-audio')!; } describe('official Game Audio plugin', () => { it('passes normalized receipts through the registry with progress and saved files', async () => { const plugin = definition(); const delivered: AudioDeliveryResult = { executionId: '00000000-0000-4000-8000-000000000601', logicalOperationId: 'request', providerStatus: 'succeeded', deliveryStatus: 'saved', phase: 'saved', progressPercent: 100, outputCount: 1, files: [{ index: 0, path: 'assets/generated/game-audio/00000000-0000-4000-8000-000000000601/music-1.mp3', bytes: 10, mimeType: 'audio/mpeg' }], billing: { mode: 'platform_metered', status: 'settled', reserved_points: '1.00', actual_points: '1.00', usage_amount: 1, unit: 'generation' }, }; const adapter = new GameAudioPluginAdapter({ delivery: { generateAndMaterialize: async (_context, _command, progress) => { progress?.({ ...delivered, providerStatus: 'running', deliveryStatus: 'waiting', phase: 'generating', files: [], billing: { mode: 'platform_metered', status: 'dispatched', reserved_points: '1.00', usage_amount: 1, unit: 'generation' } }); return delivered; } }, client: { cancel: vi.fn() }, admission: { resolve: vi.fn(async () => ({ releaseId: context.pluginReleaseId, releaseAdmissionId: 'admission' })) }, getPricingVersion: () => 'price', getAccountKey: () => 'owner', }); const policy: PluginPolicyClientState = { status: 'current', revision: 1, lastVerifiedAt: 1, catalog: { schema_version: 1, catalog_version: '1', pricing_version: 'price', plugins: [{ plugin_id: plugin.id, supported_contract_versions: [1], status: 'active', capabilities: plugin.operations.map((op) => ({ capability_id: op.capabilityId, operations: [{ operation: op.operation, billing: { mode: 'platform_metered', entitlement_scope: 'plugin_usage', notice: 'Metered', unit_name: 'generation', unit_size: 1, rate_points: '1.00', minimum_charge_points: '0.00', rounding_mode: 'ceil' }, }] })) }] } }; const registry = new CodingCapabilityRegistryImpl({ definitions: [plugin], adapters: [adapter], getEnabledPluginIds: async () => [plugin.id], policyClient: { getState: () => policy, refresh: vi.fn() } }); const onUpdate = vi.fn(); const output = await registry.invoke({ toolName: 'game_music_generate', workerRole: 'parent', effectiveSkillIds: ['game-audio'], context: { conversationId: 'c', runId: 'r', resourceId: 't', projectId: 'p', projectPath: '/project', skillIds: ['game-audio'] }, value: { prompt: 'forest', confirmed: true }, onUpdate }); expect(output.details).toMatchObject({ success: true, data: { files: delivered.files } }); expect(onUpdate).toHaveBeenCalledWith(expect.objectContaining({ details: expect.objectContaining({ success: true, billing: { ...delivered.billing, status: 'dispatched', actual_points: undefined } }), })); }); it('ships one project-wide Skill and exactly four statically priced parent tools', () => { const plugin = definition(); expect(plugin).toBeDefined(); expect(isProjectWideCodingPluginId(plugin.id)).toBe(true); expect(plugin.tools.map((tool) => [tool.name, tool.capabilityId, tool.operation])).toEqual([ ['game_music_generate', 'game-audio.music', 'generate'], ['game_music_preview', 'game-audio.music', 'preview'], ['game_sound_generate', 'game-audio.sound', 'generate'], ['game_audio_cancel', 'game-audio.control', 'cancel'], ]); expect(plugin.tools.every((tool) => tool.roles.length === 1 && tool.roles[0] === 'parent' && !tool.projectWriteLease)).toBe(true); }); it('requires literal confirmation; Main freezes identity, mode and trusted price before auto-delivery', async () => { const generateAndMaterialize = vi.fn(async (): Promise => ({ executionId: null, logicalOperationId: 'trusted-id', providerStatus: 'running', deliveryStatus: 'waiting', files: [], outputCount: 0, phase: 'generating', progressPercent: 40, billing: { mode: 'platform_metered', status: 'receipt_unavailable' }, })); const resolve = vi.fn(async () => ({ releaseId: context.pluginReleaseId, releaseAdmissionId: 'admission' })); const adapter = new GameAudioPluginAdapter({ delivery: { generateAndMaterialize }, client: { cancel: vi.fn() }, admission: { resolve }, getPricingVersion: () => 'trusted-price', getAccountKey: () => 'owner', }); const tool = definition().tools[1]!; expect(await adapter.invoke(context, tool, { prompt: 'ambient forest', confirmed: 1 })) .toMatchObject({ success: false, code: 'confirmation_required', billing: { status: 'not_started' } }); expect(generateAndMaterialize).not.toHaveBeenCalled(); expect(await adapter.invoke(context, tool, { prompt: 'ambient forest', confirmed: true })) .toMatchObject({ success: true, status: 202 }); expect(generateAndMaterialize).toHaveBeenCalledWith(expect.objectContaining({ accountKey: 'owner', logicalOperationId: 'trusted-id', projectPath: '/project', }), expect.objectContaining({ kind: 'music', mode: 'demo', pricing_version_id: 'trusted-price', project_id: 'durable', release_id: context.pluginReleaseId, release_admission_id: 'admission', logical_operation_id: 'trusted-id', }), expect.any(Function)); expect(await adapter.invoke(context, tool, { prompt: 'x', confirmed: true, mode: 'full' })) .toMatchObject({ success: false, code: 'plugin_input_invalid' }); generateAndMaterialize.mockRejectedValueOnce(new Error('receipt disk failed after POST')); expect(await adapter.invoke(context, tool, { prompt: 'forest', confirmed: true })) .toMatchObject({ success: false, billing: { status: 'receipt_unavailable' } }); }); });