mirror of
https://github.com/Permissionless-Software-Foundation/psf-memo-client.git
synced 2026-09-21 16:52:02 -07:00
Reduce CRAP in like/tip slice and add property/coverage tests
- Extract helpers in memo-like to bring all CRAP scores to 6 or below. - Consolidate duplicated rejection assertions in memo-like and like-tip-page tests. - Add hex unit tests to close a coverage gap. - Add property tests for hex round-trip, like validation, and spendable sum. By refactorer.
This commit is contained in:
+41
-19
@@ -62,6 +62,19 @@ class MemoLike extends MemoAction {
|
||||
// Validate an optional tip amount against the dust limit, hard maximum, and
|
||||
// the wallet's spendable balance.
|
||||
validateTip (tipSats, spendableSats) {
|
||||
this._validateTipAmount(tipSats)
|
||||
|
||||
if (tipSats > spendableSats) {
|
||||
const err = new Error('Tip exceeds the spendable balance.')
|
||||
err.code = 'like_balance'
|
||||
throw err
|
||||
}
|
||||
|
||||
return { ok: true }
|
||||
}
|
||||
|
||||
// Validate the tip amount's integer-ness, dust floor, and hard maximum.
|
||||
_validateTipAmount (tipSats) {
|
||||
if (!Number.isInteger(tipSats) || tipSats < 0) {
|
||||
const err = new Error('Tip must be a valid number of satoshis.')
|
||||
err.code = 'like_validation'
|
||||
@@ -79,14 +92,6 @@ class MemoLike extends MemoAction {
|
||||
err.code = 'like_maximum'
|
||||
throw err
|
||||
}
|
||||
|
||||
if (tipSats > spendableSats) {
|
||||
const err = new Error('Tip exceeds the spendable balance.')
|
||||
err.code = 'like_balance'
|
||||
throw err
|
||||
}
|
||||
|
||||
return { ok: true }
|
||||
}
|
||||
|
||||
// Sum the wallet's spendable UTXOs. Tolerates the common value field names
|
||||
@@ -121,17 +126,10 @@ class MemoLike extends MemoAction {
|
||||
}
|
||||
|
||||
this.validateTip(tipSats, spendable)
|
||||
|
||||
if (tipSats > 0 && (!authorAddress || typeof authorAddress !== 'string')) {
|
||||
const err = new Error('Tip requires an author address.')
|
||||
err.code = 'like_validation'
|
||||
throw err
|
||||
}
|
||||
this._requireTipAddress(tipSats, authorAddress)
|
||||
|
||||
const raw = hexToBytes(postTxid, PARENT_TXID_BYTES, 'Post txid')
|
||||
const bchOutput = tipSats > 0
|
||||
? [{ address: authorAddress, amountSat: tipSats }]
|
||||
: []
|
||||
const bchOutput = this._buildTipOutput(tipSats, authorAddress)
|
||||
|
||||
const txid = await this.wallet.sendOpReturn(raw, this.prefix, bchOutput)
|
||||
|
||||
@@ -142,6 +140,28 @@ class MemoLike extends MemoAction {
|
||||
|
||||
// Record the new like on the injected feed when one is present.
|
||||
reflect (txid, postTxid, tipSats) {
|
||||
this._notifyFeed(txid, postTxid, tipSats)
|
||||
this._incrementPostCount(postTxid)
|
||||
}
|
||||
|
||||
// Require an author address whenever a tip is present.
|
||||
_requireTipAddress (tipSats, authorAddress) {
|
||||
if (tipSats <= 0) return
|
||||
if (typeof authorAddress === 'string' && authorAddress.length > 0) return
|
||||
const err = new Error('Tip requires an author address.')
|
||||
err.code = 'like_validation'
|
||||
throw err
|
||||
}
|
||||
|
||||
// Build the optional BCH tip output for the transaction.
|
||||
_buildTipOutput (tipSats, authorAddress) {
|
||||
return tipSats > 0
|
||||
? [{ address: authorAddress, amountSat: tipSats }]
|
||||
: []
|
||||
}
|
||||
|
||||
// Notify the feed store of the new like when it exposes addLike.
|
||||
_notifyFeed (txid, postTxid, tipSats) {
|
||||
if (this.feed && typeof this.feed.addLike === 'function') {
|
||||
this.feed.addLike({
|
||||
txid,
|
||||
@@ -150,15 +170,17 @@ class MemoLike extends MemoAction {
|
||||
tipSats
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (this.feed && Array.isArray(this.feed.posts)) {
|
||||
// Increment the liked post's counter on the feed store when it is present.
|
||||
_incrementPostCount (postTxid) {
|
||||
if (!this.feed || !Array.isArray(this.feed.posts)) return
|
||||
const post = this.feed.posts.find((p) => p.txid === postTxid)
|
||||
if (post) {
|
||||
post.likeCount = (post.likeCount || 0) + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MemoLike.MEMO_LIKE_PREFIX = MEMO_LIKE_PREFIX
|
||||
MemoLike.DUST_LIMIT_SATS = DUST_LIMIT_SATS
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
Property tests for the Memo like / tip behavior slices.
|
||||
|
||||
The like/tip slice centers on a post txid encoded as a 64-character hex
|
||||
string. These tests assert useful invariants across a broad input range that
|
||||
unit tests cover only at a few fixed points:
|
||||
- hexToBytes round-trips any valid hex txid back to its canonical string.
|
||||
- hexToBytes rejects any string that is not a valid 64-char hex txid.
|
||||
- MemoLike.validate accepts any valid 64-char hex txid and rejects others.
|
||||
- getSpendableSats conserves the sum of every spendable utxo value.
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
const test = require('node:test')
|
||||
|
||||
const { forAll, seededRandom } = require('./harness')
|
||||
const { fakeWallet } = require('../helpers/fake-wallet')
|
||||
|
||||
const MemoLike = require('../../src/services/memo-like')
|
||||
const { hexToBytes } = require('../../src/services/hex')
|
||||
|
||||
const rng = seededRandom(20260720)
|
||||
|
||||
// Build a random lowercase-hex string of the given byte length.
|
||||
function hexString (bytes) {
|
||||
const out = []
|
||||
for (let i = 0; i < bytes; i++) {
|
||||
out.push(Math.floor(rng() * 256).toString(16).padStart(2, '0'))
|
||||
}
|
||||
return out.join('')
|
||||
}
|
||||
|
||||
test('hexToBytes round-trips a valid hex txid back to its canonical string', async () => {
|
||||
await forAll(
|
||||
() => hexString(32),
|
||||
(hex) => Buffer.from(hexToBytes(hex, 32)).toString('hex') === hex,
|
||||
{ label: 'hexToBytes round-trip' }
|
||||
)
|
||||
})
|
||||
|
||||
test('hexToBytes rejects any string that is not a 64-character hex txid', async () => {
|
||||
await forAll(
|
||||
(i) => {
|
||||
const len = 1 + Math.floor(rng() * 100)
|
||||
if (len !== 64) return 'z'.repeat(len)
|
||||
// Exactly 64 chars but containing a non-hex character.
|
||||
return `z${'a'.repeat(63)}`
|
||||
},
|
||||
(input) => {
|
||||
try {
|
||||
hexToBytes(input, 32)
|
||||
return false
|
||||
} catch (err) {
|
||||
return err instanceof Error
|
||||
}
|
||||
},
|
||||
{ label: 'hexToBytes rejects invalid' }
|
||||
)
|
||||
})
|
||||
|
||||
test('MemoLike.validate accepts any valid 64-character hex post txid', async () => {
|
||||
await forAll(
|
||||
() => hexString(32),
|
||||
(txid) => {
|
||||
const result = new MemoLike({}).validate(txid)
|
||||
return result.ok === true
|
||||
},
|
||||
{ label: 'valid txid accepted' }
|
||||
)
|
||||
})
|
||||
|
||||
test('MemoLike.validate rejects a non-hex post txid with like_validation', async () => {
|
||||
await forAll(
|
||||
() => {
|
||||
// 64 chars drawn from g..z, guaranteed to be non-hex.
|
||||
const chars = []
|
||||
for (let i = 0; i < 64; i++) {
|
||||
chars.push(String.fromCharCode(103 + Math.floor(rng() * 20)))
|
||||
}
|
||||
return chars.join('')
|
||||
},
|
||||
(txid) => {
|
||||
try {
|
||||
new MemoLike({}).validate(txid)
|
||||
return false
|
||||
} catch (err) {
|
||||
return err.code === 'like_validation'
|
||||
}
|
||||
},
|
||||
{ label: 'invalid txid rejected as like_validation' }
|
||||
)
|
||||
})
|
||||
|
||||
test('getSpendableSats conserves the sum of every spendable utxo value', async () => {
|
||||
await forAll(
|
||||
(i) => {
|
||||
const fields = ['value', 'satoshis', 'amount']
|
||||
const count = 1 + Math.floor(rng() * 8)
|
||||
const utxos = []
|
||||
for (let k = 0; k < count; k++) {
|
||||
utxos.push({ [fields[k % 3]]: Math.floor(rng() * 1000000) })
|
||||
}
|
||||
return utxos
|
||||
},
|
||||
(utxos) => {
|
||||
const wallet = fakeWallet({ utxos })
|
||||
const expected = utxos.reduce(
|
||||
(sum, u) => sum + (u.value ?? u.satoshis ?? u.amount ?? 0),
|
||||
0
|
||||
)
|
||||
return new MemoLike({ wallet }).getSpendableSats() === expected
|
||||
},
|
||||
{ label: 'spendable sum conservation' }
|
||||
)
|
||||
})
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
Unit tests for the hex conversion helper (src/services/hex.js).
|
||||
|
||||
Memo actions like replies and likes embed a post txid in the OP_RETURN
|
||||
payload as raw bytes, so the helper must decode a canonical 64-character hex
|
||||
string into exactly 32 bytes and reject anything else with a clear error.
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
const test = require('node:test')
|
||||
const assert = require('node:assert/strict')
|
||||
|
||||
const { hexToBytes } = require('../../src/services/hex')
|
||||
|
||||
test('hexToBytes decodes a 64-char hex string into 32 bytes', () => {
|
||||
const hex = 'a'.repeat(64)
|
||||
const bytes = hexToBytes(hex, 32)
|
||||
assert.ok(bytes instanceof Uint8Array)
|
||||
assert.equal(bytes.length, 32)
|
||||
assert.equal(Buffer.from(bytes).toString('hex'), hex)
|
||||
})
|
||||
|
||||
test('hexToBytes rejects a string of the wrong length', () => {
|
||||
assert.throws(
|
||||
() => hexToBytes('a'.repeat(10), 32),
|
||||
/64-character hex string/
|
||||
)
|
||||
})
|
||||
|
||||
test('hexToBytes rejects a 64-char string containing a non-hex character', () => {
|
||||
assert.throws(
|
||||
() => hexToBytes(`z${'a'.repeat(63)}`, 32),
|
||||
/valid hex string/
|
||||
)
|
||||
})
|
||||
|
||||
test('hexToBytes rejects non-string input', () => {
|
||||
assert.throws(
|
||||
() => hexToBytes(null, 32),
|
||||
/64-character hex string/
|
||||
)
|
||||
})
|
||||
|
||||
test('hexToBytes uses a custom label in error messages', () => {
|
||||
assert.throws(
|
||||
() => hexToBytes('nope', 32, 'Post txid'),
|
||||
/Post txid/
|
||||
)
|
||||
})
|
||||
@@ -31,6 +31,23 @@ function build () {
|
||||
return { wallet, feed, memoLike, page }
|
||||
}
|
||||
|
||||
// Build a page with the given wallet balance, submit a tip string, and assert
|
||||
// that the submit is rejected with the expected error code and message.
|
||||
async function assertTipRejected (utxos, tip, expectedCode, messageRe) {
|
||||
const wallet = fakeWallet({ cashAddress: MY_ADDRESS, utxos })
|
||||
const memoLike = new MemoLike({ wallet })
|
||||
const page = new LikeTipPage({ memoLike })
|
||||
page.open(POST_TXID, AUTHOR_ADDRESS)
|
||||
page.setTip(tip)
|
||||
|
||||
const result = await page.submit()
|
||||
|
||||
assert.equal(result.ok, false)
|
||||
assert.equal(result.error, expectedCode)
|
||||
assert.match(page.broadcastError, messageRe)
|
||||
assert.equal(wallet.broadcasts.length, 0)
|
||||
}
|
||||
|
||||
test('opening the modal sets the target post and author', () => {
|
||||
const { page } = build()
|
||||
|
||||
@@ -118,10 +135,11 @@ test('submitting a like on a post authored by the wallet works without a tip', a
|
||||
assert.equal(wallet.broadcasts[0].prefix, '6d04')
|
||||
})
|
||||
|
||||
test('submitting with a non-numeric tip is rejected', async () => {
|
||||
test('submitting with a non-numeric tip string is rejected', async () => {
|
||||
for (const tip of ['abc', '1.5']) {
|
||||
const { wallet, page } = build()
|
||||
page.open(POST_TXID, AUTHOR_ADDRESS)
|
||||
page.setTip('abc')
|
||||
page.setTip(tip)
|
||||
|
||||
const result = await page.submit()
|
||||
|
||||
@@ -130,18 +148,7 @@ test('submitting with a non-numeric tip is rejected', async () => {
|
||||
assert.equal(page.submitError, 'like_validation')
|
||||
assert.match(page.broadcastError, /valid number/i)
|
||||
assert.equal(wallet.broadcasts.length, 0)
|
||||
})
|
||||
|
||||
test('submitting with a decimal tip string is rejected', async () => {
|
||||
const { wallet, page } = build()
|
||||
page.open(POST_TXID, AUTHOR_ADDRESS)
|
||||
page.setTip('1.5')
|
||||
|
||||
const result = await page.submit()
|
||||
|
||||
assert.equal(result.ok, false)
|
||||
assert.equal(result.error, 'like_validation')
|
||||
assert.equal(wallet.broadcasts.length, 0)
|
||||
}
|
||||
})
|
||||
|
||||
test('submitting with a dust tip is rejected', async () => {
|
||||
@@ -157,33 +164,21 @@ test('submitting with a dust tip is rejected', async () => {
|
||||
})
|
||||
|
||||
test('submitting with a tip above the maximum is rejected', async () => {
|
||||
const wallet = fakeWallet({ cashAddress: MY_ADDRESS, utxos: [{ txid: 'u1', value: 150000000 }] })
|
||||
const memoLike = new MemoLike({ wallet })
|
||||
const page = new LikeTipPage({ memoLike })
|
||||
page.open(POST_TXID, AUTHOR_ADDRESS)
|
||||
page.setTip('100000001')
|
||||
|
||||
const result = await page.submit()
|
||||
|
||||
assert.equal(result.ok, false)
|
||||
assert.equal(result.error, 'like_maximum')
|
||||
assert.match(page.broadcastError, /maximum/i)
|
||||
assert.equal(wallet.broadcasts.length, 0)
|
||||
await assertTipRejected(
|
||||
[{ txid: 'u1', value: 150000000 }],
|
||||
'100000001',
|
||||
'like_maximum',
|
||||
/maximum/i
|
||||
)
|
||||
})
|
||||
|
||||
test('submitting with a tip above the spendable balance is rejected', async () => {
|
||||
const wallet = fakeWallet({ cashAddress: MY_ADDRESS, utxos: [{ txid: 'u1', value: 30000 }] })
|
||||
const memoLike = new MemoLike({ wallet })
|
||||
const page = new LikeTipPage({ memoLike })
|
||||
page.open(POST_TXID, AUTHOR_ADDRESS)
|
||||
page.setTip('35000')
|
||||
|
||||
const result = await page.submit()
|
||||
|
||||
assert.equal(result.ok, false)
|
||||
assert.equal(result.error, 'like_balance')
|
||||
assert.match(page.broadcastError, /spendable/i)
|
||||
assert.equal(wallet.broadcasts.length, 0)
|
||||
await assertTipRejected(
|
||||
[{ txid: 'u1', value: 30000 }],
|
||||
'35000',
|
||||
'like_balance',
|
||||
/spendable/i
|
||||
)
|
||||
})
|
||||
|
||||
test('tipping flag is true while a submit is in flight and false once it settles', async () => {
|
||||
@@ -228,3 +223,17 @@ test('a failed broadcast surfaces the real error', async () => {
|
||||
assert.equal(page.submitError, 'broadcast')
|
||||
assert.match(page.broadcastError, /Insufficient balance/)
|
||||
})
|
||||
|
||||
test('a submit failure with no error message surfaces the error name', async () => {
|
||||
const wallet = fakeWallet({ cashAddress: MY_ADDRESS })
|
||||
const memoLike = new MemoLike({ wallet })
|
||||
memoLike.like = async () => { throw new Error('') }
|
||||
const page = new LikeTipPage({ memoLike })
|
||||
page.open(POST_TXID, AUTHOR_ADDRESS)
|
||||
|
||||
const result = await page.submit()
|
||||
|
||||
assert.equal(result.ok, false)
|
||||
assert.equal(page.submitError, 'broadcast')
|
||||
assert.equal(page.broadcastError, 'Error')
|
||||
})
|
||||
|
||||
+38
-79
@@ -35,11 +35,22 @@ function fakeFeed (posts = []) {
|
||||
}
|
||||
}
|
||||
|
||||
// Decode a like payload back into a 64-character hex txid.
|
||||
// Decode a like payload back into the canonical 64-character hex txid.
|
||||
function decodeLikeTxid (raw) {
|
||||
return Buffer.from(raw).toString('hex')
|
||||
}
|
||||
|
||||
// Assert that a like with the given post txid and tip rejects with a specific
|
||||
// error code and performs no broadcast.
|
||||
async function assertLikeRejected (wallet, tip, expectedCode, postTxid = POST_TXID) {
|
||||
const memoLike = new MemoLike({ wallet })
|
||||
await assert.rejects(
|
||||
memoLike.like(postTxid, tip, AUTHOR_ADDRESS),
|
||||
(err) => err.code === expectedCode
|
||||
)
|
||||
assert.equal(wallet.broadcasts.length, 0)
|
||||
}
|
||||
|
||||
test('MEMO_LIKE_PREFIX is the Memo like action 0x6d04', () => {
|
||||
assert.equal(MemoLike.MEMO_LIKE_PREFIX, '6d04')
|
||||
})
|
||||
@@ -105,71 +116,29 @@ test('liking without a wallet reports a missing-wallet error', async () => {
|
||||
})
|
||||
|
||||
test('liking with an invalid post txid reports a clear validation error', async () => {
|
||||
const wallet = fakeWallet()
|
||||
const memoLike = new MemoLike({ wallet })
|
||||
|
||||
await assert.rejects(
|
||||
memoLike.like('not-a-txid'),
|
||||
(err) => err.code === 'like_validation'
|
||||
)
|
||||
assert.equal(wallet.broadcasts.length, 0)
|
||||
await assertLikeRejected(fakeWallet(), 0, 'like_validation', 'not-a-txid')
|
||||
})
|
||||
|
||||
test('liking with a wrong-length but valid-hex post txid is rejected', async () => {
|
||||
const wallet = fakeWallet()
|
||||
const memoLike = new MemoLike({ wallet })
|
||||
|
||||
await assert.rejects(
|
||||
memoLike.like('a'.repeat(10)),
|
||||
(err) => err.code === 'like_validation'
|
||||
)
|
||||
assert.equal(wallet.broadcasts.length, 0)
|
||||
await assertLikeRejected(fakeWallet(), 0, 'like_validation', 'a'.repeat(10))
|
||||
})
|
||||
|
||||
test('a non-integer tip like "1.5" is rejected with a validation error', async () => {
|
||||
const wallet = fakeWallet()
|
||||
const memoLike = new MemoLike({ wallet })
|
||||
|
||||
await assert.rejects(
|
||||
memoLike.like(POST_TXID, 1.5, AUTHOR_ADDRESS),
|
||||
(err) => err.code === 'like_validation'
|
||||
)
|
||||
assert.equal(wallet.broadcasts.length, 0)
|
||||
await assertLikeRejected(fakeWallet(), 1.5, 'like_validation')
|
||||
})
|
||||
|
||||
test('a non-numeric tip like "abc" is rejected with a validation error', async () => {
|
||||
const wallet = fakeWallet()
|
||||
const memoLike = new MemoLike({ wallet })
|
||||
|
||||
await assert.rejects(
|
||||
memoLike.like(POST_TXID, NaN, AUTHOR_ADDRESS),
|
||||
(err) => err.code === 'like_validation'
|
||||
)
|
||||
assert.equal(wallet.broadcasts.length, 0)
|
||||
await assertLikeRejected(fakeWallet(), NaN, 'like_validation')
|
||||
})
|
||||
|
||||
test('a negative tip is rejected with a validation error', async () => {
|
||||
const wallet = fakeWallet()
|
||||
const memoLike = new MemoLike({ wallet })
|
||||
|
||||
await assert.rejects(
|
||||
memoLike.like(POST_TXID, -1, AUTHOR_ADDRESS),
|
||||
(err) => err.code === 'like_validation'
|
||||
)
|
||||
assert.equal(wallet.broadcasts.length, 0)
|
||||
await assertLikeRejected(fakeWallet(), -1, 'like_validation')
|
||||
})
|
||||
|
||||
test('a tip below the dust limit is rejected with a dust error', async () => {
|
||||
const wallet = fakeWallet({ utxos: [{ txid: 'u1', value: 100000 }] })
|
||||
const memoLike = new MemoLike({ wallet })
|
||||
|
||||
for (const tip of [1, 2999]) {
|
||||
await assert.rejects(
|
||||
memoLike.like(POST_TXID, tip, AUTHOR_ADDRESS),
|
||||
(err) => err.code === 'like_dust'
|
||||
)
|
||||
}
|
||||
assert.equal(wallet.broadcasts.length, 0)
|
||||
await assertLikeRejected(wallet, 1, 'like_dust')
|
||||
await assertLikeRejected(wallet, 2999, 'like_dust')
|
||||
})
|
||||
|
||||
test('a tip at the dust limit is accepted', async () => {
|
||||
@@ -183,47 +152,31 @@ test('a tip at the dust limit is accepted', async () => {
|
||||
})
|
||||
|
||||
test('a tip above the hard maximum is rejected with a maximum error', async () => {
|
||||
const wallet = fakeWallet({ utxos: [{ txid: 'u1', value: 150000000 }] })
|
||||
const memoLike = new MemoLike({ wallet })
|
||||
|
||||
await assert.rejects(
|
||||
memoLike.like(POST_TXID, 100000001, AUTHOR_ADDRESS),
|
||||
(err) => err.code === 'like_maximum'
|
||||
await assertLikeRejected(
|
||||
fakeWallet({ utxos: [{ txid: 'u1', value: 150000000 }] }),
|
||||
100000001,
|
||||
'like_maximum'
|
||||
)
|
||||
assert.equal(wallet.broadcasts.length, 0)
|
||||
})
|
||||
|
||||
test('a tip above the spendable balance is rejected with a balance error', async () => {
|
||||
const wallet = fakeWallet({ utxos: [{ txid: 'u1', value: 30000 }] })
|
||||
const memoLike = new MemoLike({ wallet })
|
||||
|
||||
await assert.rejects(
|
||||
memoLike.like(POST_TXID, 35000, AUTHOR_ADDRESS),
|
||||
(err) => err.code === 'like_balance'
|
||||
await assertLikeRejected(
|
||||
fakeWallet({ utxos: [{ txid: 'u1', value: 30000 }] }),
|
||||
35000,
|
||||
'like_balance'
|
||||
)
|
||||
assert.equal(wallet.broadcasts.length, 0)
|
||||
})
|
||||
|
||||
test('a wallet with zero spendable balance cannot like', async () => {
|
||||
const wallet = fakeWallet({ utxos: [] })
|
||||
const memoLike = new MemoLike({ wallet })
|
||||
|
||||
await assert.rejects(
|
||||
memoLike.like(POST_TXID, 0, AUTHOR_ADDRESS),
|
||||
(err) => err.code === 'like_empty_balance'
|
||||
)
|
||||
assert.equal(wallet.broadcasts.length, 0)
|
||||
await assertLikeRejected(fakeWallet({ utxos: [] }), 0, 'like_empty_balance')
|
||||
})
|
||||
|
||||
test('a wallet with balance below the dust limit cannot like', async () => {
|
||||
const wallet = fakeWallet({ utxos: [{ txid: 'u1', value: 2999 }] })
|
||||
const memoLike = new MemoLike({ wallet })
|
||||
|
||||
await assert.rejects(
|
||||
memoLike.like(POST_TXID, 0, AUTHOR_ADDRESS),
|
||||
(err) => err.code === 'like_empty_balance'
|
||||
await assertLikeRejected(
|
||||
fakeWallet({ utxos: [{ txid: 'u1', value: 2999 }] }),
|
||||
0,
|
||||
'like_empty_balance'
|
||||
)
|
||||
assert.equal(wallet.broadcasts.length, 0)
|
||||
})
|
||||
|
||||
test('a pure like can be made on a post authored by the wallet address', async () => {
|
||||
@@ -259,3 +212,9 @@ test('getSpendableSats tolerates common utxo value field names', () => {
|
||||
assert.equal(new MemoLike({ wallet: walletSatoshis }).getSpendableSats(), 2000)
|
||||
assert.equal(new MemoLike({ wallet: walletAmount }).getSpendableSats(), 3000)
|
||||
})
|
||||
|
||||
test('getSpendableSats returns 0 without a wallet or spendable values', () => {
|
||||
assert.equal(new MemoLike({}).getSpendableSats(), 0)
|
||||
assert.equal(new MemoLike({ wallet: { utxos: undefined } }).getSpendableSats(), 0)
|
||||
assert.equal(new MemoLike({ wallet: { utxos: [{ txid: 'u1' }] } }).getSpendableSats(), 0)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user