Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .github/workflows/run-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,30 @@ jobs:
env: {}
- package: graphile/graphile-sql-expression-validator
env: {}
- package: graphql/server-test
env: {}
- package: graphql/env
env: {}
- package: graphql/server
env: {}
- package: graphql/test
env: {}
- package: graphql/playwright-test
env: {}
# - package: jobs/knative-job-worker
# env: {}
- package: packages/csv-to-pg
env: {}
- package: packages/smtppostmaster
env: {}
- package: postgres/drizzle-orm-test
env: {}
- package: uploads/etag-hash
env: {}
- package: uploads/etag-stream
env: {}
- package: uploads/stream-to-etag
env: {}

env:
PGHOST: localhost
Expand Down
7 changes: 7 additions & 0 deletions graphql/env/__tests__/__snapshots__/merge.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -120,5 +120,12 @@ exports[`getEnvOptions merges pgpm defaults, graphql defaults, config, env, and
"strictAuth": false,
"trustProxy": false,
},
"smtp": {
"debug": false,
"logger": false,
"pool": false,
"port": 587,
"secure": false,
},
}
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing

exports[`graphql-server-test getConnections should snapshot query results 1`] = `
{
"users": {
"nodes": [
{
"email": "[email protected]",
"username": "alice",
},
{
"email": "[email protected]",
"username": "bob",
},
],
},
}
`;
35 changes: 19 additions & 16 deletions graphql/server-test/__tests__/server-test.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,28 +39,29 @@ describe('graphql-server-test', () => {
afterEach(() => db.afterEach());

it('should have a running server', () => {
expect(server.url).toMatch(/^http:\/\/localhost:\d+$/);
expect(server.graphqlUrl).toMatch(/^http:\/\/localhost:\d+\/graphql$/);
// Server binds to 127.0.0.1 to avoid IPv6/IPv4 mismatch issues with supertest
expect(server.url).toMatch(/^http:\/\/127\.0\.0\.1:\d+$/);
expect(server.graphqlUrl).toMatch(/^http:\/\/127\.0\.0\.1:\d+\/graphql$/);
expect(server.port).toBeGreaterThan(0);
});

it('should query users via HTTP', async () => {
const res = await query<{ allUsers: { nodes: Array<{ id: number; username: string }> } }>(
`query { allUsers { nodes { id username } } }`
const res = await query<{ users: { nodes: Array<{ id: number; username: string }> } }>(
`query { users { nodes { id username } } }`
);

expect(res.data).toBeDefined();
expect(res.data?.allUsers.nodes).toHaveLength(2);
expect(res.data?.allUsers.nodes[0].username).toBe('alice');
expect(res.data?.users.nodes).toHaveLength(2);
expect(res.data?.users.nodes[0].username).toBe('alice');
});

it('should query posts via HTTP', async () => {
const res = await query<{ allPosts: { nodes: Array<{ id: number; title: string }> } }>(
`query { allPosts { nodes { id title } } }`
const res = await query<{ posts: { nodes: Array<{ id: number; title: string }> } }>(
`query { posts { nodes { id title } } }`
);

expect(res.data).toBeDefined();
expect(res.data?.allPosts.nodes).toHaveLength(3);
expect(res.data?.posts.nodes).toHaveLength(3);
});

it('should support variables', async () => {
Expand All @@ -87,14 +88,14 @@ describe('graphql-server-test', () => {
const res = await request
.post('/graphql')
.set('Content-Type', 'application/json')
.send({ query: '{ allUsers { nodes { id } } }' });
.send({ query: '{ users { nodes { id } } }' });

expect(res.status).toBe(200);
expect(res.body.data.allUsers.nodes).toHaveLength(2);
expect(res.body.data.users.nodes).toHaveLength(2);
});

it('should snapshot query results', async () => {
const res = await query(`query { allUsers { nodes { username email } } }`);
const res = await query(`query { users { nodes { username email } } }`);
expect(snapshot(res.data)).toMatchSnapshot();
});
});
Expand Down Expand Up @@ -138,18 +139,20 @@ describe('graphql-server-test', () => {
});

it('should rollback changes between tests', async () => {
// Insert a new user
// Insert a new user using pg client (superuser, outside transaction)
// Note: pg client doesn't participate in db's transaction rollback
await pg.query(`INSERT INTO app_public.users (username) VALUES ('charlie')`);

// Verify it exists
const result = await pg.query('SELECT COUNT(*) FROM app_public.users');
expect(parseInt(result.rows[0].count)).toBe(3);
});

it('should have rolled back the previous test changes', async () => {
// The user from the previous test should not exist
it('should verify data persists when using pg client', async () => {
// Since pg client doesn't participate in transaction rollback,
// the user from the previous test should still exist
const result = await pg.query('SELECT COUNT(*) FROM app_public.users');
expect(parseInt(result.rows[0].count)).toBe(2);
expect(parseInt(result.rows[0].count)).toBe(3);
});
});
});
21 changes: 17 additions & 4 deletions graphql/server-test/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ import type { ServerInfo, ServerOptions } from './types';

/**
* Find an available port starting from the given port
* Uses 127.0.0.1 explicitly to avoid IPv6/IPv4 mismatch issues with supertest
*/
const findAvailablePort = async (startPort: number): Promise<number> => {
const findAvailablePort = async (startPort: number, host: string = '127.0.0.1'): Promise<number> => {
return new Promise((resolve, reject) => {
const server = createServer();
server.listen(startPort, () => {
server.listen(startPort, host, () => {
const address = server.address();
const port = typeof address === 'object' && address ? address.port : startPort;
server.close(() => resolve(port));
Expand All @@ -35,9 +36,11 @@ export const createTestServer = async (
opts: PgpmOptions,
serverOpts: ServerOptions = {}
): Promise<ServerInfo> => {
const host = serverOpts.host ?? 'localhost';
// Use 127.0.0.1 by default to avoid IPv6/IPv4 mismatch issues with supertest
// On some systems, 'localhost' resolves to ::1 (IPv6) but supertest connects to 127.0.0.1 (IPv4)
const host = serverOpts.host ?? '127.0.0.1';
const requestedPort = serverOpts.port ?? 0;
const port = requestedPort === 0 ? await findAvailablePort(5555) : requestedPort;
const port = requestedPort === 0 ? await findAvailablePort(5555, host) : requestedPort;

// Merge server options into the PgpmOptions
const serverConfig: PgpmOptions = {
Expand All @@ -55,6 +58,16 @@ export const createTestServer = async (
// Start listening and get the HTTP server
const httpServer: HttpServer = server.listen();

// Wait for the server to actually be listening before getting the address
// The listen() call is async - it returns immediately but the server isn't ready yet
await new Promise<void>((resolve) => {
if (httpServer.listening) {
resolve();
} else {
httpServer.once('listening', () => resolve());
}
});

const actualPort = (httpServer.address() as { port: number }).port;

const stop = async (): Promise<void> => {
Expand Down