🚀 Big News:Socket Has Acquired Secure Annex.Learn More
Socket
Book a DemoSign in
Socket

migrate-mongo

Package Overview
Dependencies
Maintainers
1
Versions
81
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

migrate-mongo - npm Package Compare versions

Comparing version
14.0.5
to
14.0.6
+2
__mocks__/lib/actions/status.js
const statusMock = vi.fn();
export default statusMock;
// CommonJS wrapper for backward compatibility
const esmModule = import('./migrate-mongo.js');
module.exports = esmModule.then(m => m.default || m);
// Also support direct property access for CommonJS users
const handler = {
get(target, prop) {
return esmModule.then(m => m[prop] || m.default[prop]);
}
};
module.exports = new Proxy({}, handler);
const mock = {
stat: vi.fn(),
cp: vi.fn(),
mkdir: vi.fn(),
readdir: vi.fn(),
readFile: vi.fn(),
};
export default mock;
const promises = {
stat: vi.fn(),
cp: vi.fn(),
mkdir: vi.fn(),
readdir: vi.fn(),
readFile: vi.fn(),
};
export default promises;
export const MongoClient = {
connect: vi.fn(),
};
export default {
MongoClient,
};
import { vi } from 'vitest';
// Make vitest functions globally available
global.vi = vi;
import { describe, it, expect } from 'vitest';
import moduleLoader from '../../lib/utils/module-loader.js';
describe('module-loader', () => {
describe('require', () => {
it('should load CommonJS modules', () => {
const result = moduleLoader.require('path');
expect(result).toBeDefined();
expect(typeof result.join).toBe('function');
});
});
describe('import', () => {
it('should load ES modules', async () => {
const result = await moduleLoader.import('path');
expect(result).toBeDefined();
expect(typeof result.join).toBe('function');
});
});
});
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: true,
setupFiles: ['./test/setup.js'],
environment: 'node',
coverage: {
provider: 'v8',
reporter: ['text', 'html', 'lcov'],
include: [
'lib/**/*.js',
],
exclude: [
'lib/**/*.test.js',
'lib/migrate-mongo.js', // Main export file - no logic to test
'lib/migrate-mongo.cjs', // CommonJS wrapper
'test/**',
'node_modules/**',
],
},
include: [
'test/**/*.test.js',
],
exclude: [
'node_modules/**',
],
clearMocks: true,
restoreMocks: true,
mockReset: true,
},
});
+6
-3
#! /usr/bin/env node
const { Command } = require("commander");
const Table = require("cli-table3");
const migrateMongo = require("../lib/migrate-mongo");
import { Command } from "commander";
import Table from "cli-table3";
import { createRequire } from 'module';
import migrateMongo from "../lib/migrate-mongo.js";
const require = createRequire(import.meta.url);
const pkgjson = require("../package.json");

@@ -7,0 +10,0 @@

@@ -7,2 +7,19 @@ # Changelog

## [14.0.6] - 2025-12-03
- Migrate from Jest to Vitest for testing
- Full ESM support with native import/export
- Backward compatibility: migration scripts can still use CommonJS
- Global test functions (it, describe, expect, etc.) via setup file
- 100% code coverage maintained with unit tests alone
- Faster test execution and better developer experience
- Switch codebase to ESM (ECMAScript Modules)
- Use native import/export syntax throughout
- Maintain full CommonJS compatibility for migration scripts
- Update package.json with "type": "module"
- Add .js extensions to all relative imports
- Add CommonJS migration script tests to integration suite
- Verify backward compatibility with require/module.exports
- Ensure existing migration scripts continue to work
- Test both ESM and CommonJS migration formats
## [14.0.5] - 2025-12-03

@@ -9,0 +26,0 @@ - Fix CI workflow to run integration tests only after successful unit tests

@@ -1,19 +0,34 @@

const jestPlugin = require("eslint-plugin-jest");
import vitestPlugin from "eslint-plugin-vitest";
module.exports = [
export default [
{
files: ["test/**/*.js"],
...jestPlugin.configs['flat/recommended'],
plugins: {
vitest: vitestPlugin,
},
rules: {
...jestPlugin.configs['flat/recommended'].rules,
'jest/prefer-expect-assertions': 'off',
'jest/no-conditional-expect': 'off', // We use expect in try/catch blocks
'jest/expect-expect': 'off', // We use expect.rejects and expect.resolves
'jest/valid-title': 'warn', // Allow duplicate prefixes in nested describes
'jest/no-disabled-tests': 'warn',
'jest/no-focused-tests': 'error',
'jest/no-identical-title': 'error',
'jest/valid-expect': 'error'
}
...vitestPlugin.configs.recommended.rules,
'vitest/prefer-expect-assertions': 'off',
'vitest/no-conditional-expect': 'off', // We use expect in try/catch blocks
'vitest/expect-expect': 'off', // We use expect.rejects and expect.resolves
'vitest/valid-title': 'warn', // Allow duplicate prefixes in nested describes
'vitest/no-disabled-tests': 'warn',
'vitest/no-focused-tests': 'error',
'vitest/no-identical-title': 'error',
'vitest/valid-expect': 'error'
},
languageOptions: {
globals: {
...vitestPlugin.environments.env.globals,
describe: 'readonly',
it: 'readonly',
expect: 'readonly',
beforeEach: 'readonly',
afterEach: 'readonly',
beforeAll: 'readonly',
afterAll: 'readonly',
vi: 'readonly',
},
},
}
];

@@ -1,8 +0,12 @@

const fs = require("fs/promises");
const path = require("path");
const date = require("../utils/date");
const migrationsDir = require("../env/migrationsDir");
const config = require("../env/config");
import fs from "fs/promises";
import path from "path";
import { fileURLToPath } from 'url';
import date from "../utils/date.js";
import migrationsDir from "../env/migrationsDir.js";
import config from "../env/config.js";
module.exports = async description => {
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export default async (description) => {
if (!description) {

@@ -9,0 +13,0 @@ throw new Error("Missing parameter: description");

@@ -1,7 +0,7 @@

const status = require("./status");
const config = require("../env/config");
const migrationsDir = require("../env/migrationsDir");
const lock = require("../utils/lock");
import status from "./status.js";
import config from "../env/config.js";
import migrationsDir from "../env/migrationsDir.js";
import lock from "../utils/lock.js";
module.exports = async (db, client) => {
export default async (db, client) => {
const isBlockRollback = global.options?.block;

@@ -8,0 +8,0 @@ const downgraded = [];

@@ -1,7 +0,11 @@

const fs = require("fs/promises");
const path = require("path");
import fs from "fs/promises";
import path from "path";
import { fileURLToPath } from 'url';
const migrationsDir = require("../env/migrationsDir");
const config = require("../env/config");
import migrationsDir from "../env/migrationsDir.js";
import config from "../env/config.js";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
async function copySampleConfigFile() {

@@ -21,3 +25,3 @@ const moduleSystem = global.options.module === 'esm' ? 'esm' : 'commonjs';

module.exports = async () => {
export default async () => {
await migrationsDir.shouldNotExist();

@@ -24,0 +28,0 @@ await config.shouldNotExist();

@@ -1,5 +0,5 @@

const migrationsDir = require("../env/migrationsDir");
const config = require("../env/config");
import migrationsDir from "../env/migrationsDir.js";
import config from "../env/config.js";
module.exports = async db => {
export default async (db) => {
await migrationsDir.shouldExist();

@@ -6,0 +6,0 @@ await config.shouldExist();

@@ -1,7 +0,7 @@

const status = require("./status");
const config = require("../env/config");
const migrationsDir = require("../env/migrationsDir");
const lock = require("../utils/lock");
import status from "./status.js";
import config from "../env/config.js";
import migrationsDir from "../env/migrationsDir.js";
import lock from "../utils/lock.js";
module.exports = async (db, client) => {
export default async (db, client) => {
const statusItems = await status(db);

@@ -8,0 +8,0 @@ const pendingItems = statusItems.filter(item => item.appliedAt === "PENDING");

@@ -1,5 +0,5 @@

const fs = require("fs/promises");
const path = require("path");
const url = require("url");
const moduleLoader = require('../utils/module-loader');
import fs from "fs/promises";
import path from "path";
import url from "url";
import moduleLoader from '../utils/module-loader.js';

@@ -27,3 +27,3 @@ const DEFAULT_CONFIG_FILE_NAME = "migrate-mongo-config.js";

module.exports = {
export default {
DEFAULT_CONFIG_FILE_NAME,

@@ -30,0 +30,0 @@

@@ -1,5 +0,5 @@

const { MongoClient } = require("mongodb");
const config = require("./config");
import { MongoClient } from "mongodb";
import config from "./config.js";
module.exports = {
export default {
async connect() {

@@ -6,0 +6,0 @@ const configContent = await config.read();

@@ -1,7 +0,7 @@

const fs = require("fs/promises");
const path = require("path");
const url = require("url");
const crypto = require("crypto");
const config = require("./config");
const moduleLoader = require('../utils/module-loader');
import fs from "fs/promises";
import path from "path";
import url from "url";
import crypto from "crypto";
import config from "./config.js";
import moduleLoader from '../utils/module-loader.js';

@@ -59,3 +59,8 @@ const DEFAULT_MIGRATIONS_DIR_NAME = "migrations";

module.exports = {
function getModuleExports(module) {
// If ESM module format need to return default export
return module.default ? module.default : module;
}
export default {
resolve: resolveMigrationsDirPath,

@@ -134,6 +139,1 @@ resolveSampleMigrationPath,

function getModuleExports(module) {
// If ESM module format need to return default export
return module.default ? module.default : module;
}

@@ -1,17 +0,10 @@

const init = require("./actions/init");
const create = require("./actions/create");
const up = require("./actions/up");
const down = require("./actions/down");
const status = require("./actions/status");
const database = require("./env/database");
const config = require("./env/config");
import init from "./actions/init.js";
import create from "./actions/create.js";
import up from "./actions/up.js";
import down from "./actions/down.js";
import status from "./actions/status.js";
import database from "./env/database.js";
import config from "./env/config.js";
module.exports = {
init,
create,
up,
down,
status,
database,
config
};
export { init, create, up, down, status, database, config };
export default { init, create, up, down, status, database, config };

@@ -25,5 +25,3 @@ const now = (dateString = Date.now()) => {

module.exports = {
now,
nowAsString
};
export { now, nowAsString };
export default { now, nowAsString };

@@ -1,2 +0,2 @@

const config = require('../env/config');
import config from '../env/config.js';

@@ -38,6 +38,3 @@ async function getLockCollection(db) {

module.exports = {
exist,
activate,
clear,
}
export { exist, activate, clear };
export default { exist, activate, clear };

@@ -1,7 +0,12 @@

module.exports = {
import { createRequire } from 'module';
import { pathToFileURL } from 'url';
import path from 'path';
export default {
require(requirePath) {
return require(requirePath);
// Create require from the user's working directory context, not migrate-mongo's
const requireFunc = createRequire(pathToFileURL(path.join(process.cwd(), 'package.json')));
return requireFunc(requirePath);
},
/* istanbul ignore next */
import(importPath) {

@@ -8,0 +13,0 @@ return import(importPath);

{
"name": "migrate-mongo",
"version": "14.0.5",
"version": "14.0.6",
"description": "A database migration tool for MongoDB in Node",
"type": "module",
"main": "lib/migrate-mongo.js",
"exports": {
".": {
"import": "./lib/migrate-mongo.js",
"require": "./lib/migrate-mongo.cjs"
}
},
"bin": {

@@ -10,6 +17,6 @@ "migrate-mongo": "bin/migrate-mongo.js"

"scripts": {
"test": "jest --coverage",
"test:watch": "jest --watch",
"test:ci": "jest --coverage --ci",
"test:integration": "jest test/integration.test.js --runInBand --detectOpenHandles --testPathIgnorePatterns=/node_modules/",
"test": "vitest run --coverage --exclude test/integration.test.js",
"test:watch": "vitest --exclude test/integration.test.js",
"test:ci": "vitest run --coverage --exclude test/integration.test.js",
"test:integration": "vitest run test/integration.test.js --no-coverage",
"lint": "eslint lib/ test/",

@@ -39,7 +46,8 @@ "lint:fix": "eslint lib/ test/ --fix"

"devDependencies": {
"@vitest/coverage-v8": "^4.0.15",
"eslint": "^9.39.1",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-jest": "^29.2.1",
"jest": "^30.2.0",
"mongodb-memory-server": "^10.4.0"
"eslint-plugin-vitest": "^0.5.4",
"mongodb-memory-server": "^10.4.0",
"vitest": "^4.0.15"
},

@@ -46,0 +54,0 @@ "overrides": {

@@ -1,27 +0,24 @@

jest.mock("fs/promises", () => ({
stat: jest.fn(),
cp: jest.fn(),
mkdir: jest.fn(),
readdir: jest.fn(),
readFile: jest.fn(),
}));
import { fileURLToPath } from 'url';
const __dirname = fileURLToPath(new URL('.', import.meta.url));
const path = require("path");
const fs = require("fs/promises");
const config = require("../../lib/env/config");
const migrationsDir = require("../../lib/env/migrationsDir");
const create = require("../../lib/actions/create");
import path from "path";
import fs from "fs/promises";
import config from "../../lib/env/config.js";
import migrationsDir from "../../lib/env/migrationsDir.js";
import create from "../../lib/actions/create.js";
describe("create", () => {
let cpSpy;
beforeEach(() => {
jest.clearAllMocks();
jest.restoreAllMocks();
jest.spyOn(migrationsDir, 'shouldExist').mockResolvedValue();
jest.spyOn(migrationsDir, 'resolveMigrationFileExtension').mockReturnValue('.js');
jest.spyOn(migrationsDir, 'doesSampleMigrationExist').mockResolvedValue(false);
jest.spyOn(config, 'shouldExist').mockResolvedValue();
jest.spyOn(config, 'read').mockResolvedValue({
vi.clearAllMocks();
vi.restoreAllMocks();
vi.spyOn(migrationsDir, 'shouldExist').mockResolvedValue();
vi.spyOn(migrationsDir, 'resolveMigrationFileExtension').mockReturnValue('.js');
vi.spyOn(migrationsDir, 'doesSampleMigrationExist').mockResolvedValue(false);
vi.spyOn(config, 'shouldExist').mockResolvedValue();
vi.spyOn(config, 'read').mockResolvedValue({
moduleSystem: 'commonjs',
});
fs.cp.mockResolvedValue();
cpSpy = vi.spyOn(fs, 'cp').mockResolvedValue();
});

@@ -39,3 +36,3 @@

it("should yield an error when the migrations directory does not exist", async () => {
jest.spyOn(migrationsDir, 'shouldExist').mockRejectedValue(
vi.spyOn(migrationsDir, 'shouldExist').mockRejectedValue(
new Error("migrations directory does not exist")

@@ -52,4 +49,4 @@ );

it("should create a new migration file and yield the filename", async () => {
jest.useFakeTimers();
jest.setSystemTime(new Date("2016-06-09T08:07:00.077Z"));
vi.useFakeTimers();
vi.setSystemTime(new Date("2016-06-09T08:07:00.077Z"));

@@ -59,6 +56,4 @@ const filename = await create("my_description");

expect(fs.cp).toHaveBeenCalled();
expect(fs.cp.mock.calls[0][0]).toBe(
path.join(__dirname, "../../samples/commonjs/migration.js")
);
expect(fs.cp.mock.calls[0][1]).toBe(
expect(cpSpy).toHaveBeenCalledWith(
path.join(__dirname, "../../samples/commonjs/migration.js"),
path.join(process.cwd(), "migrations", "20160609080700-my_description.js")

@@ -68,17 +63,15 @@ );

jest.useRealTimers();
vi.useRealTimers();
});
it("should create a new migration file and yield the filename with custom extension", async () => {
jest.useFakeTimers();
jest.setSystemTime(new Date("2016-06-09T08:07:00.077Z"));
vi.useFakeTimers();
vi.setSystemTime(new Date("2016-06-09T08:07:00.077Z"));
jest.spyOn(migrationsDir, 'resolveMigrationFileExtension').mockReturnValue('.ts');
vi.spyOn(migrationsDir, 'resolveMigrationFileExtension').mockReturnValue('.ts');
const filename = await create("my_description");
expect(fs.cp).toHaveBeenCalled();
expect(fs.cp.mock.calls[0][0]).toBe(
path.join(__dirname, "../../samples/commonjs/migration.js")
);
expect(fs.cp.mock.calls[0][1]).toBe(
expect(cpSpy).toHaveBeenCalledWith(
path.join(__dirname, "../../samples/commonjs/migration.js"),
path.join(process.cwd(), "migrations", "20160609080700-my_description.ts")

@@ -88,8 +81,8 @@ );

jest.useRealTimers();
vi.useRealTimers();
});
it("should replace spaces in the description with underscores", async () => {
jest.useFakeTimers();
jest.setSystemTime(new Date("2016-06-09T08:07:00.077Z"));
vi.useFakeTimers();
vi.setSystemTime(new Date("2016-06-09T08:07:00.077Z"));

@@ -99,6 +92,4 @@ await create("this description contains spaces");

expect(fs.cp).toHaveBeenCalled();
expect(fs.cp.mock.calls[0][0]).toBe(
path.join(__dirname, "../../samples/commonjs/migration.js")
);
expect(fs.cp.mock.calls[0][1]).toBe(
expect(cpSpy).toHaveBeenCalledWith(
path.join(__dirname, "../../samples/commonjs/migration.js"),
path.join(

@@ -111,7 +102,7 @@ process.cwd(),

jest.useRealTimers();
vi.useRealTimers();
});
it("should yield errors that occurred when copying the file", async () => {
fs.cp.mockRejectedValue(new Error("Copy failed"));
vi.spyOn(fs, 'cp').mockRejectedValue(new Error("Copy failed"));
await expect(create("my_description")).rejects.toThrow("Copy failed");

@@ -121,6 +112,6 @@ });

it("should use the sample migration file if it exists", async () => {
jest.useFakeTimers();
jest.setSystemTime(new Date("2016-06-09T08:07:00.077Z"));
vi.useFakeTimers();
vi.setSystemTime(new Date("2016-06-09T08:07:00.077Z"));
jest.spyOn(migrationsDir, 'doesSampleMigrationExist').mockResolvedValue(true);
vi.spyOn(migrationsDir, 'doesSampleMigrationExist').mockResolvedValue(true);
const filename = await create("my_description");

@@ -130,6 +121,4 @@

expect(fs.cp).toHaveBeenCalled();
expect(fs.cp.mock.calls[0][0]).toBe(
path.join(process.cwd(), "migrations", "sample-migration.js")
);
expect(fs.cp.mock.calls[0][1]).toBe(
expect(cpSpy).toHaveBeenCalledWith(
path.join(process.cwd(), "migrations", "sample-migration.js"),
path.join(process.cwd(), "migrations", "20160609080700-my_description.js")

@@ -139,4 +128,4 @@ );

jest.useRealTimers();
vi.useRealTimers();
});
});

@@ -1,7 +0,7 @@

jest.mock("../../lib/actions/status", () => jest.fn());
import migrationsDir from "../../lib/env/migrationsDir.js";
import config from "../../lib/env/config.js";
import status from "../../lib/actions/status.js";
import down from "../../lib/actions/down.js";
const migrationsDir = require("../../lib/env/migrationsDir");
const config = require("../../lib/env/config");
const status = require("../../lib/actions/status");
const down = require("../../lib/actions/down");
vi.mock("../../lib/actions/status");

@@ -17,3 +17,3 @@ describe("down", () => {

const theMigration = {
down: jest.fn().mockResolvedValue()
down: vi.fn().mockResolvedValue()
};

@@ -25,3 +25,3 @@ return theMigration;

const mock = {
collection: jest.fn((name) => {
collection: vi.fn((name) => {
if (name === "changelog") return changelogCollection;

@@ -41,3 +41,3 @@ if (name === "changelog_lock") return changelogLockCollection;

return {
deleteOne: jest.fn().mockResolvedValue()
deleteOne: vi.fn().mockResolvedValue()
};

@@ -48,10 +48,10 @@ }

const findStub = {
toArray: jest.fn().mockResolvedValue([])
toArray: vi.fn().mockResolvedValue([])
};
return {
insertOne: jest.fn().mockResolvedValue(),
createIndex: jest.fn().mockResolvedValue(),
find: jest.fn().mockReturnValue(findStub),
deleteMany: jest.fn().mockResolvedValue(),
insertOne: vi.fn().mockResolvedValue(),
createIndex: vi.fn().mockResolvedValue(),
find: vi.fn().mockReturnValue(findStub),
deleteMany: vi.fn().mockResolvedValue(),
};

@@ -61,4 +61,4 @@ }

beforeEach(() => {
jest.clearAllMocks();
jest.restoreAllMocks();
vi.clearAllMocks();
vi.restoreAllMocks();

@@ -88,4 +88,4 @@ migration = mockMigration();

jest.spyOn(config, 'shouldExist').mockResolvedValue();
jest.spyOn(config, 'read').mockReturnValue({
vi.spyOn(config, 'shouldExist').mockResolvedValue();
vi.spyOn(config, 'read').mockReturnValue({
changelogCollectionName: "changelog",

@@ -96,3 +96,3 @@ lockCollectionName: "changelog_lock",

jest.spyOn(migrationsDir, 'loadMigration').mockResolvedValue(migration);
vi.spyOn(migrationsDir, 'loadMigration').mockResolvedValue(migration);
});

@@ -115,3 +115,3 @@

await down(db);
expect(migrationsDir.loadMigration.mock.calls[0][0]).toBe(
expect(migrationsDir.loadMigration).toHaveBeenCalledWith(
"20160609113225-last_migration.js"

@@ -175,3 +175,3 @@ );

it("should ignore lock if feature is disabled", async() => {
jest.spyOn(config, 'read').mockReturnValue({
vi.spyOn(config, 'read').mockReturnValue({
changelogCollectionName: "changelog",

@@ -182,3 +182,3 @@ lockCollectionName: "changelog_lock",

changelogLockCollection.find.mockReturnValue({
toArray: jest.fn().mockResolvedValue([{ createdAt: new Date() }])
toArray: vi.fn().mockResolvedValue([{ createdAt: new Date() }])
});

@@ -199,3 +199,3 @@

changelogLockCollection.find.mockReturnValue({
toArray: jest.fn().mockResolvedValue([{ createdAt: new Date() }])
toArray: vi.fn().mockResolvedValue([{ createdAt: new Date() }])
});

@@ -202,0 +202,0 @@

@@ -1,24 +0,21 @@

jest.mock("fs/promises", () => ({
stat: jest.fn(),
cp: jest.fn(),
mkdir: jest.fn(),
readdir: jest.fn(),
readFile: jest.fn(),
}));
import { fileURLToPath } from 'url';
const __dirname = fileURLToPath(new URL('.', import.meta.url));
const path = require("path");
const fs = require("fs/promises");
const migrationsDir = require("../../lib/env/migrationsDir");
const config = require("../../lib/env/config");
const init = require("../../lib/actions/init");
import path from "path";
import fs from "fs/promises";
import migrationsDir from "../../lib/env/migrationsDir.js";
import config from "../../lib/env/config.js";
import init from "../../lib/actions/init.js";
describe("init", () => {
let cpSpy, mkdirSpy;
beforeEach(() => {
jest.clearAllMocks();
jest.restoreAllMocks();
vi.clearAllMocks();
vi.restoreAllMocks();
global.options = { module: 'commonjs' };
jest.spyOn(migrationsDir, 'shouldNotExist').mockResolvedValue();
jest.spyOn(config, 'shouldNotExist').mockResolvedValue();
fs.cp.mockResolvedValue();
fs.mkdir.mockResolvedValue();
vi.spyOn(migrationsDir, 'shouldNotExist').mockResolvedValue();
vi.spyOn(config, 'shouldNotExist').mockResolvedValue();
cpSpy = vi.spyOn(fs, 'cp').mockResolvedValue();
mkdirSpy = vi.spyOn(fs, 'mkdir').mockResolvedValue();
});

@@ -32,3 +29,3 @@

it("should not continue and yield an error if the migrations directory already exists", async () => {
jest.spyOn(migrationsDir, 'shouldNotExist').mockRejectedValue(new Error("Dir exists"));
vi.spyOn(migrationsDir, 'shouldNotExist').mockRejectedValue(new Error("Dir exists"));

@@ -50,3 +47,3 @@ try {

it("should not continue and yield an error if the config file already exists", async () => {
jest.spyOn(config, 'shouldNotExist').mockResolvedValue(new Error("Config exists"));
vi.spyOn(config, 'shouldNotExist').mockResolvedValue(new Error("Config exists"));

@@ -64,12 +61,5 @@ try {

await init();
expect(fs.cp).toHaveBeenCalled();
expect(fs.cp).toHaveBeenCalledTimes(1);
const source = fs.cp.mock.calls[0][0];
expect(source).toBe(
path.join(__dirname, "../../samples/commonjs/migrate-mongo-config.js")
);
const destination = fs.cp.mock.calls[0][1];
expect(destination).toBe(
expect(cpSpy).toHaveBeenCalledWith(
path.join(__dirname, "../../samples/commonjs/migrate-mongo-config.js"),
path.join(process.cwd(), "migrate-mongo-config.js")

@@ -82,12 +72,5 @@ );

await init();
expect(fs.cp).toHaveBeenCalled();
expect(fs.cp).toHaveBeenCalledTimes(1);
const source = fs.cp.mock.calls[0][0];
expect(source).toBe(
path.join(__dirname, "../../samples/esm/migrate-mongo-config.js")
);
const destination = fs.cp.mock.calls[0][1];
expect(destination).toBe(
expect(cpSpy).toHaveBeenCalledWith(
path.join(__dirname, "../../samples/esm/migrate-mongo-config.js"),
path.join(process.cwd(), "migrate-mongo-config.js")

@@ -98,3 +81,3 @@ );

it("should yield errors that occurred when copying the sample config", async () => {
fs.cp.mockRejectedValue(new Error("No space left on device"));
vi.spyOn(fs, 'cp').mockRejectedValue(new Error("No space left on device"));
await expect(init()).rejects.toThrow("No space left on device");

@@ -106,6 +89,6 @@ });

expect(fs.mkdir).toHaveBeenCalled();
expect(fs.mkdir).toHaveBeenCalledTimes(1);
expect(fs.mkdir.mock.calls[0][0]).toEqual(
path.join(process.cwd(), "migrations")
expect(mkdirSpy).toHaveBeenCalledWith(
path.join(process.cwd(), "migrations"),
{ recursive: true }
);

@@ -115,3 +98,3 @@ });

it("should yield errors that occurred when creating the migrations directory", async () => {
fs.mkdir.mockRejectedValue(new Error("I cannot do that"));
vi.spyOn(fs, 'mkdir').mockRejectedValue(new Error("I cannot do that"));

@@ -118,0 +101,0 @@ try {

@@ -1,12 +0,6 @@

jest.mock("fs/promises", () => ({
stat: jest.fn(),
cp: jest.fn(),
mkdir: jest.fn(),
readdir: jest.fn(),
readFile: jest.fn(),
}));
vi.mock("fs/promises");
const migrationsDir = require("../../lib/env/migrationsDir");
const config = require("../../lib/env/config");
const status = require("../../lib/actions/status");
import migrationsDir from "../../lib/env/migrationsDir.js";
import config from "../../lib/env/config.js";
import status from "../../lib/actions/status.js";

@@ -32,3 +26,3 @@ describe("status", () => {

const mock = {};
mock.collection = jest.fn((name) => {
mock.collection = vi.fn((name) => {
if (name === "changelog") return changelogCollection;

@@ -42,5 +36,5 @@ return null;

return {
deleteOne: jest.fn().mockResolvedValue(),
find: jest.fn().mockReturnValue({
toArray: jest.fn().mockResolvedValue([
deleteOne: vi.fn().mockResolvedValue(),
find: vi.fn().mockReturnValue({
toArray: vi.fn().mockResolvedValue([
{

@@ -60,3 +54,3 @@ fileName: "20160509113224-first_migration.js",

function enabledFileHash() {
jest.spyOn(config, 'read').mockReturnValue({
vi.spyOn(config, 'read').mockReturnValue({
changelogCollectionName: "changelog",

@@ -69,3 +63,3 @@ useFileHash: true

changelogCollection.find.mockReturnValue({
toArray: jest.fn().mockResolvedValue([
toArray: vi.fn().mockResolvedValue([
{

@@ -86,4 +80,4 @@ fileName: "20160509113224-first_migration.js",

beforeEach(() => {
jest.clearAllMocks();
jest.restoreAllMocks();
vi.clearAllMocks();
vi.restoreAllMocks();

@@ -93,4 +87,4 @@ changelogCollection = mockChangelogCollection();

jest.spyOn(migrationsDir, 'shouldExist').mockResolvedValue();
jest.spyOn(migrationsDir, 'getFileNames').mockResolvedValue([
vi.spyOn(migrationsDir, 'shouldExist').mockResolvedValue();
vi.spyOn(migrationsDir, 'getFileNames').mockResolvedValue([
"20160509113224-first_migration.js",

@@ -100,6 +94,6 @@ "20160512091701-second_migration.js",

]);
jest.spyOn(migrationsDir, 'loadFileHash').mockImplementation(defaultLoadFileHashImpl);
vi.spyOn(migrationsDir, 'loadFileHash').mockImplementation(defaultLoadFileHashImpl);
jest.spyOn(config, 'shouldExist').mockResolvedValue();
jest.spyOn(config, 'read').mockReturnValue({
vi.spyOn(config, 'shouldExist').mockResolvedValue();
vi.spyOn(config, 'read').mockReturnValue({
changelogCollectionName: "changelog"

@@ -115,3 +109,3 @@ });

it("should yield an error when the migrations directory does not exist", async () => {
jest.spyOn(migrationsDir, 'shouldExist').mockRejectedValue(
vi.spyOn(migrationsDir, 'shouldExist').mockRejectedValue(
new Error("migrations directory does not exist")

@@ -128,3 +122,3 @@ );

it("should yield an error when config file does not exist", async () => {
jest.spyOn(config, 'shouldExist').mockRejectedValue(
vi.spyOn(config, 'shouldExist').mockRejectedValue(
new Error("config file does not exist")

@@ -141,3 +135,3 @@ );

it("should yield errors that occurred when getting the list of files in the migrations directory", async () => {
jest.spyOn(migrationsDir, 'getFileNames').mockRejectedValue(
vi.spyOn(migrationsDir, 'getFileNames').mockRejectedValue(
new Error("File system unavailable")

@@ -156,3 +150,3 @@ );

changelogCollection.find.mockReturnValue({
toArray: jest.fn().mockRejectedValue(new Error("Cannot read from the database"))
toArray: vi.fn().mockRejectedValue(new Error("Cannot read from the database"))
});

@@ -237,3 +231,3 @@ await expect(status(db)).rejects.toThrow("Cannot read from the database");

addHashToChangeLog();
jest.spyOn(migrationsDir, 'loadFileHash').mockImplementation((fileName) => {
vi.spyOn(migrationsDir, 'loadFileHash').mockImplementation((fileName) => {
switch (fileName) {

@@ -240,0 +234,0 @@ case "20160509113224-first_migration.js":

@@ -1,7 +0,7 @@

jest.mock("../../lib/actions/status", () => jest.fn());
import migrationsDir from "../../lib/env/migrationsDir.js";
import config from "../../lib/env/config.js";
import status from "../../lib/actions/status.js";
import up from "../../lib/actions/up.js";
const migrationsDir = require("../../lib/env/migrationsDir");
const config = require("../../lib/env/config");
const status = require("../../lib/actions/status");
const up = require("../../lib/actions/up");
vi.mock("../../lib/actions/status");

@@ -18,3 +18,3 @@ describe("up", () => {

const migration = {
up: jest.fn().mockResolvedValue()
up: vi.fn().mockResolvedValue()
};

@@ -26,3 +26,3 @@ return migration;

const mock = {
collection: jest.fn((name) => {
collection: vi.fn((name) => {
if (name === "changelog") return changelogCollection;

@@ -42,3 +42,3 @@ if (name === "changelog_lock") return changelogLockCollection;

return {
insertOne: jest.fn().mockResolvedValue()
insertOne: vi.fn().mockResolvedValue()
};

@@ -49,10 +49,10 @@ }

const findStub = {
toArray: jest.fn().mockResolvedValue([])
toArray: vi.fn().mockResolvedValue([])
};
return {
insertOne: jest.fn().mockResolvedValue(),
createIndex: jest.fn().mockResolvedValue(),
find: jest.fn().mockReturnValue(findStub),
deleteMany: jest.fn().mockResolvedValue(),
insertOne: vi.fn().mockResolvedValue(),
createIndex: vi.fn().mockResolvedValue(),
find: vi.fn().mockReturnValue(findStub),
deleteMany: vi.fn().mockResolvedValue(),
};

@@ -62,4 +62,4 @@ }

beforeEach(() => {
jest.clearAllMocks();
jest.restoreAllMocks();
vi.clearAllMocks();
vi.restoreAllMocks();

@@ -92,4 +92,4 @@ firstPendingMigration = mockMigration();

jest.spyOn(config, 'shouldExist').mockResolvedValue();
jest.spyOn(config, 'read').mockReturnValue({
vi.spyOn(config, 'shouldExist').mockResolvedValue();
vi.spyOn(config, 'read').mockReturnValue({
changelogCollectionName: "changelog",

@@ -100,3 +100,3 @@ lockCollectionName: "changelog_lock",

jest.spyOn(migrationsDir, 'loadMigration')
vi.spyOn(migrationsDir, 'loadMigration')
.mockImplementation((fileName) => {

@@ -122,6 +122,8 @@ if (fileName === "20160607173840-first_pending_migration.js") {

expect(migrationsDir.loadMigration).toHaveBeenCalledTimes(2);
expect(migrationsDir.loadMigration.mock.calls[0][0]).toBe(
expect(migrationsDir.loadMigration).toHaveBeenNthCalledWith(
1,
"20160607173840-first_pending_migration.js"
);
expect(migrationsDir.loadMigration.mock.calls[1][0]).toBe(
expect(migrationsDir.loadMigration).toHaveBeenNthCalledWith(
2,
"20160608060209-second_pending_migration.js"

@@ -135,17 +137,14 @@ );

expect(secondPendingMigration.up).toHaveBeenCalled();
// Check call order
const firstCallOrder = firstPendingMigration.up.mock.invocationCallOrder[0];
const secondCallOrder = secondPendingMigration.up.mock.invocationCallOrder[0];
expect(firstCallOrder).toBeLessThan(secondCallOrder);
// Verify second was called after first
expect(secondPendingMigration.up).toHaveBeenCalledAfter(firstPendingMigration.up);
});
it("should populate the changelog with info about the upgraded migrations", async () => {
jest.useFakeTimers();
jest.setSystemTime(new Date("2016-06-09T08:07:00.077Z"));
vi.useFakeTimers();
vi.setSystemTime(new Date("2016-06-09T08:07:00.077Z"));
await up(db);
expect(changelogCollection.insertOne).toHaveBeenCalled();
expect(changelogCollection.insertOne).toHaveBeenCalledTimes(2);
expect(changelogCollection.insertOne.mock.calls[0][0]).toEqual({
expect(changelogCollection.insertOne).toHaveBeenNthCalledWith(1, {
appliedAt: new Date("2016-06-09T08:07:00.077Z"),

@@ -156,7 +155,7 @@ fileName: "20160607173840-first_pending_migration.js",

jest.useRealTimers();
vi.useRealTimers();
});
it("should populate the changelog with info about the upgraded migrations (using file hash)", async () => {
jest.spyOn(config, 'read').mockReturnValue({
vi.spyOn(config, 'read').mockReturnValue({
changelogCollectionName: "changelog",

@@ -168,13 +167,12 @@ lockCollectionName: "changelog_lock",

changelogLockCollection.find.mockReturnValue({
toArray: jest.fn().mockResolvedValue([{ createdAt: new Date() }])
toArray: vi.fn().mockResolvedValue([{ createdAt: new Date() }])
});
jest.useFakeTimers();
jest.setSystemTime(new Date("2016-06-09T08:07:00.077Z"));
vi.useFakeTimers();
vi.setSystemTime(new Date("2016-06-09T08:07:00.077Z"));
await up(db);
expect(changelogCollection.insertOne).toHaveBeenCalled();
expect(changelogCollection.insertOne).toHaveBeenCalledTimes(2);
expect(changelogCollection.insertOne.mock.calls[0][0]).toEqual({
expect(changelogCollection.insertOne).toHaveBeenNthCalledWith(1, {
appliedAt: new Date("2016-06-09T08:07:00.077Z"),

@@ -186,3 +184,3 @@ "fileHash": undefined,

jest.useRealTimers();
vi.useRealTimers();
});

@@ -245,3 +243,3 @@

it("should ignore lock if feature is disabled", async() => {
jest.spyOn(config, 'read').mockReturnValue({
vi.spyOn(config, 'read').mockReturnValue({
changelogCollectionName: "changelog",

@@ -252,3 +250,3 @@ lockCollectionName: "changelog_lock",

changelogLockCollection.find.mockReturnValue({
toArray: jest.fn().mockResolvedValue([{ createdAt: new Date() }])
toArray: vi.fn().mockResolvedValue([{ createdAt: new Date() }])
});

@@ -271,3 +269,3 @@

changelogLockCollection.find.mockReturnValue({
toArray: jest.fn().mockResolvedValue([{ createdAt: new Date() }])
toArray: vi.fn().mockResolvedValue([{ createdAt: new Date() }])
});

@@ -274,0 +272,0 @@

@@ -1,18 +0,10 @@

jest.mock("fs/promises", () => ({
stat: jest.fn(),
cp: jest.fn(),
mkdir: jest.fn(),
readdir: jest.fn(),
readFile: jest.fn(),
}));
const path = require("path");
const fs = require("fs/promises");
const moduleLoader = require("../../lib/utils/module-loader");
import path from "path";
import fs from "fs/promises";
import moduleLoader from "../../lib/utils/module-loader.js";
// Don't auto-mock config, we'll use the real implementation
const config = require("../../lib/env/config");
import config from "../../lib/env/config.js";
describe("config", () => {
beforeEach(() => {
jest.clearAllMocks();
vi.clearAllMocks();
// Reset config state between tests

@@ -22,3 +14,3 @@ delete global.options;

// Restore any spies
jest.restoreAllMocks();
vi.restoreAllMocks();
});

@@ -29,3 +21,3 @@

it('should not yield an error when the config was set manually', async () => {
fs.stat.mockRejectedValue(new Error("Not found"));
vi.spyOn(fs, 'stat').mockRejectedValue(new Error("Not found"));
config.set({ my: 'config'})

@@ -36,3 +28,3 @@ await config.shouldExist();

it("should not yield an error if the config exists", async () => {
fs.stat.mockResolvedValue({});
vi.spyOn(fs, 'stat').mockResolvedValue({});
await config.shouldExist();

@@ -43,3 +35,3 @@ });

const configPath = path.join(process.cwd(), "migrate-mongo-config.js");
fs.stat.mockRejectedValue(new Error("It does not exist"));
vi.spyOn(fs, 'stat').mockRejectedValue(new Error("It does not exist"));
await expect(config.shouldExist()).rejects.toThrow(

@@ -54,3 +46,3 @@ `config file does not exist: ${configPath}`

it('should not yield an error when the config was set manually', async () => {
fs.stat.mockRejectedValue(new Error("Not found"));
vi.spyOn(fs, 'stat').mockRejectedValue(new Error("Not found"));
config.set({ my: 'config'})

@@ -63,3 +55,3 @@ await config.shouldNotExist();

error.code = "ENOENT";
fs.stat.mockRejectedValue(error);
vi.spyOn(fs, 'stat').mockRejectedValue(error);
await config.shouldNotExist();

@@ -70,3 +62,3 @@ });

const configPath = path.join(process.cwd(), "migrate-mongo-config.js");
fs.stat.mockResolvedValue({});
vi.spyOn(fs, 'stat').mockResolvedValue({});
await expect(config.shouldNotExist()).rejects.toThrow(

@@ -112,4 +104,4 @@ `config file already exists: ${configPath}`

error.code = 'ERR_REQUIRE_ESM';
jest.spyOn(moduleLoader, 'require').mockImplementation(() => { throw error; });
jest.spyOn(moduleLoader, 'import').mockResolvedValue({});
vi.spyOn(moduleLoader, 'require').mockImplementation(() => { throw error; });
vi.spyOn(moduleLoader, 'import').mockResolvedValue({});
await config.read();

@@ -122,4 +114,4 @@ expect(moduleLoader.import).toHaveBeenCalled();

error.code = 'ERR_REQUIRE_ASYNC_MODULE';
jest.spyOn(moduleLoader, 'require').mockImplementation(() => { throw error; });
jest.spyOn(moduleLoader, 'import').mockResolvedValue({});
vi.spyOn(moduleLoader, 'require').mockImplementation(() => { throw error; });
vi.spyOn(moduleLoader, 'import').mockResolvedValue({});
await config.read();

@@ -137,3 +129,3 @@ expect(moduleLoader.import).toHaveBeenCalled();

jest.spyOn(moduleLoader, 'require').mockResolvedValue({
vi.spyOn(moduleLoader, 'require').mockResolvedValue({
default: expectedConfig

@@ -154,3 +146,3 @@ });

jest.spyOn(moduleLoader, 'require').mockResolvedValue(expectedConfig);
vi.spyOn(moduleLoader, 'require').mockResolvedValue(expectedConfig);

@@ -168,3 +160,3 @@ const actual = await config.read();

jest.spyOn(moduleLoader, 'require').mockResolvedValue(originalConfig);
vi.spyOn(moduleLoader, 'require').mockResolvedValue(originalConfig);
global.options = { migrationsDir: customMigrationsDir };

@@ -185,3 +177,3 @@

jest.spyOn(moduleLoader, 'require').mockResolvedValue(originalConfig);
vi.spyOn(moduleLoader, 'require').mockResolvedValue(originalConfig);
delete global.options;

@@ -202,4 +194,4 @@

error.code = 'ERR_REQUIRE_ESM';
jest.spyOn(moduleLoader, 'require').mockImplementation(() => { throw error; });
jest.spyOn(moduleLoader, 'import').mockResolvedValue(originalConfig);
vi.spyOn(moduleLoader, 'require').mockImplementation(() => { throw error; });
vi.spyOn(moduleLoader, 'import').mockResolvedValue(originalConfig);
global.options = { migrationsDir: customMigrationsDir };

@@ -206,0 +198,0 @@

@@ -1,17 +0,7 @@

jest.mock("mongodb", () => ({
MongoClient: {
connect: jest.fn()
}
}));
jest.mock("fs/promises", () => ({
stat: jest.fn(),
cp: jest.fn(),
mkdir: jest.fn(),
readdir: jest.fn(),
readFile: jest.fn(),
}));
vi.mock("mongodb");
vi.mock("fs/promises");
const config = require("../../lib/env/config");
const mongodb = require("mongodb");
const database = require("../../lib/env/database");
import config from "../../lib/env/config.js";
import mongodb from "mongodb";
import database from "../../lib/env/database.js";

@@ -37,3 +27,3 @@ describe("database", () => {

return {
db: jest.fn().mockReturnValue({ the: "db" }),
db: vi.fn().mockReturnValue({ the: "db" }),
close: "theCloseFnFromMongoClient"

@@ -44,8 +34,8 @@ };

beforeEach(() => {
jest.clearAllMocks();
jest.restoreAllMocks();
vi.clearAllMocks();
vi.restoreAllMocks();
configObj = createConfigObj();
client = mockClient();
jest.spyOn(config, 'read').mockReturnValue(configObj);
mongodb.MongoClient.connect.mockResolvedValue(client);
vi.spyOn(config, 'read').mockReturnValue(configObj);
vi.spyOn(mongodb.MongoClient, "connect").mockResolvedValue(client);
});

@@ -79,3 +69,3 @@

it("should yield an error when unable to connect", async () => {
mongodb.MongoClient.connect.mockRejectedValue(new Error("Unable to connect"));
vi.spyOn(mongodb.MongoClient, "connect").mockRejectedValue(new Error("Unable to connect"));
await expect(database.connect()).rejects.toThrow("Unable to connect");

@@ -82,0 +72,0 @@ });

@@ -1,20 +0,12 @@

jest.mock("fs/promises", () => ({
stat: jest.fn(),
cp: jest.fn(),
mkdir: jest.fn(),
readdir: jest.fn(),
readFile: jest.fn(),
}));
import path from "path";
import fs from "fs/promises";
import config from "../../lib/env/config.js";
import moduleLoader from "../../lib/utils/module-loader.js";
import migrationsDir from "../../lib/env/migrationsDir.js";
const path = require("path");
const fs = require("fs/promises");
const config = require("../../lib/env/config");
const moduleLoader = require("../../lib/utils/module-loader");
const migrationsDir = require("../../lib/env/migrationsDir");
describe("migrationsDir", () => {
beforeEach(() => {
jest.clearAllMocks();
jest.restoreAllMocks();
jest.spyOn(config, 'read').mockReturnValue({
vi.clearAllMocks();
vi.restoreAllMocks();
vi.spyOn(config, 'read').mockReturnValue({
migrationsDir: "migrations",

@@ -27,3 +19,3 @@ migrationFileExtension: ".js"

it("should use the configured relative migrations dir when a config file is available", async () => {
jest.spyOn(config, 'read').mockReturnValue({
vi.spyOn(config, 'read').mockReturnValue({
migrationsDir: "custom-migrations-dir"

@@ -37,3 +29,3 @@ });

it("should use the configured absolute migrations dir when a config file is available", async () => {
jest.spyOn(config, 'read').mockReturnValue({
vi.spyOn(config, 'read').mockReturnValue({
migrationsDir: "/absolute/path/to/my/custom-migrations-dir"

@@ -47,3 +39,3 @@ });

it("should use the default migrations directory when no migrationsDir is specified in the config file", async () => {
jest.spyOn(config, 'read').mockReturnValue({});
vi.spyOn(config, 'read').mockReturnValue({});
expect(await migrationsDir.resolve()).toBe(

@@ -55,3 +47,3 @@ path.join(process.cwd(), "migrations")

it("should use the default migrations directory when unable to read the config file", async () => {
jest.spyOn(config, 'read').mockImplementation(() => { throw new Error("Cannot read config file"); });
vi.spyOn(config, 'read').mockImplementation(() => { throw new Error("Cannot read config file"); });
expect(await migrationsDir.resolve()).toBe(

@@ -65,3 +57,3 @@ path.join(process.cwd(), "migrations")

it("should not reject with an error if the migrations dir exists", async () => {
fs.stat.mockResolvedValue({});
vi.spyOn(fs, 'stat').mockResolvedValue({});
await migrationsDir.shouldExist();

@@ -72,3 +64,3 @@ });

const migrationsPath = path.join(process.cwd(), "migrations");
fs.stat.mockRejectedValue(new Error("It does not exist"));
vi.spyOn(fs, 'stat').mockRejectedValue(new Error("It does not exist"));
await expect(migrationsDir.shouldExist()).rejects.toThrow(

@@ -84,3 +76,3 @@ `migrations directory does not exist: ${migrationsPath}`

error.code = "ENOENT";
fs.stat.mockRejectedValue(error);
vi.spyOn(fs, 'stat').mockRejectedValue(error);
await migrationsDir.shouldNotExist();

@@ -91,3 +83,3 @@ });

const migrationsPath = path.join(process.cwd(), "migrations");
fs.stat.mockResolvedValue({});
vi.spyOn(fs, 'stat').mockResolvedValue({});
await expect(migrationsDir.shouldNotExist()).rejects.toThrow(

@@ -101,3 +93,3 @@ `migrations directory already exists: ${migrationsPath}`

it("should read the directory and yield the result", async () => {
fs.readdir.mockResolvedValue(["file1.js", "file2.js"]);
vi.spyOn(fs, 'readdir').mockResolvedValue(["file1.js", "file2.js"]);
const files = await migrationsDir.getFileNames();

@@ -108,6 +100,6 @@ expect(files).toEqual(["file1.js", "file2.js"]);

it("should list only files with configured extension", async () => {
jest.spyOn(config, 'read').mockReturnValue({
vi.spyOn(config, 'read').mockReturnValue({
migrationFileExtension: ".ts"
});
fs.readdir.mockResolvedValue(["file1.ts", "file2.ts", "file1.js", "file2.js", ".keep"]);
vi.spyOn(fs, 'readdir').mockResolvedValue(["file1.ts", "file2.ts", "file1.js", "file2.js", ".keep"]);
const files = await migrationsDir.getFileNames();

@@ -118,3 +110,3 @@ expect(files).toEqual(["file1.ts", "file2.ts"]);

it("should yield errors that occurred while reading the dir", async () => {
fs.readdir.mockRejectedValue(new Error("Could not read"));
vi.spyOn(fs, 'readdir').mockRejectedValue(new Error("Could not read"));
await expect(migrationsDir.getFileNames()).rejects.toThrow("Could not read");

@@ -124,3 +116,3 @@ });

it("should be sorted in alphabetical order", async () => {
fs.readdir.mockResolvedValue([
vi.spyOn(fs, 'readdir').mockResolvedValue([
"20201014172343-test.js",

@@ -152,4 +144,4 @@ "20201014172356-test3.js",

it("should use CommonJS default", async () => {
jest.spyOn(moduleLoader, 'require').mockReturnValue({ up: jest.fn(), down: jest.fn() });
jest.spyOn(moduleLoader, 'import');
vi.spyOn(moduleLoader, 'require').mockReturnValue({ up: vi.fn(), down: vi.fn() });
vi.spyOn(moduleLoader, 'import');
await migrationsDir.loadMigration("someFile.js");

@@ -163,4 +155,4 @@ expect(moduleLoader.require).toHaveBeenCalled();

error.code = 'ERR_REQUIRE_ESM';
jest.spyOn(moduleLoader, 'require').mockImplementation(() => { throw error; });
jest.spyOn(moduleLoader, 'import').mockResolvedValue({ default: () => jest.fn() });
vi.spyOn(moduleLoader, 'require').mockImplementation(() => { throw error; });
vi.spyOn(moduleLoader, 'import').mockResolvedValue({ default: () => vi.fn() });
await migrationsDir.loadMigration("someFile.js");

@@ -173,4 +165,4 @@ expect(moduleLoader.import).toHaveBeenCalled();

error.code = 'ERR_REQUIRE_ESM';
jest.spyOn(moduleLoader, 'require').mockImplementation(() => { throw error; });
jest.spyOn(moduleLoader, 'import').mockResolvedValue({ up: jest.fn(), down: jest.fn() });
vi.spyOn(moduleLoader, 'require').mockImplementation(() => { throw error; });
vi.spyOn(moduleLoader, 'import').mockResolvedValue({ up: vi.fn(), down: vi.fn() });
await migrationsDir.loadMigration("someFile.js");

@@ -183,4 +175,4 @@ expect(moduleLoader.import).toHaveBeenCalled();

error.code = 'ERR_REQUIRE_ASYNC_MODULE';
jest.spyOn(moduleLoader, 'require').mockImplementation(() => { throw error; });
jest.spyOn(moduleLoader, 'import').mockResolvedValue({ up: jest.fn(), down: jest.fn() });
vi.spyOn(moduleLoader, 'require').mockImplementation(() => { throw error; });
vi.spyOn(moduleLoader, 'import').mockResolvedValue({ up: vi.fn(), down: vi.fn() });
await migrationsDir.loadMigration("someFile.js");

@@ -193,3 +185,3 @@ expect(moduleLoader.import).toHaveBeenCalled();

it("should provide the value if specified", async () => {
jest.spyOn(config, 'read').mockReturnValue({
vi.spyOn(config, 'read').mockReturnValue({
migrationFileExtension: ".ts"

@@ -201,3 +193,3 @@ });

it("should error if the extension does not start with dot", async () => {
jest.spyOn(config, 'read').mockReturnValue({
vi.spyOn(config, 'read').mockReturnValue({
migrationFileExtension: "js"

@@ -208,3 +200,3 @@ });

it("should use the default if not specified", async() => {
jest.spyOn(config, 'read').mockReturnValue({
vi.spyOn(config, 'read').mockReturnValue({
migrationFileExtension: undefined

@@ -216,3 +208,3 @@ });

it("should use the default if config file not found", async() => {
jest.spyOn(config, 'read').mockImplementation(() => { throw new Error(); });
vi.spyOn(config, 'read').mockImplementation(() => { throw new Error(); });
const ext = await migrationsDir.resolveMigrationFileExtension();

@@ -225,3 +217,3 @@ expect(ext).toBe(".js");

it("should return true if sample migration exists", async () => {
fs.stat.mockResolvedValue({});
vi.spyOn(fs, 'stat').mockResolvedValue({});
const result = await migrationsDir.doesSampleMigrationExist();

@@ -232,3 +224,3 @@ expect(result).toBe(true);

it("should return false if sample migration doesn't exists", async () => {
fs.stat.mockRejectedValue(new Error("It does not exist"));
vi.spyOn(fs, 'stat').mockRejectedValue(new Error("It does not exist"));
const result = await migrationsDir.doesSampleMigrationExist();

@@ -241,3 +233,3 @@ expect(result).toBe(false);

it("should return a hash based on the file contents", async () => {
fs.readFile.mockResolvedValue("some string to hash");
vi.spyOn(fs, 'readFile').mockResolvedValue("some string to hash");
const result = await migrationsDir.loadFileHash('somefile.js');

@@ -244,0 +236,0 @@ expect(result).toBe("ea83a45637a9af470a994d2c9722273ef07d47aec0660a1d10afe6e9586801ac");

@@ -1,7 +0,7 @@

const { MongoMemoryServer } = require("mongodb-memory-server");
const { MongoClient } = require("mongodb");
const { execSync } = require("child_process");
const fs = require("fs/promises");
const path = require("path");
const os = require("os");
import { MongoMemoryServer } from "mongodb-memory-server";
import { MongoClient } from "mongodb";
import { execSync } from "child_process";
import fs from "fs/promises";
import path from "path";
import os from "os";

@@ -1046,2 +1046,155 @@ describe("Integration Tests", () => {

describe("CommonJS Compatibility", () => {
beforeEach(async () => {
await fs.mkdir(migrationsDir, { recursive: true });
await writeConfig({ moduleSystem: "commonjs" });
});
it("should run CommonJS migrations with module.exports", async () => {
await createMigration(
"test-commonjs",
"await db.collection('test').insertOne({ format: 'commonjs' });",
"await db.collection('test').deleteOne({ format: 'commonjs' });"
);
const output = runMigrateMongo("up");
expect(output).toContain("MIGRATED UP");
const doc = await db.collection("test").findOne({ format: "commonjs" });
expect(doc).toBeTruthy();
expect(doc.format).toBe("commonjs");
});
it("should rollback CommonJS migrations", async () => {
await createMigration(
"rollback-cjs",
"await db.collection('test').insertOne({ rollback: 'cjs' });",
"await db.collection('test').deleteOne({ rollback: 'cjs' });"
);
runMigrateMongo("up");
const upOutput = runMigrateMongo("down");
expect(upOutput).toContain("MIGRATED DOWN");
const doc = await db.collection("test").findOne({ rollback: "cjs" });
expect(doc).toBeNull();
});
it("should handle multiple CommonJS migrations", async () => {
await createMigration(
"cjs-multi-1",
"await db.collection('test').insertOne({ seq: 1 });",
"await db.collection('test').deleteOne({ seq: 1 });"
);
await new Promise(resolve => setTimeout(resolve, 1000));
await createMigration(
"cjs-multi-2",
"await db.collection('test').insertOne({ seq: 2 });",
"await db.collection('test').deleteOne({ seq: 2 });"
);
const output = runMigrateMongo("up");
expect(output).toContain("MIGRATED UP");
const count = await db.collection("test").countDocuments({ seq: { $exists: true } });
expect(count).toBe(2);
});
it("should work with CommonJS requiring external modules", async () => {
const migrationFile = await createMigration("placeholder", "", "");
const migrationPath = path.join(migrationsDir, migrationFile);
const content = `
const crypto = require('crypto');
module.exports = {
async up(db, client) {
const hash = crypto.createHash('md5').update('test').digest('hex');
await db.collection('test').insertOne({ hash });
},
async down(db, client) {
await db.collection('test').deleteMany({});
}
};`;
await fs.writeFile(migrationPath, content);
const output = runMigrateMongo("up");
expect(output).toContain("MIGRATED UP");
const doc = await db.collection("test").findOne({ hash: { $exists: true } });
expect(doc).toBeTruthy();
expect(doc.hash).toBe("098f6bcd4621d373cade4e832627b4f6");
});
it("should support CommonJS with complex operations", async () => {
const migrationFile = await createMigration("placeholder", "", "");
const migrationPath = path.join(migrationsDir, migrationFile);
const content = `
module.exports = {
async up(db, client) {
// Use client operations directly
const collections = await db.listCollections().toArray();
await db.collection('test').insertOne({
type: 'complex-cjs',
collectionCount: collections.length
});
},
async down(db, client) {
await db.collection('test').deleteMany({ type: 'complex-cjs' });
}
};`;
await fs.writeFile(migrationPath, content);
const output = runMigrateMongo("up");
expect(output).toContain("MIGRATED UP");
const doc = await db.collection("test").findOne({ type: "complex-cjs" });
expect(doc).toBeTruthy();
expect(typeof doc.collectionCount).toBe('number');
});
it("should handle CommonJS migrations with synchronous module.exports", async () => {
const migrationFile = await createMigration("placeholder", "", "");
const migrationPath = path.join(migrationsDir, migrationFile);
const content = `
module.exports = {
up: async function(db, client) {
await db.collection('test').insertOne({ style: 'function-keyword' });
},
down: async function(db, client) {
await db.collection('test').deleteOne({ style: 'function-keyword' });
}
};`;
await fs.writeFile(migrationPath, content);
const output = runMigrateMongo("up");
expect(output).toContain("MIGRATED UP");
const doc = await db.collection("test").findOne({ style: "function-keyword" });
expect(doc).toBeTruthy();
});
it("should validate CommonJS migration structure", async () => {
const migrationFile = await createMigration("placeholder", "", "");
const migrationPath = path.join(migrationsDir, migrationFile);
// Missing 'up' function
const invalidContent = `
module.exports = {
down: async function(db, client) {
await db.collection('test').deleteMany({});
}
};`;
await fs.writeFile(migrationPath, invalidContent);
const result = runMigrateMongo("up", testDir, true);
expect(result.error).toBe(true);
});
});
describe("Large-Scale Performance", () => {

@@ -1048,0 +1201,0 @@ beforeEach(async () => {

module.exports = {
testEnvironment: 'node',
coverageDirectory: 'coverage',
collectCoverageFrom: [
'lib/**/*.js',
'!lib/**/*.test.js',
'!lib/migrate-mongo.js', // Main export file - no logic to test
],
testMatch: [
'**/test/**/*.test.js',
],
testPathIgnorePatterns: [
'/node_modules/',
'/test/integration.test.js', // Exclude integration test from unit tests
],
coverageReporters: ['text', 'html', 'lcov'],
clearMocks: true,
restoreMocks: true,
resetMocks: true,
};