Merge pull request #293 from netcookies/fix/madao-country-iso-dev

fix(madao): 适配国家中文标签和线路归一化
This commit is contained in:
QLHazyCoder
2026-05-30 04:09:50 +08:00
committed by GitHub
17 changed files with 4738 additions and 3597 deletions
@@ -71,6 +71,7 @@ test('background account history settings are normalized independently from hotm
extractFunction('normalizeMaDaoIdentifier'),
extractFunction('normalizeMaDaoProviderId'),
extractFunction('normalizeMaDaoCountry'),
extractFunction('normalizeMaDaoOperator'),
extractFunction('normalizeMaDaoPrice'),
extractFunction('normalizePhonePreferredActivation'),
extractFunction('normalizePhoneVerificationReplacementLimit'),
@@ -382,6 +383,7 @@ return {
assert.equal(api.normalizePersistentSettingValue('madaoProviderId', ' Upstream A! '), 'upstreama');
assert.equal(api.normalizePersistentSettingValue('madaoCountry', ' gb '), 'GB');
assert.equal(api.normalizePersistentSettingValue('madaoCountry', 'ANY'), 'any');
assert.equal(api.normalizePersistentSettingValue('madaoOperator', ' Operator A! '), 'operatora');
assert.equal(api.normalizePersistentSettingValue('madaoAutoPickCountry', 1), true);
assert.equal(api.normalizePersistentSettingValue('madaoReusePhone', 0), false);
assert.equal(api.normalizePersistentSettingValue('madaoMinPrice', '0.123456'), '0.1235');
@@ -419,6 +419,7 @@ return {
madaoRoutingPlanId: 'rp-openai',
madaoProviderId: 'upstream-a',
madaoCountry: 'GB',
madaoOperator: 'operator-a',
madaoAutoPickCountry: false,
madaoReusePhone: true,
madaoMinPrice: '0.12',
@@ -452,6 +453,7 @@ return {
assert.equal(api.getPayloadInput().madaoRoutingPlanId, 'rp-openai');
assert.equal(api.getPayloadInput().madaoProviderId, 'upstream-a');
assert.equal(api.getPayloadInput().madaoCountry, 'GB');
assert.equal(api.getPayloadInput().madaoOperator, 'operator-a');
assert.equal(api.getPayloadInput().madaoAutoPickCountry, false);
assert.equal(api.getPayloadInput().madaoReusePhone, true);
assert.equal(api.getPayloadInput().madaoMinPrice, '0.12');
@@ -118,6 +118,7 @@ const PERSISTED_SETTING_DEFAULTS = {
madaoRoutingPlanId: '',
madaoProviderId: '',
madaoCountry: '',
madaoOperator: '',
madaoAutoPickCountry: true,
madaoReusePhone: true,
madaoMinPrice: '',
@@ -219,6 +220,7 @@ function normalizeMaDaoBaseUrl(value = '') {
function normalizeMaDaoMode(value = '') { return String(value || '').trim().toLowerCase() === 'direct' ? 'direct' : DEFAULT_MADAO_MODE; }
function normalizeMaDaoIdentifier(value = '') { return String(value || '').trim(); }
function normalizeMaDaoProviderId(value = '') { return String(value || '').trim().toLowerCase().replace(/[^a-z0-9_-]+/g, ''); }
function normalizeMaDaoOperator(value = '') { return String(value || '').trim().toLowerCase().replace(/[^a-z0-9_-]+/g, ''); }
function normalizeMaDaoCountry(value = '') {
const trimmed = String(value || '').trim();
if (!trimmed) return '';
@@ -306,6 +308,7 @@ test('buildPersistentSettingsPayload writes canonical settings schema into persi
assert.equal(payload.phoneSmsProvider, 'hero-sms');
assert.equal(payload.madaoBaseUrl, DEFAULT_MADAO_BASE_URL_FOR_TEST);
assert.equal(payload.madaoMode, DEFAULT_MADAO_MODE_FOR_TEST);
assert.equal(payload.madaoOperator, '');
assert.equal(payload.madaoAutoPickCountry, true);
assert.equal(payload.madaoReusePhone, true);
assert.equal(Object.prototype.hasOwnProperty.call(payload, 'kiroRegion'), false);
@@ -724,6 +727,7 @@ test('buildPersistentSettingsPayload persists normalized MaDao flat settings out
madaoRoutingPlanId: ' rp-openai ',
madaoProviderId: ' Upstream A! ',
madaoCountry: ' gb ',
madaoOperator: ' Operator A! ',
madaoAutoPickCountry: 0,
madaoReusePhone: 1,
madaoMinPrice: '0.123456',
@@ -737,6 +741,7 @@ test('buildPersistentSettingsPayload persists normalized MaDao flat settings out
assert.equal(payload.madaoRoutingPlanId, 'rp-openai');
assert.equal(payload.madaoProviderId, 'upstreama');
assert.equal(payload.madaoCountry, 'GB');
assert.equal(payload.madaoOperator, 'operatora');
assert.equal(payload.madaoAutoPickCountry, false);
assert.equal(payload.madaoReusePhone, true);
assert.equal(payload.madaoMinPrice, '0.1235');
+35
View File
@@ -38,6 +38,7 @@ test('MaDao direct acquire sends only direct fields and normalizes activation da
madaoRoutingPlanId: 'route-plan-should-not-be-sent',
madaoProviderId: 'Upstream A!',
madaoCountry: 'gb',
madaoOperator: 'Operator A!',
madaoAutoPickCountry: 'false',
madaoReusePhone: '1',
madaoMinPrice: '0.01',
@@ -54,6 +55,9 @@ test('MaDao direct acquire sends only direct fields and normalizes activation da
auto_pick_country: false,
reuse_phone: true,
country: 'GB',
metadata: {
operator: 'operatora',
},
min_price: 0.01,
max_price: 0.2,
});
@@ -100,6 +104,37 @@ test('MaDao routing acquire sends routing plan only', async () => {
assert.equal(activation.madaoRoutingItemId, 'route-1');
});
test('MaDao direct acquire treats any operator as default route', async () => {
const requests = [];
const provider = api.createProvider({
fetchImpl: async (_url, options = {}) => {
requests.push({ body: JSON.parse(options.body) });
return createJsonResponse({
ticket_id: 'ticket-any',
phone_number: '+66111111111',
country: 'TH',
provider: 'fivesim',
});
},
});
await provider.acquireActivation({
madaoMode: 'direct',
madaoProviderId: 'fivesim',
madaoCountry: 'TH',
madaoOperator: 'Any operator',
});
assert.equal(requests.length, 1);
assert.deepEqual(requests[0].body, {
provider: 'fivesim',
service: 'openai',
auto_pick_country: true,
reuse_phone: true,
country: 'TH',
});
});
test('MaDao poll extracts codes from nested messages and reports pending status', async () => {
const statusEvents = [];
const provider = api.createProvider({
+62
View File
@@ -356,6 +356,68 @@ test('phone auth can auto-select country by dial code even when number has no pl
}
});
test('phone auth rejects stale selected country when it does not match phone dial code', async () => {
const originalDocument = global.document;
const originalEvent = global.Event;
const originalLocation = global.location;
const dom = createFakeAddPhoneDom({
options: [
{ value: 'TH', textContent: 'Thailand (+66)', buttonText: 'Thailand (+66)' },
{ value: 'GB', textContent: 'United Kingdom (+44)', buttonText: 'United Kingdom (+44)' },
],
selectedIndex: 0,
});
global.document = dom.document;
global.Event = class Event {
constructor(type) {
this.type = type;
}
};
global.location = { href: 'https://auth.openai.com/add-phone' };
try {
const helpers = api.createPhoneAuthHelpers({
fillInput: (element, value) => {
element.value = value;
},
getActionText: () => '',
getPageTextSnapshot: () => '',
getVerificationErrorText: () => '',
humanPause: async () => {},
isActionEnabled: () => true,
isAddPhonePageReady: () => true,
isConsentReady: () => false,
isPhoneVerificationPageReady: () => false,
isVisibleElement: () => true,
simulateClick: (element) => {
element.click?.();
},
sleep: async () => {},
throwIfStopped: () => {},
waitForElement: async () => null,
});
await assert.rejects(
() => helpers.submitPhoneNumber({
countryLabel: 'Country #10',
phoneNumber: '+84943328460',
}),
/Failed to select "Country #10" on the add-phone page\./
);
assert.equal(dom.select.value, 'TH');
assert.equal(dom.phoneInput.value, '');
assert.equal(dom.hiddenPhoneInput.value, '');
assert.equal(dom.wasSubmitClicked(), false);
} finally {
global.document = originalDocument;
global.Event = originalEvent;
global.location = originalLocation;
}
});
test('phone auth resend stops with PHONE_ROUTE_405_RECOVERY_FAILED instead of endless Try-again loop', async () => {
const originalDocument = global.document;
const originalLocation = global.location;
+44 -1
View File
@@ -3,6 +3,8 @@ const assert = require('node:assert/strict');
const fs = require('node:fs');
const source = fs.readFileSync('phone-sms/providers/registry.js', 'utf8');
const maDaoSource = fs.readFileSync('phone-sms/providers/madao.js', 'utf8');
const nexSmsSource = fs.readFileSync('phone-sms/providers/nexsms.js', 'utf8');
function loadRegistry(root = {}) {
return new Function('self', `${source}; return self.PhoneSmsProviderRegistry;`)(root);
@@ -16,6 +18,9 @@ test('phone sms provider registry normalizes ids, order and labels consistently'
PhoneSmsFiveSimProvider: {
createProvider: (deps = {}) => ({ provider: '5sim', deps }),
},
PhoneSmsNexSmsProvider: {
createProvider: (deps = {}) => ({ provider: 'nexsms', deps }),
},
PhoneSmsMaDaoProvider: {
createProvider: (deps = {}) => ({ provider: 'madao', deps }),
},
@@ -52,5 +57,43 @@ test('phone sms provider registry normalizes ids, order and labels consistently'
registry.createProvider('madao', { foo: 2 }),
{ provider: 'madao', deps: { foo: 2 } }
);
assert.throws(() => registry.createProvider('nexsms'), /接码平台模块未加载:nexsms/);
assert.deepStrictEqual(
registry.createProvider('nexsms', { foo: 3 }),
{ provider: 'nexsms', deps: { foo: 3 } }
);
});
test('phone sms provider registry can create the real MaDao provider module', () => {
const maDaoModule = new Function('self', `${maDaoSource}; return self.PhoneSmsMaDaoProvider;`)({});
const registry = loadRegistry({
PhoneSmsMaDaoProvider: maDaoModule,
});
const provider = registry.createProvider('madao', { fetchImpl: 'demo-fetch' });
assert.equal(provider.id, 'madao');
assert.equal(provider.label, 'MaDao');
assert.equal(provider.defaultProduct, 'openai');
assert.equal(typeof provider.acquireActivation, 'function');
assert.equal(typeof provider.pollActivation, 'function');
assert.equal(typeof provider.releaseActivation, 'function');
assert.equal(provider.mapTicketStatus('waiting_code'), 'waiting_code');
});
test('phone sms provider registry can create the real NexSMS provider module', () => {
const nexSmsModule = new Function('self', `${nexSmsSource}; return self.PhoneSmsNexSmsProvider;`)({});
const registry = loadRegistry({
PhoneSmsNexSmsProvider: nexSmsModule,
});
const provider = registry.createProvider('nexsms', { fetchImpl: 'demo-fetch' });
assert.equal(provider.id, 'nexsms');
assert.equal(provider.label, 'NexSMS');
assert.equal(provider.defaultProduct, 'OpenAI');
assert.equal(provider.defaultServiceCode, 'ot');
assert.equal(typeof provider.fetchBalance, 'function');
assert.equal(typeof provider.fetchPrices, 'function');
assert.equal(provider.normalizeCountryId('8'), 8);
assert.equal(provider.normalizeServiceCode(' OT '), 'ot');
});
+334 -86
View File
@@ -3,10 +3,19 @@ const assert = require('node:assert/strict');
const fs = require('node:fs');
const source = fs.readFileSync('background/phone-verification-flow.js', 'utf8');
const globalScope = {};
const api = new Function('self', `${source}; return self.MultiPageBackgroundPhoneVerification;`)(globalScope);
const heroSmsSource = fs.readFileSync('phone-sms/providers/hero-sms.js', 'utf8');
const fiveSimSource = fs.readFileSync('phone-sms/providers/five-sim.js', 'utf8');
const nexSmsSource = fs.readFileSync('phone-sms/providers/nexsms.js', 'utf8');
const maDaoSource = fs.readFileSync('phone-sms/providers/madao.js', 'utf8');
const maDaoModule = new Function('self', `${maDaoSource}; return self.PhoneSmsMaDaoProvider;`)({});
const registrySource = fs.readFileSync('phone-sms/providers/registry.js', 'utf8');
const globalScope = {};
new Function('self', `${heroSmsSource}; return self.PhoneSmsHeroSmsProvider;`)(globalScope);
new Function('self', `${fiveSimSource}; return self.PhoneSmsFiveSimProvider;`)(globalScope);
new Function('self', `${nexSmsSource}; return self.PhoneSmsNexSmsProvider;`)(globalScope);
new Function('self', `${maDaoSource}; return self.PhoneSmsMaDaoProvider;`)(globalScope);
new Function('self', `${registrySource}; return self.PhoneSmsProviderRegistry;`)(globalScope);
const api = new Function('self', `${source}; return self.MultiPageBackgroundPhoneVerification;`)(globalScope);
const maDaoModule = globalScope.PhoneSmsMaDaoProvider;
function buildHeroSmsPricesPayload({ country = '52', service = 'dr', cost = 0.08, count = 25370, physicalCount = 14528 } = {}) {
return JSON.stringify({
@@ -157,6 +166,110 @@ test('phone verification helper reads latest HeroSMS operator from persistent st
assert.equal(getNumberRequest.searchParams.get('operator'), 'dtac');
});
test('phone verification helper creates 5sim adapter through provider registry when available', async () => {
const createCalls = [];
const root = {
PhoneSmsProviderRegistry: {
createProvider: (providerId, deps = {}) => {
createCalls.push({ providerId, deps });
return {
requestActivation: async () => ({
activationId: '5sim-registry-1',
phoneNumber: '+447700900001',
provider: '5sim',
serviceCode: 'openai',
countryId: 'england',
}),
};
},
},
};
const registryApi = new Function('self', `${source}; return self.MultiPageBackgroundPhoneVerification;`)(root);
const helpers = registryApi.createPhoneVerificationHelpers({
addLog: async () => {},
ensureStep8SignupPageReady: async () => {},
fetchImpl: async () => {
throw new Error('5sim registry adapter should own network access');
},
getState: async () => ({ phoneSmsProvider: '5sim' }),
sendToContentScriptResilient: async () => ({}),
setState: async () => {},
sleepWithStop: async () => {},
throwIfStopped: () => {},
});
const activation = await helpers.requestPhoneActivation({ phoneSmsProvider: '5sim' });
assert.equal(createCalls.length, 1);
assert.equal(createCalls[0].providerId, '5sim');
assert.equal(typeof createCalls[0].deps.fetchImpl, 'function');
assert.equal(activation.activationId, '5sim-registry-1');
});
test('phone verification helper creates MaDao adapter through provider registry when available', async () => {
const createCalls = [];
const requests = [];
const root = {
PhoneSmsProviderRegistry: {
createProvider: (providerId, deps = {}) => {
createCalls.push({ providerId, deps });
return maDaoModule.createProvider(deps);
},
},
};
const registryApi = new Function('self', `${source}; return self.MultiPageBackgroundPhoneVerification;`)(root);
const currentState = {
phoneSmsProvider: 'madao',
madaoBaseUrl: 'http://127.0.0.1:7822/',
madaoHttpSecret: 'secret-token',
madaoMode: 'routing_plan',
madaoRoutingPlanId: 'rp-openai',
};
const helpers = registryApi.createPhoneVerificationHelpers({
addLog: async () => {},
ensureStep8SignupPageReady: async () => {},
fetchImpl: async (url, options = {}) => {
const parsedUrl = new URL(url);
requests.push({ url: parsedUrl, options, body: options.body ? JSON.parse(options.body) : null });
if (parsedUrl.pathname === '/api/acquire') {
return {
ok: true,
status: 200,
text: async () => JSON.stringify({
ticket_id: 'madao-registry-1',
phone_number: '+14155550123',
service: 'openai',
country: 'CA',
provider: 'upstream-a',
routing_plan_id: 'rp-openai',
routing_item_id: 'route-1',
status: 'waiting_code',
}),
};
}
throw new Error(`Unexpected MaDao path: ${parsedUrl.pathname}`);
},
getState: async () => ({ ...currentState }),
sendToContentScriptResilient: async () => ({}),
setState: async () => {},
sleepWithStop: async () => {},
throwIfStopped: () => {},
});
const activation = await helpers.requestPhoneActivation(currentState);
assert.equal(createCalls.length, 1);
assert.equal(createCalls[0].providerId, 'madao');
assert.equal(typeof createCalls[0].deps.fetchImpl, 'function');
assert.equal(activation.activationId, 'madao-registry-1');
assert.equal(activation.countryId, 'CA');
assert.deepStrictEqual(requests[0].body, {
provider: 'auto',
service: 'openai',
routing_plan_id: 'rp-openai',
});
});
test('phone verification helper acquires, polls and releases MaDao activation through provider adapter', async () => {
const requests = [];
let currentState = {
@@ -172,7 +285,6 @@ test('phone verification helper acquires, polls and releases MaDao activation th
};
const helpers = api.createPhoneVerificationHelpers({
addLog: async () => {},
createMaDaoProvider: maDaoModule.createProvider,
ensureStep8SignupPageReady: async () => {},
fetchImpl: async (url, options = {}) => {
const parsedUrl = new URL(url);
@@ -252,6 +364,161 @@ test('phone verification helper acquires, polls and releases MaDao activation th
assert.deepStrictEqual(requests[2].body, { ticket_id: 'madao-1', action: 'finish' });
});
test('phone verification helper keeps MaDao routing replacement country as ISO code on resubmit', async () => {
const requests = [];
const messages = [];
let currentState = {
phoneSmsProvider: 'madao',
madaoBaseUrl: 'http://127.0.0.1:7822/',
madaoHttpSecret: 'secret-token',
madaoMode: 'routing_plan',
madaoRoutingPlanId: 'rp-openai',
verificationResendCount: 0,
phoneVerificationReplacementLimit: 2,
phoneCodeWaitSeconds: 15,
phoneCodeTimeoutWindows: 1,
phoneCodePollIntervalSeconds: 1,
phoneCodePollMaxRounds: 1,
currentPhoneActivation: null,
reusablePhoneActivation: null,
};
let acquireCalls = 0;
let replaceCalls = 0;
const helpers = api.createPhoneVerificationHelpers({
addLog: async () => {},
ensureStep8SignupPageReady: async () => {},
fetchImpl: async (url, options = {}) => {
const parsedUrl = new URL(url);
const body = options.body ? JSON.parse(options.body) : null;
requests.push({ url: parsedUrl, options, body });
if (parsedUrl.pathname === '/api/acquire') {
acquireCalls += 1;
return {
ok: true,
status: 200,
text: async () => JSON.stringify({
ticket_id: 'madao-ticket-routing-1',
provider: 'herosms',
service: 'openai',
country: 'TH',
phone_number: '+66950002222',
routing_plan_id: 'rp-openai',
routing_plan_name: 'OpenAI Plan',
routing_item_id: 'route-1',
status: 'waiting_code',
}),
};
}
if (parsedUrl.pathname === '/api/poll') {
return {
ok: true,
status: 200,
text: async () => JSON.stringify({
ticket_id: body.ticket_id,
provider: body.ticket_id === 'madao-ticket-routing-1' ? 'herosms' : 'smsbower',
status: body.ticket_id === 'madao-ticket-routing-1' ? 'waiting_code' : 'code_received',
code: body.ticket_id === 'madao-ticket-routing-1' ? null : '654321',
}),
};
}
if (parsedUrl.pathname === '/api/routing/replace') {
replaceCalls += 1;
return {
ok: true,
status: 200,
text: async () => JSON.stringify({
current_ticket_id: 'madao-ticket-routing-1',
current_ticket_release: {
ticket_id: 'madao-ticket-routing-1',
provider: 'herosms',
status: 'cancelled',
},
next_ticket: {
ticket_id: 'madao-ticket-routing-2',
provider: 'smsbower',
service: 'openai',
country: 'VN',
phone_number: '+84943328460',
routing_plan_id: 'rp-openai',
routing_plan_name: 'OpenAI Plan',
routing_item_id: 'route-2',
status: 'waiting_code',
},
}),
};
}
if (parsedUrl.pathname === '/api/release') {
return {
ok: true,
status: 200,
text: async () => JSON.stringify({ ok: true }),
};
}
throw new Error(`Unexpected MaDao path: ${parsedUrl.pathname}`);
},
getOAuthFlowStepTimeoutMs: async (defaultTimeoutMs) => defaultTimeoutMs,
getState: async () => ({ ...currentState }),
sendToContentScriptResilient: async (_source, message) => {
messages.push(message);
if (message.type === 'STEP8_GET_STATE') {
return {
addPhonePage: true,
phoneVerificationPage: false,
url: 'https://auth.openai.com/add-phone',
};
}
if (message.type === 'SUBMIT_PHONE_NUMBER') {
return {
phoneVerificationPage: true,
url: 'https://auth.openai.com/phone-verification',
};
}
if (message.type === 'RETURN_TO_ADD_PHONE') {
return {
addPhonePage: true,
url: 'https://auth.openai.com/add-phone',
};
}
if (message.type === 'SUBMIT_PHONE_VERIFICATION_CODE') {
return {
success: true,
consentReady: true,
url: 'https://auth.openai.com/authorize',
};
}
throw new Error(`Unexpected content-script message: ${message.type}`);
},
setState: async (updates) => {
currentState = { ...currentState, ...updates };
},
sleepWithStop: async () => {},
throwIfStopped: () => {},
});
const result = await helpers.completePhoneVerificationFlow(1, {
addPhonePage: true,
phoneVerificationPage: false,
url: 'https://auth.openai.com/add-phone',
});
assert.deepStrictEqual(result, {
success: true,
consentReady: true,
url: 'https://auth.openai.com/authorize',
});
assert.equal(acquireCalls, 1);
assert.equal(replaceCalls, 1);
const phoneSubmissions = messages.filter((message) => message.type === 'SUBMIT_PHONE_NUMBER');
assert.equal(phoneSubmissions.length, 2);
assert.equal(phoneSubmissions[0].payload.phoneNumber, '+66950002222');
assert.equal(phoneSubmissions[0].payload.countryId, 'TH');
assert.equal(phoneSubmissions[0].payload.countryLabel, 'Thailand');
assert.equal(phoneSubmissions[1].payload.phoneNumber, '+84943328460');
assert.equal(phoneSubmissions[1].payload.countryId, 'VN');
assert.equal(phoneSubmissions[1].payload.countryLabel, 'Vietnam');
});
test('signup phone helper persists signup runtime state without touching add-phone activation', async () => {
const setStateCalls = [];
let currentState = {
@@ -974,8 +1241,6 @@ test('signup phone helper does not let a hung page-state probe stall HeroSMS pol
});
test('signup phone helper fails stale email-verification on 5sim RECEIVED without code during SMS polling', async () => {
const fiveSimSource = fs.readFileSync('phone-sms/providers/five-sim.js', 'utf8');
const fiveSimModule = new Function('self', `${fiveSimSource}; return self.PhoneSmsFiveSimProvider;`)({});
let checkCount = 0;
let pageStateReads = 0;
const contentMessages = [];
@@ -1005,7 +1270,6 @@ test('signup phone helper fails stale email-verification on 5sim RECEIVED withou
const helpers = api.createPhoneVerificationHelpers({
addLog: async () => {},
ensureStep8SignupPageReady: async () => {},
createFiveSimProvider: fiveSimModule.createProvider,
fetchImpl: async (url) => {
const parsedUrl = new URL(url);
if (parsedUrl.pathname === '/v1/user/check/5001') {
@@ -2352,29 +2616,28 @@ test('phone verification helper acquires a number from 5sim with fallback countr
heroSmsActivationRetryRounds: 1,
});
assert.deepStrictEqual(activation, {
activationId: '9876543',
phoneNumber: '+447911123456',
provider: '5sim',
serviceCode: 'openai',
countryId: 'england',
countryCode: 'england',
countryLabel: 'England',
successfulUses: 0,
maxUses: 3,
});
assert.equal(requests.length, 4);
assert.equal(requests[0].pathname, '/v1/guest/prices');
assert.equal(requests[0].search.get('country'), 'thailand');
assert.equal(requests[0].search.get('product'), 'openai');
assert.equal(requests[1].pathname, '/v1/user/buy/activation/thailand/any/openai');
assert.equal(requests[1].search.get('maxPrice'), '0.08');
assert.equal(requests[1].search.get('reuse'), '1');
assert.equal(requests[1].headers.Authorization, 'Bearer five-token');
assert.equal(requests[2].pathname, '/v1/guest/prices');
assert.equal(requests[2].search.get('country'), 'england');
assert.equal(requests[2].search.get('product'), 'openai');
assert.equal(requests[3].pathname, '/v1/user/buy/activation/england/any/openai');
assert.equal(activation.activationId, '9876543');
assert.equal(activation.phoneNumber, '+447911123456');
assert.equal(activation.provider, '5sim');
assert.equal(activation.serviceCode, 'openai');
assert.equal(activation.countryId, 'england');
assert.equal(activation.countryCode, 'england');
assert.equal(activation.successfulUses, 0);
assert.equal(activation.maxUses, 3);
assert.equal(requests.length, 6);
assert.equal(requests[0].pathname, '/v1/guest/products/thailand/any');
assert.equal(requests[1].pathname, '/v1/guest/prices');
assert.equal(requests[1].search.get('country'), 'thailand');
assert.equal(requests[1].search.get('product'), 'openai');
assert.equal(requests[2].pathname, '/v1/user/buy/activation/thailand/any/openai');
assert.equal(requests[2].search.get('maxPrice'), '0.08');
assert.equal(requests[2].search.get('reuse'), '1');
assert.equal(requests[2].headers.Authorization, 'Bearer five-token');
assert.equal(requests[3].pathname, '/v1/guest/products/england/any');
assert.equal(requests[4].pathname, '/v1/guest/prices');
assert.equal(requests[4].search.get('country'), 'england');
assert.equal(requests[4].search.get('product'), 'openai');
assert.equal(requests[5].pathname, '/v1/user/buy/activation/england/any/openai');
});
test('phone verification helper preserves fallback provider while refreshing latest settings', async () => {
@@ -2514,13 +2777,14 @@ test('phone verification helper preserves fallback provider while refreshing lat
assert.deepStrictEqual(
fiveSimRequests.map((entry) => entry.url.pathname),
[
'/v1/guest/products/vietnam/any',
'/v1/guest/prices',
'/v1/user/buy/activation/vietnam/any/openai',
'/v1/user/check/5020',
'/v1/user/finish/5020',
]
);
assert.equal(fiveSimRequests[1].options.headers.Authorization, 'Bearer five-token');
assert.equal(fiveSimRequests[2].options.headers.Authorization, 'Bearer five-token');
});
test('phone verification helper prefers phoneSmsReuseEnabled over legacy heroSmsReuseEnabled for 5sim acquisition', async () => {
@@ -2593,20 +2857,18 @@ test('phone verification helper prefers phoneSmsReuseEnabled over legacy heroSms
heroSmsActivationRetryRounds: 1,
});
assert.deepStrictEqual(activation, {
activationId: '1234567',
phoneNumber: '+66880000000',
provider: '5sim',
serviceCode: 'openai',
countryId: 'thailand',
countryCode: 'thailand',
countryLabel: 'Thailand',
successfulUses: 0,
maxUses: 3,
});
assert.equal(requests[0].pathname, '/v1/guest/prices');
assert.equal(requests[1].pathname, '/v1/user/buy/activation/thailand/any/openai');
assert.equal(requests[1].search.get('reuse'), null);
assert.equal(activation.activationId, '1234567');
assert.equal(activation.phoneNumber, '+66880000000');
assert.equal(activation.provider, '5sim');
assert.equal(activation.serviceCode, 'openai');
assert.equal(activation.countryId, 'thailand');
assert.equal(activation.countryCode, 'thailand');
assert.equal(activation.successfulUses, 0);
assert.equal(activation.maxUses, 3);
assert.equal(requests[0].pathname, '/v1/guest/products/thailand/any');
assert.equal(requests[1].pathname, '/v1/guest/prices');
assert.equal(requests[2].pathname, '/v1/user/buy/activation/thailand/any/openai');
assert.equal(requests[2].search.get('reuse'), null);
});
test('phone verification helper treats fiveSimReuseEnabled as legacy-only when phoneSmsReuseEnabled is absent', async () => {
@@ -2679,20 +2941,18 @@ test('phone verification helper treats fiveSimReuseEnabled as legacy-only when p
heroSmsActivationRetryRounds: 1,
});
assert.deepStrictEqual(activation, {
activationId: '1234568',
phoneNumber: '+66880000001',
provider: '5sim',
serviceCode: 'openai',
countryId: 'thailand',
countryCode: 'thailand',
countryLabel: 'Thailand',
successfulUses: 0,
maxUses: 3,
});
assert.equal(requests[0].pathname, '/v1/guest/prices');
assert.equal(requests[1].pathname, '/v1/user/buy/activation/thailand/any/openai');
assert.equal(requests[1].search.get('reuse'), '1');
assert.equal(activation.activationId, '1234568');
assert.equal(activation.phoneNumber, '+66880000001');
assert.equal(activation.provider, '5sim');
assert.equal(activation.serviceCode, 'openai');
assert.equal(activation.countryId, 'thailand');
assert.equal(activation.countryCode, 'thailand');
assert.equal(activation.successfulUses, 0);
assert.equal(activation.maxUses, 3);
assert.equal(requests[0].pathname, '/v1/guest/products/thailand/any');
assert.equal(requests[1].pathname, '/v1/guest/prices');
assert.equal(requests[2].pathname, '/v1/user/buy/activation/thailand/any/openai');
assert.equal(requests[2].search.get('reuse'), '1');
});
test('phone verification helper rejects 5sim maxPrice with custom operator before buying', async () => {
@@ -3115,7 +3375,7 @@ test('phone verification helper polls and parses 5sim verification codes', async
assert.equal(code, '246810');
assert.equal(checkCount, 2);
assert.deepStrictEqual(statusUpdates, ['PENDING']);
assert.deepStrictEqual(statusUpdates, ['PENDING', 'RECEIVED']);
});
test('phone verification helper treats HeroSMS STATUS_WAIT_RETRY payload status as pending', async () => {
@@ -3242,18 +3502,16 @@ test('phone verification helper reuses 5sim by keeping the original activation',
}
);
assert.deepStrictEqual(nextActivation, {
activationId: '600001',
phoneNumber: '+44 7911-123-456',
provider: '5sim',
serviceCode: 'openai',
countryId: 'england',
countryCode: 'england',
successfulUses: 0,
maxUses: 1,
source: '5sim-retained-reuse',
ignoredPhoneCodeKeys: ['old::111111'],
});
assert.equal(nextActivation.activationId, '600001');
assert.equal(nextActivation.phoneNumber, '+44 7911-123-456');
assert.equal(nextActivation.provider, '5sim');
assert.equal(nextActivation.serviceCode, 'openai');
assert.equal(nextActivation.countryId, 'england');
assert.equal(nextActivation.countryCode, 'england');
assert.equal(nextActivation.successfulUses, 0);
assert.equal(nextActivation.maxUses, 1);
assert.equal(nextActivation.source, '5sim-retained-reuse');
assert.deepStrictEqual(nextActivation.ignoredPhoneCodeKeys, ['old::111111']);
assert.deepStrictEqual(requests, ['/v1/user/check/600001']);
});
@@ -6448,12 +6706,9 @@ test('phone verification helper auto free-reuses 5sim by polling the retained or
},
};
const fiveSimSource = fs.readFileSync('phone-sms/providers/five-sim.js', 'utf8');
const fiveSimModule = new Function('self', `${fiveSimSource}; return self.PhoneSmsFiveSimProvider;`)({});
const helpers = api.createPhoneVerificationHelpers({
addLog: async () => {},
ensureStep8SignupPageReady: async () => {},
createFiveSimProvider: fiveSimModule.createProvider,
fetchImpl: async (url) => {
const parsedUrl = new URL(url);
requests.push(parsedUrl);
@@ -6547,12 +6802,9 @@ test('phone verification helper retires failed 5sim free-reuse record instead of
},
};
const fiveSimSource = fs.readFileSync('phone-sms/providers/five-sim.js', 'utf8');
const fiveSimModule = new Function('self', `${fiveSimSource}; return self.PhoneSmsFiveSimProvider;`)({});
const helpers = api.createPhoneVerificationHelpers({
addLog: async () => {},
ensureStep8SignupPageReady: async () => {},
createFiveSimProvider: fiveSimModule.createProvider,
fetchImpl: async (url) => {
const parsedUrl = new URL(url);
requests.push(parsedUrl);
@@ -8724,12 +8976,9 @@ test('phone verification helper routes 5sim buy, check, and finish by current ac
reusablePhoneActivation: null,
};
const fiveSimSource = fs.readFileSync('phone-sms/providers/five-sim.js', 'utf8');
const fiveSimModule = new Function('self', `${fiveSimSource}; return self.PhoneSmsFiveSimProvider;`)({});
const helpers = api.createPhoneVerificationHelpers({
addLog: async () => {},
ensureStep8SignupPageReady: async () => {},
createFiveSimProvider: fiveSimModule.createProvider,
fetchImpl: async (url, options = {}) => {
const parsedUrl = new URL(url);
requests.push({ url: parsedUrl, options });
@@ -8838,7 +9087,6 @@ test('phone verification helper uses MaDao routing replace when add-phone reject
};
const helpers = api.createPhoneVerificationHelpers({
addLog: async () => {},
createMaDaoProvider: maDaoModule.createProvider,
ensureStep8SignupPageReady: async () => {},
fetchImpl: async (url, options = {}) => {
const parsedUrl = new URL(url);
@@ -8966,6 +9214,7 @@ test('phone verification helper reacquires MaDao direct numbers after releasing
madaoRoutingPlanId: 'rp-stale',
madaoProviderId: 'upstream-a',
madaoCountry: 'gb',
madaoOperator: 'operator-a',
madaoAutoPickCountry: false,
madaoReusePhone: true,
madaoMinPrice: '0.01',
@@ -8981,7 +9230,6 @@ test('phone verification helper reacquires MaDao direct numbers after releasing
};
const helpers = api.createPhoneVerificationHelpers({
addLog: async () => {},
createMaDaoProvider: maDaoModule.createProvider,
ensureStep8SignupPageReady: async () => {},
fetchImpl: async (url, options = {}) => {
const parsedUrl = new URL(url);
@@ -9076,6 +9324,9 @@ test('phone verification helper reacquires MaDao direct numbers after releasing
auto_pick_country: false,
reuse_phone: true,
country: 'GB',
metadata: {
operator: 'operator-a',
},
min_price: 0.01,
max_price: 0.2,
});
@@ -9114,12 +9365,9 @@ test('phone verification helper keeps 5sim reusable activation on the original o
},
};
const fiveSimSource = fs.readFileSync('phone-sms/providers/five-sim.js', 'utf8');
const fiveSimModule = new Function('self', `${fiveSimSource}; return self.PhoneSmsFiveSimProvider;`)({});
const helpers = api.createPhoneVerificationHelpers({
addLog: async () => {},
ensureStep8SignupPageReady: async () => {},
createFiveSimProvider: fiveSimModule.createProvider,
fetchImpl: async (url) => {
const parsedUrl = new URL(url);
requests.push(parsedUrl);
@@ -156,15 +156,28 @@ test('sidepanel html exposes phone verification toggle and multi-provider SMS ro
assert.match(html, /id="row-madao-mode"/);
assert.match(html, /id="select-madao-mode"/);
assert.match(html, /id="row-madao-routing-plan-id"/);
assert.match(html, /id="input-madao-routing-plan-id"/);
assert.match(html, /id="select-madao-routing-plan-id"/);
assert.match(html, /id="btn-madao-refresh-routing-plans"/);
assert.doesNotMatch(html, /id="input-madao-routing-plan-id"/);
assert.match(html, /id="row-madao-provider-id"/);
assert.match(html, /id="input-madao-provider-id"/);
assert.match(html, /id="select-madao-provider-id"/);
assert.match(html, /id="btn-madao-refresh-providers"/);
assert.doesNotMatch(html, /id="input-madao-provider-id"/);
assert.match(html, /id="row-madao-country"/);
assert.match(html, /id="input-madao-country"/);
assert.match(html, /id="row-madao-auto-pick-country"/);
assert.match(html, /id="input-madao-auto-pick-country"/);
assert.match(html, /id="row-madao-reuse-phone"/);
assert.match(html, /id="input-madao-reuse-phone"/);
assert.match(html, /id="select-madao-country"/);
assert.match(html, /id="btn-madao-refresh-countries"/);
assert.doesNotMatch(html, /id="input-madao-country"/);
assert.match(html, /id="row-madao-operator"/);
assert.match(html, /id="select-madao-operator"/);
assert.match(html, /id="btn-madao-refresh-operators"/);
assert.doesNotMatch(html, /id="row-madao-auto-pick-country"/);
assert.doesNotMatch(html, /id="input-madao-auto-pick-country"/);
assert.doesNotMatch(html, /id="row-madao-reuse-phone"/);
assert.doesNotMatch(html, /id="input-madao-reuse-phone"/);
assert.doesNotMatch(html, /直连平台/);
assert.doesNotMatch(html, /直连国家/);
assert.doesNotMatch(html, /自动选国家/);
assert.doesNotMatch(html, /MaDao 复用/);
assert.match(html, /id="row-madao-price-range"/);
assert.match(html, /id="input-madao-min-price"/);
assert.match(html, /id="input-madao-max-price"/);
@@ -190,6 +203,228 @@ test('sidepanel loads live SMS country lists silently during startup', () => {
assert.doesNotMatch(sidepanelSource, /console\.error\('加载 (?:HeroSMS|5sim|NexSMS) 国家列表失败:'/);
});
test('MaDao routing plan select loads options from helper API and preserves saved plan', async () => {
const requests = [];
const fetchImpl = async (url, options = {}) => {
requests.push({ url, options });
return {
ok: true,
status: 200,
statusText: 'OK',
async text() {
return JSON.stringify({
plans: [
{ id: 'openai-plan', name: 'OpenAI Plan', service: 'openai', enabled: true },
{ id: 'kiro-plan', name: 'Kiro Plan', service: 'kiro', enabled: true },
{ id: 'disabled-plan', name: 'Disabled Plan', service: 'openai', enabled: false },
],
});
},
};
};
const api = new Function('fetch', `
const DEFAULT_MADAO_BASE_URL = 'http://127.0.0.1:7822';
let latestState = {
madaoBaseUrl: 'http://madao.local/api/acquire',
madaoHttpSecret: 'madao-secret',
madaoRoutingPlanId: 'stored-plan',
};
let maDaoRoutingPlanOptions = [];
let displayText = '';
let toastText = '';
const inputMaDaoBaseUrl = { value: 'http://madao.local/api/acquire' };
const inputMaDaoHttpSecret = { value: 'madao-secret' };
const selectMaDaoRoutingPlanId = {
value: 'stored-plan',
options: [],
replaceChildren(...children) {
this.options = children;
},
};
function updateHeroSmsPlatformDisplay() {
displayText = getSelectedMaDaoRoutingPlanLabel();
}
function showToast(message) {
toastText = message;
}
${extractFunction('normalizeMaDaoBaseUrlValue')}
${extractFunction('normalizeMaDaoIdentifierValue')}
${extractFunction('normalizeMaDaoRoutingPlanIdValue')}
${extractFunction('createSelectOptionElement')}
${extractFunction('setSelectOptions')}
${extractFunction('buildMaDaoRoutingPlanOptions')}
${extractFunction('setMaDaoRoutingPlanSelectOptions')}
${extractFunction('buildMaDaoRequestUrl')}
${extractFunction('buildMaDaoRequestHeaders')}
${extractFunction('fetchMaDaoJson')}
${extractFunction('getMaDaoRoutingPlansFromPayload')}
${extractFunction('loadMaDaoRoutingPlans')}
${extractFunction('getSelectedMaDaoRoutingPlanLabel')}
return {
loadMaDaoRoutingPlans,
selectMaDaoRoutingPlanId,
get options() { return selectMaDaoRoutingPlanId.options.map((option) => ({ value: option.value, label: option.textContent, selected: option.selected })); },
get displayText() { return displayText; },
get toastText() { return toastText; },
};
`)(fetchImpl);
const plans = await api.loadMaDaoRoutingPlans();
assert.deepStrictEqual(plans.map((plan) => plan.value), ['openai-plan']);
assert.equal(api.selectMaDaoRoutingPlanId.value, 'stored-plan');
assert.deepStrictEqual(api.options.map((option) => option.value), ['', 'stored-plan', 'openai-plan']);
assert.equal(api.options.find((option) => option.value === 'stored-plan').selected, true);
assert.equal(api.displayText, 'stored-plan');
assert.equal(api.toastText, '已刷新 MaDao 路由计划。');
assert.equal(requests[0].url, 'http://madao.local/api/routing-plans');
assert.equal(requests[0].options.headers.Authorization, 'Bearer madao-secret');
});
test('MaDao direct selects load provider country and operator options from daemon API', async () => {
const requests = [];
const fetchImpl = async (url, options = {}) => {
const parsedUrl = new URL(url);
requests.push({
pathname: parsedUrl.pathname,
options,
body: options.body ? JSON.parse(options.body) : null,
});
if (parsedUrl.pathname === '/api/providers') {
return {
ok: true,
status: 200,
statusText: 'OK',
async text() {
return JSON.stringify({
providers: [
{ id: 'stored-provider', name: 'Stored Provider', enabled: true, protocol_label: 'REST' },
{ id: 'disabled-provider', name: 'Disabled Provider', enabled: false },
],
});
},
};
}
if (parsedUrl.pathname === '/api/providers/stored-provider/countries') {
return {
ok: true,
status: 200,
statusText: 'OK',
async text() {
return JSON.stringify({
provider: 'stored-provider',
items: [
{ value: 'GB', label: 'United Kingdom', label_zh: '英国', provider_value: 'england' },
{ value: 'TH', label: 'Thailand', label_zh: '泰国', provider_value: '52' },
],
});
},
};
}
if (parsedUrl.pathname === '/api/providers/stored-provider/operators') {
return {
ok: true,
status: 200,
statusText: 'OK',
async text() {
return JSON.stringify({
provider: 'stored-provider',
items: [
{ value: 'any', label: 'Any operator' },
{ value: 'operator-a', label: 'Operator A' },
{ value: 'operator-b', label: 'Operator B' },
],
});
},
};
}
throw new Error(`Unexpected MaDao path: ${parsedUrl.pathname}`);
};
const api = new Function('fetch', `
const DEFAULT_MADAO_BASE_URL = 'http://127.0.0.1:7822';
let latestState = {
madaoBaseUrl: 'http://madao.local/api/acquire',
madaoHttpSecret: 'madao-secret',
madaoProviderId: 'stored-provider',
madaoCountry: 'england',
madaoOperator: 'operator-a',
};
let maDaoProviderOptions = [];
let maDaoCountryOptions = [];
let maDaoOperatorOptions = [];
let displayText = '';
let toastText = '';
const inputMaDaoBaseUrl = { value: 'http://madao.local/api/acquire' };
const inputMaDaoHttpSecret = { value: 'madao-secret' };
const selectMaDaoProviderId = { value: 'stored-provider', options: [], replaceChildren(...children) { this.options = children; } };
const selectMaDaoCountry = { value: 'england', options: [], replaceChildren(...children) { this.options = children; } };
const selectMaDaoOperator = { value: 'operator-a', options: [], replaceChildren(...children) { this.options = children; } };
function updateHeroSmsPlatformDisplay() {
displayText = [selectMaDaoProviderId.value, selectMaDaoCountry.value, selectMaDaoOperator.value].filter(Boolean).join('/');
}
function showToast(message) {
toastText = message;
}
${extractFunction('normalizeMaDaoBaseUrlValue')}
${extractFunction('normalizeMaDaoIdentifierValue')}
${extractFunction('normalizeMaDaoProviderIdValue')}
${extractFunction('normalizeMaDaoOperatorValue')}
${extractFunction('normalizeMaDaoCountry')}
${extractFunction('formatMaDaoCountryDisplayLabel')}
${extractFunction('createSelectOptionElement')}
${extractFunction('setSelectOptions')}
${extractFunction('normalizeMaDaoOptionListItems')}
${extractFunction('resolveMaDaoOptionSelectedValue')}
${extractFunction('setMaDaoProviderSelectOptions')}
${extractFunction('setMaDaoCountrySelectOptions')}
${extractFunction('setMaDaoOperatorSelectOptions')}
${extractFunction('buildMaDaoRequestUrl')}
${extractFunction('buildMaDaoRequestHeaders')}
${extractFunction('fetchMaDaoJson')}
${extractFunction('getMaDaoProvidersFromPayload')}
${extractFunction('getMaDaoOptionItemsFromPayload')}
${extractFunction('getSelectedMaDaoProviderId')}
${extractFunction('getSelectedMaDaoCountry')}
${extractFunction('loadMaDaoProviders')}
${extractFunction('loadMaDaoCountries')}
${extractFunction('loadMaDaoOperators')}
return {
loadMaDaoProviders,
selectMaDaoProviderId,
selectMaDaoCountry,
selectMaDaoOperator,
get providerOptions() { return selectMaDaoProviderId.options.map((option) => ({ value: option.value, label: option.textContent, selected: option.selected })); },
get countryOptions() { return selectMaDaoCountry.options.map((option) => ({ value: option.value, label: option.textContent, selected: option.selected })); },
get operatorOptions() { return selectMaDaoOperator.options.map((option) => ({ value: option.value, label: option.textContent, selected: option.selected })); },
get displayText() { return displayText; },
get toastText() { return toastText; },
};
`)(fetchImpl);
const providers = await api.loadMaDaoProviders();
assert.deepStrictEqual(providers.map((provider) => provider.value), ['stored-provider']);
assert.equal(api.selectMaDaoProviderId.value, 'stored-provider');
assert.equal(api.selectMaDaoCountry.value, 'GB');
assert.equal(api.selectMaDaoOperator.value, 'operator-a');
assert.deepStrictEqual(api.providerOptions.map((option) => option.value), ['', 'stored-provider']);
assert.deepStrictEqual(api.countryOptions.map((option) => option.value), ['', 'GB', 'TH']);
assert.deepStrictEqual(api.countryOptions.map((option) => option.label), ['请先选择服务商', '英国', '泰国']);
assert.deepStrictEqual(api.operatorOptions.map((option) => option.value), ['', 'operator-a', 'operator-b']);
assert.deepStrictEqual(api.operatorOptions.map((option) => option.label), ['任意线路', 'Operator A', 'Operator B']);
assert.equal(api.displayText, 'stored-provider/GB/operator-a');
assert.equal(api.toastText, '已刷新 MaDao 服务商。');
assert.deepStrictEqual(requests.map((request) => request.pathname), [
'/api/providers',
'/api/providers/stored-provider/countries',
'/api/providers/stored-provider/operators',
]);
assert.deepStrictEqual(requests[2].body, { country: 'GB' });
assert.equal(requests[0].options.headers.Authorization, 'Bearer madao-secret');
});
test('HeroSMS country parser accepts keyed country maps from the live API', () => {
const api = new Function(`
${extractFunction('normalizeHeroSmsCountryPayloadEntries')}
@@ -704,6 +939,7 @@ const rowMaDaoMode = { style: { display: 'none' } };
const rowMaDaoRoutingPlanId = { style: { display: 'none' } };
const rowMaDaoProviderId = { style: { display: 'none' } };
const rowMaDaoCountry = { style: { display: 'none' } };
const rowMaDaoOperator = { style: { display: 'none' } };
const rowMaDaoAutoPickCountry = { style: { display: 'none' } };
const rowMaDaoReusePhone = { style: { display: 'none' } };
const rowMaDaoPriceRange = { style: { display: 'none' } };
@@ -785,8 +1021,7 @@ const PHONE_SMS_PROVIDER_UI_DESCRIPTORS = ${JSON.stringify({
directRowKeys: [
'rowMaDaoProviderId',
'rowMaDaoCountry',
'rowMaDaoAutoPickCountry',
'rowMaDaoReusePhone',
'rowMaDaoOperator',
'rowMaDaoPriceRange',
],
},
@@ -862,6 +1097,7 @@ return {
rowMaDaoRoutingPlanId,
rowMaDaoProviderId,
rowMaDaoCountry,
rowMaDaoOperator,
rowMaDaoAutoPickCountry,
rowMaDaoReusePhone,
rowMaDaoPriceRange,
@@ -941,6 +1177,7 @@ return {
assert.equal(api.rowMaDaoRoutingPlanId.style.display, 'none');
assert.equal(api.rowMaDaoProviderId.style.display, 'none');
assert.equal(api.rowMaDaoCountry.style.display, 'none');
assert.equal(api.rowMaDaoOperator.style.display, 'none');
assert.equal(api.rowMaDaoAutoPickCountry.style.display, 'none');
assert.equal(api.rowMaDaoReusePhone.style.display, 'none');
assert.equal(api.rowMaDaoPriceRange.style.display, 'none');
@@ -1057,6 +1294,7 @@ return {
assert.equal(api.rowMaDaoRoutingPlanId.style.display, '');
assert.equal(api.rowMaDaoProviderId.style.display, 'none');
assert.equal(api.rowMaDaoCountry.style.display, 'none');
assert.equal(api.rowMaDaoOperator.style.display, 'none');
assert.equal(api.rowMaDaoAutoPickCountry.style.display, 'none');
assert.equal(api.rowMaDaoReusePhone.style.display, 'none');
assert.equal(api.rowMaDaoPriceRange.style.display, 'none');
@@ -1069,8 +1307,9 @@ return {
assert.equal(api.rowMaDaoRoutingPlanId.style.display, 'none');
assert.equal(api.rowMaDaoProviderId.style.display, '');
assert.equal(api.rowMaDaoCountry.style.display, '');
assert.equal(api.rowMaDaoAutoPickCountry.style.display, '');
assert.equal(api.rowMaDaoReusePhone.style.display, '');
assert.equal(api.rowMaDaoOperator.style.display, '');
assert.equal(api.rowMaDaoAutoPickCountry.style.display, 'none');
assert.equal(api.rowMaDaoReusePhone.style.display, 'none');
assert.equal(api.rowMaDaoPriceRange.style.display, '');
});
@@ -1160,9 +1399,10 @@ const inputNexSmsServiceCode = { value: 'ot' };
const inputMaDaoBaseUrl = { value: 'http://127.0.0.1:7822/api/acquire' };
const inputMaDaoHttpSecret = { value: 'madao-secret' };
const selectMaDaoMode = { value: 'direct' };
const inputMaDaoRoutingPlanId = { value: 'plan-1' };
const inputMaDaoProviderId = { value: 'Local Provider!!' };
const inputMaDaoCountry = { value: 'th' };
const selectMaDaoRoutingPlanId = { value: 'plan-1' };
const selectMaDaoProviderId = { value: 'Local Provider!!' };
const selectMaDaoCountry = { value: 'th' };
const selectMaDaoOperator = { value: 'Operator A!' };
const inputMaDaoAutoPickCountry = { checked: false };
const inputMaDaoReusePhone = { checked: true };
const inputMaDaoMinPrice = { value: '0.02' };
@@ -1266,7 +1506,9 @@ ${extractFunction('normalizeMaDaoBaseUrlValue')}
${extractFunction('normalizeMaDaoModeValue')}
${extractFunction('normalizeMaDaoIdentifierValue')}
${extractFunction('normalizeMaDaoProviderIdValue')}
${extractFunction('normalizeMaDaoOperatorValue')}
${extractFunction('normalizeMaDaoCountry')}
${extractFunction('formatMaDaoCountryDisplayLabel')}
${extractFunction('normalizeMaDaoPriceValue')}
${extractFunction('normalizeFiveSimCountryCode')}
${extractFunction('normalizeFiveSimCountryOrderValue')}
@@ -1338,6 +1580,7 @@ return { collectSettingsPayload };
assert.equal(payload.madaoRoutingPlanId, 'plan-1');
assert.equal(payload.madaoProviderId, 'localprovider');
assert.equal(payload.madaoCountry, 'TH');
assert.equal(payload.madaoOperator, 'operatora');
assert.equal(payload.madaoAutoPickCountry, false);
assert.equal(payload.madaoReusePhone, true);
assert.equal(payload.madaoMinPrice, '0.02');
@@ -1389,6 +1632,7 @@ let latestState = {
madaoRoutingPlanId: 'plan-old',
madaoProviderId: 'provider-old',
madaoCountry: 'TH',
madaoOperator: 'operator-old',
madaoAutoPickCountry: true,
madaoReusePhone: true,
madaoMinPrice: '0.01',
@@ -1434,9 +1678,34 @@ const inputNexSmsServiceCode = { value: 'ot' };
const inputMaDaoBaseUrl = { value: 'http://127.0.0.1:7822/api/poll' };
const inputMaDaoHttpSecret = { value: 'madao-live-secret' };
const selectMaDaoMode = { value: 'direct' };
const inputMaDaoRoutingPlanId = { value: 'plan-live' };
const inputMaDaoProviderId = { value: 'Provider Live!' };
const inputMaDaoCountry = { value: 'local' };
const selectMaDaoRoutingPlanId = {
value: 'plan-live',
options: [],
replaceChildren(...children) {
this.options = children;
},
};
const selectMaDaoProviderId = {
value: 'Provider Live!',
options: [],
replaceChildren(...children) {
this.options = children;
},
};
const selectMaDaoCountry = {
value: 'local',
options: [],
replaceChildren(...children) {
this.options = children;
},
};
const selectMaDaoOperator = {
value: 'Operator Live!',
options: [],
replaceChildren(...children) {
this.options = children;
},
};
const inputMaDaoAutoPickCountry = { checked: false };
const inputMaDaoReusePhone = { checked: false };
const inputMaDaoMinPrice = { value: '0.02' };
@@ -1454,6 +1723,10 @@ let heroSmsCountrySelectionOrder = [];
let phoneSmsProviderOrderSelection = ['hero-sms', '5sim'];
let lastPhoneSmsProviderBeforeChange = null;
let savedPayload = null;
let maDaoRoutingPlanOptions = [];
let maDaoProviderOptions = [];
let maDaoCountryOptions = [];
let maDaoOperatorOptions = [];
${extractFunction('normalizePhoneSmsProvider')}
${extractFunction('normalizePhoneSmsProviderValue')}
@@ -1483,9 +1756,21 @@ ${extractFunction('normalizeNexSmsServiceCodeValue')}
${extractFunction('normalizeMaDaoBaseUrlValue')}
${extractFunction('normalizeMaDaoModeValue')}
${extractFunction('normalizeMaDaoIdentifierValue')}
${extractFunction('normalizeMaDaoRoutingPlanIdValue')}
${extractFunction('normalizeMaDaoProviderIdValue')}
${extractFunction('normalizeMaDaoOperatorValue')}
${extractFunction('normalizeMaDaoCountry')}
${extractFunction('normalizeMaDaoPriceValue')}
${extractFunction('formatMaDaoCountryDisplayLabel')}
${extractFunction('createSelectOptionElement')}
${extractFunction('setSelectOptions')}
${extractFunction('normalizeMaDaoOptionListItems')}
${extractFunction('resolveMaDaoOptionSelectedValue')}
${extractFunction('buildMaDaoRoutingPlanOptions')}
${extractFunction('setMaDaoRoutingPlanSelectOptions')}
${extractFunction('setMaDaoProviderSelectOptions')}
${extractFunction('setMaDaoCountrySelectOptions')}
${extractFunction('setMaDaoOperatorSelectOptions')}
${extractFunction('normalizePhoneSmsMinPriceValue')}
${extractFunction('normalizePhoneSmsMaxPriceValue')}
${extractFunction('normalizeHeroSmsCountryId')}
@@ -1508,6 +1793,7 @@ function syncLatestState(patch) { latestState = { ...latestState, ...patch }; }
function loadHeroSmsCountries() { return Promise.resolve(); }
function loadFiveSimCountries() { return Promise.resolve(); }
function loadNexSmsCountries() { return Promise.resolve(); }
function loadMaDaoProviders() { return Promise.resolve(); }
function applyHeroSmsFallbackSelection() {}
function applyFiveSimCountrySelection() {}
function applyNexSmsCountrySelection() {}
@@ -1564,6 +1850,71 @@ return {
assert.equal(api.savedPayload.fiveSimMinPrice, '0.88');
});
test('buildPhoneSmsProviderStatePatch preserves MaDao hidden reuse defaults when controls are absent', () => {
const api = new Function(`
let latestState = {
phoneSmsProvider: 'madao',
madaoBaseUrl: 'http://127.0.0.1:7822',
madaoHttpSecret: 'secret-old',
madaoMode: 'direct',
madaoRoutingPlanId: 'plan-old',
madaoProviderId: 'provider-old',
madaoCountry: 'TH',
madaoOperator: 'any',
madaoAutoPickCountry: true,
madaoReusePhone: true,
madaoMinPrice: '0.01',
madaoMaxPrice: '0.09',
};
const PHONE_SMS_PROVIDER_HERO_SMS = 'hero-sms';
const PHONE_SMS_PROVIDER_HERO = PHONE_SMS_PROVIDER_HERO_SMS;
const PHONE_SMS_PROVIDER_FIVE_SIM = '5sim';
const PHONE_SMS_PROVIDER_NEXSMS = 'nexsms';
const PHONE_SMS_PROVIDER_MADAO = 'madao';
const DEFAULT_PHONE_SMS_PROVIDER = PHONE_SMS_PROVIDER_HERO_SMS;
const DEFAULT_FIVE_SIM_COUNTRY_ID = 'vietnam';
const DEFAULT_FIVE_SIM_COUNTRY_LABEL = '越南 (Vietnam)';
const DEFAULT_FIVE_SIM_OPERATOR = 'any';
const DEFAULT_FIVE_SIM_PRODUCT = 'openai';
const DEFAULT_NEX_SMS_SERVICE_CODE = 'ot';
const DEFAULT_MADAO_BASE_URL = 'http://127.0.0.1:7822';
const MADAO_MODE_ROUTING_PLAN = 'routing_plan';
const MADAO_MODE_DIRECT = 'direct';
const DEFAULT_MADAO_MODE = MADAO_MODE_ROUTING_PLAN;
const selectMaDaoMode = { value: 'direct' };
const selectMaDaoRoutingPlanId = { value: 'plan-live' };
const selectMaDaoProviderId = { value: 'provider-live' };
const selectMaDaoCountry = { value: 'GB' };
const selectMaDaoOperator = { value: 'Any operator' };
const inputMaDaoBaseUrl = { value: 'http://127.0.0.1:7822/api/acquire' };
const inputMaDaoHttpSecret = { value: 'secret-live' };
const inputMaDaoMinPrice = { value: '0.02' };
const inputMaDaoMaxPrice = { value: '0.20' };
${extractFunction('normalizePhoneSmsProvider')}
${extractFunction('normalizeMaDaoBaseUrlValue')}
${extractFunction('normalizeMaDaoModeValue')}
${extractFunction('normalizeMaDaoIdentifierValue')}
${extractFunction('normalizeMaDaoRoutingPlanIdValue')}
${extractFunction('normalizeMaDaoProviderIdValue')}
${extractFunction('normalizeMaDaoOperatorValue')}
${extractFunction('normalizeMaDaoCountry')}
${extractFunction('normalizeMaDaoPriceValue')}
${extractFunction('normalizePhoneSmsMinPriceValue')}
${extractFunction('normalizePhoneSmsMaxPriceValue')}
${extractFunction('buildPhoneSmsProviderStatePatch')}
return { buildPhoneSmsProviderStatePatch };
`)();
const patch = api.buildPhoneSmsProviderStatePatch('madao');
assert.equal(patch.madaoAutoPickCountry, true);
assert.equal(patch.madaoReusePhone, true);
assert.equal(patch.madaoOperator, '');
assert.equal(patch.madaoProviderId, 'provider-live');
assert.equal(patch.madaoCountry, 'GB');
});
test('HeroSMS operator helpers normalize keyed operator payloads', () => {
const api = new Function(`
const DEFAULT_HERO_SMS_OPERATOR = 'any';