/** * 假 Provider(任务 A2.3):给契约测试与 Core 单测当靶子,不接任何真实系统。 * * 能力刻意覆盖几条分支:读、写、慢调用(测超时)、声明错误分类(测映射)、裸异常(测未知结果)。 * 它带调用计数,便于断言「幂等重试只执行一次」。 */ import type { ExecutionReceipt } from '@aps/contracts'; import { ProviderError } from './errors.js'; import type { Capability, CapabilityCall, ExecutionContext, HealthStatus, ImpactPreview, Provider, ProviderManifest } from './index.js'; export interface FakeProvider extends Provider { stats: { calls: Record; /** 每次调用拿到的身份与凭据句柄形态:给 A4 的凭据用例断言「凭据只走句柄、不进参数」 */ contexts: Array<{ executionId: string; accountId?: string; hasCredentials: boolean; params: unknown }>; }; } export function createFakeProvider(id = 'fake'): FakeProvider { const calls: Record = {}; const contexts: FakeProvider['stats']['contexts'] = []; const capabilities: Capability[] = [ { id: `${id}.echo`, title: '回显输入', description: '把输入原样返回,用于联调链路', effect: 'read', mode: 'sync', whenNotToUse: '需要真实业务数据时不要用', synonyms: ['回显', '测试'], inputSchema: { type: 'object', properties: { text: { type: 'string' } }, required: ['text'] }, outputSchema: { type: 'object', properties: { echo: { type: 'string' } } }, idempotency: 'none', errors: ['invalid_argument'], }, { id: `${id}.write`, title: '假写入', description: '模拟一次写操作,用于验证幂等与确认链路', effect: 'write', mode: 'sync', inputSchema: { type: 'object', properties: { value: { type: 'number' } } }, outputSchema: { type: 'object', properties: { applied: { type: 'boolean' } } }, idempotency: 'key', errors: ['invalid_argument', 'conflict'], }, { id: `${id}.slow`, title: '慢调用', description: '按参数等待若干毫秒,用于验证超时与结果未知', effect: 'read', mode: 'sync', inputSchema: { type: 'object', properties: { ms: { type: 'number' } } }, outputSchema: { type: 'object' }, idempotency: 'none', errors: ['unknown_result'], }, { id: `${id}.declare-error`, title: '声明错误', description: '按参数抛出指定分类的错误,用于验证错误归一化', effect: 'read', mode: 'sync', inputSchema: { type: 'object', properties: { kind: { type: 'string' } } }, outputSchema: { type: 'object' }, idempotency: 'none', errors: ['invalid_argument', 'forbidden', 'conflict', 'rate_limited', 'unavailable', 'unknown_result'], }, { id: `${id}.crash`, title: '裸异常', description: '抛裸异常,验证归一为 unknown_result', effect: 'read', mode: 'sync', inputSchema: { type: 'object' }, outputSchema: { type: 'object' }, idempotency: 'none', errors: ['unknown_result'], }, ]; return { stats: { calls, contexts }, async describe(): Promise { // 假 Provider 没有账号概念:不需要身份绑定(真实系统默认需要,见 ProviderManifest.requiresIdentity) return { id, displayName: '假 Provider', version: '0.0.0', platformVersion: 1, requiresIdentity: false, capabilities }; }, async invoke(context: ExecutionContext, call: CapabilityCall): Promise { const known = capabilities.find((capability) => capability.id === call.capabilityId); if (!known) throw new ProviderError('invalid_argument', `假 Provider 没有能力 ${call.capabilityId}`); calls[call.capabilityId] = (calls[call.capabilityId] ?? 0) + 1; contexts.push({ executionId: context.executionId, ...(context.identity === undefined ? {} : { accountId: context.identity.accountId }), hasCredentials: context.credentials !== undefined, params: call.params, }); const params = (call.params ?? {}) as Record; switch (known.id) { case `${id}.echo`: if (typeof params.text !== 'string' || params.text === '') { throw new ProviderError('invalid_argument', 'text 不能为空'); } return { executionId: context.executionId, status: 'succeeded', result: { echo: params.text } }; case `${id}.write`: return { executionId: context.executionId, status: 'succeeded', result: { applied: true, idempotencyKey: context.idempotencyKey ?? null } }; case `${id}.slow`: { const ms = typeof params.ms === 'number' ? params.ms : 100; await new Promise((resolve) => setTimeout(resolve, ms)); return { executionId: context.executionId, status: 'succeeded', result: { waitedMs: ms } }; } case `${id}.declare-error`: { const kind = typeof params.kind === 'string' ? params.kind : 'unavailable'; throw new ProviderError(kind as ProviderError['kind'], `按要求抛出 ${kind}`); } default: throw new Error('假 Provider 内部异常:没有兜底分支'); } }, async health(): Promise { return { status: 'ok', checkedAt: Date.now() }; }, async preview(_context: ExecutionContext, call: CapabilityCall): Promise { const params = (call.params ?? {}) as Record; if (call.capabilityId !== `${id}.write`) { throw new ProviderError('invalid_argument', `假 Provider 没有 ${call.capabilityId} 的影响预览`); } return { impactSummary: `会把值写成 ${String(params.value ?? '')}(假 Provider,不碰真实数据)`, revision: 7, paramsPreview: { value: params.value ?? null }, }; }, }; }