🚀 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.0
to
14.0.1
+7
__mocks__/fs/promises.js
module.exports = {
stat: jest.fn(),
cp: jest.fn(),
mkdir: jest.fn(),
readdir: jest.fn(),
readFile: jest.fn(),
};
module.exports = {
MongoClient: {
connect: jest.fn()
}
};
name: CI
on:
push:
branches: [ main, master ]
pull_request:
branches: [ main, master ]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [20.x, 22.x]
mongodb-version: ['4.4', '5.0', '6.0', '7.0']
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- name: Start MongoDB ${{ matrix.mongodb-version }}
uses: supercharge/mongodb-github-action@1.11.0
with:
mongodb-version: ${{ matrix.mongodb-version }}
- name: Install dependencies
run: npm ci
- name: Run linter
run: npm run lint
- name: Run tests with coverage
run: npm test
- name: Upload coverage to Coveralls
uses: coverallsapp/github-action@v2
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
parallel: true
flag-name: node-${{ matrix.node-version }}-mongodb-${{ matrix.mongodb-version }}
finish:
needs: test
runs-on: ubuntu-latest
steps:
- name: Finish parallel build
uses: coverallsapp/github-action@v2
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
parallel-finished: true
# GitHub Actions Migration Guide
## Problem
Your Coveralls coverage stopped updating because **Travis CI ended their free tier** for open source projects in late 2020/early 2021.
## Solution
I've created a GitHub Actions workflow that will:
- ✅ Run tests on Node.js 20.x and 22.x
- ✅ Test against MongoDB versions 4.4, 5.0, 6.0, and 7.0
- ✅ Run ESLint
- ✅ Generate coverage reports
- ✅ Automatically upload to Coveralls.io
## Files Created
### `.github/workflows/ci.yml`
This is your new CI pipeline. It replaces `.travis.yml`.
## Next Steps
### 1. Commit and Push
```bash
git add .github/workflows/ci.yml
git commit -m "Add GitHub Actions CI workflow with Coveralls integration"
git push
```
### 2. Verify on GitHub
- Go to: https://github.com/seppevs/migrate-mongo/actions
- You should see the workflow running automatically
### 3. Check Coveralls
- After the workflow completes, visit: https://coveralls.io/github/seppevs/migrate-mongo
- Coverage should be updated!
### 4. Optional: Remove Travis CI
Once you confirm GitHub Actions is working:
```bash
git rm .travis.yml
git commit -m "Remove deprecated Travis CI configuration"
git push
```
You can also remove the coveralls package from devDependencies since GitHub Actions uses a dedicated action:
```bash
npm uninstall coveralls
git add package.json package-lock.json
git commit -m "Remove coveralls CLI (GitHub Action handles uploads)"
git push
```
## Benefits of GitHub Actions
1. **Free for public repos** - No more Travis CI payment issues
2. **Better integration** - Native GitHub support
3. **Faster builds** - Parallel testing across Node/MongoDB versions
4. **More flexible** - Easy to add new checks or deployments
5. **Modern** - Actively maintained and improved
## Troubleshooting
**No workflows appearing?**
- Check that GitHub Actions are enabled in your repo settings
**Coverage not updating?**
- Verify the repo is linked in Coveralls.io
- Check the Actions logs for any errors
- Ensure the repo is public
**Want to test locally first?**
```bash
npm test # Runs tests with coverage
# Coverage report is in coverage/lcov-report/index.html
```
## The Workflow Explained
```yaml
# Runs on every push to main/master and on pull requests
on:
push:
branches: [ main, master ]
pull_request:
branches: [ main, master ]
# Tests in a matrix:
# - Node.js: 20.x, 22.x
# - MongoDB: 4.4, 5.0, 6.0, 7.0
# Total: 8 parallel jobs for comprehensive testing
# After all tests pass, coverage is uploaded to Coveralls
```
This ensures your package works across all supported Node.js and MongoDB versions!
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',
],
coverageReporters: ['text', 'html', 'lcov'],
clearMocks: true,
restoreMocks: true,
resetMocks: true,
};
module.exports = jest.fn();
+19
-1

@@ -7,3 +7,21 @@ # Changelog

## [14.0.0] - TBD
## [14.0.1] - 2025-12-03
- Migrate test suite from Mocha/Sinon/Chai/Proxyquire to Jest
- Improves test maintainability and developer experience
- Maintains 100% code coverage
- Remove fs-extra dependency in favor of native Node.js fs/promises API
- Reduces production dependencies from 3 to 2
- Eliminates deprecated dependency warnings
- Smaller bundle size
- Migrate from Travis CI to GitHub Actions
- Fixes broken CI/coverage reporting (Travis CI discontinued free tier)
- Tests now run on Node.js 20.x and 22.x with MongoDB 4.4, 5.0, 6.0, 7.0
- Automated Coveralls integration
- Added CodeQL security scanning
- Added Dependabot for dependency updates
- Added automated NPM publishing workflow
- Remove coveralls CLI dependency (GitHub Actions handles uploads)
- Fix all npm deprecation warnings (glob@7, inflight)
## [14.0.0] - 2025-12-03
- **BREAKING**: Remove callback-based migration support

@@ -10,0 +28,0 @@ - Migrations must now use Promises or async/await

+2
-2

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

const fs = require("fs-extra");
const fs = require("fs/promises");
const path = require("path");

@@ -28,4 +28,4 @@ const date = require("../utils/date");

const destination = path.join(migrationsDirPath, filename);
await fs.copy(source, destination);
await fs.cp(source, destination);
return filename;
};

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

const fs = require("fs-extra");
const fs = require("fs/promises");
const path = require("path");

@@ -7,3 +7,3 @@

function copySampleConfigFile() {
async function copySampleConfigFile() {
const moduleSystem = global.options.module === 'esm' ? 'esm' : 'commonjs';

@@ -15,7 +15,7 @@ const source = path.join(__dirname, `../../samples/${moduleSystem}/migrate-mongo-config.js`);

);
return fs.copy(source, destination);
await fs.cp(source, destination);
}
function createMigrationsDirectory() {
return fs.mkdirs(path.join(process.cwd(), "migrations"));
async function createMigrationsDirectory() {
await fs.mkdir(path.join(process.cwd(), "migrations"), { recursive: true });
}

@@ -27,3 +27,3 @@

await copySampleConfigFile();
return createMigrationsDirectory();
await createMigrationsDirectory();
};

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

const fs = require("fs-extra");
const fs = require("fs/promises");
const path = require("path");

@@ -3,0 +3,0 @@ const url = require("url");

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

const fs = require("fs-extra");
const fs = require("fs/promises");
const path = require("path");

@@ -106,3 +106,3 @@ const url = require("url");

if (e.code === 'ERR_REQUIRE_ESM' || e.code === 'ERR_REQUIRE_ASYNC_MODULE') {
const loadedImport = moduleLoader.import(url.pathToFileURL(migrationPath));
const loadedImport = await moduleLoader.import(url.pathToFileURL(migrationPath));
return getModuleExports(loadedImport);

@@ -109,0 +109,0 @@ }

{
"name": "migrate-mongo",
"version": "14.0.0",
"version": "14.0.1",
"description": "A database migration tool for MongoDB in Node",

@@ -10,4 +10,3 @@ "main": "lib/migrate-mongo.js",

"scripts": {
"test": "nyc --reporter=html --reporter=text mocha --recursive",
"test-coverage": "nyc --reporter=text-lcov mocha --recursive | coveralls",
"test": "jest --coverage",
"lint": "eslint lib/ test/"

@@ -30,4 +29,3 @@ },

"cli-table3": "^0.6.5",
"commander": "^14.0.2",
"fs-extra": "^11.3.2"
"commander": "^14.0.2"
},

