test: improve coverage for events and runes

This commit is contained in:
Ricardo Arturo Cabral Mejia
2022-08-27 03:36:37 +00:00
parent 262e00ad53
commit 376d0eb599
5 changed files with 316 additions and 53 deletions

View File

@@ -109,24 +109,6 @@ export const isDelegatedEventValid = async (event: Event): Promise<boolean> => {
return false
}
const serializedDelegationTag = `nostr:${delegation[0]}:${event.pubkey}:${delegation[2]}`
const token = await secp256k1.utils.sha256(Buffer.from(serializedDelegationTag))
// Token generation to be decided:
// const serializedDelegationTag = [
// delegation[0], // 'delegation'
// delegation[1], // <delegator>
// event.pubkey, // <delegatee>
// delegation[2], // <rules>
// ]
// const token = await secp256k1.utils.sha256(Buffer.from(JSON.stringify(serializedDelegationTag)))
// Validate delegation signature
const verification = await secp256k1.schnorr.verify(delegation[3], token, delegation[1])
if (!verification) {
return false
}
// Validate rune
const runifiedEvent = (converge(
@@ -144,14 +126,32 @@ export const isDelegatedEventValid = async (event: Event): Promise<boolean> => {
],
) as any)(event)
let result: boolean
try {
const [result] = Rune.from(delegation[2]).test(runifiedEvent)
return result
[result] = Rune.from(delegation[2]).test(runifiedEvent)
} catch (error) {
console.error('Invalid rune')
result = false
}
if (!result) {
return false
}
const serializedDelegationTag = `nostr:${delegation[0]}:${event.pubkey}:${delegation[2]}`
const token = await secp256k1.utils.sha256(Buffer.from(serializedDelegationTag))
// Token generation to be decided:
// const serializedDelegationTag = [
// delegation[0], // 'delegation'
// delegation[1], // <delegator>
// event.pubkey, // <delegatee>
// delegation[2], // <rules>
// ]
// const token = await secp256k1.utils.sha256(Buffer.from(JSON.stringify(serializedDelegationTag)))
// Validate delegation signature
return secp256k1.schnorr.verify(delegation[3], token, delegation[1])
}
export const isEventIdValid = async (event: Event): Promise<boolean> => {

View File

@@ -16,7 +16,7 @@ export class Alternative {
}
if (!new Set(['!', '=', '/', '^', '$', '~', '<', '>', '}', '{', '#']).has(this.cond)) {
throw new Error('Cond not valid')
throw new Error('Cond is not valid')
}
}
@@ -69,10 +69,8 @@ export class Alternative {
}
case '{':
return why(val < this.value, this.field, `is the same or ordered after ${this.value}`)
case '{':
case '}':
return why(val > this.value, this.field, `is the same or ordered before ${this.value}`)
default:
throw new Error('Invalid condition')
}
}
@@ -80,14 +78,6 @@ export class Alternative {
return `${this.field}${this.cond}${this.value.replace(/[\\|&]/g, '\\$&')}`
}
public valueOf(): string {
return this.encode()
}
public toString() {
return this.encode()
}
public static decode(encodedStr: string): [Alternative, string] {
let cond = undefined
let endOff = 0
@@ -133,7 +123,6 @@ export class Alternative {
return new Alternative(field, cond, value)
}
}
export class Restriction {
@@ -162,14 +151,6 @@ export class Restriction {
return this.alternatives.map((alternative) => alternative.encode()).join('|')
}
public valueOf(): string {
return this.encode()
}
public toString() {
return this.encode()
}
public static decode(encodedStr: string): [Restriction, string] {
let encStr = encodedStr
let alternative: Alternative
@@ -219,14 +200,6 @@ export class Rune {
return this.restrictions.map((restriction) => restriction.encode()).join('&')
}
public valueOf() {
return this.encode()
}
public toString() {
return this.encode()
}
public static from(encodedStr: string): Rune {
const restrictions: Restriction[] = []
let restriction: Restriction

View File

@@ -4,6 +4,7 @@ import { CanonicalEvent, Event } from '../../../src/@types/event'
import {
isDelegatedEvent,
isDelegatedEventValid,
isDeleteEvent,
isEphemeralEvent,
isEventIdValid,
isEventMatchingFilter,
@@ -430,5 +431,58 @@ describe('NIP-26', () => {
it('resolves with true if delegated event is valid', async () => {
expect(await isDelegatedEventValid(event)).to.be.true
})
it('resolves with false if no delegation tag is found', async () => {
event.tags = []
expect(await isDelegatedEventValid(event)).to.be.false
})
it('resolves with false if delegation signature is invalid', async () => {
event.tags[0][3] = 'f'
expect(await isDelegatedEventValid(event)).to.be.false
})
it('resolves with false if delegation rule is not a valid rune', async () => {
event.tags[0][2] = '@'
expect(await isDelegatedEventValid(event)).to.be.false
})
it('resolves with false if no delegation rule does not match', async () => {
event.tags[0][2] = 'a=1'
expect(await isDelegatedEventValid(event)).to.be.false
})
})
describe('isEventMatchingFilter', () => {
it('returns true if author is delegator', () => {
expect(
isEventMatchingFilter({ authors: ['86f0689bd48dcd19c67a19d994f938ee34f251d8c39976290955ff585f2db42e'] })(event)
).to.be.true
})
it('returns false if author is not delegator', () => {
expect(
isEventMatchingFilter({ authors: ['e8b487c079b0f67c695ae6c4c2552a47f38adfa2533cc5926bd2c102942fdcb7'] })(event)
).to.be.false
})
})
})
describe('NIP-09', () => {
describe('isDeleteEvent', () => {
it('returns true if event is kind 5', () => {
const event: Event = {
kind: 5,
} as any
expect(isDeleteEvent(event)).to.be.true
})
it('returns false if event is not kind 5', () => {
const event: Event = {
kind: 5 * 100000,
} as any
expect(isDeleteEvent(event)).to.be.false
})
})
})

View File

@@ -0,0 +1,219 @@
import { expect } from 'chai'
import sinon from 'sinon'
import { Alternative } from '../../../src/utils/runes'
describe.only('Alternative', () => {
describe('constructor', () => {
it('throw error if field has punctuations', () => {
expect(() => new Alternative('%', '=', 'value')).to.throw(Error, 'Field is not valid')
})
it('throw error if cond is not valid', () => {
expect(() => new Alternative('field', '@', 'value')).to.throw(Error, 'Cond is not valid')
})
it('returns an Alternative if given valid fields', () => {
expect(new Alternative('field', '=', 'value')).to.be.an.instanceOf(Alternative)
})
})
describe('test', () => {
it('returns undefined always if rule is #', () => {
expect(new Alternative('', '#', '').test({})).to.be.undefined
})
it('returns reason if field not present with rule field=value', () => {
expect(new Alternative('field', '=', 'value').test({})).to.equal('field: is missing')
})
it('returns undefined if field is not present with rule field!', () => {
expect(new Alternative('field', '!', '').test({})).to.be.undefined
})
it('returns reason if field is present with rule field!', () => {
expect(new Alternative('field', '!', '').test({ field: 'value' })).to.equal('field: is present')
})
it('calls function if field is a function', () => {
const spy = sinon.fake.returns('reason')
const alternative = new Alternative('field', '=', 'value')
const result = alternative.test({ field: spy })
expect(spy).to.have.been.calledOnceWithExactly(alternative)
expect(result).to.equal('reason')
})
it('returns reason if field not equals value with rule field=value', () => {
expect(new Alternative('field', '=', 'value').test({ field: 'not value' })).to.equal('field: != value')
})
it('returns undefined if field equals value with rule field=value', () => {
expect(new Alternative('field', '=', 'value').test({ field: 'value' })).to.be.undefined
})
it('returns undefined if field not equals value with rule field/value', () => {
expect(new Alternative('field', '/', 'value').test({ field: 'not value' })).to.be.undefined
})
it('returns reason if field equals value with rule field/value', () => {
expect(new Alternative('field', '/', 'value').test({ field: 'value' })).to.equal('field: = value')
})
it('returns undefined if field starts with value with rule field^value', () => {
expect(new Alternative('field', '^', 'value').test({ field: 'value <- here' })).to.be.undefined
})
it('returns reason if field does not start value with rule field^value', () => {
expect(new Alternative('field', '^', 'value').test({ field: 'nope' })).to.equal('field: does not start with value')
})
it('returns undefined if field ends with value with rule field$value', () => {
expect(new Alternative('field', '$', 'value').test({ field: 'ends -> value' })).to.be.undefined
})
it('returns reason if field does not end value with rule field$value', () => {
expect(new Alternative('field', '$', 'value').test({ field: 'nope' })).to.equal('field: does not end with value')
})
it('returns undefined if field contains value with in string rule field~value', () => {
expect(new Alternative('field', '~', 'value').test({ field: '-> value <-' })).to.be.undefined
})
it('returns reason if field does not contain value in string with rule field~value', () => {
expect(new Alternative('field', '~', 'value').test({ field: 'nope' })).to.equal('field: does not contain value')
})
it('returns undefined if field contains value in array with rule field~value', () => {
expect(new Alternative('field', '~', 'value').test({ field: ['value'] })).to.be.undefined
})
it('returns reason if field does not contain value in array with rule field~value', () => {
expect(new Alternative('field', '~', 'value').test({ field: [] })).to.equal('field: does not contain value')
})
it('returns undefined if field is less than value with rule field<value', () => {
expect(new Alternative('field', '<', '0').test({ field: -1 })).to.be.undefined
})
it('returns reason if field does not less than value with rule field<value', () => {
expect(new Alternative('field', '<', '0').test({ field: 0 })).to.equal('field: >= 0')
})
it('returns undefined if field is greater than value with rule field>value', () => {
expect(new Alternative('field', '>', '0').test({ field: 1 })).to.be.undefined
})
it('returns reason if field does not greater than value with rule field>value', () => {
expect(new Alternative('field', '>', '0').test({ field: 0 })).to.equal('field: <= 0')
})
it('returns reason if field is not an integer with rule field<value', () => {
expect(new Alternative('field', '<', '0').test({ field: 'not an integer' })).to.equal('field: not an integer field')
})
it('returns reason if field is not an integer with rule field>value', () => {
expect(new Alternative('field', '>', '0').test({ field: 'not an integer' })).to.equal('field: not an integer field')
})
it('returns reason if field is not an integer with rule field<value', () => {
expect(new Alternative('field', '<', 'not an integer').test({ field: 1 })).to.equal('field: not a valid integer')
})
it('returns reason if field is not an integer with rule field>value', () => {
expect(new Alternative('field', '>', 'not an integer').test({ field: 1 })).to.equal('field: not a valid integer')
})
it('returns undefined if field is same as value with rule field{value', () => {
expect(new Alternative('field', '{', 'b').test({ field: 'b' })).to.equal('field: is the same or ordered after b')
})
it('returns undefined if field is ordered before value with rule field{value', () => {
expect(new Alternative('field', '{', 'b').test({ field: 'a' })).to.be.undefined
})
it('returns reason if field is ordered after value with rule field{value', () => {
expect(new Alternative('field', '{', 'b').test({ field: 'c' })).to.equal('field: is the same or ordered after b')
})
it('returns undefined if field is same as value with rule field}value', () => {
expect(new Alternative('field', '}', 'b').test({ field: 'b' })).to.equal('field: is the same or ordered before b')
})
it('returns undefined if field is ordered after value with rule field}value', () => {
expect(new Alternative('field', '}', 'b').test({ field: 'c' })).to.be.undefined
})
it('returns reason if field is ordered before value with rule field}value', () => {
expect(new Alternative('field', '}', 'b').test({ field: 'a' })).to.equal('field: is the same or ordered before b')
})
})
describe('encode', () => {
it('returns encoded alternative field = value', () => {
expect(new Alternative('field', '=', 'value').encode()).to.equal('field=value')
})
it('returns encoded alternative #', () => {
expect(new Alternative('', '#', '').encode()).to.equal('#')
})
it('returns encoded alternative field!', () => {
expect(new Alternative('field', '!', '').encode()).to.equal('field!')
})
it('returns encoded alternative field=\\|&', () => {
expect(new Alternative('field', '=', '\\|&').encode()).to.equal('field=\\\\\\|\\&')
})
})
describe('decode', () => {
it('decodes #', () => {
const [alternative] = Alternative.decode('#')
expect(alternative.encode()).to.equal('#')
})
it('decodes field!', () => {
const [alternative] = Alternative.decode('field!')
expect(alternative.encode()).to.equal('field!')
})
it('decodes field=value', () => {
const [alternative] = Alternative.decode('field=value')
expect(alternative.encode()).to.equal('field=value')
})
it('decodes field=\\\\\\|\\&', () => {
const [alternative] = Alternative.decode('field=\\\\\\|\\&')
expect(alternative.encode()).to.equal('field=\\\\\\|\\&')
})
it('throw error decoding value', () => {
expect(() => Alternative.decode('value')).to.throw(Error, 'value does not contain any operator')
})
it('decodes until first | and consumes it', () => {
const [alternative, remainder] = Alternative.decode('a=1|b=2')
expect(alternative.encode()).to.equal('a=1')
expect(remainder).to.equal('b=2')
})
it('decodes until first &', () => {
const [alternative, remainder] = Alternative.decode('a=1&b=2')
expect(alternative.encode()).to.equal('a=1')
expect(remainder).to.equal('&b=2')
})
})
describe('from', () => {
it('creates alternative rule with spaces "field = value"', () => {
expect(Alternative.from('field = value').encode()).to.equal('field=value')
})
})
})

View File

@@ -2,7 +2,7 @@ import * as chai from 'chai'
import * as sinon from 'sinon'
import sinonChai from 'sinon-chai'
import { streamEach, streamEnd, streamMap } from '../../../src/utils/stream'
import { streamEach, streamEnd, streamFilter, streamMap } from '../../../src/utils/stream'
chai.use(sinonChai)
@@ -54,3 +54,20 @@ describe('streamEnd', () => {
})
})
describe('streamFilter', () => {
it('passes elements that pass the given predicate downstream', () => {
const spy = sinon.spy()
const stream = streamFilter(({ a }) => a > 1 && a < 4)
stream.on('data', spy)
stream.write({ a: 1 })
stream.write({ a: 2 })
stream.write({ a: 3 })
stream.write({ a: 4 })
stream.end()
expect(spy).to.have.been.calledTwice
expect(spy.firstCall).to.have.been.calledWithExactly({ a: 2 })
expect(spy.secondCall).to.have.been.calledWithExactly({ a: 3 })
})
})