Skip to content

Commit 1fe79fd

Browse files
Merge pull request #14 from ubccsss/daniel/node-pg-tests
Added tests for CRUD query functions and implemented DB helpers/ItemIndividualQuery
2 parents fd1f4ea + f47d3a9 commit 1fe79fd

33 files changed

+1012
-61
lines changed

.env.test

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
POSTGRES_USER=admin
22
POSTGRES_PASSWORD=root
3+
POSTGRES_HOST=localhost
4+
POSTGRES_PORT=5432
35
POSTGRES_DB=test_data
46
POSTGRES_DATA_PATH="database/test_data" # Relative path to directory holding DB data
57
POSTGRES_INIT_PATH="database/init_test" # Relative path to directory containing init files

.kanelrc.js

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -27,21 +27,9 @@ module.exports = {
2727
preDeleteOutputFolder: true,
2828
outputPath: "./src/types/db",
2929

30-
// Postgres bigints are assumed to represent monetary values and converted to Dinero.js objects
30+
// Postgres bigints are mapped to JS bigints
3131
customTypeMap: {
32-
"pg_catalog.tsvector": "string",
33-
"pg_catalog.bpchar": "string",
34-
"pg_catalog.int8": {
35-
name: "Dinero",
36-
typeImports: [
37-
{
38-
name: "Dinero",
39-
path: "dinero.js",
40-
isAbsolute: true,
41-
isDefault: false
42-
}
43-
]
44-
}
32+
"pg_catalog.int8": "bigint"
4533
},
4634

4735
// This implementation will generate flavored instead of branded types.

database/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ When setting up the `.env` file, take care to use the correct database names sin
1111
```bash
1212
POSTGRES_USER="<username>"
1313
POSTGRES_PASSWORD="<password>"
14+
POSTGRES_HOST=localhost
15+
POSTGRES_PORT=5432
1416
POSTGRES_DB=["cube_data"|"test_data"]
1517
POSTGRES_DATA_PATH=["database/data"|"database/test_data"]
1618
POSTGRES_INIT_PATH=["database/init"|"database/init_test"]

docker-compose.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ services:
55
image: postgres
66
hostname: localhost
77
ports:
8-
- "5432:5432"
8+
- "5432:${POSTGRES_PORT}"
99
environment:
1010
POSTGRES_USER: ${POSTGRES_USER}
1111
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}

package-lock.json

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"chai": "^5.0.0",
2020
"cors": "^2.8.5",
2121
"dinero.js": "^1.9.1",
22+
"dotenv": "^16.4.1",
2223
"eslint": "^8.56.0",
2324
"eslint-plugin-import": "^2.29.1",
2425
"eslint-plugin-jsdoc": "^48.0.2",
@@ -34,7 +35,7 @@
3435
"scripts": {
3536
"start": "tsx src/App.ts",
3637
"lint": "eslint src test --ext .ts",
37-
"test": "mocha --require tsx --timeout 10000 --extension .spec.ts --recursive test",
38+
"test": "mocha --require tsx --require dotenv/config --timeout 10000 --extension .spec.ts --recursive test --exit dotenv_config_path=./.env.test",
3839
"fix": "npm run lint -- --fix"
3940
},
4041
"description": "### What will you be building, and how will it be used?",

src/App.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import Server from "./rest/Server";
2+
import "dotenv/config";
23

34
/**
45
* Main app class that is run with the node command. Starts the server.

src/db/DB.ts

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,26 @@
1-
import {Pool} from "pg";
1+
import {Pool, types} from "pg";
22
import type {QueryResult} from "pg";
33

4-
export default class DB {
5-
private pool: Pool;
4+
const UINT8_OID = 20;
5+
types.setTypeParser(UINT8_OID, (val) => BigInt(val));
66

7-
constructor() {
8-
this.pool = new Pool();
9-
}
107

11-
public query(text: string, params: any[]): Promise<QueryResult> {
12-
return this.pool.query(text, params);
13-
}
8+
const pool = new Pool({
9+
user: process.env.POSTGRES_USER,
10+
password: process.env.POSTGRES_PASSWORD,
11+
host: process.env.POSTGRES_HOST,
12+
port: process.env.POSTGRES_PORT,
13+
database: process.env.POSTGRES_DB,
14+
});
1415

15-
}
16+
pool.on("error", (err, client) => {
17+
console.log(`Client in pool experienced error: ${err}`);
18+
});
19+
20+
export const query = async (text: string, params?: any[]): Promise<QueryResult<any>> => {
21+
return await pool.query(text, params);
22+
};
23+
24+
export const checkoutClient = async () => {
25+
return await pool.connect();
26+
};

src/db/queries/ItemIndividualQuery.ts

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,69 @@ import ItemIndividual,
22
{ItemId, ItemIndividualInitializer, ItemIndividualMutator} from "../../types/db/public/ItemIndividual";
33
import {Category} from "../../types/db/public/ValidCategory";
44
import {SimpleCrudQueryable} from "../Queryable";
5+
import * as DB from "../../db/DB";
56

67
const simpleCrudQueries:
78
SimpleCrudQueryable<ItemIndividual, ItemIndividualInitializer, ItemIndividualMutator, ItemId> = {
89
async create(object: ItemIndividualInitializer): Promise<ItemIndividual> {
9-
throw new Error("Method not implemented.");
10+
// Object.keys and Object.values return things in the same order so this is safe
11+
const keys = Object.keys(object);
12+
const values = Object.values(object);
13+
14+
const queryResponse = await DB.query(
15+
`INSERT INTO item_individual (${keys.join(",")})` +
16+
`VALUES (${keys.map((prop, i) => `$${i + 1}`).join(",")})` +
17+
"RETURNING *",
18+
values
19+
);
20+
if (queryResponse.rows.length === 1) {
21+
return queryResponse.rows[0];
22+
} else {
23+
return null;
24+
}
1025
},
1126

1227
async read(itemId: ItemId): Promise<ItemIndividual> {
13-
throw new Error("Method not implemented.");
28+
const queryResponse = await DB.query(
29+
"SELECT * FROM item_individual WHERE item_id=$1",
30+
[itemId]
31+
);
32+
if (queryResponse.rows.length === 1) {
33+
return queryResponse.rows[0];
34+
} else {
35+
return null;
36+
}
1437
},
1538

1639
async readAll(): Promise<ItemIndividual[]> {
17-
throw new Error("Method not implemented.");
40+
const queryResponse = await DB.query("SELECT * FROM item_individual");
41+
return queryResponse.rows;
1842
},
1943

2044
async update(itemId: ItemId, mutateObject: ItemIndividualMutator): Promise<ItemIndividual> {
21-
throw new Error("Method not implemented.");
45+
if (Object.keys(mutateObject).length === 0) {
46+
return null;
47+
}
48+
49+
// Use i+2 for parameter so that $1 is reserved for the item id
50+
const keys = Object.keys(mutateObject).map((prop, i) => `${prop}=$${i + 2}`);
51+
const queryResponse = await DB.query(
52+
`UPDATE item_individual SET ${keys.join(",")} WHERE item_id=$1 RETURNING *`,
53+
[itemId, ...Object.values(mutateObject)]
54+
);
55+
if (queryResponse.rows.length === 1) {
56+
return queryResponse.rows[0];
57+
} else {
58+
return null;
59+
}
2260
},
2361

2462
async delete(itemId: ItemId): Promise<boolean> {
25-
throw new Error("Method not implemented.");
63+
const queryResponse = await DB.query(
64+
"DELETE FROM item_individual WHERE item_id=$1",
65+
[itemId]
66+
);
67+
return queryResponse.rowCount === 1;
2668
}
2769
};
2870

src/environment.d.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
declare global {
2+
namespace NodeJS {
3+
interface ProcessEnv {
4+
POSTGRES_USER: string;
5+
POSTGRES_PASSWORD: string;
6+
POSTGRES_HOST: string;
7+
POSTGRES_PORT: number;
8+
POSTGRES_DB: string;
9+
POSTGRES_DATA_PATH: string;
10+
POSTGRES_INIT_PATH: string;
11+
}
12+
}
13+
}
14+
15+
// If this file has no import/export statements (i.e. is a script)
16+
// convert it into a module by adding an empty export statement.
17+
export {};

0 commit comments

Comments
 (0)