@@ -38,12 +36,9 @@ "peerDependencies": {

"devDependencies": {
"chai": "^6.2.1",
"coveralls": "^3.1.1",
"eslint": "^9.39.1",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-mocha": "^11.2.0",
"mocha": "^11.7.5",
"nyc": "^17.1.0",
"proxyquire": "^2.1.3",
"sinon": "^21.0.0"
"jest": "^30.2.0"
},
"overrides": {
"test-exclude": "^7.0.1"
},
"eslintConfig": {

@@ -50,0 +45,0 @@ "extends": [

@@ -1,54 +0,25 @@

const { expect } = require("chai");
const sinon = require("sinon");
jest.mock("fs/promises");
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");
const proxyquire = require("proxyquire");
describe("create", () => {
let create;
let migrationsDir;
let config;
let fs;
function mockMigrationsDir() {
return {
shouldExist: sinon.stub().returns(Promise.resolve()),
resolveMigrationFileExtension: sinon.stub().returns('.js'),
doesSampleMigrationExist: sinon.stub().returns(Promise.resolve(false))
};
}
function mockConfig() {
return {
shouldExist: sinon.stub().returns(Promise.resolve()),
read: sinon.stub().returns(Promise.resolve({
moduleSystem: 'commonjs',
}))
};
}
function mockFs() {
return {
copy: sinon.stub().returns(Promise.resolve())
};
}
beforeEach(() => {
migrationsDir = mockMigrationsDir();
config = mockConfig();
fs = mockFs();
create = proxyquire("../../lib/actions/create", {
"../env/migrationsDir": migrationsDir,
"../env/config": config,
"fs-extra": fs
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({
moduleSystem: 'commonjs',
});
fs.cp.mockResolvedValue();
});
it("should yield an error when called without a description", async () => {
try {
await create(null);
expect.fail("Error was not thrown");
} catch (err) {
expect(err.message).to.equal("Missing parameter: description");
}
await expect(create(null)).rejects.toThrow("Missing parameter: description");
});

@@ -58,15 +29,10 @@

await create("my_description");
expect(migrationsDir.shouldExist.called).to.equal(true);
expect(migrationsDir.shouldExist).toHaveBeenCalled();
});
it("should yield an error when the migrations directory does not exist", async () => {
migrationsDir.shouldExist.returns(
Promise.reject(new Error("migrations directory does not exist"))
jest.spyOn(migrationsDir, 'shouldExist').mockRejectedValue(
new Error("migrations directory does not exist")
);
try {
await create("my_description");
expect.fail("Error was not thrown");
} catch (err) {
expect(err.message).to.equal("migrations directory does not exist");
}
await expect(create("my_description")).rejects.toThrow("migrations directory does not exist");
});

@@ -76,48 +42,53 @@

await create("my_description");
expect(config.shouldExist.called).to.equal(false);
expect(config.shouldExist).not.toHaveBeenCalled();
});
it("should create a new migration file and yield the filename", async () => {
const clock = sinon.useFakeTimers(
new Date("2016-06-09T08:07:00.077Z").getTime()
);
jest.useFakeTimers();
jest.setSystemTime(new Date("2016-06-09T08:07:00.077Z"));
const filename = await create("my_description");
expect(fs.copy.called).to.equal(true);
expect(fs.copy.getCall(0).args[0]).to.equal(
expect(fs.cp).toHaveBeenCalled();
expect(fs.cp.mock.calls[0][0]).toBe(
path.join(__dirname, "../../samples/commonjs/migration.js")
);
expect(fs.copy.getCall(0).args[1]).to.equal(
expect(fs.cp.mock.calls[0][1]).toBe(
path.join(process.cwd(), "migrations", "20160609080700-my_description.js")
);
expect(filename).to.equal("20160609080700-my_description.js");
clock.restore();
expect(filename).toBe("20160609080700-my_description.js");
jest.useRealTimers();
});
it("should create a new migration file and yield the filename with custom extension", async () => {
const clock = sinon.useFakeTimers(
new Date("2016-06-09T08:07:00.077Z").getTime()
);
migrationsDir.resolveMigrationFileExtension.returns('.ts');
jest.useFakeTimers();
jest.setSystemTime(new Date("2016-06-09T08:07:00.077Z"));
jest.spyOn(migrationsDir, 'resolveMigrationFileExtension').mockReturnValue('.ts');
const filename = await create("my_description");
expect(fs.copy.called).to.equal(true);
expect(fs.copy.getCall(0).args[0]).to.equal(
expect(fs.cp).toHaveBeenCalled();
expect(fs.cp.mock.calls[0][0]).toBe(
path.join(__dirname, "../../samples/commonjs/migration.js")
);
expect(fs.copy.getCall(0).args[1]).to.equal(
expect(fs.cp.mock.calls[0][1]).toBe(
path.join(process.cwd(), "migrations", "20160609080700-my_description.ts")
);
expect(filename).to.equal("20160609080700-my_description.ts");
clock.restore();
expect(filename).toBe("20160609080700-my_description.ts");
jest.useRealTimers();
});
it("should replace spaces in the description with underscores", async () => {
const clock = sinon.useFakeTimers(
new Date("2016-06-09T08:07:00.077Z").getTime()
);
jest.useFakeTimers();
jest.setSystemTime(new Date("2016-06-09T08:07:00.077Z"));
await create("this description contains spaces");
expect(fs.copy.called).to.equal(true);
expect(fs.copy.getCall(0).args[0]).to.equal(
expect(fs.cp).toHaveBeenCalled();
expect(fs.cp.mock.calls[0][0]).toBe(
path.join(__dirname, "../../samples/commonjs/migration.js")
);
expect(fs.copy.getCall(0).args[1]).to.equal(
expect(fs.cp.mock.calls[0][1]).toBe(
path.join(

@@ -129,32 +100,30 @@ process.cwd(),

);
clock.restore();
jest.useRealTimers();
});
it("should yield errors that occurred when copying the file", async () => {
fs.copy.returns(Promise.reject(new Error("Copy failed")));
try {
await create("my_description");
expect.fail("Error was not thrown");
} catch (err) {
expect(err.message).to.equal("Copy failed");
}
fs.cp.mockRejectedValue(new Error("Copy failed"));
await expect(create("my_description")).rejects.toThrow("Copy failed");
});
it("should use the sample migration file if it exists", async () => {
const clock = sinon.useFakeTimers(
new Date("2016-06-09T08:07:00.077Z").getTime()
);
migrationsDir.doesSampleMigrationExist.returns(true);
jest.useFakeTimers();
jest.setSystemTime(new Date("2016-06-09T08:07:00.077Z"));
jest.spyOn(migrationsDir, 'doesSampleMigrationExist').mockResolvedValue(true);
const filename = await create("my_description");
expect(migrationsDir.doesSampleMigrationExist.called).to.equal(true);
expect(fs.copy.called).to.equal(true);
expect(fs.copy.getCall(0).args[0]).to.equal(
path.join(process.cwd(), "migrations", "sample-migration.js")
expect(migrationsDir.doesSampleMigrationExist).toHaveBeenCalled();
expect(fs.cp).toHaveBeenCalled();
expect(fs.cp.mock.calls[0][0]).toBe(
path.join(process.cwd(), "migrations", "sample-migration.js")
);
expect(fs.copy.getCall(0).args[1]).to.equal(
path.join(process.cwd(), "migrations", "20160609080700-my_description.js")
expect(fs.cp.mock.calls[0][1]).toBe(
path.join(process.cwd(), "migrations", "20160609080700-my_description.js")
);
expect(filename).to.equal("20160609080700-my_description.js");
clock.restore();
expect(filename).toBe("20160609080700-my_description.js");
jest.useRealTimers();
});
});

@@ -1,12 +0,9 @@

const { expect } = require("chai");
const sinon = require("sinon");
jest.mock("../../lib/actions/status");
const proxyquire = require("proxyquire");
const migrationsDir = require("../../lib/env/migrationsDir");
const config = require("../../lib/env/config");
const status = require("../../lib/actions/status");
const down = require("../../lib/actions/down");
describe("down", () => {
let down;
let status;
let config;
let lock;
let migrationsDir;
let db;

@@ -18,45 +15,17 @@ let client;

function mockStatus() {
return sinon.stub().returns(
Promise.resolve([
{
fileName: "20160609113224-first_migration.js",
appliedAt: new Date(),
},
{
fileName: "20160609113224-second_migration.js",
appliedAt: new Date(),
migrationBlock: 1
},
{
fileName: "20160609113225-last_migration.js",
appliedAt: new Date(),
migrationBlock: 1
}
])
);
function mockMigration() {
const theMigration = {
down: jest.fn().mockResolvedValue()
};
return theMigration;
}
function mockConfig() {
return {
shouldExist: sinon.stub().returns(Promise.resolve()),
read: sinon.stub().returns({
changelogCollectionName: "changelog",
lockCollectionName: "changelog_lock",
lockTtl: 10
function mockDb() {
const mock = {
collection: jest.fn((name) => {
if (name === "changelog") return changelogCollection;
if (name === "changelog_lock") return changelogLockCollection;
return null;
})
};
}
function mockMigrationsDir() {
return {
loadMigration: sinon.stub().returns(Promise.resolve(migration))
};
}
function mockDb() {
const mock = {};
mock.collection = sinon.stub();
mock.collection.withArgs("changelog").returns(changelogCollection);
mock.collection.withArgs("changelog_lock").returns(changelogLockCollection);
return mock;

@@ -69,13 +38,5 @@ }

function mockMigration() {
const theMigration = {
down: sinon.stub()
};
theMigration.down.returns(Promise.resolve());
return theMigration;
}
function mockChangelogCollection() {
return {
deleteOne: sinon.stub().returns(Promise.resolve())
deleteOne: jest.fn().mockResolvedValue()
};

@@ -86,43 +47,48 @@ }

const findStub = {
toArray: () => {
return [];
}
}
toArray: jest.fn().mockResolvedValue([])
};
return {
insertOne: sinon.stub().returns(Promise.resolve()),
createIndex: sinon.stub().returns(Promise.resolve()),
find: sinon.stub().returns(findStub),
deleteMany: sinon.stub().returns(Promise.resolve()),
}
insertOne: jest.fn().mockResolvedValue(),
createIndex: jest.fn().mockResolvedValue(),
find: jest.fn().mockReturnValue(findStub),
deleteMany: jest.fn().mockResolvedValue(),
};
}
function loadDownWithInjectedMocks() {
return proxyquire("../../lib/actions/down", {
"./status": status,
"../env/config": config,
"../env/migrationsDir": migrationsDir,
"../utils/lock": lock,
});
}
beforeEach(() => {
jest.clearAllMocks();
jest.restoreAllMocks();
function loadLockWithInjectedMocks() {
return proxyquire("../../lib/utils/lock", {
"../env/config": config
});
}
beforeEach(() => {
migration = mockMigration();
changelogCollection = mockChangelogCollection();
changelogLockCollection = mockChangelogLockCollection();
status = mockStatus();
config = mockConfig();
migrationsDir = mockMigrationsDir();
db = mockDb();
client = mockClient();
lock = loadLockWithInjectedMocks();
down = loadDownWithInjectedMocks();
status.mockResolvedValue([
{
fileName: "20160609113224-first_migration.js",
appliedAt: new Date(),
},
{
fileName: "20160609113224-second_migration.js",
appliedAt: new Date(),
migrationBlock: 1
},
{
fileName: "20160609113225-last_migration.js",
appliedAt: new Date(),
migrationBlock: 1
}
]);
jest.spyOn(config, 'shouldExist').mockResolvedValue();
jest.spyOn(config, 'read').mockReturnValue({
changelogCollectionName: "changelog",
lockCollectionName: "changelog_lock",
lockTtl: 10
});
jest.spyOn(migrationsDir, 'loadMigration').mockResolvedValue(migration);
});

@@ -132,13 +98,11 @@

await down(db);
expect(status.called).to.equal(true);
expect(status).toHaveBeenCalled();
});
it("should yield empty list when nothing to downgrade", async () => {
status.returns(
Promise.resolve([
{ fileName: "20160609113224-some_migration.js", appliedAt: "PENDING" }
])
);
status.mockResolvedValue([
{ fileName: "20160609113224-some_migration.js", appliedAt: "PENDING" }
]);
const migrated = await down(db);
expect(migrated).to.deep.equal([]);
expect(migrated).toEqual([]);
});

@@ -148,3 +112,3 @@

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

@@ -156,23 +120,16 @@ );

await down(db);
expect(migration.down.called).to.equal(true);
expect(migration.down).toHaveBeenCalled();
});
/* eslint no-unused-vars: "off" */
it("should allow downgrade to return promise", async () => {
migrationsDir = mockMigrationsDir();
down = loadDownWithInjectedMocks();
await down(db);
expect(migration.down.called).to.equal(true);
expect(migration.down).toHaveBeenCalled();
});
it("should yield an error when an error occurred during the downgrade", async () => {
migration.down.returns(Promise.reject(new Error("Invalid syntax")));
try {
await down(db);
expect.fail("Error was not thrown");
} catch (err) {
expect(err.message).to.equal(
"Could not migrate down 20160609113225-last_migration.js: Invalid syntax"
);
}
migration.down.mockRejectedValue(new Error("Invalid syntax"));
await expect(down(db)).rejects.toThrow(
"Could not migrate down 20160609113225-last_migration.js: Invalid syntax"
);
});

@@ -182,16 +139,13 @@

await down(db);
expect(changelogCollection.deleteOne.called).to.equal(true);
expect(changelogCollection.deleteOne.callCount).to.equal(1);
expect(changelogCollection.deleteOne).toHaveBeenCalled();
expect(changelogCollection.deleteOne).toHaveBeenCalledTimes(1);
});
it("should yield errors that occurred when deleting from the changelog collection", async () => {
changelogCollection.deleteOne.returns(
Promise.reject(new Error("Could not delete"))
);
changelogCollection.deleteOne.mockRejectedValue(new Error("Could not delete"));
try {
await down(db);
} catch (err) {
expect(err.message).to.equal(
"Could not update changelog: Could not delete"
);
expect(err.message).toBe("Could not update changelog: Could not delete");
}

@@ -202,3 +156,3 @@ });

const items = await down(db);
expect(items).to.deep.equal(["20160609113225-last_migration.js"]);
expect(items).toEqual(["20160609113225-last_migration.js"]);
});

@@ -209,3 +163,3 @@

const items = await down(db);
expect(items).to.deep.equal(["20160609113225-last_migration.js", "20160609113224-second_migration.js"]);
expect(items).toEqual(["20160609113225-last_migration.js", "20160609113224-second_migration.js"]);
});

@@ -215,10 +169,10 @@

await down(db);
expect(changelogLockCollection.createIndex.called).to.equal(true);
expect(changelogLockCollection.find.called).to.equal(true);
expect(changelogLockCollection.insertOne.called).to.equal(true);
expect(changelogLockCollection.deleteMany.called).to.equal(true);
expect(changelogLockCollection.createIndex).toHaveBeenCalled();
expect(changelogLockCollection.find).toHaveBeenCalled();
expect(changelogLockCollection.insertOne).toHaveBeenCalled();
expect(changelogLockCollection.deleteMany).toHaveBeenCalled();
});
it("should ignore lock if feature is disabled", async() => {
config.read = sinon.stub().returns({
jest.spyOn(config, 'read').mockReturnValue({
changelogCollectionName: "changelog",

@@ -228,44 +182,24 @@ lockCollectionName: "changelog_lock",

});
const findStub = {
toArray: () => {
return [{ createdAt: new Date() }];
}
}
changelogLockCollection.find.returns(findStub);
changelogLockCollection.find.mockReturnValue({
toArray: jest.fn().mockResolvedValue([{ createdAt: new Date() }])
});
await down(db);
expect(changelogLockCollection.createIndex.called).to.equal(false);
expect(changelogLockCollection.find.called).to.equal(false);
expect(changelogLockCollection.createIndex).not.toHaveBeenCalled();
expect(changelogLockCollection.find).not.toHaveBeenCalled();
});
it("should yield an error when unable to create a lock", async() => {
changelogLockCollection.insertOne.returns(Promise.reject(new Error("Kernel panic")));
changelogLockCollection.insertOne.mockRejectedValue(new Error("Kernel panic"));
try {
await down(db);
expect.fail("Error was not thrown");
} catch (err) {
expect(err.message).to.deep.equal(
"Could not create a lock: Kernel panic"
);
}
await expect(down(db)).rejects.toThrow("Could not create a lock: Kernel panic");
});
it("should yield an error when changelog is locked", async() => {
const findStub = {
toArray: () => {
return [{ createdAt: new Date() }];
}
}
changelogLockCollection.find.returns(findStub);
changelogLockCollection.find.mockReturnValue({
toArray: jest.fn().mockResolvedValue([{ createdAt: new Date() }])
});
try {
await down(db);
expect.fail("Error was not thrown");
} catch (err) {
expect(err.message).to.deep.equal(
"Could not migrate down, a lock is in place."
);
}
await expect(down(db)).rejects.toThrow("Could not migrate down, a lock is in place.");
});
});

@@ -1,41 +0,18 @@

const { expect } = require("chai");
const sinon = require("sinon");
jest.mock("fs/promises");
const path = require("path");
const proxyquire = require("proxyquire");
const fs = require("fs/promises");
const migrationsDir = require("../../lib/env/migrationsDir");
const config = require("../../lib/env/config");
const init = require("../../lib/actions/init");
describe("init", () => {
let init;
let migrationsDir;
let config;
let fs;
function mockMigrationsDir() {
return {
shouldNotExist: sinon.stub().returns(Promise.resolve())
};
}
function mockConfig() {
return {
shouldNotExist: sinon.stub().returns(Promise.resolve())
};
}
function mockFs() {
return {
copy: sinon.stub().returns(Promise.resolve()),
mkdirs: sinon.stub().returns(Promise.resolve())
};
}
beforeEach(() => {
jest.clearAllMocks();
jest.restoreAllMocks();
global.options = { module: 'commonjs' };
migrationsDir = mockMigrationsDir();
config = mockConfig();
fs = mockFs();
init = proxyquire("../../lib/actions/init", {
"../env/migrationsDir": migrationsDir,
"../env/config": config,
"fs-extra": fs
});
jest.spyOn(migrationsDir, 'shouldNotExist').mockResolvedValue();
jest.spyOn(config, 'shouldNotExist').mockResolvedValue();
fs.cp.mockResolvedValue();
fs.mkdir.mockResolvedValue();
});

@@ -45,15 +22,14 @@

await init();
expect(migrationsDir.shouldNotExist.called).to.equal(true);
expect(migrationsDir.shouldNotExist).toHaveBeenCalled();
});
it("should not continue and yield an error if the migrations directory already exists", async () => {
migrationsDir.shouldNotExist.returns(
Promise.reject(new Error("Dir exists"))
);
jest.spyOn(migrationsDir, 'shouldNotExist').mockRejectedValue(new Error("Dir exists"));
try {
await init();
} catch (err) {
expect(err.message).to.equal("Dir exists");
expect(fs.copy.called).to.equal(false);
expect(fs.mkdirs.called).to.equal(false);
expect(err.message).toBe("Dir exists");
expect(fs.cp).not.toHaveBeenCalled();
expect(fs.mkdir).not.toHaveBeenCalled();
}

@@ -64,15 +40,14 @@ });

await init();
expect(config.shouldNotExist.called).to.equal(true);
expect(config.shouldNotExist).toHaveBeenCalled();
});
it("should not continue and yield an error if the config file already exists", async () => {
config.shouldNotExist.returns(
Promise.resolve(new Error("Config exists"))
);
jest.spyOn(config, 'shouldNotExist').mockResolvedValue(new Error("Config exists"));
try {
await init();
} catch (err) {
expect(err.message).to.equal("Config exists");
expect(fs.copy.called).to.equal(false);
expect(fs.mkdirs.called).to.equal(false);
expect(err.message).toBe("Config exists");
expect(fs.cp).not.toHaveBeenCalled();
expect(fs.mkdir).not.toHaveBeenCalled();
}

@@ -83,12 +58,12 @@ });

await init();
expect(fs.copy.called).to.equal(true);
expect(fs.copy.callCount).to.equal(1);
expect(fs.cp).toHaveBeenCalled();
expect(fs.cp).toHaveBeenCalledTimes(1);
const source = fs.copy.getCall(0).args[0];
expect(source).to.equal(
const source = fs.cp.mock.calls[0][0];
expect(source).toBe(
path.join(__dirname, "../../samples/commonjs/migrate-mongo-config.js")
);
const destination = fs.copy.getCall(0).args[1];
expect(destination).to.equal(
const destination = fs.cp.mock.calls[0][1];
expect(destination).toBe(
path.join(process.cwd(), "migrate-mongo-config.js")

@@ -101,12 +76,12 @@ );

await init();
expect(fs.copy.called).to.equal(true);
expect(fs.copy.callCount).to.equal(1);
expect(fs.cp).toHaveBeenCalled();
expect(fs.cp).toHaveBeenCalledTimes(1);
const source = fs.copy.getCall(0).args[0];
expect(source).to.equal(
const source = fs.cp.mock.calls[0][0];
expect(source).toBe(
path.join(__dirname, "../../samples/esm/migrate-mongo-config.js")
);
const destination = fs.copy.getCall(0).args[1];
expect(destination).to.equal(
const destination = fs.cp.mock.calls[0][1];
expect(destination).toBe(
path.join(process.cwd(), "migrate-mongo-config.js")

@@ -116,11 +91,5 @@ );

it("should yield errors that occurred when copying the sample config", async () => {
fs.copy.returns(Promise.reject(new Error("No space left on device")));
try {
await init();
expect.fail("Error was not thrown");
} catch (err) {
expect(err.message).to.equal("No space left on device");
}
fs.cp.mockRejectedValue(new Error("No space left on device"));
await expect(init()).rejects.toThrow("No space left on device");
});

@@ -131,5 +100,5 @@

expect(fs.mkdirs.called).to.equal(true);
expect(fs.mkdirs.callCount).to.equal(1);
expect(fs.mkdirs.getCall(0).args[0]).to.deep.equal(
expect(fs.mkdir).toHaveBeenCalled();
expect(fs.mkdir).toHaveBeenCalledTimes(1);
expect(fs.mkdir.mock.calls[0][0]).toEqual(
path.join(process.cwd(), "migrations")

@@ -140,9 +109,10 @@ );

it("should yield errors that occurred when creating the migrations directory", async () => {
fs.mkdirs.returns(Promise.reject(new Error("I cannot do that")));
fs.mkdir.mockRejectedValue(new Error("I cannot do that"));
try {
await init();
} catch (err) {
expect(err.message).to.equal("I cannot do that");
expect(err.message).toBe("I cannot do that");
}
});
});

@@ -1,63 +0,30 @@

const { expect } = require("chai");
const sinon = require("sinon");
jest.mock("fs/promises");
const proxyquire = require("proxyquire");
const migrationsDir = require("../../lib/env/migrationsDir");
const config = require("../../lib/env/config");
const status = require("../../lib/actions/status");
describe("status", () => {
let status;
let migrationsDir;
let config;
let fs;
let db;
let changelogCollection;
function mockMigrationsDir() {
return {
shouldExist: sinon.stub().returns(Promise.resolve()),
getFileNames: sinon
.stub()
.returns(
Promise.resolve([
"20160509113224-first_migration.js",
"20160512091701-second_migration.js",
"20160513155321-third_migration.js"
])
),
loadFileHash: sinon
.stub()
.callsFake((fileName) => {
switch (fileName) {
case "20160509113224-first_migration.js":
return Promise.resolve("0f295f21f63c66dc78d8dc091ce3c8bab8c56d8b74fb35a0c99f6d9953e37d1a");
case "20160512091701-second_migration.js":
return Promise.resolve("18b4d9c95a8678ae3a6dd3ae5b8961737a6c3dd65e3e655a5f5718d97a0bff70");
case "20160513155321-third_migration.js":
return Promise.resolve("1f9eb3b5eb70b2fb5b83fa0c660d859082f0bb615e835d29943d26fb0d352022");
default:
return Promise.resolve();
}
}),
};
}
const defaultLoadFileHashImpl = (fileName) => {
switch (fileName) {
case "20160509113224-first_migration.js":
return Promise.resolve("0f295f21f63c66dc78d8dc091ce3c8bab8c56d8b74fb35a0c99f6d9953e37d1a");
case "20160512091701-second_migration.js":
return Promise.resolve("18b4d9c95a8678ae3a6dd3ae5b8961737a6c3dd65e3e655a5f5718d97a0bff70");
case "20160513155321-third_migration.js":
return Promise.resolve("1f9eb3b5eb70b2fb5b83fa0c660d859082f0bb615e835d29943d26fb0d352022");
default:
return Promise.resolve();
}
};
function mockConfig() {
return {
shouldExist: sinon.stub().returns(Promise.resolve()),
read: sinon.stub().returns({
changelogCollectionName: "changelog"
})
};
}
function mockFs() {
return {
copy: sinon.stub().returns(Promise.resolve()),
readFile: sinon.stub().returns(Promise.resolve("some file content"))
};
}
function mockDb() {
const mock = {};
mock.collection = sinon.stub();
mock.collection.withArgs("changelog").returns(changelogCollection);
mock.collection = jest.fn((name) => {
if (name === "changelog") return changelogCollection;
return null;
});
return mock;

@@ -68,34 +35,7 @@ }

return {
deleteOne: sinon.stub().returns(Promise.resolve()),
find: sinon.stub().returns({
toArray: sinon.stub().returns(
Promise.resolve([
{
fileName: "20160509113224-first_migration.js",
appliedAt: new Date("2016-06-03T20:10:12.123Z")
},
{
fileName: "20160512091701-second_migration.js",
appliedAt: new Date("2016-06-09T20:10:12.123Z")
}
])
)
})
};
}
function enabledFileHash(configContent) {
configContent.read.returns({
changelogCollectionName: "changelog",
useFileHash: true
})
}
function addHashToChangeLog(changelog) {
changelog.find.returns({
toArray: sinon.stub().returns(
Promise.resolve([
deleteOne: jest.fn().mockResolvedValue(),
find: jest.fn().mockReturnValue({
toArray: jest.fn().mockResolvedValue([
{
fileName: "20160509113224-first_migration.js",
fileHash: "0f295f21f63c66dc78d8dc091ce3c8bab8c56d8b74fb35a0c99f6d9953e37d1a",
appliedAt: new Date("2016-06-03T20:10:12.123Z")

@@ -105,21 +45,51 @@ },

fileName: "20160512091701-second_migration.js",
fileHash: "18b4d9c95a8678ae3a6dd3ae5b8961737a6c3dd65e3e655a5f5718d97a0bff70",
appliedAt: new Date("2016-06-09T20:10:12.123Z")
}
])
)
})
})
};
}
function enabledFileHash() {
jest.spyOn(config, 'read').mockReturnValue({
changelogCollectionName: "changelog",
useFileHash: true
});
}
function addHashToChangeLog() {
changelogCollection.find.mockReturnValue({
toArray: jest.fn().mockResolvedValue([
{
fileName: "20160509113224-first_migration.js",
fileHash: "0f295f21f63c66dc78d8dc091ce3c8bab8c56d8b74fb35a0c99f6d9953e37d1a",
appliedAt: new Date("2016-06-03T20:10:12.123Z")
},
{
fileName: "20160512091701-second_migration.js",
fileHash: "18b4d9c95a8678ae3a6dd3ae5b8961737a6c3dd65e3e655a5f5718d97a0bff70",
appliedAt: new Date("2016-06-09T20:10:12.123Z")
}
])
});
}
beforeEach(() => {
jest.clearAllMocks();
jest.restoreAllMocks();
changelogCollection = mockChangelogCollection();
db = mockDb();
migrationsDir = mockMigrationsDir();
config = mockConfig();
fs = mockFs();
db = mockDb();
status = proxyquire("../../lib/actions/status", {
"../env/migrationsDir": migrationsDir,
"../env/config": config,
"fs-extra": fs
jest.spyOn(migrationsDir, 'shouldExist').mockResolvedValue();
jest.spyOn(migrationsDir, 'getFileNames').mockResolvedValue([
"20160509113224-first_migration.js",
"20160512091701-second_migration.js",
"20160513155321-third_migration.js"
]);
jest.spyOn(migrationsDir, 'loadFileHash').mockImplementation(defaultLoadFileHashImpl);
jest.spyOn(config, 'shouldExist').mockResolvedValue();
jest.spyOn(config, 'read').mockReturnValue({
changelogCollectionName: "changelog"
});

@@ -130,15 +100,10 @@ });

await status(db);
expect(migrationsDir.shouldExist.called).to.equal(true);
expect(migrationsDir.shouldExist).toHaveBeenCalled();
});
it("should yield an error when the migrations directory does not exist", async () => {
migrationsDir.shouldExist.returns(
Promise.reject(new Error("migrations directory does not exist"))
jest.spyOn(migrationsDir, 'shouldExist').mockRejectedValue(
new Error("migrations directory does not exist")
);
try {
await status(db);
expect.fail("Error was not thrown");
} catch (err) {
expect(err.message).to.equal("migrations directory does not exist");
}
await expect(status(db)).rejects.toThrow("migrations directory does not exist");
});

@@ -148,15 +113,10 @@

await status(db);
expect(config.shouldExist.called).to.equal(true);
expect(config.shouldExist).toHaveBeenCalled();
});
it("should yield an error when config file does not exist", async () => {
config.shouldExist.returns(
Promise.reject(new Error("config file does not exist"))
jest.spyOn(config, 'shouldExist').mockRejectedValue(
new Error("config file does not exist")
);
try {
await status(db);
expect.fail("Error was not thrown");
} catch (err) {
expect(err.message).to.equal("config file does not exist");
}
await expect(status(db)).rejects.toThrow("config file does not exist");
});

@@ -166,15 +126,10 @@

await status(db);
expect(migrationsDir.getFileNames.called).to.equal(true);
expect(migrationsDir.getFileNames).toHaveBeenCalled();
});
it("should yield errors that occurred when getting the list of files in the migrations directory", async () => {
migrationsDir.getFileNames.returns(
Promise.reject(new Error("File system unavailable"))
jest.spyOn(migrationsDir, 'getFileNames').mockRejectedValue(
new Error("File system unavailable")
);
try {
await status(db);
expect.fail("Error was not thrown");
} catch (err) {
expect(err.message).to.equal("File system unavailable");
}
await expect(status(db)).rejects.toThrow("File system unavailable");
});

@@ -184,18 +139,11 @@

await status(db);
expect(changelogCollection.find.called).to.equal(true);
expect(changelogCollection.find({}).toArray.called).to.equal(true);
expect(changelogCollection.find).toHaveBeenCalled();
expect(changelogCollection.find({}).toArray).toHaveBeenCalled();
});
it("should yield errors that occurred when fetching the changelog collection", async () => {
changelogCollection
.find({})
.toArray.returns(
Promise.reject(new Error("Cannot read from the database"))
);
try {
await status(db);
expect.fail("Error was not thrown");
} catch (err) {
expect(err.message).to.equal("Cannot read from the database");
}
changelogCollection.find.mockReturnValue({
toArray: jest.fn().mockRejectedValue(new Error("Cannot read from the database"))
});
await expect(status(db)).rejects.toThrow("Cannot read from the database");
});

@@ -205,3 +153,3 @@

const statusItems = await status(db);
expect(statusItems).to.deep.equal([
expect(statusItems).toEqual([
{

@@ -226,5 +174,5 @@ appliedAt: "2016-06-03T20:10:12.123Z",

it("it should mark all scripts as pending when enabling for the first time", async () => {
enabledFileHash(config);
enabledFileHash();
const statusItems = await status(db);
expect(statusItems).to.deep.equal([
expect(statusItems).toEqual([
{

@@ -252,6 +200,6 @@ appliedAt: "PENDING",

it("it should mark new scripts as pending with a file hash", async () => {
enabledFileHash(config);
addHashToChangeLog(changelogCollection);
enabledFileHash();
addHashToChangeLog();
const statusItems = await status(db);
expect(statusItems).to.deep.equal([
expect(statusItems).toEqual([
{

@@ -279,5 +227,5 @@ appliedAt: "2016-06-03T20:10:12.123Z",

it("it should mark changed scripts with pending", async () => {
enabledFileHash(config);
addHashToChangeLog(changelogCollection);
migrationsDir.loadFileHash.callsFake((fileName) => {
enabledFileHash();
addHashToChangeLog();
jest.spyOn(migrationsDir, 'loadFileHash').mockImplementation((fileName) => {
switch (fileName) {

@@ -289,10 +237,10 @@ case "20160509113224-first_migration.js":

case "20160513155321-third_migration.js":
return Promise.resolve("1f9eb3b5eb70b2fb5b83fa0c660d859082f0bb615e835d29943d26fb0d352022");
return Promise.resolve("1f9eb3b5eb70b2fb5b83fa0c660d859082f0bb615e835d29943d26fb0d352022");
default:
return Promise.resolve();
}
})
});
const statusItems = await status(db);
expect(statusItems).to.deep.equal([
expect(statusItems).toEqual([
{

@@ -299,0 +247,0 @@ appliedAt: "2016-06-03T20:10:12.123Z",

@@ -1,15 +0,11 @@

const { expect } = require("chai");
const sinon = require("sinon");
jest.mock("../../lib/actions/status");
const proxyquire = require("proxyquire");
const migrationsDir = require("../../lib/env/migrationsDir");
const config = require("../../lib/env/config");
const status = require("../../lib/actions/status");
const up = require("../../lib/actions/up");
describe("up", () => {
let up;
let status;
let config;
let lock;
let migrationsDir;
let db;
let client;
let firstPendingMigration;

@@ -20,56 +16,20 @@ let secondPendingMigration;

function mockStatus() {
return sinon.stub().returns(
Promise.resolve([
{
fileName: "20160605123224-first_applied_migration.js",
appliedAt: new Date()
},
{
fileName: "20160606093207-second_applied_migration.js",
appliedAt: new Date()
},
{
fileName: "20160607173840-first_pending_migration.js",
appliedAt: "PENDING"
},
{
fileName: "20160608060209-second_pending_migration.js",
appliedAt: "PENDING"
}
])
);
function mockMigration() {
const migration = {
up: jest.fn().mockResolvedValue()
};
return migration;
}
function mockConfig() {
return {
shouldExist: sinon.stub().returns(Promise.resolve()),
read: sinon.stub().returns({
changelogCollectionName: "changelog",
lockCollectionName: "changelog_lock",
lockTtl: 10
function mockDb() {
const mock = {
collection: jest.fn((name) => {
if (name === "changelog") return changelogCollection;
if (name === "changelog_lock") return changelogLockCollection;
return null;
})
};
}
function mockMigrationsDir() {
const mock = {};
mock.loadMigration = sinon.stub();
mock.loadMigration
.withArgs("20160607173840-first_pending_migration.js")
.returns(Promise.resolve(firstPendingMigration));
mock.loadMigration
.withArgs("20160608060209-second_pending_migration.js")
.returns(Promise.resolve(secondPendingMigration));
return mock;
}
function mockDb() {
const mock = {};
mock.collection = sinon.stub();
mock.collection.withArgs("changelog").returns(changelogCollection);
mock.collection.withArgs("changelog_lock").returns(changelogLockCollection);
return mock;
}
function mockClient() {

@@ -79,13 +39,5 @@ return { the: 'client' };

function mockMigration() {
const migration = {
up: sinon.stub()
};
migration.up.returns(Promise.resolve());
return migration;
}
function mockChangelogCollection() {
return {
insertOne: sinon.stub().returns(Promise.resolve())
insertOne: jest.fn().mockResolvedValue()
};

@@ -96,31 +48,17 @@ }

const findStub = {
toArray: () => {
return [];
}
}
toArray: jest.fn().mockResolvedValue([])
};
return {
insertOne: sinon.stub().returns(Promise.resolve()),
createIndex: sinon.stub().returns(Promise.resolve()),
find: sinon.stub().returns(findStub),
deleteMany: sinon.stub().returns(Promise.resolve()),
}
insertOne: jest.fn().mockResolvedValue(),
createIndex: jest.fn().mockResolvedValue(),
find: jest.fn().mockReturnValue(findStub),
deleteMany: jest.fn().mockResolvedValue(),
};
}
function loadUpWithInjectedMocks() {
return proxyquire("../../lib/actions/up", {
"./status": status,
"../env/config": config,
"../env/migrationsDir": migrationsDir,
"../utils/lock": lock
});
}
beforeEach(() => {
jest.clearAllMocks();
jest.restoreAllMocks();
function loadLockWithInjectedMocks() {
return proxyquire("../../lib/utils/lock", {
"../env/config": config
});
}
beforeEach(() => {
firstPendingMigration = mockMigration();

@@ -130,11 +68,41 @@ secondPendingMigration = mockMigration();

changelogLockCollection = mockChangelogLockCollection();
status = mockStatus();
config = mockConfig();
migrationsDir = mockMigrationsDir();
db = mockDb();
client = mockClient();
lock = loadLockWithInjectedMocks();
up = loadUpWithInjectedMocks();
status.mockResolvedValue([
{
fileName: "20160605123224-first_applied_migration.js",
appliedAt: new Date()
},
{
fileName: "20160606093207-second_applied_migration.js",
appliedAt: new Date()
},
{
fileName: "20160607173840-first_pending_migration.js",
appliedAt: "PENDING"
},
{
fileName: "20160608060209-second_pending_migration.js",
appliedAt: "PENDING"
}
]);
jest.spyOn(config, 'shouldExist').mockResolvedValue();
jest.spyOn(config, 'read').mockReturnValue({
changelogCollectionName: "changelog",
lockCollectionName: "changelog_lock",
lockTtl: 10
});
jest.spyOn(migrationsDir, 'loadMigration')
.mockImplementation((fileName) => {
if (fileName === "20160607173840-first_pending_migration.js") {
return Promise.resolve(firstPendingMigration);
}
if (fileName === "20160608060209-second_pending_migration.js") {
return Promise.resolve(secondPendingMigration);
}
return Promise.resolve(mockMigration());
});
});

@@ -144,3 +112,3 @@

await up(db);
expect(status.called).to.equal(true);
expect(status).toHaveBeenCalled();
});

@@ -150,8 +118,8 @@

await up(db);
expect(migrationsDir.loadMigration.called).to.equal(true);
expect(migrationsDir.loadMigration.callCount).to.equal(2);
expect(migrationsDir.loadMigration.getCall(0).args[0]).to.equal(
expect(migrationsDir.loadMigration).toHaveBeenCalled();
expect(migrationsDir.loadMigration).toHaveBeenCalledTimes(2);
expect(migrationsDir.loadMigration.mock.calls[0][0]).toBe(
"20160607173840-first_pending_migration.js"
);
expect(migrationsDir.loadMigration.getCall(1).args[0]).to.equal(
expect(migrationsDir.loadMigration.mock.calls[1][0]).toBe(
"20160608060209-second_pending_migration.js"

@@ -163,16 +131,19 @@ );

await up(db);
expect(firstPendingMigration.up.called).to.equal(true);
expect(secondPendingMigration.up.called).to.equal(true);
sinon.assert.callOrder(firstPendingMigration.up, secondPendingMigration.up);
expect(firstPendingMigration.up).toHaveBeenCalled();
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);
});
it("should populate the changelog with info about the upgraded migrations", async () => {
const clock = sinon.useFakeTimers(
new Date("2016-06-09T08:07:00.077Z").getTime()
);
jest.useFakeTimers();
jest.setSystemTime(new Date("2016-06-09T08:07:00.077Z"));
await up(db);
expect(changelogCollection.insertOne.called).to.equal(true);
expect(changelogCollection.insertOne.callCount).to.equal(2);
expect(changelogCollection.insertOne.getCall(0).args[0]).to.deep.equal({
expect(changelogCollection.insertOne).toHaveBeenCalled();
expect(changelogCollection.insertOne).toHaveBeenCalledTimes(2);
expect(changelogCollection.insertOne.mock.calls[0][0]).toEqual({
appliedAt: new Date("2016-06-09T08:07:00.077Z"),

@@ -182,7 +153,8 @@ fileName: "20160607173840-first_pending_migration.js",

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

@@ -193,17 +165,14 @@ lockCollectionName: "changelog_lock",

});
const findStub = {
toArray: () => {
return [{ createdAt: new Date() }];
}
}
changelogLockCollection.find.returns(findStub);
changelogLockCollection.find.mockReturnValue({
toArray: jest.fn().mockResolvedValue([{ createdAt: new Date() }])
});
const clock = sinon.useFakeTimers(
new Date("2016-06-09T08:07:00.077Z").getTime()
);
jest.useFakeTimers();
jest.setSystemTime(new Date("2016-06-09T08:07:00.077Z"));
await up(db);
expect(changelogCollection.insertOne.called).to.equal(true);
expect(changelogCollection.insertOne.callCount).to.equal(2);
expect(changelogCollection.insertOne.getCall(0).args[0]).to.deep.equal({
expect(changelogCollection.insertOne).toHaveBeenCalled();
expect(changelogCollection.insertOne).toHaveBeenCalledTimes(2);
expect(changelogCollection.insertOne.mock.calls[0][0]).toEqual({
appliedAt: new Date("2016-06-09T08:07:00.077Z"),

@@ -214,3 +183,4 @@ "fileHash": undefined,

});
clock.restore();
jest.useRealTimers();
});

@@ -220,3 +190,3 @@

const upgradedFileNames = await up(db);
expect(upgradedFileNames).to.deep.equal([
expect(upgradedFileNames).toEqual([
"20160607173840-first_pending_migration.js",

@@ -228,11 +198,7 @@ "20160608060209-second_pending_migration.js"

it("should stop migrating when an error occurred and yield the error", async () => {
secondPendingMigration.up.returns(Promise.reject(new Error("Nope")));
try {
await up(db);
expect.fail("Error was not thrown");
} catch (err) {
expect(err.message).to.deep.equal(
"Could not migrate up 20160608060209-second_pending_migration.js: Nope"
);
}
secondPendingMigration.up.mockRejectedValue(new Error("Nope"));
await expect(up(db)).rejects.toThrow(
"Could not migrate up 20160608060209-second_pending_migration.js: Nope"
);
});

@@ -249,11 +215,12 @@

};
secondPendingMigration.up.returns(Promise.reject(mongoError));
secondPendingMigration.up.mockRejectedValue(mongoError);
try {
await up(db);
expect.fail("Error was not thrown");
throw new Error("Error was not thrown");
} catch (err) {
expect(err.message).to.deep.equal(
expect(err.message).toBe(
"Could not migrate up 20160608060209-second_pending_migration.js: Document failed validation"
);
expect(err.additionalInfo).to.deep.equal(mongoError.errInfo);
expect(err.additionalInfo).toEqual(mongoError.errInfo);
}

@@ -264,12 +231,6 @@ });

changelogCollection.insertOne
.onSecondCall()
.returns(Promise.reject(new Error("Kernel panic")));
try {
await up(db);
expect.fail("Error was not thrown");
} catch (err) {
expect(err.message).to.deep.equal(
"Could not update changelog: Kernel panic"
);
}
.mockResolvedValueOnce()
.mockRejectedValueOnce(new Error("Kernel panic"));
await expect(up(db)).rejects.toThrow("Could not update changelog: Kernel panic");
});

@@ -279,10 +240,10 @@

await up(db);
expect(changelogLockCollection.createIndex.called).to.equal(true);
expect(changelogLockCollection.find.called).to.equal(true);
expect(changelogLockCollection.insertOne.called).to.equal(true);
expect(changelogLockCollection.deleteMany.called).to.equal(true);
expect(changelogLockCollection.createIndex).toHaveBeenCalled();
expect(changelogLockCollection.find).toHaveBeenCalled();
expect(changelogLockCollection.insertOne).toHaveBeenCalled();
expect(changelogLockCollection.deleteMany).toHaveBeenCalled();
});
it("should ignore lock if feature is disabled", async() => {
config.read = sinon.stub().returns({
jest.spyOn(config, 'read').mockReturnValue({
changelogCollectionName: "changelog",

@@ -292,46 +253,26 @@ lockCollectionName: "changelog_lock",

});
const findStub = {
toArray: () => {
return [{ createdAt: new Date() }];
}
}
changelogLockCollection.find.returns(findStub);
changelogLockCollection.find.mockReturnValue({
toArray: jest.fn().mockResolvedValue([{ createdAt: new Date() }])
});
await up(db);
expect(changelogLockCollection.createIndex.called).to.equal(false);
expect(changelogLockCollection.find.called).to.equal(false);
expect(changelogLockCollection.insertOne.called).to.equal(false);
expect(changelogLockCollection.deleteMany.called).to.equal(false);
expect(changelogLockCollection.createIndex).not.toHaveBeenCalled();
expect(changelogLockCollection.find).not.toHaveBeenCalled();
expect(changelogLockCollection.insertOne).not.toHaveBeenCalled();
expect(changelogLockCollection.deleteMany).not.toHaveBeenCalled();
});
it("should yield an error when unable to create a lock", async() => {
changelogLockCollection.insertOne.returns(Promise.reject(new Error("Kernel panic")));
changelogLockCollection.insertOne.mockRejectedValue(new Error("Kernel panic"));
try {
await up(db);
expect.fail("Error was not thrown");
} catch (err) {
expect(err.message).to.deep.equal(
"Could not create a lock: Kernel panic"
);
}
await expect(up(db)).rejects.toThrow("Could not create a lock: Kernel panic");
});
it("should yield an error when changelog is locked", async() => {
const findStub = {
toArray: () => {
return [{ createdAt: new Date() }];
}
}
changelogLockCollection.find.returns(findStub);
changelogLockCollection.find.mockReturnValue({
toArray: jest.fn().mockResolvedValue([{ createdAt: new Date() }])
});
try {
await up(db);
expect.fail("Error was not thrown");
} catch (err) {
expect(err.message).to.deep.equal(
"Could not migrate up, a lock is in place."
);
}
await expect(up(db)).rejects.toThrow("Could not migrate up, a lock is in place.");
});
});

@@ -1,31 +0,17 @@

const { expect } = require("chai");
const sinon = require("sinon");
const proxyquire = require("proxyquire");
jest.mock("fs/promises");
const path = require("path");
const fs = require("fs/promises");
const moduleLoader = require("../../lib/utils/module-loader");
// Don't auto-mock config, we'll use the real implementation
const config = require("../../lib/env/config");
describe("config", () => {
let config; // module under test
let fs; // mocked dependencies
let moduleLoader;
function mockFs() {
return {
stat: sinon.stub()
};
}
function mockModuleLoader() {
return {
import: sinon.stub(),
};
}
beforeEach(() => {
fs = mockFs();
moduleLoader = mockModuleLoader();
config = proxyquire("../../lib/env/config", {
"fs-extra": fs,
"../utils/module-loader": moduleLoader
});
jest.clearAllMocks();
// Reset config state between tests
delete global.options;
config.set(null);
// Restore any spies
jest.restoreAllMocks();
});

@@ -36,3 +22,3 @@

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

@@ -43,3 +29,3 @@ await config.shouldExist();

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

@@ -50,11 +36,6 @@ });

const configPath = path.join(process.cwd(), "migrate-mongo-config.js");
fs.stat.returns(Promise.reject(new Error("It does not exist")));
try {
await config.shouldExist();
expect.fail("Error was not thrown");
} catch (err) {
expect(err.message).to.equal(
`config file does not exist: ${configPath}`
);
}
fs.stat.mockRejectedValue(new Error("It does not exist"));
await expect(config.shouldExist()).rejects.toThrow(
`config file does not exist: ${configPath}`
);
});

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

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

@@ -75,3 +56,3 @@ await config.shouldNotExist();

error.code = "ENOENT";
fs.stat.returns(Promise.reject(error));
fs.stat.mockRejectedValue(error);
await config.shouldNotExist();

@@ -82,11 +63,6 @@ });

const configPath = path.join(process.cwd(), "migrate-mongo-config.js");
fs.stat.returns(Promise.resolve());
try {
await config.shouldNotExist();
expect.fail("Error was not thrown");
} catch (err) {
expect(err.message).to.equal(
`config file already exists: ${configPath}`
);
}
fs.stat.mockResolvedValue({});
await expect(config.shouldNotExist()).rejects.toThrow(
`config file already exists: ${configPath}`
);
});

@@ -97,5 +73,3 @@ });

it("should return the config file name", () => {
expect(config.getConfigFilename()).to.equal(
"migrate-mongo-config.js"
);
expect(config.getConfigFilename()).toBe("migrate-mongo-config.js");
});

@@ -110,3 +84,3 @@ });

const actual = await config.read();
expect(actual).to.deep.equal(expected);
expect(actual).toEqual(expected);
});

@@ -116,8 +90,3 @@

const configPath = path.join(process.cwd(), "migrate-mongo-config.js");
try {
await config.read();
expect.fail("Error was not thrown");
} catch (err) {
expect(err.message).to.have.string(`Cannot find module '${configPath}'`);
}
await expect(config.read()).rejects.toThrow(`Cannot find module '${configPath}'`);
});

@@ -127,8 +96,3 @@

global.options = { file: "/some/absolute/path/to/a-config-file.js" };
try {
await config.read();
expect.fail("Error was not thrown");
} catch (err) {
expect(err.message).to.have.string(`Cannot find module '${global.options.file}'`);
}
await expect(config.read()).rejects.toThrow(`Cannot find module '${global.options.file}'`);
});

@@ -139,8 +103,3 @@

const configPath = path.join(process.cwd(), global.options.file);
try {
await config.read();
expect.fail("Error was not thrown");
} catch (err) {
expect(err.message).to.have.string(`Cannot find module '${configPath}'`);
}
await expect(config.read()).rejects.toThrow(`Cannot find module '${configPath}'`);
});

@@ -151,6 +110,6 @@

error.code = 'ERR_REQUIRE_ESM';
moduleLoader.require = sinon.stub().throws(error);
moduleLoader.import.returns({});
jest.spyOn(moduleLoader, 'require').mockImplementation(() => { throw error; });
jest.spyOn(moduleLoader, 'import').mockResolvedValue({});
await config.read();
expect(moduleLoader.import.called).to.equal(true);
expect(moduleLoader.import).toHaveBeenCalled();
});

@@ -161,6 +120,6 @@

error.code = 'ERR_REQUIRE_ASYNC_MODULE';
moduleLoader.require = sinon.stub().throws(error);
moduleLoader.import.returns({});
jest.spyOn(moduleLoader, 'require').mockImplementation(() => { throw error; });
jest.spyOn(moduleLoader, 'import').mockResolvedValue({});
await config.read();
expect(moduleLoader.import.called).to.equal(true);
expect(moduleLoader.import).toHaveBeenCalled();
});

@@ -176,3 +135,3 @@

moduleLoader.require = sinon.stub().resolves({
jest.spyOn(moduleLoader, 'require').mockResolvedValue({
default: expectedConfig

@@ -182,3 +141,3 @@ });

const actual = await config.read();
expect(actual).to.deep.equal(expectedConfig);
expect(actual).toEqual(expectedConfig);
});

@@ -194,6 +153,6 @@

moduleLoader.require = sinon.stub().resolves(expectedConfig);
jest.spyOn(moduleLoader, 'require').mockResolvedValue(expectedConfig);
const actual = await config.read();
expect(actual).to.deep.equal(expectedConfig);
expect(actual).toEqual(expectedConfig);
});

@@ -208,7 +167,7 @@

moduleLoader.require = sinon.stub().resolves(originalConfig);
jest.spyOn(moduleLoader, 'require').mockResolvedValue(originalConfig);
global.options = { migrationsDir: customMigrationsDir };
const actual = await config.read();
expect(actual.migrationsDir).to.equal(customMigrationsDir);
expect(actual.migrationsDir).toBe(customMigrationsDir);

@@ -225,7 +184,7 @@ // Clean up

moduleLoader.require = sinon.stub().resolves(originalConfig);
jest.spyOn(moduleLoader, 'require').mockResolvedValue(originalConfig);
delete global.options;
const actual = await config.read();
expect(actual.migrationsDir).to.equal('./migrations');
expect(actual.migrationsDir).toBe('./migrations');
});

@@ -242,8 +201,8 @@

error.code = 'ERR_REQUIRE_ESM';
moduleLoader.require = sinon.stub().rejects(error);
moduleLoader.import = sinon.stub().resolves(originalConfig);
jest.spyOn(moduleLoader, 'require').mockImplementation(() => { throw error; });
jest.spyOn(moduleLoader, 'import').mockResolvedValue(originalConfig);
global.options = { migrationsDir: customMigrationsDir };
const actual = await config.read();
expect(actual.migrationsDir).to.equal(customMigrationsDir);
expect(actual.migrationsDir).toBe(customMigrationsDir);

@@ -250,0 +209,0 @@ // Clean up

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

const { expect } = require("chai");
const sinon = require("sinon");
const proxyquire = require("proxyquire");
jest.mock("mongodb");
jest.mock("fs/promises");
const config = require("../../lib/env/config");
const mongodb = require("mongodb");
const database = require("../../lib/env/database");
describe("database", () => {
let configObj;
let database;
let config;
let mongodb;
let client;

@@ -27,3 +27,3 @@

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

@@ -33,26 +33,9 @@ };

function mockConfig() {
return {
read: sinon.stub().returns(configObj)
};
}
function mockMongodb() {
return {
MongoClient: {
connect: sinon.stub().returns(Promise.resolve(client))
}
};
}
beforeEach(() => {
jest.clearAllMocks();
jest.restoreAllMocks();
configObj = createConfigObj();
client = mockClient();
config = mockConfig();
mongodb = mockMongodb();
database = proxyquire("../../lib/env/database", {
"./config": config,
mongodb
});
jest.spyOn(config, 'read').mockReturnValue(configObj);
mongodb.MongoClient.connect.mockResolvedValue(client);
});

@@ -63,18 +46,17 @@

const result = await database.connect();
expect(mongodb.MongoClient.connect.called).to.equal(true);
expect(mongodb.MongoClient.connect.getCall(0).args[0]).to.equal(
"mongodb://someserver:27017"
expect(mongodb.MongoClient.connect).toHaveBeenCalled();
expect(mongodb.MongoClient.connect).toHaveBeenCalledWith(
"mongodb://someserver:27017",
{
connectTimeoutMS: 3600000, // 1 hour
socketTimeoutMS: 3600000 // 1 hour
}
);
expect(mongodb.MongoClient.connect.getCall(0).args[1]).to.deep.equal({
connectTimeoutMS: 3600000, // 1 hour
socketTimeoutMS: 3600000 // 1 hour
});
expect(client.db.getCall(0).args[0]).to.equal("testDb");
expect(result.db).to.deep.equal({
expect(client.db).toHaveBeenCalledWith("testDb");
expect(result.db).toEqual({
the: "db",
close: "theCloseFnFromMongoClient"
});
expect(result.client).to.deep.equal(client);
expect(result.client).toEqual(client);
});

@@ -84,21 +66,10 @@

delete configObj.mongodb.url;
try {
await database.connect();
expect.fail("Error was not thrown");
} catch (err) {
expect(err.message).to.equal("No `url` defined in config file!");
}
await expect(database.connect()).rejects.toThrow("No `url` defined in config file!");
});
it("should yield an error when unable to connect", async () => {
mongodb.MongoClient.connect.returns(
Promise.reject(new Error("Unable to connect"))
);
try {
await database.connect();
} catch (err) {
expect(err.message).to.equal("Unable to connect");
}
mongodb.MongoClient.connect.mockRejectedValue(new Error("Unable to connect"));
await expect(database.connect()).rejects.toThrow("Unable to connect");
});
});
});

@@ -1,44 +0,16 @@

const { expect } = require("chai");
const sinon = require("sinon");
const proxyquire = require("proxyquire");
jest.mock("fs/promises");
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", () => {
let migrationsDir;
let fs;
let config;
let moduleLoader;
function mockFs() {
return {
stat: sinon.stub(),
readdir: sinon.stub(),
readFile: sinon.stub()
};
}
function mockConfig() {
return {
read: sinon.stub().returns({
migrationsDir: "migrations",
migrationFileExtension: ".js"
})
};
}
function mockModuleLoader() {
return {
import: sinon.stub(),
};
}
beforeEach(() => {
fs = mockFs();
config = mockConfig();
moduleLoader = mockModuleLoader();
migrationsDir = proxyquire("../../lib/env/migrationsDir", {
"fs-extra": fs,
"./config": config,
"../utils/module-loader": moduleLoader
jest.clearAllMocks();
jest.restoreAllMocks();
jest.spyOn(config, 'read').mockReturnValue({
migrationsDir: "migrations",
migrationFileExtension: ".js"
});

@@ -49,6 +21,6 @@ });

it("should use the configured relative migrations dir when a config file is available", async () => {
config.read.returns({
jest.spyOn(config, 'read').mockReturnValue({
migrationsDir: "custom-migrations-dir"
});
expect(await migrationsDir.resolve()).to.equal(
expect(await migrationsDir.resolve()).toBe(
path.join(process.cwd(), "custom-migrations-dir")

@@ -59,6 +31,6 @@ );

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

@@ -69,4 +41,4 @@ );

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

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

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

@@ -87,3 +59,3 @@ );

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

@@ -94,11 +66,6 @@ });

const migrationsPath = path.join(process.cwd(), "migrations");
fs.stat.returns(Promise.reject(new Error("It does not exist")));
try {
await migrationsDir.shouldExist();
expect.fail("Error was not thrown");
} catch (err) {
expect(err.message).to.equal(
`migrations directory does not exist: ${migrationsPath}`
);
}
fs.stat.mockRejectedValue(new Error("It does not exist"));
await expect(migrationsDir.shouldExist()).rejects.toThrow(
`migrations directory does not exist: ${migrationsPath}`
);
});

@@ -111,3 +78,3 @@ });

error.code = "ENOENT";
fs.stat.returns(Promise.reject(error));
fs.stat.mockRejectedValue(error);
await migrationsDir.shouldNotExist();

@@ -118,11 +85,6 @@ });

const migrationsPath = path.join(process.cwd(), "migrations");
fs.stat.returns(Promise.resolve());
try {
await migrationsDir.shouldNotExist();
expect.fail("Error was not thrown");
} catch (err) {
expect(err.message).to.equal(
`migrations directory already exists: ${migrationsPath}`
);
}
fs.stat.mockResolvedValue({});
await expect(migrationsDir.shouldNotExist()).rejects.toThrow(
`migrations directory already exists: ${migrationsPath}`
);
});

