Refactor auto-run flow state preservation
This commit is contained in:
@@ -0,0 +1,228 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
|
||||
test('auto-run controller preserves kiro flow across fresh reset and starts from the kiro first node', async () => {
|
||||
const source = fs.readFileSync('background/auto-run-controller.js', 'utf8');
|
||||
const globalScope = {};
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundAutoRunController;`)(globalScope);
|
||||
|
||||
const executedNodeIds = [];
|
||||
const kiroNodeIds = ['kiro-start-device-login', 'kiro-await-device-login', 'kiro-upload-credential'];
|
||||
const openAiNodeIds = ['open-chatgpt', 'submit-signup-email', 'fill-password'];
|
||||
let helperCalls = 0;
|
||||
let sessionSeed = 700;
|
||||
let currentState = {
|
||||
activeFlowId: 'kiro',
|
||||
flowId: 'kiro',
|
||||
panelMode: 'cpa',
|
||||
kiroSourceId: 'kiro-rs',
|
||||
kiroRsUrl: 'https://kiro.example/admin',
|
||||
kiroRsKey: 'demo-key',
|
||||
kiroRsApiRegion: 'ap-east-1',
|
||||
customFutureFlowField: 'future-ready',
|
||||
plusModeEnabled: false,
|
||||
plusPaymentMethod: 'paypal',
|
||||
phoneVerificationEnabled: false,
|
||||
phoneSignupReloginAfterBindEmailEnabled: false,
|
||||
autoRunSkipFailures: false,
|
||||
autoRunFallbackThreadIntervalMinutes: 0,
|
||||
autoRunDelayEnabled: false,
|
||||
autoRunDelayMinutes: 30,
|
||||
autoStepDelaySeconds: null,
|
||||
signupMethod: 'email',
|
||||
stepExecutionRangeByFlow: {
|
||||
openai: { enabled: false, fromStep: 1, toStep: 11 },
|
||||
kiro: { enabled: false, fromStep: 1, toStep: 3 },
|
||||
},
|
||||
nodeStatuses: {
|
||||
'open-chatgpt': 'stopped',
|
||||
'kiro-start-device-login': 'pending',
|
||||
'kiro-await-device-login': 'pending',
|
||||
'kiro-upload-credential': 'pending',
|
||||
},
|
||||
tabRegistry: {
|
||||
stale: { tabId: 99 },
|
||||
},
|
||||
sourceLastUrls: {
|
||||
stale: 'https://chatgpt.com/',
|
||||
},
|
||||
};
|
||||
|
||||
const runtime = {
|
||||
state: {
|
||||
autoRunActive: false,
|
||||
autoRunCurrentRun: 0,
|
||||
autoRunTotalRuns: 1,
|
||||
autoRunAttemptRun: 0,
|
||||
autoRunSessionId: 0,
|
||||
},
|
||||
get() {
|
||||
return { ...this.state };
|
||||
},
|
||||
set(updates = {}) {
|
||||
this.state = {
|
||||
...this.state,
|
||||
...updates,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
const controller = api.createAutoRunController({
|
||||
addLog: async () => {},
|
||||
appendAccountRunRecord: async () => null,
|
||||
AUTO_RUN_MAX_RETRIES_PER_ROUND: 3,
|
||||
AUTO_RUN_RETRY_DELAY_MS: 3000,
|
||||
AUTO_RUN_TIMER_KIND_BEFORE_RETRY: 'before_retry',
|
||||
AUTO_RUN_TIMER_KIND_BETWEEN_ROUNDS: 'between_rounds',
|
||||
broadcastAutoRunStatus: async (phase, payload = {}, extraState = {}) => {
|
||||
currentState = {
|
||||
...currentState,
|
||||
...extraState,
|
||||
autoRunning: ['scheduled', 'running', 'waiting_step', 'waiting_email', 'retrying', 'waiting_interval'].includes(phase),
|
||||
autoRunPhase: phase,
|
||||
autoRunCurrentRun: payload.currentRun ?? currentState.autoRunCurrentRun ?? 0,
|
||||
autoRunTotalRuns: payload.totalRuns ?? currentState.autoRunTotalRuns ?? 1,
|
||||
autoRunAttemptRun: payload.attemptRun ?? currentState.autoRunAttemptRun ?? 0,
|
||||
};
|
||||
},
|
||||
broadcastStopToContentScripts: async () => {},
|
||||
cancelPendingCommands: () => {},
|
||||
clearStopRequest: () => {},
|
||||
createAutoRunSessionId: () => {
|
||||
sessionSeed += 1;
|
||||
return sessionSeed;
|
||||
},
|
||||
buildFreshAutoRunKeepState: (prevState = {}, context = {}) => {
|
||||
helperCalls += 1;
|
||||
assert.equal(context.targetRun, 1);
|
||||
assert.equal(context.attemptRun, 1);
|
||||
return {
|
||||
activeFlowId: prevState.activeFlowId,
|
||||
flowId: prevState.activeFlowId,
|
||||
panelMode: prevState.panelMode,
|
||||
kiroSourceId: prevState.kiroSourceId,
|
||||
kiroRsUrl: prevState.kiroRsUrl,
|
||||
kiroRsKey: prevState.kiroRsKey,
|
||||
kiroRsApiRegion: prevState.kiroRsApiRegion,
|
||||
customFutureFlowField: prevState.customFutureFlowField,
|
||||
};
|
||||
},
|
||||
ensureHotmailMailboxReadyForAutoRunRound: async () => {},
|
||||
getAutoRunStatusPayload: (phase, payload = {}) => ({
|
||||
autoRunning: ['scheduled', 'running', 'waiting_step', 'waiting_email', 'retrying', 'waiting_interval'].includes(phase),
|
||||
autoRunPhase: phase,
|
||||
autoRunCurrentRun: payload.currentRun ?? 0,
|
||||
autoRunTotalRuns: payload.totalRuns ?? 1,
|
||||
autoRunAttemptRun: payload.attemptRun ?? 0,
|
||||
autoRunSessionId: payload.sessionId ?? 0,
|
||||
}),
|
||||
getErrorMessage: (error) => error?.message || String(error || ''),
|
||||
getFirstUnfinishedNodeId: (statuses = {}, state = {}) => {
|
||||
const flowId = String(state?.activeFlowId || state?.flowId || 'openai').trim().toLowerCase();
|
||||
const candidateNodeIds = flowId === 'kiro' ? kiroNodeIds : openAiNodeIds;
|
||||
for (const nodeId of candidateNodeIds) {
|
||||
const status = String(statuses?.[nodeId] || 'pending').trim().toLowerCase();
|
||||
if (!['completed', 'manual_completed', 'skipped'].includes(status)) {
|
||||
return nodeId;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
},
|
||||
getPendingAutoRunTimerPlan: () => null,
|
||||
getRunningNodeIds: () => [],
|
||||
getState: async () => ({
|
||||
...currentState,
|
||||
nodeStatuses: { ...(currentState.nodeStatuses || {}) },
|
||||
tabRegistry: { ...(currentState.tabRegistry || {}) },
|
||||
sourceLastUrls: { ...(currentState.sourceLastUrls || {}) },
|
||||
}),
|
||||
getStopRequested: () => false,
|
||||
hasSavedNodeProgress: () => false,
|
||||
isAddPhoneAuthFailure: () => false,
|
||||
isGpcTaskEndedFailure: () => false,
|
||||
isPhoneSmsPlatformRateLimitFailure: () => false,
|
||||
isPlusCheckoutNonFreeTrialFailure: () => false,
|
||||
isRestartCurrentAttemptError: () => false,
|
||||
isStep4Route405RecoveryLimitFailure: () => false,
|
||||
isSignupUserAlreadyExistsFailure: () => false,
|
||||
isStopError: () => false,
|
||||
launchAutoRunTimerPlan: async () => false,
|
||||
normalizeAutoRunFallbackThreadIntervalMinutes: (value) => Math.max(0, Number(value) || 0),
|
||||
persistAutoRunTimerPlan: async () => {},
|
||||
resetState: async () => {
|
||||
currentState = {
|
||||
activeFlowId: 'openai',
|
||||
flowId: 'openai',
|
||||
panelMode: 'cpa',
|
||||
kiroSourceId: '',
|
||||
kiroRsUrl: '',
|
||||
kiroRsKey: '',
|
||||
kiroRsApiRegion: '',
|
||||
customFutureFlowField: '',
|
||||
plusModeEnabled: false,
|
||||
plusPaymentMethod: 'paypal',
|
||||
phoneVerificationEnabled: false,
|
||||
phoneSignupReloginAfterBindEmailEnabled: false,
|
||||
autoRunSkipFailures: false,
|
||||
autoRunFallbackThreadIntervalMinutes: 0,
|
||||
autoRunDelayEnabled: false,
|
||||
autoRunDelayMinutes: 30,
|
||||
autoStepDelaySeconds: null,
|
||||
signupMethod: 'email',
|
||||
stepExecutionRangeByFlow: {
|
||||
openai: { enabled: false, fromStep: 1, toStep: 11 },
|
||||
kiro: { enabled: false, fromStep: 1, toStep: 3 },
|
||||
},
|
||||
nodeStatuses: {},
|
||||
tabRegistry: {},
|
||||
sourceLastUrls: {},
|
||||
};
|
||||
},
|
||||
runAutoSequenceFromNode: async (nodeId) => {
|
||||
executedNodeIds.push(nodeId);
|
||||
assert.equal(currentState.activeFlowId, 'kiro');
|
||||
assert.equal(currentState.flowId, 'kiro');
|
||||
assert.equal(currentState.kiroSourceId, 'kiro-rs');
|
||||
assert.equal(currentState.kiroRsApiRegion, 'ap-east-1');
|
||||
assert.equal(currentState.customFutureFlowField, 'future-ready');
|
||||
currentState = {
|
||||
...currentState,
|
||||
nodeStatuses: {
|
||||
'kiro-start-device-login': 'completed',
|
||||
'kiro-await-device-login': 'completed',
|
||||
'kiro-upload-credential': 'completed',
|
||||
},
|
||||
};
|
||||
},
|
||||
runtime,
|
||||
setState: async (updates = {}) => {
|
||||
currentState = {
|
||||
...currentState,
|
||||
...updates,
|
||||
nodeStatuses: updates.nodeStatuses
|
||||
? { ...updates.nodeStatuses }
|
||||
: currentState.nodeStatuses,
|
||||
tabRegistry: updates.tabRegistry
|
||||
? { ...updates.tabRegistry }
|
||||
: currentState.tabRegistry,
|
||||
sourceLastUrls: updates.sourceLastUrls
|
||||
? { ...updates.sourceLastUrls }
|
||||
: currentState.sourceLastUrls,
|
||||
};
|
||||
},
|
||||
sleepWithStop: async () => {},
|
||||
throwIfAutoRunSessionStopped: () => {},
|
||||
waitForRunningNodesToFinish: async () => currentState,
|
||||
chrome: {
|
||||
runtime: {
|
||||
sendMessage: () => Promise.resolve(),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await controller.autoRunLoop(1, { autoRunSkipFailures: false, mode: 'restart' });
|
||||
|
||||
assert.deepStrictEqual(executedNodeIds, ['kiro-start-device-login']);
|
||||
assert.equal(helperCalls, 1);
|
||||
});
|
||||
@@ -134,7 +134,6 @@ const FIVE_SIM_SUPPORTED_COUNTRY_ID_SET = new Set(['indonesia', 'thailand', 'vie
|
||||
const HERO_SMS_SUPPORTED_COUNTRY_ID_SET = new Set(['6', '52', '10']);
|
||||
const self = {
|
||||
MultiPageFlowRegistry: {
|
||||
DEFAULT_KIRO_REGION: 'us-east-1',
|
||||
DEFAULT_KIRO_RS_URL: 'https://kiro.leftcode.xyz/admin',
|
||||
normalizeFlowId(value, fallback = 'openai') {
|
||||
const normalized = String(value || '').trim().toLowerCase();
|
||||
@@ -279,7 +278,6 @@ return {
|
||||
assert.equal(api.normalizePersistentSettingValue('activeFlowId', 'kiro'), 'kiro');
|
||||
assert.equal(api.normalizePersistentSettingValue('kiroSourceId', 'unknown'), 'kiro-rs');
|
||||
assert.equal(api.normalizePersistentSettingValue('kiroRsUrl', ''), 'https://kiro.leftcode.xyz/admin');
|
||||
assert.equal(api.normalizePersistentSettingValue('kiroRegion', ''), 'us-east-1');
|
||||
assert.equal(api.normalizePersistentSettingValue('kiroRsKey', ' key-1 '), ' key-1 ');
|
||||
assert.equal(api.normalizePersistentSettingValue('phoneSmsProvider', '5SIM'), '5sim');
|
||||
assert.equal(api.normalizePersistentSettingValue('phoneSmsProvider', 'NEXSMS'), 'nexsms');
|
||||
|
||||
@@ -5,6 +5,7 @@ const fs = require('node:fs');
|
||||
test('background imports auto-run controller module', () => {
|
||||
const source = fs.readFileSync('background.js', 'utf8');
|
||||
assert.match(source, /background\/auto-run-controller\.js/);
|
||||
assert.match(source, /buildFreshAutoRunKeepState/);
|
||||
});
|
||||
|
||||
test('auto-run controller module exposes a factory', () => {
|
||||
|
||||
@@ -76,7 +76,6 @@ test('kiro start device login registers client, opens auth tab, and completes wi
|
||||
throw new Error(`Unexpected fetch URL: ${url}`);
|
||||
},
|
||||
getState: async () => ({
|
||||
kiroRegion: 'eu-west-1',
|
||||
}),
|
||||
registerTab: async (source, tabId) => {
|
||||
registerCalls.push({ source, tabId });
|
||||
@@ -94,12 +93,11 @@ test('kiro start device login registers client, opens auth tab, and completes wi
|
||||
|
||||
await executor.executeKiroStartDeviceLogin({
|
||||
nodeId: 'kiro-start-device-login',
|
||||
kiroRegion: 'eu-west-1',
|
||||
});
|
||||
|
||||
assert.equal(fetchCalls.length, 2);
|
||||
assert.equal(fetchCalls[0].url, 'https://oidc.eu-west-1.amazonaws.com/client/register');
|
||||
assert.equal(fetchCalls[1].url, 'https://oidc.eu-west-1.amazonaws.com/device_authorization');
|
||||
assert.equal(fetchCalls[0].url, 'https://oidc.us-east-1.amazonaws.com/client/register');
|
||||
assert.equal(fetchCalls[1].url, 'https://oidc.us-east-1.amazonaws.com/device_authorization');
|
||||
assert.deepEqual(fetchCalls[1].body, {
|
||||
clientId: 'client-001',
|
||||
clientSecret: 'secret-001',
|
||||
@@ -120,7 +118,7 @@ test('kiro start device login registers client, opens auth tab, and completes wi
|
||||
assert.equal(finalState.kiroDeviceAuthorizationCode, 'device-code-001');
|
||||
assert.equal(finalState.kiroDeviceCode, 'ABCD-1234');
|
||||
assert.equal(finalState.kiroLoginUrl, 'https://device.example.com/complete');
|
||||
assert.equal(finalState.kiroAuthRegion, 'eu-west-1');
|
||||
assert.equal(finalState.kiroAuthRegion, 'us-east-1');
|
||||
assert.equal(finalState.kiroAuthIntervalSeconds, 7);
|
||||
assert.equal(finalState.kiroAuthStatus, 'waiting_user');
|
||||
assert.equal(finalState.kiroUploadStatus, 'waiting_login');
|
||||
|
||||
@@ -296,6 +296,125 @@ test('SAVE_SETTING broadcasts operation delay setting without background success
|
||||
assert.equal(logs.length, 0);
|
||||
});
|
||||
|
||||
test('SAVE_SETTING mirrors activeFlowId into flowId when switching to kiro flow', async () => {
|
||||
const source = fs.readFileSync('background/message-router.js', 'utf8');
|
||||
const globalScope = { console };
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundMessageRouter;`)(globalScope);
|
||||
const broadcasts = [];
|
||||
let state = { activeFlowId: 'openai', flowId: 'openai', panelMode: 'cpa', plusModeEnabled: false, plusPaymentMethod: 'paypal' };
|
||||
|
||||
const router = api.createMessageRouter({
|
||||
addLog: async () => {},
|
||||
buildLuckmailSessionSettingsPayload: () => ({}),
|
||||
buildPersistentSettingsPayload: (input = {}) => Object.prototype.hasOwnProperty.call(input, 'activeFlowId')
|
||||
? { activeFlowId: input.activeFlowId }
|
||||
: {},
|
||||
broadcastDataUpdate: (payload) => broadcasts.push(payload),
|
||||
getState: async () => ({ ...state }),
|
||||
setPersistentSettings: async () => {},
|
||||
setState: async (updates) => { state = { ...state, ...updates }; },
|
||||
});
|
||||
|
||||
const response = await router.handleMessage({
|
||||
type: 'SAVE_SETTING',
|
||||
source: 'sidepanel',
|
||||
payload: { activeFlowId: 'kiro' },
|
||||
});
|
||||
|
||||
assert.equal(response.ok, true);
|
||||
assert.equal(state.activeFlowId, 'kiro');
|
||||
assert.equal(state.flowId, 'kiro');
|
||||
assert.deepStrictEqual(broadcasts.at(-1), {
|
||||
activeFlowId: 'kiro',
|
||||
flowId: 'kiro',
|
||||
signupMethod: 'email',
|
||||
});
|
||||
});
|
||||
|
||||
test('AUTO_RUN applies current flow selection from payload before starting loop', async () => {
|
||||
const source = fs.readFileSync('background/message-router.js', 'utf8');
|
||||
const globalScope = { console };
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundMessageRouter;`)(globalScope);
|
||||
const calls = [];
|
||||
const validations = [];
|
||||
let state = {
|
||||
activeFlowId: 'openai',
|
||||
flowId: 'openai',
|
||||
panelMode: 'cpa',
|
||||
plusModeEnabled: false,
|
||||
plusPaymentMethod: 'paypal',
|
||||
};
|
||||
|
||||
const router = api.createMessageRouter({
|
||||
clearStopRequest: () => {},
|
||||
getPendingAutoRunTimerPlan: () => null,
|
||||
getState: async () => ({ ...state }),
|
||||
normalizeRunCount: (value) => Number(value) || 1,
|
||||
setState: async (updates) => {
|
||||
calls.push({ type: 'setState', updates: { ...updates } });
|
||||
state = { ...state, ...updates };
|
||||
},
|
||||
startAutoRunLoop: (totalRuns, options) => {
|
||||
calls.push({ type: 'startAutoRunLoop', totalRuns, options });
|
||||
},
|
||||
validateAutoRunStart: (validationState, options = {}) => {
|
||||
validations.push({
|
||||
activeFlowId: validationState?.activeFlowId,
|
||||
flowId: validationState?.flowId,
|
||||
kiroSourceId: validationState?.kiroSourceId,
|
||||
optionActiveFlowId: options?.activeFlowId,
|
||||
});
|
||||
return { ok: true, errors: [] };
|
||||
},
|
||||
});
|
||||
|
||||
const response = await router.handleMessage({
|
||||
type: 'AUTO_RUN',
|
||||
payload: {
|
||||
totalRuns: 1,
|
||||
activeFlowId: 'kiro',
|
||||
sourceId: 'kiro-rs',
|
||||
},
|
||||
});
|
||||
|
||||
assert.equal(response.ok, true);
|
||||
assert.equal(state.activeFlowId, 'kiro');
|
||||
assert.equal(state.flowId, 'kiro');
|
||||
assert.equal(state.kiroSourceId, 'kiro-rs');
|
||||
assert.deepStrictEqual(calls, [
|
||||
{
|
||||
type: 'setState',
|
||||
updates: {
|
||||
activeFlowId: 'kiro',
|
||||
flowId: 'kiro',
|
||||
kiroSourceId: 'kiro-rs',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'setState',
|
||||
updates: {
|
||||
autoRunSkipFailures: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'startAutoRunLoop',
|
||||
totalRuns: 1,
|
||||
options: {
|
||||
autoRunSkipFailures: false,
|
||||
mode: 'restart',
|
||||
},
|
||||
},
|
||||
]);
|
||||
assert.deepStrictEqual(validations, [
|
||||
{
|
||||
activeFlowId: 'kiro',
|
||||
flowId: 'kiro',
|
||||
kiroSourceId: 'kiro-rs',
|
||||
optionActiveFlowId: 'kiro',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('SAVE_SETTING re-resolves signup method when panel mode changes', async () => {
|
||||
const source = fs.readFileSync('background/message-router.js', 'utf8');
|
||||
const globalScope = { console };
|
||||
|
||||
@@ -136,3 +136,34 @@ test('runtime-state patch accepts nested flow and node updates without legacy st
|
||||
assert.equal(patch.runtimeState.flowState.openai.auth.oauthUrl, 'https://new.example.com/start');
|
||||
assert.equal(patch.runtimeState.flowState.openai.plus.plusCheckoutTabId, 99);
|
||||
});
|
||||
|
||||
test('runtime-state patch prefers explicit activeFlowId over stale legacy flowId', () => {
|
||||
const api = loadRuntimeStateApi();
|
||||
const helpers = api.createRuntimeStateHelpers({
|
||||
DEFAULT_ACTIVE_FLOW_ID: 'openai',
|
||||
defaultNodeStatuses: {
|
||||
'open-chatgpt': 'pending',
|
||||
'kiro-start-device-login': 'pending',
|
||||
},
|
||||
});
|
||||
|
||||
const patch = helpers.buildSessionStatePatch({
|
||||
flowId: 'openai',
|
||||
activeFlowId: 'openai',
|
||||
nodeStatuses: {
|
||||
'open-chatgpt': 'completed',
|
||||
},
|
||||
}, {
|
||||
activeFlowId: 'kiro',
|
||||
nodeStatuses: {
|
||||
'kiro-start-device-login': 'running',
|
||||
},
|
||||
});
|
||||
|
||||
assert.equal(patch.activeFlowId, 'kiro');
|
||||
assert.equal(patch.flowId, 'kiro');
|
||||
assert.deepStrictEqual(patch.nodeStatuses, {
|
||||
'open-chatgpt': 'pending',
|
||||
'kiro-start-device-login': 'running',
|
||||
});
|
||||
});
|
||||
|
||||
@@ -71,7 +71,6 @@ const PERSISTED_SETTING_DEFAULTS = {
|
||||
kiroSourceId: 'kiro-rs',
|
||||
kiroRsUrl: 'https://kiro.leftcode.xyz/admin',
|
||||
kiroRsKey: '',
|
||||
kiroRegion: 'us-east-1',
|
||||
stepExecutionRangeByFlow: {},
|
||||
};
|
||||
const PERSISTED_SETTING_KEYS = Object.keys(PERSISTED_SETTING_DEFAULTS);
|
||||
@@ -153,14 +152,13 @@ test('buildPersistentSettingsPayload writes canonical settings schema into persi
|
||||
activeFlowId: 'kiro',
|
||||
kiroRsUrl: 'https://kiro.example.com/admin',
|
||||
kiroRsKey: 'secret-key',
|
||||
kiroRegion: 'eu-west-1',
|
||||
}, { fillDefaults: true });
|
||||
|
||||
assert.equal(payload.activeFlowId, 'kiro');
|
||||
assert.equal(payload.kiroSourceId, 'kiro-rs');
|
||||
assert.equal(payload.kiroRsUrl, 'https://kiro.example.com/admin');
|
||||
assert.equal(payload.kiroRsKey, 'secret-key');
|
||||
assert.equal(payload.kiroRegion, 'eu-west-1');
|
||||
assert.equal(Object.prototype.hasOwnProperty.call(payload, 'kiroRegion'), false);
|
||||
assert.equal(payload.settingsSchemaVersion, 3);
|
||||
assert.equal(payload.settingsState.activeFlowId, 'kiro');
|
||||
assert.equal(payload.settingsState.flows.kiro.source.selected, 'kiro-rs');
|
||||
@@ -205,7 +203,6 @@ test('buildPersistentSettingsPayload accepts schema-only input when requireKnown
|
||||
},
|
||||
},
|
||||
options: {
|
||||
kiroRegion: 'eu-west-1',
|
||||
},
|
||||
autoRun: {
|
||||
stepExecutionRange: { enabled: true, fromStep: 1, toStep: 3 },
|
||||
@@ -219,7 +216,7 @@ test('buildPersistentSettingsPayload accepts schema-only input when requireKnown
|
||||
assert.equal(payload.kiroSourceId, 'kiro-rs');
|
||||
assert.equal(payload.kiroRsUrl, 'https://kiro.example.com/admin');
|
||||
assert.equal(payload.kiroRsKey, 'schema-only-key');
|
||||
assert.equal(payload.kiroRegion, 'eu-west-1');
|
||||
assert.equal(Object.prototype.hasOwnProperty.call(payload, 'kiroRegion'), false);
|
||||
assert.equal(payload.settingsSchemaVersion, 3);
|
||||
});
|
||||
|
||||
@@ -294,7 +291,6 @@ const chrome = {
|
||||
},
|
||||
},
|
||||
options: {
|
||||
kiroRegion: 'ap-southeast-1',
|
||||
},
|
||||
autoRun: {
|
||||
stepExecutionRange: { enabled: true, fromStep: 1, toStep: 3 },
|
||||
@@ -317,7 +313,7 @@ const chrome = {
|
||||
assert.equal(state.ipProxyEnabled, true);
|
||||
assert.equal(state.kiroRsUrl, 'https://kiro.example.com/admin');
|
||||
assert.equal(state.kiroRsKey, 'stored-key');
|
||||
assert.equal(state.kiroRegion, 'ap-southeast-1');
|
||||
assert.equal(Object.prototype.hasOwnProperty.call(state, 'kiroRegion'), false);
|
||||
assert.deepEqual(state.stepExecutionRangeByFlow.kiro, {
|
||||
enabled: true,
|
||||
fromStep: 1,
|
||||
@@ -381,7 +377,6 @@ function getPersistedWrites() {
|
||||
},
|
||||
},
|
||||
options: {
|
||||
kiroRegion: 'us-west-2',
|
||||
},
|
||||
autoRun: {
|
||||
stepExecutionRange: { enabled: true, fromStep: 1, toStep: 3 },
|
||||
@@ -396,12 +391,12 @@ function getPersistedWrites() {
|
||||
assert.equal(persisted.activeFlowId, 'kiro');
|
||||
assert.equal(persisted.kiroRsUrl, 'https://kiro.example.com/admin');
|
||||
assert.equal(persisted.kiroRsKey, 'nested-only-key');
|
||||
assert.equal(persisted.kiroRegion, 'us-west-2');
|
||||
assert.equal(Object.prototype.hasOwnProperty.call(persisted, 'kiroRegion'), false);
|
||||
assert.equal(persisted.settingsSchemaVersion, 3);
|
||||
assert.equal(write.activeFlowId, 'kiro');
|
||||
assert.equal(write.kiroRsUrl, 'https://kiro.example.com/admin');
|
||||
assert.equal(write.kiroRsKey, 'nested-only-key');
|
||||
assert.equal(write.kiroRegion, 'us-west-2');
|
||||
assert.equal(Object.prototype.hasOwnProperty.call(write, 'kiroRegion'), false);
|
||||
assert.equal(write.settingsSchemaVersion, 3);
|
||||
assert.equal(write.settingsState.activeFlowId, 'kiro');
|
||||
});
|
||||
|
||||
@@ -47,7 +47,6 @@ test('settings schema normalizes flat input into canonical flow and service name
|
||||
ipProxyService: '711proxy',
|
||||
kiroRsUrl: 'https://kiro.example.com/admin',
|
||||
kiroRsKey: 'secret-key',
|
||||
kiroRegion: 'eu-west-1',
|
||||
stepExecutionRangeByFlow: {
|
||||
openai: { enabled: true, fromStep: 2, toStep: 9 },
|
||||
kiro: { enabled: true, fromStep: 1, toStep: 3 },
|
||||
@@ -61,7 +60,6 @@ test('settings schema normalizes flat input into canonical flow and service name
|
||||
assert.equal(normalized.flows.kiro.source.selected, 'kiro-rs');
|
||||
assert.equal(normalized.flows.kiro.source.entries['kiro-rs'].kiroRsUrl, 'https://kiro.example.com/admin');
|
||||
assert.equal(normalized.flows.kiro.source.entries['kiro-rs'].kiroRsKey, 'secret-key');
|
||||
assert.equal(normalized.flows.kiro.options.kiroRegion, 'eu-west-1');
|
||||
assert.deepEqual(normalized.flows.kiro.autoRun.stepExecutionRange, {
|
||||
enabled: true,
|
||||
fromStep: 1,
|
||||
@@ -77,7 +75,6 @@ test('settings schema can project canonical state back to legacy payload without
|
||||
kiroSourceId: 'kiro-rs',
|
||||
kiroRsUrl: 'https://kiro.example.com/admin',
|
||||
kiroRsKey: 'key-123',
|
||||
kiroRegion: 'ap-southeast-1',
|
||||
}));
|
||||
|
||||
assert.equal(payload.activeFlowId, 'kiro');
|
||||
@@ -85,7 +82,7 @@ test('settings schema can project canonical state back to legacy payload without
|
||||
assert.equal(payload.kiroSourceId, 'kiro-rs');
|
||||
assert.equal(payload.kiroRsUrl, 'https://kiro.example.com/admin');
|
||||
assert.equal(payload.kiroRsKey, 'key-123');
|
||||
assert.equal(payload.kiroRegion, 'ap-southeast-1');
|
||||
assert.equal(Object.prototype.hasOwnProperty.call(payload, 'kiroRegion'), false);
|
||||
assert.equal(payload.settingsSchemaVersion, 3);
|
||||
assert.equal(payload.settingsState.activeFlowId, 'kiro');
|
||||
});
|
||||
|
||||
@@ -60,9 +60,16 @@ function createApi({
|
||||
extractFunction('startAutoRunFromCurrentSettings'),
|
||||
].join('\n');
|
||||
|
||||
return new Function(`
|
||||
return new Function(`
|
||||
const events = [];
|
||||
const latestState = { contributionMode: false };
|
||||
const DEFAULT_ACTIVE_FLOW_ID = 'openai';
|
||||
const latestState = {
|
||||
contributionMode: false,
|
||||
activeFlowId: 'openai',
|
||||
flowId: 'openai',
|
||||
panelMode: 'cpa',
|
||||
kiroSourceId: 'kiro-rs',
|
||||
};
|
||||
const inputAutoSkipFailures = { checked: false };
|
||||
const inputContributionNickname = { value: 'tester' };
|
||||
const inputContributionQq = { value: '123456' };
|
||||
@@ -71,6 +78,8 @@ const inputAutoDelayEnabled = { checked: false };
|
||||
const inputAutoDelayMinutes = { value: '30' };
|
||||
const btnAutoRun = { disabled: false, innerHTML: '' };
|
||||
const inputRunCount = { disabled: false };
|
||||
const selectFlow = { value: latestState.activeFlowId };
|
||||
const selectPanelMode = { value: latestState.panelMode };
|
||||
let runCountValue = ${Math.max(1, Number(runCount) || 1)};
|
||||
let pendingAutoRunStartTotalRuns = 0;
|
||||
let pendingAutoRunStartExpiresAt = 0;
|
||||
@@ -103,6 +112,19 @@ async function persistCurrentSettingsForAction() {
|
||||
}
|
||||
function getRunCountValue() { return Math.max(1, Number(runCountValue) || 1); }
|
||||
function normalizeAutoRunThreadIntervalMinutes(value) { return Number(value) || 0; }
|
||||
function normalizePanelMode(value = '', fallback = 'cpa') {
|
||||
return String(value || fallback || 'cpa').trim().toLowerCase() || 'cpa';
|
||||
}
|
||||
function getSelectedFlowId(state = latestState) {
|
||||
return String(selectFlow.value || state.activeFlowId || state.flowId || DEFAULT_ACTIVE_FLOW_ID).trim().toLowerCase() || DEFAULT_ACTIVE_FLOW_ID;
|
||||
}
|
||||
function getSelectedSourceId(flowId = getSelectedFlowId()) {
|
||||
return String(
|
||||
flowId === 'kiro'
|
||||
? (selectPanelMode.value || latestState.kiroSourceId || 'kiro-rs')
|
||||
: normalizePanelMode(selectPanelMode.value || latestState.panelMode || 'cpa')
|
||||
).trim().toLowerCase() || (flowId === 'kiro' ? 'kiro-rs' : 'cpa');
|
||||
}
|
||||
function shouldOfferAutoModeChoice() { return false; }
|
||||
async function openAutoStartChoiceDialog() { throw new Error('should not be called'); }
|
||||
function getFirstUnfinishedStep() { return 1; }
|
||||
@@ -196,6 +218,26 @@ test('startAutoRunFromCurrentSettings freezes run count before async settings sy
|
||||
assert.equal(events[3].message.payload.totalRuns, 20);
|
||||
});
|
||||
|
||||
test('startAutoRunFromCurrentSettings sends current flow selection with auto run payload', async () => {
|
||||
const api = createApi({
|
||||
persistImpl: `(events) => {
|
||||
selectFlow.value = 'kiro';
|
||||
selectPanelMode.value = 'kiro-rs';
|
||||
latestState.activeFlowId = 'openai';
|
||||
latestState.flowId = 'openai';
|
||||
latestState.kiroSourceId = 'kiro-rs';
|
||||
events.push({ type: 'flow-switch-race' });
|
||||
}`,
|
||||
});
|
||||
|
||||
const result = await api.startAutoRunFromCurrentSettings();
|
||||
const sendEvent = api.getEvents().find((entry) => entry.type === 'send');
|
||||
|
||||
assert.equal(result, true);
|
||||
assert.equal(sendEvent.message.payload.activeFlowId, 'kiro');
|
||||
assert.equal(sendEvent.message.payload.sourceId, 'kiro-rs');
|
||||
});
|
||||
|
||||
test('startAutoRunFromCurrentSettings blocks when shared flow capability validation fails', async () => {
|
||||
const bundle = [
|
||||
extractFunction('normalizePendingAutoRunStartRunCount'),
|
||||
|
||||
@@ -39,7 +39,6 @@ test('sidepanel html exposes flow selector and kiro source fields', () => {
|
||||
'id="label-source-selector"',
|
||||
'id="row-kiro-rs-url"',
|
||||
'id="row-kiro-rs-key"',
|
||||
'id="row-kiro-region"',
|
||||
'id="row-kiro-device-code"',
|
||||
'id="row-kiro-login-url"',
|
||||
'id="row-kiro-upload-status"',
|
||||
@@ -114,6 +113,52 @@ return {
|
||||
assert.deepEqual(api.calls[1], { type: 'render', stepIds: [88] });
|
||||
});
|
||||
|
||||
test('syncLatestState keeps activeFlowId and flowId in sync when only one side changes', () => {
|
||||
const bundle = [
|
||||
extractFunction(sidepanelSource, 'syncLatestState'),
|
||||
].join('\n');
|
||||
|
||||
const api = new Function(`
|
||||
let latestState = {
|
||||
activeFlowId: 'openai',
|
||||
flowId: 'openai',
|
||||
nodeStatuses: { 'open-chatgpt': 'completed' },
|
||||
};
|
||||
const DEFAULT_ACTIVE_FLOW_ID = 'openai';
|
||||
const NODE_DEFAULT_STATUSES = { 'open-chatgpt': 'pending' };
|
||||
const calls = [];
|
||||
function normalizeFlowId(value = '', fallback = DEFAULT_ACTIVE_FLOW_ID) {
|
||||
return String(value || fallback || DEFAULT_ACTIVE_FLOW_ID).trim().toLowerCase() || DEFAULT_ACTIVE_FLOW_ID;
|
||||
}
|
||||
function getStoredNodeStatuses(state = {}) {
|
||||
return { ...NODE_DEFAULT_STATUSES, ...(state?.nodeStatuses || {}) };
|
||||
}
|
||||
function renderAccountRecords(state) {
|
||||
calls.push({ ...state });
|
||||
}
|
||||
${bundle}
|
||||
return {
|
||||
syncLatestState,
|
||||
getLatestState() {
|
||||
return latestState;
|
||||
},
|
||||
getCalls() {
|
||||
return calls;
|
||||
},
|
||||
};
|
||||
`)();
|
||||
|
||||
api.syncLatestState({ flowId: 'kiro' });
|
||||
|
||||
assert.deepStrictEqual(api.getLatestState(), {
|
||||
activeFlowId: 'kiro',
|
||||
flowId: 'kiro',
|
||||
nodeStatuses: { 'open-chatgpt': 'completed' },
|
||||
});
|
||||
assert.equal(api.getCalls()[0].activeFlowId, 'kiro');
|
||||
assert.equal(api.getCalls()[0].flowId, 'kiro');
|
||||
});
|
||||
|
||||
test('updatePanelModeUI reapplies dynamic Plus and phone visibility after flow group visibility', () => {
|
||||
const bundle = [
|
||||
extractFunction(sidepanelSource, 'updatePanelModeUI'),
|
||||
|
||||
Reference in New Issue
Block a user