mirror of
https://gitlab.com/soapbox-pub/ditto.git
synced 2025-12-06 11:29:46 +00:00
Fix stats test
This commit is contained in:
parent
4f46a69131
commit
6cd64500ce
4 changed files with 113 additions and 82 deletions
|
|
@ -47,7 +47,6 @@ export class DittoPglite implements DittoDB {
|
|||
}
|
||||
|
||||
async [Symbol.asyncDispose](): Promise<void> {
|
||||
await this.pglite.close();
|
||||
await this.kysely.destroy();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -164,9 +164,9 @@ export class DittoPgStore extends NPostgres {
|
|||
opts: { signal?: AbortSignal; timeout?: number } = {},
|
||||
): Promise<undefined> {
|
||||
try {
|
||||
await super.transaction(async (store, kysely) => {
|
||||
await updateStats({ event, store, kysely: kysely as unknown as Kysely<DittoTables> });
|
||||
await store.event(event, opts);
|
||||
await super.transaction(async (relay, kysely) => {
|
||||
await updateStats({ event, relay, kysely: kysely as unknown as Kysely<DittoTables> });
|
||||
await relay.event(event, opts);
|
||||
});
|
||||
} catch (e) {
|
||||
// If the failure is only because of updateStats (which runs first), insert the event anyway.
|
||||
|
|
|
|||
|
|
@ -1,43 +1,48 @@
|
|||
import { DittoConf } from '@ditto/conf';
|
||||
import { DittoPolyPg } from '@ditto/db';
|
||||
import { NPostgres } from '@nostrify/db';
|
||||
import { genEvent } from '@nostrify/nostrify/test';
|
||||
import { assertEquals } from '@std/assert';
|
||||
import { sql } from 'kysely';
|
||||
import { generateSecretKey, getPublicKey } from 'nostr-tools';
|
||||
|
||||
import { createTestDB } from '@/test.ts';
|
||||
import { countAuthorStats, getAuthorStats, getEventStats, getFollowDiff, updateStats } from '@/utils/stats.ts';
|
||||
|
||||
Deno.test('updateStats with kind 1 increments notes count', async () => {
|
||||
await using db = await createTestDB();
|
||||
await using test = await setupTest();
|
||||
|
||||
const sk = generateSecretKey();
|
||||
const pubkey = getPublicKey(sk);
|
||||
|
||||
await updateStats({ ...db, event: genEvent({ kind: 1 }, sk) });
|
||||
await updateStats({ ...test, event: genEvent({ kind: 1 }, sk) });
|
||||
|
||||
const stats = await getAuthorStats(db.kysely, pubkey);
|
||||
const stats = await getAuthorStats(test.kysely, pubkey);
|
||||
|
||||
assertEquals(stats!.notes_count, 1);
|
||||
});
|
||||
|
||||
Deno.test('updateStats with kind 1 increments replies count', async () => {
|
||||
await using db = await createTestDB();
|
||||
await using test = await setupTest();
|
||||
const { relay, kysely } = test;
|
||||
|
||||
const sk = generateSecretKey();
|
||||
|
||||
const note = genEvent({ kind: 1 }, sk);
|
||||
await updateStats({ ...db, event: note });
|
||||
await db.store.event(note);
|
||||
await updateStats({ ...test, event: note });
|
||||
await relay.event(note);
|
||||
|
||||
const reply = genEvent({ kind: 1, tags: [['e', note.id]] }, sk);
|
||||
await updateStats({ ...db, event: reply });
|
||||
await db.store.event(reply);
|
||||
await updateStats({ ...test, event: reply });
|
||||
await relay.event(reply);
|
||||
|
||||
const stats = await getEventStats(db.kysely, note.id);
|
||||
const stats = await getEventStats(kysely, note.id);
|
||||
|
||||
assertEquals(stats!.replies_count, 1);
|
||||
});
|
||||
|
||||
Deno.test('updateStats with kind 5 decrements notes count', async () => {
|
||||
await using db = await createTestDB();
|
||||
await using test = await setupTest();
|
||||
const { relay, kysely } = test;
|
||||
|
||||
const sk = generateSecretKey();
|
||||
const pubkey = getPublicKey(sk);
|
||||
|
|
@ -45,41 +50,43 @@ Deno.test('updateStats with kind 5 decrements notes count', async () => {
|
|||
const create = genEvent({ kind: 1 }, sk);
|
||||
const remove = genEvent({ kind: 5, tags: [['e', create.id]] }, sk);
|
||||
|
||||
await updateStats({ ...db, event: create });
|
||||
assertEquals((await getAuthorStats(db.kysely, pubkey))!.notes_count, 1);
|
||||
await db.store.event(create);
|
||||
await updateStats({ ...test, event: create });
|
||||
assertEquals((await getAuthorStats(kysely, pubkey))!.notes_count, 1);
|
||||
await relay.event(create);
|
||||
|
||||
await updateStats({ ...db, event: remove });
|
||||
assertEquals((await getAuthorStats(db.kysely, pubkey))!.notes_count, 0);
|
||||
await db.store.event(remove);
|
||||
await updateStats({ ...test, event: remove });
|
||||
assertEquals((await getAuthorStats(kysely, pubkey))!.notes_count, 0);
|
||||
await relay.event(remove);
|
||||
});
|
||||
|
||||
Deno.test('updateStats with kind 3 increments followers count', async () => {
|
||||
await using db = await createTestDB();
|
||||
await using test = await setupTest();
|
||||
const { kysely } = test;
|
||||
|
||||
await updateStats({ ...db, event: genEvent({ kind: 3, tags: [['p', 'alex']] }) });
|
||||
await updateStats({ ...db, event: genEvent({ kind: 3, tags: [['p', 'alex']] }) });
|
||||
await updateStats({ ...db, event: genEvent({ kind: 3, tags: [['p', 'alex']] }) });
|
||||
await updateStats({ ...test, event: genEvent({ kind: 3, tags: [['p', 'alex']] }) });
|
||||
await updateStats({ ...test, event: genEvent({ kind: 3, tags: [['p', 'alex']] }) });
|
||||
await updateStats({ ...test, event: genEvent({ kind: 3, tags: [['p', 'alex']] }) });
|
||||
|
||||
const stats = await getAuthorStats(db.kysely, 'alex');
|
||||
const stats = await getAuthorStats(kysely, 'alex');
|
||||
|
||||
assertEquals(stats!.followers_count, 3);
|
||||
});
|
||||
|
||||
Deno.test('updateStats with kind 3 decrements followers count', async () => {
|
||||
await using db = await createTestDB();
|
||||
await using test = await setupTest();
|
||||
const { relay, kysely } = test;
|
||||
|
||||
const sk = generateSecretKey();
|
||||
const follow = genEvent({ kind: 3, tags: [['p', 'alex']], created_at: 0 }, sk);
|
||||
const remove = genEvent({ kind: 3, tags: [], created_at: 1 }, sk);
|
||||
|
||||
await updateStats({ ...db, event: follow });
|
||||
assertEquals((await getAuthorStats(db.kysely, 'alex'))!.followers_count, 1);
|
||||
await db.store.event(follow);
|
||||
await updateStats({ ...test, event: follow });
|
||||
assertEquals((await getAuthorStats(kysely, 'alex'))!.followers_count, 1);
|
||||
await relay.event(follow);
|
||||
|
||||
await updateStats({ ...db, event: remove });
|
||||
assertEquals((await getAuthorStats(db.kysely, 'alex'))!.followers_count, 0);
|
||||
await db.store.event(remove);
|
||||
await updateStats({ ...test, event: remove });
|
||||
assertEquals((await getAuthorStats(kysely, 'alex'))!.followers_count, 0);
|
||||
await relay.event(remove);
|
||||
});
|
||||
|
||||
Deno.test('getFollowDiff returns added and removed followers', () => {
|
||||
|
|
@ -93,86 +100,91 @@ Deno.test('getFollowDiff returns added and removed followers', () => {
|
|||
});
|
||||
|
||||
Deno.test('updateStats with kind 6 increments reposts count', async () => {
|
||||
await using db = await createTestDB();
|
||||
await using test = await setupTest();
|
||||
const { relay, kysely } = test;
|
||||
|
||||
const note = genEvent({ kind: 1 });
|
||||
await updateStats({ ...db, event: note });
|
||||
await db.store.event(note);
|
||||
await updateStats({ ...test, event: note });
|
||||
await relay.event(note);
|
||||
|
||||
const repost = genEvent({ kind: 6, tags: [['e', note.id]] });
|
||||
await updateStats({ ...db, event: repost });
|
||||
await db.store.event(repost);
|
||||
await updateStats({ ...test, event: repost });
|
||||
await relay.event(repost);
|
||||
|
||||
const stats = await getEventStats(db.kysely, note.id);
|
||||
const stats = await getEventStats(kysely, note.id);
|
||||
|
||||
assertEquals(stats!.reposts_count, 1);
|
||||
});
|
||||
|
||||
Deno.test('updateStats with kind 5 decrements reposts count', async () => {
|
||||
await using db = await createTestDB();
|
||||
await using test = await setupTest();
|
||||
const { relay, kysely } = test;
|
||||
|
||||
const note = genEvent({ kind: 1 });
|
||||
await updateStats({ ...db, event: note });
|
||||
await db.store.event(note);
|
||||
await updateStats({ ...test, event: note });
|
||||
await relay.event(note);
|
||||
|
||||
const sk = generateSecretKey();
|
||||
const repost = genEvent({ kind: 6, tags: [['e', note.id]] }, sk);
|
||||
await updateStats({ ...db, event: repost });
|
||||
await db.store.event(repost);
|
||||
await updateStats({ ...test, event: repost });
|
||||
await relay.event(repost);
|
||||
|
||||
await updateStats({ ...db, event: genEvent({ kind: 5, tags: [['e', repost.id]] }, sk) });
|
||||
await updateStats({ ...test, event: genEvent({ kind: 5, tags: [['e', repost.id]] }, sk) });
|
||||
|
||||
const stats = await getEventStats(db.kysely, note.id);
|
||||
const stats = await getEventStats(kysely, note.id);
|
||||
|
||||
assertEquals(stats!.reposts_count, 0);
|
||||
});
|
||||
|
||||
Deno.test('updateStats with kind 7 increments reactions count', async () => {
|
||||
await using db = await createTestDB();
|
||||
await using test = await setupTest();
|
||||
const { relay, kysely } = test;
|
||||
|
||||
const note = genEvent({ kind: 1 });
|
||||
await updateStats({ ...db, event: note });
|
||||
await db.store.event(note);
|
||||
await updateStats({ ...test, event: note });
|
||||
await relay.event(note);
|
||||
|
||||
await updateStats({ ...db, event: genEvent({ kind: 7, content: '+', tags: [['e', note.id]] }) });
|
||||
await updateStats({ ...db, event: genEvent({ kind: 7, content: '😂', tags: [['e', note.id]] }) });
|
||||
await updateStats({ ...test, event: genEvent({ kind: 7, content: '+', tags: [['e', note.id]] }) });
|
||||
await updateStats({ ...test, event: genEvent({ kind: 7, content: '😂', tags: [['e', note.id]] }) });
|
||||
|
||||
const stats = await getEventStats(db.kysely, note.id);
|
||||
const stats = await getEventStats(kysely, note.id);
|
||||
|
||||
assertEquals(stats!.reactions, JSON.stringify({ '+': 1, '😂': 1 }));
|
||||
assertEquals(stats!.reactions_count, 2);
|
||||
});
|
||||
|
||||
Deno.test('updateStats with kind 5 decrements reactions count', async () => {
|
||||
await using db = await createTestDB();
|
||||
await using test = await setupTest();
|
||||
const { relay, kysely } = test;
|
||||
|
||||
const note = genEvent({ kind: 1 });
|
||||
await updateStats({ ...db, event: note });
|
||||
await db.store.event(note);
|
||||
await updateStats({ ...test, event: note });
|
||||
await relay.event(note);
|
||||
|
||||
const sk = generateSecretKey();
|
||||
const reaction = genEvent({ kind: 7, content: '+', tags: [['e', note.id]] }, sk);
|
||||
await updateStats({ ...db, event: reaction });
|
||||
await db.store.event(reaction);
|
||||
await updateStats({ ...test, event: reaction });
|
||||
await relay.event(reaction);
|
||||
|
||||
await updateStats({ ...db, event: genEvent({ kind: 5, tags: [['e', reaction.id]] }, sk) });
|
||||
await updateStats({ ...test, event: genEvent({ kind: 5, tags: [['e', reaction.id]] }, sk) });
|
||||
|
||||
const stats = await getEventStats(db.kysely, note.id);
|
||||
const stats = await getEventStats(kysely, note.id);
|
||||
|
||||
assertEquals(stats!.reactions, JSON.stringify({}));
|
||||
});
|
||||
|
||||
Deno.test('countAuthorStats counts author stats from the database', async () => {
|
||||
await using db = await createTestDB();
|
||||
await using test = await setupTest();
|
||||
const { relay } = test;
|
||||
|
||||
const sk = generateSecretKey();
|
||||
const pubkey = getPublicKey(sk);
|
||||
|
||||
await db.store.event(genEvent({ kind: 1, content: 'hello' }, sk));
|
||||
await db.store.event(genEvent({ kind: 1, content: 'yolo' }, sk));
|
||||
await db.store.event(genEvent({ kind: 3, tags: [['p', pubkey]] }));
|
||||
await relay.event(genEvent({ kind: 1, content: 'hello' }, sk));
|
||||
await relay.event(genEvent({ kind: 1, content: 'yolo' }, sk));
|
||||
await relay.event(genEvent({ kind: 3, tags: [['p', pubkey]] }));
|
||||
|
||||
await db.kysely.insertInto('author_stats').values({
|
||||
await test.kysely.insertInto('author_stats').values({
|
||||
pubkey,
|
||||
search: 'Yolo Lolo',
|
||||
notes_count: 0,
|
||||
|
|
@ -181,8 +193,28 @@ Deno.test('countAuthorStats counts author stats from the database', async () =>
|
|||
}).onConflict((oc) => oc.column('pubkey').doUpdateSet({ 'search': 'baka' }))
|
||||
.execute();
|
||||
|
||||
const stats = await countAuthorStats({ store: db.store, pubkey, kysely: db.kysely });
|
||||
const stats = await countAuthorStats({ ...test, pubkey });
|
||||
|
||||
assertEquals(stats!.notes_count, 2);
|
||||
assertEquals(stats!.followers_count, 1);
|
||||
});
|
||||
|
||||
async function setupTest() {
|
||||
const conf = new DittoConf(Deno.env);
|
||||
|
||||
const db = new DittoPolyPg(conf.databaseUrl);
|
||||
await db.migrate();
|
||||
|
||||
const { kysely } = db;
|
||||
const relay = new NPostgres(kysely);
|
||||
|
||||
return {
|
||||
relay,
|
||||
kysely,
|
||||
[Symbol.asyncDispose]: async () => {
|
||||
await sql`truncate table event_stats cascade`.execute(kysely);
|
||||
await sql`truncate table author_stats cascade`.execute(kysely);
|
||||
await db[Symbol.asyncDispose]();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,14 +9,14 @@ import { findQuoteTag, findReplyTag, getTagSet } from '@/utils/tags.ts';
|
|||
|
||||
interface UpdateStatsOpts {
|
||||
kysely: Kysely<DittoTables>;
|
||||
store: NStore;
|
||||
relay: NStore;
|
||||
event: NostrEvent;
|
||||
x?: 1 | -1;
|
||||
}
|
||||
|
||||
/** Handle one event at a time and update relevant stats for it. */
|
||||
// deno-lint-ignore require-await
|
||||
export async function updateStats({ event, kysely, store, x = 1 }: UpdateStatsOpts): Promise<void> {
|
||||
export async function updateStats({ event, kysely, relay, x = 1 }: UpdateStatsOpts): Promise<void> {
|
||||
switch (event.kind) {
|
||||
case 1:
|
||||
case 20:
|
||||
|
|
@ -24,9 +24,9 @@ export async function updateStats({ event, kysely, store, x = 1 }: UpdateStatsOp
|
|||
case 30023:
|
||||
return handleEvent1(kysely, event, x);
|
||||
case 3:
|
||||
return handleEvent3(kysely, event, x, store);
|
||||
return handleEvent3(kysely, event, x, relay);
|
||||
case 5:
|
||||
return handleEvent5(kysely, event, -1, store);
|
||||
return handleEvent5(kysely, event, -1, relay);
|
||||
case 6:
|
||||
return handleEvent6(kysely, event, x);
|
||||
case 7:
|
||||
|
|
@ -88,12 +88,12 @@ async function handleEvent1(kysely: Kysely<DittoTables>, event: NostrEvent, x: n
|
|||
}
|
||||
|
||||
/** Update stats for kind 3 event. */
|
||||
async function handleEvent3(kysely: Kysely<DittoTables>, event: NostrEvent, x: number, store: NStore): Promise<void> {
|
||||
async function handleEvent3(kysely: Kysely<DittoTables>, event: NostrEvent, x: number, relay: NStore): Promise<void> {
|
||||
const following = getTagSet(event.tags, 'p');
|
||||
|
||||
await updateAuthorStats(kysely, event.pubkey, () => ({ following_count: following.size }));
|
||||
|
||||
const [prev] = await store.query([
|
||||
const [prev] = await relay.query([
|
||||
{ kinds: [3], authors: [event.pubkey], limit: 1 },
|
||||
]);
|
||||
|
||||
|
|
@ -117,12 +117,12 @@ async function handleEvent3(kysely: Kysely<DittoTables>, event: NostrEvent, x: n
|
|||
}
|
||||
|
||||
/** Update stats for kind 5 event. */
|
||||
async function handleEvent5(kysely: Kysely<DittoTables>, event: NostrEvent, x: -1, store: NStore): Promise<void> {
|
||||
async function handleEvent5(kysely: Kysely<DittoTables>, event: NostrEvent, x: -1, relay: NStore): Promise<void> {
|
||||
const id = event.tags.find(([name]) => name === 'e')?.[1];
|
||||
if (id) {
|
||||
const [target] = await store.query([{ ids: [id], authors: [event.pubkey], limit: 1 }]);
|
||||
const [target] = await relay.query([{ ids: [id], authors: [event.pubkey], limit: 1 }]);
|
||||
if (target) {
|
||||
await updateStats({ event: target, kysely, store, x });
|
||||
await updateStats({ event: target, kysely, relay, x });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -300,13 +300,13 @@ export async function updateEventStats(
|
|||
|
||||
/** Calculate author stats from the database. */
|
||||
export async function countAuthorStats(
|
||||
{ pubkey, store }: RefreshAuthorStatsOpts,
|
||||
{ pubkey, relay }: RefreshAuthorStatsOpts,
|
||||
): Promise<DittoTables['author_stats']> {
|
||||
const [{ count: followers_count }, { count: notes_count }, [followList], [kind0]] = await Promise.all([
|
||||
store.count([{ kinds: [3], '#p': [pubkey] }]),
|
||||
store.count([{ kinds: [1, 20], authors: [pubkey] }]),
|
||||
store.query([{ kinds: [3], authors: [pubkey], limit: 1 }]),
|
||||
store.query([{ kinds: [0], authors: [pubkey], limit: 1 }]),
|
||||
relay.count([{ kinds: [3], '#p': [pubkey] }]),
|
||||
relay.count([{ kinds: [1, 20], authors: [pubkey] }]),
|
||||
relay.query([{ kinds: [3], authors: [pubkey], limit: 1 }]),
|
||||
relay.query([{ kinds: [0], authors: [pubkey], limit: 1 }]),
|
||||
]);
|
||||
let search: string = '';
|
||||
const metadata = n.json().pipe(n.metadata()).catch({}).safeParse(kind0?.content);
|
||||
|
|
@ -333,14 +333,14 @@ export async function countAuthorStats(
|
|||
export interface RefreshAuthorStatsOpts {
|
||||
pubkey: string;
|
||||
kysely: Kysely<DittoTables>;
|
||||
store: SetRequired<NStore, 'count'>;
|
||||
relay: SetRequired<NStore, 'count'>;
|
||||
}
|
||||
|
||||
/** Refresh the author's stats in the database. */
|
||||
export async function refreshAuthorStats(
|
||||
{ pubkey, kysely, store }: RefreshAuthorStatsOpts,
|
||||
{ pubkey, kysely, relay }: RefreshAuthorStatsOpts,
|
||||
): Promise<DittoTables['author_stats']> {
|
||||
const stats = await countAuthorStats({ store, pubkey, kysely });
|
||||
const stats = await countAuthorStats({ relay, pubkey, kysely });
|
||||
|
||||
await kysely.insertInto('author_stats')
|
||||
.values(stats)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue