287 lines
8.5 KiB
TypeScript
287 lines
8.5 KiB
TypeScript
import {now} from "@welshman/lib"
|
|
import {describe, it, expect} from "vitest"
|
|
import {verifiedSymbol} from "nostr-tools/pure"
|
|
import * as Events from "../src/Events"
|
|
import {COMMENT} from "../src/Kinds"
|
|
|
|
describe("Events", () => {
|
|
// Realistic Nostr data
|
|
const pubkey = "ee".repeat(32)
|
|
const sig = "ee".repeat(64)
|
|
const id = "ff".repeat(32)
|
|
const currentTime = now()
|
|
|
|
const createBaseEvent = () => ({
|
|
kind: 1,
|
|
content: "Hello Nostr!",
|
|
tags: [["p", pubkey]],
|
|
})
|
|
|
|
const createStampedEvent = () => ({
|
|
...createBaseEvent(),
|
|
created_at: currentTime,
|
|
})
|
|
|
|
const createOwnedEvent = () => ({
|
|
...createStampedEvent(),
|
|
pubkey: pubkey,
|
|
})
|
|
|
|
const createHashedEvent = () => ({
|
|
...createOwnedEvent(),
|
|
id: id,
|
|
})
|
|
|
|
const createSignedEvent = () => ({
|
|
...createHashedEvent(),
|
|
sig: sig,
|
|
})
|
|
|
|
const createCommentEvent = (parentId: string) => ({
|
|
...createHashedEvent(),
|
|
kind: COMMENT,
|
|
tags: [
|
|
["E", parentId, "", "root"],
|
|
["P", pubkey],
|
|
],
|
|
})
|
|
|
|
const createReplyEvent = (parentId: string) => ({
|
|
...createHashedEvent(),
|
|
kind: 1,
|
|
tags: [
|
|
["e", parentId, "", "root"],
|
|
["e", parentId, "", "reply"],
|
|
["p", pubkey, "", "root"],
|
|
["p", pubkey, "", "reply"],
|
|
],
|
|
})
|
|
|
|
describe("makeEvent", () => {
|
|
it("should create event with defaults", () => {
|
|
const event = Events.makeEvent(1, {})
|
|
expect(event.kind).toBe(1)
|
|
expect(event.content).toBe("")
|
|
expect(event.tags).toEqual([])
|
|
expect(event.created_at).toBeLessThanOrEqual(now())
|
|
})
|
|
|
|
it("should create event with provided values", () => {
|
|
const event = Events.makeEvent(1, {
|
|
content: "Hello Nostr!",
|
|
tags: [["p", pubkey]],
|
|
created_at: currentTime,
|
|
})
|
|
expect(event).toEqual(createStampedEvent())
|
|
})
|
|
})
|
|
|
|
describe("type guards", () => {
|
|
it("should validate EventTemplate", () => {
|
|
expect(Events.isEventTemplate(createBaseEvent())).toBe(true)
|
|
expect(Events.isEventTemplate({kind: 1} as Events.EventTemplate)).toBe(false)
|
|
})
|
|
|
|
it("should validate StampedEvent", () => {
|
|
expect(Events.isStampedEvent(createStampedEvent())).toBe(true)
|
|
expect(Events.isStampedEvent(createBaseEvent() as Events.StampedEvent)).toBe(false)
|
|
})
|
|
|
|
it("should validate OwnedEvent", () => {
|
|
expect(Events.isOwnedEvent(createOwnedEvent())).toBe(true)
|
|
expect(Events.isOwnedEvent(createStampedEvent() as Events.OwnedEvent)).toBe(false)
|
|
})
|
|
|
|
it("should validate HashedEvent", () => {
|
|
expect(Events.isHashedEvent(createHashedEvent())).toBe(true)
|
|
expect(Events.isHashedEvent(createOwnedEvent() as Events.HashedEvent)).toBe(false)
|
|
})
|
|
|
|
it("should validate SignedEvent", () => {
|
|
expect(Events.isSignedEvent(createSignedEvent())).toBe(true)
|
|
expect(Events.isSignedEvent(createHashedEvent())).toBe(false)
|
|
})
|
|
|
|
it("should validate TrustedEvent", () => {
|
|
const unwrapped = {
|
|
...createHashedEvent(),
|
|
wraps: [createSignedEvent()],
|
|
}
|
|
expect(Events.isTrustedEvent(createHashedEvent())).toBe(false)
|
|
expect(Events.isTrustedEvent(createSignedEvent())).toBe(true)
|
|
expect(Events.isTrustedEvent(unwrapped)).toBe(true)
|
|
})
|
|
|
|
it("should validate UnwrappedEvent", () => {
|
|
const unwrapped = {
|
|
...createHashedEvent(),
|
|
wraps: [createSignedEvent()],
|
|
}
|
|
expect(Events.isUnwrappedEvent(unwrapped)).toBe(true)
|
|
expect(Events.isUnwrappedEvent(createHashedEvent())).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe("event conversion", () => {
|
|
it("should convert to EventTemplate", () => {
|
|
const result = Events.asEventTemplate(createSignedEvent())
|
|
expect(result).toHaveProperty("kind")
|
|
expect(result).toHaveProperty("tags")
|
|
expect(result).toHaveProperty("content")
|
|
expect(result).not.toHaveProperty("created_at")
|
|
})
|
|
|
|
it("should convert to StampedEvent", () => {
|
|
const result = Events.asStampedEvent(createSignedEvent())
|
|
expect(result).toHaveProperty("created_at")
|
|
expect(result).not.toHaveProperty("pubkey")
|
|
})
|
|
|
|
it("should convert to OwnedEvent", () => {
|
|
const result = Events.asOwnedEvent(createSignedEvent())
|
|
expect(result).not.toHaveProperty("sig")
|
|
expect(result).not.toHaveProperty("id")
|
|
})
|
|
|
|
it("should convert to HashedEvent", () => {
|
|
const result = Events.asHashedEvent(createSignedEvent())
|
|
expect(result).not.toHaveProperty("sig")
|
|
})
|
|
|
|
it("should convert to SignedEvent", () => {
|
|
const trustedEvent = {
|
|
...createHashedEvent(),
|
|
sig: sig,
|
|
wraps: [createSignedEvent()],
|
|
}
|
|
const result = Events.asSignedEvent(trustedEvent)
|
|
expect(result).not.toHaveProperty("wraps")
|
|
expect(result).toHaveProperty("sig")
|
|
})
|
|
|
|
it("should convert to UnwrappedEvent", () => {
|
|
const trustedEvent = {
|
|
...createHashedEvent(),
|
|
sig: sig,
|
|
wraps: [createSignedEvent()],
|
|
}
|
|
const result = Events.asUnwrappedEvent(trustedEvent)
|
|
expect(result).toHaveProperty("wraps")
|
|
expect(result).not.toHaveProperty("sig")
|
|
})
|
|
|
|
it("should convert to TrustedEvent", () => {
|
|
const trustedEvent = {
|
|
...createHashedEvent(),
|
|
sig: sig,
|
|
wraps: [createSignedEvent()],
|
|
}
|
|
const result = Events.asTrustedEvent(trustedEvent)
|
|
expect(result).toHaveProperty("sig")
|
|
expect(result).toHaveProperty("wraps")
|
|
})
|
|
})
|
|
|
|
describe("signature validation", () => {
|
|
it("should validate signature using verifiedSymbol", () => {
|
|
const event = createSignedEvent() as Events.SignedEvent
|
|
event[verifiedSymbol] = true
|
|
expect(Events.verifyEvent(event)).toBe(true)
|
|
|
|
// Clear verifiedSymbol and verify the actual signature
|
|
delete event[verifiedSymbol]
|
|
// the signature is invalid, so verifyEvent should return false
|
|
expect(Events.verifyEvent(event)).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe("event identifiers", () => {
|
|
it("should get identifier from d tag", () => {
|
|
const event = {
|
|
...createBaseEvent(),
|
|
tags: [["d", "test-identifier"]],
|
|
}
|
|
expect(Events.getIdentifier(event)).toBe("test-identifier")
|
|
})
|
|
|
|
it("should get address for replaceable events", () => {
|
|
const event = {
|
|
...createHashedEvent(),
|
|
kind: 10000, // replaceable kind
|
|
}
|
|
expect(Events.getIdOrAddress(event)).toMatch(/^10000:/)
|
|
})
|
|
})
|
|
|
|
describe("event relationships", () => {
|
|
it("should identify parent-child relationships", () => {
|
|
const parent = createHashedEvent()
|
|
const child = createCommentEvent(parent.id)
|
|
expect(Events.isChildOf(child, parent)).toBe(true)
|
|
})
|
|
|
|
it("should get parent IDs", () => {
|
|
const parentId = id
|
|
const event = createCommentEvent(parentId)
|
|
expect(Events.getParentIds(event)).toContain(parentId)
|
|
})
|
|
|
|
it("should get parent addresses", () => {
|
|
const event = {
|
|
...createCommentEvent(id),
|
|
tags: [["e", "30023:pubkey:identifier", "", "root"]],
|
|
}
|
|
expect(Events.getParentAddrs(event)[0]).toMatch(/^\d+:/)
|
|
})
|
|
})
|
|
|
|
describe("event type checks", () => {
|
|
it("should identify ephemeral events", () => {
|
|
const event = {
|
|
...createBaseEvent(),
|
|
kind: 20000, // ephemeral kind
|
|
}
|
|
expect(Events.isEphemeral(event)).toBe(true)
|
|
})
|
|
|
|
it("should identify replaceable events", () => {
|
|
const event = {
|
|
...createBaseEvent(),
|
|
kind: 10000, // replaceable kind
|
|
}
|
|
expect(Events.isReplaceable(event)).toBe(true)
|
|
})
|
|
|
|
it("should identify parameterized replaceable events", () => {
|
|
const event = {
|
|
...createBaseEvent(),
|
|
kind: 30000, // parameterized replaceable kind
|
|
}
|
|
expect(Events.isParameterizedReplaceable(event)).toBe(true)
|
|
})
|
|
})
|
|
|
|
describe("ancestor handling", () => {
|
|
it("should get ancestors for comments", () => {
|
|
const parentId = id
|
|
const event = createCommentEvent(parentId)
|
|
const ancestors = Events.getAncestors(event)
|
|
expect(ancestors.roots).toContain(parentId)
|
|
})
|
|
|
|
it("should get ancestors for replies", () => {
|
|
const parentId = id
|
|
const event = createReplyEvent(parentId)
|
|
const ancestors = Events.getAncestors(event)
|
|
expect(ancestors.roots).toContain(parentId)
|
|
})
|
|
|
|
it("should handle events without ancestors", () => {
|
|
const event = createBaseEvent()
|
|
const ancestors = Events.getAncestors(event)
|
|
expect(ancestors.roots).toEqual([])
|
|
expect(ancestors.replies).toEqual([])
|
|
})
|
|
})
|
|
})
|