@@ -133,28 +95,23 @@ });

it("should read the directory and yield the result", async () => {
fs.readdir.returns(Promise.resolve(["file1.js", "file2.js"]));
fs.readdir.mockResolvedValue(["file1.js", "file2.js"]);
const files = await migrationsDir.getFileNames();
expect(files).to.deep.equal(["file1.js", "file2.js"]);
expect(files).toEqual(["file1.js", "file2.js"]);
});
it("should list only files with configured extension", async () => {
config.read.returns({
jest.spyOn(config, 'read').mockReturnValue({
migrationFileExtension: ".ts"
});
fs.readdir.returns(Promise.resolve(["file1.ts", "file2.ts", "file1.js", "file2.js", ".keep"]));
fs.readdir.mockResolvedValue(["file1.ts", "file2.ts", "file1.js", "file2.js", ".keep"]);
const files = await migrationsDir.getFileNames();
expect(files).to.deep.equal(["file1.ts", "file2.ts"]);
expect(files).toEqual(["file1.ts", "file2.ts"]);
});
it("should yield errors that occurred while reading the dir", async () => {
fs.readdir.returns(Promise.reject(new Error("Could not read")));
try {
await migrationsDir.getFileNames();
expect.fail("Error was not thrown");
} catch (err) {
expect(err.message).to.equal("Could not read");
}
fs.readdir.mockRejectedValue(new Error("Could not read"));
await expect(migrationsDir.getFileNames()).rejects.toThrow("Could not read");
});
it("should be sorted in alphabetical order", async () => {
fs.readdir.returns(Promise.resolve([
fs.readdir.mockResolvedValue([
"20201014172343-test.js",

@@ -164,5 +121,5 @@ "20201014172356-test3.js",

"20201014172345-test1.js"
]));
]);
const files = await migrationsDir.getFileNames();
expect(files).to.deep.equal([
expect(files).toEqual([
"20201014172343-test.js",

@@ -183,15 +140,11 @@ "20201014172345-test1.js",

);
try {
await migrationsDir.loadMigration("someFile.js");
expect.fail("Error was not thrown");
} catch (err) {
expect(err.message).to.have.string(`Cannot find module '${pathToMigration}'`);
}
await expect(migrationsDir.loadMigration("someFile.js")).rejects.toThrow(`Cannot find module '${pathToMigration}'`);
});
it("should use CommonJS default", async () => {
moduleLoader.require = sinon.stub().returns({ up: sinon.stub(), down: sinon.stub() });
jest.spyOn(moduleLoader, 'require').mockReturnValue({ up: jest.fn(), down: jest.fn() });
jest.spyOn(moduleLoader, 'import');
await migrationsDir.loadMigration("someFile.js");
expect(moduleLoader.require.called).to.equal(true);
expect(moduleLoader.import.called).to.equal(false);
expect(moduleLoader.require).toHaveBeenCalled();
expect(moduleLoader.import).not.toHaveBeenCalled();
});

@@ -202,6 +155,6 @@

error.code = 'ERR_REQUIRE_ESM';
moduleLoader.require = sinon.stub().throws(error);
moduleLoader.import = sinon.stub().returns({ default: () => sinon.stub() });
jest.spyOn(moduleLoader, 'require').mockImplementation(() => { throw error; });
jest.spyOn(moduleLoader, 'import').mockResolvedValue({ default: () => jest.fn() });
await migrationsDir.loadMigration("someFile.js");
expect(moduleLoader.import.called).to.equal(true);
expect(moduleLoader.import).toHaveBeenCalled();
});

@@ -212,6 +165,6 @@

error.code = 'ERR_REQUIRE_ESM';
moduleLoader.require = sinon.stub().throws(error);
moduleLoader.import = sinon.stub().returns({ up: sinon.stub(), down: sinon.stub() });
jest.spyOn(moduleLoader, 'require').mockImplementation(() => { throw error; });
jest.spyOn(moduleLoader, 'import').mockResolvedValue({ up: jest.fn(), down: jest.fn() });
await migrationsDir.loadMigration("someFile.js");
expect(moduleLoader.import.called).to.equal(true);
expect(moduleLoader.import).toHaveBeenCalled();
});

@@ -222,6 +175,6 @@

error.code = 'ERR_REQUIRE_ASYNC_MODULE';
moduleLoader.require = sinon.stub().throws(error);
moduleLoader.import = sinon.stub().returns({ up: sinon.stub(), down: sinon.stub() });
jest.spyOn(moduleLoader, 'require').mockImplementation(() => { throw error; });
jest.spyOn(moduleLoader, 'import').mockResolvedValue({ up: jest.fn(), down: jest.fn() });
await migrationsDir.loadMigration("someFile.js");
expect(moduleLoader.import.called).to.equal(true);
expect(moduleLoader.import).toHaveBeenCalled();
});

