From 48ee79ba4761a4b5efa12b686a025c777d96265d Mon Sep 17 00:00:00 2001 From: Morgan Roderick Date: Thu, 18 Jun 2026 12:19:10 +0200 Subject: [PATCH 1/6] feat: add container.sh and prereqs checker for Postgres test container --- package-lock.json | 433 +------------------------------------- package.json | 7 +- scripts/container.sh | 115 ++++++++++ test/helpers/container.js | 144 +++++++++++++ 4 files changed, 266 insertions(+), 433 deletions(-) create mode 100755 scripts/container.sh create mode 100644 test/helpers/container.js diff --git a/package-lock.json b/package-lock.json index 8042308..58b2054 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,6 @@ "dependencies": { "@hono/node-server": "^2.0.4", "better-auth": "^1.6.15", - "better-sqlite3": "^12.10.0", "date-fns": "^4.4.0", "hono": "^4.12.24", "hono-pino": "^0.10.3", @@ -2336,26 +2335,6 @@ "dev": true, "license": "MIT" }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, "node_modules/better-auth": { "version": "1.6.15", "resolved": "https://registry.npmjs.org/better-auth/-/better-auth-1.6.15.tgz", @@ -2497,40 +2476,6 @@ } } }, - "node_modules/better-sqlite3": { - "version": "12.10.0", - "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.10.0.tgz", - "integrity": "sha512-CyzaZRQKyHkB2ZInfTTl2nvT33EbDpjkLEbE8/Zck3Ll6O0qqvuGdrJ45HgtH+HykRg88ITY3AdreBGN70aBSQ==", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "bindings": "^1.5.0", - "prebuild-install": "^7.1.1" - }, - "engines": { - "node": "20.x || 22.x || 23.x || 24.x || 25.x || 26.x" - } - }, - "node_modules/bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "license": "MIT", - "dependencies": { - "file-uri-to-path": "1.0.0" - } - }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "license": "MIT", - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, "node_modules/brace-expansion": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", @@ -2554,30 +2499,6 @@ "node": "18 || 20 || >=22" } }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, "node_modules/c8": { "version": "10.1.3", "resolved": "https://registry.npmjs.org/c8/-/c8-10.1.3.tgz", @@ -2690,12 +2611,6 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "license": "ISC" - }, "node_modules/cli-boxes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", @@ -3055,30 +2970,6 @@ } } }, - "node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "license": "MIT", - "dependencies": { - "mimic-response": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "license": "MIT", - "engines": { - "node": ">=4.0.0" - } - }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -3096,6 +2987,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "devOptional": true, "license": "Apache-2.0", "engines": { "node": ">=8" @@ -3138,15 +3030,6 @@ "dev": true, "license": "MIT" }, - "node_modules/end-of-stream": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", - "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - } - }, "node_modules/env-paths": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", @@ -3389,15 +3272,6 @@ "node": ">=12" } }, - "node_modules/expand-template": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", - "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", - "license": "(MIT OR WTFPL)", - "engines": { - "node": ">=6" - } - }, "node_modules/exponential-backoff": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", @@ -3502,12 +3376,6 @@ "node": ">=16.0.0" } }, - "node_modules/file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "license": "MIT" - }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -3583,12 +3451,6 @@ ], "license": "MIT" }, - "node_modules/fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "license": "MIT" - }, "node_modules/fs-minipass": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", @@ -3649,12 +3511,6 @@ "node": ">=18" } }, - "node_modules/github-from-package": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", - "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", - "license": "MIT" - }, "node_modules/glob": { "version": "13.0.6", "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", @@ -3827,26 +3683,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -3920,18 +3756,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "license": "ISC" - }, "node_modules/ink": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/ink/-/ink-5.2.1.tgz", @@ -4404,18 +4228,6 @@ "node": ">=6" } }, - "node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/minimatch": { "version": "10.2.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", @@ -4432,15 +4244,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/minipass": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", @@ -4590,12 +4393,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/mkdirp-classic": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "license": "MIT" - }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -4618,12 +4415,6 @@ "node": "^20.0.0 || >=22.0.0" } }, - "node_modules/napi-build-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", - "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", - "license": "MIT" - }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -4641,18 +4432,6 @@ "node": ">= 0.6" } }, - "node_modules/node-abi": { - "version": "3.75.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.75.0.tgz", - "integrity": "sha512-OhYaY5sDsIka7H7AtijtI9jwGYLyl29eQn/W623DiN/MIv5sUqc4g7BIDThX+gb7di9f6xK02nkp8sdfFWZLTg==", - "license": "MIT", - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/node-gyp": { "version": "12.3.0", "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.3.0.tgz", @@ -4838,15 +4617,6 @@ "node": ">=14.0.0" } }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, "node_modules/onetime": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", @@ -5262,32 +5032,6 @@ "node": ">=0.10.0" } }, - "node_modules/prebuild-install": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", - "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", - "license": "MIT", - "dependencies": { - "detect-libc": "^2.0.0", - "expand-template": "^2.0.3", - "github-from-package": "0.0.0", - "minimist": "^1.2.3", - "mkdirp-classic": "^0.5.3", - "napi-build-utils": "^2.0.0", - "node-abi": "^3.3.0", - "pump": "^3.0.0", - "rc": "^1.2.7", - "simple-get": "^4.0.0", - "tar-fs": "^2.0.0", - "tunnel-agent": "^0.6.0" - }, - "bin": { - "prebuild-install": "bin.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -5381,16 +5125,6 @@ ], "license": "MIT" }, - "node_modules/pump": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", - "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -5407,21 +5141,6 @@ "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", "license": "MIT" }, - "node_modules/rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" - } - }, "node_modules/react": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", @@ -5490,20 +5209,6 @@ "react": "^18.3.1" } }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/readdirp": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", @@ -5631,26 +5336,6 @@ "integrity": "sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg==", "license": "MIT" }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, "node_modules/safe-stable-stringify": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", @@ -5682,6 +5367,7 @@ "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "devOptional": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -5750,51 +5436,6 @@ "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/simple-concat": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", - "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/simple-get": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", - "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "decompress-response": "^6.0.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" - } - }, "node_modules/sinon": { "version": "22.0.0", "resolved": "https://registry.npmjs.org/sinon/-/sinon-22.0.0.tgz", @@ -5975,15 +5616,6 @@ "node": ">=8" } }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, "node_modules/string-length": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/string-length/-/string-length-6.0.0.tgz", @@ -6114,15 +5746,6 @@ "node": ">=8" } }, - "node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -6244,34 +5867,6 @@ "node": ">=18" } }, - "node_modules/tar-fs": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", - "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", - "license": "MIT", - "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "license": "MIT", - "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/tar/node_modules/chownr": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", @@ -6569,18 +6164,6 @@ "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "license": "Apache-2.0", - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" - } - }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -6659,12 +6242,6 @@ "punycode": "^2.1.0" } }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "license": "MIT" - }, "node_modules/uuid": { "version": "14.0.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.0.tgz", @@ -6882,12 +6459,6 @@ "node": ">=8" } }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "license": "ISC" - }, "node_modules/ws": { "version": "8.21.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", diff --git a/package.json b/package.json index d657c50..5572343 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "node": ">= 24.0.0" }, "scripts": { - "dev": "node --watch src/index.js", + "dev": "scripts/container.sh ensure && node --watch src/index.js", "db:generate": "npx @better-auth/cli generate --yes", "db:migrate": "npx @better-auth/cli migrate --yes", "fallow": "fallow", @@ -13,14 +13,17 @@ "prettier:write": "prettier --write '**/*.{js,css,md,yml}'", "lint": "eslint .", "start": "node src/index.js", + "pretest": "node test/helpers/container.js", "test": "tap", "test:watch": "tap --watch", + "test:pg:start": "scripts/container.sh start", + "test:pg:stop": "scripts/container.sh stop", + "test:pg:status": "scripts/container.sh status", "test:coverage": "tap --coverage-report=html" }, "dependencies": { "@hono/node-server": "^2.0.4", "better-auth": "^1.6.15", - "better-sqlite3": "^12.10.0", "date-fns": "^4.4.0", "hono": "^4.12.24", "hono-pino": "^0.10.3", diff --git a/scripts/container.sh b/scripts/container.sh new file mode 100755 index 0000000..7db2c14 --- /dev/null +++ b/scripts/container.sh @@ -0,0 +1,115 @@ +#!/bin/bash +set -e + +CONTAINER_NAME="auth-dev-pg" + +# Env vars with defaults +AUTH_TEST_PG_PORT="${AUTH_TEST_PG_PORT:-5433}" +AUTH_TEST_PG_USER="${AUTH_TEST_PG_USER:-auth}" +AUTH_TEST_PG_PASSWORD="${AUTH_TEST_PG_PASSWORD:-auth}" +AUTH_TEST_PG_DB="${AUTH_TEST_PG_DB:-test}" + +if ! command -v container >/dev/null 2>&1; then + echo "apple/container not installed — skipping container management" + case "${1:-}" in + start|stop|restart|logs) exit 1 ;; + ensure|status) exit 0 ;; + *) exit 1 ;; + esac +fi + +container_exists() { + local output + output=$(container inspect "$CONTAINER_NAME" 2>/dev/null) + [ -n "$output" ] && [ "$output" != "[]" ] +} + +wait_for_port() { + local port="$1" + for _ in $(seq 1 30); do + if nc -z 127.0.0.1 "$port" 2>/dev/null; then + return 0 + fi + sleep 1 + done + return 1 +} + +start() { + if container_exists "$CONTAINER_NAME"; then + echo "$CONTAINER_NAME is already running (port $AUTH_TEST_PG_PORT -> 5432)" + exit 0 + fi + + container stop "$CONTAINER_NAME" 2>/dev/null || true + container delete "$CONTAINER_NAME" 2>/dev/null || true + + echo "Starting Postgres on port $AUTH_TEST_PG_PORT..." + container run -d \ + --name "$CONTAINER_NAME" \ + -p "${AUTH_TEST_PG_PORT}:5432" \ + -e POSTGRES_USER="$AUTH_TEST_PG_USER" \ + -e POSTGRES_PASSWORD="$AUTH_TEST_PG_PASSWORD" \ + -e POSTGRES_DB="$AUTH_TEST_PG_DB" \ + postgres:16-alpine + + echo "Waiting for Postgres on port $AUTH_TEST_PG_PORT..." + if wait_for_port "$AUTH_TEST_PG_PORT"; then + echo "$CONTAINER_NAME is running (port $AUTH_TEST_PG_PORT -> 5432)" + else + echo "ERROR: Postgres failed to start" + container logs "$CONTAINER_NAME" + exit 1 + fi +} + +stop() { + echo "Stopping $CONTAINER_NAME..." + container stop "$CONTAINER_NAME" 2>/dev/null || true + container delete "$CONTAINER_NAME" 2>/dev/null || true + echo "Stopped" +} + +status() { + if container_exists "$CONTAINER_NAME"; then + echo "$CONTAINER_NAME: running (port ${AUTH_TEST_PG_PORT} -> 5432)" + else + echo "$CONTAINER_NAME: not running" + fi +} + +logs() { + container logs "$CONTAINER_NAME" 2>/dev/null || echo "Container not found" +} + +ensure() { + if container_exists "$CONTAINER_NAME"; then + # Already running — verify port is reachable + if nc -z 127.0.0.1 "$AUTH_TEST_PG_PORT" 2>/dev/null; then + exit 0 + fi + # Container exists but port not reachable — restart + echo "Container exists but port not reachable, restarting..." + stop + fi + start +} + +case "$1" in + start) start ;; + stop) stop ;; + status) status ;; + logs) logs ;; + ensure) ensure ;; + restart) stop; start ;; + *) + echo "Usage: $0 {start|stop|status|logs|ensure|restart}" + echo "" + echo "Environment variables (with defaults):" + echo " AUTH_TEST_PG_PORT Host port (5433)" + echo " AUTH_TEST_PG_USER Postgres user (auth)" + echo " AUTH_TEST_PG_PASSWORD Postgres password (auth)" + echo " AUTH_TEST_PG_DB Database name (test)" + exit 1 + ;; +esac diff --git a/test/helpers/container.js b/test/helpers/container.js new file mode 100644 index 0000000..83f3a99 --- /dev/null +++ b/test/helpers/container.js @@ -0,0 +1,144 @@ +import { execSync } from "child_process"; +import net from "net"; +import { Pool } from "pg"; + +const PORT = parseInt(process.env.AUTH_TEST_PG_PORT || "5433"); +const HOST = process.env.AUTH_TEST_PG_HOST || "localhost"; +const USER = process.env.AUTH_TEST_PG_USER || "auth"; +const PASSWORD = process.env.AUTH_TEST_PG_PASSWORD || "auth"; +const DATABASE = process.env.AUTH_TEST_PG_DB || "test"; + +function log(message) { + console.log(`[container] ${message}`); +} + +function error(message) { + console.error(`[container] ERROR: ${message}`); +} + +function checkContainerCommand() { + try { + execSync("command -v container", { stdio: "ignore" }); + return true; + } catch { + return false; + } +} + +function checkContainerService() { + try { + const output = execSync("container system status", { + encoding: "utf8", + }); + return output.includes("running"); + } catch { + return false; + } +} + +function checkPort(port, host = "127.0.0.1") { + return new Promise((resolve) => { + const socket = new net.Socket(); + socket.setTimeout(1000); + socket.on("connect", () => { + socket.destroy(); + resolve(true); + }); + socket.on("timeout", () => { + socket.destroy(); + resolve(false); + }); + socket.on("error", () => { + socket.destroy(); + resolve(false); + }); + socket.connect(port, host); + }); +} + +async function waitForPostgres(maxRetries = 30) { + for (let i = 0; i < maxRetries; i++) { + if (await checkPort(PORT)) { + return true; + } + await new Promise((r) => setTimeout(r, 1000)); + } + return false; +} + +async function verifyPostgresConnection() { + try { + const pool = new Pool({ + host: HOST, + port: PORT, + database: DATABASE, + user: USER, + password: PASSWORD, + connectionTimeoutMillis: 5000, + }); + await pool.query("SELECT 1"); + await pool.end(); + return true; + } catch { + return false; + } +} + +export async function ensurePostgres() { + // Skip container checks in CI — Postgres is provided as a service + if (process.env.CI) { + log("CI detected — skipping container checks"); + return; + } + + log("Checking prerequisites..."); + + if (!checkContainerCommand()) { + error("apple/container is not installed."); + error("Install from: https://github.com/apple/container"); + process.exit(1); + } + log("container CLI found"); + + if (!checkContainerService()) { + error("container service is not running."); + error("Start it with: container system start"); + process.exit(1); + } + log("container service is running"); + + const portOpen = await checkPort(PORT); + if (!portOpen) { + log("Postgres not reachable. Starting container..."); + try { + execSync("scripts/container.sh ensure", { stdio: "inherit" }); + } catch { + error("Failed to start Postgres container"); + process.exit(1); + } + + const ready = await waitForPostgres(); + if (!ready) { + error("Postgres did not become reachable"); + process.exit(1); + } + } + + log("Postgres port is reachable"); + + const connected = await verifyPostgresConnection(); + if (!connected) { + error("Cannot authenticate to Postgres"); + process.exit(1); + } + + log("Postgres connection verified — ready"); +} + +// Run directly: node test/helpers/container.js +ensurePostgres() + .then(() => process.exit(0)) + .catch((err) => { + console.error(err); + process.exit(1); + }); From 7ddd7911c6a99dca1bbc32dd830a4e319a82e219 Mon Sep 17 00:00:00 2001 From: Morgan Roderick Date: Thu, 18 Jun 2026 12:22:17 +0200 Subject: [PATCH 2/6] refactor: remove SQLite branches from auth and config, defer health handler to test update --- src/auth.js | 39 ++++++++++----------------------------- src/config.js | 2 +- 2 files changed, 11 insertions(+), 30 deletions(-) diff --git a/src/auth.js b/src/auth.js index 9eb62c3..20ed703 100644 --- a/src/auth.js +++ b/src/auth.js @@ -1,38 +1,19 @@ +import { Pool } from "pg"; import { betterAuth } from "better-auth"; import { admin, magicLink } from "better-auth/plugins"; -import Database from "better-sqlite3"; -import { Pool } from "pg"; import appConfig from "./config.js"; -// Detect database type from connection string -const isPostgres = appConfig.database_url.startsWith("postgres://"); - -let db; -if (isPostgres) { - // PostgreSQL for CI/production - db = new Pool({ - connectionString: appConfig.database_url, - max: 10, // reasonable pool size - }); - - db.on("error", (err) => { - console.error("Unexpected database error", err); - process.exit(-1); - }); -} else { - // SQLite for local development - db = new Database(appConfig.database_url); +// PostgreSQL connection pool for CI/production and local dev +const db = new Pool({ + connectionString: appConfig.database_url, + max: 10, +}); - try { - db.pragma("busy_timeout = 5000"); - db.pragma("synchronous = NORMAL"); - } catch (e) { - console.error(`error occurred: ${e.message}`); - process.exit(-1); - } -} +db.on("error", (err) => { + console.error("Unexpected database error", err); + process.exit(-1); +}); -// Export db for graceful shutdown export { db }; export const auth = betterAuth({ diff --git a/src/config.js b/src/config.js index c37568f..1c229c0 100644 --- a/src/config.js +++ b/src/config.js @@ -1,7 +1,7 @@ const config = { port: process.env.PORT || 3000, base_url: process.env.CODEBAR_AUTH_URL || "http://localhost:3000", - database_url: process.env.DATABASE_URL || "./auth.db", + database_url: process.env.DATABASE_URL || "postgres://auth:auth@localhost:5433/test", allowed_redirects: ["http://localhost:3000/demo"], social: { github: { From e119d59bb864e5817399bc89f23956f5438f9175 Mon Sep 17 00:00:00 2001 From: Morgan Roderick Date: Thu, 18 Jun 2026 12:33:09 +0200 Subject: [PATCH 3/6] feat: migrate tests from SQLite to Postgres via Apple Container - Rewrite test helper with per-test Postgres schema isolation - Update all test files to pass t context and use pool - Simplify health handler to Postgres-only queries - Update health test fakes to use .query() instead of .prepare() - Run tests serially (jobs: 1) for reliability with shared Postgres DB - Update envrc, docs, and remove SQLite artifacts --- .envrc-dist | 6 + .github/workflows/ci.yml | 2 - .taprc | 1 + docs/development.md | 54 ++++++- src/app/routes/health.js | 14 +- src/config.js | 3 +- test/features/admin.test.js | 21 ++- test/features/authentication.test.js | 2 +- test/features/health.test.js | 210 +++++++++++---------------- test/features/home.test.js | 4 +- test/features/magic-links.test.js | 8 +- test/features/profile.test.js | 6 +- test/helpers/test-instance.js | 186 +++++++++++++----------- 13 files changed, 268 insertions(+), 249 deletions(-) diff --git a/.envrc-dist b/.envrc-dist index 0e8ec38..5b6f3d0 100644 --- a/.envrc-dist +++ b/.envrc-dist @@ -1,2 +1,8 @@ export GITHUB_CLIENT_ID= export GITHUB_CLIENT_SECRET= +export DATABASE_URL=postgres://auth:auth@localhost:5433/test +export AUTH_TEST_PG_HOST=localhost +export AUTH_TEST_PG_PORT=5433 +export AUTH_TEST_PG_USER=auth +export AUTH_TEST_PG_PASSWORD=auth +export AUTH_TEST_PG_DB=test diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fb994b5..186adf2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -82,8 +82,6 @@ jobs: npm run prettier:check npm run lint npm run fallow - npm run db:generate - npm run db:migrate test: needs: diff --git a/.taprc b/.taprc index aa64b90..fc35f45 100644 --- a/.taprc +++ b/.taprc @@ -1,5 +1,6 @@ files: - "test/**/*.test.js" +jobs: 1 timeout: 30 coverage-report: - "text" diff --git a/docs/development.md b/docs/development.md index 3033624..330fda2 100644 --- a/docs/development.md +++ b/docs/development.md @@ -1,8 +1,54 @@ # Development -You can test the container setup by running `make run-dev` in the root of the repository. +## Prerequisites -This will build and start the stack: +1. Install [Apple Container](https://github.com/apple/container) +2. Start the container service: -- application container -- local s3 storage (based on RustFS) + ```sh + container system start + ``` + +## Running the app + +The app automatically starts the Postgres container if needed: + +```sh +npm run dev +``` + +Or manually manage the container: + +```sh +npm run test:pg:start # Start Postgres container +npm run test:pg:stop # Stop Postgres container +npm run test:pg:status # Check container status +``` + +## Environment + +Copy `.envrc-dist` to `.envrc` and add your GitHub OAuth credentials: + +```sh +cp .envrc-dist .envrc +# Edit .envrc to add GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET +# then: source .envrc (or use direnv/mise to load it) +``` + +The default `DATABASE_URL` points to the local Postgres container. + +## Database + +Run migrations after pulling new changes: + +```sh +npm run db:migrate +``` + +## Testing + +Tests automatically start the Postgres container if it's not running: + +```sh +npm test +``` diff --git a/src/app/routes/health.js b/src/app/routes/health.js index 6d98214..ef09b6a 100644 --- a/src/app/routes/health.js +++ b/src/app/routes/health.js @@ -9,10 +9,6 @@ healthHandler.get("/health", async (c) => { // Get database from context (allows injection for testing) const db = c.get("db"); - // Determine database type by checking object capabilities - // PostgreSQL/Kysely has .query(), better-sqlite3 has .prepare() - const isPostgres = typeof db?.query === "function"; - // Check for Heroku startup health check (skip DB check for fast startup) const isStartupCheck = c.req.query("type") === "startup"; @@ -55,13 +51,7 @@ healthHandler.get("/health", async (c) => { } // Check database connectivity - if (isPostgres) { - // PostgreSQL: use pool query - await db.query("SELECT 1"); - } else { - // SQLite: use prepare/get - db.prepare("SELECT 1").get(); - } + await db.query("SELECT 1"); return c.json( { @@ -72,7 +62,7 @@ healthHandler.get("/health", async (c) => { app: { status: "up" }, database: { status: "connected", - type: isPostgres ? "postgresql" : "sqlite", + type: "postgresql", }, }, }, diff --git a/src/config.js b/src/config.js index 1c229c0..b554f9b 100644 --- a/src/config.js +++ b/src/config.js @@ -1,7 +1,8 @@ const config = { port: process.env.PORT || 3000, base_url: process.env.CODEBAR_AUTH_URL || "http://localhost:3000", - database_url: process.env.DATABASE_URL || "postgres://auth:auth@localhost:5433/test", + database_url: + process.env.DATABASE_URL || "postgres://auth:auth@localhost:5433/test", allowed_redirects: ["http://localhost:3000/demo"], social: { github: { diff --git a/test/features/admin.test.js b/test/features/admin.test.js index a9b37ad..2fbea46 100644 --- a/test/features/admin.test.js +++ b/test/features/admin.test.js @@ -4,7 +4,7 @@ import { createApp } from "../../src/app/app.js"; test("admin feature tests", async (t) => { t.test("admin page can be requested", async (t) => { - const testInstance = await getTestInstance(); + const testInstance = await getTestInstance(t); const app = createApp(testInstance.auth); // For now, just test that the route exists and we can request it @@ -18,7 +18,7 @@ test("admin feature tests", async (t) => { }); t.test("unauthenticated user cannot access admin panel", async (t) => { - const testInstance = await getTestInstance(); + const testInstance = await getTestInstance(t); const app = createApp(testInstance.auth); const res = await app.request("/admin"); @@ -30,18 +30,18 @@ test("admin feature tests", async (t) => { }); t.test("admin role can be set on a user via magic link auth", async (t) => { - const testInstance = await getTestInstance(); - const { getAuthHeaders, client } = testInstance; + const testInstance = await getTestInstance(t); + const { getAuthHeaders, client, pool } = testInstance; // Get a user session via magic link await getAuthHeaders("admin@example.com"); // Query the user from the database to get their ID - const db = testInstance.auth.options?.database; - if (!db) throw new Error("Cannot access database"); - const user = db - .prepare("SELECT id FROM user WHERE email = ?") - .get("admin@example.com"); + // "user" is a reserved word, must be quoted in Postgres + const result = await pool.query('SELECT id FROM "user" WHERE email = $1', [ + "admin@example.com", + ]); + const user = result.rows[0]; if (!user) { throw new Error("User not found after magic link verification"); @@ -53,8 +53,7 @@ test("admin feature tests", async (t) => { role: "admin", }); - // We can't directly query the user from a different instance, - // but setRole should not throw if it worked correctly + // setRole should not throw if it worked correctly t.pass("admin role successfully set"); }); }); diff --git a/test/features/authentication.test.js b/test/features/authentication.test.js index aaac1d1..7d7a7da 100644 --- a/test/features/authentication.test.js +++ b/test/features/authentication.test.js @@ -4,7 +4,7 @@ import { createApp } from "../../src/app/app.js"; test("authentication feature tests", async (t) => { t.test("user can logout", async (t) => { - const testInstance = await getTestInstance(); + const testInstance = await getTestInstance(t); const app = createApp(testInstance.auth); const { getAuthHeaders } = testInstance; diff --git a/test/features/health.test.js b/test/features/health.test.js index 36c7818..bdd3d06 100644 --- a/test/features/health.test.js +++ b/test/features/health.test.js @@ -3,13 +3,52 @@ import sinon from "sinon"; import { getTestInstance } from "../helpers/test-instance.js"; import { createApp } from "../../src/app/app.js"; +/** + * Convenience: create a health-request function bound to an auth/pool pair + */ +function makeHealthRequest(auth, pool) { + const app = createApp(auth, pool); + return () => app.request("/health"); +} + +/** + * Assert standard RFC 9457 error fields on a 503 response body + */ +function assertProblemDetails(t, body) { + t.equal(body.type, "/problems/database-unavailable", "has RFC 9457 type"); + t.equal(body.title, "Database Unavailable", "has RFC 9457 title"); + t.equal(body.status, 503, "has RFC 9457 status"); + t.ok(body.timestamp, "has timestamp"); + t.ok(body.checks, "has checks extension"); + t.equal(body.checks.app.status, "up", "app status is up"); + t.equal( + body.checks.database.status, + "disconnected", + "database is disconnected", + ); +} + +/** + * Fetch /health and assert it returns a 503 RFC 9457 problem response + * @returns {Promise} parsed response body + */ +async function expect503Problem(t, auth, pool) { + const res = await makeHealthRequest(auth, pool)(); + t.equal(res.status, 503, "returns 503 Service Unavailable"); + t.match( + res.headers.get("content-type"), + /application\/problem\+json/, + "content-type is application/problem+json", + ); + const body = await res.json(); + assertProblemDetails(t, body); + return body; +} + test("health endpoint feature tests", async (t) => { t.test("GET /health returns 200 with healthy status", async (t) => { - const testInstance = await getTestInstance(); - const app = createApp(testInstance.auth, testInstance.db); - - const res = await app.request("/health"); - + const ti = await getTestInstance(t); + const res = await makeHealthRequest(ti.auth, ti.pool)(); t.equal(res.status, 200, "returns 200 OK"); const body = await res.json(); @@ -18,17 +57,16 @@ test("health endpoint feature tests", async (t) => { t.ok(body.checks, "has checks"); t.equal(body.checks.app.status, "up", "app status is up"); t.equal(body.checks.database.status, "connected", "database is connected"); - t.ok( - ["sqlite", "postgresql"].includes(body.checks.database.type), - "database type is valid", + t.equal( + body.checks.database.type, + "postgresql", + "database type is postgresql", ); }); t.test("GET /health returns JSON with correct structure", async (t) => { - const testInstance = await getTestInstance(); - const app = createApp(testInstance.auth, testInstance.db); - - const res = await app.request("/health"); + const ti = await getTestInstance(t); + const res = await makeHealthRequest(ti.auth, ti.pool)(); const body = await res.json(); t.ok(body.status, "has status field"); @@ -39,14 +77,16 @@ test("health endpoint feature tests", async (t) => { t.equal(body.checks.app.status, "up", "app status is up"); t.ok(body.checks.database, "has checks.database field"); t.equal(body.checks.database.status, "connected", "database is connected"); - t.ok(body.checks.database.type, "has database type"); + t.equal( + body.checks.database.type, + "postgresql", + "database type is postgresql", + ); }); t.test("timestamp is valid ISO 8601 format", async (t) => { - const testInstance = await getTestInstance(); - const app = createApp(testInstance.auth, testInstance.db); - - const res = await app.request("/health"); + const ti = await getTestInstance(t); + const res = await makeHealthRequest(ti.auth, ti.pool)(); const body = await res.json(); const date = new Date(body.timestamp); @@ -56,9 +96,8 @@ test("health endpoint feature tests", async (t) => { t.test( "GET /health?type=startup returns 200 with minimal checks", async (t) => { - const testInstance = await getTestInstance(); - const app = createApp(testInstance.auth, testInstance.db); - + const ti = await getTestInstance(t); + const app = createApp(ti.auth, ti.pool); const res = await app.request("/health?type=startup"); t.equal(res.status, 200, "returns 200 OK"); @@ -75,119 +114,48 @@ test("health endpoint failure states", async (t) => { t.test( "returns 503 with RFC 9457 format when database query fails", async (t) => { - // Create fake db - test instance uses SQLite (has .prepare(), no .query()) - const fakeGet = sinon.fake.throws(new Error("database is locked")); - const fakePrepare = sinon.fake.returns({ - get: fakeGet, + const fakeQuery = sinon.fake.rejects(new Error("database is locked")); + const ti = await getTestInstance(t); + const body = await expect503Problem(t, ti.auth, { + query: fakeQuery, }); - const fakeDb = { - prepare: fakePrepare, - }; - // Create app with injected fake database - const testInstance = await getTestInstance(); - const app = createApp(testInstance.auth, fakeDb); - - const res = await app.request("/health"); - - t.equal(res.status, 503, "returns 503 Service Unavailable"); - t.match( - res.headers.get("content-type"), - /application\/problem\+json/, - "content-type is application/problem+json", - ); - - const body = await res.json(); - t.equal(body.type, "/problems/database-unavailable", "has RFC 9457 type"); - t.equal(body.title, "Database Unavailable", "has RFC 9457 title"); - t.equal(body.status, 503, "has RFC 9457 status"); t.ok(body.detail, "has RFC 9457 detail"); - t.ok(body.timestamp, "has timestamp"); - t.ok(body.checks, "has checks extension"); - t.equal(body.checks.app.status, "up", "app status is up"); - t.equal( - body.checks.database.status, - "disconnected", - "database is disconnected", - ); - - // Verify specific error message for SQLite t.match( body.checks.database.message, /database is locked/, "error message matches expected", ); - - // Verify appropriate fake was called - t.ok(fakePrepare.calledOnce, "prepare was called"); - t.ok(fakeGet.calledOnce, "get was called"); + t.ok(fakeQuery.calledOnce, "query was called"); }, ); - t.test( - "returns 503 with RFC 9457 format on generic database error", - async (t) => { - // Create fake db that throws error (SQLite pattern) - const fakeDb = { - prepare: () => { - throw new Error("database connection failed"); - }, - }; - - // Create app with injected fake database - const testInstance = await getTestInstance(); - const app = createApp(testInstance.auth, fakeDb); - - const res = await app.request("/health"); - - t.equal(res.status, 503, "returns 503 Service Unavailable"); - t.match( - res.headers.get("content-type"), - /application\/problem\+json/, - "content-type is application/problem+json", - ); + t.test("returns 503 with generic database error", async (t) => { + const fakeDb = { + query: async () => { + throw new Error("database connection failed"); + }, + }; - const body = await res.json(); - t.equal(body.type, "/problems/database-unavailable", "has RFC 9457 type"); - t.equal(body.title, "Database Unavailable", "has RFC 9457 title"); - t.equal(body.status, 503, "has RFC 9457 status"); - t.ok(body.timestamp, "has timestamp"); - t.ok(body.checks, "has checks extension"); - t.equal(body.checks.app.status, "up", "app status is up"); + const ti = await getTestInstance(t); + const body = await expect503Problem(t, ti.auth, fakeDb); - // Verify specific error message for SQLite - t.match( - body.detail, - /database connection failed/, - "error detail matches expected for sqlite", - ); - }, - ); + t.match( + body.detail, + /database connection failed/, + "error detail matches expected from fake", + ); + }); t.test("returns 503 when database is null", async (t) => { - // Create app with null database - const testInstance = await getTestInstance(); - const app = createApp(testInstance.auth, null); + const ti = await getTestInstance(t); + const body = await expect503Problem(t, ti.auth, null); - const res = await app.request("/health"); - - t.equal(res.status, 503, "returns 503 Service Unavailable"); - - const body = await res.json(); - t.equal(body.type, "/problems/database-unavailable", "has RFC 9457 type"); - t.equal(body.title, "Database Unavailable", "has RFC 9457 title"); - t.equal(body.status, 503, "has RFC 9457 status"); t.equal( body.detail, "Database connection is not available", "detail says database not available", ); - t.equal(body.checks.app.status, "up", "app status is up"); - t.equal( - body.checks.database.status, - "disconnected", - "database is disconnected", - ); t.equal( body.checks.database.message, "Database not available", @@ -196,24 +164,10 @@ test("health endpoint failure states", async (t) => { }); t.test("returns 503 when PostgreSQL-style db query fails", async (t) => { - // Create fake db that has .query() method (PostgreSQL-style) const fakeQuery = sinon.fake.rejects(new Error("connection refused")); - const fakeDb = { - query: fakeQuery, - }; - - const testInstance = await getTestInstance(); - const app = createApp(testInstance.auth, fakeDb); - - const res = await app.request("/health"); + const ti = await getTestInstance(t); + const body = await expect503Problem(t, ti.auth, { query: fakeQuery }); - t.equal(res.status, 503, "returns 503 Service Unavailable"); - const body = await res.json(); - t.equal( - body.checks.database.status, - "disconnected", - "database is disconnected", - ); t.match( body.checks.database.message, /connection refused/, diff --git a/test/features/home.test.js b/test/features/home.test.js index e430b13..a839c03 100644 --- a/test/features/home.test.js +++ b/test/features/home.test.js @@ -4,7 +4,7 @@ import { createApp } from "../../src/app/app.js"; test("home page feature tests", async (t) => { t.test("home page loads without authentication", async (t) => { - const testInstance = await getTestInstance(); + const testInstance = await getTestInstance(t); const app = createApp(testInstance.auth); const res = await app.request("/"); @@ -15,7 +15,7 @@ test("home page feature tests", async (t) => { }); t.test("home page has login link", async (t) => { - const testInstance = await getTestInstance(); + const testInstance = await getTestInstance(t); const app = createApp(testInstance.auth); const res = await app.request("/"); diff --git a/test/features/magic-links.test.js b/test/features/magic-links.test.js index 2da5f5f..dcc61f6 100644 --- a/test/features/magic-links.test.js +++ b/test/features/magic-links.test.js @@ -23,7 +23,7 @@ async function makeMagicLinkRequest(app, email) { test("magic links feature tests", async (t) => { t.test("user can request magic link", async (t) => { - const testInstance = await getTestInstance(); + const testInstance = await getTestInstance(t); const app = createApp(testInstance.auth); const res = await makeMagicLinkRequest(app, "magic@example.com"); @@ -37,7 +37,7 @@ test("magic links feature tests", async (t) => { }); t.test("magic link request for nonexistent user succeeds", async (t) => { - const testInstance = await getTestInstance(); + const testInstance = await getTestInstance(t); const app = createApp(testInstance.auth); const res = await makeMagicLinkRequest(app, "nonexistent@example.com"); @@ -52,7 +52,7 @@ test("magic links feature tests", async (t) => { }); t.test("magic link GET page renders form", async (t) => { - const testInstance = await getTestInstance(); + const testInstance = await getTestInstance(t); const app = createApp(testInstance.auth); const res = await app.request("/login/magic-link"); @@ -64,7 +64,7 @@ test("magic links feature tests", async (t) => { }); t.test("magic link URL is captured in test", async (t) => { - const testInstance = await getTestInstance(); + const testInstance = await getTestInstance(t); const app = createApp(testInstance.auth); const { getMagicLinks } = testInstance; diff --git a/test/features/profile.test.js b/test/features/profile.test.js index 0b56526..e8eab42 100644 --- a/test/features/profile.test.js +++ b/test/features/profile.test.js @@ -4,7 +4,7 @@ import { createApp } from "../../src/app/app.js"; test("profile feature tests", async (t) => { t.test("authenticated user can view profile", async (t) => { - const testInstance = await getTestInstance(); + const testInstance = await getTestInstance(t); const app = createApp(testInstance.auth); const { getAuthHeaders } = testInstance; @@ -17,7 +17,7 @@ test("profile feature tests", async (t) => { }); t.test("unauthenticated user redirects to login", async (t) => { - const testInstance = await getTestInstance(); + const testInstance = await getTestInstance(t); const app = createApp(testInstance.auth); const res = await app.request("/profile"); @@ -27,7 +27,7 @@ test("profile feature tests", async (t) => { }); t.test("profile with invalid session redirects to login", async (t) => { - const testInstance = await getTestInstance(); + const testInstance = await getTestInstance(t); const app = createApp(testInstance.auth); // Use invalid cookie diff --git a/test/helpers/test-instance.js b/test/helpers/test-instance.js index ca26933..eb632c3 100644 --- a/test/helpers/test-instance.js +++ b/test/helpers/test-instance.js @@ -1,71 +1,136 @@ +import pg from "pg"; import { betterAuth } from "better-auth"; import { admin, magicLink } from "better-auth/plugins"; import { getMigrations } from "better-auth/db/migration"; -import Database from "better-sqlite3"; /** - * Creates a Better Auth test instance with in-memory database - * Adapted from Better Auth's test utilities for use with tap - * - * EXPECTED ERROR MESSAGES DURING TESTS: - * - * You will see these errors in test output - they are EXPECTED and do NOT indicate test failures: - * - * 1. "INTERNAL_SERVER_ERROR SqliteError: no such table: session" - * - Occurs when tests provide invalid session tokens/cookies - * - Better Auth attempts to query the session table before catching the error - * - Tests verify the app correctly handles this (redirects to login) - * - * These errors are logged by Better Auth's internal error handling and cannot be - * suppressed without hiding legitimate errors. They indicate the application is - * correctly catching and handling error conditions. - * - * Tests pass if assertions succeed - ignore these stderr messages. + * Parse DATABASE_URL into a pool config, falling back to defaults on failure + */ +function getPoolConfig() { + const { + AUTH_TEST_PG_HOST: host = "localhost", + AUTH_TEST_PG_PORT: port = "5433", + AUTH_TEST_PG_DB: database = "test", + AUTH_TEST_PG_USER: user = "auth", + AUTH_TEST_PG_PASSWORD: password = "auth", + } = process.env; + + const cfg = { host, port: parseInt(port), database, user, password, max: 1 }; + + if (!process.env.DATABASE_URL) return cfg; + + try { + const p = new URL(process.env.DATABASE_URL); + cfg.host = p.hostname || cfg.host; + cfg.port = parseInt(p.port) || cfg.port; + cfg.database = p.pathname.replace(/^\//, "") || cfg.database; + cfg.user = decodeURIComponent(p.username) || cfg.user; + cfg.password = decodeURIComponent(p.password) || cfg.password; + } catch { + /* keep defaults */ + } + + return cfg; +} + +/** + * Creates a Better Auth test instance with isolated PostgreSQL schema * - * @returns {Promise<{auth: Object, client: Object, db: Database}>} + * @param {Object} t - Tap test context (for auto-teardown via t.teardown()) + * @returns {Promise<{auth: Object, pool: pg.Pool, client: Object, getAuthHeaders: Function, teardown: Function}>} */ -export async function getTestInstance() { - // Create in-memory database - const db = new Database(":memory:"); - const magicLinksStore = []; +export async function getTestInstance(t) { + // Unique schema per test instance + const schemaName = `test_${crypto.randomUUID().replace(/-/g, "_")}`; - // Configure Better Auth for testing - const auth = createTestAuth(db, magicLinksStore); + // Create a small pool for this test + const poolConfig = getPoolConfig(); + const pool = new pg.Pool(poolConfig); - // Run migrations to create database tables - const migrations = await getMigrations(auth.options); - await migrations.runMigrations(); + // Create the schema and set search_path so Better Auth tables go here + await pool.query(`CREATE SCHEMA IF NOT EXISTS "${schemaName}"`); + await pool.query(`SET search_path TO "${schemaName}"`); - // Create client helper for common operations - const client = createTestClient(auth); + // Store magic links for test retrieval + const magicLinksStore = []; - // Helper to get session headers for authenticated requests via magic link + // Configure Better Auth with the pool as database + const auth = betterAuth({ + database: pool, + baseURL: "http://localhost:3000", + logger: { + disabled: true, + }, + socialProviders: { + github: { + clientId: "test-client-id", + clientSecret: "test-client-secret", + }, + }, + telemetry: { + enabled: false, + }, + session: { + expiresIn: 60 * 60 * 24 * 7, // 7 days + updateAge: 60 * 60 * 24, // 1 day + }, + plugins: [ + magicLink({ + sendMagicLink: async ({ email, token, url }) => { + magicLinksStore.push({ email, token, url }); + }, + }), + admin(), + ], + }); + + // Run Better Auth migrations inside the test's schema + const { runMigrations } = await getMigrations(auth.options); + await runMigrations(); + + // Create helpers const getAuthHeaders = createGetAuthHeaders(auth, magicLinksStore); + const client = createTestClient(pool); + + // Auto-teardown via tap context + if (t && typeof t.teardown === "function") { + t.teardown(async () => { + await pool.query(`DROP SCHEMA IF EXISTS "${schemaName}" CASCADE`); + await pool.end(); + }); + } return { auth, + pool, client, - db, getAuthHeaders, getMagicLinks: () => magicLinksStore, + teardown: async () => { + try { + await pool.query(`DROP SCHEMA IF EXISTS "${schemaName}" CASCADE`); + } catch { + // Ignore teardown errors + } + await pool.end(); + }, }; } /** * Creates a test client helper with admin operations - * @param {Object} auth - Better Auth instance + * @param {pg.Pool} pool * @returns {Object} Client helper object with admin methods */ -function createTestClient(auth) { +function createTestClient(pool) { return { - // Admin methods (based on spike findings) admin: { setRole: async ({ userId, role }) => { - // Based on spike findings: direct database UPDATE is required - // The admin.setRole endpoint requires authentication, so direct DB access is simpler for testing - const db = auth.options?.database; - if (!db) throw new Error("Cannot access database for role update"); - db.prepare("UPDATE user SET role = ? WHERE id = ?").run(role, userId); + // "user" is a reserved word, must be quoted + await pool.query('UPDATE "user" SET role = $1 WHERE id = $2', [ + role, + userId, + ]); }, }, }; @@ -96,7 +161,6 @@ function createGetAuthHeaders(auth, magicLinksStore) { } // Verify the magic link to get a session - // magicLinkVerify returns a 302 redirect with session cookie headers let result; try { result = await auth.api.magicLinkVerify({ @@ -131,46 +195,6 @@ function createGetAuthHeaders(auth, magicLinksStore) { }; } -/** - * Creates a Better Auth instance configured for testing - * @param {Database} db - better-sqlite3 database instance - * @param {Array} magicLinksStore - External array to store captured magic links - * @returns {Object} Configured Better Auth instance - */ -function createTestAuth(db, magicLinksStore) { - return betterAuth({ - database: db, - baseURL: "http://localhost:3000", - logger: { - disabled: true, // Suppress logs during tests - }, - socialProviders: { - github: { - clientId: "test-client-id", - clientSecret: "test-client-secret", - }, - }, - telemetry: { - enabled: false, - }, - session: { - expiresIn: 60 * 60 * 24 * 7, // 7 days - updateAge: 60 * 60 * 24, // 1 day - }, - plugins: [ - magicLink({ - sendMagicLink: async ({ email, token, url }) => { - // Store magic link for testing - // Tests can access this via the returned magicLinks array - magicLinksStore.push({ email, token, url }); - return Promise.resolve(); - }, - }), - admin(), - ], - }); -} - /** * Extract session cookie from a 302 redirect error response * @param {Error} error - The error thrown by magicLinkVerify From 98c798adfcbf260fa79573e3ea8b0be62238ebb3 Mon Sep 17 00:00:00 2001 From: Morgan Roderick Date: Sun, 21 Jun 2026 12:15:40 +0200 Subject: [PATCH 4/6] fix: remove unused better-sqlite3 dependency --- package-lock.json | 433 +--------------------------------------------- package.json | 1 - 2 files changed, 2 insertions(+), 432 deletions(-) diff --git a/package-lock.json b/package-lock.json index c3c4537..cd7ada6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,6 @@ "dependencies": { "@hono/node-server": "^2.0.4", "better-auth": "^1.6.16", - "better-sqlite3": "^12.10.0", "date-fns": "^4.4.0", "hono": "^4.12.25", "hono-pino": "^0.10.3", @@ -2336,26 +2335,6 @@ "dev": true, "license": "MIT" }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, "node_modules/better-auth": { "version": "1.6.16", "resolved": "https://registry.npmjs.org/better-auth/-/better-auth-1.6.16.tgz", @@ -2497,40 +2476,6 @@ } } }, - "node_modules/better-sqlite3": { - "version": "12.10.0", - "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.10.0.tgz", - "integrity": "sha512-CyzaZRQKyHkB2ZInfTTl2nvT33EbDpjkLEbE8/Zck3Ll6O0qqvuGdrJ45HgtH+HykRg88ITY3AdreBGN70aBSQ==", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "bindings": "^1.5.0", - "prebuild-install": "^7.1.1" - }, - "engines": { - "node": "20.x || 22.x || 23.x || 24.x || 25.x || 26.x" - } - }, - "node_modules/bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "license": "MIT", - "dependencies": { - "file-uri-to-path": "1.0.0" - } - }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "license": "MIT", - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, "node_modules/brace-expansion": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", @@ -2554,30 +2499,6 @@ "node": "18 || 20 || >=22" } }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, "node_modules/c8": { "version": "10.1.3", "resolved": "https://registry.npmjs.org/c8/-/c8-10.1.3.tgz", @@ -2690,12 +2611,6 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "license": "ISC" - }, "node_modules/cli-boxes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", @@ -3055,30 +2970,6 @@ } } }, - "node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "license": "MIT", - "dependencies": { - "mimic-response": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "license": "MIT", - "engines": { - "node": ">=4.0.0" - } - }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -3096,6 +2987,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "devOptional": true, "license": "Apache-2.0", "engines": { "node": ">=8" @@ -3138,15 +3030,6 @@ "dev": true, "license": "MIT" }, - "node_modules/end-of-stream": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", - "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - } - }, "node_modules/env-paths": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", @@ -3389,15 +3272,6 @@ "node": ">=12" } }, - "node_modules/expand-template": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", - "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", - "license": "(MIT OR WTFPL)", - "engines": { - "node": ">=6" - } - }, "node_modules/exponential-backoff": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", @@ -3502,12 +3376,6 @@ "node": ">=16.0.0" } }, - "node_modules/file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "license": "MIT" - }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -3583,12 +3451,6 @@ ], "license": "MIT" }, - "node_modules/fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "license": "MIT" - }, "node_modules/fs-minipass": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", @@ -3649,12 +3511,6 @@ "node": ">=18" } }, - "node_modules/github-from-package": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", - "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", - "license": "MIT" - }, "node_modules/glob": { "version": "13.0.6", "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", @@ -3827,26 +3683,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -3920,18 +3756,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "license": "ISC" - }, "node_modules/ink": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/ink/-/ink-5.2.1.tgz", @@ -4404,18 +4228,6 @@ "node": ">=6" } }, - "node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/minimatch": { "version": "10.2.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", @@ -4432,15 +4244,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/minipass": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", @@ -4590,12 +4393,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/mkdirp-classic": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "license": "MIT" - }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -4618,12 +4415,6 @@ "node": "^20.0.0 || >=22.0.0" } }, - "node_modules/napi-build-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", - "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", - "license": "MIT" - }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -4641,18 +4432,6 @@ "node": ">= 0.6" } }, - "node_modules/node-abi": { - "version": "3.75.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.75.0.tgz", - "integrity": "sha512-OhYaY5sDsIka7H7AtijtI9jwGYLyl29eQn/W623DiN/MIv5sUqc4g7BIDThX+gb7di9f6xK02nkp8sdfFWZLTg==", - "license": "MIT", - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/node-gyp": { "version": "12.3.0", "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.3.0.tgz", @@ -4838,15 +4617,6 @@ "node": ">=14.0.0" } }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, "node_modules/onetime": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", @@ -5262,32 +5032,6 @@ "node": ">=0.10.0" } }, - "node_modules/prebuild-install": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", - "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", - "license": "MIT", - "dependencies": { - "detect-libc": "^2.0.0", - "expand-template": "^2.0.3", - "github-from-package": "0.0.0", - "minimist": "^1.2.3", - "mkdirp-classic": "^0.5.3", - "napi-build-utils": "^2.0.0", - "node-abi": "^3.3.0", - "pump": "^3.0.0", - "rc": "^1.2.7", - "simple-get": "^4.0.0", - "tar-fs": "^2.0.0", - "tunnel-agent": "^0.6.0" - }, - "bin": { - "prebuild-install": "bin.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -5381,16 +5125,6 @@ ], "license": "MIT" }, - "node_modules/pump": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", - "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -5407,21 +5141,6 @@ "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", "license": "MIT" }, - "node_modules/rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" - } - }, "node_modules/react": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", @@ -5490,20 +5209,6 @@ "react": "^18.3.1" } }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/readdirp": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", @@ -5631,26 +5336,6 @@ "integrity": "sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg==", "license": "MIT" }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, "node_modules/safe-stable-stringify": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", @@ -5682,6 +5367,7 @@ "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "devOptional": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -5750,51 +5436,6 @@ "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/simple-concat": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", - "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/simple-get": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", - "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "decompress-response": "^6.0.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" - } - }, "node_modules/sinon": { "version": "22.0.0", "resolved": "https://registry.npmjs.org/sinon/-/sinon-22.0.0.tgz", @@ -5975,15 +5616,6 @@ "node": ">=8" } }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, "node_modules/string-length": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/string-length/-/string-length-6.0.0.tgz", @@ -6114,15 +5746,6 @@ "node": ">=8" } }, - "node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -6244,34 +5867,6 @@ "node": ">=18" } }, - "node_modules/tar-fs": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", - "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", - "license": "MIT", - "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "license": "MIT", - "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/tar/node_modules/chownr": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", @@ -6569,18 +6164,6 @@ "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "license": "Apache-2.0", - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" - } - }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -6659,12 +6242,6 @@ "punycode": "^2.1.0" } }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "license": "MIT" - }, "node_modules/uuid": { "version": "14.0.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.0.tgz", @@ -6882,12 +6459,6 @@ "node": ">=8" } }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "license": "ISC" - }, "node_modules/ws": { "version": "8.21.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", diff --git a/package.json b/package.json index 1022e25..08fc0fa 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,6 @@ "dependencies": { "@hono/node-server": "^2.0.4", "better-auth": "^1.6.16", - "better-sqlite3": "^12.10.0", "date-fns": "^4.4.0", "hono": "^4.12.25", "hono-pino": "^0.10.3", From a628f2152493a7917c9873796a3b056188b96be5 Mon Sep 17 00:00:00 2001 From: Morgan Roderick Date: Sun, 21 Jun 2026 13:03:41 +0200 Subject: [PATCH 5/6] fix: persist Postgres data across container restarts Address review feedback: the container previously had no data volume, so every stop/start destroyed the database. - Mount named volume auth-dev-pg-data for /var/lib/postgresql/data - stop now only stops the container (data survives) - New destroy command wipes container + volume explicitly - start reuses stopped containers instead of recreating - status distinguishes running/stopped/not-created --- scripts/container.sh | 79 +++++++++++++++++++++++++++++++------------- 1 file changed, 56 insertions(+), 23 deletions(-) diff --git a/scripts/container.sh b/scripts/container.sh index 7db2c14..457667d 100755 --- a/scripts/container.sh +++ b/scripts/container.sh @@ -2,6 +2,7 @@ set -e CONTAINER_NAME="auth-dev-pg" +VOLUME_NAME="auth-dev-pg-data" # Env vars with defaults AUTH_TEST_PG_PORT="${AUTH_TEST_PG_PORT:-5433}" @@ -12,7 +13,7 @@ AUTH_TEST_PG_DB="${AUTH_TEST_PG_DB:-test}" if ! command -v container >/dev/null 2>&1; then echo "apple/container not installed — skipping container management" case "${1:-}" in - start|stop|restart|logs) exit 1 ;; + start|stop|restart|logs|destroy) exit 1 ;; ensure|status) exit 0 ;; *) exit 1 ;; esac @@ -24,6 +25,10 @@ container_exists() { [ -n "$output" ] && [ "$output" != "[]" ] } +container_is_running() { + container inspect "$CONTAINER_NAME" 2>/dev/null | grep -q '"Running": true' +} + wait_for_port() { local port="$1" for _ in $(seq 1 30); do @@ -36,22 +41,25 @@ wait_for_port() { } start() { - if container_exists "$CONTAINER_NAME"; then + if container_is_running; then echo "$CONTAINER_NAME is already running (port $AUTH_TEST_PG_PORT -> 5432)" exit 0 fi - container stop "$CONTAINER_NAME" 2>/dev/null || true - container delete "$CONTAINER_NAME" 2>/dev/null || true - - echo "Starting Postgres on port $AUTH_TEST_PG_PORT..." - container run -d \ - --name "$CONTAINER_NAME" \ - -p "${AUTH_TEST_PG_PORT}:5432" \ - -e POSTGRES_USER="$AUTH_TEST_PG_USER" \ - -e POSTGRES_PASSWORD="$AUTH_TEST_PG_PASSWORD" \ - -e POSTGRES_DB="$AUTH_TEST_PG_DB" \ - postgres:16-alpine + if container_exists; then + echo "Starting existing $CONTAINER_NAME..." + container start "$CONTAINER_NAME" + else + echo "Creating and starting Postgres on port $AUTH_TEST_PG_PORT..." + container run -d \ + --name "$CONTAINER_NAME" \ + -p "${AUTH_TEST_PG_PORT}:5432" \ + -e POSTGRES_USER="$AUTH_TEST_PG_USER" \ + -e POSTGRES_PASSWORD="$AUTH_TEST_PG_PASSWORD" \ + -e POSTGRES_DB="$AUTH_TEST_PG_DB" \ + -v "${VOLUME_NAME}:/var/lib/postgresql/data" \ + postgres:16-alpine + fi echo "Waiting for Postgres on port $AUTH_TEST_PG_PORT..." if wait_for_port "$AUTH_TEST_PG_PORT"; then @@ -64,17 +72,30 @@ start() { } stop() { - echo "Stopping $CONTAINER_NAME..." + if container_is_running; then + echo "Stopping $CONTAINER_NAME..." + container stop "$CONTAINER_NAME" 2>/dev/null || true + echo "Stopped" + else + echo "$CONTAINER_NAME is not running" + fi +} + +destroy() { + echo "Destroying $CONTAINER_NAME and its data..." container stop "$CONTAINER_NAME" 2>/dev/null || true container delete "$CONTAINER_NAME" 2>/dev/null || true - echo "Stopped" + container volume delete "$VOLUME_NAME" 2>/dev/null || true + echo "Destroyed" } status() { - if container_exists "$CONTAINER_NAME"; then + if container_is_running; then echo "$CONTAINER_NAME: running (port ${AUTH_TEST_PG_PORT} -> 5432)" + elif container_exists; then + echo "$CONTAINER_NAME: stopped" else - echo "$CONTAINER_NAME: not running" + echo "$CONTAINER_NAME: not created" fi } @@ -83,27 +104,39 @@ logs() { } ensure() { - if container_exists "$CONTAINER_NAME"; then - # Already running — verify port is reachable + if container_is_running; then if nc -z 127.0.0.1 "$AUTH_TEST_PG_PORT" 2>/dev/null; then exit 0 fi - # Container exists but port not reachable — restart - echo "Container exists but port not reachable, restarting..." + echo "Container running but port not reachable, restarting..." stop + start + elif container_exists; then + start + else + start fi - start } case "$1" in start) start ;; stop) stop ;; + destroy) destroy ;; status) status ;; logs) logs ;; ensure) ensure ;; restart) stop; start ;; *) - echo "Usage: $0 {start|stop|status|logs|ensure|restart}" + echo "Usage: $0 {start|stop|status|logs|ensure|restart|destroy}" + echo "" + echo "Commands:" + echo " start Create/start the container (data persists across restarts)" + echo " stop Stop the container (data is preserved)" + echo " destroy Stop, delete container, and wipe all data" + echo " restart Stop then start" + echo " status Show container state" + echo " logs Show container logs" + echo " ensure Start if not running, restart if unhealthy" echo "" echo "Environment variables (with defaults):" echo " AUTH_TEST_PG_PORT Host port (5433)" From ea91806e9446712b3ded39474b900740a8bc9b0f Mon Sep 17 00:00:00 2001 From: Morgan Roderick Date: Sun, 21 Jun 2026 13:37:23 +0200 Subject: [PATCH 6/6] fix: skip SSL for local Postgres connections CI Postgres service does not support SSL. Only enable SSL for non-localhost connections (e.g. Heroku production). --- src/auth.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/auth.js b/src/auth.js index 4bc577c..f77a6f5 100644 --- a/src/auth.js +++ b/src/auth.js @@ -4,10 +4,14 @@ import { admin, magicLink } from "better-auth/plugins"; import appConfig from "./config.js"; // PostgreSQL connection pool for CI/production and local dev +// SSL only for non-local connections (Heroku requires it; local/CI does not) +const isLocal = + appConfig.database_url.includes("@localhost") || + appConfig.database_url.includes("@127.0.0.1"); const db = new Pool({ connectionString: appConfig.database_url, max: 10, - ssl: { rejectUnauthorized: false }, + ...(isLocal ? {} : { ssl: { rejectUnauthorized: false } }), }); db.on("error", (err) => {