blowater/nostr.test.ts
2023-06-30 14:05:57 +00:00

523 lines
15 KiB
TypeScript

import {
assertEquals,
assertInstanceOf,
assertNotEquals,
fail,
} from "https://deno.land/std@0.176.0/testing/asserts.ts";
import { utf8Decode } from "https://raw.githubusercontent.com/BlowaterNostr/nostr.ts/main/ende.ts";
import {
blobToBase64,
decryptNostrEvent,
InMemoryAccountContext,
NostrEvent,
NostrKind,
prepareNormalNostrEvent,
} from "https://raw.githubusercontent.com/BlowaterNostr/nostr.ts/main/nostr.ts";
import {
computeThreads,
groupImageEvents,
prepareNostrImageEvents,
prepareReplyEvent,
reassembleBase64ImageFromEvents,
} from "./nostr.ts";
import { LamportTime } from "./time.ts";
import {
generatePrivateKeyHex,
PrivateKey,
toPublicKeyHex,
} from "https://raw.githubusercontent.com/BlowaterNostr/nostr.ts/main/key.ts";
Deno.test("prepareNostrImageEvents", async (t) => {
const pri = PrivateKey.Generate();
const pub = toPublicKeyHex(pri.hex);
let randomData = new Uint8Array(1024 * 48); // 48KB raw data
for (let i = 0; i < randomData.length; i++) {
randomData.fill(Math.floor(Math.random() * 128), i); // https://en.wikipedia.org/wiki/UTF-8
}
let randomStr = utf8Decode(randomData);
const blob = new Blob([randomStr]);
const imgEvents = await prepareNostrImageEvents(
InMemoryAccountContext.New(pri),
pub,
blob,
NostrKind.DIRECT_MESSAGE,
);
if (imgEvents instanceof Error) {
fail(imgEvents.message);
}
const [events, _] = imgEvents;
await t.step("full", async () => {
const decryptedEvents = [];
for (const e of events) {
const decryptedEvent = await decryptNostrEvent(e, InMemoryAccountContext.New(pri), pub);
if (decryptedEvent instanceof Error) {
fail(decryptedEvent.message);
}
decryptedEvents.push(decryptedEvent);
}
const content = reassembleBase64ImageFromEvents(decryptedEvents);
if (content instanceof Error) {
fail(content.message);
}
assertEquals(await blobToBase64(blob), content);
});
await t.step("partial, should fail", async () => {
const decryptedEvents = [];
const partialEvents = events.slice(0, 1); // not enough events
for (const e of partialEvents) {
const decryptedEvent = await decryptNostrEvent(e, InMemoryAccountContext.New(pri), pub);
if (decryptedEvent instanceof Error) {
fail(decryptedEvent.message);
}
decryptedEvents.push(decryptedEvent);
}
const content = reassembleBase64ImageFromEvents(decryptedEvents);
assertInstanceOf(content, Error);
});
});
Deno.test("groupImageEvents", async () => {
const pri = PrivateKey.Generate();
const pub = toPublicKeyHex(pri.hex);
let randomData = new Uint8Array(1024 * 17);
for (let i = 0; i < randomData.length; i++) {
randomData.fill(Math.floor(Math.random() * 128), i); // https://en.wikipedia.org/wiki/UTF-8
}
let randomStr = utf8Decode(randomData);
const blob = new Blob([randomStr]);
const imgEvents1 = await prepareNostrImageEvents(
InMemoryAccountContext.New(pri),
pub,
blob,
NostrKind.DIRECT_MESSAGE,
);
if (imgEvents1 instanceof Error) {
fail(imgEvents1.message);
}
const [events1, id1] = imgEvents1;
const imgEvents2 = await prepareNostrImageEvents(
InMemoryAccountContext.New(pri),
pub,
blob,
NostrKind.DIRECT_MESSAGE,
);
if (imgEvents2 instanceof Error) {
fail(imgEvents2.message);
}
const [events2, id2] = imgEvents2;
const groups = groupImageEvents(events1.concat(events2));
assertEquals(groups.size, 2);
const group1 = groups.get(id1);
assertNotEquals(group1, undefined);
assertEquals(group1, events1);
const group2 = groups.get(id2);
assertNotEquals(group2, undefined);
assertEquals(group2, events2);
});
Deno.test("Generate reply event", async () => {
const userAPrivateKey = PrivateKey.Generate();
const userBPrivateKey = PrivateKey.Generate();
const userAContext = InMemoryAccountContext.New(userAPrivateKey);
const message1 = await prepareNormalNostrEvent(
userAContext,
NostrKind.DIRECT_MESSAGE,
[],
"text message 1",
);
const replyMessage1WithText = await prepareReplyEvent(
userAContext,
message1,
[],
"aaaa",
) as NostrEvent;
assertEquals(replyMessage1WithText.kind, message1.kind);
assertEquals(replyMessage1WithText.pubkey, toPublicKeyHex(userAPrivateKey.hex));
assertEquals(replyMessage1WithText.tags, [[
"e",
message1.id,
"",
"reply",
]]);
});
Deno.test("Group reply messages", async (t) => {
const userAPrivateKey = PrivateKey.Generate();
const userBPrivateKey = PrivateKey.Generate();
const userAContext = InMemoryAccountContext.New(userAPrivateKey);
const userBContext = InMemoryAccountContext.New(userBPrivateKey);
const lamport = new LamportTime(0);
await t.step("has reply", async () => {
const message1 = await prepareNormalNostrEvent(
userAContext,
NostrKind.DIRECT_MESSAGE,
[
["lamport", String(lamport.now())],
],
"text message 1",
);
const message2 = await prepareReplyEvent(
userAContext,
message1,
[["lamport", String(lamport.now())]],
"text message 2, text message 1's reply",
);
const message3 = await prepareReplyEvent(
userBContext,
message1,
[["lamport", String(lamport.now())]],
"text message 3, text message 1's reply",
) as NostrEvent;
const message4 = await prepareReplyEvent(
userAContext,
message3,
[["lamport", String(lamport.now())]],
"text message 4, text message 3's reply",
);
const message5 = await prepareReplyEvent(
userBContext,
message3,
[["lamport", String(lamport.now())]],
"text message 5, text message 3's reply",
) as NostrEvent;
const message6LeadEventID = generatePrivateKeyHex();
const message6 = await prepareNormalNostrEvent(
userBContext,
NostrKind.DIRECT_MESSAGE,
[
[
"image",
message6LeadEventID,
"3",
"0",
],
["lamport", String(lamport.now())],
],
"image message 6",
);
const message7 = await prepareNormalNostrEvent(
userBContext,
NostrKind.DIRECT_MESSAGE,
[
[
"image",
message6LeadEventID,
"3",
"1",
],
["lamport", String(lamport.now())],
],
"image message 7",
);
const message8 = await prepareNormalNostrEvent(
userBContext,
NostrKind.DIRECT_MESSAGE,
[
[
"image",
message6LeadEventID,
"3",
"2",
],
["lamport", String(lamport.now())],
],
"image message 8",
);
const message9LeadEventID = generatePrivateKeyHex();
const message9 = await prepareReplyEvent(
userAContext,
message6,
[
[
"image",
message9LeadEventID,
"3",
"0",
],
["lamport", String(lamport.now())],
],
"image message 9, image message 6's reply",
);
const message10 = await prepareReplyEvent(
userAContext,
message6,
[
[
"image",
message9LeadEventID,
"3",
"1",
],
["lamport", String(lamport.now())],
],
"image message 10, image message 6's reply",
);
const message11 = await prepareReplyEvent(
userAContext,
message6,
[
[
"image",
message9LeadEventID,
"3",
"2",
],
["lamport", String(lamport.now())],
],
"image message 11, image message 6's reply",
);
const message12 = await prepareReplyEvent(
userAContext,
message5,
[
["lamport", String(lamport.now())],
],
"text message 12, text message 5's reply",
);
const testEvent: NostrEvent[] = [
message1,
message2 as NostrEvent,
message3 as NostrEvent,
message4 as NostrEvent,
message5 as NostrEvent,
message6,
message7,
message8,
message9 as NostrEvent,
message10 as NostrEvent,
message11 as NostrEvent,
message12 as NostrEvent,
];
const validResult = new Set<Set<NostrEvent>>([
new Set([
message1 as NostrEvent,
message2 as NostrEvent,
message3 as NostrEvent,
message4 as NostrEvent,
message5 as NostrEvent,
message12 as NostrEvent,
]),
new Set([
message6 as NostrEvent,
message7 as NostrEvent,
message8 as NostrEvent,
message9 as NostrEvent,
message10 as NostrEvent,
message11 as NostrEvent,
]),
]);
for (let i = 0; i < 10; i++) {
const result = computeThreads(testEvent.sort((a, b) => 0.5 - Math.random()));
const res = new Set(result.map((r) => new Set(r)));
assertEquals(res, validResult);
}
});
await t.step("no reply", async () => {
const message1 = await prepareNormalNostrEvent(
userAContext,
NostrKind.DIRECT_MESSAGE,
[],
"text message 1",
);
const message2LeadEventID = generatePrivateKeyHex();
const message2 = await prepareNormalNostrEvent(
userBContext,
NostrKind.DIRECT_MESSAGE,
[
[
"image",
message2LeadEventID,
"3",
"0",
],
],
"text message 2",
);
const message3 = await prepareNormalNostrEvent(
userBContext,
NostrKind.DIRECT_MESSAGE,
[
[
"image",
message2LeadEventID,
"3",
"1",
],
],
"text message 3",
);
const message4 = await prepareNormalNostrEvent(
userBContext,
NostrKind.DIRECT_MESSAGE,
[
[
"image",
message2LeadEventID,
"3",
"2",
],
],
"text message 4",
);
const testEvent = [
message1,
message2,
message3,
message4,
];
const validResult = new Set([
new Set([
message1,
]),
new Set([
message2,
message3,
message4,
]),
]);
for (let i = 0; i < 10; i++) {
const result = computeThreads(testEvent.sort((a, b) => 0.5 - Math.random()));
const res = new Set(result.map((r) => new Set(r)));
assertEquals(res, validResult);
}
});
await t.step("has reply but no root", async () => {
const message1 = await prepareNormalNostrEvent(
userAContext,
NostrKind.DIRECT_MESSAGE,
[],
"text message 1",
);
const notUsed = await prepareNormalNostrEvent(
userAContext,
NostrKind.DIRECT_MESSAGE,
[],
"not used",
);
const message2 = await prepareReplyEvent(
userAContext,
notUsed,
[],
"text",
) as NostrEvent;
const message3 = await prepareReplyEvent(
userAContext,
notUsed,
[[
"image",
generatePrivateKeyHex(),
"1",
"0",
]],
"image",
) as NostrEvent;
const testEvent = [
message1,
message2,
message3,
];
const validResult = new Set([
new Set([
message1,
]),
new Set([
message2,
message3,
]),
]);
for (let i = 0; i < 10; i++) {
const result = computeThreads(testEvent.sort((a, b) => 0.5 - Math.random()));
const res = new Set(result.map((r) => new Set(r)));
assertEquals(res, validResult);
}
});
await t.step("if one of reply has both reply tag and root tag", async () => {
const userAPrivateKey = PrivateKey.Generate();
const userBPrivateKey = PrivateKey.Generate();
const userAContext = InMemoryAccountContext.New(userAPrivateKey);
const userBContext = InMemoryAccountContext.New(userBPrivateKey);
const message1 = await prepareNormalNostrEvent(
userAContext,
NostrKind.TEXT_NOTE,
[],
"message 1",
);
const message2 = await prepareReplyEvent(
userAContext,
message1,
[],
"message2, message1's reply",
) as NostrEvent;
const message3 = await prepareReplyEvent(
userBContext,
message1,
[],
"message3, message1's reply",
) as NostrEvent;
const message4 = await prepareReplyEvent(
userAContext,
message2,
[
[
"e",
message1.id,
"",
"root",
],
],
"message4, message 2' reply",
) as NostrEvent;
const testEvent = [
message1,
message2,
message3,
message4,
];
const validResult = new Set([
new Set([
message1,
message2,
message3,
message4,
]),
]);
for (let i = 0; i < 10; i++) {
const data = testEvent.sort((a, b) => 0.5 - Math.random());
console.log(data);
const result = computeThreads(data);
const res = new Set(result.map((r) => new Set(r)));
assertEquals(res, validResult);
}
});
});