diff --git a/config/env/common.js b/config/env/common.js index d2ec368..a3a246f 100644 --- a/config/env/common.js +++ b/config/env/common.js @@ -175,7 +175,11 @@ export default { // v2 Circuit Relay server (FullStack.cash) // '/ip4/78.46.129.7/tcp/4001/p2p/12D3KooWFQ11GQ5NubsJGhYZ4X3wrAGimLevxfm6HPExCrMYhpSL' - ] + ], // END IPFS CONFIGURATION + + // Nostr + nostrRelay: process.env.NOSTR_RELAY ? process.env.NOSTR_RELAY : '', + nostrTopic: process.env.NOSTR_TOPIC ? process.env.NOSTR_TOPIC : 'bch-dex-test-topic' } diff --git a/config/env/test.js b/config/env/test.js index dfe0e6c..e848878 100644 --- a/config/env/test.js +++ b/config/env/test.js @@ -8,5 +8,6 @@ export default { session: 'secret-boilerplate-token', token: 'secret-jwt-token', database: 'mongodb://localhost:27017/bch-swap-service-test', - env: 'test' + env: 'test', + nostrRelay: 'wss://' } diff --git a/package-lock.json b/package-lock.json index b69026a..7e17dc4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,7 @@ "@psf/bch-js": "6.7.3", "axios": "0.27.2", "bch-message-lib": "2.2.1", + "bch-nostr": "^1.3.1", "bch-token-sweep": "2.2.1", "bcryptjs": "2.4.3", "bitcoincashjs-lib": "3.3.3", @@ -5500,6 +5501,93 @@ "dev": true, "license": "MIT" }, + "node_modules/@scure/base": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz", + "integrity": "sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "license": "MIT" + }, + "node_modules/@scure/bip32": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.3.1.tgz", + "integrity": "sha512-osvveYtyzdEVbt3OfwwXFr4P2iVBL5u1Q3q4ONBfDY/UpOuXmOlbgwc1xECEboY8wIays8Yt6onaWMUdUbfl0A==", + "license": "MIT", + "dependencies": { + "@noble/curves": "~1.1.0", + "@noble/hashes": "~1.3.1", + "@scure/base": "~1.1.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32/node_modules/@noble/curves": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.1.0.tgz", + "integrity": "sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.3.1" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32/node_modules/@noble/curves/node_modules/@noble/hashes": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.1.tgz", + "integrity": "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32/node_modules/@noble/hashes": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.3.tgz", + "integrity": "sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.2.1.tgz", + "integrity": "sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "~1.3.0", + "@scure/base": "~1.1.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39/node_modules/@noble/hashes": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.3.tgz", + "integrity": "sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@semantic-release/commit-analyzer": { "version": "9.0.2", "resolved": "http://94.130.170.209:4873/@semantic-release%2fcommit-analyzer/-/commit-analyzer-9.0.2.tgz", @@ -7293,6 +7381,15 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/base58-js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base58-js/-/base58-js-2.0.0.tgz", + "integrity": "sha512-nAV5d32QXuGcGptSApkKpC1gGakWBnfJMNjKrYTBh4tb0szfZF+ooueFLy8T4VrY+o4SrE/TyrtUnRZcwZchaA==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "http://94.130.170.209:4873/base64-js/-/base64-js-1.5.1.tgz", @@ -7499,6 +7596,91 @@ "minimal-slp-wallet": ">= 4" } }, + "node_modules/bch-nostr": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/bch-nostr/-/bch-nostr-1.3.1.tgz", + "integrity": "sha512-bSq70zQkJ0ELXnZHqY2NjaZ9ovwL6eMOdvxbajPzuq3ZcNce5r8mTgyFgJASLdloDxFUjfP6yeJBJj0HZzE8Pg==", + "license": "MIT", + "dependencies": { + "apidoc": "0.53.0", + "base58-js": "2.0.0", + "bch-message-lib": "2.3.1", + "nostr": "0.2.8", + "nostr-tools": "2.10.1", + "ws": "8.18.0" + } + }, + "node_modules/bch-nostr/node_modules/apidoc": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/apidoc/-/apidoc-0.53.0.tgz", + "integrity": "sha512-H4hYToCrI6TgSksdVHxS7h5/Xoqr/rR9m9/IOQaYqXbX07cPVbg0O3RM4+bR4tJKG1aqBWKKm+xojQ8+4i7O5A==", + "license": "MIT", + "os": [ + "darwin", + "freebsd", + "linux", + "openbsd", + "win32" + ], + "dependencies": { + "bootstrap": "3.4.1", + "commander": "^8.3.0", + "diff-match-patch": "^1.0.5", + "esbuild-loader": "^2.16.0", + "expose-loader": "^3.1.0", + "fs-extra": "^10.0.0", + "glob": "^7.2.0", + "handlebars": "^4.7.7", + "iconv-lite": "^0.6.3", + "jquery": "^3.6.0", + "klaw-sync": "^6.0.0", + "lodash": "^4.17.21", + "markdown-it": "^12.2.0", + "nodemon": "^2.0.15", + "prismjs": "^1.25.0", + "semver": "^7.3.5", + "style-loader": "^3.3.1", + "webpack": "^5.64.2", + "webpack-cli": "^4.9.1", + "winston": "^3.3.3" + }, + "bin": { + "apidoc": "bin/apidoc" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/bch-nostr/node_modules/bch-message-lib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/bch-message-lib/-/bch-message-lib-2.3.1.tgz", + "integrity": "sha512-xAWMRvmcP2mYTdp9KgjSQfxlJF4DTloCJrjGx0TumD3HPF9mmYZ/CiCGUAFdQk3LBCavnK0AZi9v3+JTNSnrcA==", + "license": "MIT", + "peerDependencies": { + "minimal-slp-wallet": ">= 5" + } + }, + "node_modules/bch-nostr/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/bch-token-sweep": { "version": "2.2.1", "resolved": "http://94.130.170.209:4873/bch-token-sweep/-/bch-token-sweep-2.2.1.tgz", @@ -17184,6 +17366,7 @@ "resolved": "http://94.130.170.209:4873/mkdirp/-/mkdirp-0.5.6.tgz", "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "license": "MIT", + "peer": true, "dependencies": { "minimist": "^1.2.6" }, @@ -17786,6 +17969,13 @@ "node": ">=4" } }, + "node_modules/noble-secp256k1": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/noble-secp256k1/-/noble-secp256k1-1.2.14.tgz", + "integrity": "sha512-GSCXyoZBUaaPwVWdYncMEmzlSUjF9J/YeEHpklYJCyg8wPuJP3NzDx0BkiwArzINkdX2HJHvUJhL6vVWPOQQcg==", + "deprecated": "Switch to namespaced @noble/secp256k1 for security and feature updates", + "license": "MIT" + }, "node_modules/node-abi": { "version": "3.71.0", "resolved": "http://94.130.170.209:4873/node-abi/-/node-abi-3.71.0.tgz", @@ -18034,6 +18224,93 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/nostr": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/nostr/-/nostr-0.2.8.tgz", + "integrity": "sha512-p25OQiEB5x8rWWoWLRRbxtxQiaQgypaQQakSKw5+cp/SW0DXea/soMzqmTXei1I6HrcARuyfvedGTdoEU471Ow==", + "license": "MIT", + "dependencies": { + "noble-secp256k1": "^1.2.14", + "ws": "^8.8.1" + } + }, + "node_modules/nostr-tools": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/nostr-tools/-/nostr-tools-2.10.1.tgz", + "integrity": "sha512-fnAxLi92UgyMAEw4fMEhDKzH53uBJ0WkkVPTNfX0b3bspeWWn0n5tDZtKRJbvW0wAGOPmU6LYWi/IKPOsq3p2Q==", + "license": "Unlicense", + "dependencies": { + "@noble/ciphers": "^0.5.1", + "@noble/curves": "1.2.0", + "@noble/hashes": "1.3.1", + "@scure/base": "1.1.1", + "@scure/bip32": "1.3.1", + "@scure/bip39": "1.2.1" + }, + "optionalDependencies": { + "nostr-wasm": "v0.1.0" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/nostr-tools/node_modules/@noble/ciphers": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.5.3.tgz", + "integrity": "sha512-B0+6IIHiqEs3BPMT0hcRmHvEj2QHOLu+uwt+tqDDeVd0oyVzh7BPrDcPjRnV1PV/5LaknXJJQvOuRGR0zQJz+w==", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/nostr-tools/node_modules/@noble/curves": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", + "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.3.2" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/nostr-tools/node_modules/@noble/curves/node_modules/@noble/hashes": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", + "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/nostr-tools/node_modules/@noble/hashes": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.1.tgz", + "integrity": "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/nostr-wasm": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/nostr-wasm/-/nostr-wasm-0.1.0.tgz", + "integrity": "sha512-78BTryCLcLYv96ONU8Ws3Q1JzjlAt+43pWQhIl86xZmWeegYCNLPml7yQ+gG3vR6V5h4XGj+TxO+SS5dsThQIA==", + "license": "MIT", + "optional": true + }, "node_modules/npm": { "version": "8.19.4", "resolved": "http://94.130.170.209:4873/npm/-/npm-8.19.4.tgz", @@ -26382,6 +26659,7 @@ "resolved": "http://94.130.170.209:4873/write-file-atomic/-/write-file-atomic-4.0.2.tgz", "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", "license": "ISC", + "peer": true, "dependencies": { "imurmurhash": "^0.1.4", "signal-exit": "^3.0.7" diff --git a/package.json b/package.json index f709a0f..a4078fc 100644 --- a/package.json +++ b/package.json @@ -28,11 +28,10 @@ }, "repository": "Permissionless-Software-Foundation/bch-dex", "dependencies": { - "@chris.troutner/retry-queue": "1.0.8", - "@psf/bch-js": "6.7.3", "@chainsafe/libp2p-gossipsub": "13.1.1", "@chainsafe/libp2p-noise": "15.1.1", "@chainsafe/libp2p-yamux": "6.0.2", + "@chris.troutner/retry-queue": "1.0.8", "@helia/unixfs": "3.0.7", "@libp2p/bootstrap": "10.1.5", "@libp2p/circuit-relay-v2": "1.1.5", @@ -43,18 +42,20 @@ "@libp2p/webrtc": "4.1.7", "@libp2p/websockets": "8.2.0", "@multiformats/multiaddr": "12.3.0", + "@psf/bch-js": "6.7.3", "axios": "0.27.2", "bch-message-lib": "2.2.1", + "bch-nostr": "1.3.1", "bch-token-sweep": "2.2.1", "bcryptjs": "2.4.3", "bitcoincashjs-lib": "3.3.3", - "ipfs-coord-esm": "9.1.7", - "ipfs-http-client": "58.0.0", "blockstore-fs": "2.0.0", "datastore-fs": "9.1.7", "glob": "7.1.6", "helia": "4.2.5", "helia-coord": "1.5.15", + "ipfs-coord-esm": "9.1.7", + "ipfs-http-client": "58.0.0", "jsonrpc-lite": "2.2.0", "jsonwebtoken": "8.5.1", "jwt-bch-lib": "1.3.0", diff --git a/src/adapters/index.js b/src/adapters/index.js index d7438fd..701899d 100644 --- a/src/adapters/index.js +++ b/src/adapters/index.js @@ -23,6 +23,7 @@ import BCHAdapter from './bch.js' import WalletAdapter from './wallet.js' import P2wdbAdapter from './p2wdb-adapter.js' import Webhook from './webhook.js' +import NostrAdapter from './nostr.js' class Adapters { constructor (localConfig = {}) { @@ -41,6 +42,8 @@ class Adapters { localConfig.bchWallet = this.wallet.bchWallet this.p2wdb = new P2wdbAdapter(localConfig) this.webhook = new Webhook() + this.nostr = new NostrAdapter(this.config) + // this.wallet = new Wallet(localConfig) // Get a valid JWT API key and instance bch-js. @@ -73,6 +76,8 @@ class Adapters { // Open the wallet file const walletData = await this.wallet.openWallet() + await this.nostr.start(walletData.privateKey) + // console.log('walletData: ', walletData) if (this.config.env !== 'test') { diff --git a/src/adapters/nostr.js b/src/adapters/nostr.js new file mode 100644 index 0000000..b44fb0d --- /dev/null +++ b/src/adapters/nostr.js @@ -0,0 +1,120 @@ +/* + Adapter library for working with Nostr. +*/ + +import BchNostr from 'bch-nostr' +import { RelayPool } from 'nostr' + +class NostrAdapter { + constructor (localConfig = { nostrRelay: '', nostrTopic: '' }) { + this.relayWs = localConfig.nostrRelay // + this.topic = localConfig.nostrTopic + + if (!this.relayWs) { + throw new Error( + '"nostrRelay" must be passed when instantiate NostrAdapter' + ) + } + + if (!this.topic) { + throw new Error( + '"nostrTopic" must be passed when instantiate NostrAdapter' + ) + } + + // Encapsulate dependencies + this.bchNostr = new BchNostr() + this.RelayPool = RelayPool + + // Bind the 'this' object + this.start = this.start.bind(this) + this.post = this.post.bind(this) + this.read = this.read.bind(this) + } + + // Create nostr keys. + // NOTE: WIF must be provided in this function instead the constructor function + // we can provide it after app wallet creation. + async start (WIF) { + try { + if (!WIF || typeof WIF !== 'string') { + throw new Error('WIF must be a string!') + } + + const { privKeyBuf, nostrPubKey } = + this.bchNostr.keys.createNostrPubKeyFromWif({ wif: WIF }) + + this.privKeyBuf = privKeyBuf + this.nostrPubKey = nostrPubKey + + console.log(`Nostr Pub Key : ${nostrPubKey}`) + + return true + } catch (error) { + console.log(`Error in nostr.js/start() ${error.message} `) + throw error + } + } + + // Post a message over the instance-topic. + async post (msg = '') { + try { + if (!msg || typeof msg !== 'string') { + throw new Error('msg must be a string!') + } + + const inObj = { + kind: 867, + privKeyBuf: this.privKeyBuf, + nostrPubKey: this.nostrPubKey, + relayWs: this.relayWs, + msg, + tags: [['t', this.topic]] + } + const eventId = await this.bchNostr.post.uploadToNostr(inObj) + return eventId + } catch (error) { + console.log(`Error in nostr.js/post() ${error.message} `) + throw error + } + } + + // Read a message over the instance-topic. + async read (limit = 10) { + try { + if (typeof limit !== 'number' || limit < 1) { + throw new Error('Limit must be greater than 0') + } + + const relays = [this.relayWs] + const pool = this.RelayPool(relays) + + const nostrData = new Promise((resolve, reject) => { + const messages = [] + + pool.on('open', (relay) => { + // relay.subscribe('REQ', { ids: [eventId] }) + relay.subscribe('REQ', { limit, kinds: [867], '#t': [this.topic] }) + }) + + pool.on('eose', (relay) => { + relay.close() + resolve(messages) + }) + + pool.on('event', (relay, subId, ev) => { + messages.push(ev.content) + }) + }) + + const messages = await nostrData + + return messages + } catch (error) { + console.log(`Error in nostr.js/read() ${error.message} `) + throw error + } + } +} + +export default NostrAdapter diff --git a/test/unit/adapters/nostr.adapter.unit.js b/test/unit/adapters/nostr.adapter.unit.js new file mode 100644 index 0000000..341512b --- /dev/null +++ b/test/unit/adapters/nostr.adapter.unit.js @@ -0,0 +1,131 @@ +/* + Unit tests for the Nostr Adapter library. +*/ + +// Public npm libraries. +import { assert } from 'chai' +import sinon from 'sinon' +import NostrAdapter from '../../../src/adapters/nostr.js' +import NostrMock from '../mocks/nostr-mock.js' + +describe('#nostr.js', () => { + let uut + let sandbox + + before(() => {}) + + beforeEach(() => { + uut = new NostrAdapter({ + nostrRelay: 'wss://nostr', + nostrTopic: 'uut-topic' + }) + uut.RelayPool = NostrMock.RelayPoolMock + + sandbox = sinon.createSandbox() + }) + + afterEach(() => sandbox.restore()) + + after(() => {}) + + describe('#contructor', () => { + it('should throw an error if nostr relay is not provided', async () => { + try { + const _uut = new NostrAdapter() + // Eslint + console.log(_uut) + + assert.fail('Unexpected code path') + } catch (err) { + // console.log('err: ', err) + assert.include( + err.message, + '"nostrRelay" must be passed when instantiate NostrAdapter' + ) + } + }) + it('should throw an error if nostr topic is not provided', async () => { + try { + const _uut = new NostrAdapter({ nostrRelay: 'wss://' }) + // Eslint + console.log(_uut) + + assert.fail('Unexpected code path') + } catch (err) { + // console.log('err: ', err) + assert.include( + err.message, + '"nostrTopic" must be passed when instantiate NostrAdapter' + ) + } + }) + }) + describe('#start', () => { + it('should throw an error if WIF is not provided', async () => { + try { + await uut.start() + + assert.fail('Unexpected code path') + } catch (err) { + // console.log('err: ', err) + assert.include(err.message, 'WIF must be a string!') + } + }) + it('should start adapter', async () => { + // Stub bchNostr + sandbox + .stub(uut.bchNostr.keys, 'createNostrPubKeyFromWif') + .returns({ privKeyBuf: 'privKeyBuf', nostrPubKey: 'nostrPubKey' }) + + const result = await uut.start('WIF') + assert.isTrue(result) + }) + }) + + describe('#post', () => { + it('should throw an error if msg is not provided', async () => { + try { + await uut.post() + + assert.fail('Unexpected code path') + } catch (err) { + // console.log('err: ', err) + assert.include(err.message, 'msg must be a string!') + } + }) + it('should post message', async () => { + const evId = 'uut-eventId' // Mock + + sandbox.stub(uut.bchNostr.post, 'uploadToNostr').resolves(evId) + + const result = await uut.post('uut-message') + assert.isString(result) + assert.equal(result, evId) + }) + }) + + describe('#read', () => { + it('should throw an error if provided limit is wrong', async () => { + try { + await uut.read(-5) + + assert.fail('Unexpected code path') + } catch (err) { + // console.log('err: ', err) + assert.include(err.message, 'Limit must be greater than 0') + } + }) + + it('should read message', async () => { + const result = await uut.read() + assert.isArray(result) + assert.equal(result.length, 1) + }) + + it('should read message with custom limit', async () => { + const result = await uut.read(1) + assert.isArray(result) + assert.equal(result.length, 1) + }) + }) +}) diff --git a/test/unit/mocks/nostr-mock.js b/test/unit/mocks/nostr-mock.js new file mode 100644 index 0000000..b457d82 --- /dev/null +++ b/test/unit/mocks/nostr-mock.js @@ -0,0 +1,15 @@ +class RelayPoolClassMock { + constructor() { + this.relay = { close: () => { } , subscribe: () => { } } + this.event = { content: 'content' } + } + on(ev, callback) { + callback(this.relay, null, this.event) + } +} + +const RelayPoolMock =()=>{ + return new RelayPoolClassMock() +} + +export default { RelayPoolMock } \ No newline at end of file