feat: add SMS Bower phone SMS provider
This commit is contained in:
@@ -128,7 +128,8 @@ const PHONE_SMS_PROVIDER_HERO_SMS = '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_ORDER = ['hero-sms', '5sim', 'nexsms', 'madao'];
|
||||
const PHONE_SMS_PROVIDER_SMS_BOWER = 'sms-bower';
|
||||
const DEFAULT_PHONE_SMS_PROVIDER_ORDER = ['hero-sms', '5sim', 'nexsms', 'madao', 'sms-bower'];
|
||||
const DEFAULT_PHONE_SMS_PROVIDER = PHONE_SMS_PROVIDER_HERO_SMS;
|
||||
const DEFAULT_MADAO_BASE_URL = 'http://127.0.0.1:7822';
|
||||
const DEFAULT_MADAO_MODE = 'routing_plan';
|
||||
|
||||
@@ -24,30 +24,36 @@ test('phone sms provider registry normalizes ids, order and labels consistently'
|
||||
PhoneSmsMaDaoProvider: {
|
||||
createProvider: (deps = {}) => ({ provider: 'madao', deps }),
|
||||
},
|
||||
PhoneSmsBowerProvider: {
|
||||
createProvider: (deps = {}) => ({ provider: 'sms-bower', deps }),
|
||||
},
|
||||
});
|
||||
|
||||
assert.deepStrictEqual(registry.getProviderIds(), ['hero-sms', '5sim', 'nexsms', 'madao']);
|
||||
assert.deepStrictEqual(registry.getProviderIds(), ['hero-sms', '5sim', 'nexsms', 'madao', 'sms-bower']);
|
||||
assert.equal(registry.normalizeProviderId(' NEXSMS '), 'nexsms');
|
||||
assert.equal(registry.normalizeProviderId(' MaDao '), 'madao');
|
||||
assert.equal(registry.normalizeProviderId('unknown-provider'), 'hero-sms');
|
||||
assert.equal(registry.getProviderLabel('nexsms'), 'NexSMS');
|
||||
assert.equal(registry.getProviderLabel('madao'), 'MaDao');
|
||||
assert.equal(registry.getProviderLabel('sms-bower'), 'SMS Bower');
|
||||
assert.equal(registry.getProviderDefinition('nexsms').moduleKey, 'PhoneSmsNexSmsProvider');
|
||||
assert.equal(registry.getProviderDefinition('madao').moduleKey, 'PhoneSmsMaDaoProvider');
|
||||
assert.equal(registry.getProviderDefinition('sms-bower').moduleKey, 'PhoneSmsBowerProvider');
|
||||
assert.deepStrictEqual(
|
||||
registry.normalizeProviderOrder([
|
||||
{ provider: 'madao' },
|
||||
{ provider: 'sms-bower' },
|
||||
{ provider: 'nexsms' },
|
||||
{ id: '5sim' },
|
||||
{ value: 'hero-sms' },
|
||||
'MADAO',
|
||||
'NEXSMS',
|
||||
]),
|
||||
['madao', 'nexsms', '5sim', 'hero-sms']
|
||||
['madao', 'sms-bower', 'nexsms', '5sim', 'hero-sms']
|
||||
);
|
||||
assert.deepStrictEqual(
|
||||
registry.normalizeProviderOrder([], ['madao', 'nexsms', '5sim', 'nexsms']),
|
||||
['madao', 'nexsms', '5sim']
|
||||
registry.normalizeProviderOrder([], ['madao', 'sms-bower', 'nexsms', '5sim', 'nexsms']),
|
||||
['madao', 'sms-bower', 'nexsms', '5sim']
|
||||
);
|
||||
assert.deepStrictEqual(
|
||||
registry.createProvider('5sim', { foo: 1 }),
|
||||
@@ -61,6 +67,10 @@ test('phone sms provider registry normalizes ids, order and labels consistently'
|
||||
registry.createProvider('nexsms', { foo: 3 }),
|
||||
{ provider: 'nexsms', deps: { foo: 3 } }
|
||||
);
|
||||
assert.deepStrictEqual(
|
||||
registry.createProvider('sms-bower', { foo: 4 }),
|
||||
{ provider: 'sms-bower', deps: { foo: 4 } }
|
||||
);
|
||||
});
|
||||
|
||||
test('phone sms provider registry can create the real MaDao provider module', () => {
|
||||
|
||||
@@ -933,6 +933,7 @@ const rowNexSmsApiKey = { style: { display: 'none' } };
|
||||
const rowNexSmsCountry = { style: { display: 'none' } };
|
||||
const rowNexSmsCountryFallback = { style: { display: 'none' } };
|
||||
const rowNexSmsServiceCode = { style: { display: 'none' } };
|
||||
const rowSmsBowerApiKey = { style: { display: 'none' } };
|
||||
const rowMaDaoBaseUrl = { style: { display: 'none' } };
|
||||
const rowMaDaoHttpSecret = { style: { display: 'none' } };
|
||||
const rowMaDaoMode = { style: { display: 'none' } };
|
||||
@@ -1009,6 +1010,11 @@ const PHONE_SMS_PROVIDER_UI_DESCRIPTORS = ${JSON.stringify({
|
||||
'rowNexSmsServiceCode',
|
||||
],
|
||||
},
|
||||
'sms-bower': {
|
||||
rowKeys: [
|
||||
'rowSmsBowerApiKey',
|
||||
],
|
||||
},
|
||||
madao: {
|
||||
rowKeys: [
|
||||
'rowMaDaoBaseUrl',
|
||||
@@ -1091,6 +1097,7 @@ return {
|
||||
rowNexSmsCountry,
|
||||
rowNexSmsCountryFallback,
|
||||
rowNexSmsServiceCode,
|
||||
rowSmsBowerApiKey,
|
||||
rowMaDaoBaseUrl,
|
||||
rowMaDaoHttpSecret,
|
||||
rowMaDaoMode,
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
|
||||
const source = fs.readFileSync('phone-sms/providers/sms-bower.js', 'utf8');
|
||||
const api = new Function('self', `${source}; return self.PhoneSmsBowerProvider;`)({});
|
||||
|
||||
function createTextResponse(payload, ok = true, status = ok ? 200 : 400, statusText = ok ? 'OK' : 'Bad Request') {
|
||||
return {
|
||||
ok,
|
||||
status,
|
||||
statusText,
|
||||
text: async () => (typeof payload === 'string' ? payload : JSON.stringify(payload)),
|
||||
};
|
||||
}
|
||||
|
||||
test('SMS Bower provider uses handler API key query for balance and price catalog', async () => {
|
||||
const requests = [];
|
||||
const provider = api.createProvider({
|
||||
fetchImpl: async (url, options = {}) => {
|
||||
const parsed = new URL(url);
|
||||
requests.push({ url: parsed, options });
|
||||
if (parsed.searchParams.get('action') === 'getBalance') {
|
||||
return createTextResponse('ACCESS_BALANCE:12.34');
|
||||
}
|
||||
if (parsed.searchParams.get('action') === 'getPrices') {
|
||||
return createTextResponse({ 52: { ot: { cost: 0.21, count: 7 } } });
|
||||
}
|
||||
throw new Error(`unexpected ${parsed.toString()}`);
|
||||
},
|
||||
});
|
||||
|
||||
const balance = await provider.fetchBalance({ smsBowerApiKey: 'demo-key' });
|
||||
const prices = await provider.fetchPrices({ smsBowerApiKey: 'demo-key', smsBowerCountryId: 52 });
|
||||
const entries = provider.collectPriceEntries(prices, []);
|
||||
|
||||
assert.equal(requests[0].url.origin + requests[0].url.pathname, 'https://smsbower.page/stubs/handler_api.php');
|
||||
assert.equal(requests[0].url.searchParams.get('api_key'), 'demo-key');
|
||||
assert.equal(requests[0].url.searchParams.get('action'), 'getBalance');
|
||||
assert.equal(balance.balance, 12.34);
|
||||
assert.equal(requests[1].url.searchParams.get('action'), 'getPrices');
|
||||
assert.equal(requests[1].url.searchParams.get('service'), 'ot');
|
||||
assert.equal(requests[1].url.searchParams.get('country'), '52');
|
||||
assert.deepStrictEqual(entries, [{ cost: 0.21, count: 7, inStock: true }]);
|
||||
});
|
||||
|
||||
test('SMS Bower provider acquires number, polls code, and updates activation statuses', async () => {
|
||||
const requests = [];
|
||||
const provider = api.createProvider({
|
||||
fetchImpl: async (url, options = {}) => {
|
||||
const parsed = new URL(url);
|
||||
const action = parsed.searchParams.get('action');
|
||||
requests.push({ url: parsed, options });
|
||||
if (action === 'getNumber') {
|
||||
return createTextResponse('ACCESS_NUMBER:12345:447700900123');
|
||||
}
|
||||
if (action === 'getStatus') {
|
||||
return createTextResponse('STATUS_OK:Your OpenAI code is 654321');
|
||||
}
|
||||
if (action === 'setStatus') {
|
||||
return createTextResponse(parsed.searchParams.get('status') === '6' ? 'ACCESS_ACTIVATION' : 'ACCESS_CANCEL');
|
||||
}
|
||||
throw new Error(`unexpected ${parsed.toString()}`);
|
||||
},
|
||||
sleepWithStop: async () => {},
|
||||
throwIfStopped: () => {},
|
||||
});
|
||||
|
||||
const state = {
|
||||
smsBowerApiKey: 'demo-key',
|
||||
smsBowerServiceCode: 'ot',
|
||||
smsBowerCountryId: 16,
|
||||
smsBowerCountryLabel: 'United Kingdom',
|
||||
smsBowerMaxPrice: '0.3',
|
||||
smsBowerMinPrice: '0.1',
|
||||
smsBowerProviderIds: '3170,3180',
|
||||
};
|
||||
|
||||
const activation = await provider.requestActivation(state);
|
||||
const code = await provider.pollActivationCode(state, activation, { timeoutMs: 1000, intervalMs: 1, maxRounds: 1 });
|
||||
await provider.finishActivation(state, activation);
|
||||
await provider.cancelActivation(state, activation);
|
||||
await provider.banActivation(state, activation);
|
||||
const reused = await provider.reuseActivation(state, activation);
|
||||
|
||||
assert.equal(activation.provider, 'sms-bower');
|
||||
assert.equal(activation.activationId, '12345');
|
||||
assert.equal(activation.phoneNumber, '447700900123');
|
||||
assert.equal(activation.countryId, 16);
|
||||
assert.equal(code, '654321');
|
||||
assert.equal(reused.activationId, '12345');
|
||||
|
||||
const acquireUrl = requests[0].url;
|
||||
assert.equal(acquireUrl.searchParams.get('action'), 'getNumber');
|
||||
assert.equal(acquireUrl.searchParams.get('service'), 'ot');
|
||||
assert.equal(acquireUrl.searchParams.get('country'), '16');
|
||||
assert.equal(acquireUrl.searchParams.get('maxPrice'), '0.3');
|
||||
assert.equal(acquireUrl.searchParams.get('minPrice'), '0.1');
|
||||
assert.equal(acquireUrl.searchParams.get('providerIds'), '3170,3180');
|
||||
assert.deepStrictEqual(
|
||||
requests.map((entry) => [entry.url.searchParams.get('action'), entry.url.searchParams.get('status')]),
|
||||
[
|
||||
['getNumber', null],
|
||||
['getStatus', null],
|
||||
['setStatus', '6'],
|
||||
['setStatus', '8'],
|
||||
['setStatus', '8'],
|
||||
['setStatus', '3'],
|
||||
]
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const vm = require('node:vm');
|
||||
|
||||
function loadRegistry() {
|
||||
const sandbox = { globalThis: {} };
|
||||
sandbox.self = sandbox.globalThis;
|
||||
vm.runInNewContext(fs.readFileSync('phone-sms/providers/sms-bower.js', 'utf8'), sandbox);
|
||||
vm.runInNewContext(fs.readFileSync('phone-sms/providers/registry.js', 'utf8'), sandbox);
|
||||
return sandbox.globalThis.PhoneSmsProviderRegistry;
|
||||
}
|
||||
|
||||
test('SMS Bower is registered as a known phone SMS provider', () => {
|
||||
const registry = loadRegistry();
|
||||
assert.equal(registry.normalizeProviderId('sms-bower'), 'sms-bower');
|
||||
assert.equal(registry.getProviderLabel('sms-bower'), 'SMS Bower');
|
||||
assert.ok(registry.getProviderIds().includes('sms-bower'));
|
||||
assert.deepEqual(
|
||||
Array.from(registry.normalizeProviderOrder(['sms-bower', 'hero-sms'])),
|
||||
['sms-bower', 'hero-sms']
|
||||
);
|
||||
const provider = registry.createProvider('sms-bower', { fetchImpl: async () => ({ ok: true, text: async () => 'ACCESS_BALANCE:1.23' }) });
|
||||
assert.equal(provider.id, 'sms-bower');
|
||||
});
|
||||
Reference in New Issue
Block a user