@@ -232,30 +185,25 @@ });

it("should provide the value if specified", async () => {
config.read.returns({
jest.spyOn(config, 'read').mockReturnValue({
migrationFileExtension: ".ts"
});
const ext = await migrationsDir.resolveMigrationFileExtension();
expect(ext).to.equal(".ts");
expect(ext).toBe(".ts");
});
it("should error if the extension does not start with dot", async () => {
config.read.returns({
jest.spyOn(config, 'read').mockReturnValue({
migrationFileExtension: "js"
});
try {
await migrationsDir.resolveMigrationFileExtension();
expect.fail("Error was not thrown");
} catch(err) {
expect(err.message).to.equal("migrationFileExtension must start with dot");
}
await expect(migrationsDir.resolveMigrationFileExtension()).rejects.toThrow("migrationFileExtension must start with dot");
});
it("should use the default if not specified", async() => {
config.read.returns({
jest.spyOn(config, 'read').mockReturnValue({
migrationFileExtension: undefined
});
const ext = await migrationsDir.resolveMigrationFileExtension();
expect(ext).to.equal(".js");
expect(ext).toBe(".js");
});
it("should use the default if config file not found", async() => {
config.read.throws();
jest.spyOn(config, 'read').mockImplementation(() => { throw new Error(); });
const ext = await migrationsDir.resolveMigrationFileExtension();
expect(ext).to.equal(".js");
expect(ext).toBe(".js");
});

@@ -266,11 +214,11 @@ });

it("should return true if sample migration exists", async () => {
fs.stat.returns(Promise.resolve());
fs.stat.mockResolvedValue({});
const result = await migrationsDir.doesSampleMigrationExist();
expect(result).to.equal(true);
expect(result).toBe(true);
});
it("should return false if sample migration doesn't exists", async () => {
fs.stat.returns(Promise.reject(new Error("It does not exist")));
fs.stat.mockRejectedValue(new Error("It does not exist"));
const result = await migrationsDir.doesSampleMigrationExist();
expect(result).to.equal(false);
expect(result).toBe(false);
});

@@ -281,7 +229,7 @@ });

it("should return a hash based on the file contents", async () => {
fs.readFile.returns(Promise.resolve("some string to hash"));
fs.readFile.mockResolvedValue("some string to hash");
const result = await migrationsDir.loadFileHash('somefile.js');
expect(result).to.equal("ea83a45637a9af470a994d2c9722273ef07d47aec0660a1d10afe6e9586801ac");
expect(result).toBe("ea83a45637a9af470a994d2c9722273ef07d47aec0660a1d10afe6e9586801ac");
})
})
});
language: node_js
node_js:
- "10"
- "12"
- "14"
script: "npm run-script test-coverage"
after_script: "cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js"