Skip to content

Commit

Permalink
tests
Browse files Browse the repository at this point in the history
  • Loading branch information
jgoux committed Sep 13, 2024
1 parent 5691efd commit 4f7fc24
Show file tree
Hide file tree
Showing 6 changed files with 199 additions and 72 deletions.
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/pg-gateway/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
},
"devDependencies": {
"@biomejs/biome": "^1.8.3",
"@electric-sql/pglite": "^0.2.6",
"@electric-sql/pglite": "^0.2.7",
"@nodeweb/knex": "^3.1.0-alpha.13",
"@nodeweb/pg": "^8.12.0-alpha.5",
"@std/bytes": "npm:@jsr/std__bytes@^1.0.2",
Expand Down
21 changes: 21 additions & 0 deletions packages/pg-gateway/test/node/certs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,3 +145,24 @@ export async function signCert(caCert: ArrayBuffer, caKey: ArrayBuffer, csr: Arr

return certBytes;
}

export async function generateAllCertificates() {
const { caKey, caCert } = await generateCA('My Root CA');

const { key: serverKey, csr: serverCsr } = await generateCSR('localhost');
const serverCert = await signCert(caCert, caKey, serverCsr);

const { key: clientKey, csr: clientCsr } = await generateCSR('postgres');
const clientCert = await signCert(caCert, caKey, clientCsr);

const encoder = new TextEncoder();

return {
caKey: encoder.encode(toPEM(caKey, 'PRIVATE KEY')),
caCert: encoder.encode(toPEM(caCert, 'CERTIFICATE')),
serverKey: encoder.encode(toPEM(serverKey, 'PRIVATE KEY')),
serverCert: encoder.encode(toPEM(serverCert, 'CERTIFICATE')),
clientKey: encoder.encode(toPEM(clientKey, 'PRIVATE KEY')),
clientCert: encoder.encode(toPEM(clientCert, 'CERTIFICATE')),
};
}
110 changes: 110 additions & 0 deletions packages/pg-gateway/test/node/errors.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { BackendError } from 'pg-gateway';
import { describe, expect, it, vi } from 'vitest';
import { createPostgresClient, createPostgresServer, getPort } from '../util';
import { generateAllCertificates } from './certs';

describe('errors', () => {
it('sends backend error thrown in onTlsUpgrade to the client', async () => {
const { caCert, serverKey, serverCert } = await generateAllCertificates();
await using server = await createPostgresServer({
tls: {
cert: serverCert,
key: serverKey,
},
async onTlsUpgrade() {
throw BackendError.create({
message: 'onTlsUpgrade failed',
code: 'P0000',
severity: 'FATAL',
});
},
});
const promise = createPostgresClient({
port: getPort(server),
ssl: {
ca: Buffer.from(caCert),
},
});
await expect(promise).rejects.toThrow('onTlsUpgrade failed');
});

it('sends backend error thrown in onAuthenticated to the client', async () => {
await using server = await createPostgresServer({
async onAuthenticated() {
throw BackendError.create({
message: 'onAuthenticated failed',
code: 'P0000',
severity: 'FATAL',
});
},
});
const promise = createPostgresClient({
port: getPort(server),
});
await expect(promise).rejects.toThrow('onAuthenticated failed');
});

it('sends backend error thrown in onStartup to the client', async () => {
await using server = await createPostgresServer({
async onStartup() {
throw BackendError.create({
message: 'onStartup failed',
code: 'P0000',
severity: 'FATAL',
});
},
});
const promise = createPostgresClient({
port: getPort(server),
});
await expect(promise).rejects.toThrow('onStartup failed');
});

it('sends backend error thrown in onMessage to the client', async () => {
await using server = await createPostgresServer({
async onMessage() {
throw BackendError.create({
message: 'onMessage failed',
code: 'P0000',
severity: 'FATAL',
});
},
});
const promise = createPostgresClient({
port: getPort(server),
});
await expect(promise).rejects.toThrow('onMessage failed');
});

const mockOutput = () => {
const output = {
stderr: '',
[Symbol.dispose]() {
consoleErrorMock.mockRestore();
},
};
const consoleErrorMock = vi.spyOn(console, 'error').mockImplementation((...args) => {
output.stderr += args.join(' ');
});
return output;
};

it('does not send non backend errors to the client', async () => {
using output = mockOutput();
await using server = await createPostgresServer({
async onMessage() {
throw Error('wat?');
},
});
const promise = createPostgresClient({
port: getPort(server),
});
try {
await promise;
} catch (error) {
expect(error.message).not.toContain('wat?');
expect(output.stderr).toContain('wat?');
expect(error.message).toContain('Connection terminated unexpectedly');
}
});
});
80 changes: 16 additions & 64 deletions packages/pg-gateway/test/node/tls.test.ts
Original file line number Diff line number Diff line change
@@ -1,64 +1,16 @@
import { createServer, type Server } from 'node:net';
import type { ClientConfig } from 'pg';
import { type PostgresConnectionOptions, createDuplexPair } from 'pg-gateway';
import { fromDuplexStream, fromNodeSocket } from 'pg-gateway/node';
import { createDuplexPair } from 'pg-gateway';
import { fromDuplexStream } from 'pg-gateway/node';
import { describe, expect, it } from 'vitest';
import { DisposablePgClient, socketFromDuplexStream } from '../util';
import { generateCA, generateCSR, signCert, toPEM } from './certs';
import { once } from 'node:events';

async function generateAllCertificates() {
const { caKey, caCert } = await generateCA('My Root CA');

const { key: serverKey, csr: serverCsr } = await generateCSR('localhost');
const serverCert = await signCert(caCert, caKey, serverCsr);

const { key: clientKey, csr: clientCsr } = await generateCSR('postgres');
const clientCert = await signCert(caCert, caKey, clientCsr);

const encoder = new TextEncoder();

return {
caKey: encoder.encode(toPEM(caKey, 'PRIVATE KEY')),
caCert: encoder.encode(toPEM(caCert, 'CERTIFICATE')),
serverKey: encoder.encode(toPEM(serverKey, 'PRIVATE KEY')),
serverCert: encoder.encode(toPEM(serverCert, 'CERTIFICATE')),
clientKey: encoder.encode(toPEM(clientKey, 'PRIVATE KEY')),
clientCert: encoder.encode(toPEM(clientCert, 'CERTIFICATE')),
};
}
import {
createPostgresClient,
createPostgresServer,
getPort,
socketFromDuplexStream,
} from '../util';
import { generateAllCertificates } from './certs';

const { caCert, serverKey, serverCert, clientKey, clientCert } = await generateAllCertificates();

async function createPostgresServer(options?: PostgresConnectionOptions) {
const server = createServer((socket) => fromNodeSocket(socket, options));

// Listen on a random free port
server.listen(0);
await once(server, 'listening');
return server;
}

function getPort(server: Server) {
const address = server.address();

if (typeof address !== 'object') {
throw new Error(`Invalid server address '${address}'`);
}

if (!address) {
throw new Error('Server has no address');
}

return address.port;
}

async function connectPg(config: string | ClientConfig) {
const client = new DisposablePgClient(config);
await client.connect();
return client;
}

describe('tls', () => {
it('basic tls over tcp', async () => {
await using server = await createPostgresServer({
Expand All @@ -71,7 +23,7 @@ describe('tls', () => {
},
});

await using client = await connectPg({
await using client = await createPostgresClient({
port: getPort(server),
ssl: {
ca: Buffer.from(caCert),
Expand All @@ -93,7 +45,7 @@ describe('tls', () => {
},
});

await using client = await connectPg({
await using client = await createPostgresClient({
host: 'localhost',
port: getPort(server),
ssl: {
Expand All @@ -116,7 +68,7 @@ describe('tls', () => {
},
});

await using client = await connectPg({
await using client = await createPostgresClient({
host: '127.0.0.1',
port: getPort(server),
ssl: {
Expand All @@ -137,7 +89,7 @@ describe('tls', () => {
},
});

await using client = await connectPg({
await using client = await createPostgresClient({
port: getPort(server),
user: 'postgres',
ssl: {
Expand All @@ -160,7 +112,7 @@ describe('tls', () => {
},
});

const promise = connectPg({
const promise = createPostgresClient({
port: getPort(server),
user: 'bob',
ssl: {
Expand All @@ -185,7 +137,7 @@ describe('tls', () => {
},
});

const promise = connectPg({
const promise = createPostgresClient({
port: getPort(server),
ssl: {
ca: Buffer.from(serverCert),
Expand All @@ -209,7 +161,7 @@ describe('tls', () => {
},
});

await using client = await connectPg({
await using client = await createPostgresClient({
stream: socketFromDuplexStream(clientDuplex),
ssl: {
ca: Buffer.from(caCert),
Expand Down
50 changes: 47 additions & 3 deletions packages/pg-gateway/test/util.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import EventEmitter from 'node:events';
import type { DuplexStream } from 'pg-gateway';
import { Client } from 'pg';
import EventEmitter, { once } from 'node:events';
import { Server, createServer } from 'node:net';
import { Client, type ClientConfig } from 'pg';
import type { DuplexStream, PostgresConnectionOptions } from 'pg-gateway';
import { fromNodeSocket } from 'pg-gateway/node';

/**
* Creates a passthrough socket object that can be passed
Expand Down Expand Up @@ -128,3 +130,45 @@ export class DisposablePgClient extends Client {
await this.end();
}
}

export async function createPostgresClient(config: string | ClientConfig) {
const client = new DisposablePgClient(config);
await client.connect();
return client;
}

export class DisposableServer extends Server {
async [Symbol.asyncDispose]() {
await new Promise((resolve, reject) => {
this.close((err) => {
if (err) {
reject(err);
} else {
resolve(undefined);
}
});
});
}
}

export async function createPostgresServer(options?: PostgresConnectionOptions) {
const server = new DisposableServer((socket) => fromNodeSocket(socket, options));
// Listen on a random free port
server.listen(0);
await once(server, 'listening');
return server;
}

export function getPort(server: Server) {
const address = server.address();

if (typeof address !== 'object') {
throw new Error(`Invalid server address '${address}'`);
}

if (!address) {
throw new Error('Server has no address');
}

return address.port;
}

0 comments on commit 4f7fc24

Please sign in to comment.