diff --git a/packages/pglite-postgis/tests/postgis.test.ts b/packages/pglite-postgis/tests/postgis.test.ts index d3f4c805b..e578889d7 100644 --- a/packages/pglite-postgis/tests/postgis.test.ts +++ b/packages/pglite-postgis/tests/postgis.test.ts @@ -1,16 +1,31 @@ -import { describe, it, expect } from 'vitest' +import { describe, it, expect, beforeEach, afterEach } from 'vitest' import { PGlite } from '@electric-sql/pglite' import { postgis } from '../src/index.js' describe(`postgis`, () => { - it('basic', async () => { - const pg = new PGlite({ - extensions: { - postgis, - }, - }) - + let pg: PGlite + let dataDirArchive: File | Blob + beforeEach(async () => { + if (!dataDirArchive) { + pg = await PGlite.create({ + extensions: { postgis }, + }) + dataDirArchive = await pg.dumpDataDir('gzip') + } else { + pg = await PGlite.create({ + extensions: { postgis }, + loadDataDir: dataDirArchive, + }) + } await pg.exec('CREATE EXTENSION IF NOT EXISTS postgis;') + }) + afterEach(async () => { + if (!pg.closed) { + await pg.close() + } + }) + + it('basic', async () => { await pg.exec(` CREATE TABLE vehicle_location ( time TIMESTAMPTZ NOT NULL, @@ -26,13 +41,6 @@ describe(`postgis`, () => { expect(inserted.affectedRows).toEqual(3) }), it('cities', async () => { - const pg = new PGlite({ - extensions: { - postgis, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS postgis;') await pg.exec(` CREATE TABLE cities ( id SERIAL PRIMARY KEY, @@ -62,17 +70,8 @@ WHERE ST_Within(c.location, s.geom);`) name: 'Chicago', }) }) -}) - -it('areas', async () => { - const pg = new PGlite({ - extensions: { - postgis, - }, - }) - await pg.exec('CREATE EXTENSION IF NOT EXISTS postgis;') - - const area1 = await pg.exec(` + it('areas', async () => { + const area1 = await pg.exec(` select ST_Area(geom) sqft, ST_Area(geom) * 0.3048 ^ 2 sqm from ( @@ -80,29 +79,29 @@ it('areas', async () => { 743265 2967450,743265.625 2967416,743238 2967416))' :: geometry geom ) subquery;`) - expect(area1).toEqual([ - { - rows: [ - { - sqft: 928.625, - sqm: 86.27208552, - }, - ], - fields: [ - { - name: 'sqft', - dataTypeID: 701, - }, - { - name: 'sqm', - dataTypeID: 701, - }, - ], - affectedRows: 0, - }, - ]) + expect(area1).toEqual([ + { + rows: [ + { + sqft: 928.625, + sqm: 86.27208552, + }, + ], + fields: [ + { + name: 'sqft', + dataTypeID: 701, + }, + { + name: 'sqm', + dataTypeID: 701, + }, + ], + affectedRows: 0, + }, + ]) - const area2 = await pg.exec(` + const area2 = await pg.exec(` select ST_Area(geom) sqft, ST_Area(ST_Transform(geom, 26986)) As sqm from ( @@ -115,29 +114,29 @@ it('areas', async () => { -- DROP SCHEMA postgis_test CASCADE; `) - expect(area2).toEqual([ - { - rows: [ - { - sqft: 928.625, - sqm: 86.27243061926092, - }, - ], - fields: [ - { - name: 'sqft', - dataTypeID: 701, - }, - { - name: 'sqm', - dataTypeID: 701, - }, - ], - affectedRows: 0, - }, - ]) + expect(area2).toEqual([ + { + rows: [ + { + sqft: 928.625, + sqm: 86.27243061926092, + }, + ], + fields: [ + { + name: 'sqft', + dataTypeID: 701, + }, + { + name: 'sqm', + dataTypeID: 701, + }, + ], + affectedRows: 0, + }, + ]) - const area3 = await pg.exec(` + const area3 = await pg.exec(` select ST_Area(geog) / 0.3048 ^ 2 sqft_spheroid, ST_Area(geog, false) / 0.3048 ^ 2 sqft_sphere, ST_Area(geog) sqm_spheroid @@ -149,42 +148,36 @@ it('areas', async () => { ) as subquery; `) - expect(area3).toEqual([ - { - rows: [ - { - sqft_spheroid: 928.6844047556697, - sqft_sphere: 926.609762750544, - sqm_spheroid: 86.27760440239217, - }, - ], - fields: [ - { - name: 'sqft_spheroid', - dataTypeID: 701, - }, - { - name: 'sqft_sphere', - dataTypeID: 701, - }, - { - name: 'sqm_spheroid', - dataTypeID: 701, - }, - ], - affectedRows: 0, - }, - ]) -}) - -it('ST_Polygonize', async () => { - const pg = new PGlite({ - extensions: { - postgis, - }, + expect(area3).toEqual([ + { + rows: [ + { + sqft_spheroid: 928.6844047556697, + sqft_sphere: 926.609762750544, + sqm_spheroid: 86.27760440239217, + }, + ], + fields: [ + { + name: 'sqft_spheroid', + dataTypeID: 701, + }, + { + name: 'sqft_sphere', + dataTypeID: 701, + }, + { + name: 'sqm_spheroid', + dataTypeID: 701, + }, + ], + affectedRows: 0, + }, + ]) }) - await pg.exec('CREATE EXTENSION IF NOT EXISTS postgis;') - const res = await pg.exec(` + + it('ST_Polygonize', async () => { + const res = await pg.exec(` WITH data(geom) AS (VALUES ('LINESTRING (180 40, 30 20, 20 90)'::geometry) ,('LINESTRING (180 40, 160 160)'::geometry) @@ -200,34 +193,27 @@ it('ST_Polygonize', async () => { FROM data; `) - expect(res).toEqual([ - { - rows: [ - { - st_astext: - 'GEOMETRYCOLLECTION(POLYGON((180 40,30 20,20 90,70 70,80 130,160 160,180 40),(150 80,120 130,80 60,150 80)),POLYGON((80 60,120 130,150 80,80 60)),POLYGON((80 130,70 70,20 90,20 160,70 190,80 130)),POLYGON((160 160,80 130,70 190,160 160)))', - }, - ], - fields: [ - { - name: 'st_astext', - dataTypeID: 25, - }, - ], - affectedRows: 0, - }, - ]) -}) - -it('complex1', async () => { - const pg = new PGlite({ - extensions: { - postgis, - }, + expect(res).toEqual([ + { + rows: [ + { + st_astext: + 'GEOMETRYCOLLECTION(POLYGON((180 40,30 20,20 90,70 70,80 130,160 160,180 40),(150 80,120 130,80 60,150 80)),POLYGON((80 60,120 130,150 80,80 60)),POLYGON((80 130,70 70,20 90,20 160,70 190,80 130)),POLYGON((160 160,80 130,70 190,160 160)))', + }, + ], + fields: [ + { + name: 'st_astext', + dataTypeID: 25, + }, + ], + affectedRows: 0, + }, + ]) }) - await pg.exec('CREATE EXTENSION IF NOT EXISTS postgis;') - await pg.exec(` + it('complex1', async () => { + await pg.exec(` -- Create test schema -- CREATE SCHEMA IF NOT EXISTS postgis_test; -- SET search_path TO postgis_test; @@ -240,7 +226,7 @@ it('complex1', async () => { geom GEOMETRY(Point, 4326) );`) - await pg.exec(` + await pg.exec(` CREATE TABLE rivers ( id SERIAL PRIMARY KEY, name TEXT NOT NULL, @@ -281,20 +267,15 @@ it('complex1', async () => { ORDER BY distance_km; `) -}) - -it('The coordinates in GeoJSON are not sufficiently nested', async () => { - const pg = new PGlite({ - extensions: { - postgis, - }, - debug: 1, }) - await pg.exec('CREATE EXTENSION IF NOT EXISTS postgis;') - await expect( - pg.exec( - `SELECT '#3583', ST_AsText(ST_GeomFromGeoJSON('{"type":"MultiPolygon", "coordinates":[[[139.10030364990232,35.16777444430609],5842.4224490305424]]}'));`, - ), - ).rejects.toThrow(`The 'coordinates' in GeoJSON are not sufficiently nested`) + it('The coordinates in GeoJSON are not sufficiently nested', async () => { + await expect( + pg.exec( + `SELECT '#3583', ST_AsText(ST_GeomFromGeoJSON('{"type":"MultiPolygon", "coordinates":[[[139.10030364990232,35.16777444430609],5842.4224490305424]]}'));`, + ), + ).rejects.toThrow( + `The 'coordinates' in GeoJSON are not sufficiently nested`, + ) + }) }) diff --git a/packages/pglite-tools/tests/pg_dump.test.ts b/packages/pglite-tools/tests/pg_dump.test.ts index e25a56e8a..0174a48fb 100644 --- a/packages/pglite-tools/tests/pg_dump.test.ts +++ b/packages/pglite-tools/tests/pg_dump.test.ts @@ -1,11 +1,26 @@ -import { describe, it, expect } from 'vitest' +import { describe, it, expect, beforeEach, afterEach } from 'vitest' import { PGlite } from '@electric-sql/pglite' import { pgDump } from '../dist/pg_dump.js' import * as fs from 'fs/promises' describe('pgDump', () => { + let pg: PGlite + let dataDirArchive: File | Blob + beforeEach(async () => { + if (!dataDirArchive) { + pg = await PGlite.create() + dataDirArchive = await pg.dumpDataDir('gzip') + } else { + pg = await PGlite.create() + } + }) + afterEach(async () => { + if (!pg.closed) { + await pg.close() + } + }) + it('should dump an empty database', async () => { - const pg = await PGlite.create() const dump = await pgDump({ pg }) expect(dump).toBeInstanceOf(File) @@ -16,8 +31,6 @@ describe('pgDump', () => { }) it('should dump an empty database multiple times', async () => { - const pg = await PGlite.create() - for (let i = 0; i < 5; i++) { const fileName = `dump_${i}.sql` const dump = await pgDump({ pg, fileName }) @@ -31,8 +44,6 @@ describe('pgDump', () => { }) it('should dump a database with tables and data', async () => { - const pg = await PGlite.create() - // Create test tables and insert data await pg.exec(` CREATE TABLE test1 ( @@ -63,14 +74,12 @@ describe('pgDump', () => { }) it('should respect custom filename', async () => { - const pg = await PGlite.create() const dump = await pgDump({ pg, fileName: 'custom.sql' }) expect(dump.name).toBe('custom.sql') }) it('should handle custom pg_dump arguments', async () => { - const pg = await PGlite.create() await pg.exec(` CREATE TABLE test (id SERIAL PRIMARY KEY, name TEXT); INSERT INTO test (name) VALUES ('row1'); @@ -85,20 +94,18 @@ describe('pgDump', () => { }) it('should be able to restore dumped database', async () => { - const pg1 = await PGlite.create() - // Create original database - await pg1.exec(` + await pg.exec(` CREATE TABLE test (id SERIAL PRIMARY KEY, name TEXT); INSERT INTO test (name) VALUES ('row1'), ('row2'); `) const initialSearchPath = ( - await pg1.query<{ search_path: string }>('SHOW SEARCH_PATH;') + await pg.query<{ search_path: string }>('SHOW SEARCH_PATH;') ).rows[0].search_path // Dump database - const dump = await pgDump({ pg: pg1 }) + const dump = await pgDump({ pg }) const dumpContent = await dump.text() // Create new database and restore @@ -118,8 +125,6 @@ describe('pgDump', () => { }) it('pg_dump should not change SEARCH_PATH', async () => { - const pg = await PGlite.create() - await pg.exec(`SET SEARCH_PATH = amigo;`) const initialSearchPath = await pg.query('SHOW SEARCH_PATH;') @@ -168,8 +173,6 @@ describe('pgDump', () => { }) it('param --quote-all-identifiers should work', async () => { - const pg = await PGlite.create() - // Create test tables and insert data await pg.exec(` CREATE TABLE test1 ( diff --git a/packages/pglite/tests/age.test.ts b/packages/pglite/tests/age.test.ts index fff679c72..915eb5b24 100644 --- a/packages/pglite/tests/age.test.ts +++ b/packages/pglite/tests/age.test.ts @@ -16,8 +16,17 @@ * ``` */ -import { describe, it, expect, beforeAll, afterAll } from 'vitest' +import { + describe, + it, + expect, + beforeAll, + afterAll, + beforeEach, + afterEach, +} from 'vitest' import { testEsmCjsAndDTC } from './test-utils.ts' +import { PGlite } from '../dist/index.js' await testEsmCjsAndDTC(async (importType) => { const { PGlite } = @@ -35,22 +44,39 @@ await testEsmCjsAndDTC(async (importType) => { )) as unknown as typeof import('../dist/age/index.js')) describe(`age (${importType})`, () => { - // ========================================================================= - // BASIC EXTENSION LOADING - // ========================================================================= + let pg: PGlite + let dataDirArchive: File | Blob + beforeEach(async () => { + if (!dataDirArchive) { + pg = await PGlite.create({ + extensions: { age }, + }) + dataDirArchive = await pg.dumpDataDir('gzip') + } else { + pg = await PGlite.create({ + extensions: { age }, + loadDataDir: dataDirArchive, + }) + } - it('can load extension', async () => { - const pg = new PGlite({ - extensions: { - age, - }, - }) await pg.exec(` CREATE EXTENSION IF NOT EXISTS age; LOAD 'age'; SET search_path = ag_catalog, "$user", public; `) + }) + + afterEach(async () => { + if (!pg.closed) { + await pg.close() + } + }) + // ========================================================================= + // BASIC EXTENSION LOADING + // ========================================================================= + + it('can load extension', async () => { const res = await pg.query<{ extname: string }>(` SELECT extname FROM pg_extension WHERE extname = 'age' `) @@ -65,17 +91,6 @@ await testEsmCjsAndDTC(async (importType) => { // ========================================================================= it('can create a graph', async () => { - const pg = new PGlite({ - extensions: { - age, - }, - }) - await pg.exec(` - CREATE EXTENSION IF NOT EXISTS age; - LOAD 'age'; - SET search_path = ag_catalog, "$user", public; - `) - // Create a new graph using ag_catalog.create_graph() // This creates the graph metadata and necessary internal tables await pg.exec("SELECT ag_catalog.create_graph('test_graph');") @@ -91,17 +106,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('can drop graph', async () => { - const pg = new PGlite({ - extensions: { - age, - }, - }) - await pg.exec(` - CREATE EXTENSION IF NOT EXISTS age; - LOAD 'age'; - SET search_path = ag_catalog, "$user", public; - `) - // Create and then drop a graph await pg.exec("SELECT ag_catalog.create_graph('temp_graph');") await pg.exec("SELECT ag_catalog.drop_graph('temp_graph', true);") @@ -120,17 +124,6 @@ await testEsmCjsAndDTC(async (importType) => { // ========================================================================= it('can execute cypher CREATE and MATCH', async () => { - const pg = new PGlite({ - extensions: { - age, - }, - }) - await pg.exec(` - CREATE EXTENSION IF NOT EXISTS age; - LOAD 'age'; - SET search_path = ag_catalog, "$user", public; - `) - await pg.exec("SELECT ag_catalog.create_graph('cypher_test');") // CREATE a node with a label and properties @@ -163,17 +156,6 @@ await testEsmCjsAndDTC(async (importType) => { // ========================================================================= it('can create edges between nodes', async () => { - const pg = new PGlite({ - extensions: { - age, - }, - }) - await pg.exec(` - CREATE EXTENSION IF NOT EXISTS age; - LOAD 'age'; - SET search_path = ag_catalog, "$user", public; - `) - await pg.exec("SELECT ag_catalog.create_graph('edge_test');") // Create a full path: two nodes connected by an edge @@ -206,17 +188,6 @@ await testEsmCjsAndDTC(async (importType) => { // ========================================================================= it('hooks are active - cypher syntax parses correctly', async () => { - const pg = new PGlite({ - extensions: { - age, - }, - }) - await pg.exec(` - CREATE EXTENSION IF NOT EXISTS age; - LOAD 'age'; - SET search_path = ag_catalog, "$user", public; - `) - await pg.exec("SELECT ag_catalog.create_graph('hook_test');") // This query uses Cypher-specific syntax that PostgreSQL @@ -238,17 +209,6 @@ await testEsmCjsAndDTC(async (importType) => { // ========================================================================= it('can use WHERE clause in MATCH', async () => { - const pg = new PGlite({ - extensions: { - age, - }, - }) - await pg.exec(` - CREATE EXTENSION IF NOT EXISTS age; - LOAD 'age'; - SET search_path = ag_catalog, "$user", public; - `) - await pg.exec("SELECT ag_catalog.create_graph('where_test');") // Create multiple nodes @@ -282,17 +242,6 @@ await testEsmCjsAndDTC(async (importType) => { // ========================================================================= it('EXPLAIN works on cypher queries', async () => { - const pg = new PGlite({ - extensions: { - age, - }, - }) - await pg.exec(` - CREATE EXTENSION IF NOT EXISTS age; - LOAD 'age'; - SET search_path = ag_catalog, "$user", public; - `) - await pg.exec("SELECT ag_catalog.create_graph('explain_test');") // EXPLAIN shows the query execution plan @@ -313,17 +262,6 @@ await testEsmCjsAndDTC(async (importType) => { // ========================================================================= it('handles unicode in properties', async () => { - const pg = new PGlite({ - extensions: { - age, - }, - }) - await pg.exec(` - CREATE EXTENSION IF NOT EXISTS age; - LOAD 'age'; - SET search_path = ag_catalog, "$user", public; - `) - await pg.exec("SELECT ag_catalog.create_graph('unicode_test');") // Create node with unicode properties @@ -355,17 +293,6 @@ await testEsmCjsAndDTC(async (importType) => { // ========================================================================= it('handles invalid cypher syntax gracefully', async () => { - const pg = new PGlite({ - extensions: { - age, - }, - }) - await pg.exec(` - CREATE EXTENSION IF NOT EXISTS age; - LOAD 'age'; - SET search_path = ag_catalog, "$user", public; - `) - await pg.exec("SELECT ag_catalog.create_graph('error_test');") // Invalid Cypher syntax should throw an error @@ -385,17 +312,6 @@ await testEsmCjsAndDTC(async (importType) => { // ========================================================================= it('can update node properties', async () => { - const pg = new PGlite({ - extensions: { - age, - }, - }) - await pg.exec(` - CREATE EXTENSION IF NOT EXISTS age; - LOAD 'age'; - SET search_path = ag_catalog, "$user", public; - `) - await pg.exec("SELECT ag_catalog.create_graph('update_test');") // Create a node @@ -433,17 +349,6 @@ await testEsmCjsAndDTC(async (importType) => { // ========================================================================= it('can delete nodes', async () => { - const pg = new PGlite({ - extensions: { - age, - }, - }) - await pg.exec(` - CREATE EXTENSION IF NOT EXISTS age; - LOAD 'age'; - SET search_path = ag_catalog, "$user", public; - `) - await pg.exec("SELECT ag_catalog.create_graph('delete_test');") // Create nodes @@ -480,17 +385,6 @@ await testEsmCjsAndDTC(async (importType) => { // ========================================================================= it('can use ORDER BY and LIMIT', async () => { - const pg = new PGlite({ - extensions: { - age, - }, - }) - await pg.exec(` - CREATE EXTENSION IF NOT EXISTS age; - LOAD 'age'; - SET search_path = ag_catalog, "$user", public; - `) - await pg.exec("SELECT ag_catalog.create_graph('order_test');") // Create multiple nodes with different ages diff --git a/packages/pglite/tests/basic.test.ts b/packages/pglite/tests/basic.test.ts index b845b4f1f..c5666c5d5 100644 --- a/packages/pglite/tests/basic.test.ts +++ b/packages/pglite/tests/basic.test.ts @@ -1,6 +1,7 @@ -import { describe, it, expect } from 'vitest' +import { describe, it, expect, beforeEach, afterEach } from 'vitest' import { expectToThrowAsync, testEsmCjsAndDTC } from './test-utils.ts' import { identifier } from '../dist/templating.js' +import { PGlite } from '../dist/index.js' await testEsmCjsAndDTC(async (importType) => { const { PGlite } = @@ -11,8 +12,27 @@ await testEsmCjsAndDTC(async (importType) => { )) as unknown as typeof import('../dist/index.js')) describe(`basic`, () => { + let db: PGlite + let dataDirArchive: File | Blob + + beforeEach(async () => { + if (!dataDirArchive) { + db = await PGlite.create() + dataDirArchive = await db.dumpDataDir('gzip') + } else { + db = await PGlite.create({ + loadDataDir: dataDirArchive, + }) + } + }) + + afterEach(async () => { + if (!db.closed) { + await db.close() + } + }) + it('exec', async () => { - const db = await PGlite.create() await db.exec(` CREATE TABLE IF NOT EXISTS test ( id SERIAL PRIMARY KEY, @@ -46,12 +66,9 @@ await testEsmCjsAndDTC(async (importType) => { affectedRows: 2, }, ]) - - await db.close() }) it('query', async () => { - const db = new PGlite() await db.query(` CREATE TABLE IF NOT EXISTS test ( id SERIAL PRIMARY KEY, @@ -92,7 +109,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('query templated', async () => { - const db = new PGlite() const tableName = identifier`test` await db.sql` CREATE TABLE IF NOT EXISTS ${tableName} ( @@ -133,7 +149,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('types', async () => { - const db = await PGlite.create() await db.query(` CREATE TABLE IF NOT EXISTS test ( id SERIAL PRIMARY KEY, @@ -295,6 +310,7 @@ await testEsmCjsAndDTC(async (importType) => { it('custom parser and serializer', async () => { const db = new PGlite({ + loadDataDir: dataDirArchive, serializers: { 1700: (x) => x.toString() }, parsers: { 1700: (x) => BigInt(x) }, }) @@ -331,7 +347,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('params', async () => { - const db = new PGlite() await db.query(` CREATE TABLE IF NOT EXISTS test ( id SERIAL PRIMARY KEY, @@ -365,7 +380,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('array params', async () => { - const db = new PGlite() await db.query(` CREATE TABLE IF NOT EXISTS test ( id SERIAL PRIMARY KEY, @@ -418,14 +432,12 @@ await testEsmCjsAndDTC(async (importType) => { }) it('error', async () => { - const db = await PGlite.create() await expectToThrowAsync(async () => { await db.query('SELECT * FROM test;') }, 'relation "test" does not exist') }) it('transaction', async () => { - const db = new PGlite() await db.query(` CREATE TABLE IF NOT EXISTS test ( id SERIAL PRIMARY KEY, @@ -487,7 +499,6 @@ await testEsmCjsAndDTC(async (importType) => { }) }) it('merge delete', async () => { - const db = new PGlite() await db.exec(` CREATE TABLE employees ( id SERIAL PRIMARY KEY, @@ -520,7 +531,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('copy to/from blob', async () => { - const db = new PGlite() await db.exec(` CREATE TABLE IF NOT EXISTS test ( id SERIAL PRIMARY KEY, @@ -588,7 +598,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('close', async () => { - const db = new PGlite() await db.query(` CREATE TABLE IF NOT EXISTS test ( id SERIAL PRIMARY KEY, @@ -603,8 +612,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('use same param multiple times', async () => { - const db = new PGlite() - await db.exec(` CREATE TABLE IF NOT EXISTS test ( id SERIAL PRIMARY KEY, @@ -630,8 +637,6 @@ await testEsmCjsAndDTC(async (importType) => { }) }) it('timezone', async () => { - const db = new PGlite() - const res = await db.query( `SELECT now(),* FROM pg_timezone_names WHERE name = current_setting('TIMEZONE')`, ) @@ -639,8 +644,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('default database, user and role should be "postgres"', async () => { - const db = await PGlite.create() - const databaseAndRole = await db.exec( `SELECT current_database(), current_user, current_role;`, ) @@ -664,8 +667,6 @@ await testEsmCjsAndDTC(async (importType) => { // this tests the parameter 'max_parallel_workers_per_gather=0', it('it shouldnt use parallel workers on gather', async () => { - const db = await PGlite.create() - const ROWS = 400_000 await db.exec(` @@ -704,7 +705,7 @@ await testEsmCjsAndDTC(async (importType) => { it('restores process.exitCode', async () => { const origExitCode = process.exitCode - const db = await PGlite.create() + expect(process.exitCode).toEqual(origExitCode) await db.exec(` diff --git a/packages/pglite/tests/contrib/pgcrypto.test.js b/packages/pglite/tests/contrib/pgcrypto.test.ts similarity index 71% rename from packages/pglite/tests/contrib/pgcrypto.test.js rename to packages/pglite/tests/contrib/pgcrypto.test.ts index a60d577b6..a03f558c5 100644 --- a/packages/pglite/tests/contrib/pgcrypto.test.js +++ b/packages/pglite/tests/contrib/pgcrypto.test.ts @@ -1,18 +1,34 @@ -import { describe, it, expect } from 'vitest' +import { describe, it, expect, beforeEach, afterEach } from 'vitest' import { PGlite } from '../../dist/index.js' import { pgcrypto } from '../../dist/contrib/pgcrypto.js' import * as openpgp from 'openpgp' describe('pg_pgcryptotrgm', () => { - it('digest', async () => { - const pg = new PGlite({ - extensions: { - pgcrypto, - }, - }) - + let pg: PGlite + let dataDirArchive: File | Blob + + beforeEach(async () => { + if (!dataDirArchive) { + pg = await PGlite.create({ + extensions: { pgcrypto }, + }) + dataDirArchive = await pg.dumpDataDir('gzip') + } else { + pg = await PGlite.create({ + extensions: { pgcrypto }, + loadDataDir: dataDirArchive, + }) + } await pg.exec('CREATE EXTENSION IF NOT EXISTS pgcrypto;') + }) + afterEach(async () => { + if (!pg.closed) { + await pg.close() + } + }) + + it('digest', async () => { const res = await pg.query( "SELECT encode(digest(convert_to('test', 'UTF8'), 'sha1'), 'hex') as value;", ) @@ -20,14 +36,6 @@ describe('pg_pgcryptotrgm', () => { }) it('hmac', async () => { - const pg = new PGlite({ - extensions: { - pgcrypto, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pgcrypto;') - const res = await pg.query( "SELECT encode(hmac(convert_to('test', 'UTF8'), convert_to('key', 'UTF8'), 'sha1'), 'hex') as value;", ) @@ -37,54 +45,22 @@ describe('pg_pgcryptotrgm', () => { }) it('crypt', async () => { - const pg = new PGlite({ - extensions: { - pgcrypto, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pgcrypto;') - const res = await pg.query("SELECT crypt('test', gen_salt('bf')) as value;") expect(res.rows[0].value.length).toEqual(60) }) it('gen_salt', async () => { - const pg = new PGlite({ - extensions: { - pgcrypto, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pgcrypto;') - const res = await pg.query("SELECT gen_salt('bf') as value;") expect(res.rows[0].value.length).toEqual(29) }) it('armor', async () => { - const pg = new PGlite({ - extensions: { - pgcrypto, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pgcrypto;') - const res = await pg.query("SELECT armor(digest('test', 'sha1')) as value;") expect(res.rows[0].value).toContain('-----BEGIN PGP MESSAGE-----') expect(res.rows[0].value).toContain('-----END PGP MESSAGE-----') }) it('pgp_sym_encrypt and pgp_sym_decrypt', async () => { - const pg = new PGlite({ - extensions: { - pgcrypto, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pgcrypto;') - const res = await pg.query( "SELECT pgp_sym_encrypt('test', 'key') as value;", ) @@ -97,14 +73,6 @@ describe('pg_pgcryptotrgm', () => { }) it('pgp_pub_encrypt and pgp_pub_decrypt', async () => { - const pg = new PGlite({ - extensions: { - pgcrypto, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pgcrypto;') - const { privateKey, publicKey } = await openpgp.generateKey({ type: 'rsa', rsaBits: 2048, @@ -128,14 +96,6 @@ FROM encrypted; }) it('pgp_key_id', async () => { - const pg = new PGlite({ - extensions: { - pgcrypto, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pgcrypto;') - const { publicKey } = await openpgp.generateKey({ type: 'rsa', rsaBits: 2048, @@ -152,14 +112,6 @@ FROM encrypted; }) it('pgp_armor_headers', async () => { - const pg = new PGlite({ - extensions: { - pgcrypto, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pgcrypto;') - // Create armored data with headers const res = await pg.query( `SELECT armor(digest('test', 'sha1'), ARRAY['key1'], ARRAY['value1']) as armored;`, @@ -173,14 +125,6 @@ FROM encrypted; }) it('encrypt and decrypt', async () => { - const pg = new PGlite({ - extensions: { - pgcrypto, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pgcrypto;') - const res = await pg.query( `SELECT encrypt('test data'::bytea, 'secret key'::bytea, 'aes') as encrypted;`, ) @@ -194,14 +138,6 @@ FROM encrypted; }) it('encrypt_iv and decrypt_iv', async () => { - const pg = new PGlite({ - extensions: { - pgcrypto, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pgcrypto;') - // AES block size is 16 bytes, so IV must be 16 bytes const iv = '1234567890123456' @@ -218,14 +154,6 @@ FROM encrypted; }) it('gen_random_bytes', async () => { - const pg = new PGlite({ - extensions: { - pgcrypto, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pgcrypto;') - const res = await pg.query( `SELECT length(gen_random_bytes(32)) as len, encode(gen_random_bytes(16), 'hex') as bytes;`, ) @@ -235,14 +163,6 @@ FROM encrypted; }) it('gen_random_uuid', async () => { - const pg = new PGlite({ - extensions: { - pgcrypto, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pgcrypto;') - const res = await pg.query(`SELECT gen_random_uuid() as uuid;`) // UUID format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx expect(res.rows[0].uuid).toMatch( diff --git a/packages/pglite/tests/contrib/uuid_ossp.test.js b/packages/pglite/tests/contrib/uuid_ossp.test.ts similarity index 58% rename from packages/pglite/tests/contrib/uuid_ossp.test.js rename to packages/pglite/tests/contrib/uuid_ossp.test.ts index 6ca486692..b644d68e0 100644 --- a/packages/pglite/tests/contrib/uuid_ossp.test.js +++ b/packages/pglite/tests/contrib/uuid_ossp.test.ts @@ -1,31 +1,37 @@ -import { describe, it, expect } from 'vitest' +import { describe, it, expect, beforeEach, afterEach } from 'vitest' import { PGlite } from '../../dist/index.js' import { uuid_ossp } from '../../dist/contrib/uuid_ossp.js' describe('uuid_ossp', () => { - it('uuid_generate_v1', async () => { - const pg = new PGlite({ - extensions: { - uuid_ossp, - }, - }) - + let pg: PGlite + let dataDirArchive: File | Blob + beforeEach(async () => { + if (!dataDirArchive) { + pg = await PGlite.create({ + extensions: { uuid_ossp }, + }) + dataDirArchive = await pg.dumpDataDir('gzip') + } else { + pg = await PGlite.create({ + extensions: { uuid_ossp }, + loadDataDir: dataDirArchive, + }) + } await pg.exec('CREATE EXTENSION IF NOT EXISTS "uuid-ossp";') + }) + afterEach(async () => { + if (!pg.closed) { + await pg.close() + } + }) + it('uuid_generate_v1', async () => { const res = await pg.query('SELECT uuid_generate_v1() as value;') expect(res.rows[0].value.length).toBe(36) }) it('uuid_generate_v3', async () => { - const pg = new PGlite({ - extensions: { - uuid_ossp, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS "uuid-ossp";') - const res = await pg.query( "SELECT uuid_generate_v3(uuid_ns_dns(), 'www.example.com') as value;", ) @@ -34,28 +40,12 @@ describe('uuid_ossp', () => { }) it('uuid_generate_v4', async () => { - const pg = new PGlite({ - extensions: { - uuid_ossp, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS "uuid-ossp";') - const res = await pg.query('SELECT uuid_generate_v4() as value;') expect(res.rows[0].value.length).toBe(36) }) it('uuid_generate_v5', async () => { - const pg = new PGlite({ - extensions: { - uuid_ossp, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS "uuid-ossp";') - const res = await pg.query( "SELECT uuid_generate_v5(uuid_ns_dns(), 'www.example.com') as value;", ) @@ -64,42 +54,18 @@ describe('uuid_ossp', () => { }) it('uuid_nil', async () => { - const pg = new PGlite({ - extensions: { - uuid_ossp, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS "uuid-ossp";') - const res = await pg.query('SELECT uuid_nil() as value;') expect(res.rows[0].value).toBe('00000000-0000-0000-0000-000000000000') }) it('uuid_ns_dns', async () => { - const pg = new PGlite({ - extensions: { - uuid_ossp, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS "uuid-ossp";') - const res = await pg.query('SELECT uuid_ns_dns() as value;') expect(res.rows[0].value).toBe('6ba7b810-9dad-11d1-80b4-00c04fd430c8') }) it('uuid_ns_oid', async () => { - const pg = new PGlite({ - extensions: { - uuid_ossp, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS "uuid-ossp";') - const res = await pg.query('SELECT uuid_ns_oid() as value;') expect(res.rows[0].value).toBe('6ba7b812-9dad-11d1-80b4-00c04fd430c8') diff --git a/packages/pglite/tests/describe-query.test.ts b/packages/pglite/tests/describe-query.test.ts index 1a28103b3..c4336dce6 100644 --- a/packages/pglite/tests/describe-query.test.ts +++ b/packages/pglite/tests/describe-query.test.ts @@ -1,9 +1,24 @@ -import { test, expect } from 'vitest' +import { test, expect, afterEach, beforeEach } from 'vitest' import { PGlite } from '../dist/index.js' +let pg: PGlite +let dataDirArchive: File | Blob +beforeEach(async () => { + if (!dataDirArchive) { + pg = await PGlite.create() + dataDirArchive = await pg.dumpDataDir('gzip') + } else { + pg = await PGlite.create() + } +}) +afterEach(async () => { + if (!pg.closed) { + await pg.close() + } +}) + test('describeQuery returns parameter and result types', async () => { - const db = await PGlite.create() - await db.query(` + await pg.query(` CREATE TABLE users ( id INTEGER PRIMARY KEY, name TEXT, @@ -12,7 +27,7 @@ test('describeQuery returns parameter and result types', async () => { ) `) - const description = await db.describeQuery( + const description = await pg.describeQuery( 'SELECT name, age FROM users WHERE id = $1 AND active = $2', ) @@ -34,9 +49,7 @@ test('describeQuery returns parameter and result types', async () => { }) test('describeQuery handles queries with no parameters or results', async () => { - const db = await PGlite.create() - - const description = await db.describeQuery('SELECT 1') + const description = await pg.describeQuery('SELECT 1') expect(description.queryParams).toHaveLength(0) expect(description.resultFields).toHaveLength(1) @@ -44,15 +57,14 @@ test('describeQuery handles queries with no parameters or results', async () => }) test('describeQuery handles INSERT queries', async () => { - const db = await PGlite.create() - await db.query(` + await pg.query(` CREATE TABLE test ( id INTEGER PRIMARY KEY, value TEXT ) `) - const description = await db.describeQuery( + const description = await pg.describeQuery( 'INSERT INTO test (id, value) VALUES ($1, $2)', ) @@ -63,9 +75,7 @@ test('describeQuery handles INSERT queries', async () => { }) test('describeQuery handles invalid queries', async () => { - const db = await PGlite.create() - await expect( - db.describeQuery('SELECT * FROM nonexistent_table'), + pg.describeQuery('SELECT * FROM nonexistent_table'), ).rejects.toThrow(/relation "nonexistent_table" does not exist/) }) diff --git a/packages/pglite/tests/live.test.ts b/packages/pglite/tests/live.test.ts index f955ee9d7..afd3f3cf3 100644 --- a/packages/pglite/tests/live.test.ts +++ b/packages/pglite/tests/live.test.ts @@ -1,5 +1,6 @@ -import { describe, it, expect } from 'vitest' +import { describe, it, expect, beforeEach, afterEach } from 'vitest' import { testEsmCjsAndDTC } from './test-utils.ts' +import { PGliteWithLive } from '../dist/live/index.js' await testEsmCjsAndDTC(async (importType) => { const { PGlite } = ( @@ -14,11 +15,29 @@ await testEsmCjsAndDTC(async (importType) => { : await import('../dist/live/index.cjs') describe(`live`, () => { - it('basic live query', async () => { - const db = await PGlite.create({ - extensions: { live }, - }) + let db: PGliteWithLive + let dataDirArchive: File | Blob + beforeEach(async () => { + if (!dataDirArchive) { + db = await PGlite.create({ + extensions: { live }, + }) + dataDirArchive = await db.dumpDataDir('gzip') + } else { + db = await PGlite.create({ + extensions: { live }, + loadDataDir: dataDirArchive, + }) + } + }) + + afterEach(async () => { + if (!db.closed) { + await db.close() + } + }) + it('basic live query', async () => { await db.exec(` CREATE TABLE IF NOT EXISTS testTable ( id SERIAL PRIMARY KEY, @@ -110,10 +129,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('live query on view', async () => { - const db = await PGlite.create({ - extensions: { live }, - }) - await db.exec(` CREATE TABLE IF NOT EXISTS testTable ( id SERIAL PRIMARY KEY, @@ -220,10 +235,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('live query with params', async () => { - const db = await PGlite.create({ - extensions: { live }, - }) - await db.exec(` CREATE TABLE IF NOT EXISTS testTable ( id SERIAL PRIMARY KEY, @@ -305,10 +316,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('incremental query unordered', async () => { - const db = await PGlite.create({ - extensions: { live }, - }) - await db.exec(` CREATE TABLE IF NOT EXISTS testTable ( id SERIAL PRIMARY KEY, @@ -354,10 +361,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('incremental query with non-integer key', async () => { - const db = await PGlite.create({ - extensions: { live }, - }) - await db.exec(` CREATE TABLE IF NOT EXISTS testTable ( id TEXT PRIMARY KEY, @@ -403,10 +406,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('basic live incremental query', async () => { - const db = await PGlite.create({ - extensions: { live }, - }) - await db.exec(` CREATE TABLE IF NOT EXISTS testTable ( id SERIAL PRIMARY KEY, @@ -499,10 +498,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('basic live incremental query with limit 1', async () => { - const db = await PGlite.create({ - extensions: { live }, - }) - await db.exec(` CREATE TABLE IF NOT EXISTS testTable ( id SERIAL PRIMARY KEY, @@ -541,10 +536,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('live incremental query on view', async () => { - const db = await PGlite.create({ - extensions: { live }, - }) - await db.exec(` CREATE TABLE IF NOT EXISTS testTable ( id SERIAL PRIMARY KEY, @@ -652,10 +643,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('live incremental query with params', async () => { - const db = await PGlite.create({ - extensions: { live }, - }) - await db.exec(` CREATE TABLE IF NOT EXISTS testTable ( id SERIAL PRIMARY KEY, @@ -738,10 +725,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('basic live changes', async () => { - const db = await PGlite.create({ - extensions: { live }, - }) - await db.exec(` CREATE TABLE IF NOT EXISTS testTable ( id SERIAL PRIMARY KEY, @@ -913,10 +896,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('subscribe to live query after creation', async () => { - const db = await PGlite.create({ - extensions: { live }, - }) - await db.exec(` CREATE TABLE IF NOT EXISTS testTable ( id SERIAL PRIMARY KEY, @@ -969,10 +948,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('live changes limit 1', async () => { - const db = await PGlite.create({ - extensions: { live }, - }) - await db.exec(` CREATE TABLE IF NOT EXISTS testTable ( id SERIAL PRIMARY KEY, @@ -1035,10 +1010,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('subscribe to live changes after creation', async () => { - const db = await PGlite.create({ - extensions: { live }, - }) - await db.exec(` CREATE TABLE IF NOT EXISTS testTable ( id SERIAL PRIMARY KEY, @@ -1094,10 +1065,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('live query with windowing', async () => { - const db = await PGlite.create({ - extensions: { live }, - }) - await db.exec(` CREATE TABLE IF NOT EXISTS testTable ( id SERIAL PRIMARY KEY, @@ -1185,10 +1152,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('throws error when only one of offset/limit is provided', async () => { - const db = await PGlite.create({ - extensions: { live }, - }) - await expect( db.live.query({ query: 'SELECT * FROM (VALUES (1)) t', @@ -1205,10 +1168,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('throws error when offset/limit are not numbers', async () => { - const db = await PGlite.create({ - extensions: { live }, - }) - await expect( db.live.query({ query: 'SELECT * FROM (VALUES (1)) t', @@ -1227,10 +1186,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it("doesn't have a race condition when unsubscribing from a live query", async () => { - const db = await PGlite.create({ - extensions: { live }, - }) - await db.exec(` CREATE TABLE IF NOT EXISTS testTable ( id SERIAL PRIMARY KEY, @@ -1274,10 +1229,6 @@ await testEsmCjsAndDTC(async (importType) => { }, 3000) it('works with pattern matching', async () => { - const db = await PGlite.create({ - extensions: { live }, - }) - await db.exec(` CREATE TABLE IF NOT EXISTS testTable ( id SERIAL PRIMARY KEY, diff --git a/packages/pglite/tests/notify.test.ts b/packages/pglite/tests/notify.test.ts index 001b947d7..29b9260a7 100644 --- a/packages/pglite/tests/notify.test.ts +++ b/packages/pglite/tests/notify.test.ts @@ -1,51 +1,59 @@ -import { describe, it, expect, vi } from 'vitest' +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' import { PGlite } from '../dist/index.js' import { expectToThrowAsync } from './test-utils.js' describe('notify API', () => { - it('notify', async () => { - const db = new PGlite() + let pg: PGlite + let dataDirArchive: File | Blob + beforeEach(async () => { + if (!dataDirArchive) { + pg = await PGlite.create() + dataDirArchive = await pg.dumpDataDir('gzip') + } else { + pg = await PGlite.create() + } + }) + afterEach(async () => { + if (!pg.closed) { + await pg.close() + } + }) - await db.listen('test', (payload) => { + it('notify', async () => { + await pg.listen('test', (payload) => { expect(payload).toBe('321') }) - await db.exec("NOTIFY test, '321'") + await pg.exec("NOTIFY test, '321'") await new Promise((resolve) => setTimeout(resolve, 1000)) }) it('unlisten', async () => { - const db = new PGlite() - - const unsub = await db.listen('test', () => { + const unsub = await pg.listen('test', () => { throw new Error('Notification received after unsubscribed') }) await unsub() - await db.exec('NOTIFY test') + await pg.exec('NOTIFY test') await new Promise((resolve) => setTimeout(resolve, 1000)) }) it('onNotification', async () => { - const db = new PGlite() - - db.onNotification((chan, payload) => { + pg.onNotification((chan, payload) => { expect(chan).toBe('test') expect(payload).toBe('123') }) - await db.exec('LISTEN test') - await db.exec("NOTIFY test, '123'") + await pg.exec('LISTEN test') + await pg.exec("NOTIFY test, '123'") await new Promise((resolve) => setTimeout(resolve, 1000)) }) it('check notify case sensitivity + special chars as Postgresql', async () => { - const pg = new PGlite() - const allLower1 = vi.fn() await pg.listen('postgresdefaultlower', allLower1) await pg.exec(`NOTIFY postgresdefaultlower, 'payload1'`) @@ -111,8 +119,6 @@ describe('notify API', () => { }) it('check unlisten case sensitivity + special chars as Postgresql', async () => { - const pg = new PGlite() - const allLower1 = vi.fn() { const unsub1 = await pg.listen('postgresdefaultlower', allLower1) diff --git a/packages/pglite/tests/pg_hashids.test.ts b/packages/pglite/tests/pg_hashids.test.ts index 61224c857..82b1ed2af 100644 --- a/packages/pglite/tests/pg_hashids.test.ts +++ b/packages/pglite/tests/pg_hashids.test.ts @@ -1,5 +1,6 @@ -import { describe, it, expect } from 'vitest' +import { describe, it, expect, beforeEach, afterEach } from 'vitest' import { testEsmCjsAndDTC } from './test-utils.ts' +import { PGlite } from '../dist/index.js' await testEsmCjsAndDTC(async (importType) => { const { PGlite } = @@ -17,15 +18,30 @@ await testEsmCjsAndDTC(async (importType) => { )) as unknown as typeof import('../dist/pg_hashids/index.js')) describe(`pg_hashids`, () => { - it('can load extension', async () => { - const pg = new PGlite({ - extensions: { - pg_hashids, - }, - }) - + let pg: PGlite + let dataDirArchive: File | Blob + beforeEach(async () => { + if (!dataDirArchive) { + pg = await PGlite.create({ + extensions: { pg_hashids }, + }) + dataDirArchive = await pg.dumpDataDir('gzip') + } else { + pg = await PGlite.create({ + extensions: { pg_hashids }, + loadDataDir: dataDirArchive, + }) + } await pg.exec('CREATE EXTENSION IF NOT EXISTS pg_hashids;') + }) + + afterEach(async () => { + if (!pg.closed) { + await pg.close() + } + }) + it('can load extension', async () => { const res = await pg.query<{ extname: string }>(` SELECT extname FROM pg_extension @@ -37,42 +53,18 @@ await testEsmCjsAndDTC(async (importType) => { }) it('should return a hash using the default alphabet and empty salt', async () => { - const pg = new PGlite({ - extensions: { - pg_hashids, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pg_hashids;') - const res = await pg.exec(`SELECT id_encode(1001);`) expect(res[0].rows[0].id_encode).toEqual('jNl') }) it('should return a hash using the default alphabet and supplied salt', async () => { - const pg = new PGlite({ - extensions: { - pg_hashids, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pg_hashids;') - const res = await pg.exec(`SELECT id_encode(1234567, 'This is my salt');`) expect(res[0].rows[0].id_encode).toEqual('Pdzxp') }) it('should return a hash using the default alphabet, salt and minimum hash length', async () => { - const pg = new PGlite({ - extensions: { - pg_hashids, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pg_hashids;') - const res = await pg.exec( `SELECT id_encode(1234567, 'This is my salt', 10);`, ) @@ -81,14 +73,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('should return a hash using the supplied alphabet, salt and minimum hash length', async () => { - const pg = new PGlite({ - extensions: { - pg_hashids, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pg_hashids;') - const res = await pg.exec( `SELECT id_encode(1234567, 'This is my salt', 10, 'abcdefghijABCDxFGHIJ1234567890');`, ) @@ -97,14 +81,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('should decode previously generated hash', async () => { - const pg = new PGlite({ - extensions: { - pg_hashids, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pg_hashids;') - const res = await pg.exec( `SELECT id_decode('PlRPdzxpR7', 'This is my salt', 10);`, ) @@ -113,14 +89,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('should decode previously generated hash using the supplied alphabet', async () => { - const pg = new PGlite({ - extensions: { - pg_hashids, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pg_hashids;') - const res = await pg.exec( `SELECT id_decode('3GJ956J9B9', 'This is my salt', 10, 'abcdefghijABCDxFGHIJ1234567890');`, ) @@ -129,28 +97,12 @@ await testEsmCjsAndDTC(async (importType) => { }) it('should decode previously generated hash into a single integer', async () => { - const pg = new PGlite({ - extensions: { - pg_hashids, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pg_hashids;') - const res = await pg.exec(`SELECT id_decode_once('jNl');`) expect(res[0].rows[0].id_decode_once).toEqual(1001) }) it('should decode previously generated hash into a single integer using the supplied salt', async () => { - const pg = new PGlite({ - extensions: { - pg_hashids, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pg_hashids;') - const res = await pg.exec( `SELECT id_decode_once('Pdzxp', 'This is my salt');`, ) @@ -159,14 +111,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('should decode previously generated hash into a single integer using the supplied salt and minimum hash length', async () => { - const pg = new PGlite({ - extensions: { - pg_hashids, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pg_hashids;') - const res = await pg.exec( `SELECT id_decode_once('PlRPdzxpR7', 'This is my salt', 10);`, ) @@ -175,14 +119,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('should decode previously generated hash into a single integer using the supplied alphabet', async () => { - const pg = new PGlite({ - extensions: { - pg_hashids, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pg_hashids;') - const res = await pg.exec( `SELECT id_decode_once('3GJ956J9B9', 'This is my salt', 10, 'abcdefghijABCDxFGHIJ1234567890');`, ) diff --git a/packages/pglite/tests/pg_ivm.test.ts b/packages/pglite/tests/pg_ivm.test.ts index 60c7aad57..d5fea80bc 100644 --- a/packages/pglite/tests/pg_ivm.test.ts +++ b/packages/pglite/tests/pg_ivm.test.ts @@ -1,5 +1,6 @@ -import { describe, it, expect } from 'vitest' +import { describe, it, expect, beforeEach, afterEach } from 'vitest' import { testEsmCjsAndDTC } from './test-utils.ts' +import { PGlite } from '../dist/index.js' await testEsmCjsAndDTC(async (importType) => { const { PGlite } = @@ -17,15 +18,30 @@ await testEsmCjsAndDTC(async (importType) => { )) as unknown as typeof import('../dist/pg_ivm/index.js')) describe(`pg_ivm`, () => { - it('can load extension', async () => { - const pg = new PGlite({ - extensions: { - pg_ivm, - }, - }) - + let pg: PGlite + let dataDirArchive: File | Blob + beforeEach(async () => { + if (!dataDirArchive) { + pg = await PGlite.create({ + extensions: { pg_ivm }, + }) + dataDirArchive = await pg.dumpDataDir('gzip') + } else { + pg = await PGlite.create({ + extensions: { pg_ivm }, + loadDataDir: dataDirArchive, + }) + } await pg.exec('CREATE EXTENSION IF NOT EXISTS pg_ivm;') + }) + afterEach(async () => { + if (!pg.closed) { + await pg.close() + } + }) + + it('can load extension', async () => { // Verify the extension is loaded const res = await pg.query<{ extname: string }>(` SELECT extname @@ -38,14 +54,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('can create incremental materialized view', async () => { - const pg = new PGlite({ - extensions: { - pg_ivm, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pg_ivm;') - // Create base table await pg.exec(` CREATE TABLE orders ( @@ -83,14 +91,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('automatically updates view when base table changes', async () => { - const pg = new PGlite({ - extensions: { - pg_ivm, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pg_ivm;') - // Create base table await pg.exec(` CREATE TABLE products ( @@ -208,14 +208,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('supports simple views without aggregates', async () => { - const pg = new PGlite({ - extensions: { - pg_ivm, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pg_ivm;') - // Create base tables await pg.exec(` CREATE TABLE users ( @@ -301,14 +293,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('supports DISTINCT in views', async () => { - const pg = new PGlite({ - extensions: { - pg_ivm, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pg_ivm;') - // Create base table with potential duplicates await pg.exec(` CREATE TABLE events ( @@ -377,14 +361,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('can use refresh_immv function', async () => { - const pg = new PGlite({ - extensions: { - pg_ivm, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pg_ivm;') - // Create base table await pg.exec(` CREATE TABLE items ( diff --git a/packages/pglite/tests/pg_textsearch.test.ts b/packages/pglite/tests/pg_textsearch.test.ts index 33ea75758..e9bbde5d3 100644 --- a/packages/pglite/tests/pg_textsearch.test.ts +++ b/packages/pglite/tests/pg_textsearch.test.ts @@ -2,8 +2,9 @@ * Tests for pg_textsearch extension. * Based on tests from https://github.com/timescale/pg_textsearch/tree/main/test/sql */ -import { describe, it, expect } from 'vitest' +import { describe, it, expect, beforeEach, afterEach } from 'vitest' import { testEsmCjsAndDTC } from './test-utils.ts' +import { PGlite } from '../dist/index.js' await testEsmCjsAndDTC(async (importType) => { const { PGlite } = @@ -21,16 +22,30 @@ await testEsmCjsAndDTC(async (importType) => { )) as unknown as typeof import('../dist/pg_textsearch/index.js')) describe(`pg_textsearch`, () => { - // From test/sql/basic.sql - it('extension creation and bm25 access method', async () => { - const pg = await PGlite.create({ - extensions: { - pg_textsearch, - }, - }) - + let pg: PGlite + let dataDirArchive: File | Blob + beforeEach(async () => { + if (!dataDirArchive) { + pg = await PGlite.create({ + extensions: { pg_textsearch }, + }) + dataDirArchive = await pg.dumpDataDir('gzip') + } else { + pg = await PGlite.create({ + extensions: { pg_textsearch }, + loadDataDir: dataDirArchive, + }) + } await pg.exec('CREATE EXTENSION IF NOT EXISTS pg_textsearch;') + }) + afterEach(async () => { + if (!pg.closed) { + await pg.close() + } + }) + // From test/sql/basic.sql + it('extension creation and bm25 access method', async () => { // Test bm25 access method exists const res = await pg.query<{ amname: string }>( "SELECT amname FROM pg_am WHERE amname = 'bm25';", @@ -41,14 +56,6 @@ await testEsmCjsAndDTC(async (importType) => { // From test/sql/basic.sql - bm25vector type it('bm25vector type exists and works', async () => { - const pg = new PGlite({ - extensions: { - pg_textsearch, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pg_textsearch;') - // Test bm25vector type exists const res = await pg.query<{ pg_typeof: string }>( "SELECT pg_typeof('my_index:{database:2,system:1}'::bm25vector);", @@ -64,14 +71,6 @@ await testEsmCjsAndDTC(async (importType) => { // From test/sql/basic.sql - bm25query type it('bm25query type exists and works', async () => { - const pg = new PGlite({ - extensions: { - pg_textsearch, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pg_textsearch;') - // Test bm25query type exists const res = await pg.query<{ pg_typeof: string }>( "SELECT pg_typeof('search terms'::bm25query);", @@ -87,13 +86,6 @@ await testEsmCjsAndDTC(async (importType) => { // From test/sql/basic.sql - index creation and basic search it('bm25 index creation and basic search', async () => { - const pg = new PGlite({ - extensions: { - pg_textsearch, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pg_textsearch;') await pg.exec(` CREATE TABLE test_docs (id SERIAL PRIMARY KEY, content TEXT); `) @@ -135,13 +127,6 @@ await testEsmCjsAndDTC(async (importType) => { // From test/sql/queries.sql - realistic search queries it('top-k query patterns', async () => { - const pg = new PGlite({ - extensions: { - pg_textsearch, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pg_textsearch;') await pg.exec(` CREATE TABLE articles ( id SERIAL PRIMARY KEY, @@ -199,13 +184,6 @@ await testEsmCjsAndDTC(async (importType) => { // From test/sql/scoring1.sql - bulk vs incremental index build it('bulk build mode (insert then create index)', async () => { - const pg = new PGlite({ - extensions: { - pg_textsearch, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pg_textsearch;') await pg.exec(` CREATE TABLE scoring_bulk ( id SERIAL PRIMARY KEY, @@ -246,13 +224,6 @@ await testEsmCjsAndDTC(async (importType) => { // From test/sql/strings.sql - various text patterns it('handles various text patterns', async () => { - const pg = new PGlite({ - extensions: { - pg_textsearch, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pg_textsearch;') await pg.exec(` CREATE TABLE text_patterns ( id SERIAL PRIMARY KEY, @@ -289,13 +260,6 @@ await testEsmCjsAndDTC(async (importType) => { // From test/sql/updates.sql - update and delete operations it('handles updates and deletes', async () => { - const pg = new PGlite({ - extensions: { - pg_textsearch, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pg_textsearch;') await pg.exec(` CREATE TABLE update_test ( id SERIAL PRIMARY KEY, diff --git a/packages/pglite/tests/pgtap.test.ts b/packages/pglite/tests/pgtap.test.ts index 1f4f12edf..637d1ace1 100644 --- a/packages/pglite/tests/pgtap.test.ts +++ b/packages/pglite/tests/pgtap.test.ts @@ -1,5 +1,6 @@ -import { describe, it, expect } from 'vitest' +import { describe, it, expect, beforeEach, afterEach } from 'vitest' import { testEsmCjsAndDTC } from './test-utils.ts' +import { PGlite } from '../dist/index.js' await testEsmCjsAndDTC(async (importType) => { const { PGlite } = @@ -17,15 +18,29 @@ await testEsmCjsAndDTC(async (importType) => { )) as unknown as typeof import('../dist/pgtap/index.js')) describe(`pgtap`, () => { - it('can load extension', async () => { - const pg = new PGlite({ - extensions: { - pgtap, - }, - }) - + let pg: PGlite + let dataDirArchive: File | Blob + beforeEach(async () => { + if (!dataDirArchive) { + pg = await PGlite.create({ + extensions: { pgtap }, + }) + dataDirArchive = await pg.dumpDataDir('gzip') + } else { + pg = await PGlite.create({ + extensions: { pgtap }, + loadDataDir: dataDirArchive, + }) + } await pg.exec('CREATE EXTENSION IF NOT EXISTS pgtap;') + }) + afterEach(async () => { + if (!pg.closed) { + await pg.close() + } + }) + it('can load extension', async () => { // Verify the extension is loaded const res = await pg.query<{ extname: string }>(` SELECT extname @@ -38,14 +53,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('should run individual pgTAP assertions', async () => { - const pg = new PGlite({ - extensions: { - pgtap, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pgtap;') - const res = await pg.exec(` -- Start transaction and plan the tests. BEGIN; @@ -69,14 +76,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('should check for correct amounts of tests', async () => { - const pg = new PGlite({ - extensions: { - pgtap, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pgtap;') - const res = await pg.exec(` BEGIN; SELECT plan(1); -- wrong amount of tests @@ -98,14 +97,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('should run multiple tests', async () => { - const pg = new PGlite({ - extensions: { - pgtap, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pgtap;') - const res = await pg.exec(` -- Start transaction and plan the tests. BEGIN; @@ -148,14 +139,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('should run pgTAP test suite', async () => { - const pg = new PGlite({ - extensions: { - pgtap, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pgtap;') - const res = await pg.exec(` BEGIN; CREATE TABLE users ( @@ -193,14 +176,6 @@ await testEsmCjsAndDTC(async (importType) => { }) it('should run in-depth assertion tests', async () => { - const pg = new PGlite({ - extensions: { - pgtap, - }, - }) - - await pg.exec('CREATE EXTENSION IF NOT EXISTS pgtap;') - const res = await pg.exec(` BEGIN;