mirror of
https://gitlab.com/soapbox-pub/ditto.git
synced 2025-12-06 03:19:46 +00:00
Merge branch 'postgres-support-testing' into 'main'
Support tests with Postgres Closes #171 See merge request soapbox-pub/ditto!435
This commit is contained in:
commit
ac15aa431b
8 changed files with 231 additions and 130 deletions
|
|
@ -35,11 +35,11 @@ test:
|
|||
|
||||
postgres:
|
||||
stage: test
|
||||
script: deno task db:migrate
|
||||
script: deno task db:migrate && deno task test
|
||||
services:
|
||||
- postgres:16
|
||||
variables:
|
||||
DITTO_NSEC: nsec1zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs4rm7hz
|
||||
DATABASE_URL: postgres://postgres:postgres@postgres:5432/postgres
|
||||
POSTGRES_HOST_AUTH_METHOD: trust
|
||||
|
||||
ALLOW_TO_USE_DATABASE_URL: true
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
"db:migrate": "deno run -A scripts/db-migrate.ts",
|
||||
"nostr:pull": "deno run -A scripts/nostr-pull.ts",
|
||||
"debug": "deno run -A --inspect src/server.ts",
|
||||
"test": "DATABASE_URL=\"sqlite://:memory:\" deno test -A --junit-path=./deno-test.xml",
|
||||
"test": "deno test -A --junit-path=./deno-test.xml",
|
||||
"check": "deno check src/server.ts",
|
||||
"nsec": "deno run scripts/nsec.ts",
|
||||
"admin:event": "deno run -A scripts/admin-event.ts",
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { assertEquals } from '@std/assert';
|
||||
import { generateSecretKey } from 'nostr-tools';
|
||||
|
||||
import { genEvent, getTestDB } from '@/test.ts';
|
||||
import { createTestDB, genEvent, getTestDB } from '@/test.ts';
|
||||
import { handleZaps } from '@/pipeline.ts';
|
||||
|
||||
Deno.test('store one zap receipt in nostr_events; convert it into event_zaps table format and store it', async () => {
|
||||
await using db = await getTestDB();
|
||||
await using db = await createTestDB();
|
||||
const kysely = db.kysely;
|
||||
|
||||
const sk = generateSecretKey();
|
||||
|
|
|
|||
|
|
@ -1,87 +1,75 @@
|
|||
import { Database as Sqlite } from '@db/sqlite';
|
||||
import { DenoSqlite3Dialect } from '@soapbox/kysely-deno-sqlite';
|
||||
import { assertEquals, assertRejects } from '@std/assert';
|
||||
import { Kysely } from 'kysely';
|
||||
import { generateSecretKey } from 'nostr-tools';
|
||||
|
||||
import { Conf } from '@/config.ts';
|
||||
import { DittoDB } from '@/db/DittoDB.ts';
|
||||
import { DittoTables } from '@/db/DittoTables.ts';
|
||||
import { RelayError } from '@/RelayError.ts';
|
||||
import { EventsDB } from '@/storages/EventsDB.ts';
|
||||
import { eventFixture, genEvent } from '@/test.ts';
|
||||
|
||||
/** Create in-memory database for testing. */
|
||||
const createDB = async () => {
|
||||
const kysely = new Kysely<DittoTables>({
|
||||
dialect: new DenoSqlite3Dialect({
|
||||
database: new Sqlite(':memory:'),
|
||||
}),
|
||||
});
|
||||
const eventsDB = new EventsDB(kysely);
|
||||
await DittoDB.migrate(kysely);
|
||||
return { eventsDB, kysely };
|
||||
};
|
||||
import { Conf } from '@/config.ts';
|
||||
import { createTestDB } from '@/test.ts';
|
||||
|
||||
Deno.test('count filters', async () => {
|
||||
const { eventsDB } = await createDB();
|
||||
await using db = await createTestDB();
|
||||
const { store } = db;
|
||||
|
||||
const event1 = await eventFixture('event-1');
|
||||
|
||||
assertEquals((await eventsDB.count([{ kinds: [1] }])).count, 0);
|
||||
await eventsDB.event(event1);
|
||||
assertEquals((await eventsDB.count([{ kinds: [1] }])).count, 1);
|
||||
assertEquals((await store.count([{ kinds: [1] }])).count, 0);
|
||||
await store.event(event1);
|
||||
assertEquals((await store.count([{ kinds: [1] }])).count, 1);
|
||||
});
|
||||
|
||||
Deno.test('insert and filter events', async () => {
|
||||
const { eventsDB } = await createDB();
|
||||
await using db = await createTestDB();
|
||||
const { store } = db;
|
||||
|
||||
const event1 = await eventFixture('event-1');
|
||||
await eventsDB.event(event1);
|
||||
await store.event(event1);
|
||||
|
||||
assertEquals(await eventsDB.query([{ kinds: [1] }]), [event1]);
|
||||
assertEquals(await eventsDB.query([{ kinds: [3] }]), []);
|
||||
assertEquals(await eventsDB.query([{ since: 1691091000 }]), [event1]);
|
||||
assertEquals(await eventsDB.query([{ until: 1691091000 }]), []);
|
||||
assertEquals(await store.query([{ kinds: [1] }]), [event1]);
|
||||
assertEquals(await store.query([{ kinds: [3] }]), []);
|
||||
assertEquals(await store.query([{ since: 1691091000 }]), [event1]);
|
||||
assertEquals(await store.query([{ until: 1691091000 }]), []);
|
||||
assertEquals(
|
||||
await eventsDB.query([{ '#proxy': ['https://gleasonator.com/objects/8f6fac53-4f66-4c6e-ac7d-92e5e78c3e79'] }]),
|
||||
await store.query([{ '#proxy': ['https://gleasonator.com/objects/8f6fac53-4f66-4c6e-ac7d-92e5e78c3e79'] }]),
|
||||
[event1],
|
||||
);
|
||||
});
|
||||
|
||||
Deno.test('query events with domain search filter', async () => {
|
||||
const { eventsDB, kysely } = await createDB();
|
||||
await using db = await createTestDB();
|
||||
const { store, kysely } = db;
|
||||
|
||||
const event1 = await eventFixture('event-1');
|
||||
await eventsDB.event(event1);
|
||||
await store.event(event1);
|
||||
|
||||
assertEquals(await eventsDB.query([{}]), [event1]);
|
||||
assertEquals(await eventsDB.query([{ search: 'domain:localhost:4036' }]), []);
|
||||
assertEquals(await eventsDB.query([{ search: '' }]), [event1]);
|
||||
assertEquals(await store.query([{}]), [event1]);
|
||||
assertEquals(await store.query([{ search: 'domain:localhost:4036' }]), []);
|
||||
assertEquals(await store.query([{ search: '' }]), [event1]);
|
||||
|
||||
await kysely
|
||||
.insertInto('pubkey_domains')
|
||||
.values({ pubkey: event1.pubkey, domain: 'localhost:4036', last_updated_at: event1.created_at })
|
||||
.execute();
|
||||
|
||||
assertEquals(await eventsDB.query([{ kinds: [1], search: 'domain:localhost:4036' }]), [event1]);
|
||||
assertEquals(await eventsDB.query([{ kinds: [1], search: 'domain:example.com' }]), []);
|
||||
assertEquals(await store.query([{ kinds: [1], search: 'domain:localhost:4036' }]), [event1]);
|
||||
assertEquals(await store.query([{ kinds: [1], search: 'domain:example.com' }]), []);
|
||||
});
|
||||
|
||||
Deno.test('delete events', async () => {
|
||||
const { eventsDB } = await createDB();
|
||||
await using db = await createTestDB();
|
||||
const { store } = db;
|
||||
|
||||
const [one, two] = [
|
||||
{ id: '1', kind: 1, pubkey: 'abc', content: 'hello world', created_at: 1, sig: '', tags: [] },
|
||||
{ id: '2', kind: 1, pubkey: 'abc', content: 'yolo fam', created_at: 2, sig: '', tags: [] },
|
||||
];
|
||||
|
||||
await eventsDB.event(one);
|
||||
await eventsDB.event(two);
|
||||
await store.event(one);
|
||||
await store.event(two);
|
||||
|
||||
// Sanity check
|
||||
assertEquals(await eventsDB.query([{ kinds: [1] }]), [two, one]);
|
||||
assertEquals(await store.query([{ kinds: [1] }]), [two, one]);
|
||||
|
||||
await eventsDB.event({
|
||||
await store.event({
|
||||
kind: 5,
|
||||
pubkey: one.pubkey,
|
||||
tags: [['e', one.id]],
|
||||
|
|
@ -91,19 +79,20 @@ Deno.test('delete events', async () => {
|
|||
sig: '',
|
||||
});
|
||||
|
||||
assertEquals(await eventsDB.query([{ kinds: [1] }]), [two]);
|
||||
assertEquals(await store.query([{ kinds: [1] }]), [two]);
|
||||
});
|
||||
|
||||
Deno.test("user cannot delete another user's event", async () => {
|
||||
const { eventsDB } = await createDB();
|
||||
await using db = await createTestDB();
|
||||
const { store } = db;
|
||||
|
||||
const event = { id: '1', kind: 1, pubkey: 'abc', content: 'hello world', created_at: 1, sig: '', tags: [] };
|
||||
await eventsDB.event(event);
|
||||
await store.event(event);
|
||||
|
||||
// Sanity check
|
||||
assertEquals(await eventsDB.query([{ kinds: [1] }]), [event]);
|
||||
assertEquals(await store.query([{ kinds: [1] }]), [event]);
|
||||
|
||||
await eventsDB.event({
|
||||
await store.event({
|
||||
kind: 5,
|
||||
pubkey: 'def', // different pubkey
|
||||
tags: [['e', event.id]],
|
||||
|
|
@ -113,24 +102,25 @@ Deno.test("user cannot delete another user's event", async () => {
|
|||
sig: '',
|
||||
});
|
||||
|
||||
assertEquals(await eventsDB.query([{ kinds: [1] }]), [event]);
|
||||
assertEquals(await store.query([{ kinds: [1] }]), [event]);
|
||||
});
|
||||
|
||||
Deno.test('admin can delete any event', async () => {
|
||||
const { eventsDB } = await createDB();
|
||||
await using db = await createTestDB();
|
||||
const { store } = db;
|
||||
|
||||
const [one, two] = [
|
||||
{ id: '1', kind: 1, pubkey: 'abc', content: 'hello world', created_at: 1, sig: '', tags: [] },
|
||||
{ id: '2', kind: 1, pubkey: 'abc', content: 'yolo fam', created_at: 2, sig: '', tags: [] },
|
||||
];
|
||||
|
||||
await eventsDB.event(one);
|
||||
await eventsDB.event(two);
|
||||
await store.event(one);
|
||||
await store.event(two);
|
||||
|
||||
// Sanity check
|
||||
assertEquals(await eventsDB.query([{ kinds: [1] }]), [two, one]);
|
||||
assertEquals(await store.query([{ kinds: [1] }]), [two, one]);
|
||||
|
||||
await eventsDB.event({
|
||||
await store.event({
|
||||
kind: 5,
|
||||
pubkey: Conf.pubkey, // Admin pubkey
|
||||
tags: [['e', one.id]],
|
||||
|
|
@ -140,83 +130,89 @@ Deno.test('admin can delete any event', async () => {
|
|||
sig: '',
|
||||
});
|
||||
|
||||
assertEquals(await eventsDB.query([{ kinds: [1] }]), [two]);
|
||||
assertEquals(await store.query([{ kinds: [1] }]), [two]);
|
||||
});
|
||||
|
||||
Deno.test('throws a RelayError when inserting an event deleted by the admin', async () => {
|
||||
const { eventsDB } = await createDB();
|
||||
await using db = await createTestDB();
|
||||
const { store } = db;
|
||||
|
||||
const event = genEvent();
|
||||
await eventsDB.event(event);
|
||||
await store.event(event);
|
||||
|
||||
const deletion = genEvent({ kind: 5, tags: [['e', event.id]] }, Conf.seckey);
|
||||
await eventsDB.event(deletion);
|
||||
await store.event(deletion);
|
||||
|
||||
await assertRejects(
|
||||
() => eventsDB.event(event),
|
||||
() => store.event(event),
|
||||
RelayError,
|
||||
'event deleted by admin',
|
||||
);
|
||||
});
|
||||
|
||||
Deno.test('throws a RelayError when inserting an event deleted by a user', async () => {
|
||||
const { eventsDB } = await createDB();
|
||||
await using db = await createTestDB();
|
||||
const { store } = db;
|
||||
|
||||
const sk = generateSecretKey();
|
||||
|
||||
const event = genEvent({}, sk);
|
||||
await eventsDB.event(event);
|
||||
await store.event(event);
|
||||
|
||||
const deletion = genEvent({ kind: 5, tags: [['e', event.id]] }, sk);
|
||||
await eventsDB.event(deletion);
|
||||
await store.event(deletion);
|
||||
|
||||
await assertRejects(
|
||||
() => eventsDB.event(event),
|
||||
() => store.event(event),
|
||||
RelayError,
|
||||
'event deleted by user',
|
||||
);
|
||||
});
|
||||
|
||||
Deno.test('inserting replaceable events', async () => {
|
||||
const { eventsDB } = await createDB();
|
||||
await using db = await createTestDB();
|
||||
const { store } = db;
|
||||
|
||||
const event = await eventFixture('event-0');
|
||||
await eventsDB.event(event);
|
||||
await store.event(event);
|
||||
|
||||
const olderEvent = { ...event, id: '123', created_at: event.created_at - 1 };
|
||||
await eventsDB.event(olderEvent);
|
||||
assertEquals(await eventsDB.query([{ kinds: [0], authors: [event.pubkey] }]), [event]);
|
||||
await store.event(olderEvent);
|
||||
assertEquals(await store.query([{ kinds: [0], authors: [event.pubkey] }]), [event]);
|
||||
|
||||
const newerEvent = { ...event, id: '123', created_at: event.created_at + 1 };
|
||||
await eventsDB.event(newerEvent);
|
||||
assertEquals(await eventsDB.query([{ kinds: [0] }]), [newerEvent]);
|
||||
await store.event(newerEvent);
|
||||
assertEquals(await store.query([{ kinds: [0] }]), [newerEvent]);
|
||||
});
|
||||
|
||||
Deno.test("throws a RelayError when querying an event with a large 'since'", async () => {
|
||||
const { eventsDB } = await createDB();
|
||||
await using db = await createTestDB();
|
||||
const { store } = db;
|
||||
|
||||
await assertRejects(
|
||||
() => eventsDB.query([{ since: 33333333333333 }]),
|
||||
() => store.query([{ since: 33333333333333 }]),
|
||||
RelayError,
|
||||
'since filter too far into the future',
|
||||
);
|
||||
});
|
||||
|
||||
Deno.test("throws a RelayError when querying an event with a large 'until'", async () => {
|
||||
const { eventsDB } = await createDB();
|
||||
await using db = await createTestDB();
|
||||
const { store } = db;
|
||||
|
||||
await assertRejects(
|
||||
() => eventsDB.query([{ until: 66666666666666 }]),
|
||||
() => store.query([{ until: 66666666666666 }]),
|
||||
RelayError,
|
||||
'until filter too far into the future',
|
||||
);
|
||||
});
|
||||
|
||||
Deno.test("throws a RelayError when querying an event with a large 'kind'", async () => {
|
||||
const { eventsDB } = await createDB();
|
||||
await using db = await createTestDB();
|
||||
const { store } = db;
|
||||
|
||||
await assertRejects(
|
||||
() => eventsDB.query([{ kinds: [99999999999999] }]),
|
||||
() => store.query([{ kinds: [99999999999999] }]),
|
||||
RelayError,
|
||||
'kind filter too far into the future',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -3,21 +3,23 @@ import { assertEquals } from '@std/assert';
|
|||
|
||||
import { DittoEvent } from '@/interfaces/DittoEvent.ts';
|
||||
import { hydrateEvents } from '@/storages/hydrate.ts';
|
||||
import { eventFixture } from '@/test.ts';
|
||||
import { createTestDB, eventFixture } from '@/test.ts';
|
||||
|
||||
Deno.test('hydrateEvents(): author --- WITHOUT stats', async () => {
|
||||
const db = new MockRelay();
|
||||
const relay = new MockRelay();
|
||||
await using db = await createTestDB();
|
||||
|
||||
const event0 = await eventFixture('event-0');
|
||||
const event1 = await eventFixture('event-1');
|
||||
|
||||
// Save events to database
|
||||
await db.event(event0);
|
||||
await db.event(event1);
|
||||
await relay.event(event0);
|
||||
await relay.event(event1);
|
||||
|
||||
await hydrateEvents({
|
||||
events: [event1],
|
||||
store: db,
|
||||
store: relay,
|
||||
kysely: db.kysely,
|
||||
});
|
||||
|
||||
const expectedEvent = { ...event1, author: event0 };
|
||||
|
|
@ -25,7 +27,8 @@ Deno.test('hydrateEvents(): author --- WITHOUT stats', async () => {
|
|||
});
|
||||
|
||||
Deno.test('hydrateEvents(): repost --- WITHOUT stats', async () => {
|
||||
const db = new MockRelay();
|
||||
const relay = new MockRelay();
|
||||
await using db = await createTestDB();
|
||||
|
||||
const event0madePost = await eventFixture('event-0-the-one-who-post-and-users-repost');
|
||||
const event0madeRepost = await eventFixture('event-0-the-one-who-repost');
|
||||
|
|
@ -33,14 +36,15 @@ Deno.test('hydrateEvents(): repost --- WITHOUT stats', async () => {
|
|||
const event6 = await eventFixture('event-6');
|
||||
|
||||
// Save events to database
|
||||
await db.event(event0madePost);
|
||||
await db.event(event0madeRepost);
|
||||
await db.event(event1reposted);
|
||||
await db.event(event6);
|
||||
await relay.event(event0madePost);
|
||||
await relay.event(event0madeRepost);
|
||||
await relay.event(event1reposted);
|
||||
await relay.event(event6);
|
||||
|
||||
await hydrateEvents({
|
||||
events: [event6],
|
||||
store: db,
|
||||
store: relay,
|
||||
kysely: db.kysely,
|
||||
});
|
||||
|
||||
const expectedEvent6 = {
|
||||
|
|
@ -52,7 +56,8 @@ Deno.test('hydrateEvents(): repost --- WITHOUT stats', async () => {
|
|||
});
|
||||
|
||||
Deno.test('hydrateEvents(): quote repost --- WITHOUT stats', async () => {
|
||||
const db = new MockRelay();
|
||||
const relay = new MockRelay();
|
||||
await using db = await createTestDB();
|
||||
|
||||
const event0madeQuoteRepost = await eventFixture('event-0-the-one-who-quote-repost');
|
||||
const event0 = await eventFixture('event-0');
|
||||
|
|
@ -60,14 +65,15 @@ Deno.test('hydrateEvents(): quote repost --- WITHOUT stats', async () => {
|
|||
const event1willBeQuoteReposted = await eventFixture('event-1-that-will-be-quote-reposted');
|
||||
|
||||
// Save events to database
|
||||
await db.event(event0madeQuoteRepost);
|
||||
await db.event(event0);
|
||||
await db.event(event1quoteRepost);
|
||||
await db.event(event1willBeQuoteReposted);
|
||||
await relay.event(event0madeQuoteRepost);
|
||||
await relay.event(event0);
|
||||
await relay.event(event1quoteRepost);
|
||||
await relay.event(event1willBeQuoteReposted);
|
||||
|
||||
await hydrateEvents({
|
||||
events: [event1quoteRepost],
|
||||
store: db,
|
||||
store: relay,
|
||||
kysely: db.kysely,
|
||||
});
|
||||
|
||||
const expectedEvent1quoteRepost = {
|
||||
|
|
@ -80,7 +86,8 @@ Deno.test('hydrateEvents(): quote repost --- WITHOUT stats', async () => {
|
|||
});
|
||||
|
||||
Deno.test('hydrateEvents(): repost of quote repost --- WITHOUT stats', async () => {
|
||||
const db = new MockRelay();
|
||||
const relay = new MockRelay();
|
||||
await using db = await createTestDB();
|
||||
|
||||
const author = await eventFixture('event-0-makes-repost-with-quote-repost');
|
||||
const event1 = await eventFixture('event-1-will-be-reposted-with-quote-repost');
|
||||
|
|
@ -88,14 +95,15 @@ Deno.test('hydrateEvents(): repost of quote repost --- WITHOUT stats', async ()
|
|||
const event1quote = await eventFixture('event-1-quote-repost-will-be-reposted');
|
||||
|
||||
// Save events to database
|
||||
await db.event(author);
|
||||
await db.event(event1);
|
||||
await db.event(event1quote);
|
||||
await db.event(event6);
|
||||
await relay.event(author);
|
||||
await relay.event(event1);
|
||||
await relay.event(event1quote);
|
||||
await relay.event(event6);
|
||||
|
||||
await hydrateEvents({
|
||||
events: [event6],
|
||||
store: db,
|
||||
store: relay,
|
||||
kysely: db.kysely,
|
||||
});
|
||||
|
||||
const expectedEvent6 = {
|
||||
|
|
@ -107,7 +115,8 @@ Deno.test('hydrateEvents(): repost of quote repost --- WITHOUT stats', async ()
|
|||
});
|
||||
|
||||
Deno.test('hydrateEvents(): report pubkey and post // kind 1984 --- WITHOUT stats', async () => {
|
||||
const db = new MockRelay();
|
||||
const relay = new MockRelay();
|
||||
await using db = await createTestDB();
|
||||
|
||||
const authorDictator = await eventFixture('kind-0-dictator');
|
||||
const authorVictim = await eventFixture('kind-0-george-orwell');
|
||||
|
|
@ -115,14 +124,15 @@ Deno.test('hydrateEvents(): report pubkey and post // kind 1984 --- WITHOUT stat
|
|||
const event1 = await eventFixture('kind-1-author-george-orwell');
|
||||
|
||||
// Save events to database
|
||||
await db.event(authorDictator);
|
||||
await db.event(authorVictim);
|
||||
await db.event(reportEvent);
|
||||
await db.event(event1);
|
||||
await relay.event(authorDictator);
|
||||
await relay.event(authorVictim);
|
||||
await relay.event(reportEvent);
|
||||
await relay.event(event1);
|
||||
|
||||
await hydrateEvents({
|
||||
events: [reportEvent],
|
||||
store: db,
|
||||
store: relay,
|
||||
kysely: db.kysely,
|
||||
});
|
||||
|
||||
const expectedEvent: DittoEvent = {
|
||||
|
|
|
|||
|
|
@ -7,16 +7,18 @@ import { Conf } from '@/config.ts';
|
|||
import { type DittoEvent } from '@/interfaces/DittoEvent.ts';
|
||||
import { findQuoteTag } from '@/utils/tags.ts';
|
||||
import { findQuoteInContent } from '@/utils/note.ts';
|
||||
import { Kysely } from 'kysely';
|
||||
|
||||
interface HydrateOpts {
|
||||
events: DittoEvent[];
|
||||
store: NStore;
|
||||
signal?: AbortSignal;
|
||||
kysely?: Kysely<DittoTables>;
|
||||
}
|
||||
|
||||
/** Hydrate events using the provided storage. */
|
||||
async function hydrateEvents(opts: HydrateOpts): Promise<DittoEvent[]> {
|
||||
const { events, store, signal } = opts;
|
||||
const { events, store, signal, kysely = await DittoDB.getInstance() } = opts;
|
||||
|
||||
if (!events.length) {
|
||||
return events;
|
||||
|
|
@ -57,8 +59,8 @@ async function hydrateEvents(opts: HydrateOpts): Promise<DittoEvent[]> {
|
|||
}
|
||||
|
||||
const stats = {
|
||||
authors: await gatherAuthorStats(cache),
|
||||
events: await gatherEventStats(cache),
|
||||
authors: await gatherAuthorStats(cache, kysely),
|
||||
events: await gatherEventStats(cache, kysely),
|
||||
};
|
||||
|
||||
// Dedupe events.
|
||||
|
|
@ -276,7 +278,10 @@ function gatherReportedProfiles({ events, store, signal }: HydrateOpts): Promise
|
|||
}
|
||||
|
||||
/** Collect author stats from the events. */
|
||||
async function gatherAuthorStats(events: DittoEvent[]): Promise<DittoTables['author_stats'][]> {
|
||||
async function gatherAuthorStats(
|
||||
events: DittoEvent[],
|
||||
kysely: Kysely<DittoTables>,
|
||||
): Promise<DittoTables['author_stats'][]> {
|
||||
const pubkeys = new Set<string>(
|
||||
events
|
||||
.filter((event) => event.kind === 0)
|
||||
|
|
@ -287,8 +292,6 @@ async function gatherAuthorStats(events: DittoEvent[]): Promise<DittoTables['aut
|
|||
return Promise.resolve([]);
|
||||
}
|
||||
|
||||
const kysely = await DittoDB.getInstance();
|
||||
|
||||
const rows = await kysely
|
||||
.selectFrom('author_stats')
|
||||
.selectAll()
|
||||
|
|
@ -304,7 +307,10 @@ async function gatherAuthorStats(events: DittoEvent[]): Promise<DittoTables['aut
|
|||
}
|
||||
|
||||
/** Collect event stats from the events. */
|
||||
async function gatherEventStats(events: DittoEvent[]): Promise<DittoTables['event_stats'][]> {
|
||||
async function gatherEventStats(
|
||||
events: DittoEvent[],
|
||||
kysely: Kysely<DittoTables>,
|
||||
): Promise<DittoTables['event_stats'][]> {
|
||||
const ids = new Set<string>(
|
||||
events
|
||||
.filter((event) => event.kind === 1)
|
||||
|
|
@ -315,8 +321,6 @@ async function gatherEventStats(events: DittoEvent[]): Promise<DittoTables['even
|
|||
return Promise.resolve([]);
|
||||
}
|
||||
|
||||
const kysely = await DittoDB.getInstance();
|
||||
|
||||
const rows = await kysely
|
||||
.selectFrom('event_stats')
|
||||
.selectAll()
|
||||
|
|
|
|||
95
src/test.ts
95
src/test.ts
|
|
@ -2,13 +2,19 @@ import fs from 'node:fs/promises';
|
|||
import path from 'node:path';
|
||||
|
||||
import { Database as Sqlite } from '@db/sqlite';
|
||||
import { NDatabase, NostrEvent } from '@nostrify/nostrify';
|
||||
import { DenoSqlite3Dialect } from '@soapbox/kysely-deno-sqlite';
|
||||
import { FileMigrationProvider, Kysely, Migrator } from 'kysely';
|
||||
import { finalizeEvent, generateSecretKey } from 'nostr-tools';
|
||||
import { NDatabase, NostrEvent } from '@nostrify/nostrify';
|
||||
import { FileMigrationProvider, Kysely, Migrator } from 'kysely';
|
||||
import postgres from 'postgres';
|
||||
import { PostgresJSDialect, PostgresJSDialectConfig } from 'kysely-postgres-js';
|
||||
|
||||
import { DittoDB } from '@/db/DittoDB.ts';
|
||||
import { DittoTables } from '@/db/DittoTables.ts';
|
||||
import { purifyEvent } from '@/storages/hydrate.ts';
|
||||
import { KyselyLogger } from '@/db/KyselyLogger.ts';
|
||||
import { EventsDB } from '@/storages/EventsDB.ts';
|
||||
import { Conf } from '@/config.ts';
|
||||
|
||||
/** Import an event fixture by name in tests. */
|
||||
export async function eventFixture(name: string): Promise<NostrEvent> {
|
||||
|
|
@ -63,6 +69,91 @@ export async function getTestDB() {
|
|||
};
|
||||
}
|
||||
|
||||
/** Create an database for testing. */
|
||||
export const createTestDB = async (databaseUrl?: string) => {
|
||||
databaseUrl ??= Deno.env.get('DATABASE_URL') ?? 'sqlite://:memory:';
|
||||
|
||||
let dialect: 'sqlite' | 'postgres' = (() => {
|
||||
const protocol = databaseUrl.split(':')[0];
|
||||
switch (protocol) {
|
||||
case 'sqlite':
|
||||
return 'sqlite';
|
||||
case 'postgres':
|
||||
return protocol;
|
||||
case 'postgresql':
|
||||
return 'postgres';
|
||||
default:
|
||||
throw new Error(`Unsupported protocol: ${protocol}`);
|
||||
}
|
||||
})();
|
||||
|
||||
const allowToUseDATABASE_URL = Deno.env.get('ALLOW_TO_USE_DATABASE_URL')?.toLowerCase() ?? '';
|
||||
if (allowToUseDATABASE_URL !== 'true' && dialect === 'postgres') {
|
||||
console.warn(
|
||||
'%cRunning tests with sqlite, if you meant to use Postgres, run again with ALLOW_TO_USE_DATABASE_URL environment variable set to true',
|
||||
'color: yellow;',
|
||||
);
|
||||
dialect = 'sqlite';
|
||||
}
|
||||
|
||||
console.warn(`Using: ${dialect}`);
|
||||
|
||||
let kysely: Kysely<DittoTables>;
|
||||
|
||||
if (dialect === 'sqlite') {
|
||||
// migration 021_pgfts_index.ts calls 'Conf.db.dialect',
|
||||
// and this calls the DATABASE_URL environment variable.
|
||||
// The following line ensures to NOT use the DATABASE_URL that may exist in an .env file.
|
||||
Deno.env.set('DATABASE_URL', 'sqlite://:memory:');
|
||||
|
||||
kysely = new Kysely<DittoTables>({
|
||||
dialect: new DenoSqlite3Dialect({
|
||||
database: new Sqlite(':memory:'),
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
kysely = new Kysely({
|
||||
dialect: new PostgresJSDialect({
|
||||
postgres: postgres(Conf.databaseUrl, {
|
||||
max: Conf.pg.poolSize,
|
||||
}) as unknown as PostgresJSDialectConfig['postgres'],
|
||||
}),
|
||||
log: KyselyLogger,
|
||||
});
|
||||
}
|
||||
await DittoDB.migrate(kysely);
|
||||
|
||||
const store = new EventsDB(kysely);
|
||||
|
||||
return {
|
||||
store,
|
||||
kysely,
|
||||
[Symbol.asyncDispose]: async () => {
|
||||
if (dialect === 'postgres') {
|
||||
for (
|
||||
const table of [
|
||||
'author_stats',
|
||||
'event_stats',
|
||||
'event_zaps',
|
||||
'kysely_migration',
|
||||
'kysely_migration_lock',
|
||||
'nip46_tokens',
|
||||
'pubkey_domains',
|
||||
'unattached_media',
|
||||
'nostr_events',
|
||||
'nostr_tags',
|
||||
'nostr_pgfts',
|
||||
'event_zaps',
|
||||
]
|
||||
) {
|
||||
await kysely.schema.dropTable(table).ifExists().cascade().execute();
|
||||
}
|
||||
await kysely.destroy();
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export function sleep(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { assertEquals } from '@std/assert';
|
||||
import { generateSecretKey, getPublicKey } from 'nostr-tools';
|
||||
|
||||
import { genEvent, getTestDB } from '@/test.ts';
|
||||
import { createTestDB, genEvent } 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 getTestDB();
|
||||
await using db = await createTestDB();
|
||||
|
||||
const sk = generateSecretKey();
|
||||
const pubkey = getPublicKey(sk);
|
||||
|
|
@ -18,7 +18,7 @@ Deno.test('updateStats with kind 1 increments notes count', async () => {
|
|||
});
|
||||
|
||||
Deno.test('updateStats with kind 1 increments replies count', async () => {
|
||||
await using db = await getTestDB();
|
||||
await using db = await createTestDB();
|
||||
|
||||
const sk = generateSecretKey();
|
||||
|
||||
|
|
@ -36,7 +36,7 @@ Deno.test('updateStats with kind 1 increments replies count', async () => {
|
|||
});
|
||||
|
||||
Deno.test('updateStats with kind 5 decrements notes count', async () => {
|
||||
await using db = await getTestDB();
|
||||
await using db = await createTestDB();
|
||||
|
||||
const sk = generateSecretKey();
|
||||
const pubkey = getPublicKey(sk);
|
||||
|
|
@ -54,7 +54,7 @@ Deno.test('updateStats with kind 5 decrements notes count', async () => {
|
|||
});
|
||||
|
||||
Deno.test('updateStats with kind 3 increments followers count', async () => {
|
||||
await using db = await getTestDB();
|
||||
await using db = await createTestDB();
|
||||
|
||||
await updateStats({ ...db, event: genEvent({ kind: 3, tags: [['p', 'alex']] }) });
|
||||
await updateStats({ ...db, event: genEvent({ kind: 3, tags: [['p', 'alex']] }) });
|
||||
|
|
@ -66,7 +66,7 @@ Deno.test('updateStats with kind 3 increments followers count', async () => {
|
|||
});
|
||||
|
||||
Deno.test('updateStats with kind 3 decrements followers count', async () => {
|
||||
await using db = await getTestDB();
|
||||
await using db = await createTestDB();
|
||||
|
||||
const sk = generateSecretKey();
|
||||
const follow = genEvent({ kind: 3, tags: [['p', 'alex']], created_at: 0 }, sk);
|
||||
|
|
@ -92,7 +92,7 @@ Deno.test('getFollowDiff returns added and removed followers', () => {
|
|||
});
|
||||
|
||||
Deno.test('updateStats with kind 6 increments reposts count', async () => {
|
||||
await using db = await getTestDB();
|
||||
await using db = await createTestDB();
|
||||
|
||||
const note = genEvent({ kind: 1 });
|
||||
await updateStats({ ...db, event: note });
|
||||
|
|
@ -108,7 +108,7 @@ Deno.test('updateStats with kind 6 increments reposts count', async () => {
|
|||
});
|
||||
|
||||
Deno.test('updateStats with kind 5 decrements reposts count', async () => {
|
||||
await using db = await getTestDB();
|
||||
await using db = await createTestDB();
|
||||
|
||||
const note = genEvent({ kind: 1 });
|
||||
await updateStats({ ...db, event: note });
|
||||
|
|
@ -127,7 +127,7 @@ Deno.test('updateStats with kind 5 decrements reposts count', async () => {
|
|||
});
|
||||
|
||||
Deno.test('updateStats with kind 7 increments reactions count', async () => {
|
||||
await using db = await getTestDB();
|
||||
await using db = await createTestDB();
|
||||
|
||||
const note = genEvent({ kind: 1 });
|
||||
await updateStats({ ...db, event: note });
|
||||
|
|
@ -143,7 +143,7 @@ Deno.test('updateStats with kind 7 increments reactions count', async () => {
|
|||
});
|
||||
|
||||
Deno.test('updateStats with kind 5 decrements reactions count', async () => {
|
||||
await using db = await getTestDB();
|
||||
await using db = await createTestDB();
|
||||
|
||||
const note = genEvent({ kind: 1 });
|
||||
await updateStats({ ...db, event: note });
|
||||
|
|
@ -162,7 +162,7 @@ Deno.test('updateStats with kind 5 decrements reactions count', async () => {
|
|||
});
|
||||
|
||||
Deno.test('countAuthorStats counts author stats from the database', async () => {
|
||||
await using db = await getTestDB();
|
||||
await using db = await createTestDB();
|
||||
|
||||
const sk = generateSecretKey();
|
||||
const pubkey = getPublicKey(sk);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue