Files
FlowPilot/tests/auto-run-phone-signup-success-email-cleanup.test.js
T
2026-05-28 06:52:35 +08:00

314 lines
11 KiB
JavaScript

const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
function loadAutoRunControllerApi() {
const source = fs.readFileSync('background/auto-run-controller.js', 'utf8');
const globalScope = {};
return new Function('self', `${source}; return self.MultiPageBackgroundAutoRunController;`)(globalScope);
}
const FULL_NODE_IDS = [
'open-chatgpt',
'submit-signup-email',
'fill-password',
'fetch-signup-code',
'fill-profile',
'wait-registration-success',
'oauth-login',
'fetch-login-code',
'confirm-oauth',
'platform-verify',
];
const EMPTY_REGISTRATION_EMAIL_STATE = {
current: '',
previous: '',
source: '',
updatedAt: 0,
};
const PHONE_NUMBER = '+6612345';
const PHONE_ACTIVATION = {
activationId: 'signup-completed',
phoneNumber: PHONE_NUMBER,
};
function createNodeStatuses(doneNodeIds = []) {
const doneSet = new Set(doneNodeIds);
return Object.fromEntries(FULL_NODE_IDS.map((nodeId) => [
nodeId,
doneSet.has(nodeId) ? 'completed' : 'pending',
]));
}
function createBaseState() {
return {
activeFlowId: 'openai',
flowId: 'openai',
signupMethod: 'phone',
resolvedSignupMethod: 'phone',
autoRunFallbackThreadIntervalMinutes: 0,
autoRunSkipFailures: false,
nodeStatuses: createNodeStatuses([]),
stepStatuses: {},
};
}
function createHarness({ completedNodeIds = [], initialState = {} } = {}) {
const api = loadAutoRunControllerApi();
let currentState = {
...createBaseState(),
...initialState,
};
let runCalls = 0;
let sessionSeed = 1000;
const clone = (value) => JSON.parse(JSON.stringify(value));
async function getState() {
return clone(currentState);
}
async function setState(updates = {}) {
currentState = {
...currentState,
...updates,
nodeStatuses: updates.nodeStatuses ? { ...updates.nodeStatuses } : currentState.nodeStatuses,
stepStatuses: updates.stepStatuses ? { ...updates.stepStatuses } : currentState.stepStatuses,
};
}
async function resetState() {
currentState = {
activeFlowId: currentState.activeFlowId,
flowId: currentState.flowId,
signupMethod: currentState.signupMethod,
resolvedSignupMethod: currentState.resolvedSignupMethod,
autoRunFallbackThreadIntervalMinutes: currentState.autoRunFallbackThreadIntervalMinutes,
autoRunSkipFailures: currentState.autoRunSkipFailures,
nodeStatuses: createNodeStatuses([]),
stepStatuses: {},
currentPhoneActivation: currentState.currentPhoneActivation,
phoneNumber: currentState.phoneNumber,
accountIdentifierType: currentState.accountIdentifierType,
accountIdentifier: currentState.accountIdentifier,
signupPhoneNumber: currentState.signupPhoneNumber,
signupPhoneActivation: currentState.signupPhoneActivation,
signupPhoneCompletedActivation: currentState.signupPhoneCompletedActivation,
signupPhoneVerificationRequestedAt: currentState.signupPhoneVerificationRequestedAt,
signupPhoneVerificationPurpose: currentState.signupPhoneVerificationPurpose,
currentPhoneVerificationCode: currentState.currentPhoneVerificationCode,
currentPhoneVerificationCountdownEndsAt: currentState.currentPhoneVerificationCountdownEndsAt,
currentPhoneVerificationCountdownWindowIndex: currentState.currentPhoneVerificationCountdownWindowIndex,
currentPhoneVerificationCountdownWindowTotal: currentState.currentPhoneVerificationCountdownWindowTotal,
email: currentState.email,
registrationEmailState: currentState.registrationEmailState,
step8VerificationTargetEmail: currentState.step8VerificationTargetEmail,
lastEmailTimestamp: currentState.lastEmailTimestamp,
lastSignupCode: currentState.lastSignupCode,
lastLoginCode: currentState.lastLoginCode,
bindEmailSubmitted: currentState.bindEmailSubmitted,
};
}
async function runAutoSequenceFromNode() {
runCalls += 1;
if (runCalls === 2) {
assert.equal(currentState.email, null);
assert.equal(currentState.currentPhoneActivation, null);
assert.equal(currentState.phoneNumber, '');
assert.equal(currentState.signupPhoneNumber, '');
assert.equal(currentState.accountIdentifierType, null);
assert.equal(currentState.accountIdentifier, '');
}
await setState({
accountIdentifierType: 'phone',
accountIdentifier: PHONE_NUMBER,
currentPhoneActivation: PHONE_ACTIVATION,
phoneNumber: PHONE_NUMBER,
signupPhoneNumber: PHONE_NUMBER,
signupPhoneActivation: PHONE_ACTIVATION,
signupPhoneCompletedActivation: PHONE_ACTIVATION,
signupPhoneVerificationRequestedAt: 123,
signupPhoneVerificationPurpose: 'signup',
currentPhoneVerificationCode: '222222',
currentPhoneVerificationCountdownEndsAt: Date.now() + 60000,
currentPhoneVerificationCountdownWindowIndex: 1,
currentPhoneVerificationCountdownWindowTotal: 2,
email: 'bound.user@example.com',
registrationEmailState: {
current: 'bound.user@example.com',
previous: 'old.bound@example.com',
source: 'bind_email',
updatedAt: 123,
},
step8VerificationTargetEmail: 'bound.user@example.com',
lastEmailTimestamp: 456,
lastSignupCode: '111111',
lastLoginCode: '222222',
bindEmailSubmitted: true,
nodeStatuses: createNodeStatuses(completedNodeIds),
});
}
const runtime = {
state: {},
get() {
return { ...this.state };
},
set(updates = {}) {
this.state = { ...this.state, ...updates };
},
};
const controller = api.createAutoRunController({
addLog: async () => {},
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 () => {},
broadcastStopToContentScripts: async () => {},
cancelPendingCommands: () => {},
clearStopRequest: () => {},
createAutoRunSessionId: () => {
sessionSeed += 1;
return sessionSeed;
},
getAutoRunStatusPayload: () => ({}),
getErrorMessage: (error) => error?.message || String(error || ''),
getFirstUnfinishedNodeId: () => 'open-chatgpt',
getNodeIdsForState: () => FULL_NODE_IDS.slice(),
getPendingAutoRunTimerPlan: () => null,
getRunningNodeIds: () => [],
getState,
getStopRequested: () => false,
hasSavedNodeProgress: () => false,
isRestartCurrentAttemptError: () => false,
isStopError: () => false,
launchAutoRunTimerPlan: async () => false,
normalizeAutoRunFallbackThreadIntervalMinutes: () => 0,
persistAutoRunTimerPlan: async () => {},
resetState,
runAutoSequenceFromNode,
runtime,
setState,
sleepWithStop: async () => {},
throwIfAutoRunSessionStopped: () => {},
waitForRunningNodesToFinish: getState,
chrome: {
runtime: {
sendMessage: () => Promise.resolve(),
},
},
});
return {
controller,
getState: () => currentState,
};
}
test('auto-run clears phone signup and bound email runtime only after full workflow success', async () => {
const { controller, getState } = createHarness({ completedNodeIds: FULL_NODE_IDS });
await controller.autoRunLoop(1, { mode: 'restart', autoRunSkipFailures: false });
const state = getState();
assert.equal(state.email, null);
assert.deepEqual(state.registrationEmailState, EMPTY_REGISTRATION_EMAIL_STATE);
assert.equal(state.step8VerificationTargetEmail, '');
assert.equal(state.lastEmailTimestamp, null);
assert.equal(state.lastSignupCode, '');
assert.equal(state.lastLoginCode, '');
assert.equal(state.bindEmailSubmitted, false);
assert.equal(state.currentPhoneActivation, null);
assert.equal(state.currentPhoneVerificationCode, '');
assert.equal(state.currentPhoneVerificationCountdownEndsAt, 0);
assert.equal(state.currentPhoneVerificationCountdownWindowIndex, 0);
assert.equal(state.currentPhoneVerificationCountdownWindowTotal, 0);
assert.equal(state.accountIdentifierType, null);
assert.equal(state.accountIdentifier, '');
assert.equal(state.phoneNumber, '');
assert.equal(state.signupPhoneNumber, '');
assert.equal(state.signupPhoneActivation, null);
assert.equal(state.signupPhoneCompletedActivation, null);
});
test('auto-run keeps phone signup and bound email runtime when workflow is only partially done', async () => {
const { controller, getState } = createHarness({
completedNodeIds: ['open-chatgpt', 'submit-signup-email', 'fill-password'],
});
await controller.autoRunLoop(1, { mode: 'restart', autoRunSkipFailures: false });
const state = getState();
assert.equal(state.email, 'bound.user@example.com');
assert.deepEqual(state.registrationEmailState, {
current: 'bound.user@example.com',
previous: 'old.bound@example.com',
source: 'bind_email',
updatedAt: 123,
});
assert.equal(state.step8VerificationTargetEmail, 'bound.user@example.com');
assert.equal(state.lastEmailTimestamp, 456);
assert.equal(state.lastSignupCode, '111111');
assert.equal(state.lastLoginCode, '222222');
assert.equal(state.bindEmailSubmitted, true);
assert.deepEqual(state.currentPhoneActivation, PHONE_ACTIVATION);
assert.equal(state.currentPhoneVerificationCode, '222222');
assert.equal(state.accountIdentifierType, 'phone');
assert.equal(state.accountIdentifier, PHONE_NUMBER);
assert.equal(state.phoneNumber, PHONE_NUMBER);
assert.equal(state.signupPhoneNumber, PHONE_NUMBER);
assert.deepEqual(state.signupPhoneActivation, PHONE_ACTIVATION);
assert.deepEqual(state.signupPhoneCompletedActivation, PHONE_ACTIVATION);
});
test('auto-run cleanup prevents next phone signup run from reusing previous identity', async () => {
const { controller, getState } = createHarness({ completedNodeIds: FULL_NODE_IDS });
await controller.autoRunLoop(2, { mode: 'restart', autoRunSkipFailures: false });
const state = getState();
assert.equal(state.email, null);
assert.equal(state.currentPhoneActivation, null);
assert.equal(state.phoneNumber, '');
assert.equal(state.signupPhoneNumber, '');
assert.equal(state.accountIdentifierType, null);
assert.equal(state.accountIdentifier, '');
assert.equal(state.signupPhoneActivation, null);
assert.equal(state.signupPhoneCompletedActivation, null);
assert.equal(state.bindEmailSubmitted, false);
});
test('auto-run cleanup uses frozen resolved signup method instead of stale setting', async () => {
const emailRun = createHarness({
completedNodeIds: FULL_NODE_IDS,
initialState: {
signupMethod: 'phone',
resolvedSignupMethod: 'email',
},
});
await emailRun.controller.autoRunLoop(1, { mode: 'restart', autoRunSkipFailures: false });
assert.equal(emailRun.getState().email, 'bound.user@example.com');
assert.equal(emailRun.getState().accountIdentifierType, 'phone');
const phoneRun = createHarness({
completedNodeIds: FULL_NODE_IDS,
initialState: {
signupMethod: 'email',
resolvedSignupMethod: 'phone',
},
});
await phoneRun.controller.autoRunLoop(1, { mode: 'restart', autoRunSkipFailures: false });
assert.equal(phoneRun.getState().email, null);
assert.equal(phoneRun.getState().accountIdentifierType, null);
assert.equal(phoneRun.getState().signupPhoneNumber, '');
});