2026-08-28 14:50:05 -07:00
|
|
|
/*
|
|
|
|
|
Unit tests for the Memo poll-vote behavior.
|
|
|
|
|
|
|
|
|
|
A poll-vote transaction carries the Memo poll-vote protocol prefix (0x6d14)
|
|
|
|
|
followed by the poll's 32-byte txid and a comment. The comment is limited
|
|
|
|
|
to 184 bytes.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
'use strict'
|
|
|
|
|
|
|
|
|
|
const test = require('node:test')
|
|
|
|
|
const assert = require('node:assert/strict')
|
|
|
|
|
const MemoPollVote = require('../../src/services/memo-poll-vote')
|
|
|
|
|
|
|
|
|
|
const MY_ADDRESS = 'bitcoincash:qqlrzp23w08434twmvr4fxw672whkjy0py26r63g3d'
|
2026-09-16 14:40:13 -07:00
|
|
|
// A non-palindromic txid so a missing byte reversal is observable.
|
|
|
|
|
const POLL_TXID = '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'
|
2026-08-28 14:50:05 -07:00
|
|
|
|
|
|
|
|
function makeWallet (address = MY_ADDRESS) {
|
|
|
|
|
return {
|
|
|
|
|
walletInfo: { cashAddress: address },
|
|
|
|
|
broadcasts: [],
|
|
|
|
|
async getUtxos () {
|
|
|
|
|
return []
|
|
|
|
|
},
|
|
|
|
|
async sendOpReturn (msg, prefix) {
|
|
|
|
|
this.broadcasts.push({ msg, prefix })
|
|
|
|
|
return 'aa'.repeat(32)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-16 20:37:59 -07:00
|
|
|
test('vote broadcasts the poll txid and comment as separate pushes', async () => {
|
2026-08-28 14:50:05 -07:00
|
|
|
const wallet = makeWallet()
|
|
|
|
|
const memoPollVote = new MemoPollVote({ wallet, pollTxid: POLL_TXID })
|
|
|
|
|
|
|
|
|
|
await memoPollVote.vote('yes')
|
|
|
|
|
|
|
|
|
|
assert.equal(wallet.broadcasts.length, 1)
|
|
|
|
|
assert.equal(wallet.broadcasts[0].prefix, MemoPollVote.MEMO_POLL_VOTE_PREFIX)
|
2026-09-16 20:37:59 -07:00
|
|
|
const pushes = wallet.broadcasts[0].msg
|
|
|
|
|
assert.ok(Array.isArray(pushes), 'expected separate pushes')
|
|
|
|
|
assert.equal(pushes.length, 2)
|
|
|
|
|
assert.equal(Buffer.from(pushes[0]).reverse().toString('hex'), POLL_TXID)
|
|
|
|
|
assert.equal(Buffer.from(pushes[1]).toString('utf8'), 'yes')
|
2026-08-28 14:50:05 -07:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('vote reflects the new vote on the injected poll store', async () => {
|
|
|
|
|
const wallet = makeWallet()
|
|
|
|
|
const added = []
|
|
|
|
|
const polls = {
|
|
|
|
|
addVote (vote) {
|
|
|
|
|
added.push(vote)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const memoPollVote = new MemoPollVote({ wallet, pollTxid: POLL_TXID, polls })
|
|
|
|
|
|
|
|
|
|
await memoPollVote.vote('I choose this one')
|
|
|
|
|
|
|
|
|
|
assert.equal(added.length, 1)
|
|
|
|
|
assert.equal(added[0].comment, 'I choose this one')
|
|
|
|
|
assert.equal(added[0].pollTxid, POLL_TXID)
|
|
|
|
|
assert.equal(added[0].address, wallet.walletInfo.cashAddress)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('vote rejects an empty comment', async () => {
|
|
|
|
|
const wallet = makeWallet()
|
|
|
|
|
const memoPollVote = new MemoPollVote({ wallet, pollTxid: POLL_TXID })
|
|
|
|
|
|
|
|
|
|
await assert.rejects(
|
|
|
|
|
() => memoPollVote.vote(''),
|
|
|
|
|
{ code: 'poll_vote_validation', message: /must not be empty/ }
|
|
|
|
|
)
|
|
|
|
|
assert.equal(wallet.broadcasts.length, 0)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('vote rejects a comment that exceeds the byte limit', async () => {
|
|
|
|
|
const wallet = makeWallet()
|
|
|
|
|
const memoPollVote = new MemoPollVote({ wallet, pollTxid: POLL_TXID })
|
|
|
|
|
const comment = 'a'.repeat(185)
|
|
|
|
|
|
|
|
|
|
await assert.rejects(
|
|
|
|
|
() => memoPollVote.vote(comment),
|
|
|
|
|
{ code: 'poll_vote_length', message: /too long/ }
|
|
|
|
|
)
|
|
|
|
|
assert.equal(wallet.broadcasts.length, 0)
|
|
|
|
|
})
|
|
|
|
|
|
2026-08-28 17:51:03 -07:00
|
|
|
test('vote accepts a comment at the byte limit', async () => {
|
|
|
|
|
const wallet = makeWallet()
|
|
|
|
|
const memoPollVote = new MemoPollVote({ wallet, pollTxid: POLL_TXID })
|
|
|
|
|
const comment = 'a'.repeat(MemoPollVote.MAX_COMMENT_BYTES)
|
|
|
|
|
|
|
|
|
|
await memoPollVote.vote(comment)
|
|
|
|
|
|
|
|
|
|
assert.equal(wallet.broadcasts.length, 1)
|
|
|
|
|
})
|
|
|
|
|
|
2026-08-28 14:50:05 -07:00
|
|
|
test('vote requires a wallet', async () => {
|
|
|
|
|
const memoPollVote = new MemoPollVote({ pollTxid: POLL_TXID })
|
|
|
|
|
|
|
|
|
|
await assert.rejects(
|
|
|
|
|
() => memoPollVote.vote('yes'),
|
|
|
|
|
/requires a wallet/
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('vote requires a poll txid', async () => {
|
|
|
|
|
const wallet = makeWallet()
|
|
|
|
|
const memoPollVote = new MemoPollVote({ wallet })
|
|
|
|
|
|
|
|
|
|
await assert.rejects(
|
|
|
|
|
() => memoPollVote.vote('yes'),
|
|
|
|
|
{ code: 'poll_vote_validation', message: /Poll txid is required/ }
|
|
|
|
|
)
|
|
|
|
|
})
|