305 lines
14 KiB
JavaScript
305 lines
14 KiB
JavaScript
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const source = fs.readFileSync('background.js', 'utf8');
|
|
function load(extra = {}) {
|
|
const sandbox = {
|
|
chrome: {
|
|
action: { onClicked: { addListener() {} } },
|
|
tabs: { async sendMessage() {} },
|
|
scripting: { async executeScript() {} },
|
|
runtime: { onMessage: { addListener() {} } },
|
|
storage: { local: { async get() { return {}; }, async set() {} } }
|
|
},
|
|
fetch: async () => { throw new Error('not mocked'); },
|
|
URL,
|
|
console,
|
|
Date,
|
|
Number,
|
|
String,
|
|
Object,
|
|
Array,
|
|
RegExp,
|
|
Math,
|
|
JSON,
|
|
Set,
|
|
...extra,
|
|
};
|
|
const fn = new Function('globalThis', 'self', `with (globalThis) { ${source}; return { collectPriceEntries, filterPriceEntries, normalizeActivation, extractVerificationCode, isPhoneNumberUsedError, isPhoneNumberDeliveryRefusedError, getPhoneReplacementReleaseAction, fetchNextPhone, normalizeSettings, buildUrl, fetchActiveCode, releaseActivation }; }`);
|
|
return fn(sandbox, sandbox);
|
|
}
|
|
test('collectPriceEntries parses keyed price inventory', () => {
|
|
const api = load();
|
|
const entries = api.collectPriceEntries({ 6: { dr: { '0.12': { count: 3 }, '0.20': { count: 0 } } } });
|
|
assert.deepEqual(entries.filter((x) => x.inStock).map((x) => x.cost), [0.12]);
|
|
});
|
|
test('filterPriceEntries respects min and max', () => {
|
|
const api = load();
|
|
const entries = [{ cost: 0.05, inStock: true }, { cost: 0.15, inStock: true }, { cost: 0.3, inStock: true }];
|
|
assert.deepEqual(api.filterPriceEntries(entries, { minPrice: '0.1', maxPrice: '0.2' }).map((x) => x.cost), [0.15]);
|
|
});
|
|
test('normalizeActivation parses ACCESS_NUMBER response', () => {
|
|
const api = load();
|
|
const activation = api.normalizeActivation('ACCESS_NUMBER:12345:628123456789', { countryId: 6, countryLabel: 'Indonesia' });
|
|
assert.equal(activation.activationId, '12345');
|
|
assert.equal(activation.phoneNumber, '628123456789');
|
|
assert.equal(activation.countryLabel, 'Indonesia');
|
|
});
|
|
test('extractVerificationCode parses STATUS_OK response', () => {
|
|
const api = load();
|
|
assert.equal(api.extractVerificationCode('STATUS_OK: Your code is 654321'), '654321');
|
|
assert.equal(api.extractVerificationCode('STATUS_OK:987654'), '987654');
|
|
});
|
|
test('FlowPilot-style phone failure classifiers choose release action', () => {
|
|
const api = load();
|
|
assert.equal(api.isPhoneNumberUsedError('该电话已被使用,请换一个号码'), true);
|
|
assert.equal(api.isPhoneNumberUsedError('手机号已绑定,请换一个号码'), true);
|
|
assert.equal(api.isPhoneNumberDeliveryRefusedError('无法向此电话号码发送验证码'), true);
|
|
assert.equal(api.getPhoneReplacementReleaseAction('phone_number_used'), 'ban');
|
|
assert.equal(api.getPhoneReplacementReleaseAction('phone_delivery_refused'), 'cancel');
|
|
});
|
|
|
|
test('buildUrl keeps api_key and replacement queries explicit', () => {
|
|
const api = load();
|
|
const url = new URL(api.buildUrl(
|
|
{ baseUrl: 'https://smsbower.app/stubs/handler_api.php', apiKey: 'demo-key' },
|
|
{ action: 'getNumberV2', service: 'dr', country: 6, maxPrice: 0.2 }
|
|
));
|
|
assert.equal(url.searchParams.get('api_key'), 'demo-key');
|
|
assert.equal(url.searchParams.get('action'), 'getNumberV2');
|
|
assert.equal(url.searchParams.get('service'), 'dr');
|
|
assert.equal(url.searchParams.get('country'), '6');
|
|
assert.equal(url.searchParams.get('maxPrice'), '0.2');
|
|
});
|
|
|
|
test('fetchNextPhone releases previous activation then acquires replacement', async () => {
|
|
const requests = [];
|
|
const stored = {
|
|
apiKey: 'demo-key',
|
|
baseUrl: 'https://smsbower.app/stubs/handler_api.php',
|
|
serviceCode: 'dr',
|
|
minPrice: '0.1',
|
|
maxPrice: '0.3',
|
|
countries: [{ id: 6, label: 'Indonesia' }],
|
|
activeActivation: { activationId: 'old-activation', phoneNumber: '628111', countryId: 6 },
|
|
};
|
|
const api = load({
|
|
chrome: {
|
|
action: { onClicked: { addListener() {} } },
|
|
tabs: { async sendMessage() {} },
|
|
scripting: { async executeScript() {} },
|
|
runtime: { onMessage: { addListener() {} } },
|
|
storage: { local: { async get() { return stored; }, async set(update) { Object.assign(stored, update); } } }
|
|
},
|
|
fetch: async (url) => {
|
|
const parsed = new URL(url);
|
|
const action = parsed.searchParams.get('action');
|
|
requests.push([action, parsed.searchParams.get('id'), parsed.searchParams.get('status'), parsed.searchParams.get('country')]);
|
|
if (action === 'setStatus') return new Response('ACCESS_CANCEL', { status: 200 });
|
|
if (action === 'getPricesV3') return new Response(JSON.stringify({ 6: { dr: { '0.2': { count: 2 } } } }), { status: 200 });
|
|
if (action === 'getNumberV2') return new Response(JSON.stringify({ activationId: 'new-activation', phoneNumber: '628222', countryCode: 6 }), { status: 200 });
|
|
throw new Error(`unexpected ${url}`);
|
|
},
|
|
Response,
|
|
});
|
|
const result = await api.fetchNextPhone({ reason: 'phone_number_used', releaseAction: 'ban' });
|
|
assert.equal(result.activation.activationId, 'new-activation');
|
|
assert.equal(result.releasePrevious.releaseAction, 'ban');
|
|
assert.equal(stored.activeActivation.previousActivationId, 'old-activation');
|
|
assert.deepEqual(requests, [
|
|
['setStatus', 'old-activation', '8', null],
|
|
['getPricesV3', null, null, '6'],
|
|
['getNumberV2', null, null, '6'],
|
|
]);
|
|
});
|
|
|
|
|
|
test('buildUrl uses Luban apikey parameter and base endpoint', () => {
|
|
const api = load();
|
|
const url = new URL(api.buildUrl(
|
|
{ provider: 'luban-sms', lubanBaseUrl: 'https://lubansms.com/v2/api/getNumber', apiKey: 'luban-key' },
|
|
{ service_id: '121949' }
|
|
));
|
|
assert.equal(url.searchParams.get('apikey'), 'luban-key');
|
|
assert.equal(url.searchParams.get('api_key'), null);
|
|
assert.equal(url.searchParams.get('service_id'), '121949');
|
|
});
|
|
|
|
test('normalizeActivation parses Luban getNumber response', () => {
|
|
const api = load();
|
|
const activation = api.normalizeActivation(
|
|
{ code: 0, msg: '', number: '79781901206', request_id: 230698 },
|
|
{ provider: 'luban-sms', serviceCode: '121949' }
|
|
);
|
|
assert.equal(activation.provider, 'luban-sms');
|
|
assert.equal(activation.activationId, '230698');
|
|
assert.equal(activation.phoneNumber, '79781901206');
|
|
assert.equal(activation.serviceCode, '121949');
|
|
});
|
|
|
|
test('fetchNextPhone acquires Luban number and rejects previous request', async () => {
|
|
const requests = [];
|
|
const stored = {
|
|
provider: 'luban-sms',
|
|
apiKey: 'luban-key',
|
|
lubanBaseUrl: 'https://lubansms.com/v2/api/getNumber',
|
|
lubanServiceId: '121949',
|
|
activeActivation: { activationId: 'old-request', phoneNumber: '79780000000', provider: 'luban-sms' },
|
|
};
|
|
const api = load({
|
|
chrome: {
|
|
action: { onClicked: { addListener() {} } },
|
|
tabs: { async sendMessage() {} },
|
|
scripting: { async executeScript() {} },
|
|
runtime: { onMessage: { addListener() {} } },
|
|
storage: { local: { async get() { return stored; }, async set(update) { Object.assign(stored, update); } } }
|
|
},
|
|
fetch: async (url) => {
|
|
const parsed = new URL(url);
|
|
requests.push([parsed.pathname, parsed.searchParams.get('apikey'), parsed.searchParams.get('request_id'), parsed.searchParams.get('status'), parsed.searchParams.get('service_id')]);
|
|
if (parsed.pathname.endsWith('/setStatus')) return new Response(JSON.stringify({ code: 0, msg: 'success' }), { status: 200 });
|
|
if (parsed.pathname.endsWith('/List')) return new Response(JSON.stringify({ code: 0, msg: [] }), { status: 200 });
|
|
if (parsed.pathname.endsWith('/getNumber')) return new Response(JSON.stringify({ code: 0, msg: '', number: '79781901206', request_id: 230698 }), { status: 200 });
|
|
throw new Error(`unexpected ${url}`);
|
|
},
|
|
Response,
|
|
});
|
|
const result = await api.fetchNextPhone({ reason: 'manual_replace', releaseAction: 'cancel' });
|
|
assert.equal(result.activation.provider, 'luban-sms');
|
|
assert.equal(result.activation.activationId, '230698');
|
|
assert.equal(stored.activeActivation.previousActivationId, 'old-request');
|
|
assert.deepEqual(requests, [
|
|
['/v2/api/setStatus', 'luban-key', 'old-request', 'reject', null],
|
|
['/v2/api/List', 'luban-key', null, null, null],
|
|
['/v2/api/getNumber', 'luban-key', null, null, '121949'],
|
|
]);
|
|
});
|
|
|
|
test('fetchActiveCode parses Luban success SMS code', async () => {
|
|
const stored = {
|
|
provider: 'luban-sms',
|
|
apiKey: 'luban-key',
|
|
lubanBaseUrl: 'https://lubansms.com/v2/api/getSms',
|
|
activeActivation: { activationId: '230698', phoneNumber: '79781901206', provider: 'luban-sms' },
|
|
};
|
|
const api = load({
|
|
chrome: {
|
|
action: { onClicked: { addListener() {} } },
|
|
tabs: { async sendMessage() {} },
|
|
scripting: { async executeScript() {} },
|
|
runtime: { onMessage: { addListener() {} } },
|
|
storage: { local: { async get() { return stored; }, async set(update) { Object.assign(stored, update); } } }
|
|
},
|
|
fetch: async (url) => {
|
|
const parsed = new URL(url);
|
|
assert.equal(parsed.pathname, '/v2/api/getSms');
|
|
assert.equal(parsed.searchParams.get('request_id'), '230698');
|
|
return new Response(JSON.stringify({ code: 0, msg: 'success', sms_code: '380682' }), { status: 200 });
|
|
},
|
|
Response,
|
|
});
|
|
const result = await api.fetchActiveCode();
|
|
assert.equal(result.code, '380682');
|
|
assert.equal(stored.activeActivation.lastCode, '380682');
|
|
});
|
|
|
|
|
|
test('buildUrl uses 5SIM bearer-token style URL without api_key query', () => {
|
|
const api = load();
|
|
const url = new URL(api.buildUrl(
|
|
{ provider: '5sim', fiveSimBaseUrl: 'https://5sim.net', apiKey: 'five-token' },
|
|
{ __path: 'v1/user/buy/activation/vietnam/any/openai', maxPrice: 0.2 }
|
|
));
|
|
assert.equal(url.origin, 'https://5sim.net');
|
|
assert.equal(url.pathname, '/v1/user/buy/activation/vietnam/any/openai');
|
|
assert.equal(url.searchParams.get('api_key'), null);
|
|
assert.equal(url.searchParams.get('apikey'), null);
|
|
assert.equal(url.searchParams.get('maxPrice'), '0.2');
|
|
});
|
|
|
|
test('fetchNextPhone acquires 5SIM number with Authorization bearer token', async () => {
|
|
const requests = [];
|
|
const stored = {
|
|
provider: '5sim',
|
|
apiKey: 'five-token',
|
|
fiveSimBaseUrl: 'https://5sim.net',
|
|
fiveSimCountry: 'vietnam',
|
|
fiveSimOperator: 'any',
|
|
fiveSimProduct: 'openai',
|
|
maxPrice: '0.25',
|
|
};
|
|
const api = load({
|
|
chrome: {
|
|
action: { onClicked: { addListener() {} } },
|
|
tabs: { async sendMessage() {} },
|
|
scripting: { async executeScript() {} },
|
|
runtime: { onMessage: { addListener() {} } },
|
|
storage: { local: { async get() { return stored; }, async set(update) { Object.assign(stored, update); } } }
|
|
},
|
|
fetch: async (url, options = {}) => {
|
|
const parsed = new URL(url);
|
|
requests.push([parsed.pathname, parsed.searchParams.get('maxPrice'), options.headers.Authorization]);
|
|
if (parsed.pathname === '/v1/user/buy/activation/vietnam/any/openai') {
|
|
return new Response(JSON.stringify({ id: 11631253, phone: '+84901234567', operator: 'viettel', product: 'openai', price: 0.2, status: 'PENDING', country: 'vietnam' }), { status: 200 });
|
|
}
|
|
throw new Error(`unexpected ${url}`);
|
|
},
|
|
Response,
|
|
});
|
|
const result = await api.fetchNextPhone({ reason: 'manual_replace', releaseAction: 'cancel' });
|
|
assert.equal(result.activation.provider, '5sim');
|
|
assert.equal(result.activation.activationId, '11631253');
|
|
assert.equal(result.activation.phoneNumber, '+84901234567');
|
|
assert.equal(stored.activeActivation.activationId, '11631253');
|
|
assert.deepEqual(requests, [['/v1/user/buy/activation/vietnam/any/openai', '0.25', 'Bearer five-token']]);
|
|
});
|
|
|
|
test('fetchActiveCode parses 5SIM order SMS code', async () => {
|
|
const stored = {
|
|
provider: '5sim',
|
|
apiKey: 'five-token',
|
|
fiveSimBaseUrl: 'https://5sim.net',
|
|
activeActivation: { activationId: '11631253', phoneNumber: '+84901234567', provider: '5sim' },
|
|
};
|
|
const api = load({
|
|
chrome: {
|
|
action: { onClicked: { addListener() {} } },
|
|
tabs: { async sendMessage() {} },
|
|
scripting: { async executeScript() {} },
|
|
runtime: { onMessage: { addListener() {} } },
|
|
storage: { local: { async get() { return stored; }, async set(update) { Object.assign(stored, update); } } }
|
|
},
|
|
fetch: async (url, options = {}) => {
|
|
const parsed = new URL(url);
|
|
assert.equal(parsed.pathname, '/v1/user/check/11631253');
|
|
assert.equal(options.headers.Authorization, 'Bearer five-token');
|
|
return new Response(JSON.stringify({ id: 11631253, status: 'RECEIVED', sms: [{ text: 'Your code is 09363', code: '09363' }] }), { status: 200 });
|
|
},
|
|
Response,
|
|
});
|
|
const result = await api.fetchActiveCode();
|
|
assert.equal(result.code, '09363');
|
|
assert.equal(stored.activeActivation.lastCode, '09363');
|
|
});
|
|
|
|
test('releaseActivation cancels or bans 5SIM order using management endpoints', async () => {
|
|
const requests = [];
|
|
const stored = { provider: '5sim', apiKey: 'five-token', fiveSimBaseUrl: 'https://5sim.net' };
|
|
const api = load({
|
|
fetch: async (url, options = {}) => {
|
|
const parsed = new URL(url);
|
|
requests.push([parsed.pathname, options.headers.Authorization]);
|
|
return new Response(JSON.stringify({ id: 1001, status: parsed.pathname.includes('/ban/') ? 'BANNED' : 'CANCELED' }), { status: 200 });
|
|
},
|
|
Response,
|
|
});
|
|
const cancel = await api.releaseActivation(stored, { activationId: '1001' }, 'cancel');
|
|
const ban = await api.releaseActivation(stored, { activationId: '1002' }, 'ban');
|
|
assert.equal(cancel.ok, true);
|
|
assert.equal(ban.ok, true);
|
|
assert.deepEqual(requests, [
|
|
['/v1/user/cancel/1001', 'Bearer five-token'],
|
|
['/v1/user/ban/1002', 'Bearer five-token'],
|
|
]);
|
|
});
|