const test = require('node:test'); const assert = require('node:assert/strict'); const fs = require('node:fs'); function loadKiroContributionModules() { const stateSource = fs.readFileSync('flows/kiro/background/state.js', 'utf8'); const artifactSource = fs.readFileSync('flows/kiro/background/credential-artifact.js', 'utf8'); const adapterSource = fs.readFileSync('background/contribution/adapters/kiro-builder-id.js', 'utf8'); const globalScope = {}; new Function('self', `${stateSource}; ${artifactSource}; ${adapterSource}; return self;`)(globalScope); return globalScope; } function getKiroRuntime(state = {}) { return state?.runtimeState?.flowState?.kiro || {}; } function buildAuthorizedKiroState(overrides = {}) { return { activeFlowId: 'kiro', flowId: 'kiro', accountContributionEnabled: true, contributionAdapterId: 'kiro-builder-id', contributionNickname: '贡献者', contributionQq: '123456', runtimeState: { flowState: { kiro: { register: { email: 'kiro-user@example.com', }, desktopAuth: { region: 'us-east-1', clientId: 'client-id-001', clientSecret: 'client-secret-super-long', refreshToken: 'refresh-token-super-secret', tokenSource: 'desktop_authorization_code_pkce', authorizedAt: 1760000000000, }, upload: { targetId: 'kiro-rs', }, }, }, }, ...overrides, }; } test('Kiro Builder ID artifact builder validates and builds unified contribution artifact', () => { const scope = loadKiroContributionModules(); const api = scope.MultiPageBackgroundKiroCredentialArtifact; const artifact = api.buildKiroBuilderIdArtifact(buildAuthorizedKiroState()); assert.equal(artifact.flow, 'kiro'); assert.equal(artifact.adapter, 'kiro-builder-id'); assert.equal(artifact.artifact, 'kiro-builder-id'); assert.equal(artifact.account.email, 'kiro-user@example.com'); assert.equal(artifact.credentials.refreshToken, 'refresh-token-super-secret'); assert.equal(artifact.credentials.clientId, 'client-id-001'); assert.equal(artifact.credentials.clientSecret, 'client-secret-super-long'); assert.equal(artifact.credentials.region, 'us-east-1'); assert.equal(artifact.metadata.targetId, 'kiro-rs'); const safeSummary = api.buildSafeArtifactSummary(artifact); assert.equal(safeSummary.refreshToken.includes('refresh-token-super-secret'), false); assert.equal(safeSummary.clientSecret.includes('client-secret-super-long'), false); }); test('Kiro Builder ID artifact builder rejects missing required fields', () => { const scope = loadKiroContributionModules(); const api = scope.MultiPageBackgroundKiroCredentialArtifact; assert.throws( () => api.buildKiroBuilderIdArtifact(buildAuthorizedKiroState({ runtimeState: { flowState: { kiro: { ...getKiroRuntime(buildAuthorizedKiroState()), desktopAuth: { ...getKiroRuntime(buildAuthorizedKiroState()).desktopAuth, refreshToken: '', }, }, }, }, })), /refreshToken/ ); assert.throws( () => api.buildKiroBuilderIdArtifact(buildAuthorizedKiroState({ runtimeState: { flowState: { kiro: { ...getKiroRuntime(buildAuthorizedKiroState()), desktopAuth: { ...getKiroRuntime(buildAuthorizedKiroState()).desktopAuth, clientId: '', }, }, }, }, })), /clientId/ ); assert.throws( () => api.buildKiroBuilderIdArtifact(buildAuthorizedKiroState({ runtimeState: { flowState: { kiro: { ...getKiroRuntime(buildAuthorizedKiroState()), desktopAuth: { ...getKiroRuntime(buildAuthorizedKiroState()).desktopAuth, clientSecret: '', }, }, }, }, })), /clientSecret/ ); assert.throws( () => api.buildKiroBuilderIdArtifact(buildAuthorizedKiroState({ runtimeState: { flowState: { kiro: { ...getKiroRuntime(buildAuthorizedKiroState()), register: { email: '' }, }, }, }, email: '', accountIdentifier: '', })), /注册邮箱/ ); }); test('Kiro contribution adapter submits public contribution without requiring kiro.rs config and redacts logs', async () => { const scope = loadKiroContributionModules(); const adapterApi = scope.MultiPageBackgroundKiroBuilderIdContributionAdapter; const fetchCalls = []; const logs = []; const statePatches = []; const adapter = adapterApi.createKiroBuilderIdContributionAdapter({ addLog: async (message, level, options) => logs.push({ message, level, options }), fetchImpl: async (url, options) => { fetchCalls.push({ url, options }); return { ok: true, status: 200, async text() { return JSON.stringify({ ok: true, contribution_id: 'kiro-contribution-001', message: '已接收' }); }, }; }, setState: async (patch) => statePatches.push(patch), }); const result = await adapter.maybeSubmitFlowContribution(buildAuthorizedKiroState({ kiroRsUrl: '', kiroRsKey: '', }), { nodeId: 'kiro-complete-desktop-authorize' }); assert.equal(result.ok, true); assert.equal(fetchCalls.length, 1); assert.equal(fetchCalls[0].url, 'https://flowpilot.qlhazycoder.top/api/contributions'); const requestBody = JSON.parse(fetchCalls[0].options.body); assert.equal(requestBody.flow, 'kiro'); assert.equal(requestBody.adapter_id, 'kiro-builder-id'); assert.equal(requestBody.artifact_kind, 'kiro-builder-id'); assert.equal(requestBody.artifact.credentials.refreshToken, 'refresh-token-super-secret'); assert.equal(statePatches.at(-1).flowContributionRuntime.kiro.status, 'submitted'); const logText = logs.map((entry) => entry.message).join('\n'); assert.equal(logText.includes('refresh-token-super-secret'), false); assert.equal(logText.includes('client-secret-super-long'), false); }); test('Kiro contribution adapter skips invalid artifacts without sending secrets', async () => { const scope = loadKiroContributionModules(); const adapterApi = scope.MultiPageBackgroundKiroBuilderIdContributionAdapter; const fetchCalls = []; const logs = []; const adapter = adapterApi.createKiroBuilderIdContributionAdapter({ addLog: async (message) => logs.push(message), fetchImpl: async () => { fetchCalls.push(true); throw new Error('should not be called'); }, setState: async () => {}, }); const result = await adapter.maybeSubmitFlowContribution(buildAuthorizedKiroState({ runtimeState: { flowState: { kiro: { ...getKiroRuntime(buildAuthorizedKiroState()), desktopAuth: { ...getKiroRuntime(buildAuthorizedKiroState()).desktopAuth, refreshToken: '', }, }, }, }, })); assert.equal(result.skipped, true); assert.equal(result.reason, 'missing_refreshToken'); assert.equal(fetchCalls.length, 0); assert.equal(logs.join('\n').includes('refresh-token-super-secret'), false); }); test('Kiro contribution adapter redacts server errors that echo submitted secrets', async () => { const scope = loadKiroContributionModules(); const adapterApi = scope.MultiPageBackgroundKiroBuilderIdContributionAdapter; const logs = []; const statePatches = []; const adapter = adapterApi.createKiroBuilderIdContributionAdapter({ addLog: async (message) => logs.push(message), fetchImpl: async () => ({ ok: false, status: 400, async text() { return JSON.stringify({ ok: false, message: 'bad refresh-token-super-secret and client-secret-super-long', }); }, }), setState: async (patch) => statePatches.push(patch), }); const result = await adapter.maybeSubmitFlowContribution(buildAuthorizedKiroState()); assert.equal(result.ok, false); const combined = `${logs.join('\n')}\n${JSON.stringify(statePatches)}`; assert.equal(combined.includes('refresh-token-super-secret'), false); assert.equal(combined.includes('client-secret-super-long'), false); }); test('Kiro public contribution is triggered from step 9 instead of step 8', () => { const desktopSource = fs.readFileSync('flows/kiro/background/desktop-authorize-runner.js', 'utf8'); const publisherSource = fs.readFileSync('flows/kiro/background/publisher-kiro-rs.js', 'utf8'); assert.doesNotMatch(desktopSource, /trigger:\s*'kiro-step-8'/); assert.match(publisherSource, /maybeSubmitFlowContribution/); assert.match(publisherSource, /trigger:\s*'kiro-step-9'/); });