Skip to content

Commit f1b8d02

Browse files
Architecting tests and mocking seed data. Refactor, rename 'Set' APIs to 'Match'
1 parent c52ff1c commit f1b8d02

27 files changed

Lines changed: 1471 additions & 242 deletions

backend/CONTRIBUTING.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
## Backend HTTP server
2+
3+
Much of the structure of this project is inspired by [this set of recommendations](https://github.com/zhanymkanov/fastapi-best-practices) on structuring a backend project, despite this project not using FastAPI.

backend/package.json

Lines changed: 32 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,33 @@
11
{
2-
"name": "backend",
3-
"type": "module",
4-
"scripts": {
5-
"dev": "tsx watch src/index.ts",
6-
"build": "tsc",
7-
"start": "node dist/index.js",
8-
"test": "vitest",
9-
"check": "biome check src/",
10-
"lint": "biome lint src/"
11-
},
12-
"dependencies": {
13-
"@hono/node-server": "^1.19.6",
14-
"@hono/standard-validator": "^0.2.0",
15-
"@hono/zod-validator": "^0.7.5",
16-
"better-sqlite3": "^12.4.6",
17-
"hono": "^4.10.6",
18-
"hono-openapi": "^1.1.1",
19-
"knex": "^3.1.0",
20-
"pino": "^10.1.0",
21-
"zod": "^4.1.13"
22-
},
23-
"devDependencies": {
24-
"@biomejs/biome": "^2.3.7",
25-
"@types/node": "^20.11.17",
26-
"tsx": "^4.7.1",
27-
"typescript": "^5.8.3",
28-
"vite-tsconfig-paths": "^5.1.4",
29-
"vitest": "^4.0.14"
30-
}
31-
}
2+
"name": "backend",
3+
"type": "module",
4+
"scripts": {
5+
"dev": "tsx watch src/index.ts",
6+
"build": "tsc",
7+
"start": "node dist/index.js",
8+
"test": "vitest",
9+
"test:unit": "vitest unit",
10+
"check": "biome check src/",
11+
"lint": "biome lint src/"
12+
},
13+
"dependencies": {
14+
"@hono/node-server": "^1.19.6",
15+
"@hono/standard-validator": "^0.2.0",
16+
"@hono/zod-validator": "^0.7.5",
17+
"better-sqlite3": "^12.4.6",
18+
"hono": "^4.10.6",
19+
"hono-openapi": "^1.1.1",
20+
"knex": "^3.1.0",
21+
"pino": "^10.1.0",
22+
"zod": "^4.1.13"
23+
},
24+
"devDependencies": {
25+
"@biomejs/biome": "^2.3.7",
26+
"@faker-js/faker": "^10.1.0",
27+
"@types/node": "^20.11.17",
28+
"tsx": "^4.7.1",
29+
"typescript": "^5.8.3",
30+
"vite-tsconfig-paths": "^5.1.4",
31+
"vitest": "^4.0.14"
32+
}
33+
}

backend/pnpm-lock.yaml

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

backend/src/db/SeedSource.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { seed as SSBUCharacters_seed } from "@seeds/SSBUCharacters";
2+
import pino from "pino";
3+
4+
const log = pino();
5+
6+
// Seek more customizable API where list of seeds can be provided to the seedsource
7+
export class SeedSource {
8+
getSeeds() {
9+
return Promise.resolve(["SSBUCharacters"]);
10+
}
11+
12+
getSeed(seed) {
13+
log.info(`Seeding ${seed}`);
14+
switch (seed) {
15+
case "SSBUCharacters":
16+
return {
17+
async seed(knex) {
18+
await SSBUCharacters_seed(knex);
19+
},
20+
};
21+
}
22+
}
23+
}

backend/src/db/init_tables.ts

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
11
import { knexDb } from "@db/knexfile";
2-
import { SetCharacterTable, SetResultTable, SetTable, SSBUCharTable } from "@v1/set/models";
2+
import {
3+
MatchCharacterTable,
4+
MatchPlayerTable,
5+
MatchTable,
6+
SSBUCharTable,
7+
} from "@v1/match/models";
8+
import { MatchWinnerView } from "@v1/match/views";
39
import type { Knex } from "knex";
410

511
import pino from "pino";
612

713
const log = pino();
814

9-
const tables = [SetTable, SetResultTable, SetCharacterTable, SSBUCharTable];
15+
const tables = [MatchTable, MatchPlayerTable, MatchCharacterTable, SSBUCharTable];
16+
const views = [MatchWinnerView];
1017

