| 1 | import { describe, it, expect } from "vitest" ; |
| 2 | import { newEvent, newKey, pub, now, randHex, sockets } from "./helpers.ts" ; |
| 3 | |
| 4 | const connect = sockets(); |
| 5 | |
| 6 | describe("NIP-45 COUNT" , () => { |
| 7 | it("counts across OR'd filters without double counting, and adds an hll sketch for tag filters" , async () => { |
| 8 | const sk = newKey(); |
| 9 | const c = await connect(); |
| 10 | const target = randHex(); |
| 11 | for (let i = 0; i < 3; i++) await c.publish(newEvent(sk, 1, "n" , [["t" , target]], now() - 10 + i)); |
| 12 | await c.publish(newEvent(sk, 7, "+" , [["e" , target]], now() - 5)); |
| 13 | |
| 14 | c.send("COUNT" , "n" , { kinds: [1], authors: [pub(sk)] }, { kinds: [7], authors: [pub(sk)] }); |
| 15 | expect((await c.expect("COUNT" ))[2].count).toBe(4); |
| 16 | c.send("COUNT" , "n" , { kinds: [1], authors: [pub(sk)] }, { "#t" : [target], authors: [pub(sk)] }); |
| 17 | expect((await c.expect("COUNT" ))[2].count).toBe(3); |
| 18 | |
| 19 | c.send("COUNT" , "r" , { kinds: [7], "#e" : [target] }); |
| 20 | const res = (await c.expect("COUNT" ))[2]; |
| 21 | expect(res.count).toBe(1); |
| 22 | expect(res.hll).toMatch(/^[0-9a-f]{512}$/); |
| 23 | // Register: offset = nibble 32 of the target + 8; byte[offset] indexes, |
| 24 | // zero run after it plus one is the value. |
| 25 | const offset = parseInt(target[32], 16) + 8; |
| 26 | const pk = pub(sk); |
| 27 | const regIndex = parseInt(pk.substr(offset * 2, 2), 16); |
| 28 | const rest = pk.slice((offset + 1) * 2); |
| 29 | let zeros = 0; |
| 30 | for (const ch of rest) { |
| 31 | const n = parseInt(ch, 16); |
| 32 | if (n === 0) { |
| 33 | zeros += 4; |
| 34 | continue; |
| 35 | } |
| 36 | zeros += Math.clz32(n) - 28; |
| 37 | break; |
| 38 | } |
| 39 | expect(parseInt(res.hll.substr(regIndex * 2, 2), 16)).toBe(zeros + 1); |
| 40 | }); |
| 41 | }); |