182 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			182 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| import pglue, { PostgresError, SqlTypeError } from "./mod.ts";
 | |
| import { expect } from "jsr:@std/expect";
 | |
| 
 | |
| async function connect() {
 | |
|   const pg = await pglue.connect(`postgres://test:test@localhost:5432/test`, {
 | |
|     runtime_params: { client_min_messages: "INFO" },
 | |
|   });
 | |
| 
 | |
|   return pg.on("log", (_level, ctx, msg) => {
 | |
|     console.info(`${msg}`, ctx);
 | |
|   });
 | |
| }
 | |
| 
 | |
| Deno.test(`integers`, async () => {
 | |
|   await using pg = await connect();
 | |
|   await using _tx = await pg.begin();
 | |
| 
 | |
|   const [{ a, b, c }] = await pg.query`
 | |
|     select
 | |
|       ${"0x100"}::int2 as a,
 | |
|       ${777}::int4 as b,
 | |
|       ${{
 | |
|         [Symbol.toPrimitive](hint: string) {
 | |
|           expect(hint).toBe("number");
 | |
|           return "1234";
 | |
|         },
 | |
|       }}::int8 as c
 | |
|   `.first();
 | |
| 
 | |
|   expect(a).toBe(0x100);
 | |
|   expect(b).toBe(777);
 | |
|   expect(c).toBe(1234);
 | |
| 
 | |
|   const [{ large }] =
 | |
|     await pg.query`select ${"10000000000000000"}::int8 as large`.first();
 | |
| 
 | |
|   expect(large).toBe(10000000000000000n);
 | |
| 
 | |
|   await expect(pg.query`select ${100000}::int2`).rejects.toThrow(SqlTypeError);
 | |
|   await expect(pg.query`select ${"100000"}::text::int2`).rejects.toThrow(
 | |
|     PostgresError
 | |
|   );
 | |
| });
 | |
| 
 | |
| Deno.test(`boolean`, async () => {
 | |
|   await using pg = await connect();
 | |
|   await using _tx = await pg.begin();
 | |
| 
 | |
|   const [{ a, b, c }] = await pg.query`
 | |
|     select
 | |
|       ${true}::bool as a,
 | |
|       ${"n"}::bool as b,
 | |
|       ${undefined}::bool as c
 | |
|   `.first();
 | |
| 
 | |
|   expect(a).toBe(true);
 | |
|   expect(b).toBe(false);
 | |
|   expect(c).toBe(null);
 | |
| });
 | |
| 
 | |
| Deno.test(`bytea`, async () => {
 | |
|   await using pg = await connect();
 | |
|   await using _tx = await pg.begin();
 | |
| 
 | |
|   const [{ string, array, buffer }] = await pg.query`
 | |
|     select
 | |
|       ${"hello, world"}::bytea as string,
 | |
|       ${[1, 2, 3, 4, 5]}::bytea as array,
 | |
|       ${Uint8Array.of(5, 4, 3, 2, 1)}::bytea as buffer
 | |
|   `.first();
 | |
| 
 | |
|   expect(string).toEqual(new TextEncoder().encode("hello, world"));
 | |
|   expect(array).toEqual(Uint8Array.of(1, 2, 3, 4, 5));
 | |
|   expect(buffer).toEqual(Uint8Array.of(5, 4, 3, 2, 1));
 | |
| });
 | |
| 
 | |
| Deno.test(`row`, async () => {
 | |
|   await using pg = await connect();
 | |
|   await using _tx = await pg.begin();
 | |
| 
 | |
|   expect(
 | |
|     (
 | |
|       await pg.query`create table my_table (a text not null, b text not null, c text not null)`
 | |
|     ).tag
 | |
|   ).toBe(`CREATE TABLE`);
 | |
| 
 | |
|   expect(
 | |
|     (
 | |
|       await pg.query`copy my_table from stdin`.stdin(
 | |
|         `field a\tfield b\tfield c`
 | |
|       )
 | |
|     ).tag
 | |
|   ).toBe(`COPY 1`);
 | |
| 
 | |
|   const [row] = await pg.query`select * from my_table`.first();
 | |
|   {
 | |
|     // columns by name
 | |
|     const { a, b, c } = row;
 | |
|     expect(a).toBe("field a");
 | |
|     expect(b).toBe("field b");
 | |
|     expect(c).toBe("field c");
 | |
|   }
 | |
|   {
 | |
|     // columns by index
 | |
|     const [a, b, c] = row;
 | |
|     expect(a).toBe("field a");
 | |
|     expect(b).toBe("field b");
 | |
|     expect(c).toBe("field c");
 | |
|   }
 | |
| });
 | |
| 
 | |
| Deno.test(`sql injection`, async () => {
 | |
|   await using pg = await connect();
 | |
|   await using _tx = await pg.begin();
 | |
| 
 | |
|   const input = `injection'); drop table users; --`;
 | |
| 
 | |
|   expect((await pg.query`create table users (name text not null)`).tag).toBe(
 | |
|     `CREATE TABLE`
 | |
|   );
 | |
| 
 | |
|   expect((await pg.query`insert into users (name) values (${input})`).tag).toBe(
 | |
|     `INSERT 0 1`
 | |
|   );
 | |
| 
 | |
|   const [{ name }] = await pg.query<{ name: string }>`
 | |
|     select name from users
 | |
|   `.first();
 | |
| 
 | |
|   expect(name).toBe(input);
 | |
| });
 | |
| 
 | |
| Deno.test(`pubsub`, async () => {
 | |
|   await using pg = await connect();
 | |
|   const sent: string[] = [];
 | |
| 
 | |
|   await using ch = await pg.listen(`my channel`, (payload) => {
 | |
|     expect(payload).toBe(sent.shift());
 | |
|   });
 | |
| 
 | |
|   for (let i = 0; i < 5; i++) {
 | |
|     const payload = `test payload ${i}`;
 | |
|     sent.push(payload);
 | |
|     await ch.notify(payload);
 | |
|   }
 | |
| });
 | |
| 
 | |
| Deno.test(`transactions`, async () => {
 | |
|   await using pg = await connect();
 | |
| 
 | |
|   await pg.begin(async (pg) => {
 | |
|     await pg.begin(async (pg, tx) => {
 | |
|       await pg.query`create table my_table (field text not null)`;
 | |
|       await tx.rollback();
 | |
|     });
 | |
| 
 | |
|     await expect(pg.query`select * from my_table`).rejects.toThrow(
 | |
|       PostgresError
 | |
|     );
 | |
|   });
 | |
| 
 | |
|   await expect(pg.query`select * from my_table`).rejects.toThrow(PostgresError);
 | |
| 
 | |
|   await pg.begin(async (pg) => {
 | |
|     await pg.begin(async (pg, tx) => {
 | |
|       await pg.begin(async (pg, tx) => {
 | |
|         await pg.begin(async (pg) => {
 | |
|           await pg.query`create table my_table (field text not null)`;
 | |
|         });
 | |
|         await tx.commit();
 | |
|       });
 | |
| 
 | |
|       expect(await pg.query`select * from my_table`.count()).toBe(0);
 | |
|       await tx.rollback();
 | |
|     });
 | |
| 
 | |
|     await expect(pg.query`select * from my_table`).rejects.toThrow(
 | |
|       PostgresError
 | |
|     );
 | |
|   });
 | |
| });
 |