From 72eb4bc3a091715b4ed30b5fc7a2153985a0c9cf Mon Sep 17 00:00:00 2001 From: Wes Copeland Date: Sat, 28 Feb 2026 11:40:30 -0500 Subject: [PATCH 1/2] ci: use run-p for parallel task execution --- bun.lock | 27 ++++++++++++++++++++++++--- package.json | 3 ++- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/bun.lock b/bun.lock index 955246e..0b448f7 100644 --- a/bun.lock +++ b/bun.lock @@ -19,6 +19,7 @@ "@typescript/native-preview": "^7.0.0-dev.20260228.1", "@vitest/coverage-v8": "4.0.18", "drizzle-kit": "0.31.4", + "npm-run-all2": "^8.0.4", "oxfmt": "^0.35.0", "oxlint": "^1.50.0", "vitest": "^4.0.18", @@ -404,7 +405,7 @@ "ajv": ["ajv@6.14.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw=="], - "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + "ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], @@ -556,7 +557,7 @@ "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], - "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + "isexe": ["isexe@3.1.5", "", {}, "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w=="], "istanbul-lib-coverage": ["istanbul-lib-coverage@3.2.2", "", {}, "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg=="], @@ -574,6 +575,8 @@ "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], + "json-parse-even-better-errors": ["json-parse-even-better-errors@4.0.0", "", {}, "sha512-lR4MXjGNgkJc7tkQ97kb2nuEMnNCyU//XYVH0MKTGcXEiSudQ5MKGKen3C5QubYy0vmq+JGitUg92uuywGEwIA=="], + "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], @@ -604,6 +607,8 @@ "make-dir": ["make-dir@4.0.0", "", { "dependencies": { "semver": "^7.5.3" } }, "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw=="], + "memorystream": ["memorystream@0.3.1", "", {}, "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw=="], + "minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], @@ -620,6 +625,10 @@ "node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="], + "npm-normalize-package-bin": ["npm-normalize-package-bin@4.0.0", "", {}, "sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w=="], + + "npm-run-all2": ["npm-run-all2@8.0.4", "", { "dependencies": { "ansi-styles": "^6.2.1", "cross-spawn": "^7.0.6", "memorystream": "^0.3.1", "picomatch": "^4.0.2", "pidtree": "^0.6.0", "read-package-json-fast": "^4.0.0", "shell-quote": "^1.7.3", "which": "^5.0.0" }, "bin": { "run-p": "bin/run-p/index.js", "run-s": "bin/run-s/index.js", "npm-run-all": "bin/npm-run-all/index.js", "npm-run-all2": "bin/npm-run-all/index.js" } }, "sha512-wdbB5My48XKp2ZfJUlhnLVihzeuA1hgBnqB2J9ahV77wLS+/YAJAlN8I+X3DIFIPZ3m5L7nplmlbhNiFDmXRDA=="], + "obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="], "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], @@ -644,6 +653,8 @@ "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], + "pidtree": ["pidtree@0.6.0", "", { "bin": { "pidtree": "bin/pidtree.js" } }, "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g=="], + "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], @@ -652,6 +663,8 @@ "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], + "read-package-json-fast": ["read-package-json-fast@4.0.0", "", { "dependencies": { "json-parse-even-better-errors": "^4.0.0", "npm-normalize-package-bin": "^4.0.0" } }, "sha512-qpt8EwugBWDw2cgE2W+/3oxC+KTez2uSVR8JU9Q36TXPAGCaozfQUs59v4j4GFpWTaw0i6hAZSvOmu1J0uOEUg=="], + "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], @@ -666,6 +679,8 @@ "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + "shell-quote": ["shell-quote@1.8.3", "", {}, "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw=="], + "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="], "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], @@ -722,7 +737,7 @@ "whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="], - "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + "which": ["which@5.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ=="], "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="], @@ -758,8 +773,12 @@ "@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], + "chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + "cross-fetch/node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], + "cross-spawn/which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + "make-dir/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], "vite/esbuild": ["esbuild@0.27.3", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.3", "@esbuild/android-arm": "0.27.3", "@esbuild/android-arm64": "0.27.3", "@esbuild/android-x64": "0.27.3", "@esbuild/darwin-arm64": "0.27.3", "@esbuild/darwin-x64": "0.27.3", "@esbuild/freebsd-arm64": "0.27.3", "@esbuild/freebsd-x64": "0.27.3", "@esbuild/linux-arm": "0.27.3", "@esbuild/linux-arm64": "0.27.3", "@esbuild/linux-ia32": "0.27.3", "@esbuild/linux-loong64": "0.27.3", "@esbuild/linux-mips64el": "0.27.3", "@esbuild/linux-ppc64": "0.27.3", "@esbuild/linux-riscv64": "0.27.3", "@esbuild/linux-s390x": "0.27.3", "@esbuild/linux-x64": "0.27.3", "@esbuild/netbsd-arm64": "0.27.3", "@esbuild/netbsd-x64": "0.27.3", "@esbuild/openbsd-arm64": "0.27.3", "@esbuild/openbsd-x64": "0.27.3", "@esbuild/openharmony-arm64": "0.27.3", "@esbuild/sunos-x64": "0.27.3", "@esbuild/win32-arm64": "0.27.3", "@esbuild/win32-ia32": "0.27.3", "@esbuild/win32-x64": "0.27.3" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg=="], @@ -810,6 +829,8 @@ "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@5.0.4", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg=="], + "cross-spawn/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + "vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.3", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg=="], "vite/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.27.3", "", { "os": "android", "cpu": "arm" }, "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA=="], diff --git a/package.json b/package.json index e00a6d3..e4b536d 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "lint:fix": "oxlint src --fix", "test": "vitest run", "test:watch": "vitest", - "verify": "bun run format:check && bun run lint && bun run tsc && vitest run", + "verify": "run-p format:check lint tsc test", "postinstall": "git config core.hooksPath .hooks && chmod +x .hooks/*" }, "dependencies": { @@ -54,6 +54,7 @@ "@typescript/native-preview": "^7.0.0-dev.20260228.1", "@vitest/coverage-v8": "4.0.18", "drizzle-kit": "0.31.4", + "npm-run-all2": "^8.0.4", "oxfmt": "^0.35.0", "oxlint": "^1.50.0", "vitest": "^4.0.18" From 8aeb27919083dda3beef110c2ed3b4266359bc52 Mon Sep 17 00:00:00 2001 From: Wes Copeland Date: Sat, 28 Feb 2026 11:52:53 -0500 Subject: [PATCH 2/2] test: use an in-memory db instead of mocks --- CONTRIBUTING.md | 3 +- package.json | 2 +- src/services/poll.service.test.ts | 207 ++++++++++---------------- src/services/team.service.test.ts | 198 ++++++++++-------------- src/services/uwc-poll.service.test.ts | 49 +++--- src/test/create-test-db.ts | 25 ++++ src/test/mocks/database.mock.ts | 51 ------- 7 files changed, 210 insertions(+), 325 deletions(-) create mode 100644 src/test/create-test-db.ts delete mode 100644 src/test/mocks/database.mock.ts diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5e93675..14ea9cc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -161,7 +161,8 @@ export default { ### Testing - Write tests for services and utilities. -- Use the existing mock utilities in `src/test/mocks/`. +- Use `createTestDb()` from `src/test/create-test-db.ts` for service tests that need a real database. +- Use the existing mock utilities in `src/test/mocks/` for Discord and API mocks. - Test both success and error cases. - Keep tests focused and independent. diff --git a/package.json b/package.json index e4b536d..1aa0926 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "lint:fix": "oxlint src --fix", "test": "vitest run", "test:watch": "vitest", - "verify": "run-p format:check lint tsc test", + "verify": "run-p format lint:fix tsc test", "postinstall": "git config core.hooksPath .hooks && chmod +x .hooks/*" }, "dependencies": { diff --git a/src/services/poll.service.test.ts b/src/services/poll.service.test.ts index 9f24329..1af5c75 100644 --- a/src/services/poll.service.test.ts +++ b/src/services/poll.service.test.ts @@ -1,32 +1,23 @@ -import { beforeEach, describe, expect, it, vi } from "vitest"; +import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -import { createMockPoll, createMockPollVote } from "../test/mocks/database.mock"; +import { cleanAllTables, createTestDb } from "../test/create-test-db"; import { PollService } from "./poll.service"; -const { mockDb } = vi.hoisted(() => { - const mockDb: any = { - select: vi.fn(() => mockDb), - from: vi.fn(() => mockDb), - where: vi.fn(() => Promise.resolve([])), - insert: vi.fn(() => mockDb), - values: vi.fn(() => mockDb), - returning: vi.fn(() => Promise.resolve([])), - }; - - return { mockDb }; -}); +let testDb: Awaited>; -vi.mock("../database/db", () => ({ db: mockDb })); +vi.mock("../database/db", () => ({ + get db() { + return testDb; + }, +})); describe("Service: PollService", () => { - beforeEach(() => { - // ... reset all mocks before each test ... - mockDb.select.mockClear().mockReturnValue(mockDb); - mockDb.from.mockClear().mockReturnValue(mockDb); - mockDb.where.mockClear().mockResolvedValue([]); - mockDb.insert.mockClear().mockReturnValue(mockDb); - mockDb.values.mockClear().mockReturnValue(mockDb); - mockDb.returning.mockClear().mockResolvedValue([]); + beforeAll(async () => { + testDb = await createTestDb(); + }); + + beforeEach(async () => { + await cleanAllTables(testDb); }); describe("createPoll", () => { @@ -36,21 +27,6 @@ describe("Service: PollService", () => { }); it("creates a new poll with the provided details", async () => { - // ARRANGE - const mockPoll = createMockPoll({ - messageId: "msg123", - channelId: "ch456", - creatorId: "user789", - question: "What's your favorite color?", - options: JSON.stringify([ - { text: "Red", votes: [] }, - { text: "Blue", votes: [] }, - { text: "Green", votes: [] }, - ]), - endTime: null, - }); - mockDb.returning.mockResolvedValue([mockPoll]); - // ACT const result = await PollService.createPoll( "msg123", @@ -61,27 +37,22 @@ describe("Service: PollService", () => { ); // ASSERT - expect(mockDb.insert).toHaveBeenCalledWith(expect.anything()); - expect(mockDb.values).toHaveBeenCalledWith({ - messageId: "msg123", - channelId: "ch456", - creatorId: "user789", - question: "What's your favorite color?", - options: JSON.stringify([ - { text: "Red", votes: [] }, - { text: "Blue", votes: [] }, - { text: "Green", votes: [] }, - ]), - endTime: undefined, - }); - expect(result).toEqual(mockPoll); + expect(result.messageId).toEqual("msg123"); + expect(result.channelId).toEqual("ch456"); + expect(result.creatorId).toEqual("user789"); + expect(result.question).toEqual("What's your favorite color?"); + expect(JSON.parse(result.options)).toEqual([ + { text: "Red", votes: [] }, + { text: "Blue", votes: [] }, + { text: "Green", votes: [] }, + ]); + expect(result.endTime).toBeNull(); + expect(result.id).toBeDefined(); }); it("creates a poll with an end time when provided", async () => { // ARRANGE const endTime = new Date("2024-12-31T23:59:59Z"); - const mockPoll = createMockPoll({ endTime }); - mockDb.returning.mockResolvedValue([mockPoll]); // ACT const result = await PollService.createPoll( @@ -94,11 +65,6 @@ describe("Service: PollService", () => { ); // ASSERT - expect(mockDb.values).toHaveBeenCalledWith( - expect.objectContaining({ - endTime, - }), - ); expect(result.endTime).toEqual(endTime); }); }); @@ -111,23 +77,17 @@ describe("Service: PollService", () => { it("returns a poll when found", async () => { // ARRANGE - const mockPoll = createMockPoll({ messageId: "msg123" }); - mockDb.where.mockResolvedValue([mockPoll]); + await PollService.createPoll("msg123", "ch456", "user789", "Question?", ["A", "B"]); // ACT const result = await PollService.getPoll("msg123"); // ASSERT - expect(mockDb.select).toHaveBeenCalled(); - expect(mockDb.from).toHaveBeenCalledWith(expect.anything()); - expect(mockDb.where).toHaveBeenCalled(); - expect(result).toEqual(mockPoll); + expect(result).not.toBeNull(); + expect(result?.messageId).toEqual("msg123"); }); it("returns null when poll is not found", async () => { - // ARRANGE - mockDb.where.mockResolvedValue([]); - // ACT const result = await PollService.getPoll("nonexistent"); @@ -144,50 +104,43 @@ describe("Service: PollService", () => { it("adds a vote when user has not voted", async () => { // ARRANGE - // ... mock getUserVote to return null ... - mockDb.where.mockResolvedValueOnce([]); + const poll = await PollService.createPoll("msg1", "ch1", "creator1", "Q?", ["A", "B"]); // ACT - const result = await PollService.addVote(1, "user123", 0); + const result = await PollService.addVote(poll.id, "user123", 0); // ASSERT - expect(mockDb.insert).toHaveBeenCalledWith(expect.anything()); - expect(mockDb.values).toHaveBeenCalledWith({ - pollId: 1, - userId: "user123", - optionIndex: 0, - }); expect(result).toEqual(true); + + const vote = await PollService.getUserVote(poll.id, "user123"); + expect(vote).not.toBeNull(); + expect(vote?.optionIndex).toEqual(0); }); it("returns false when user has already voted", async () => { // ARRANGE - const existingVote = createMockPollVote(); - // ... mock getUserVote to return existing vote ... - mockDb.where.mockResolvedValueOnce([existingVote]); + const poll = await PollService.createPoll("msg1", "ch1", "creator1", "Q?", ["A", "B"]); + await PollService.addVote(poll.id, "user123", 0); // ACT - const result = await PollService.addVote(1, "user123", 1); + const result = await PollService.addVote(poll.id, "user123", 1); // ASSERT - expect(mockDb.insert).not.toHaveBeenCalled(); expect(result).toEqual(false); }); it("allows voting for different option indices", async () => { // ARRANGE - mockDb.where.mockResolvedValueOnce([]); // ... no existing vote ... + const poll = await PollService.createPoll("msg1", "ch1", "creator1", "Q?", ["A", "B", "C"]); // ACT - const result = await PollService.addVote(1, "user123", 2); + const result = await PollService.addVote(poll.id, "user123", 2); // ASSERT - expect(mockDb.values).toHaveBeenCalledWith({ - pollId: 1, - userId: "user123", - optionIndex: 2, - }); expect(result).toEqual(true); + + const vote = await PollService.getUserVote(poll.id, "user123"); + expect(vote?.optionIndex).toEqual(2); }); }); @@ -199,29 +152,25 @@ describe("Service: PollService", () => { it("returns a vote when user has voted", async () => { // ARRANGE - const mockVote = createMockPollVote({ - pollId: 1, - userId: "user123", - optionIndex: 1, - }); - mockDb.where.mockResolvedValue([mockVote]); + const poll = await PollService.createPoll("msg1", "ch1", "creator1", "Q?", ["A", "B"]); + await PollService.addVote(poll.id, "user123", 1); // ACT - const result = await PollService.getUserVote(1, "user123"); + const result = await PollService.getUserVote(poll.id, "user123"); // ASSERT - expect(mockDb.select).toHaveBeenCalled(); - expect(mockDb.from).toHaveBeenCalledWith(expect.anything()); - expect(mockDb.where).toHaveBeenCalled(); - expect(result).toEqual(mockVote); + expect(result).not.toBeNull(); + expect(result?.pollId).toEqual(poll.id); + expect(result?.userId).toEqual("user123"); + expect(result?.optionIndex).toEqual(1); }); it("returns null when user has not voted", async () => { // ARRANGE - mockDb.where.mockResolvedValue([]); + const poll = await PollService.createPoll("msg1", "ch1", "creator1", "Q?", ["A", "B"]); // ACT - const result = await PollService.getUserVote(1, "user456"); + const result = await PollService.getUserVote(poll.id, "user456"); // ASSERT expect(result).toBeNull(); @@ -236,17 +185,15 @@ describe("Service: PollService", () => { it("returns vote counts by option index", async () => { // ARRANGE - const mockVotes = [ - createMockPollVote({ optionIndex: 0 }), - createMockPollVote({ optionIndex: 0 }), - createMockPollVote({ optionIndex: 1 }), - createMockPollVote({ optionIndex: 0 }), - createMockPollVote({ optionIndex: 2 }), - ]; - mockDb.where.mockResolvedValue(mockVotes); + const poll = await PollService.createPoll("msg1", "ch1", "creator1", "Q?", ["A", "B", "C"]); + await PollService.addVote(poll.id, "user1", 0); + await PollService.addVote(poll.id, "user2", 0); + await PollService.addVote(poll.id, "user3", 1); + await PollService.addVote(poll.id, "user4", 0); + await PollService.addVote(poll.id, "user5", 2); // ACT - const results = await PollService.getPollResults(1); + const results = await PollService.getPollResults(poll.id); // ASSERT expect(results).toBeInstanceOf(Map); @@ -257,10 +204,10 @@ describe("Service: PollService", () => { it("returns empty map when there are no votes", async () => { // ARRANGE - mockDb.where.mockResolvedValue([]); + const poll = await PollService.createPoll("msg1", "ch1", "creator1", "Q?", ["A", "B"]); // ACT - const results = await PollService.getPollResults(1); + const results = await PollService.getPollResults(poll.id); // ASSERT expect(results.size).toEqual(0); @@ -268,15 +215,20 @@ describe("Service: PollService", () => { it("handles votes for non-sequential option indices", async () => { // ARRANGE - const mockVotes = [ - createMockPollVote({ optionIndex: 0 }), - createMockPollVote({ optionIndex: 5 }), - createMockPollVote({ optionIndex: 5 }), - ]; - mockDb.where.mockResolvedValue(mockVotes); + const poll = await PollService.createPoll("msg1", "ch1", "creator1", "Q?", [ + "A", + "B", + "C", + "D", + "E", + "F", + ]); + await PollService.addVote(poll.id, "user1", 0); + await PollService.addVote(poll.id, "user2", 5); + await PollService.addVote(poll.id, "user3", 5); // ACT - const results = await PollService.getPollResults(1); + const results = await PollService.getPollResults(poll.id); // ASSERT expect(results.get(0)).toEqual(1); @@ -293,25 +245,20 @@ describe("Service: PollService", () => { it("returns polls with no end time", async () => { // ARRANGE - const mockPolls = [ - createMockPoll({ id: 1, endTime: null }), - createMockPoll({ id: 2, endTime: null }), - ]; - mockDb.where.mockResolvedValue(mockPolls); + await PollService.createPoll("msg1", "ch1", "creator1", "Q1?", ["A", "B"]); + await PollService.createPoll("msg2", "ch1", "creator1", "Q2?", ["A", "B"]); + await PollService.createPoll("msg3", "ch1", "creator1", "Q3?", ["A", "B"], new Date()); // ACT const result = await PollService.getActivePolls(); // ASSERT - expect(mockDb.select).toHaveBeenCalled(); - expect(mockDb.from).toHaveBeenCalledWith(expect.anything()); - expect(mockDb.where).toHaveBeenCalled(); - expect(result).toEqual(mockPolls); + expect(result).toHaveLength(2); }); it("returns empty array when there are no active polls", async () => { // ARRANGE - mockDb.where.mockResolvedValue([]); + await PollService.createPoll("msg1", "ch1", "creator1", "Q?", ["A", "B"], new Date()); // ACT const result = await PollService.getActivePolls(); diff --git a/src/services/team.service.test.ts b/src/services/team.service.test.ts index 144216f..04743f7 100644 --- a/src/services/team.service.test.ts +++ b/src/services/team.service.test.ts @@ -1,36 +1,23 @@ -import { beforeEach, describe, expect, it, vi } from "vitest"; +import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -import { createMockTeam, createMockTeamMember } from "../test/mocks/database.mock"; +import { cleanAllTables, createTestDb } from "../test/create-test-db"; import { TeamService } from "./team.service"; -const { mockDb } = vi.hoisted(() => { - const mockDb: any = { - select: vi.fn(() => mockDb), - from: vi.fn(() => mockDb), - where: vi.fn(() => Promise.resolve([])), - insert: vi.fn(() => mockDb), - values: vi.fn(() => mockDb), - returning: vi.fn(() => Promise.resolve([])), - onConflictDoNothing: vi.fn(() => Promise.resolve()), - delete: vi.fn(() => mockDb), - }; - - return { mockDb }; -}); +let testDb: Awaited>; -vi.mock("../database/db", () => ({ db: mockDb })); +vi.mock("../database/db", () => ({ + get db() { + return testDb; + }, +})); describe("Service: TeamService", () => { - beforeEach(() => { - // ... reset all mocks before each test ... - mockDb.select.mockClear().mockReturnValue(mockDb); - mockDb.from.mockClear().mockReturnValue(mockDb); - mockDb.where.mockClear().mockResolvedValue([]); - mockDb.insert.mockClear().mockReturnValue(mockDb); - mockDb.values.mockClear().mockReturnValue(mockDb); - mockDb.returning.mockClear().mockResolvedValue([]); - mockDb.onConflictDoNothing.mockClear().mockResolvedValue(undefined); - mockDb.delete.mockClear().mockReturnValue(mockDb); + beforeAll(async () => { + testDb = await createTestDb(); + }); + + beforeEach(async () => { + await cleanAllTables(testDb); }); describe("createTeam", () => { @@ -40,21 +27,14 @@ describe("Service: TeamService", () => { }); it("creates a new team with the provided details", async () => { - // ARRANGE - const mockTeam = createMockTeam({ id: "test-team-id", name: "test-team" }); - mockDb.returning.mockResolvedValue([mockTeam]); - // ACT const result = await TeamService.createTeam("test-team-id", "test-team", "admin123"); // ASSERT - expect(mockDb.insert).toHaveBeenCalledWith(expect.anything()); - expect(mockDb.values).toHaveBeenCalledWith({ - id: "test-team-id", - name: "test-team", - addedBy: "admin123", - }); - expect(result).toEqual(mockTeam); + expect(result.id).toEqual("test-team-id"); + expect(result.name).toEqual("test-team"); + expect(result.addedBy).toEqual("admin123"); + expect(result.addedAt).toBeDefined(); }); }); @@ -66,23 +46,18 @@ describe("Service: TeamService", () => { it("returns a team when found", async () => { // ARRANGE - const mockTeam = createMockTeam({ id: "team123" }); - mockDb.where.mockResolvedValue([mockTeam]); + await TeamService.createTeam("team123", "My Team", "admin1"); // ACT const result = await TeamService.getTeam("team123"); // ASSERT - expect(mockDb.select).toHaveBeenCalled(); - expect(mockDb.from).toHaveBeenCalledWith(expect.anything()); - expect(mockDb.where).toHaveBeenCalled(); - expect(result).toEqual(mockTeam); + expect(result).not.toBeNull(); + expect(result?.id).toEqual("team123"); + expect(result?.name).toEqual("My Team"); }); it("returns null when team is not found", async () => { - // ARRANGE - mockDb.where.mockResolvedValue([]); - // ACT const result = await TeamService.getTeam("nonexistent"); @@ -99,18 +74,27 @@ describe("Service: TeamService", () => { it("adds a member to a team", async () => { // ARRANGE + await TeamService.createTeam("team123", "Team", "admin1"); // ACT await TeamService.addMember("team123", "user456", "admin789"); // ASSERT - expect(mockDb.insert).toHaveBeenCalledWith(expect.anything()); - expect(mockDb.values).toHaveBeenCalledWith({ - teamId: "team123", - userId: "user456", - addedBy: "admin789", - }); - expect(mockDb.onConflictDoNothing).toHaveBeenCalled(); + const isMember = await TeamService.isTeamMember("team123", "user456"); + expect(isMember).toEqual(true); + }); + + it("silently handles duplicate additions", async () => { + // ARRANGE + await TeamService.createTeam("team123", "Team", "admin1"); + await TeamService.addMember("team123", "user456", "admin789"); + + // ACT - adding the same member again should not throw. + await TeamService.addMember("team123", "user456", "admin789"); + + // ASSERT + const members = await TeamService.getTeamMembers("team123"); + expect(members).toEqual(["user456"]); }); }); @@ -122,31 +106,27 @@ describe("Service: TeamService", () => { it("removes an existing member from a team", async () => { // ARRANGE - const mockMember = createMockTeamMember(); - - // ... mock isTeamMember to return true ... - mockDb.where.mockResolvedValueOnce([mockMember]); + await TeamService.createTeam("team123", "Team", "admin1"); + await TeamService.addMember("team123", "user456", "admin1"); // ACT const result = await TeamService.removeMember("team123", "user456"); // ASSERT - expect(mockDb.delete).toHaveBeenCalledWith(expect.anything()); - expect(mockDb.where).toHaveBeenCalled(); expect(result).toEqual(true); + + const isMember = await TeamService.isTeamMember("team123", "user456"); + expect(isMember).toEqual(false); }); it("returns false when member does not exist", async () => { // ARRANGE - - // ... mock isTeamMember to return false ... - mockDb.where.mockResolvedValueOnce([]); + await TeamService.createTeam("team123", "Team", "admin1"); // ACT const result = await TeamService.removeMember("team123", "user456"); // ASSERT - expect(mockDb.delete).not.toHaveBeenCalled(); expect(result).toEqual(false); }); }); @@ -159,20 +139,24 @@ describe("Service: TeamService", () => { it("returns an array of user IDs for team members", async () => { // ARRANGE - const mockMembers = [{ userId: "user1" }, { userId: "user2" }, { userId: "user3" }]; - mockDb.where.mockResolvedValue(mockMembers); + await TeamService.createTeam("team123", "Team", "admin1"); + await TeamService.addMember("team123", "user1", "admin1"); + await TeamService.addMember("team123", "user2", "admin1"); + await TeamService.addMember("team123", "user3", "admin1"); // ACT const result = await TeamService.getTeamMembers("team123"); // ASSERT - expect(mockDb.select).toHaveBeenCalledWith({ userId: expect.anything() }); - expect(result).toEqual(["user1", "user2", "user3"]); + expect(result).toHaveLength(3); + expect(result).toContain("user1"); + expect(result).toContain("user2"); + expect(result).toContain("user3"); }); it("returns an empty array when team has no members", async () => { // ARRANGE - mockDb.where.mockResolvedValue([]); + await TeamService.createTeam("team123", "Team", "admin1"); // ACT const result = await TeamService.getTeamMembers("team123"); @@ -190,7 +174,8 @@ describe("Service: TeamService", () => { it("returns true when user is a team member", async () => { // ARRANGE - mockDb.where.mockResolvedValue([createMockTeamMember()]); + await TeamService.createTeam("team123", "Team", "admin1"); + await TeamService.addMember("team123", "user456", "admin1"); // ACT const result = await TeamService.isTeamMember("team123", "user456"); @@ -201,7 +186,7 @@ describe("Service: TeamService", () => { it("returns false when user is not a team member", async () => { // ARRANGE - mockDb.where.mockResolvedValue([]); + await TeamService.createTeam("team123", "Team", "admin1"); // ACT const result = await TeamService.isTeamMember("team123", "user456"); @@ -219,19 +204,14 @@ describe("Service: TeamService", () => { it("returns all teams", async () => { // ARRANGE - const mockTeams = [ - createMockTeam({ id: "team1", name: "Team One" }), - createMockTeam({ id: "team2", name: "Team Two" }), - ]; - mockDb.from.mockReturnValue(Promise.resolve(mockTeams)); + await TeamService.createTeam("team1", "Team One", "admin1"); + await TeamService.createTeam("team2", "Team Two", "admin1"); // ACT const result = await TeamService.getAllTeams(); // ASSERT - expect(mockDb.select).toHaveBeenCalled(); - expect(mockDb.from).toHaveBeenCalledWith(expect.anything()); - expect(result).toEqual(mockTeams); + expect(result).toHaveLength(2); }); }); @@ -243,21 +223,17 @@ describe("Service: TeamService", () => { it("returns a team when found by name", async () => { // ARRANGE - const mockTeam = createMockTeam({ name: "test-team" }); - mockDb.where.mockResolvedValue([mockTeam]); + await TeamService.createTeam("team-id", "test-team", "admin1"); // ACT const result = await TeamService.getTeamByName("test-team"); // ASSERT - expect(mockDb.where).toHaveBeenCalled(); - expect(result).toEqual(mockTeam); + expect(result).not.toBeNull(); + expect(result?.name).toEqual("test-team"); }); it("returns null when team is not found by name", async () => { - // ARRANGE - mockDb.where.mockResolvedValue([]); - // ACT const result = await TeamService.getTeamByName("nonexistent"); @@ -274,27 +250,17 @@ describe("Service: TeamService", () => { it("adds a member when team exists", async () => { // ARRANGE - const mockTeam = createMockTeam({ id: "team123", name: "test-team" }); - - // ... mock getTeamByName ... - mockDb.where.mockResolvedValueOnce([mockTeam]); + await TeamService.createTeam("team123", "test-team", "admin1"); // ACT await TeamService.addMemberByTeamName("test-team", "user456", "admin789"); // ASSERT - expect(mockDb.insert).toHaveBeenCalled(); - expect(mockDb.values).toHaveBeenCalledWith({ - teamId: "team123", - userId: "user456", - addedBy: "admin789", - }); + const isMember = await TeamService.isTeamMember("team123", "user456"); + expect(isMember).toEqual(true); }); it("throws an error when team does not exist", async () => { - // ARRANGE - mockDb.where.mockResolvedValueOnce([]); - // ASSERT await expect( TeamService.addMemberByTeamName("nonexistent", "user456", "admin789"), @@ -310,31 +276,24 @@ describe("Service: TeamService", () => { it("removes a member when team and member exist", async () => { // ARRANGE - const mockTeam = createMockTeam({ id: "team123", name: "test-team" }); - const mockMember = createMockTeamMember(); - - // ... mock getTeamByName ... - mockDb.where.mockResolvedValueOnce([mockTeam]); - // ... mock isTeamMember ... - mockDb.where.mockResolvedValueOnce([mockMember]); + await TeamService.createTeam("team123", "test-team", "admin1"); + await TeamService.addMember("team123", "user456", "admin1"); // ACT const result = await TeamService.removeMemberByTeamName("test-team", "user456"); // ASSERT - expect(mockDb.delete).toHaveBeenCalled(); expect(result).toEqual(true); + + const isMember = await TeamService.isTeamMember("team123", "user456"); + expect(isMember).toEqual(false); }); it("returns false when team does not exist", async () => { - // ARRANGE - mockDb.where.mockResolvedValueOnce([]); - // ACT const result = await TeamService.removeMemberByTeamName("nonexistent", "user456"); // ASSERT - expect(mockDb.delete).not.toHaveBeenCalled(); expect(result).toEqual(false); }); }); @@ -347,25 +306,20 @@ describe("Service: TeamService", () => { it("returns team members when team exists", async () => { // ARRANGE - const mockTeam = createMockTeam({ id: "team123", name: "test-team" }); - const mockMembers = [{ userId: "user1" }, { userId: "user2" }]; - - // ... mock getTeamByName ... - mockDb.where.mockResolvedValueOnce([mockTeam]); - // ... mock getTeamMembers ... - mockDb.where.mockResolvedValueOnce(mockMembers); + await TeamService.createTeam("team123", "test-team", "admin1"); + await TeamService.addMember("team123", "user1", "admin1"); + await TeamService.addMember("team123", "user2", "admin1"); // ACT const result = await TeamService.getTeamMembersByName("test-team"); // ASSERT - expect(result).toEqual(["user1", "user2"]); + expect(result).toHaveLength(2); + expect(result).toContain("user1"); + expect(result).toContain("user2"); }); it("returns empty array when team does not exist", async () => { - // ARRANGE - mockDb.where.mockResolvedValueOnce([]); - // ACT const result = await TeamService.getTeamMembersByName("nonexistent"); diff --git a/src/services/uwc-poll.service.test.ts b/src/services/uwc-poll.service.test.ts index 36e4131..b07dc93 100644 --- a/src/services/uwc-poll.service.test.ts +++ b/src/services/uwc-poll.service.test.ts @@ -1,14 +1,23 @@ -import { afterEach, beforeEach, describe, expect, it } from "vitest"; +import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -import { db } from "../database/db"; -import { uwcPollResults, uwcPolls } from "../database/schema"; +import { cleanAllTables, createTestDb } from "../test/create-test-db"; import { UwcPollService } from "./uwc-poll.service"; +let testDb: Awaited>; + +vi.mock("../database/db", () => ({ + get db() { + return testDb; + }, +})); + describe("UwcPollService", () => { - // Clean up database after each test. - afterEach(async () => { - await db.delete(uwcPollResults); - await db.delete(uwcPolls); + beforeAll(async () => { + testDb = await createTestDb(); + }); + + beforeEach(async () => { + await cleanAllTables(testDb); }); describe("createUwcPoll", () => { @@ -31,11 +40,11 @@ describe("UwcPollService", () => { // ASSERT expect(poll).toBeDefined(); - expect(poll.messageId).toBe(pollData.messageId); - expect(poll.channelId).toBe(pollData.channelId); - expect(poll.threadId).toBe(pollData.threadId); - expect(poll.achievementId).toBe(pollData.achievementId); - expect(poll.status).toBe("active"); + expect(poll.messageId).toEqual(pollData.messageId); + expect(poll.channelId).toEqual(pollData.channelId); + expect(poll.threadId).toEqual(pollData.threadId); + expect(poll.achievementId).toEqual(pollData.achievementId); + expect(poll.status).toEqual("active"); expect(poll.endedAt).toBeNull(); }); @@ -74,7 +83,7 @@ describe("UwcPollService", () => { // ASSERT expect(poll).toBeDefined(); - expect(poll?.messageId).toBe("123456789"); + expect(poll?.messageId).toEqual("123456789"); }); it("returns null for non-existent poll", async () => { @@ -108,11 +117,11 @@ describe("UwcPollService", () => { ); // ASSERT - expect(poll.status).toBe("completed"); + expect(poll.status).toEqual("completed"); expect(poll.endedAt).toBeDefined(); expect(storedResults).toHaveLength(3); - expect(storedResults[0]?.optionText).toBe("No, leave as is"); - expect(storedResults[0]?.voteCount).toBe(5); + expect(storedResults[0]?.optionText).toEqual("No, leave as is"); + expect(storedResults[0]?.voteCount).toEqual(5); }); it("throws error for non-existent poll", async () => { @@ -145,7 +154,7 @@ describe("UwcPollService", () => { // ASSERT expect(activePolls).toHaveLength(1); - expect(activePolls[0]?.messageId).toBe("active1"); + expect(activePolls[0]?.messageId).toEqual("active1"); }); }); @@ -182,7 +191,7 @@ describe("UwcPollService", () => { // ASSERT expect(polls).toHaveLength(2); - expect(polls.every((p) => p.achievementId === 14402)).toBe(true); + expect(polls.every((p) => p.achievementId === 14402)).toEqual(true); }); }); @@ -218,7 +227,7 @@ describe("UwcPollService", () => { // ASSERT expect(polls).toHaveLength(1); - expect(polls[0]?.achievementName).toBe("Sonic Speed"); + expect(polls[0]?.achievementName).toEqual("Sonic Speed"); }); it("searches by game name", async () => { @@ -227,7 +236,7 @@ describe("UwcPollService", () => { // ASSERT expect(polls).toHaveLength(1); - expect(polls[0]?.gameName).toBe("Super Mario Bros."); + expect(polls[0]?.gameName).toEqual("Super Mario Bros."); }); it("returns empty array for no matches", async () => { diff --git a/src/test/create-test-db.ts b/src/test/create-test-db.ts new file mode 100644 index 0000000..4e75bbf --- /dev/null +++ b/src/test/create-test-db.ts @@ -0,0 +1,25 @@ +import { drizzle } from "drizzle-orm/libsql"; +import { migrate } from "drizzle-orm/libsql/migrator"; +import { resolve } from "node:path"; + +import * as schema from "../database/schema"; + +const migrationsFolder = resolve(import.meta.dirname, "../../drizzle"); + +export async function createTestDb() { + const db = drizzle({ connection: { url: "file::memory:" } }); + + await migrate(db, { migrationsFolder }); + + return db; +} + +// Tables are deleted in reverse dependency order to respect foreign key constraints. +export async function cleanAllTables(db: ReturnType) { + await db.delete(schema.uwcPollResults); + await db.delete(schema.pollVotes); + await db.delete(schema.teamMembers); + await db.delete(schema.uwcPolls); + await db.delete(schema.polls); + await db.delete(schema.teams); +} diff --git a/src/test/mocks/database.mock.ts b/src/test/mocks/database.mock.ts deleted file mode 100644 index d1cd3e5..0000000 --- a/src/test/mocks/database.mock.ts +++ /dev/null @@ -1,51 +0,0 @@ -import type { teamMembers, teams } from "../../database/schema"; - -type Team = typeof teams.$inferSelect; -type TeamMember = typeof teamMembers.$inferSelect; - -export function createMockTeam(overrides?: Partial): Team { - return { - id: "team123", - name: "test-team", - addedBy: "admin123", - addedAt: new Date("2023-01-01"), - ...overrides, - }; -} - -export function createMockTeamMember(overrides?: Partial): TeamMember { - return { - userId: "user123", - teamId: "team123", - addedBy: "admin123", - addedAt: new Date("2023-01-01"), - ...overrides, - }; -} - -export function createMockPoll(overrides?: any) { - return { - id: 1, - messageId: "msg123", - channelId: "channel123", - creatorId: "user123", - question: "Test poll question?", - options: JSON.stringify([ - { text: "Option A", votes: [] }, - { text: "Option B", votes: [] }, - ]), - endTime: null, - createdAt: new Date("2023-01-01"), - ...overrides, - }; -} - -export function createMockPollVote(overrides?: any) { - return { - pollId: 1, - userId: "voter123", - optionIndex: 0, - votedAt: new Date("2023-01-01"), - ...overrides, - }; -}