feat: upgrade multi-flow account contributions
This commit is contained in:
@@ -559,7 +559,7 @@ test('auto-run restarts from confirm-oauth step after transient step10 token_exc
|
||||
stepStatuses: { 3: 'completed' },
|
||||
stepsVersion: 'ultra2.0',
|
||||
visibleStep: 10,
|
||||
contributionMode: false,
|
||||
accountContributionEnabled: false,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -102,7 +102,7 @@ test('account run history helper upgrades old records, keeps stopped items and s
|
||||
attemptRun: 3,
|
||||
},
|
||||
plusModeEnabled: false,
|
||||
contributionMode: false,
|
||||
accountContributionEnabled: false,
|
||||
});
|
||||
|
||||
const appended = await helpers.appendAccountRunRecord('node:fetch-login-code:failed', null, '步骤 8:认证页进入了手机号页面,当前不是 OAuth 同意页,无法继续自动授权。');
|
||||
@@ -213,7 +213,7 @@ test('account run history helper accepts phone-only records without forcing emai
|
||||
source: 'manual',
|
||||
autoRunContext: null,
|
||||
plusModeEnabled: false,
|
||||
contributionMode: false,
|
||||
accountContributionEnabled: false,
|
||||
});
|
||||
|
||||
const normalized = helpers.normalizeAccountRunHistoryRecord({
|
||||
@@ -426,22 +426,22 @@ test('account run history records preserve Plus and contribution mode flags', ()
|
||||
email: 'plus@example.com',
|
||||
password: 'secret',
|
||||
plusModeEnabled: true,
|
||||
contributionMode: true,
|
||||
accountContributionEnabled: true,
|
||||
}, 'success');
|
||||
|
||||
assert.equal(record.plusModeEnabled, true);
|
||||
assert.equal(record.contributionMode, true);
|
||||
assert.equal(record.accountContributionEnabled, true);
|
||||
|
||||
const normalized = helpers.normalizeAccountRunHistoryRecord({
|
||||
email: 'plus@example.com',
|
||||
password: 'secret',
|
||||
finalStatus: 'success',
|
||||
plusModeEnabled: true,
|
||||
contributionMode: true,
|
||||
accountContributionEnabled: true,
|
||||
});
|
||||
|
||||
assert.equal(normalized.plusModeEnabled, true);
|
||||
assert.equal(normalized.contributionMode, true);
|
||||
assert.equal(normalized.accountContributionEnabled, true);
|
||||
});
|
||||
|
||||
test('account run history helper clears persisted records and syncs full snapshot payload to local helper', async () => {
|
||||
|
||||
@@ -69,7 +69,7 @@ test('background imports contribution oauth module and keeps contribution runtim
|
||||
|
||||
assert.match(backgroundSource, /background\/contribution-oauth\.js/);
|
||||
assert.doesNotMatch(persistedBlock, /contributionSessionId|contributionAuthUrl|contributionCallbackUrl|contributionStatus/);
|
||||
assert.match(defaultStateBlock, /contributionMode:\s*false|CONTRIBUTION_RUNTIME_DEFAULTS/);
|
||||
assert.match(defaultStateBlock, /accountContributionEnabled:\s*false|CONTRIBUTION_RUNTIME_DEFAULTS/);
|
||||
});
|
||||
|
||||
test('contribution oauth module exposes a factory', () => {
|
||||
@@ -84,14 +84,48 @@ test('contribution oauth module exposes a factory', () => {
|
||||
assert.equal(Array.isArray(api?.RUNTIME_KEYS), true);
|
||||
});
|
||||
|
||||
test('buildContributionModeState preserves active contribution runtime while keeping contribution on sub2api', () => {
|
||||
const bundle = extractFunction(backgroundSource, 'buildContributionModeState');
|
||||
test('buildAccountContributionState preserves active contribution runtime while keeping contribution on sub2api', () => {
|
||||
const bundle = extractFunction(backgroundSource, 'buildAccountContributionState');
|
||||
const helperBundle = [
|
||||
'normalizeAccountContributionFlowId',
|
||||
'normalizeAccountContributionAdapterId',
|
||||
'assertAccountContributionAdapterAvailable',
|
||||
'buildFlowContributionRuntimePatch',
|
||||
'normalizeOpenAiContributionSource',
|
||||
'resolveOpenAiContributionRoutingState',
|
||||
].map((name) => extractFunction(backgroundSource, name)).join('\n');
|
||||
|
||||
const api = new Function(`
|
||||
const DEFAULT_ACTIVE_FLOW_ID = 'openai';
|
||||
const CONTRIBUTION_SOURCE_CPA = 'cpa';
|
||||
const CONTRIBUTION_SOURCE_SUB2API = 'sub2api';
|
||||
const CONTRIBUTION_SUB2API_DEFAULT_GROUP_NAME = 'codex号池';
|
||||
const CONTRIBUTION_SUB2API_PLUS_GROUP_NAME = 'openai-plus';
|
||||
const self = {
|
||||
MultiPageFlowRegistry: {
|
||||
normalizeFlowId(value = '', fallback = 'openai') {
|
||||
const normalized = String(value || '').trim().toLowerCase();
|
||||
return normalized || fallback || 'openai';
|
||||
},
|
||||
},
|
||||
MultiPageContributionRegistry: {
|
||||
normalizeAdapterId(value = '') {
|
||||
return String(value || '').trim().toLowerCase();
|
||||
},
|
||||
hasContributionAdapter(flowId, adapterId) {
|
||||
return flowId === 'openai' && adapterId === 'openai-oauth';
|
||||
},
|
||||
getDefaultContributionAdapterId(flowId) {
|
||||
return flowId === 'openai' ? 'openai-oauth' : '';
|
||||
},
|
||||
},
|
||||
};
|
||||
const DEFAULT_STATE = { panelMode: 'cpa' };
|
||||
const CONTRIBUTION_RUNTIME_DEFAULTS = {
|
||||
contributionMode: false,
|
||||
contributionModeExpected: false,
|
||||
accountContributionEnabled: false,
|
||||
accountContributionExpected: false,
|
||||
contributionAdapterId: '',
|
||||
flowContributionRuntime: {},
|
||||
contributionSource: 'sub2api',
|
||||
contributionTargetGroupName: 'codex号池',
|
||||
contributionNickname: '',
|
||||
@@ -110,143 +144,130 @@ const CONTRIBUTION_RUNTIME_DEFAULTS = {
|
||||
};
|
||||
const CONTRIBUTION_RUNTIME_KEYS = Object.keys(CONTRIBUTION_RUNTIME_DEFAULTS);
|
||||
function isPlusModeState(state = {}) { return Boolean(state?.plusModeEnabled); }
|
||||
function normalizeContributionModeSource(value = '') {
|
||||
const normalized = String(value || '').trim().toLowerCase();
|
||||
return normalized === 'sub2api' ? 'sub2api' : 'cpa';
|
||||
}
|
||||
function resolveContributionModeRoutingState(state = {}) {
|
||||
const currentStatus = String(state?.contributionStatus || '').trim().toLowerCase();
|
||||
const currentSource = normalizeContributionModeSource(state?.contributionSource);
|
||||
const hasActiveSession = Boolean(
|
||||
String(state?.contributionSessionId || '').trim()
|
||||
&& currentStatus
|
||||
&& !['auto_approved', 'auto_rejected', 'expired', 'error'].includes(currentStatus)
|
||||
);
|
||||
if (hasActiveSession) {
|
||||
return {
|
||||
source: currentSource,
|
||||
targetGroupName: currentSource === 'sub2api'
|
||||
? (String(state?.contributionTargetGroupName || '').trim() || 'codex号池')
|
||||
: '',
|
||||
};
|
||||
}
|
||||
const source = 'sub2api';
|
||||
return {
|
||||
source,
|
||||
targetGroupName: isPlusModeState(state)
|
||||
? 'openai-plus'
|
||||
: (String(state?.contributionTargetGroupName || '').trim() || 'codex号池'),
|
||||
};
|
||||
}
|
||||
${helperBundle}
|
||||
${bundle}
|
||||
return { buildContributionModeState };
|
||||
return { buildAccountContributionState };
|
||||
`)();
|
||||
|
||||
assert.deepStrictEqual(
|
||||
api.buildContributionModeState(true, {
|
||||
panelMode: 'sub2api',
|
||||
customPassword: 'Secret123!',
|
||||
accountRunHistoryTextEnabled: true,
|
||||
}, {
|
||||
contributionSessionId: 'session-001',
|
||||
contributionAuthUrl: 'https://auth.example.com',
|
||||
contributionStatus: 'waiting',
|
||||
contributionCallbackStatus: 'waiting',
|
||||
}),
|
||||
{
|
||||
contributionMode: true,
|
||||
contributionModeExpected: true,
|
||||
contributionSource: 'sub2api',
|
||||
contributionTargetGroupName: 'codex号池',
|
||||
contributionNickname: '',
|
||||
contributionQq: '',
|
||||
contributionSessionId: 'session-001',
|
||||
contributionAuthUrl: 'https://auth.example.com',
|
||||
contributionAuthState: '',
|
||||
contributionCallbackUrl: '',
|
||||
contributionStatus: 'waiting',
|
||||
contributionStatusMessage: '',
|
||||
contributionLastPollAt: 0,
|
||||
contributionCallbackStatus: 'waiting',
|
||||
contributionCallbackMessage: '',
|
||||
contributionAuthOpenedAt: 0,
|
||||
contributionAuthTabId: 0,
|
||||
panelMode: 'sub2api',
|
||||
customPassword: '',
|
||||
accountRunHistoryTextEnabled: false,
|
||||
}
|
||||
);
|
||||
const enabledState = api.buildAccountContributionState(true, {
|
||||
panelMode: 'sub2api',
|
||||
customPassword: 'Secret123!',
|
||||
accountRunHistoryTextEnabled: true,
|
||||
}, {
|
||||
contributionSessionId: 'session-001',
|
||||
contributionAuthUrl: 'https://auth.example.com',
|
||||
contributionStatus: 'waiting',
|
||||
contributionCallbackStatus: 'waiting',
|
||||
});
|
||||
assert.equal(enabledState.accountContributionEnabled, true);
|
||||
assert.equal(enabledState.accountContributionExpected, true);
|
||||
assert.equal(enabledState.contributionAdapterId, 'openai-oauth');
|
||||
assert.deepStrictEqual(enabledState.flowContributionRuntime, {
|
||||
openai: { enabled: true, adapterId: 'openai-oauth' },
|
||||
});
|
||||
assert.equal(enabledState.contributionSessionId, 'session-001');
|
||||
assert.equal(enabledState.panelMode, 'sub2api');
|
||||
assert.equal(enabledState.customPassword, '');
|
||||
assert.equal(enabledState.accountRunHistoryTextEnabled, false);
|
||||
|
||||
assert.deepStrictEqual(
|
||||
api.buildContributionModeState(false, {
|
||||
panelMode: 'sub2api',
|
||||
customPassword: 'Secret123!',
|
||||
accountRunHistoryTextEnabled: true,
|
||||
}, {
|
||||
contributionSessionId: 'session-001',
|
||||
contributionAuthUrl: 'https://auth.example.com',
|
||||
contributionStatus: 'waiting',
|
||||
}),
|
||||
{
|
||||
contributionMode: false,
|
||||
contributionModeExpected: false,
|
||||
contributionSource: 'sub2api',
|
||||
contributionTargetGroupName: 'codex号池',
|
||||
contributionNickname: '',
|
||||
contributionQq: '',
|
||||
contributionSessionId: '',
|
||||
contributionAuthUrl: '',
|
||||
contributionAuthState: '',
|
||||
contributionCallbackUrl: '',
|
||||
contributionStatus: '',
|
||||
contributionStatusMessage: '',
|
||||
contributionLastPollAt: 0,
|
||||
contributionCallbackStatus: 'idle',
|
||||
contributionCallbackMessage: '',
|
||||
contributionAuthOpenedAt: 0,
|
||||
contributionAuthTabId: 0,
|
||||
panelMode: 'sub2api',
|
||||
customPassword: 'Secret123!',
|
||||
accountRunHistoryTextEnabled: true,
|
||||
}
|
||||
);
|
||||
const disabledState = api.buildAccountContributionState(false, {
|
||||
panelMode: 'sub2api',
|
||||
customPassword: 'Secret123!',
|
||||
accountRunHistoryTextEnabled: true,
|
||||
}, {
|
||||
contributionSessionId: 'session-001',
|
||||
contributionAuthUrl: 'https://auth.example.com',
|
||||
contributionStatus: 'waiting',
|
||||
});
|
||||
assert.equal(disabledState.accountContributionEnabled, false);
|
||||
assert.equal(disabledState.accountContributionExpected, false);
|
||||
assert.equal(disabledState.contributionAdapterId, '');
|
||||
assert.deepStrictEqual(disabledState.flowContributionRuntime, {});
|
||||
assert.equal(disabledState.contributionSessionId, '');
|
||||
assert.equal(disabledState.panelMode, 'sub2api');
|
||||
assert.equal(disabledState.customPassword, 'Secret123!');
|
||||
|
||||
assert.deepStrictEqual(
|
||||
api.buildContributionModeState(true, {
|
||||
panelMode: 'cpa',
|
||||
plusModeEnabled: true,
|
||||
customPassword: 'Secret123!',
|
||||
accountRunHistoryTextEnabled: true,
|
||||
}, {}),
|
||||
{
|
||||
contributionMode: true,
|
||||
contributionModeExpected: true,
|
||||
contributionSource: 'sub2api',
|
||||
contributionTargetGroupName: 'openai-plus',
|
||||
contributionNickname: '',
|
||||
contributionQq: '',
|
||||
contributionSessionId: '',
|
||||
contributionAuthUrl: '',
|
||||
contributionAuthState: '',
|
||||
contributionCallbackUrl: '',
|
||||
contributionStatus: '',
|
||||
contributionStatusMessage: '',
|
||||
contributionLastPollAt: 0,
|
||||
contributionCallbackStatus: 'idle',
|
||||
contributionCallbackMessage: '',
|
||||
contributionAuthOpenedAt: 0,
|
||||
contributionAuthTabId: 0,
|
||||
panelMode: 'sub2api',
|
||||
customPassword: '',
|
||||
accountRunHistoryTextEnabled: false,
|
||||
}
|
||||
);
|
||||
const plusContributionState = api.buildAccountContributionState(true, {
|
||||
panelMode: 'cpa',
|
||||
plusModeEnabled: true,
|
||||
customPassword: 'Secret123!',
|
||||
accountRunHistoryTextEnabled: true,
|
||||
}, {});
|
||||
assert.equal(plusContributionState.contributionTargetGroupName, 'openai-plus');
|
||||
assert.equal(plusContributionState.panelMode, 'sub2api');
|
||||
});
|
||||
|
||||
test('resetState preserves contribution runtime across reset', () => {
|
||||
assert.match(backgroundSource, /CONTRIBUTION_RUNTIME_KEYS/);
|
||||
assert.match(backgroundSource, /const contributionModeState = buildContributionModeState/);
|
||||
assert.match(backgroundSource, /\.\.\.contributionModeState/);
|
||||
assert.match(backgroundSource, /const accountContributionState = buildAccountContributionState/);
|
||||
assert.match(backgroundSource, /\.\.\.accountContributionState/);
|
||||
});
|
||||
|
||||
test('storage migration upgrades legacy contribution mode into unified account contribution state', async () => {
|
||||
const helperBundle = [
|
||||
'normalizeAccountContributionFlowId',
|
||||
'normalizeAccountContributionAdapterId',
|
||||
'buildFlowContributionRuntimePatch',
|
||||
].map((name) => extractFunction(backgroundSource, name)).join('\n');
|
||||
const migrationBundle = extractFunction(backgroundSource, 'migrateLegacyAccountContributionState');
|
||||
const api = new Function(`
|
||||
const DEFAULT_ACTIVE_FLOW_ID = 'openai';
|
||||
const self = {
|
||||
MultiPageFlowRegistry: {
|
||||
normalizeFlowId(value = '', fallback = 'openai') {
|
||||
const normalized = String(value || '').trim().toLowerCase();
|
||||
return normalized || fallback || 'openai';
|
||||
},
|
||||
},
|
||||
MultiPageContributionRegistry: {
|
||||
normalizeAdapterId(value = '') {
|
||||
return String(value || '').trim().toLowerCase();
|
||||
},
|
||||
hasContributionAdapter(flowId, adapterId) {
|
||||
return (flowId === 'openai' && adapterId === 'openai-oauth')
|
||||
|| (flowId === 'kiro' && adapterId === 'kiro-builder-id');
|
||||
},
|
||||
getDefaultContributionAdapterId(flowId) {
|
||||
return flowId === 'kiro' ? 'kiro-builder-id' : 'openai-oauth';
|
||||
},
|
||||
},
|
||||
};
|
||||
const sessionStore = {
|
||||
contributionMode: true,
|
||||
contributionModeExpected: true,
|
||||
activeFlowId: 'kiro',
|
||||
};
|
||||
const removed = [];
|
||||
const chrome = {
|
||||
storage: {
|
||||
session: {
|
||||
async get() { return { ...sessionStore }; },
|
||||
async set(updates) { Object.assign(sessionStore, updates); },
|
||||
async remove(keys) { removed.push(['session', ...keys]); keys.forEach((key) => { delete sessionStore[key]; }); },
|
||||
},
|
||||
local: {
|
||||
async remove(keys) { removed.push(['local', ...keys]); },
|
||||
},
|
||||
},
|
||||
};
|
||||
${helperBundle}
|
||||
${migrationBundle}
|
||||
return { migrateLegacyAccountContributionState, sessionStore, removed };
|
||||
`)();
|
||||
|
||||
await api.migrateLegacyAccountContributionState();
|
||||
|
||||
assert.equal(api.sessionStore.accountContributionEnabled, true);
|
||||
assert.equal(api.sessionStore.accountContributionExpected, true);
|
||||
assert.equal(api.sessionStore.contributionAdapterId, 'kiro-builder-id');
|
||||
assert.deepStrictEqual(api.sessionStore.flowContributionRuntime, {
|
||||
kiro: { enabled: true, adapterId: 'kiro-builder-id' },
|
||||
});
|
||||
assert.equal(Object.prototype.hasOwnProperty.call(api.sessionStore, 'contributionMode'), false);
|
||||
assert.deepStrictEqual(api.removed, [
|
||||
['session', 'contributionMode', 'contributionModeExpected'],
|
||||
['local', 'contributionMode', 'contributionModeExpected'],
|
||||
]);
|
||||
});
|
||||
|
||||
test('message router handles contribution mode, start flow, and status polling messages', async () => {
|
||||
@@ -258,23 +279,23 @@ test('message router handles contribution mode, start flow, and status polling m
|
||||
const router = api.createMessageRouter({
|
||||
ensureManualInteractionAllowed: async () => ({
|
||||
stepStatuses: { 1: 'pending', 2: 'completed' },
|
||||
contributionMode: true,
|
||||
accountContributionEnabled: true,
|
||||
}),
|
||||
pollContributionStatus: async (options) => {
|
||||
calls.push({ type: 'poll', options });
|
||||
return { contributionStatus: 'waiting' };
|
||||
},
|
||||
setContributionMode: async (enabled) => {
|
||||
setAccountContributionMode: async (enabled) => {
|
||||
calls.push({ type: 'toggle', enabled });
|
||||
return {
|
||||
contributionMode: Boolean(enabled),
|
||||
accountContributionEnabled: Boolean(enabled),
|
||||
panelMode: 'cpa',
|
||||
};
|
||||
},
|
||||
startContributionFlow: async (options) => {
|
||||
startFlowContribution: async (options) => {
|
||||
calls.push({ type: 'start', options });
|
||||
return {
|
||||
contributionMode: true,
|
||||
accountContributionEnabled: true,
|
||||
contributionSessionId: 'session-001',
|
||||
contributionStatus: 'started',
|
||||
};
|
||||
@@ -282,15 +303,15 @@ test('message router handles contribution mode, start flow, and status polling m
|
||||
});
|
||||
|
||||
const enableResponse = await router.handleMessage({
|
||||
type: 'SET_CONTRIBUTION_MODE',
|
||||
type: 'SET_ACCOUNT_CONTRIBUTION_MODE',
|
||||
payload: { enabled: true },
|
||||
});
|
||||
const startResponse = await router.handleMessage({
|
||||
type: 'START_CONTRIBUTION_FLOW',
|
||||
type: 'START_FLOW_CONTRIBUTION',
|
||||
payload: { nickname: '阿青', qq: '123456' },
|
||||
});
|
||||
const pollResponse = await router.handleMessage({
|
||||
type: 'POLL_CONTRIBUTION_STATUS',
|
||||
type: 'POLL_FLOW_CONTRIBUTION_STATUS',
|
||||
payload: { reason: 'test_poll' },
|
||||
});
|
||||
|
||||
@@ -304,7 +325,7 @@ test('message router handles contribution mode, start flow, and status polling m
|
||||
]);
|
||||
});
|
||||
|
||||
test('message router re-syncs contribution mode before AUTO_RUN when sidepanel payload marks contributionMode=true', async () => {
|
||||
test('message router re-syncs contribution mode before AUTO_RUN when sidepanel payload marks accountContributionEnabled=true', async () => {
|
||||
const source = fs.readFileSync('background/message-router.js', 'utf8');
|
||||
const globalScope = {};
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundMessageRouter;`)(globalScope);
|
||||
@@ -314,13 +335,13 @@ test('message router re-syncs contribution mode before AUTO_RUN when sidepanel p
|
||||
clearStopRequest: () => {},
|
||||
getPendingAutoRunTimerPlan: () => null,
|
||||
getState: async () => ({
|
||||
contributionMode: false,
|
||||
accountContributionEnabled: false,
|
||||
stepStatuses: {},
|
||||
}),
|
||||
normalizeRunCount: (value) => Number(value) || 1,
|
||||
setContributionMode: async (enabled) => {
|
||||
setAccountContributionMode: async (enabled) => {
|
||||
calls.push({ type: 'toggle', enabled });
|
||||
return { contributionMode: true };
|
||||
return { accountContributionEnabled: true };
|
||||
},
|
||||
setState: async (updates) => {
|
||||
calls.push({ type: 'setState', updates });
|
||||
@@ -336,7 +357,7 @@ test('message router re-syncs contribution mode before AUTO_RUN when sidepanel p
|
||||
totalRuns: 2,
|
||||
autoRunSkipFailures: true,
|
||||
mode: 'restart',
|
||||
contributionMode: true,
|
||||
accountContributionEnabled: true,
|
||||
contributionNickname: '阿青',
|
||||
contributionQq: '123456',
|
||||
},
|
||||
@@ -433,7 +454,7 @@ test('account run history snapshot sync is disabled in contribution mode', () =>
|
||||
|
||||
assert.equal(
|
||||
helpers.shouldSyncAccountRunHistorySnapshot({
|
||||
contributionMode: true,
|
||||
accountContributionEnabled: true,
|
||||
accountRunHistoryTextEnabled: true,
|
||||
accountRunHistoryHelperBaseUrl: 'http://127.0.0.1:17373',
|
||||
}),
|
||||
@@ -442,7 +463,7 @@ test('account run history snapshot sync is disabled in contribution mode', () =>
|
||||
|
||||
assert.equal(
|
||||
helpers.shouldSyncAccountRunHistorySnapshot({
|
||||
contributionMode: false,
|
||||
accountContributionEnabled: false,
|
||||
accountRunHistoryTextEnabled: true,
|
||||
accountRunHistoryHelperBaseUrl: 'http://127.0.0.1:17373',
|
||||
}),
|
||||
@@ -458,7 +479,7 @@ test('contribution oauth manager starts session, opens auth url, submits callbac
|
||||
const closeCallbackCalls = [];
|
||||
let statusPollCount = 0;
|
||||
let currentState = {
|
||||
contributionMode: true,
|
||||
accountContributionEnabled: true,
|
||||
contributionSource: 'sub2api',
|
||||
contributionTargetGroupName: 'codex号池',
|
||||
email: 'user@example.com',
|
||||
@@ -541,7 +562,7 @@ test('contribution oauth manager starts session, opens auth url, submits callbac
|
||||
},
|
||||
});
|
||||
|
||||
const startedState = await manager.startContributionFlow();
|
||||
const startedState = await manager.startFlowContribution();
|
||||
assert.equal(startedState.contributionSessionId, 'session-001');
|
||||
assert.equal(startedState.contributionAuthState, 'oauth-state-001');
|
||||
assert.equal(startedState.contributionStatus, 'waiting');
|
||||
@@ -577,7 +598,7 @@ test('contribution oauth manager deduplicates concurrent callback captures for t
|
||||
let submitCallCount = 0;
|
||||
let resolveSubmitRequest = null;
|
||||
let currentState = {
|
||||
contributionMode: true,
|
||||
accountContributionEnabled: true,
|
||||
contributionSource: 'sub2api',
|
||||
contributionTargetGroupName: 'codex号池',
|
||||
contributionSessionId: 'session-001',
|
||||
@@ -664,7 +685,7 @@ test('contribution oauth manager ignores tabs.onUpdated events without a real ur
|
||||
const fetchCalls = [];
|
||||
const addedListeners = {};
|
||||
let currentState = {
|
||||
contributionMode: true,
|
||||
accountContributionEnabled: true,
|
||||
contributionSource: 'sub2api',
|
||||
contributionTargetGroupName: 'codex号池',
|
||||
contributionSessionId: 'session-001',
|
||||
@@ -756,7 +777,7 @@ test('contribution oauth manager switches Plus contribution traffic to sub2api o
|
||||
const globalScope = {};
|
||||
const fetchCalls = [];
|
||||
let currentState = {
|
||||
contributionMode: true,
|
||||
accountContributionEnabled: true,
|
||||
plusModeEnabled: true,
|
||||
contributionSource: 'sub2api',
|
||||
contributionTargetGroupName: 'openai-plus',
|
||||
@@ -817,7 +838,7 @@ test('contribution oauth manager switches Plus contribution traffic to sub2api o
|
||||
},
|
||||
});
|
||||
|
||||
await manager.startContributionFlow();
|
||||
await manager.startFlowContribution();
|
||||
|
||||
assert.match(String(fetchCalls[0].options.body || ''), /"source":"sub2api"/);
|
||||
assert.match(String(fetchCalls[0].options.body || ''), /"target_group_name":"openai-plus"/);
|
||||
@@ -836,7 +857,7 @@ return { refreshOAuthUrlBeforeStep6 };
|
||||
calls.push({ type: 'log', message, level, options });
|
||||
};
|
||||
globalThis.contributionOAuthManager = {
|
||||
async startContributionFlow(options) {
|
||||
async startFlowContribution(options) {
|
||||
calls.push({ type: 'contribution', options });
|
||||
return {
|
||||
contributionAuthUrl: 'https://auth.example.com/oauth?state=oauth-state-001',
|
||||
@@ -854,7 +875,7 @@ return { refreshOAuthUrlBeforeStep6 };
|
||||
globalThis.LOG_PREFIX = '[test]';
|
||||
|
||||
const oauthUrl = await api.refreshOAuthUrlBeforeStep6({
|
||||
contributionMode: true,
|
||||
accountContributionEnabled: true,
|
||||
email: 'user@example.com',
|
||||
});
|
||||
|
||||
@@ -862,7 +883,7 @@ return { refreshOAuthUrlBeforeStep6 };
|
||||
assert.deepStrictEqual(calls, [
|
||||
{
|
||||
type: 'log',
|
||||
message: 'contributionMode=true,走公开贡献接口,正在申请 OAuth 登录地址...',
|
||||
message: '账号贡献已开启,走公开贡献接口,正在申请 OAuth 登录地址...',
|
||||
level: 'info',
|
||||
options: { step: 7, stepKey: 'oauth-login' },
|
||||
},
|
||||
@@ -872,7 +893,7 @@ return { refreshOAuthUrlBeforeStep6 };
|
||||
nickname: '',
|
||||
openAuthTab: false,
|
||||
stateOverride: {
|
||||
contributionMode: true,
|
||||
accountContributionEnabled: true,
|
||||
email: 'user@example.com',
|
||||
},
|
||||
},
|
||||
@@ -894,7 +915,7 @@ return { refreshOAuthUrlBeforeStep6 };
|
||||
delete globalThis.LOG_PREFIX;
|
||||
});
|
||||
|
||||
test('refreshOAuthUrlBeforeStep6 logs the normal CPA/SUB2API/Codex2API path explicitly when contributionMode=false', async () => {
|
||||
test('refreshOAuthUrlBeforeStep6 logs the normal CPA/SUB2API/Codex2API path explicitly when accountContributionEnabled=false', async () => {
|
||||
const bundle = extractFunction(backgroundSource, 'refreshOAuthUrlBeforeStep6');
|
||||
const calls = [];
|
||||
|
||||
@@ -907,7 +928,7 @@ return { refreshOAuthUrlBeforeStep6 };
|
||||
calls.push({ type: 'log', message, level, options });
|
||||
};
|
||||
globalThis.contributionOAuthManager = {
|
||||
async startContributionFlow() {
|
||||
async startFlowContribution() {
|
||||
calls.push({ type: 'contribution' });
|
||||
return {
|
||||
contributionAuthUrl: 'https://auth.example.com/oauth?state=unexpected',
|
||||
@@ -925,7 +946,7 @@ return { refreshOAuthUrlBeforeStep6 };
|
||||
globalThis.LOG_PREFIX = '[test]';
|
||||
|
||||
const oauthUrl = await api.refreshOAuthUrlBeforeStep6({
|
||||
contributionMode: false,
|
||||
accountContributionEnabled: false,
|
||||
panelMode: 'sub2api',
|
||||
email: 'user@example.com',
|
||||
});
|
||||
@@ -934,7 +955,7 @@ return { refreshOAuthUrlBeforeStep6 };
|
||||
assert.deepStrictEqual(calls, [
|
||||
{
|
||||
type: 'log',
|
||||
message: 'contributionMode=false,走普通 CPA / SUB2API / Codex2API 链路(当前面板:SUB2API),正在刷新 OAuth 登录地址...',
|
||||
message: '账号贡献未开启,走普通 CPA / SUB2API / Codex2API 链路(当前面板:SUB2API),正在刷新 OAuth 登录地址...',
|
||||
level: 'info',
|
||||
options: { step: 7, stepKey: 'oauth-login' },
|
||||
},
|
||||
@@ -956,7 +977,7 @@ return { refreshOAuthUrlBeforeStep6 };
|
||||
delete globalThis.LOG_PREFIX;
|
||||
});
|
||||
|
||||
test('executeStep10 blocks silent fallback when contributionModeExpected=true but contributionMode=false', async () => {
|
||||
test('executeStep10 blocks silent fallback when accountContributionExpected=true but accountContributionEnabled=false', async () => {
|
||||
const bundle = extractFunction(backgroundSource, 'executeStep10');
|
||||
|
||||
const api = new Function(`
|
||||
@@ -973,10 +994,10 @@ return { executeStep10 };
|
||||
|
||||
await assert.rejects(
|
||||
() => api.executeStep10({
|
||||
contributionModeExpected: true,
|
||||
contributionMode: false,
|
||||
accountContributionExpected: true,
|
||||
accountContributionEnabled: false,
|
||||
}),
|
||||
/步骤 10:当前自动流程预期使用贡献模式/
|
||||
/步骤 10:当前自动流程预期使用账号贡献/
|
||||
);
|
||||
|
||||
delete globalThis.executeContributionStep10;
|
||||
|
||||
@@ -674,7 +674,7 @@ return {
|
||||
|
||||
test('resetState preserves LuckMail session config, used map, and preserve tag cache while clearing runtime purchase state', async () => {
|
||||
const bundle = [
|
||||
extractFunction('buildContributionModeState'),
|
||||
extractFunction('buildAccountContributionState'),
|
||||
extractFunction('resetState'),
|
||||
].join('\n');
|
||||
|
||||
@@ -702,7 +702,7 @@ test('resetState preserves LuckMail session config, used map, and preserve tag c
|
||||
' email: null,',
|
||||
'};',
|
||||
'const CONTRIBUTION_RUNTIME_DEFAULTS = {',
|
||||
' contributionMode: false,',
|
||||
' accountContributionEnabled: false,',
|
||||
" contributionSessionId: '',",
|
||||
" contributionAuthUrl: '',",
|
||||
" contributionAuthState: '',",
|
||||
|
||||
@@ -100,7 +100,7 @@ function createRouterWithFinalNode(options = {}) {
|
||||
selectLuckmailPurchase: async () => {},
|
||||
setCurrentHotmailAccount: async () => {},
|
||||
setCurrentMail2925Account: async () => {},
|
||||
setContributionMode: async () => {},
|
||||
setAccountContributionMode: async () => {},
|
||||
setEmailState: async () => {},
|
||||
setEmailStateSilently: async () => {},
|
||||
setIcloudAliasPreservedState: async () => {},
|
||||
|
||||
@@ -58,7 +58,7 @@ let state = {
|
||||
signupMethod: 'phone',
|
||||
phoneVerificationEnabled: true,
|
||||
plusModeEnabled: false,
|
||||
contributionMode: false,
|
||||
accountContributionEnabled: false,
|
||||
resolvedSignupMethod: null,
|
||||
};
|
||||
async function getState() { return { ...state }; }
|
||||
|
||||
@@ -61,7 +61,7 @@ function getErrorMessage(error) {
|
||||
}
|
||||
async function getState() {
|
||||
events.push({ type: 'getState' });
|
||||
return { nodeStatuses: {}, contributionMode: true };
|
||||
return { nodeStatuses: {}, accountContributionEnabled: true };
|
||||
}
|
||||
function getLastNodeIdForState() {
|
||||
return lastNodeId;
|
||||
|
||||
@@ -8,6 +8,7 @@ function createContributionContentService(options = {}) {
|
||||
const cache = new Map();
|
||||
const windowObject = {};
|
||||
let fetchCalls = 0;
|
||||
const fetchUrls = [];
|
||||
|
||||
const localStorage = {
|
||||
getItem(key) {
|
||||
@@ -23,7 +24,7 @@ function createContributionContentService(options = {}) {
|
||||
|
||||
if (options.cachedSnapshot) {
|
||||
cache.set(
|
||||
'multipage-contribution-content-summary-v1',
|
||||
options.cacheKey || 'multipage-contribution-content-summary-v2:openai:cpa',
|
||||
JSON.stringify(options.cachedSnapshot)
|
||||
);
|
||||
}
|
||||
@@ -44,6 +45,7 @@ function createContributionContentService(options = {}) {
|
||||
|
||||
const wrappedFetch = async (...args) => {
|
||||
fetchCalls += 1;
|
||||
fetchUrls.push(String(args[0] || ''));
|
||||
return fetchImpl(...args);
|
||||
};
|
||||
|
||||
@@ -69,11 +71,14 @@ function createContributionContentService(options = {}) {
|
||||
getFetchCalls() {
|
||||
return fetchCalls;
|
||||
},
|
||||
getFetchUrls() {
|
||||
return fetchUrls.slice();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
test('getContentUpdateSnapshot returns a prompt version for visible contribution content updates', async () => {
|
||||
const { api } = createContributionContentService({
|
||||
const { api, getFetchUrls } = createContributionContentService({
|
||||
fetchImpl: async () => ({
|
||||
ok: true,
|
||||
async json() {
|
||||
@@ -100,10 +105,13 @@ test('getContentUpdateSnapshot returns a prompt version for visible contribution
|
||||
}),
|
||||
});
|
||||
|
||||
const snapshot = await api.getContentUpdateSnapshot();
|
||||
const snapshot = await api.getContentUpdateSnapshot({ flowId: 'kiro', targetId: 'kiro-rs' });
|
||||
|
||||
assert.equal(snapshot.status, 'update-available');
|
||||
assert.equal(snapshot.promptVersion, 'auto_run_notice:2026-04-21T12:05:00Z');
|
||||
assert.equal(snapshot.flowId, 'kiro');
|
||||
assert.equal(snapshot.targetId, 'kiro-rs');
|
||||
assert.equal(getFetchUrls()[0], 'https://flowpilot.qlhazycoder.top/api/content-summary?flow=kiro&target=kiro-rs');
|
||||
assert.equal(snapshot.hasVisibleUpdates, true);
|
||||
assert.equal(snapshot.latestUpdatedAt, '2026-04-21T12:05:00Z');
|
||||
assert.equal(snapshot.items.length, 1);
|
||||
@@ -132,7 +140,7 @@ test('getContentUpdateSnapshot falls back to cached snapshot when the live reque
|
||||
},
|
||||
],
|
||||
portalUrl: 'https://flowpilot.qlhazycoder.top',
|
||||
apiUrl: 'https://flowpilot.qlhazycoder.top/api/content-summary',
|
||||
apiUrl: 'https://flowpilot.qlhazycoder.top/api/content-summary?flow=openai&target=cpa',
|
||||
checkedAt: Date.now() - 1000,
|
||||
};
|
||||
|
||||
@@ -151,3 +159,35 @@ test('getContentUpdateSnapshot falls back to cached snapshot when the live reque
|
||||
assert.equal(snapshot.errorMessage, 'offline');
|
||||
assert.equal(snapshot.items[0].slug, 'announcement');
|
||||
});
|
||||
|
||||
test('getContentUpdateSnapshot keeps flow caches isolated', async () => {
|
||||
const cachedSnapshot = {
|
||||
status: 'update-available',
|
||||
promptVersion: 'flow:kiro|target:kiro-rs|auto_run_notice:2026-04-22T00:00:00Z',
|
||||
hasVisibleUpdates: true,
|
||||
latestUpdatedAt: '2026-04-22T00:00:00Z',
|
||||
latestUpdatedAtDisplay: '2026-04-22 08:00',
|
||||
flowId: 'kiro',
|
||||
targetId: 'kiro-rs',
|
||||
items: [{ slug: 'auto_run_notice', isVisible: true, text: 'Kiro 提示' }],
|
||||
checkedAt: Date.now() - 1000,
|
||||
};
|
||||
|
||||
const { api } = createContributionContentService({
|
||||
cachedSnapshot,
|
||||
cacheKey: 'multipage-contribution-content-summary-v2:kiro:kiro-rs',
|
||||
fetchImpl: async () => {
|
||||
throw new Error('offline');
|
||||
},
|
||||
});
|
||||
|
||||
const openAiSnapshot = await api.getContentUpdateSnapshot({ flowId: 'openai', targetId: 'cpa' });
|
||||
const kiroSnapshot = await api.getContentUpdateSnapshot({ flowId: 'kiro', targetId: 'kiro-rs' });
|
||||
|
||||
assert.equal(openAiSnapshot.status, 'error');
|
||||
assert.equal(openAiSnapshot.fromCache, undefined);
|
||||
assert.equal(openAiSnapshot.flowId, 'openai');
|
||||
assert.equal(kiroSnapshot.fromCache, true);
|
||||
assert.equal(kiroSnapshot.flowId, 'kiro');
|
||||
assert.equal(kiroSnapshot.promptVersion, cachedSnapshot.promptVersion);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
|
||||
const flowRegistrySource = fs.readFileSync('shared/flow-registry.js', 'utf8');
|
||||
const contributionRegistrySource = fs.readFileSync('shared/contribution-registry.js', 'utf8');
|
||||
|
||||
function loadApi() {
|
||||
const scope = {};
|
||||
return new Function(
|
||||
'self',
|
||||
`${flowRegistrySource}; ${contributionRegistrySource}; return self.MultiPageContributionRegistry;`
|
||||
)(scope);
|
||||
}
|
||||
|
||||
test('contribution registry exposes OpenAI and Kiro adapters through one contract', () => {
|
||||
const api = loadApi();
|
||||
|
||||
assert.deepEqual(
|
||||
api.getContributionAdapterIds('openai'),
|
||||
['openai-oauth', 'openai-codex-file', 'openai-sub2api-file']
|
||||
);
|
||||
assert.deepEqual(api.getContributionAdapterIds('kiro'), ['kiro-builder-id']);
|
||||
assert.equal(api.getDefaultContributionAdapterId('openai'), 'openai-oauth');
|
||||
assert.equal(api.getDefaultContributionAdapterId('kiro'), 'kiro-builder-id');
|
||||
assert.equal(api.getAdapterDefinition('kiro-builder-id')?.artifactKind, 'kiro-builder-id');
|
||||
assert.equal(api.getAdapterDefinition('openai-oauth')?.flowId, 'openai');
|
||||
assert.equal(api.hasContributionAdapter('kiro', 'kiro-builder-id'), true);
|
||||
assert.equal(api.hasContributionAdapter('kiro', 'openai-oauth'), false);
|
||||
});
|
||||
|
||||
test('contribution registry resolves the combined tutorial entry per flow', () => {
|
||||
const api = loadApi();
|
||||
|
||||
assert.deepEqual(
|
||||
api.getContributionTutorialEntry('openai', {
|
||||
portalBaseUrl: 'https://flowpilot.example/root/',
|
||||
targetId: 'sub2api',
|
||||
}),
|
||||
{
|
||||
id: 'openai-contribution-tutorial',
|
||||
flowId: 'openai',
|
||||
label: '贡献/使用教程',
|
||||
portalPath: '/tutorial',
|
||||
defaultTargetId: 'cpa',
|
||||
contributionAdapterId: 'openai-oauth',
|
||||
action: 'open-portal-and-enable-contribution',
|
||||
targetId: 'sub2api',
|
||||
portalUrl: 'https://flowpilot.example/root/tutorial?flow=openai&target=sub2api',
|
||||
}
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
api.getContributionTutorialEntry('kiro', {
|
||||
portalBaseUrl: 'https://flowpilot.example',
|
||||
}),
|
||||
{
|
||||
id: 'kiro-contribution-tutorial',
|
||||
flowId: 'kiro',
|
||||
label: '贡献/使用教程',
|
||||
portalPath: '/tutorial',
|
||||
defaultTargetId: 'kiro-rs',
|
||||
contributionAdapterId: 'kiro-builder-id',
|
||||
action: 'open-portal-and-enable-contribution',
|
||||
targetId: 'kiro-rs',
|
||||
portalUrl: 'https://flowpilot.example/tutorial?flow=kiro&target=kiro-rs',
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test('contribution registry fails fast when a published flow has no adapter', () => {
|
||||
const api = loadApi();
|
||||
|
||||
assert.equal(api.assertPublishedFlowsHaveContributionAdapters(['openai', 'kiro']), true);
|
||||
assert.throws(
|
||||
() => api.assertPublishedFlowsHaveContributionAdapters(['openai', 'missing-flow']),
|
||||
/缺少账号贡献适配器:missing-flow/
|
||||
);
|
||||
});
|
||||
@@ -3,6 +3,7 @@ const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
|
||||
const flowRegistrySource = fs.readFileSync('shared/flow-registry.js', 'utf8');
|
||||
const contributionRegistrySource = fs.readFileSync('shared/contribution-registry.js', 'utf8');
|
||||
const settingsSchemaSource = fs.readFileSync('shared/settings-schema.js', 'utf8');
|
||||
const source = fs.readFileSync('shared/flow-capabilities.js', 'utf8');
|
||||
|
||||
@@ -10,7 +11,7 @@ function loadApi() {
|
||||
const scope = {};
|
||||
return new Function(
|
||||
'self',
|
||||
`${flowRegistrySource}; ${settingsSchemaSource}; ${source}; return self.MultiPageFlowCapabilities;`
|
||||
`${flowRegistrySource}; ${contributionRegistrySource}; ${settingsSchemaSource}; ${source}; return self.MultiPageFlowCapabilities;`
|
||||
)(scope);
|
||||
}
|
||||
|
||||
@@ -24,7 +25,7 @@ test('flow capability registry keeps OpenAI phone signup available only when run
|
||||
openaiIntegrationTargetId: 'cpa',
|
||||
phoneVerificationEnabled: true,
|
||||
plusModeEnabled: false,
|
||||
contributionMode: false,
|
||||
accountContributionEnabled: false,
|
||||
signupMethod: 'phone',
|
||||
},
|
||||
});
|
||||
@@ -40,7 +41,7 @@ test('flow capability registry keeps OpenAI phone signup available only when run
|
||||
openaiIntegrationTargetId: 'sub2api',
|
||||
phoneVerificationEnabled: true,
|
||||
plusModeEnabled: true,
|
||||
contributionMode: false,
|
||||
accountContributionEnabled: false,
|
||||
signupMethod: 'phone',
|
||||
},
|
||||
});
|
||||
@@ -61,7 +62,7 @@ test('flow capability registry defaults unknown flows to minimal non-phone capab
|
||||
openaiIntegrationTargetId: 'codex2api',
|
||||
phoneVerificationEnabled: true,
|
||||
plusModeEnabled: true,
|
||||
contributionMode: true,
|
||||
accountContributionEnabled: true,
|
||||
signupMethod: 'phone',
|
||||
},
|
||||
});
|
||||
@@ -94,8 +95,10 @@ test('flow capability registry exposes Kiro as an independent flow with its own
|
||||
assert.equal(capabilityState.activeFlowId, 'kiro');
|
||||
assert.equal(capabilityState.canShowPhoneSettings, false);
|
||||
assert.equal(capabilityState.canShowPlusSettings, false);
|
||||
assert.equal(capabilityState.canShowContributionMode, true);
|
||||
assert.equal(capabilityState.effectiveSignupMethod, 'email');
|
||||
assert.equal(capabilityState.effectiveTargetId, 'kiro-rs');
|
||||
assert.deepEqual(capabilityState.flowCapabilities.contributionAdapterIds, ['kiro-builder-id']);
|
||||
assert.deepEqual(
|
||||
capabilityState.visibleGroupIds,
|
||||
['kiro-runtime-status', 'kiro-target-kiro-rs', 'service-account', 'service-email', 'service-proxy']
|
||||
@@ -121,7 +124,7 @@ test('flow capability registry exposes shared auto-run validation for phone lock
|
||||
signupMethod: 'phone',
|
||||
phoneVerificationEnabled: true,
|
||||
plusModeEnabled: true,
|
||||
contributionMode: false,
|
||||
accountContributionEnabled: false,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -159,14 +162,14 @@ test('flow capability registry normalizes unsupported mode switches back to the
|
||||
signupMethod: 'phone',
|
||||
phoneVerificationEnabled: true,
|
||||
plusModeEnabled: true,
|
||||
contributionMode: true,
|
||||
accountContributionEnabled: true,
|
||||
},
|
||||
changedKeys: [
|
||||
'openaiIntegrationTargetId',
|
||||
'signupMethod',
|
||||
'phoneVerificationEnabled',
|
||||
'plusModeEnabled',
|
||||
'contributionMode',
|
||||
'accountContributionEnabled',
|
||||
],
|
||||
});
|
||||
|
||||
@@ -178,7 +181,8 @@ test('flow capability registry normalizes unsupported mode switches back to the
|
||||
signupMethod: 'email',
|
||||
phoneVerificationEnabled: false,
|
||||
plusModeEnabled: false,
|
||||
contributionMode: false,
|
||||
accountContributionEnabled: false,
|
||||
accountContributionEnabled: false,
|
||||
});
|
||||
assert.deepEqual(
|
||||
validation.errors.map((entry) => entry.code),
|
||||
|
||||
@@ -39,6 +39,16 @@ test('flow registry exposes canonical flow and target metadata', () => {
|
||||
['row-plus-mode', 'row-plus-account-access-strategy', 'row-plus-payment-method']
|
||||
);
|
||||
assert.equal(flowRegistry.getPublicationTargetDefinition('kiro', 'kiro-rs')?.label, 'kiro.rs');
|
||||
assert.equal(flowRegistry.getFlowCapabilities('openai').supportsAccountContribution, true);
|
||||
assert.equal(flowRegistry.getFlowCapabilities('kiro').supportsAccountContribution, true);
|
||||
assert.deepEqual(
|
||||
flowRegistry.getFlowCapabilities('openai').contributionAdapterIds,
|
||||
['openai-oauth', 'openai-codex-file', 'openai-sub2api-file']
|
||||
);
|
||||
assert.deepEqual(
|
||||
flowRegistry.getFlowCapabilities('kiro').contributionAdapterIds,
|
||||
['kiro-builder-id']
|
||||
);
|
||||
});
|
||||
|
||||
test('settings schema normalizes view input into canonical nested namespaces', () => {
|
||||
|
||||
@@ -0,0 +1,217 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
|
||||
function loadKiroContributionModules() {
|
||||
const stateSource = fs.readFileSync('background/kiro/state.js', 'utf8');
|
||||
const artifactSource = fs.readFileSync('background/kiro/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 buildAuthorizedKiroState(overrides = {}) {
|
||||
return {
|
||||
activeFlowId: 'kiro',
|
||||
flowId: 'kiro',
|
||||
accountContributionEnabled: true,
|
||||
contributionAdapterId: 'kiro-builder-id',
|
||||
contributionNickname: '贡献者',
|
||||
contributionQq: '123456',
|
||||
kiroRuntime: {
|
||||
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({
|
||||
kiroRuntime: {
|
||||
...buildAuthorizedKiroState().kiroRuntime,
|
||||
desktopAuth: {
|
||||
...buildAuthorizedKiroState().kiroRuntime.desktopAuth,
|
||||
refreshToken: '',
|
||||
},
|
||||
},
|
||||
})),
|
||||
/refreshToken/
|
||||
);
|
||||
assert.throws(
|
||||
() => api.buildKiroBuilderIdArtifact(buildAuthorizedKiroState({
|
||||
kiroRuntime: {
|
||||
...buildAuthorizedKiroState().kiroRuntime,
|
||||
desktopAuth: {
|
||||
...buildAuthorizedKiroState().kiroRuntime.desktopAuth,
|
||||
clientId: '',
|
||||
},
|
||||
},
|
||||
})),
|
||||
/clientId/
|
||||
);
|
||||
assert.throws(
|
||||
() => api.buildKiroBuilderIdArtifact(buildAuthorizedKiroState({
|
||||
kiroRuntime: {
|
||||
...buildAuthorizedKiroState().kiroRuntime,
|
||||
desktopAuth: {
|
||||
...buildAuthorizedKiroState().kiroRuntime.desktopAuth,
|
||||
clientSecret: '',
|
||||
},
|
||||
},
|
||||
})),
|
||||
/clientSecret/
|
||||
);
|
||||
assert.throws(
|
||||
() => api.buildKiroBuilderIdArtifact(buildAuthorizedKiroState({
|
||||
kiroRuntime: {
|
||||
...buildAuthorizedKiroState().kiroRuntime,
|
||||
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({
|
||||
kiroRuntime: {
|
||||
...buildAuthorizedKiroState().kiroRuntime,
|
||||
desktopAuth: {
|
||||
...buildAuthorizedKiroState().kiroRuntime.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 desktop authorization runner is wired to submit public contribution after step 8', () => {
|
||||
const source = fs.readFileSync('background/kiro/desktop-authorize-runner.js', 'utf8');
|
||||
assert.match(source, /maybeSubmitFlowContribution/);
|
||||
assert.match(source, /trigger:\s*'kiro-step-8'/);
|
||||
});
|
||||
@@ -64,7 +64,7 @@ return new Function(`
|
||||
const events = [];
|
||||
const DEFAULT_ACTIVE_FLOW_ID = 'openai';
|
||||
const latestState = {
|
||||
contributionMode: false,
|
||||
accountContributionEnabled: false,
|
||||
activeFlowId: 'openai',
|
||||
flowId: 'openai',
|
||||
panelMode: 'cpa',
|
||||
@@ -266,7 +266,7 @@ const latestState = {
|
||||
activeFlowId: 'site-a',
|
||||
panelMode: 'cpa',
|
||||
signupMethod: 'phone',
|
||||
contributionMode: false,
|
||||
accountContributionEnabled: false,
|
||||
phoneVerificationEnabled: true,
|
||||
};
|
||||
const inputAutoSkipFailures = { checked: false };
|
||||
|
||||
@@ -4,6 +4,8 @@ const fs = require('node:fs');
|
||||
const vm = require('node:vm');
|
||||
|
||||
const source = fs.readFileSync('sidepanel/contribution-mode.js', 'utf8');
|
||||
const flowRegistrySource = fs.readFileSync('shared/flow-registry.js', 'utf8');
|
||||
const contributionRegistrySource = fs.readFileSync('shared/contribution-registry.js', 'utf8');
|
||||
|
||||
function createElement() {
|
||||
return {
|
||||
@@ -19,7 +21,10 @@ function createElement() {
|
||||
},
|
||||
},
|
||||
setAttribute() {},
|
||||
addEventListener() {},
|
||||
listeners: {},
|
||||
addEventListener(type, handler) {
|
||||
this.listeners[type] = handler;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -37,12 +42,14 @@ test('contribution mode manager does not project openai-only ui state into kiro
|
||||
const rowVpsUrl = createElement();
|
||||
const dom = {
|
||||
btnContributionMode: createElement(),
|
||||
contributionModePanel: createElement(),
|
||||
contributionModeText: createElement(),
|
||||
contributionModeBadge: createElement(),
|
||||
accountContributionPanel: createElement(),
|
||||
accountContributionText: createElement(),
|
||||
accountContributionBadge: createElement(),
|
||||
contributionPrimaryStatusLabel: createElement(),
|
||||
contributionSecondaryStatusLabel: createElement(),
|
||||
contributionOauthStatus: createElement(),
|
||||
contributionCallbackStatus: createElement(),
|
||||
contributionModeSummary: createElement(),
|
||||
accountContributionSummary: createElement(),
|
||||
inputContributionNickname: createElement(),
|
||||
inputContributionQq: createElement(),
|
||||
btnStartContribution: createElement(),
|
||||
@@ -57,7 +64,9 @@ test('contribution mode manager does not project openai-only ui state into kiro
|
||||
getLatestState: () => ({
|
||||
activeFlowId: 'kiro',
|
||||
flowId: 'kiro',
|
||||
contributionMode: true,
|
||||
accountContributionEnabled: true,
|
||||
supportsAccountContribution: true,
|
||||
contributionAdapterId: 'kiro-builder-id',
|
||||
contributionSource: 'cpa',
|
||||
}),
|
||||
},
|
||||
@@ -80,11 +89,107 @@ test('contribution mode manager does not project openai-only ui state into kiro
|
||||
|
||||
manager.render();
|
||||
|
||||
assert.equal(dom.contributionModePanel.hidden, true);
|
||||
assert.equal(dom.accountContributionPanel.hidden, false);
|
||||
assert.equal(dom.contributionPrimaryStatusLabel.textContent, '账号产物');
|
||||
assert.equal(dom.contributionSecondaryStatusLabel.textContent, '提交');
|
||||
assert.equal(dom.contributionOauthStatus.textContent, '等待账号产物');
|
||||
assert.equal(dom.selectPanelMode.disabled, false);
|
||||
assert.equal(dom.btnContributionMode.disabled, true);
|
||||
assert.equal(dom.btnContributionMode.title, '当前 flow 不支持贡献模式');
|
||||
assert.equal(dom.btnStartContribution.disabled, true);
|
||||
assert.equal(dom.btnOpenContributionUpload.disabled, true);
|
||||
assert.equal(dom.selectPanelMode.value, '');
|
||||
assert.equal(dom.btnContributionMode.disabled, false);
|
||||
assert.equal(dom.btnContributionMode.title, '打开当前 flow 教程;当前已在贡献模式');
|
||||
assert.equal(dom.btnStartContribution.disabled, false);
|
||||
assert.equal(dom.btnOpenContributionUpload.disabled, false);
|
||||
assert.equal(dom.btnOpenContributionUpload.textContent, '查看当前 flow 贡献说明');
|
||||
assert.equal(rowVpsUrl.classList.hiddenState, false);
|
||||
});
|
||||
|
||||
test('combined contribution tutorial button opens current flow page and enables current flow adapter', async () => {
|
||||
const windowObject = {};
|
||||
const api = new Function(
|
||||
'self',
|
||||
'window',
|
||||
'setTimeout',
|
||||
'clearTimeout',
|
||||
`${flowRegistrySource}; ${contributionRegistrySource}; ${source}; return window.SidepanelContributionMode;`
|
||||
)(windowObject, windowObject, setTimeout, clearTimeout);
|
||||
|
||||
let latestState = {
|
||||
activeFlowId: 'kiro',
|
||||
flowId: 'kiro',
|
||||
accountContributionEnabled: false,
|
||||
supportsAccountContribution: true,
|
||||
contributionAdapterId: '',
|
||||
kiroTargetId: 'kiro-rs',
|
||||
};
|
||||
const openedUrls = [];
|
||||
const sentMessages = [];
|
||||
const dom = {
|
||||
btnContributionMode: createElement(),
|
||||
accountContributionPanel: createElement(),
|
||||
accountContributionText: createElement(),
|
||||
accountContributionBadge: createElement(),
|
||||
contributionPrimaryStatusLabel: createElement(),
|
||||
contributionSecondaryStatusLabel: createElement(),
|
||||
contributionOauthStatus: createElement(),
|
||||
contributionCallbackStatus: createElement(),
|
||||
accountContributionSummary: createElement(),
|
||||
inputContributionNickname: createElement(),
|
||||
inputContributionQq: createElement(),
|
||||
btnStartContribution: createElement(),
|
||||
btnOpenContributionUpload: createElement(),
|
||||
btnExitContributionMode: createElement(),
|
||||
btnOpenAccountRecords: createElement(),
|
||||
selectPanelMode: createElement(),
|
||||
};
|
||||
const manager = api.createContributionModeManager({
|
||||
state: {
|
||||
getLatestState: () => latestState,
|
||||
},
|
||||
dom,
|
||||
helpers: {
|
||||
applySettingsState(nextState) {
|
||||
latestState = nextState;
|
||||
},
|
||||
updatePanelModeUI() {},
|
||||
updateAccountRunHistorySettingsUI() {},
|
||||
updateConfigMenuControls() {},
|
||||
closeConfigMenu() {},
|
||||
closeAccountRecordsPanel() {},
|
||||
isModeSwitchBlocked() {
|
||||
return false;
|
||||
},
|
||||
openExternalUrl(url) {
|
||||
openedUrls.push(url);
|
||||
},
|
||||
showToast() {},
|
||||
},
|
||||
runtime: {
|
||||
sendMessage: async (message) => {
|
||||
sentMessages.push(message);
|
||||
return {
|
||||
state: {
|
||||
...latestState,
|
||||
accountContributionEnabled: true,
|
||||
contributionAdapterId: message.payload.adapterId,
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
constants: {
|
||||
contributionPortalUrl: 'https://flowpilot.qlhazycoder.top',
|
||||
},
|
||||
});
|
||||
|
||||
manager.bindEvents();
|
||||
await dom.btnContributionMode.listeners.click();
|
||||
|
||||
assert.deepEqual(openedUrls, ['https://flowpilot.qlhazycoder.top/tutorial?flow=kiro&target=kiro-rs']);
|
||||
assert.equal(sentMessages[0].type, 'SET_ACCOUNT_CONTRIBUTION_MODE');
|
||||
assert.deepEqual(sentMessages[0].payload, {
|
||||
enabled: true,
|
||||
flowId: 'kiro',
|
||||
adapterId: 'kiro-builder-id',
|
||||
});
|
||||
assert.equal(latestState.accountContributionEnabled, true);
|
||||
assert.equal(latestState.contributionAdapterId, 'kiro-builder-id');
|
||||
});
|
||||
|
||||
@@ -193,7 +193,7 @@ test('collectSettingsPayload omits custom password and local sync settings in co
|
||||
const bundle = extractFunction('collectSettingsPayload');
|
||||
|
||||
const api = new Function('normalizeIcloudTargetMailboxType', 'normalizeIcloudForwardMailProvider', `
|
||||
let latestState = { contributionMode: true };
|
||||
let latestState = { accountContributionEnabled: true };
|
||||
const window = {};
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
||||
@@ -337,7 +337,7 @@ return {
|
||||
assert.equal(contributionPayload.phoneVerificationEnabled, true);
|
||||
assert.equal(contributionPayload.cloudflareTempEmailUseRandomSubdomain, true);
|
||||
|
||||
api.setLatestState({ contributionMode: false });
|
||||
api.setLatestState({ accountContributionEnabled: false });
|
||||
const normalPayload = api.collectSettingsPayload();
|
||||
assert.equal(normalPayload.panelMode, 'cpa');
|
||||
assert.equal(normalPayload.customPassword, 'Secret123!');
|
||||
@@ -370,7 +370,7 @@ test('contribution mode manager enters mode, starts main auto flow, polls contri
|
||||
assert.equal(typeof api?.createContributionModeManager, 'function');
|
||||
|
||||
let latestState = {
|
||||
contributionMode: false,
|
||||
accountContributionEnabled: false,
|
||||
panelMode: 'sub2api',
|
||||
contributionSource: 'sub2api',
|
||||
contributionTargetGroupName: 'codex号池',
|
||||
@@ -401,13 +401,13 @@ test('contribution mode manager enters mode, starts main auto flow, polls contri
|
||||
btnOpenAccountRecords: createElement(),
|
||||
btnOpenContributionUpload: createElement(),
|
||||
btnStartContribution: createElement(),
|
||||
contributionModeBadge: createElement(),
|
||||
accountContributionBadge: createElement(),
|
||||
inputContributionNickname: createElement({ value: '贡献者昵称' }),
|
||||
inputContributionQq: createElement({ value: '123456' }),
|
||||
contributionCallbackStatus: createElement(),
|
||||
contributionModePanel: createElement({ hidden: true }),
|
||||
contributionModeSummary: createElement(),
|
||||
contributionModeText: createElement(),
|
||||
accountContributionPanel: createElement({ hidden: true }),
|
||||
accountContributionSummary: createElement(),
|
||||
accountContributionText: createElement(),
|
||||
contributionOauthStatus: createElement(),
|
||||
rowAccountRunHistoryHelperBaseUrl: createElement(),
|
||||
rowAccountRunHistoryTextEnabled: createElement(),
|
||||
@@ -492,11 +492,11 @@ test('contribution mode manager enters mode, starts main auto flow, polls contri
|
||||
runtime: {
|
||||
sendMessage: async (message) => {
|
||||
sentMessages.push(message);
|
||||
if (message.type === 'SET_CONTRIBUTION_MODE') {
|
||||
if (message.type === 'SET_ACCOUNT_CONTRIBUTION_MODE') {
|
||||
return {
|
||||
state: message.payload.enabled
|
||||
? {
|
||||
contributionMode: true,
|
||||
accountContributionEnabled: true,
|
||||
panelMode: 'sub2api',
|
||||
contributionSource: 'sub2api',
|
||||
contributionTargetGroupName: 'codex号池',
|
||||
@@ -512,7 +512,7 @@ test('contribution mode manager enters mode, starts main auto flow, polls contri
|
||||
email: latestState.email,
|
||||
}
|
||||
: {
|
||||
contributionMode: false,
|
||||
accountContributionEnabled: false,
|
||||
panelMode: 'cpa',
|
||||
contributionSource: 'cpa',
|
||||
contributionTargetGroupName: '',
|
||||
@@ -530,7 +530,7 @@ test('contribution mode manager enters mode, starts main auto flow, polls contri
|
||||
},
|
||||
};
|
||||
}
|
||||
if (message.type === 'POLL_CONTRIBUTION_STATUS') {
|
||||
if (message.type === 'POLL_FLOW_CONTRIBUTION_STATUS') {
|
||||
return {
|
||||
state: {
|
||||
...latestState,
|
||||
@@ -541,14 +541,6 @@ test('contribution mode manager enters mode, starts main auto flow, polls contri
|
||||
},
|
||||
};
|
||||
}
|
||||
if (message.type === 'SET_CONTRIBUTION_PROFILE') {
|
||||
latestState = {
|
||||
...latestState,
|
||||
contributionNickname: message.payload.nickname,
|
||||
contributionQq: message.payload.qq,
|
||||
};
|
||||
return { state: latestState };
|
||||
}
|
||||
return {};
|
||||
},
|
||||
},
|
||||
@@ -560,21 +552,21 @@ test('contribution mode manager enters mode, starts main auto flow, polls contri
|
||||
});
|
||||
|
||||
manager.render();
|
||||
assert.equal(dom.contributionModePanel.hidden, true);
|
||||
assert.equal(dom.accountContributionPanel.hidden, true);
|
||||
assert.equal(dom.btnContributionMode.disabled, false);
|
||||
assert.equal(dom.contributionModeBadge.textContent, '');
|
||||
assert.equal(dom.accountContributionBadge.textContent, '');
|
||||
|
||||
manager.bindEvents();
|
||||
await dom.btnContributionMode.listeners.click();
|
||||
|
||||
assert.equal(dom.contributionModePanel.hidden, false);
|
||||
assert.equal(dom.accountContributionPanel.hidden, false);
|
||||
assert.equal(dom.selectPanelMode.value, 'sub2api');
|
||||
assert.equal(dom.selectPanelMode.disabled, true);
|
||||
assert.equal(dom.contributionModeBadge.textContent, 'SUB2API');
|
||||
assert.equal(dom.accountContributionBadge.textContent, 'SUB2API');
|
||||
assert.equal(dom.btnOpenAccountRecords.disabled, true);
|
||||
assert.equal(dom.contributionOauthStatus.textContent, '\u672a\u751f\u6210\u767b\u5f55\u5730\u5740');
|
||||
assert.equal(dom.contributionCallbackStatus.textContent, '\u7b49\u5f85\u56de\u8c03');
|
||||
assert.equal(dom.contributionModeSummary.textContent.length > 0, true);
|
||||
assert.equal(dom.accountContributionSummary.textContent.length > 0, true);
|
||||
assert.equal(dom.btnContributionMode.classList.contains('is-active'), true);
|
||||
assert.equal(dom.rowVpsUrl.classList.contains('is-contribution-hidden'), true);
|
||||
assert.equal(dom.rowCodex2ApiUrl.classList.contains('is-contribution-hidden'), true);
|
||||
@@ -595,28 +587,28 @@ test('contribution mode manager enters mode, starts main auto flow, polls contri
|
||||
assert.equal(appliedState.contributionSessionId, '');
|
||||
assert.equal(latestState.contributionSessionId, 'session-002');
|
||||
assert.equal(latestState.contributionStatus, 'started');
|
||||
const contributionProfileMessage = sentMessages.find((message) => message.type === 'SET_CONTRIBUTION_PROFILE');
|
||||
assert.deepStrictEqual(contributionProfileMessage?.payload, { nickname: '贡献者昵称', qq: '123456' });
|
||||
assert.equal(latestState.contributionNickname, '贡献者昵称');
|
||||
assert.equal(latestState.contributionQq, '123456');
|
||||
assert.equal(timers.length > 0, true);
|
||||
|
||||
await manager.pollOnce({ reason: 'test_poll' });
|
||||
assert.equal(statusState.contributionStatus, 'processing');
|
||||
assert.equal(dom.contributionOauthStatus.textContent, '\u5df2\u63d0\u4ea4\u56de\u8c03');
|
||||
assert.equal(dom.contributionCallbackStatus.textContent, '\u5df2\u63d0\u4ea4\u56de\u8c03');
|
||||
assert.equal(dom.contributionModeSummary.textContent, '\u5df2\u63d0\u4ea4\u56de\u8c03\uff0c\u7b49\u5f85\u670d\u52a1\u7aef\u786e\u8ba4');
|
||||
assert.equal(dom.accountContributionSummary.textContent, '\u5df2\u63d0\u4ea4\u56de\u8c03\uff0c\u7b49\u5f85\u670d\u52a1\u7aef\u786e\u8ba4');
|
||||
|
||||
dom.btnOpenContributionUpload.listeners.click();
|
||||
assert.deepStrictEqual(openedUrls, ['https://flowpilot.qlhazycoder.top', 'https://flowpilot.qlhazycoder.top/upload']);
|
||||
|
||||
await dom.btnExitContributionMode.listeners.click();
|
||||
manager.render();
|
||||
assert.equal(dom.contributionModePanel.hidden, true);
|
||||
assert.equal(dom.accountContributionPanel.hidden, true);
|
||||
assert.equal(dom.btnContributionMode.classList.contains('is-active'), false);
|
||||
assert.equal(dom.selectPanelMode.disabled, false);
|
||||
assert.equal(dom.rowVpsUrl.classList.contains('is-contribution-hidden'), false);
|
||||
assert.deepStrictEqual(
|
||||
sentMessages.map((message) => message.type),
|
||||
['SET_CONTRIBUTION_MODE', 'SET_CONTRIBUTION_PROFILE', 'POLL_CONTRIBUTION_STATUS', 'SET_CONTRIBUTION_MODE']
|
||||
['SET_ACCOUNT_CONTRIBUTION_MODE', 'POLL_FLOW_CONTRIBUTION_STATUS', 'SET_ACCOUNT_CONTRIBUTION_MODE']
|
||||
);
|
||||
assert.deepStrictEqual(
|
||||
toasts.map((item) => item.message),
|
||||
@@ -625,7 +617,7 @@ test('contribution mode manager enters mode, starts main auto flow, polls contri
|
||||
|
||||
blocked = true;
|
||||
latestState = {
|
||||
contributionMode: true,
|
||||
accountContributionEnabled: true,
|
||||
panelMode: 'sub2api',
|
||||
contributionSource: 'sub2api',
|
||||
contributionTargetGroupName: 'codex号池',
|
||||
@@ -640,7 +632,7 @@ test('contribution mode manager enters mode, starts main auto flow, polls contri
|
||||
};
|
||||
manager.render();
|
||||
assert.equal(dom.selectPanelMode.value, 'sub2api');
|
||||
assert.equal(dom.contributionModeBadge.textContent, 'SUB2API');
|
||||
assert.equal(dom.accountContributionBadge.textContent, 'SUB2API');
|
||||
assert.equal(dom.btnExitContributionMode.disabled, true);
|
||||
manager.stopPolling();
|
||||
});
|
||||
|
||||
@@ -84,7 +84,7 @@ test('collectSettingsPayload persists icloud target mailbox settings', () => {
|
||||
const bundle = extractFunction('collectSettingsPayload');
|
||||
|
||||
const api = new Function(`
|
||||
let latestState = { contributionMode: false };
|
||||
let latestState = { accountContributionEnabled: false };
|
||||
const window = {};
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
||||
|
||||
@@ -154,7 +154,7 @@ test('collectSettingsPayload persists currentMail2925AccountId for 2925 account
|
||||
|
||||
const api = new Function('normalizeIcloudTargetMailboxType', 'normalizeIcloudForwardMailProvider', `
|
||||
let latestState = {
|
||||
contributionMode: false,
|
||||
accountContributionEnabled: false,
|
||||
mail2925UseAccountPool: true,
|
||||
currentMail2925AccountId: 'acc-2',
|
||||
};
|
||||
|
||||
@@ -72,7 +72,7 @@ const localStorage = {
|
||||
},
|
||||
};
|
||||
const btnContributionMode = { disabled: false };
|
||||
let latestState = { contributionMode: false };
|
||||
let latestState = { accountContributionEnabled: false };
|
||||
${bundle}
|
||||
return {
|
||||
shouldPromptNewUserGuide,
|
||||
@@ -82,8 +82,8 @@ return {
|
||||
setButtonDisabled(value) {
|
||||
btnContributionMode.disabled = Boolean(value);
|
||||
},
|
||||
setContributionMode(value) {
|
||||
latestState = { contributionMode: Boolean(value) };
|
||||
setAccountContributionMode(value) {
|
||||
latestState = { accountContributionEnabled: Boolean(value) };
|
||||
},
|
||||
};
|
||||
`)();
|
||||
@@ -98,7 +98,7 @@ return {
|
||||
assert.equal(api.shouldPromptNewUserGuide(), false);
|
||||
|
||||
api.setButtonDisabled(false);
|
||||
api.setContributionMode(true);
|
||||
api.setAccountContributionMode(true);
|
||||
assert.equal(api.shouldPromptNewUserGuide(), false);
|
||||
});
|
||||
|
||||
@@ -129,7 +129,7 @@ const localStorage = {
|
||||
},
|
||||
};
|
||||
const btnContributionMode = { disabled: false };
|
||||
const latestState = { contributionMode: false };
|
||||
const latestState = { accountContributionEnabled: false };
|
||||
const contributionContentService = { portalUrl: 'https://flowpilot.qlhazycoder.top' };
|
||||
const openedUrls = [];
|
||||
let modalOptions = null;
|
||||
|
||||
@@ -337,7 +337,7 @@ const window = {
|
||||
};
|
||||
let latestState = {
|
||||
activeFlowId: 'site-a',
|
||||
contributionMode: false,
|
||||
accountContributionEnabled: false,
|
||||
panelMode: 'cpa',
|
||||
};
|
||||
const inputPhoneVerificationEnabled = { checked: true };
|
||||
@@ -877,7 +877,7 @@ const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
||||
const DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY = PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
|
||||
let latestState = {
|
||||
contributionMode: false,
|
||||
accountContributionEnabled: false,
|
||||
mail2925UseAccountPool: false,
|
||||
currentMail2925AccountId: '',
|
||||
fiveSimCountryOrder: ['thailand', 'england'],
|
||||
|
||||
Reference in New Issue
Block a user