4
0

prepare-db.ts 3.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. import { execFileSync } from 'node:child_process'
  2. import { existsSync, readFileSync } from 'node:fs'
  3. import { resolve } from 'node:path'
  4. /**
  5. * Puts a known database in front of the suite: every migration applied to an
  6. * empty schema, then the demo seed.
  7. *
  8. * The seed is the reason this is cheap. It pins its user and organisation ids
  9. * and writes a password hash better-auth can verify, so the tests get a
  10. * populated workshop and a working login without a mail server in the loop.
  11. *
  12. * Run from the web server's own command line, ahead of `next start`, because
  13. * Playwright brings the server up before global setup runs and a server on an
  14. * empty schema answers every page with an error. When the suite is pointed at
  15. * a server somebody else started, global setup calls this instead.
  16. */
  17. const TEST_DB_MARKERS = ['e2e', 'test']
  18. function devDatabaseUrl(): string | null {
  19. const envFile = resolve(process.cwd(), '.env')
  20. if (!existsSync(envFile)) return null
  21. for (const line of readFileSync(envFile, 'utf8').split('\n')) {
  22. const match = line.match(/^\s*DATABASE_URL\s*=\s*"?([^"\n]+)"?/)
  23. if (match) return match[1].trim()
  24. }
  25. return null
  26. }
  27. /**
  28. * Refuses to point the reset at anything that looks like real data. Resetting
  29. * drops every table, so a copy-pasted connection string is the one mistake
  30. * worth being rude about.
  31. */
  32. function assertSafeToDestroy(url: string): void {
  33. if (process.env.E2E_ALLOW_ANY_DB === '1') return
  34. const dev = devDatabaseUrl()
  35. if (dev && dev === url) {
  36. throw new Error(
  37. 'E2E_DATABASE_URL is the same database as .env DATABASE_URL. ' +
  38. 'The suite resets the database it is given; point it at a throwaway one.'
  39. )
  40. }
  41. const database = url.split('/').pop()?.split('?')[0]?.toLowerCase() ?? ''
  42. if (!TEST_DB_MARKERS.some((marker) => database.includes(marker))) {
  43. throw new Error(
  44. `Refusing to reset database "${database}": the name contains neither "e2e" nor "test". ` +
  45. 'Rename it, or set E2E_ALLOW_ANY_DB=1 if you are certain.'
  46. )
  47. }
  48. }
  49. function run(command: string, args: string[], env: NodeJS.ProcessEnv): void {
  50. execFileSync(command, args, { stdio: 'inherit', env })
  51. }
  52. export function prepareDatabase(): void {
  53. const url = process.env.E2E_DATABASE_URL
  54. if (!url) {
  55. throw new Error('E2E_DATABASE_URL is not set. See e2e/README.md.')
  56. }
  57. if (process.env.E2E_SKIP_SEED === '1') {
  58. console.log('[e2e] E2E_SKIP_SEED=1, leaving the database alone.')
  59. return
  60. }
  61. assertSafeToDestroy(url)
  62. const env: NodeJS.ProcessEnv = {
  63. ...process.env,
  64. DATABASE_URL: url,
  65. // The seed writes vehicle photos next to the app's uploads. Kept out of the
  66. // real data directory so a test run cannot disturb a running instance.
  67. DATA_ROOT: process.env.E2E_DATA_ROOT ?? resolve(process.cwd(), 'e2e/.data'),
  68. // The seed's own default is four characters, which the reset-password
  69. // page refuses; the suite seeds the owner with the password it signs in
  70. // with, long enough to be set back after the reset flow has changed it.
  71. DEMO_USER_EMAIL: process.env.E2E_USER_EMAIL ?? 'demo@torqvoice.com',
  72. DEMO_USER_PASSWORD: process.env.E2E_USER_PASSWORD ?? 'demo-e2e-pass',
  73. }
  74. console.log('[e2e] resetting the test database')
  75. // Prisma 7 has no seed hook in this config and no skip flags: reset applies
  76. // every migration and nothing else, and the seed is the step after.
  77. run('npx', ['prisma', 'migrate', 'reset', '--force'], env)
  78. console.log('[e2e] seeding')
  79. run('npx', ['tsx', 'prisma/seed_dummy_data.ts'], env)
  80. }
  81. // `tsx e2e/prepare-db.ts` from the web server command.
  82. if (process.argv[1]?.endsWith('prepare-db.ts')) prepareDatabase()