mirror of
https://github.com/Permissionless-Software-Foundation/psf-memo.git
synced 2026-09-21 16:52:01 -07:00
Reply, topic message, add-poll-option, poll-vote, and create-poll now broadcast each protocol field as its own OP_RETURN script push instead of combining them into one push. The indexer requires three pushes for reply, topic message, and poll children, and accepts four for create-poll; the combined form was rejected or misparsed. hex.js builds the txid and text as separate pushes, and a new memo-multipush adapter teaches minimal-slp-wallet's single-push sendOpReturn to expand an array of fields into separate pushes. Acceptance step handlers assert the individual pushes, and unit/property tests cover the new shape. By coder.
158 lines
4.6 KiB
JavaScript
158 lines
4.6 KiB
JavaScript
/*
|
|
Unit tests for the Memo create-poll behavior.
|
|
|
|
A create-poll transaction carries the Memo create-poll protocol prefix
|
|
(0x6d10) followed by a poll_type byte, an option_count byte, and the
|
|
question text. The question is limited to 209 bytes.
|
|
*/
|
|
|
|
'use strict'
|
|
|
|
const test = require('node:test')
|
|
const assert = require('node:assert/strict')
|
|
const MemoPollCreate = require('../../src/services/memo-poll-create')
|
|
|
|
const MY_ADDRESS = 'bitcoincash:qqlrzp23w08434twmvr4fxw672whkjy0py26r63g3d'
|
|
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
|
|
test('create broadcasts the poll type, option count, and question as separate pushes', async () => {
|
|
const wallet = makeWallet()
|
|
const memoPollCreate = new MemoPollCreate({ wallet })
|
|
|
|
await memoPollCreate.create('which is better?', 2)
|
|
|
|
assert.equal(wallet.broadcasts.length, 1)
|
|
assert.equal(wallet.broadcasts[0].prefix, MemoPollCreate.MEMO_CREATE_POLL_PREFIX)
|
|
const pushes = wallet.broadcasts[0].msg
|
|
assert.ok(Array.isArray(pushes), 'expected separate pushes')
|
|
assert.equal(pushes.length, 3)
|
|
assert.equal(Buffer.from(pushes[0])[0], 1)
|
|
assert.equal(Buffer.from(pushes[1])[0], 2)
|
|
assert.equal(Buffer.from(pushes[2]).toString('utf8'), 'which is better?')
|
|
})
|
|
|
|
test('create accepts an option count of one', async () => {
|
|
const wallet = makeWallet()
|
|
const memoPollCreate = new MemoPollCreate({ wallet })
|
|
|
|
await memoPollCreate.create('one option?', 1)
|
|
|
|
assert.equal(wallet.broadcasts.length, 1)
|
|
})
|
|
|
|
test('create rejects an option count of zero', async () => {
|
|
const wallet = makeWallet()
|
|
const memoPollCreate = new MemoPollCreate({ wallet })
|
|
|
|
await assert.rejects(
|
|
() => memoPollCreate.create('zero options?', 0),
|
|
{ code: 'poll_create_validation', message: /positive number/ }
|
|
)
|
|
assert.equal(wallet.broadcasts.length, 0)
|
|
})
|
|
|
|
test('create rejects a non-numeric option count', async () => {
|
|
const wallet = makeWallet()
|
|
const memoPollCreate = new MemoPollCreate({ wallet })
|
|
|
|
await assert.rejects(
|
|
() => memoPollCreate.create('weird count?', 'abc'),
|
|
{ code: 'poll_create_validation', message: /positive number/ }
|
|
)
|
|
assert.equal(wallet.broadcasts.length, 0)
|
|
})
|
|
|
|
test('create reflects the new poll on the injected poll store', async () => {
|
|
const wallet = makeWallet()
|
|
const added = []
|
|
const polls = {
|
|
addPoll (poll) {
|
|
added.push(poll)
|
|
}
|
|
}
|
|
const memoPollCreate = new MemoPollCreate({ wallet, polls })
|
|
|
|
await memoPollCreate.create('what next?', 3)
|
|
|
|
assert.equal(added.length, 1)
|
|
assert.equal(added[0].question, 'what next?')
|
|
assert.equal(added[0].optionCount, 3)
|
|
assert.equal(added[0].address, wallet.walletInfo.cashAddress)
|
|
})
|
|
|
|
test('create rejects an empty question', async () => {
|
|
const wallet = makeWallet()
|
|
const memoPollCreate = new MemoPollCreate({ wallet })
|
|
|
|
await assert.rejects(
|
|
() => memoPollCreate.create('', 2),
|
|
{ code: 'poll_create_validation', message: /must not be empty/ }
|
|
)
|
|
assert.equal(wallet.broadcasts.length, 0)
|
|
})
|
|
|
|
test('create rejects a question that exceeds the byte limit', async () => {
|
|
const wallet = makeWallet()
|
|
const memoPollCreate = new MemoPollCreate({ wallet })
|
|
const question = 'a'.repeat(210)
|
|
|
|
await assert.rejects(
|
|
() => memoPollCreate.create(question, 2),
|
|
{ code: 'poll_create_length', message: /too long/ }
|
|
)
|
|
assert.equal(wallet.broadcasts.length, 0)
|
|
})
|
|
|
|
test('create accepts a question at the byte limit', async () => {
|
|
const wallet = makeWallet()
|
|
const memoPollCreate = new MemoPollCreate({ wallet })
|
|
const question = 'a'.repeat(209)
|
|
|
|
await memoPollCreate.create(question, 2)
|
|
|
|
assert.equal(wallet.broadcasts.length, 1)
|
|
})
|
|
|
|
test('create counts UTF-8 bytes for the limit', async () => {
|
|
const wallet = makeWallet()
|
|
const memoPollCreate = new MemoPollCreate({ wallet })
|
|
|
|
await assert.rejects(
|
|
() => memoPollCreate.create('é'.repeat(105), 2),
|
|
{ code: 'poll_create_length' }
|
|
)
|
|
})
|
|
|
|
test('create requires a wallet', async () => {
|
|
const memoPollCreate = new MemoPollCreate({})
|
|
|
|
await assert.rejects(
|
|
() => memoPollCreate.create('hello', 2),
|
|
/requires a wallet/
|
|
)
|
|
})
|
|
|
|
test('create surfaces a broadcast failure', async () => {
|
|
const wallet = makeWallet()
|
|
wallet.sendOpReturn = async () => { throw new Error('broadcast failed') }
|
|
const memoPollCreate = new MemoPollCreate({ wallet })
|
|
|
|
await assert.rejects(
|
|
() => memoPollCreate.create('hello', 2),
|
|
/broadcast failed/
|
|
)
|
|
})
|