mongoms.ts 3.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. import { MongoMemoryServer } from 'mongodb-memory-server-core';
  2. import mongoose from 'mongoose';
  3. import { mongoOptions } from '~/server/util/mongoose-utils';
  4. let mongoServer: MongoMemoryServer | undefined;
  5. /**
  6. * Replace the database name in a MongoDB connection URI.
  7. * Supports various URI formats including authentication, replica sets, and query parameters.
  8. * Uses a simple string-based approach that handles most MongoDB URI formats correctly.
  9. *
  10. * @param uri - MongoDB connection URI
  11. * @param newDbName - New database name to use
  12. * @returns Modified URI with the new database name
  13. */
  14. function replaceMongoDbName(uri: string, newDbName: string): string {
  15. try {
  16. // For standard single-host URIs, use URL API for robust parsing
  17. // Format: mongodb://[username:password@]host[:port][/database][?options]
  18. if (!uri.includes(',')) {
  19. const url = new URL(uri);
  20. url.pathname = `/${newDbName}`;
  21. return url.toString();
  22. }
  23. // For replica set URIs with multiple hosts (contains comma)
  24. // Format: mongodb://host1:port1,host2:port2[/database][?options]
  25. // URL API doesn't support multiple hosts, so use string manipulation
  26. const [beforeDb, afterDb] = uri.split('?');
  27. const queryString = afterDb ? `?${afterDb}` : '';
  28. // Find the last slash before the database name (after all hosts)
  29. const lastSlashIndex = beforeDb.lastIndexOf('/');
  30. if (lastSlashIndex > 'mongodb://'.length) {
  31. // URI has a database name, replace it
  32. const baseUri = beforeDb.substring(0, lastSlashIndex);
  33. return `${baseUri}/${newDbName}${queryString}`;
  34. }
  35. // URI has no database name, append it
  36. return `${beforeDb}/${newDbName}${queryString}`;
  37. } catch (error) {
  38. // If parsing fails, throw an error with helpful message
  39. throw new Error(`Failed to parse MongoDB URI: ${error instanceof Error ? error.message : String(error)}`);
  40. }
  41. }
  42. beforeAll(async () => {
  43. // Generate unique database name for each test worker to avoid conflicts in parallel execution
  44. // VITEST_WORKER_ID is provided by Vitest (e.g., "1", "2", "3"...)
  45. const workerId = process.env.VITEST_WORKER_ID || '1';
  46. const dbName = `growi_test_${workerId}`;
  47. // Use external MongoDB if MONGO_URI is provided (e.g., in CI with GitHub Actions services)
  48. if (process.env.MONGO_URI) {
  49. const mongoUri = replaceMongoDbName(process.env.MONGO_URI, dbName);
  50. // biome-ignore lint/suspicious/noConsole: Allow logging
  51. console.log(`Using external MongoDB at ${mongoUri} (worker: ${workerId})`);
  52. await mongoose.connect(mongoUri, mongoOptions);
  53. return;
  54. }
  55. // Use MongoMemoryServer for local development
  56. // set debug flag
  57. process.env.MONGOMS_DEBUG = process.env.VITE_MONGOMS_DEBUG;
  58. // set version
  59. mongoServer = await MongoMemoryServer.create({
  60. instance: {
  61. // Use unique database name per worker to avoid conflicts in parallel execution
  62. dbName,
  63. },
  64. binary: {
  65. version: process.env.VITE_MONGOMS_VERSION,
  66. downloadDir: 'node_modules/.cache/mongodb-binaries',
  67. },
  68. });
  69. // biome-ignore lint/suspicious/noConsole: Allow logging
  70. console.log(`MongoMemoryServer is running on ${mongoServer.getUri()} (worker: ${workerId})`);
  71. await mongoose.connect(mongoServer.getUri(), mongoOptions);
  72. });
  73. afterAll(async () => {
  74. await mongoose.disconnect();
  75. // Stop MongoMemoryServer if it was created
  76. if (mongoServer) {
  77. await mongoServer.stop();
  78. }
  79. });