migrate-mongo
Advanced tools
| 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 |
@@ -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 @@ } |
+7
-12
| { | ||
| "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(); | ||
| }); | ||
| }); |
+88
-154
@@ -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"); | ||
| } | ||
| }); | ||
| }); |
+94
-146
@@ -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", |
+127
-186
@@ -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."); | ||
| }); | ||
| }); |
+45
-86
@@ -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" |
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
3
-25%3
-66.67%42
13.51%126164
-0.73%1983
-14.27%13
44.44%- Removed
- Removed
- Removed
- Removed
- Removed