1118
async function create_table_if_notexists(
1219
db: Knex = knexDb,
@@ -19,7 +26,9 @@ async function create_table_if_notexists(
1926
await db.schema.createTable(tableName, callback);
2027
log.info(`${tableName} table successfully initialized.`);
2128
} else {
22-
log.info(`Database already contains ${tableName} table. Skipping initialization.`);
29+
log.info(
30+
`Database already contains ${tableName} table. Skipping initialization.`,
31+
);
2332
}
2433
}
2534

@@ -32,9 +41,21 @@ export async function init_tables(db: Knex = knexDb) {
3241
await trx.commit();
3342
}
3443

44+
export async function init_views(db: Knex = knexDb) {
45+
const trx = await db.transaction();
46+
for (const _view of views) {
47+
const view = _view(db);
48+
await trx.schema.createViewOrReplace(view.view_name, view.initialize);
49+
log.info(`${view.view_name} view successfully initialized.`);
50+
}
51+
await trx.commit();
52+
}
53+
3554
export async function teardown(db: Knex = knexDb) {
3655
if (process.env.NODE_ENV === "production") {
37-
log.error("`teardown` was called on a production database -- no action will be performed");
56+
log.error(
57+
"`teardown` was called on a production database -- no action will be performed",
58+
);
3859
return;
3960
}
4061
const trx = await db.transaction();

backend/src/db/knexfile.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { SeedSource } from "@db/SeedSource";
12
import knex, { type Knex } from "knex";
23

34
// TODO: https://knexjs.org/guide/#log
@@ -7,7 +8,7 @@ export const config = {
78
filename: "./bot_data.db",
89
},
910
seeds: {
10-
director: "./seeds/"
11+
seedSource: new SeedSource(),
1112
},
1213
useNullAsDefault: true,
1314
};

backend/src/index.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,18 @@
1-
import { init_tables, teardown } from "@db/init_tables";
1+
import { init_tables, init_views, teardown } from "@db/init_tables";
22
import { serve } from "@hono/node-server";
3-
import set_router from "@v1/set/router";
3+
import match_router from "@v1/match/router";
44
import user_router from "@v1/user/router";
55
import { Hono } from "hono";
6+
import { trimTrailingSlash } from "hono/trailing-slash";
67
import { openAPIRouteHandler } from "hono-openapi";
7-
import {
8-
trimTrailingSlash,
9-
} from 'hono/trailing-slash'
10-
118

129
const app = new Hono();
1310

1411
await teardown();
1512
await init_tables();
13+
await init_views();
1614

17-
18-
app.use(trimTrailingSlash())
15+
app.use(trimTrailingSlash());
1916

2017
app.get("/", (c) => {
2118
return c.text("Hello Hono!");
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { seed as SSBUCharacters_seed } from "@seeds/SSBUCharacters";
2+
import { seed as FakeMatches_seed } from "@v1/match/tests/mock.models";
3+
import pino from "pino";
4+
5+
const log = pino();
6+
7+
export class MockSeedSource {
8+
getSeeds() {
9+
return Promise.resolve(["SSBUCharacters", "FakeMatches"]);
10+
}
11+
12+
getSeed(seed) {
13+
log.info(`Seeding ${seed}`);
14+
switch (seed) {
15+
case "SSBUCharacters":
16+
return {
17+
async seed(knex) {
18+
await SSBUCharacters_seed(knex);
19+
},
20+
};
21+
case "FakeMatches":
22+
return {
23+
async seed(knex) {
24+
await FakeMatches_seed(knex);
25+
},
26+
};
27+
}
28+
}
29+
}

backend/src/tests/mock.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// General utilities for generating mock data
2+
import { faker } from "@faker-js/faker";
3+
import { ssbu_character_names } from "@seeds/SSBUCharacters";
4+
5+
export const snowflake = () =>
6+
faker.string.numeric({
7+
length: 18,
8+
});
9+
10+
export const randint = (max) => {
11+
return Math.floor(Math.random() * max);
12+
};
13+
14+
export const rand_character_array: Array<(typeof ssbu_character_names)[number]> = () =>
15+
Array.from(
16+
{ length: 1 + randint(4) },
17+
() => ssbu_character_names[randint(ssbu_character_names.length)],
18+
);
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1+
import { MockSeedSource } from "@tests/MockSeedSource";
12
import knex, { type Knex } from "knex";
23

3-
export const test_config = {
4+
export const test_config: Knex.Config = {
45
client: "better-sqlite3",
56
connection: {
67
filename: ":memory:",
78
},
89
seeds: {
9-
director: "./seeds/"
10+
seedSource: new MockSeedSource(),
1011
},
1112
useNullAsDefault: true,
1213
};

0 commit comments

Comments
 (0)