Aktueller Stand
This commit is contained in:
717
backend/node_modules/@electric-sql/pglite-socket/tests/query-with-node-pg.test.ts
generated
vendored
Normal file
717
backend/node_modules/@electric-sql/pglite-socket/tests/query-with-node-pg.test.ts
generated
vendored
Normal file
@@ -0,0 +1,717 @@
|
||||
import {
|
||||
describe,
|
||||
it,
|
||||
expect,
|
||||
beforeAll,
|
||||
afterAll,
|
||||
beforeEach,
|
||||
afterEach,
|
||||
} from 'vitest'
|
||||
import { Client } from 'pg'
|
||||
import { PGlite } from '@electric-sql/pglite'
|
||||
import { PGLiteSocketServer } from '../src'
|
||||
import { spawn, ChildProcess } from 'node:child_process'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { dirname, join } from 'node:path'
|
||||
import fs from 'fs'
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = dirname(__filename)
|
||||
|
||||
/**
|
||||
* Debug configuration for testing
|
||||
*
|
||||
* To test against a real PostgreSQL server:
|
||||
* - Set DEBUG_TESTS=true as an environment variable
|
||||
* - Optionally set DEBUG_TESTS_REAL_SERVER with a connection URL (defaults to localhost)
|
||||
*
|
||||
* Example:
|
||||
* DEBUG_TESTS=true DEBUG_TESTS_REAL_SERVER=postgres://user:pass@host:port/db npm vitest ./tests/query-with-node-pg.test.ts
|
||||
*/
|
||||
const DEBUG_TESTS = process.env.DEBUG_TESTS === 'true'
|
||||
const DEBUG_TESTS_REAL_SERVER =
|
||||
process.env.DEBUG_TESTS_REAL_SERVER ||
|
||||
'postgres://postgres:postgres@localhost:5432/postgres'
|
||||
const TEST_PORT = 5434
|
||||
|
||||
describe(`PGLite Socket Server`, () => {
|
||||
describe('with node-pg client', () => {
|
||||
let db: PGlite
|
||||
let server: PGLiteSocketServer
|
||||
let client: typeof Client.prototype
|
||||
let connectionConfig: any
|
||||
|
||||
beforeAll(async () => {
|
||||
if (DEBUG_TESTS) {
|
||||
console.log('TESTING WITH REAL POSTGRESQL SERVER')
|
||||
console.log(`Connection URL: ${DEBUG_TESTS_REAL_SERVER}`)
|
||||
} else {
|
||||
console.log('TESTING WITH PGLITE SERVER')
|
||||
|
||||
// Create a PGlite instance
|
||||
db = await PGlite.create()
|
||||
|
||||
// Wait for database to be ready
|
||||
await db.waitReady
|
||||
|
||||
console.log('PGLite database ready')
|
||||
|
||||
// Create and start the server with explicit host
|
||||
server = new PGLiteSocketServer({
|
||||
db,
|
||||
port: TEST_PORT,
|
||||
host: '127.0.0.1',
|
||||
})
|
||||
|
||||
// Add event listeners for debugging
|
||||
server.addEventListener('error', (event) => {
|
||||
console.error('Socket server error:', (event as CustomEvent).detail)
|
||||
})
|
||||
|
||||
server.addEventListener('connection', (event) => {
|
||||
console.log(
|
||||
'Socket connection received:',
|
||||
(event as CustomEvent).detail,
|
||||
)
|
||||
})
|
||||
|
||||
await server.start()
|
||||
console.log(`PGLite Socket Server started on port ${TEST_PORT}`)
|
||||
|
||||
connectionConfig = {
|
||||
host: '127.0.0.1',
|
||||
port: TEST_PORT,
|
||||
database: 'postgres',
|
||||
user: 'postgres',
|
||||
password: 'postgres',
|
||||
// Connection timeout in milliseconds
|
||||
connectionTimeoutMillis: 10000,
|
||||
// Query timeout in milliseconds
|
||||
statement_timeout: 5000,
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
if (!DEBUG_TESTS) {
|
||||
// Stop server if running
|
||||
if (server) {
|
||||
await server.stop()
|
||||
console.log('PGLite Socket Server stopped')
|
||||
}
|
||||
|
||||
// Close database
|
||||
if (db) {
|
||||
await db.close()
|
||||
console.log('PGLite database closed')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
// Create pg client instance before each test
|
||||
if (DEBUG_TESTS) {
|
||||
// Direct connection to real PostgreSQL server using URL
|
||||
client = new Client({
|
||||
connectionString: DEBUG_TESTS_REAL_SERVER,
|
||||
connectionTimeoutMillis: 10000,
|
||||
statement_timeout: 5000,
|
||||
})
|
||||
} else {
|
||||
// Connection to PGLite Socket Server
|
||||
client = new Client(connectionConfig)
|
||||
}
|
||||
|
||||
// Connect the client
|
||||
await client.connect()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
// Clean up any tables created in tests
|
||||
try {
|
||||
await client.query('DROP TABLE IF EXISTS test_users')
|
||||
} catch (e) {
|
||||
console.error('Error cleaning up tables:', e)
|
||||
}
|
||||
|
||||
// Disconnect the client after each test
|
||||
if (client) {
|
||||
await client.end()
|
||||
}
|
||||
})
|
||||
|
||||
it('should execute a basic SELECT query', async () => {
|
||||
const result = await client.query('SELECT 1 as one')
|
||||
expect(result.rows[0].one).toBe(1)
|
||||
})
|
||||
|
||||
it('should create a table', async () => {
|
||||
await client.query(`
|
||||
CREATE TABLE test_users (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
email TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
`)
|
||||
|
||||
// Verify table exists by querying the schema
|
||||
const tableCheck = await client.query(`
|
||||
SELECT table_name
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = 'public' AND table_name = 'test_users'
|
||||
`)
|
||||
|
||||
expect(tableCheck.rows.length).toBe(1)
|
||||
expect(tableCheck.rows[0].table_name).toBe('test_users')
|
||||
})
|
||||
|
||||
it('should insert rows into a table', async () => {
|
||||
// Create table
|
||||
await client.query(`
|
||||
CREATE TABLE test_users (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
email TEXT
|
||||
)
|
||||
`)
|
||||
|
||||
// Insert data
|
||||
const insertResult = await client.query(`
|
||||
INSERT INTO test_users (name, email)
|
||||
VALUES
|
||||
('Alice', 'alice@example.com'),
|
||||
('Bob', 'bob@example.com')
|
||||
RETURNING *
|
||||
`)
|
||||
|
||||
expect(insertResult.rows.length).toBe(2)
|
||||
expect(insertResult.rows[0].name).toBe('Alice')
|
||||
expect(insertResult.rows[1].name).toBe('Bob')
|
||||
|
||||
// Verify data is there
|
||||
const count = await client.query(
|
||||
'SELECT COUNT(*)::int as count FROM test_users',
|
||||
)
|
||||
expect(count.rows[0].count).toBe(2)
|
||||
})
|
||||
|
||||
it('should update rows in a table', async () => {
|
||||
// Create and populate table
|
||||
await client.query(`
|
||||
CREATE TABLE test_users (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
email TEXT
|
||||
)
|
||||
`)
|
||||
|
||||
await client.query(`
|
||||
INSERT INTO test_users (name, email)
|
||||
VALUES ('Alice', 'alice@example.com')
|
||||
`)
|
||||
|
||||
// Update
|
||||
const updateResult = await client.query(`
|
||||
UPDATE test_users
|
||||
SET email = 'alice.new@example.com'
|
||||
WHERE name = 'Alice'
|
||||
RETURNING *
|
||||
`)
|
||||
|
||||
expect(updateResult.rows.length).toBe(1)
|
||||
expect(updateResult.rows[0].email).toBe('alice.new@example.com')
|
||||
})
|
||||
|
||||
it('should delete rows from a table', async () => {
|
||||
// Create and populate table
|
||||
await client.query(`
|
||||
CREATE TABLE test_users (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
email TEXT
|
||||
)
|
||||
`)
|
||||
|
||||
await client.query(`
|
||||
INSERT INTO test_users (name, email)
|
||||
VALUES
|
||||
('Alice', 'alice@example.com'),
|
||||
('Bob', 'bob@example.com')
|
||||
`)
|
||||
|
||||
// Delete
|
||||
const deleteResult = await client.query(`
|
||||
DELETE FROM test_users
|
||||
WHERE name = 'Alice'
|
||||
RETURNING *
|
||||
`)
|
||||
|
||||
expect(deleteResult.rows.length).toBe(1)
|
||||
expect(deleteResult.rows[0].name).toBe('Alice')
|
||||
|
||||
// Verify only Bob remains
|
||||
const remaining = await client.query('SELECT * FROM test_users')
|
||||
expect(remaining.rows.length).toBe(1)
|
||||
expect(remaining.rows[0].name).toBe('Bob')
|
||||
})
|
||||
|
||||
it('should execute operations in a transaction', async () => {
|
||||
// Create table
|
||||
await client.query(`
|
||||
CREATE TABLE test_users (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
balance INTEGER DEFAULT 0
|
||||
)
|
||||
`)
|
||||
|
||||
// Insert initial data
|
||||
await client.query(`
|
||||
INSERT INTO test_users (name, balance)
|
||||
VALUES ('Alice', 100), ('Bob', 50)
|
||||
`)
|
||||
|
||||
// Start a transaction and perform operations
|
||||
await client.query('BEGIN')
|
||||
|
||||
try {
|
||||
// Deduct from Alice
|
||||
await client.query(`
|
||||
UPDATE test_users
|
||||
SET balance = balance - 30
|
||||
WHERE name = 'Alice'
|
||||
`)
|
||||
|
||||
// Add to Bob
|
||||
await client.query(`
|
||||
UPDATE test_users
|
||||
SET balance = balance + 30
|
||||
WHERE name = 'Bob'
|
||||
`)
|
||||
|
||||
// Commit the transaction
|
||||
await client.query('COMMIT')
|
||||
} catch (error) {
|
||||
// Rollback on error
|
||||
await client.query('ROLLBACK')
|
||||
throw error
|
||||
}
|
||||
|
||||
// Verify both operations succeeded
|
||||
const users = await client.query(
|
||||
'SELECT name, balance FROM test_users ORDER BY name',
|
||||
)
|
||||
|
||||
expect(users.rows.length).toBe(2)
|
||||
expect(users.rows[0].name).toBe('Alice')
|
||||
expect(users.rows[0].balance).toBe(70)
|
||||
expect(users.rows[1].name).toBe('Bob')
|
||||
expect(users.rows[1].balance).toBe(80)
|
||||
})
|
||||
|
||||
it('should rollback a transaction on ROLLBACK', async () => {
|
||||
// Create table
|
||||
await client.query(`
|
||||
CREATE TABLE test_users (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
balance INTEGER DEFAULT 0
|
||||
)
|
||||
`)
|
||||
|
||||
// Insert initial data
|
||||
await client.query(`
|
||||
INSERT INTO test_users (name, balance)
|
||||
VALUES ('Alice', 100), ('Bob', 50)
|
||||
`)
|
||||
|
||||
// Get initial balance
|
||||
const initialResult = await client.query(`
|
||||
SELECT balance FROM test_users WHERE name = 'Alice'
|
||||
`)
|
||||
const initialBalance = initialResult.rows[0].balance
|
||||
|
||||
// Start a transaction
|
||||
await client.query('BEGIN')
|
||||
|
||||
try {
|
||||
// Deduct from Alice
|
||||
await client.query(`
|
||||
UPDATE test_users
|
||||
SET balance = balance - 30
|
||||
WHERE name = 'Alice'
|
||||
`)
|
||||
|
||||
// Verify balance is changed within transaction
|
||||
const midResult = await client.query(`
|
||||
SELECT balance FROM test_users WHERE name = 'Alice'
|
||||
`)
|
||||
expect(midResult.rows[0].balance).toBe(70)
|
||||
|
||||
// Explicitly roll back (cancel) the transaction
|
||||
await client.query('ROLLBACK')
|
||||
} catch (error) {
|
||||
await client.query('ROLLBACK')
|
||||
throw error
|
||||
}
|
||||
|
||||
// Verify balance wasn't changed after rollback
|
||||
const finalResult = await client.query(`
|
||||
SELECT balance FROM test_users WHERE name = 'Alice'
|
||||
`)
|
||||
expect(finalResult.rows[0].balance).toBe(initialBalance)
|
||||
})
|
||||
|
||||
it('should rollback a transaction on error', async () => {
|
||||
// Create table
|
||||
await client.query(`
|
||||
CREATE TABLE test_users (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
balance INTEGER DEFAULT 0
|
||||
)
|
||||
`)
|
||||
|
||||
// Insert initial data
|
||||
await client.query(`
|
||||
INSERT INTO test_users (name, balance)
|
||||
VALUES ('Alice', 100), ('Bob', 50)
|
||||
`)
|
||||
|
||||
try {
|
||||
// Start a transaction
|
||||
await client.query('BEGIN')
|
||||
|
||||
// Deduct from Alice
|
||||
await client.query(`
|
||||
UPDATE test_users
|
||||
SET balance = balance - 30
|
||||
WHERE name = 'Alice'
|
||||
`)
|
||||
|
||||
// This will trigger an error
|
||||
await client.query(`
|
||||
UPDATE test_users_nonexistent
|
||||
SET balance = balance + 30
|
||||
WHERE name = 'Bob'
|
||||
`)
|
||||
|
||||
// Should never get here
|
||||
await client.query('COMMIT')
|
||||
} catch (error) {
|
||||
// Expected to fail - rollback transaction
|
||||
await client.query('ROLLBACK').catch(() => {
|
||||
// If the client connection is in a bad state, we just ignore
|
||||
// the rollback error
|
||||
})
|
||||
}
|
||||
|
||||
// Verify Alice's balance was not changed due to rollback
|
||||
const users = await client.query(
|
||||
'SELECT name, balance FROM test_users ORDER BY name',
|
||||
)
|
||||
|
||||
expect(users.rows.length).toBe(2)
|
||||
expect(users.rows[0].name).toBe('Alice')
|
||||
expect(users.rows[0].balance).toBe(100) // Should remain 100 after rollback
|
||||
})
|
||||
|
||||
it('should handle a syntax error', async () => {
|
||||
// Expect syntax error
|
||||
let errorMessage = ''
|
||||
try {
|
||||
await client.query('THIS IS NOT VALID SQL;')
|
||||
} catch (error) {
|
||||
errorMessage = (error as Error).message
|
||||
}
|
||||
|
||||
expect(errorMessage).not.toBe('')
|
||||
expect(errorMessage.toLowerCase()).toContain('syntax error')
|
||||
})
|
||||
|
||||
it('should support cursor-based pagination', async () => {
|
||||
// Create a test table with many rows
|
||||
await client.query(`
|
||||
CREATE TABLE test_users (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
value INTEGER
|
||||
)
|
||||
`)
|
||||
|
||||
// Insert 100 rows using generate_series (server-side generation)
|
||||
await client.query(`
|
||||
INSERT INTO test_users (name, value)
|
||||
SELECT
|
||||
'User ' || i as name,
|
||||
i as value
|
||||
FROM generate_series(1, 100) as i
|
||||
`)
|
||||
|
||||
// Use a cursor to read data in smaller chunks
|
||||
const chunkSize = 10
|
||||
let results: any[] = []
|
||||
let page = 0
|
||||
|
||||
try {
|
||||
// Begin transaction
|
||||
await client.query('BEGIN')
|
||||
|
||||
// Declare a cursor
|
||||
await client.query(
|
||||
'DECLARE user_cursor CURSOR FOR SELECT * FROM test_users ORDER BY id',
|
||||
)
|
||||
|
||||
let hasMoreData = true
|
||||
while (hasMoreData) {
|
||||
// Fetch a batch of results
|
||||
const chunk = await client.query('FETCH 10 FROM user_cursor')
|
||||
|
||||
// If no rows returned, we're done
|
||||
if (chunk.rows.length === 0) {
|
||||
hasMoreData = false
|
||||
continue
|
||||
}
|
||||
|
||||
// Process this chunk
|
||||
page++
|
||||
|
||||
// Add to our results array
|
||||
results = [...results, ...chunk.rows]
|
||||
|
||||
// Verify each chunk has correct data (except possibly the last one)
|
||||
if (chunk.rows.length === chunkSize) {
|
||||
expect(chunk.rows.length).toBe(chunkSize)
|
||||
expect(chunk.rows[0].id).toBe((page - 1) * chunkSize + 1)
|
||||
}
|
||||
}
|
||||
|
||||
// Close the cursor
|
||||
await client.query('CLOSE user_cursor')
|
||||
|
||||
// Commit transaction
|
||||
await client.query('COMMIT')
|
||||
} catch (error) {
|
||||
await client.query('ROLLBACK')
|
||||
throw error
|
||||
}
|
||||
|
||||
// Verify we got all 100 records
|
||||
expect(results.length).toBe(100)
|
||||
expect(results[0].name).toBe('User 1')
|
||||
expect(results[99].name).toBe('User 100')
|
||||
|
||||
// Verify we received the expected number of pages
|
||||
expect(page).toBe(Math.ceil(100 / chunkSize))
|
||||
})
|
||||
|
||||
it('should support LISTEN/NOTIFY for pub/sub messaging', async () => {
|
||||
// Set up listener for notifications
|
||||
let receivedPayload = ''
|
||||
const notificationReceived = new Promise<void>((resolve) => {
|
||||
client.on('notification', (msg) => {
|
||||
receivedPayload = msg.payload || ''
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
|
||||
// Start listening
|
||||
await client.query('LISTEN test_channel')
|
||||
|
||||
// Small delay to ensure listener is set up
|
||||
await new Promise((resolve) => setTimeout(resolve, 100))
|
||||
|
||||
// Send a notification
|
||||
await client.query("NOTIFY test_channel, 'Hello from PGlite!'")
|
||||
|
||||
// Wait for the notification to be received with an appropriate timeout
|
||||
const timeoutPromise = new Promise<void>((_, reject) => {
|
||||
setTimeout(() => reject(new Error('Notification timeout')), 2000)
|
||||
})
|
||||
|
||||
await Promise.race([notificationReceived, timeoutPromise]).catch(
|
||||
(error) => {
|
||||
console.error('Notification error:', error)
|
||||
},
|
||||
)
|
||||
|
||||
// Verify the notification was received with the correct payload
|
||||
expect(receivedPayload).toBe('Hello from PGlite!')
|
||||
})
|
||||
})
|
||||
|
||||
describe('with extensions via CLI', () => {
|
||||
const UNIX_SOCKET_DIR_PATH = `/tmp/${Date.now().toString()}`
|
||||
fs.mkdirSync(UNIX_SOCKET_DIR_PATH)
|
||||
const UNIX_SOCKET_PATH = `${UNIX_SOCKET_DIR_PATH}/.s.PGSQL.5432`
|
||||
let serverProcess: ChildProcess | null = null
|
||||
let client: typeof Client.prototype
|
||||
|
||||
beforeAll(async () => {
|
||||
// Start the server with extensions via CLI using tsx for dev or node for dist
|
||||
const serverScript = join(__dirname, '../src/scripts/server.ts')
|
||||
serverProcess = spawn(
|
||||
'npx',
|
||||
[
|
||||
'tsx',
|
||||
serverScript,
|
||||
'--path',
|
||||
UNIX_SOCKET_PATH,
|
||||
'--extensions',
|
||||
'vector,pg_uuidv7,@electric-sql/pglite/pg_hashids:pg_hashids',
|
||||
],
|
||||
{
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
},
|
||||
)
|
||||
|
||||
// Wait for server to be ready by checking for "listening" message
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const timeout = setTimeout(() => {
|
||||
reject(new Error('Server startup timeout'))
|
||||
}, 30000)
|
||||
|
||||
const onData = (data: Buffer) => {
|
||||
const output = data.toString()
|
||||
if (output.includes('listening')) {
|
||||
clearTimeout(timeout)
|
||||
resolve()
|
||||
}
|
||||
}
|
||||
|
||||
serverProcess!.stdout?.on('data', onData)
|
||||
serverProcess!.stderr?.on('data', (data) => {
|
||||
console.error('Server stderr:', data.toString())
|
||||
})
|
||||
|
||||
serverProcess!.on('error', (err) => {
|
||||
clearTimeout(timeout)
|
||||
reject(err)
|
||||
})
|
||||
|
||||
serverProcess!.on('exit', (code) => {
|
||||
if (code !== 0 && code !== null) {
|
||||
clearTimeout(timeout)
|
||||
reject(new Error(`Server exited with code ${code}`))
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
console.log('Server with extensions started')
|
||||
|
||||
client = new Client({
|
||||
host: UNIX_SOCKET_DIR_PATH,
|
||||
database: 'postgres',
|
||||
user: 'postgres',
|
||||
password: 'postgres',
|
||||
connectionTimeoutMillis: 10000,
|
||||
})
|
||||
await client.connect()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
if (client) {
|
||||
await client.end().catch(() => {})
|
||||
}
|
||||
|
||||
if (serverProcess) {
|
||||
serverProcess.kill('SIGTERM')
|
||||
await new Promise<void>((resolve) => {
|
||||
serverProcess!.on('exit', () => resolve())
|
||||
setTimeout(resolve, 2000) // Force resolve after 2s
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
it('should load and use vector extension', async () => {
|
||||
// Create the extension
|
||||
await client.query('CREATE EXTENSION IF NOT EXISTS vector')
|
||||
|
||||
// Verify extension is loaded
|
||||
const extCheck = await client.query(`
|
||||
SELECT extname FROM pg_extension WHERE extname = 'vector'
|
||||
`)
|
||||
expect(extCheck.rows).toHaveLength(1)
|
||||
expect(extCheck.rows[0].extname).toBe('vector')
|
||||
|
||||
// Create a table with vector column
|
||||
await client.query(`
|
||||
CREATE TABLE test_vectors (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name TEXT,
|
||||
vec vector(3)
|
||||
)
|
||||
`)
|
||||
|
||||
// Insert test data
|
||||
await client.query(`
|
||||
INSERT INTO test_vectors (name, vec) VALUES
|
||||
('test1', '[1,2,3]'),
|
||||
('test2', '[4,5,6]'),
|
||||
('test3', '[7,8,9]')
|
||||
`)
|
||||
|
||||
// Query with vector distance
|
||||
const result = await client.query(`
|
||||
SELECT name, vec, vec <-> '[3,1,2]' AS distance
|
||||
FROM test_vectors
|
||||
ORDER BY distance
|
||||
`)
|
||||
|
||||
expect(result.rows).toHaveLength(3)
|
||||
expect(result.rows[0].name).toBe('test1')
|
||||
expect(result.rows[0].vec).toBe('[1,2,3]')
|
||||
expect(parseFloat(result.rows[0].distance)).toBeCloseTo(2.449, 2)
|
||||
})
|
||||
|
||||
it('should load and use pg_uuidv7 extension', async () => {
|
||||
// Create the extension
|
||||
await client.query('CREATE EXTENSION IF NOT EXISTS pg_uuidv7')
|
||||
|
||||
// Verify extension is loaded
|
||||
const extCheck = await client.query(`
|
||||
SELECT extname FROM pg_extension WHERE extname = 'pg_uuidv7'
|
||||
`)
|
||||
expect(extCheck.rows).toHaveLength(1)
|
||||
expect(extCheck.rows[0].extname).toBe('pg_uuidv7')
|
||||
|
||||
// Generate a UUIDv7
|
||||
const result = await client.query('SELECT uuid_generate_v7() as uuid')
|
||||
expect(result.rows[0].uuid).toHaveLength(36)
|
||||
|
||||
// Test uuid_v7_to_timestamptz function
|
||||
const tsResult = await client.query(`
|
||||
SELECT uuid_v7_to_timestamptz('018570bb-4a7d-7c7e-8df4-6d47afd8c8fc') as ts
|
||||
`)
|
||||
const timestamp = new Date(tsResult.rows[0].ts)
|
||||
expect(timestamp.toISOString()).toBe('2023-01-02T04:26:40.637Z')
|
||||
})
|
||||
|
||||
it('should load and use pg_hashids extension from npm package path', async () => {
|
||||
// Create the extension
|
||||
await client.query('CREATE EXTENSION IF NOT EXISTS pg_hashids')
|
||||
|
||||
// Verify extension is loaded
|
||||
const extCheck = await client.query(`
|
||||
SELECT extname FROM pg_extension WHERE extname = 'pg_hashids'
|
||||
`)
|
||||
expect(extCheck.rows).toHaveLength(1)
|
||||
expect(extCheck.rows[0].extname).toBe('pg_hashids')
|
||||
|
||||
// Test id_encode function
|
||||
const result = await client.query(`
|
||||
SELECT id_encode(1234567, 'salt', 10, 'abcdefghijABCDEFGHIJ1234567890') as hash
|
||||
`)
|
||||
expect(result.rows[0].hash).toBeTruthy()
|
||||
expect(typeof result.rows[0].hash).toBe('string')
|
||||
|
||||
// Test id_decode function (round-trip)
|
||||
const hash = result.rows[0].hash
|
||||
const decodeResult = await client.query(`
|
||||
SELECT id_decode('${hash}', 'salt', 10, 'abcdefghijABCDEFGHIJ1234567890') as id
|
||||
`)
|
||||
expect(decodeResult.rows[0].id[0]).toBe('1234567')
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user