From 4b557a5d97efa3977ae5cd3c4bc593752c08db1f Mon Sep 17 00:00:00 2001 From: Rastislav Date: Wed, 28 Aug 2024 16:29:57 +0200 Subject: [PATCH] Downloads and tests --- .gitignore | 1 + README.md | 19 ++- bin/txms | 200 ++++++++++++++++++++------ package.json | 14 +- src/index.ts | 34 +++-- test/index.js | 352 ++++++++++++++++++++++++++++++---------------- test/samples.json | 5 + tsconfig.json | 1 + 8 files changed, 442 insertions(+), 184 deletions(-) diff --git a/.gitignore b/.gitignore index 347ecfd..99303e4 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ .output /build /dist +/test/output # OS .DS_Store diff --git a/README.md b/README.md index 444ae3b..9d702c6 100644 --- a/README.md +++ b/README.md @@ -145,7 +145,7 @@ The library is designed to be compatible with both module systems, so you can ch - `getEndpoint(network?: number | string, countriesList?: string | Array): { [key: string]: Array }` — Get an object of SMS endpoints (phone numbers) per country. - `sms(number?: boolean | string | number | Array, message?: string, network?: number | string, encodeMessage?: boolean, platform?: string): string` — Create an SMS URI based on the provided parameters. - `mms(number?: boolean | string | number | Array, message?: string, network?: number | string, encodeMessage?: boolean, platform?: string): string` — Create an MMS URI based on the provided parameters. -- `downloadMessage(hex: string | string[], optionalFilename?: string): Promise` — Download a file with the encoded content as `.txms.txt` file in your working directory. You can provide one hex transaction or an array of transactions (`.batch` will be prepended to suffix if batch is chosen and optional name not defined). +- `downloadMessage(hex: string | string[], optionalFilename?: string, optionalPath?: string): Promise` — Download a file with the encoded content as `.txms.txt` file in your working directory. You can provide one hex transaction or an array of transactions (`.batch` will be prepended to suffix if batch is chosen and optional name not defined). Note: The `downloadMessage` function is asynchronous and returns a Promise. You can use the `await` keyword to wait for the Promise to resolve. The function will download a file with the encoded content as a `(.batch).txms.txt` file in your working directory. You can optionally provide a filename as the second parameter. It is designed to be used in Node.js environments as well as Browser. It is not designed to download high amount of files. if you prefer to do your own download flow, you can use the `encode` function and save the result to a file. @@ -160,6 +160,7 @@ Note: The `downloadMessage` function is asynchronous and returns a Promise. You - `encodeMessage` (default: `true`) = whether to encode the message before using `encodeURIComponent`. - `platform` = the platform to use for the SMS URI. Currently supported: `ios`, `global`. Default: `global`. `ios` uses the `&body=`, while `global` uses the `?` for `?body=` parameter. - `optionalFilename` = the optional filename for the downloaded file suffixed with `.txms.txt`. Filename is slugified. +- `optionalPath` = the optional path for the downloaded file. If not provided, the file will be saved in the working directory. ## CLI @@ -172,17 +173,23 @@ npm i -g txms.js ### Getting started ```bash -txms {type} {value} {value1} +txms {type}={value} ``` -- type: `version` (`v`), `encode` (`e`), `decode` (`d`), `getendpoint` (`g`), `sms`, `mms`, `download` (`dl`) -- value: 1st parameter for the type -- value1: 2nd parameter for the type +Types: + +- `--version` (`-v`) - Get the version of the library. +- `--encode` (`-e`) - Encode the HEX transaction. +- `--decode` (`-d`) - Decode the UTF-16BE transaction. +- `--getendpoint` (`-g`) - Get the SMS/MMS endpoint for the network and country. +- `--sms` - Create an SMS URI based on the provided parameters. +- `--mms` - Create an MMS URI based on the provided parameters. +- `--download` (`-dl`) - Boolean value to download a file with the encoded content as `.txms.txt` file in your working directory. ### Piping ```bash -echo {value} | txms {type} {value1} +echo {value} | txms {type}={value1} ``` ## Extending Aliases and Countries diff --git a/bin/txms b/bin/txms index 170b105..93cbccf 100644 --- a/bin/txms +++ b/bin/txms @@ -1,14 +1,122 @@ #!/usr/bin/env node +import { mkdirSync, readFileSync, existsSync } from 'fs'; +import { fileURLToPath } from 'url'; +import path from 'path'; import txms from '../dist/index.js'; -if (process.stdin.isTTY) { - const typ = process.argv[2]; - const value = process.argv[3]; - const value1 = process.argv[4]; +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +// Helper function to parse command-line arguments with key-value pairs +function parseArgs(argv) { + const args = { + kind: null, // The type of operation to perform + value: null, // This will hold the hex data for encode/decode + output: null, // For download output directory + filename: null, // For download filename + countryCodes: null, // For getEndpoint + phoneNumbers: null, // For SMS/MMS phone numbers + download: false, // Flag to indicate download + }; + + argv.forEach((arg) => { + if (arg.startsWith('--')) { + const [key, value] = arg.split('='); + switch (key) { + case '--version': + args.kind = 'version'; + break; + case '--encode': + args.kind = 'encode'; + args.value = value; + break; + case '--decode': + args.kind = 'decode'; + args.value = value; + break; + case '--getendpoint': + args.kind = 'getendpoint'; + args.value = value; // Network type for getEndpoint + break; + case '--sms': + args.kind = 'sms'; + args.phoneNumbers = value; // Comma-separated phone numbers + break; + case '--mms': + args.kind = 'mms'; + args.phoneNumbers = value; // Comma-separated phone numbers + break; + case '--download': + args.download = true; + break; + case '--output': + args.output = value; + break; + case '--filename': + args.filename = value; + break; + case '--countries': + args.countryCodes = value.split(','); // Comma-separated country codes + break; + default: + break; + } + } else if (arg.startsWith('-')) { + const [key, value] = arg.split('='); + switch (key) { + case '-v': + args.kind = 'version'; + break; + case '-e': + args.kind = 'encode'; + args.value = value; + break; + case '-d': + args.kind = 'decode'; + args.value = value; + break; + case '-g': + args.kind = 'getendpoint'; + args.value = value; + break; + case '-s': + args.kind = 'sms'; + args.phoneNumbers = value; + break; + case '-m': + args.kind = 'mms'; + args.phoneNumbers = value; + break; + case '-o': + args.output = value; + break; + case '-f': + args.filename = value; + break; + case '-c': + args.countryCodes = value.split(','); + break; + default: + break; + } + } else { + if (!args.value) { + args.value = arg; + } + } + }); + return args; +} - run(typ, value, value1); +// Parse the arguments +const args = parseArgs(process.argv.slice(2)); + +if (process.stdin.isTTY) { + // If the script is run with a TTY, process the command-line arguments + run(args.kind, args.value, args.output, args.countryCodes, args.phoneNumbers, args.download, args.filename); } else { + // If data is being piped into the script, capture it let content = ''; process.stdin.setEncoding('utf8'); process.stdin.on('data', (buf) => { @@ -17,59 +125,65 @@ if (process.stdin.isTTY) { process.stdin.on('end', () => { content = content.trim(); - let value = content; - let typ = process.argv[2]; - let value1 = process.argv[3]; - if (!content) { - value = process.argv[2]; - typ = process.argv[3]; - value1 = process.argv[4]; + run(args.kind, args.value, args.output, args.countryCodes, args.phoneNumbers, args.download, args.filename); + } else { + run(args.kind, content, args.output, args.countryCodes, args.phoneNumbers, args.download, args.filename); } - - run(typ, value, value1); }); } -function run(typ, value, value1) { - if (!value) { - process.stderr.write('value is required\n'); +async function run(kind, value, output, countryCodes, phoneNumbers, download, filename) { + if (!value && kind !== 'version') { + process.stderr.write('Value is required\n'); process.exit(1); } try { - if (typ === 'version' || typ === 'v') { - const packageJsonPath = join(__dirname, '../package.json'); + if (download && output && !existsSync(output)) { + mkdirSync(output, { recursive: true }); + } + + if (kind === 'version' || kind === 'v') { + const packageJsonPath = path.join(__dirname, '../package.json'); const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8')); const version = packageJson.version; - process.stdout.write(`TxMS version: ${version}\n`); - } else if (typ === 'encode' || typ === 'e') { - const encoded = txms.encode(value); - process.stdout.write(`${encoded}\n`); - } else if (typ === 'decode' || typ === 'd') { + process.stdout.write(version); + process.exit(0); + } else if (kind === 'encode' || kind === 'e') { + if (download) { + const filenm = await txms.downloadMessage(value, filename ? filename : undefined, output); + process.stdout.write(`TxMS file was downloaded as "${filenm}".\n`); + } else { + const encoded = txms.encode(value); + process.stdout.write(encoded); + } + process.exit(0); + } else if (kind === 'decode' || kind === 'd') { const decoded = txms.decode(value); - process.stdout.write(`${decoded}\n`); - } else if (typ === 'getendpoint' || typ === 'g') { - const endpoint = txms.getEndpoint(value, value1); - process.stdout.write(`${JSON.stringify(endpoint, null, 2)}\n`); - } else if (typ === 'sms') { - const sms = txms.sms(true, value); - process.stdout.write(`${sms}\n`); - } else if (typ === 'mms') { - const mms = txms.mms(true, value); - process.stdout.write(`${mms}\n`); - } else if (typ === 'download' || typ === 'dl') { - txms.downloadMessage(value, value1).then((filename) => { - process.stdout.write(`TxMS file was downloaded as "${filename}" in your working directory.\n`); - process.exit(0); - }).catch((err) => { - process.stderr.write(`${err.message}\n`); - process.exit(1); - }); + process.stdout.write(decoded); + process.exit(0); + } else if (kind === 'getendpoint' || kind === 'g') { + const endpoint = txms.getEndpoint(value, countryCodes); + let endpointString = Object.keys(endpoint).map(key => { + const numbers = endpoint[key].join(','); + return `${key}:${numbers}`; + }).join(';'); + process.stdout.write(endpointString); + process.exit(0); + } else if (kind === 'sms') { + const message = txms.encode(value); + const sms = txms.sms(phoneNumbers ? phoneNumbers.split(',') : true, message); + process.stdout.write(sms); + process.exit(0); + } else if (kind === 'mms') { + const message = txms.encode(value); + const mms = txms.mms(phoneNumbers ? phoneNumbers.split(',') : true, message); + process.stdout.write(mms); + process.exit(0); } else { throw new Error('Invalid type specified.'); } - process.exit(0); } catch (err) { process.stderr.write(`${err.message}\n`); process.exit(1); diff --git a/package.json b/package.json index 9c18459..02c0158 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "txms.js", - "version": "1.2.7", + "version": "1.2.8", "description": "Transaction messaging service protocol", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -12,7 +12,7 @@ "build": "tsc -p ./tsconfig.json", "test": "npm run unit", "preunit": "npm run build", - "unit": "tape test/*.js", + "unit": "node --test", "lint": "eslint 'src/**/*.{js,ts}' --fix" }, "engines": { @@ -46,11 +46,11 @@ "author": "@bchainhub", "license": "CORE", "devDependencies": { - "@types/node": "^22.3.0", - "@typescript-eslint/eslint-plugin": "^8.1.0", - "@typescript-eslint/parser": "^8.1.0", - "eslint": "^9.9.0", - "tape": "^5.8.1", + "@types/node": "^22.5.1", + "@typescript-eslint/eslint-plugin": "^8.3.0", + "@typescript-eslint/parser": "^8.3.0", + "eslint": "^9.9.1", + "jsdom": "^25.0.0", "typescript": "^5.5.4" } } diff --git a/src/index.ts b/src/index.ts index 7a7cc76..1b3b61a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,7 +5,7 @@ export interface Transport { sms(number?: boolean | string | number | Array, message?: string, network?: number | string, encodeMessage?: boolean, platform?: string): string; mms(number?: boolean | string | number | Array, message?: string, network?: number | string, encodeMessage?: boolean, platform?: string): string; generateMessageUri(type: 'sms' | 'mms', number?: boolean | string | number | Array, message?: string, network?: number | string, encodeMessage?: boolean, platform?: string): string; - downloadMessage(hex: string | string[], optionalFilename?: string): Promise; + downloadMessage(hex: string | string[], optionalFilename?: string, optionalPath?: string): Promise; } export interface Error extends globalThis.Error { @@ -97,7 +97,7 @@ const txms: Transport = { if (!network) { netw = 1; } else if (typeof network === 'string') { - netw = aliases[network.toLowerCase()]; + netw = aliases[network.toLowerCase()] !== undefined ? aliases[network.toLowerCase()] : parseInt(network, 10); } else { netw = network; } @@ -107,7 +107,10 @@ const txms: Transport = { } else { let endpoints: { [key: string]: string[] } = {}; for (let n = 0; n < requestedList.length; n++) { - endpoints[requestedList[n]] = countries[netw][requestedList[n]]; + const countryCode = requestedList[n]; + if (countries[netw] && countries[netw][countryCode]) { + endpoints[countryCode] = countries[netw][countryCode]; + } } return endpoints; } @@ -167,7 +170,7 @@ const txms: Transport = { return endpoint ? `${type}:${endpoint}${encodedMessage ? `${platform === 'ios' ? '&' : '?'}body=${encodedMessage}` : ''}` : `${type}:${platform === 'ios' ? '&' : '?'}body=${encodedMessage}`; }, - async downloadMessage(hex: string | string[], optionalFilename?: string): Promise { + async downloadMessage(hex: string | string[], optionalFilename?: string, optionalPath?: string): Promise { // If hex is an array, join the encoded messages with newlines const encodedMessage = Array.isArray(hex) ? hex.map(h => this.encode(h)).join('\n') @@ -203,24 +206,37 @@ const txms: Transport = { if (typeof process !== 'undefined' && process.versions && process.versions.node) { // Node.js: Use 'fs' to write the file const fs = await import('fs'); - fs.writeFileSync(filename, encodedMessage); + const path = await import('path'); + + // If an optional path is provided, join it with the filename + const outputPath = optionalPath ? path.join(optionalPath, filename) : filename; + + // Ensure the directory exists + const dir = path.dirname(outputPath); + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } + + fs.writeFileSync(outputPath, encodedMessage); + return outputPath; // Return the full path of the created file } else if (typeof window !== 'undefined' && typeof Blob !== 'undefined' && typeof document !== 'undefined') { // Browser environment + // If an optional path is provided, prepend it to the filename (simulate a path structure) + const fullFilename = optionalPath ? `${optionalPath}/${filename}` : filename; + const blob = new Blob([encodedMessage], { type: 'text/plain;charset=utf-16' }); const link = document.createElement('a'); link.href = URL.createObjectURL(blob); - link.download = filename; + link.download = fullFilename; document.body.appendChild(link); // Append link to body link.click(); // Trigger download document.body.removeChild(link); // Clean up + return fullFilename; // Return the "path/filename" (simulated for the browser) } else { throw new Error('Unsupported environment'); } /* eslint-enable no-undef */ - - return filename; } - }; export default txms; diff --git a/test/index.js b/test/index.js index ba7a1f8..9ea7404 100644 --- a/test/index.js +++ b/test/index.js @@ -1,152 +1,266 @@ +import { test, describe } from 'node:test'; +import { spawnSync } from 'child_process'; +import assert from 'node:assert/strict'; +import path from 'path'; +import { JSDOM } from 'jsdom'; import txms from '../dist/index.js'; -import tape from 'tape'; import samples from './samples.json' assert { type: 'json' }; +import fs, { readFileSync } from 'fs'; +import { fileURLToPath } from 'url'; -samples.valid.forEach(function (f) { - tape.test('OK - Encode - to data. Description: ' + f.description + ' // hex: <' + f.hex.substring(0, 4) + f.hex.slice(-4) + '>', function (t) { - const actual = txms.encode(f.hex); - t.plan(1); - t.same(actual, f.data); +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const txmsPath = path.resolve(__dirname, '../bin/txms'); +const outputDir = path.resolve(__dirname, './output'); + +// Ensure the output directory exists +if (!fs.existsSync(outputDir)) { + fs.mkdirSync(outputDir, { recursive: true }); +} + +// Encode/Decode Tests +describe('Encode/Decode Tests', () => { + samples.valid.forEach((f) => { + test(`OK - Encode - to data. Description: ${f.description}`, () => { + const actual = txms.encode(f.hex); + assert.strictEqual(actual, f.data); + }); }); -}); -samples.valid.forEach(function (f) { - tape.test('OK - Decode - to hex. Description: ' + f.description + ' // data: <' + f.data.substring(0, 4) + f.data.slice(-4) + '>', function (t) { - const actual = txms.decode(f.data); - t.plan(1); - t.same(actual, f.hex); + samples.valid.forEach((f) => { + test(`OK - Decode - to hex. Description: ${f.description}`, () => { + const actual = txms.decode(f.data); + const normalizedActual = actual.startsWith('0x') ? actual.slice(2) : actual; + const normalizedExpected = f.hex.startsWith('0x') ? f.hex.slice(2) : f.hex; + assert.strictEqual(normalizedActual, normalizedExpected); + }); }); -}); -samples.invalid.forEach(function (f) { - tape.test('Encode — ' + f.description + ' // hex: <' + f.hex.substring(0, 4) + f.hex.slice(-4) + '>', function (t) { - t.plan(1); - t.throws(function () { - txms.encode(f.hex); - }, /Not a hex format/); + samples.invalid.forEach((f) => { + test(`Encode — ${f.description}`, () => { + assert.throws(() => { + txms.encode(f.hex); + }, /Not a hex format/); + }); }); }); -tape.test('Endpoints - Mainnet - should return object.', function (t) { - const endpoints = txms.getEndpoint(1, ['us', 'ca']); - t.true(endpoints instanceof Object); - t.end(); -}); +// Endpoint Tests +describe('Endpoint Tests', () => { + test('Endpoints - Mainnet - should return object.', () => { + const endpoints = txms.getEndpoint(1, ['us', 'ca']); + assert.ok(endpoints instanceof Object); + }); -tape.test('Endpoints - Devin - should return object.', function (t) { - const endpoints = txms.getEndpoint('devin', ['bb', 'sx']); - t.true(endpoints instanceof Object); - t.end(); -}); + test('Endpoints - Devin - should return object.', () => { + const endpoints = txms.getEndpoint('devin', ['bb', 'sx']); + assert.ok(endpoints instanceof Object); + }); -tape.test('Endpoints - Default: Mainnet - should return object.', function (t) { - const endpoints = txms.getEndpoint(undefined, ['us', 'ca']); - t.true(endpoints instanceof Object); - t.end(); + test('Endpoints - Default: Mainnet - should return object.', () => { + const endpoints = txms.getEndpoint(undefined, ['us', 'ca']); + assert.ok(endpoints instanceof Object); + }); }); -const hexMessage = 'f8dc821ae4850ee6b280008252080196cb65d677385703c528527f2a0f0e401b4af1988d91c5896e3f4f2ab21845000080b8abcffa127f34f8dc8d8bc9a50da5def786a16ecab58d9d1cdc3e1347077f531ad0339797568345464f542f8da3bcd50fd683878f52e6d094610025d6e4a5fb3699acd20ebd1ec2fdde9d12f5e82fe5f4c8d9061466475b3293bb18c34504c6eb43bc0ba48d61a8edfda686c69773fa96b90d00760d8277330d90589ba26fb63874952b013a8af1a5edacbcabb37108b47518c79abd6e50be00da0a08fb9126fd265175cace1ac93d1f809b80'; +// SMS/MMS Tests +describe('SMS/MMS Tests', () => { + const hexMessage = samples.valid[0].hex; -tape.test('SMS - Single number as string', function (t) { - const smsUri = txms.sms('+12019715152', hexMessage, 'mainnet'); - t.ok(smsUri.startsWith('sms:+12019715152?body=')); - t.end(); -}); + test('SMS - Single number as string', () => { + const smsUri = txms.sms('+12019715152', hexMessage, 'mainnet'); + assert.ok(smsUri.startsWith('sms:+12019715152?body=')); + }); -tape.test('SMS - Single number as number', function (t) { - const smsUri = txms.sms(12019715152, hexMessage, 'mainnet'); - t.ok(smsUri.startsWith('sms:+12019715152?body=')); - t.end(); -}); + test('SMS - Single number as number', () => { + const smsUri = txms.sms(12019715152, hexMessage, 'mainnet'); + assert.ok(smsUri.startsWith('sms:+12019715152?body=')); + }); -tape.test('SMS - Multiple numbers as array', function (t) { - const smsUri = txms.sms(['+12019715152', '+12014835939'], hexMessage, 'mainnet'); - t.ok(smsUri.startsWith('sms:+12019715152,+12014835939?body=')); - t.end(); -}); + test('SMS - Multiple numbers as array', () => { + const smsUri = txms.sms(['+12019715152', '+12014835939'], hexMessage, 'mainnet'); + assert.ok(smsUri.startsWith('sms:+12019715152,+12014835939?body=')); + }); -tape.test('SMS - Default number with boolean true', function (t) { - const smsUri = txms.sms(true, hexMessage, 'mainnet'); - t.ok(smsUri.startsWith('sms:+12019715152?body=')); - t.end(); -}); + test('SMS - Default number with boolean true', () => { + const smsUri = txms.sms(true, hexMessage, 'mainnet'); + assert.ok(smsUri.startsWith('sms:+12019715152?body=')); + }); -tape.test('SMS - Invalid number format', function (t) { - t.plan(1); - t.throws(function () { - txms.sms('2019715152', hexMessage, 'mainnet'); - }, /Invalid number format/); - t.end(); -}); + test('SMS - Invalid number format', () => { + assert.throws(() => { + txms.sms('2019715152', hexMessage, 'mainnet'); + }, /Invalid number format/); + }); -tape.test('SMS - No number provided', function (t) { - const smsUri = txms.sms(false, hexMessage, 'mainnet'); - t.ok(smsUri.startsWith('sms:?body=')); - t.end(); -}); + test('SMS - No number provided', () => { + const smsUri = txms.sms(false, hexMessage, 'mainnet'); + assert.ok(smsUri.startsWith('sms:?body=')); + }); -tape.test('SMS - Encoding hex message', function (t) { - const smsUri = txms.sms('+12019715152', hexMessage, 'mainnet', true); - // The encoded message will vary depending on the implementation of the encode function - t.ok(smsUri.startsWith('sms:+12019715152?body=')); - t.end(); -}); + test('SMS - Encoding hex message', () => { + const smsUri = txms.sms('+12019715152', hexMessage, 'mainnet', true); + assert.ok(smsUri.startsWith('sms:+12019715152?body=')); + }); -tape.test('SMS - No encoding, only URL encode', function (t) { - const smsUri = txms.sms('+12019715152', hexMessage, 'mainnet', false); - // Ensure the message is only URL encoded and not double encoded - t.ok(smsUri.includes(encodeURIComponent(hexMessage))); - t.end(); -}); + test('SMS - No encoding, only URL encode', () => { + const smsUri = txms.sms('+12019715152', hexMessage, 'mainnet', false); + assert.ok(smsUri.includes(encodeURIComponent(hexMessage))); + }); -tape.test('MMS - Single number as string', function (t) { - const mmsUri = txms.mms('+12019715152', hexMessage, 'mainnet'); - t.ok(mmsUri.startsWith('mms:+12019715152?body=')); - t.end(); -}); + test('MMS - Single number as string', () => { + const mmsUri = txms.mms('+12019715152', hexMessage, 'mainnet'); + assert.ok(mmsUri.startsWith('mms:+12019715152?body=')); + }); -tape.test('MMS - Single number as number', function (t) { - const mmsUri = txms.mms(12019715152, hexMessage, 'mainnet'); - t.ok(mmsUri.startsWith('mms:+12019715152?body=')); - t.end(); -}); + test('MMS - Single number as number', () => { + const mmsUri = txms.mms(12019715152, hexMessage, 'mainnet'); + assert.ok(mmsUri.startsWith('mms:+12019715152?body=')); + }); -tape.test('MMS - Multiple numbers as array', function (t) { - const mmsUri = txms.mms(['+12019715152', '+12014835939'], hexMessage, 'mainnet'); - t.ok(mmsUri.startsWith('mms:+12019715152,+12014835939?body=')); - t.end(); -}); + test('MMS - Multiple numbers as array', () => { + const mmsUri = txms.mms(['+12019715152', '+12014835939'], hexMessage, 'mainnet'); + assert.ok(mmsUri.startsWith('mms:+12019715152,+12014835939?body=')); + }); -tape.test('MMS - Default number with boolean true', function (t) { - const mmsUri = txms.mms(true, hexMessage, 'mainnet'); - t.ok(mmsUri.startsWith('mms:+12019715152?body=')); - t.end(); -}); + test('MMS - Default number with boolean true', () => { + const mmsUri = txms.mms(true, hexMessage, 'mainnet'); + assert.ok(mmsUri.startsWith('mms:+12019715152?body=')); + }); -tape.test('MMS - Invalid number format', function (t) { - t.plan(1); - t.throws(function () { - txms.mms('2019715152', hexMessage, 'mainnet'); - }, /Invalid number format/); - t.end(); -}); + test('MMS - Invalid number format', () => { + assert.throws(() => { + txms.mms('2019715152', hexMessage, 'mainnet'); + }, /Invalid number format/); + }); -tape.test('MMS - No number provided', function (t) { - const mmsUri = txms.mms(false, hexMessage, 'mainnet'); - t.ok(mmsUri.startsWith('mms:?body=')); - t.end(); + test('MMS - No number provided', () => { + const mmsUri = txms.mms(false, hexMessage, 'mainnet'); + assert.ok(mmsUri.startsWith('mms:?body=')); + }); + + test('MMS - Encoding hex message', () => { + const mmsUri = txms.mms('+12019715152', hexMessage, 'mainnet', true); + assert.ok(mmsUri.startsWith('mms:+12019715152?body=')); + }); + + test('MMS - No encoding, only URL encode', () => { + const mmsUri = txms.mms('+12019715152', hexMessage, 'mainnet', false); + assert.ok(mmsUri.includes(encodeURIComponent(hexMessage))); + }); }); -tape.test('MMS - Encoding hex message', function (t) { - const mmsUri = txms.mms('+12019715152', hexMessage, 'mainnet', true); - // The encoded message will vary depending on the implementation of the encode function - t.ok(mmsUri.startsWith('mms:+12019715152?body=')); - t.end(); +// Download Message Tests +describe('Download Message Tests', () => { + // Node.js environment test + test('Should download message file in Node.js environment', async () => { + const hex = samples.valid[0].hex; + + // Define the output directory + const outputDir = 'test/output'; + + // Download message to the test/output directory + const filename = await txms.downloadMessage(hex, 'nodejs testdata', outputDir); + assert.match(filename, new RegExp(`${outputDir}/nodejs-testdata\\.txms\\.txt$`)); // Ensure the file was saved in the output directory + assert.ok(fs.existsSync(filename)); // Check if the file actually exists + }); + + // Simulate the browser environment using jsdom + test('Should download message file in simulated browser environment', async () => { + // Create a new JSDOM instance, which simulates a browser environment + const { window } = new JSDOM(); + global.window = window; + global.document = window.document; + global.Blob = window.Blob; + global.URL = window.URL; + + const hex = samples.valid[0].hex; + + // Ensure the filename does not conflict with Node.js test + const filename = await txms.downloadMessage(hex, 'browser-testdata', outputDir); + + // Assert that the filename is correct (no path since it's a browser simulation) + assert.match(filename, /browser-testdata\.txms\.txt$/); + + // Clean up the global variables after the test is done + delete global.window; + delete global.document; + delete global.Blob; + delete global.URL; + }); }); -tape.test('MMS - No encoding, only URL encode', function (t) { - const mmsUri = txms.mms('+12019715152', hexMessage, 'mainnet', false); - // Ensure the message is only URL encoded and not double encoded - t.ok(mmsUri.includes(encodeURIComponent(hexMessage))); - t.end(); +describe('CLI Tests', () => { + test('Should display version', () => { + const packageJsonPath = path.join(__dirname, '../package.json'); + const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8')); + const version = packageJson.version; + const result = spawnSync('node', [txmsPath, '--version']); + assert.strictEqual(result.status, 0); + assert.strictEqual(result.stdout.toString(), version); + }); + + test('Should encode a value', () => { + const hexValue = samples.valid[0].hex; + const result = spawnSync('node', [txmsPath, `--encode=${hexValue}`]); + assert.strictEqual(result.status, 0); + assert.ok(result.stdout.toString().trim().length > 0); + }); + + test('Should decode a value', () => { + const encodedValue = samples.valid[0].data; + const result = spawnSync('node', [txmsPath, `-d="${encodedValue}"`]); + assert.strictEqual(result.status, 0); + assert.ok(result.stdout.toString().trim().startsWith('0x')); + }); + + test('Should return endpoints', () => { + const result = spawnSync('node', [txmsPath, '--getendpoint=1', '--countries=global,sk']); + const stdout = result.stdout.toString(); + assert.strictEqual(result.status, 0); + assert.match(stdout, /^global:\+[\d,]+/); + }); + + test('Should handle invalid input', () => { + const invalidhex = samples.invalid[0].hex; + const result = spawnSync('node', [txmsPath, `--encode=${invalidhex}`]); + assert.notStrictEqual(result.status, 0); + assert.match(result.stderr.toString(), /Not a hex format/); + }); + + test('Should download message file in Node.js environment with specified output path', async () => { + const hexValue = samples.valid[0].hex; + const result = spawnSync('node', [txmsPath, '--download', `--encode=${hexValue}`, `-o=${outputDir}`]); + assert.strictEqual(result.status, 0); + assert.match(result.stdout.toString(), /^TxMS file was downloaded as ".*f8d880f38d00\.txms\.txt"\./); + assert.ok(fs.existsSync(outputDir + '/f8d880f38d00.txms.txt')); + }); + + test('Should download message file in Node.js environment with specified output path and filename', async () => { + const hexValue = samples.valid[0].hex; + const result = spawnSync('node', [txmsPath, '--download', `--encode=${hexValue}`, `-o=${outputDir}`, '-f=cli-testdata']); + assert.strictEqual(result.status, 0); + assert.match(result.stdout.toString(), /^TxMS file was downloaded as ".*cli-testdata\.txms\.txt"\./); + assert.ok(fs.existsSync(outputDir + '/cli-testdata.txms.txt')); + }); + + test('Should encode with piping', () => { + const hexValue = samples.valid[0].hex; + const echo = spawnSync('echo', [hexValue]); + const result = spawnSync('node', [txmsPath, '--encode'], { + input: echo.stdout + }); + assert.strictEqual(result.status, 0); + assert.strictEqual(result.stdout.toString(), samples.valid[0].data); + }); + + test('Should decode with piping', () => { + const dataValue = samples.valid[0].data; + const echo = spawnSync('echo', [dataValue]); + const result = spawnSync('node', [txmsPath, '--decode'], { + input: echo.stdout + }); + assert.strictEqual(result.status, 0); + assert.strictEqual(result.stdout.toString().trim(), samples.valid[0].hex); + }); }); diff --git a/test/samples.json b/test/samples.json index 059f1f5..3200784 100644 --- a/test/samples.json +++ b/test/samples.json @@ -4,6 +4,11 @@ "hex": "0xf8d880843b9aca008252080396ab2896c9af969b11394e3f0be750da75d0076e1d1afd880de0b6b3a764000080b8ab16acb60152c254244a6e3edcd5e66c029caf909f4f2dc4972ff01716d96de84afc62d94c47810b55b38eb0c3572b728552611d3fbb0209e100fde2e8404d168dd431c6d3dd2eec1574d86ad34e2374506cb98ee4576bbf5a78f07003c653d9e8887582020fb369c16fe1ae5e46f0dd4e0b00771fb7ab745c15389be291530859010281f2746b652118a47b07b9763c789e90576f6a2585d530a89892aa3aba2577895e63a3492cbbf38d00", "data": "~Ǹǘ肄㮚쨀艒ࠃ隫⢖즯際ᄹ丿௧僚痐ݮᴚﶈ~čǠ뚳Ꝥ~ĀĀ肸ꬖ겶Œ쉔⑊渾~ǜǕ~ǦŬʜ꾐齏ⷄ霯~ǰėᛙ淨䫼拙䱇脋喳躰썗⭲蕒愝㾻ȉ~ǡĀ~ǽǢ~Ǩŀ䴖跔㇆폝⻬ᕴ~ǘŪ퍎⍴偬릎~Ǥŗ殿婸~ǰŰφ叙~Ǩƈ疂ȏ덩셯~ǡƮ幆~ǰǝ下wᾷꭴ尕㢛~ǢƑ匈夁ʁ~DzŴ步℘ꑻ~ćƹ瘼碞遗潪▅픰ꢘ銪㪺╷襞掣䤬믳贀", "description": "Correct transaction on Devin." + }, + { + "hex": "f8dc821ae4850ee6b280008252080196cb65d677385703c528527f2a0f0e401b4af1988d91c5896e3f4f2ab21845000080b8abcffa127f34f8dc8d8bc9a50da5def786a16ecab58d9d1cdc3e1347077f531ad0339797568345464f542f8da3bcd50fd683878f52e6d094610025d6e4a5fb3699acd20ebd1ec2fdde9d12f5e82fe5f4c8d9061466475b3293bb18c34504c6eb43bc0ba48d61a8edfda686c69773fa96b90d00760d8277330d90589ba26fb63874952b013a8af1a5edacbcabb37108b47518c79abd6e50be00da0a08fb9126fd265175cace1ac93d1f809b80", + "data": "~Ǹǜ舚~Ǥƅ~ĎǦ늀~ĀƂ刈Ɩ쭥홷㡗υ⡒缪༎䀛䫱颍釅襮㽏⪲ᡅ~ĀĀ肸ꯏ晴缴~Ǹǜ趋즥ඥ~ǞǷ蚡滊떍鴜~ǜľፇݿ匚퀳鞗嚃䕆佔⾍ꎼ픏횃螏勦킔愀◖~Ǥƥזּ馬툎봞싽~ǞƝድ~Ǩį~ǥǴ죙ؔ晇嬲鎻ᣃ䔄웫䎼த赡꣭ﶦ蛆靳殺뤍vං眳ඐ墛ꉯ똸璕⬁㪊~DZƥ~ǭƬ벫덱ࢴ甘잚뵮傾Úਈﮑ⛽♑痊츚줽ᾀ鮀", + "description": "Correct transaction without 0x prefix." } ], "invalid": [ diff --git a/tsconfig.json b/tsconfig.json index fc75a84..4213501 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,6 +12,7 @@ "allowJs": false, "strict": true, "noImplicitAny": true, + "removeComments": true, "strictNullChecks": true, "strictFunctionTypes": true, "strictBindCallApply": true,