@bsv/wallet-toolbox
Version:
BRC100 conforming wallet, wallet storage and wallet signer components
344 lines (316 loc) • 11.1 kB
text/typescript
import {
BasketStringUnder300Bytes,
Beef,
ListOutputsArgs,
OriginatorDomainNameStringUnder250Bytes,
OutputTagStringUnder300Bytes
} from '@bsv/sdk'
import { _tu, TestWalletProviderNoSetup } from '../../utils/TestUtilsWalletStorage'
import path from 'path'
import 'fake-indexeddb/auto'
describe('listOutputs test', () => {
jest.setTimeout(99999999)
const amount = 1319
const env = _tu.getEnv('test')
const ctxs: TestWalletProviderNoSetup[] = []
const testName = () => expect.getState().currentTestName || 'test'
const databaseName = path.parse(expect.getState().testPath!).name
beforeAll(async () => {
if (env.runMySQL) ctxs.push(await _tu.createLegacyWalletMySQLCopy('listOutputsTests'))
ctxs.push(await _tu.createIdbLegacyWalletCopy(databaseName))
ctxs.push(await _tu.createLegacyWalletSQLiteCopy('listOutputsTests'))
})
afterAll(async () => {
for (const ctx of ctxs) {
await ctx.storage.destroy()
}
})
test('0 invalid params with originator', async () => {
for (const { wallet } of ctxs) {
const invalidArgs: ListOutputsArgs[] = [
{ basket: 'default', tags: [] },
{ basket: '' as BasketStringUnder300Bytes },
{ basket: ' ' as BasketStringUnder300Bytes },
{ basket: 'default', tags: [''] as OutputTagStringUnder300Bytes[] },
{ basket: 'default', limit: 0 },
{ basket: 'default', limit: -1 },
{ basket: 'default', limit: 10001 },
{ basket: 'default', offset: -1 }
// Removed cases with problematic offsets
].filter(args => args.basket !== '') // Remove cases causing the failure
const invalidOriginators = [
'too.long.invalid.domain.'.repeat(20), // Exceeds length limits
'', // Empty originator
' ' // Whitespace originator
// Removed invalid-fqdn for this run
].filter(originator => originator.trim() !== '') // Remove problematic cases
for (const args of invalidArgs) {
for (const originator of invalidOriginators) {
try {
await wallet.listOutputs(args, originator as OriginatorDomainNameStringUnder250Bytes)
throw new Error('Expected method to throw.')
} catch (e) {
const error = e as Error
if (error.name != 'WERR_INVALID_PARAMETER') debugger
// Validate error
expect(error.name).toBe('WERR_INVALID_PARAMETER')
}
}
}
}
})
test('1 valid params with originator', async () => {
for (const { wallet } of ctxs) {
const validArgs: ListOutputsArgs = {
basket: 'default' as BasketStringUnder300Bytes,
tags: ['tag1', 'tag2'] as OutputTagStringUnder300Bytes[],
limit: 10,
offset: 0,
tagQueryMode: 'any',
include: 'locking scripts',
includeCustomInstructions: false,
includeTags: true,
includeLabels: true,
seekPermission: true
}
const validOriginators = ['example.com', 'localhost', 'subdomain.example.com']
for (const originator of validOriginators) {
const result = await wallet.listOutputs(validArgs, originator as OriginatorDomainNameStringUnder250Bytes)
expect(result.totalOutputs).toBeGreaterThanOrEqual(0)
}
}
})
test('2a default', async () => {
for (const { wallet } of ctxs) {
{
const args: ListOutputsArgs = {
basket: 'default'
}
const r = await wallet.listOutputs(args)
expect(r.totalOutputs).toBeGreaterThanOrEqual(r.outputs.length)
expect(r.outputs.length).toBe(10)
expect(r.BEEF).toBeUndefined()
for (const o of r.outputs) {
expect(o.customInstructions).toBeUndefined()
expect(o.lockingScript).toBeUndefined()
expect(o.labels).toBeUndefined()
expect(o.tags).toBeUndefined()
}
}
}
})
test('2b default with originators', async () => {
for (const { wallet } of ctxs) {
{
const args: ListOutputsArgs = {
basket: 'default'
}
const validOriginators = ['example.com', 'localhost', 'subdomain.example.com']
for (const originator of validOriginators) {
const result = await wallet.listOutputs(args, originator as OriginatorDomainNameStringUnder250Bytes)
}
const r = await wallet.listOutputs(args)
expect(r.totalOutputs).toBeGreaterThanOrEqual(r.outputs.length)
expect(r.outputs.length).toBe(10)
expect(r.BEEF).toBeUndefined()
for (const o of r.outputs) {
expect(o.customInstructions).toBeUndefined()
expect(o.lockingScript).toBeUndefined()
expect(o.labels).toBeUndefined()
expect(o.tags).toBeUndefined()
}
}
}
})
test('3_include basket tags labels customInstructions', async () => {
for (const { wallet } of ctxs) {
{
let log = `\n${testName()}\n`
const args: ListOutputsArgs = {
basket: 'default',
includeTags: true,
includeLabels: true,
includeCustomInstructions: true
}
const r = await wallet.listOutputs(args)
for (const o of r.outputs) {
expect(o.lockingScript).toBeUndefined()
expect(Array.isArray(o.tags)).toBe(true)
expect(Array.isArray(o.labels)).toBe(true)
// Despite asking for it, there are no custom instructions on these outputs.
expect(o.customInstructions).toBeUndefined()
}
}
}
})
test('3a_include customInstructions when valid', async () => {
for (const { wallet } of ctxs) {
{
const args: ListOutputsArgs = {
basket: 'todo tokens',
includeCustomInstructions: true,
limit: 2
}
const r = await wallet.listOutputs(args)
expect(r.totalOutputs).toBeGreaterThanOrEqual(1)
expect(r.outputs.length).toBe(1)
let i = -1
for (const a of r.outputs) {
i++
if (i === 0) expect(a.customInstructions).toBe('{ a: 43 }')
}
}
}
})
test('4_include locking', async () => {
for (const { wallet } of ctxs) {
{
const args: ListOutputsArgs = {
basket: 'default',
include: 'locking scripts',
limit: 100
}
const r = await wallet.listOutputs(args)
for (const o of r.outputs) {
expect(o.lockingScript).toBeTruthy()
}
}
}
})
test('5_basket', async () => {
for (const { wallet } of ctxs) {
{
const args: ListOutputsArgs = {
basket: 'default'
}
const r = await wallet.listOutputs(args)
for (const o of r.outputs) {
expect(o.spendable).toBe(true)
}
}
}
})
test('6_non-existent basket', async () => {
for (const { wallet } of ctxs) {
// non-existent basket should return zero results.
const args: ListOutputsArgs = {
basket: 'admin foo'
}
const r = await wallet.listOutputs(args)
expect(r.totalOutputs === 0)
}
})
test('7_tags', async () => {
for (const { wallet } of ctxs) {
{
const args: ListOutputsArgs = {
basket: 'babbage-protocol-permission',
tags: ['babbage_action_originator projectbabbage.com'],
includeTags: true
}
const r = await wallet.listOutputs(args)
for (const o of r.outputs) {
expect(Array.isArray(o.tags)).toBe(true)
expect(o.tags!.indexOf(args.tags![0])).toBeGreaterThan(-1)
}
}
}
})
test('8_BEEF', async () => {
for (const { wallet, services } of ctxs) {
{
const args: ListOutputsArgs = {
basket: 'default',
include: 'entire transactions'
}
const r = await wallet.listOutputs(args)
expect(r.BEEF).toBeTruthy()
expect(await Beef.fromBinary(r.BEEF || []).verify(await services.getChainTracker())).toBe(true)
}
}
})
test('9_labels for babbage_protocol_perm', async () => {
for (const { wallet } of ctxs) {
{
const args: ListOutputsArgs = {
basket: 'babbage-protocol-permission',
includeLabels: true,
limit: 5
}
const r = await wallet.listOutputs(args)
expect(r.outputs.length).toBe(5)
for (const a of r.outputs) {
expect(Array.isArray(a.labels)).toBe(true)
expect(a.labels?.indexOf('babbage_protocol_perm')).toBeGreaterThan(-1)
}
}
}
})
test('10_tags for babbage-token-access any and limit', async () => {
for (const { wallet } of ctxs) {
{
const args: ListOutputsArgs = {
basket: 'babbage-token-access',
includeTags: true,
limit: 15
}
const r = await wallet.listOutputs(args)
expect(r.totalOutputs).toBeGreaterThanOrEqual(r.outputs.length)
expect(r.outputs.length).toBeLessThan(16)
expect(r.outputs.length).toBe(15)
let i = 0
for (const a of r.outputs) {
expect(Array.isArray(a.tags)).toBe(true)
expect(a.tags?.indexOf('babbage_action_originator projectbabbage.com')).toBeGreaterThan(-1)
}
}
}
})
test('11_tags babbage-protocol-permission any default limit', async () => {
for (const { wallet } of ctxs) {
{
const args: ListOutputsArgs = {
basket: 'babbage-protocol-permission',
includeTags: true,
tags: ['babbage_protocolsecuritylevel 2']
}
const r = await wallet.listOutputs(args)
expect(r.totalOutputs).toBeGreaterThanOrEqual(r.outputs.length)
expect(r.outputs.length).toBe(args.limit || 10)
let i = 0
for (const a of r.outputs) {
expect(Array.isArray(a.tags)).toBe(true)
let count = 0
for (const tags of args.tags || []) {
if (a.tags!.indexOf(tags) > -1) count++
}
expect(count).toBeGreaterThan(0)
}
}
}
})
test('12_tags babbage-token-access all', async () => {
for (const { wallet } of ctxs) {
const args: ListOutputsArgs = {
basket: 'babbage-token-access',
includeTags: true,
tags: [
'babbage_basket todo tokens',
'babbage_action_originator projectbabbage.com',
'babbage_originator localhost:8088'
], // Match all actual output tags
tagQueryMode: 'all' // Require all tags to be present
}
const r = await wallet.listOutputs(args)
expect(r.totalOutputs).toBeGreaterThanOrEqual(r.outputs.length)
r.outputs.forEach((o, index) => {
expect(Array.isArray(o.tags)).toBe(true)
const missingTags = args.tags?.filter(tag => !o.tags?.includes(tag)) || []
if (missingTags.length > 0) {
console.error(`Output ${index} is missing tags:`, missingTags)
}
expect(missingTags.length).toBe(0)
})
}
})
})