diff --git a/.github/workflows/unit-testing.yml b/.github/workflows/unit-testing.yml index e029a47..c2cbb21 100644 --- a/.github/workflows/unit-testing.yml +++ b/.github/workflows/unit-testing.yml @@ -28,6 +28,8 @@ jobs: node-version: 18 - name: Install app dependencies run: npm ci + - name: Create .env file + run: echo -e "WATCHER_COLLECTION=1\nWATCHER_API_BASE=url\nWATCHER_AUTHORITY=auth\nWATCHER_CLIENT_ID=clientId\nWATCHER_CLIENT_SECRET=secret" > .env - name: Run tests run: npm test - name: Upload coverage to github diff --git a/lib/cargo.js b/lib/cargo.js index ef46260..a453540 100644 --- a/lib/cargo.js +++ b/lib/cargo.js @@ -4,6 +4,7 @@ import Queue from 'better-queue' import * as api from './api.js' import { serializeError } from 'serialize-error' import { TaskObject } from 'stig-manager-client-modules' +import { addToHistory } from './scan.js' const component = 'cargo' let batchId = 0 @@ -64,6 +65,7 @@ async function writer ( taskAsset ) { }) } + return true } catch (error) { const errorObj = { @@ -87,6 +89,7 @@ async function writer ( taskAsset ) { errorObj.error = serializeError(error) } logger.error(errorObj) + return false } } @@ -94,12 +97,15 @@ async function resultsHandler( parsedResults, cb ) { const component = 'batch' try { batchId++ + const isModeScan = options.mode === 'scan' logger.info({component: component, message: `batch started`, batchId: batchId, size: parsedResults.length}) const apiAssets = await api.getCollectionAssets(options.collectionId) const apiStigs = await api.getInstalledStigs() const tasks = new TaskObject ({ parsedResults, apiAssets, apiStigs, options:options }) + isModeScan && tasks.errors.length && addToHistory(tasks.errors.map(e => e.sourceRef)) for ( const taskAsset of tasks.taskAssets.values() ) { - await writer( taskAsset ) + const success = await writer( taskAsset ) + isModeScan && success && addToHistory(taskAsset.sourceRefs) } logger.info({component: component, message: 'batch ended', batchId: batchId}) cb() diff --git a/lib/parse.js b/lib/parse.js index 963e564..7ba1e93 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -6,6 +6,8 @@ import { cargoQueue } from './cargo.js' import { promises as fs } from 'fs' import he from 'he' import { reviewsFromCkl, reviewsFromScc, reviewsFromCklb } from 'stig-manager-client-modules' +import { addToHistory } from './scan.js' +import { options } from './args.js' const valueProcessor = function (tagName, tagValue, jPath, hasAttributes, isLeafNode) { he.decode(tagValue) @@ -91,6 +93,7 @@ async function parseFileAndEnqueue (file, cb) { } catch (e) { logger.warn({component: component, message: e.message, file: file}) + options.mode === 'scan' && addToHistory(file) cb(e, null) } } diff --git a/lib/scan.js b/lib/scan.js index 07a278c..bde0814 100644 --- a/lib/scan.js +++ b/lib/scan.js @@ -42,7 +42,6 @@ async function startScanner() { } // if the file is not in the history, add it to the in memory history set. else { - addToHistory(entry) parseQueue.push(entry) logger.info({component, message: `queued for parsing`, file: entry}) } diff --git a/package-lock.json b/package-lock.json index 49ad756..ef33048 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,8 +34,7 @@ "chai": "^5.0.3", "esbuild": "^0.20.0", "mocha": "^10.2.0", - "nodemon": "^3.0.3", - "sinon": "^17.0.1" + "nodemon": "^3.0.3" }, "engines": { "node": ">=14" @@ -510,50 +509,6 @@ "url": "https://github.com/sindresorhus/is?sponsor=1" } }, - "node_modules/@sinonjs/commons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", - "dev": true, - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "11.2.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-11.2.2.tgz", - "integrity": "sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^3.0.0" - } - }, - "node_modules/@sinonjs/samsam": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.0.tgz", - "integrity": "sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^2.0.0", - "lodash.get": "^4.4.2", - "type-detect": "^4.0.8" - } - }, - "node_modules/@sinonjs/samsam/node_modules/@sinonjs/commons": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", - "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", - "dev": true, - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/text-encoding": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", - "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", - "dev": true - }, "node_modules/@szmarczak/http-timer": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", @@ -1616,12 +1571,6 @@ "npm": ">=6" } }, - "node_modules/just-extend": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz", - "integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==", - "dev": true - }, "node_modules/jwa": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", @@ -1669,12 +1618,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lodash.get": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", - "dev": true - }, "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", @@ -1953,19 +1896,6 @@ "node": ">=6.x.x" } }, - "node_modules/nise": { - "version": "5.1.9", - "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.9.tgz", - "integrity": "sha512-qOnoujW4SV6e40dYxJOb3uvuoPHtmLzIk4TFo+j0jPJoC+5Z9xja5qH5JZobEPsa8+YYphMrOSwnrshEhG2qww==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^3.0.0", - "@sinonjs/fake-timers": "^11.2.2", - "@sinonjs/text-encoding": "^0.7.2", - "just-extend": "^6.2.0", - "path-to-regexp": "^6.2.1" - } - }, "node_modules/node-eta": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/node-eta/-/node-eta-0.9.0.tgz", @@ -2157,12 +2087,6 @@ "node": ">=8" } }, - "node_modules/path-to-regexp": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz", - "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==", - "dev": true - }, "node_modules/pathval": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", @@ -2439,33 +2363,6 @@ "node": ">=10" } }, - "node_modules/sinon": { - "version": "17.0.1", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-17.0.1.tgz", - "integrity": "sha512-wmwE19Lie0MLT+ZYNpDymasPHUKTaZHUH/pKEubRXIzySv9Atnlw+BUMGCzWgV7b7wO+Hw6f1TEOr0IUnmU8/g==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^3.0.0", - "@sinonjs/fake-timers": "^11.2.2", - "@sinonjs/samsam": "^8.0.0", - "diff": "^5.1.0", - "nise": "^5.1.5", - "supports-color": "^7.2.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/sinon" - } - }, - "node_modules/sinon/node_modules/diff": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", - "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, "node_modules/stack-trace": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", @@ -2635,15 +2532,6 @@ "node": ">= 14.0.0" } }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", diff --git a/package.json b/package.json index 69dbc81..5779069 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,6 @@ "chai": "^5.0.3", "esbuild": "^0.20.0", "mocha": "^10.2.0", - "nodemon": "^3.0.3", - "sinon": "^17.0.1" + "nodemon": "^3.0.3" } } diff --git a/test/scan/historyfile.test.js b/test/scan/historyfile.test.js index f551524..28278b2 100644 --- a/test/scan/historyfile.test.js +++ b/test/scan/historyfile.test.js @@ -1,17 +1,16 @@ import { expect } from 'chai' -import sinon from 'sinon' import fs from 'fs' -import { - startScanner, - initHistory, - addToHistory, - removeFromHistory -} from '../../lib/scan.js' +import { initHistory, addToHistory, removeFromHistory } from '../../lib/scan.js' import { logger } from '../../lib/logger.js' import path from 'path' import { options } from '../../lib/args.js' -function setOptions(o) { +const emptyFn = () => undefined +for (const method of ['info', 'warn', 'error', 'verbose', 'debug', 'silly', 'http']) { + logger[method] = emptyFn +} + +function setOptions (o) { for (const [key, value] of Object.entries(o)) { options[key] = value } @@ -21,16 +20,12 @@ describe('testing add/remove/init functions ', function () { const historyFile = './watcher.test.history' const scannedPath = './test/testFiles/TestScannedDirectory' - let logStub - beforeEach(function () { fs.writeFileSync(historyFile, '') - logStub = sinon.stub(logger, 'info') }) afterEach(function () { fs.unlinkSync(historyFile) - logStub.restore() }) it('should correctly create an empty history file', async function () { @@ -41,9 +36,7 @@ describe('testing add/remove/init functions ', function () { oneShot: true, historyWriteInterval: 10 }) - initHistory() - expect(fs.existsSync(historyFile)).to.be.true expect(fs.readFileSync(historyFile, 'utf8')).to.equal('') }) @@ -63,22 +56,19 @@ describe('testing add/remove/init functions ', function () { addToHistory(file) - await new Promise(resolve => - setTimeout(resolve, options.historyWriteInterval) - ) - + await new Promise(resolve => setTimeout(resolve, options.historyWriteInterval)) const data = fs.readFileSync(historyFile, 'utf8').trim() // Trim the newline character expect(data).to.equal(file) }) it('should correctly remove from history file', async function () { - const options = { + setOptions({ historyFile: historyFile, path: scannedPath, scanInterval: 5000, oneShot: true, historyWriteInterval: 10 - } + }) initHistory(options) @@ -86,15 +76,11 @@ describe('testing add/remove/init functions ', function () { addToHistory(file) - await new Promise(resolve => - setTimeout(resolve, options.historyWriteInterval) - ) + await new Promise(resolve => setTimeout(resolve, options.historyWriteInterval)) removeFromHistory(file) - await new Promise(resolve => - setTimeout(resolve, options.historyWriteInterval) - ) + await new Promise(resolve => setTimeout(resolve, options.historyWriteInterval)) const data = fs.readFileSync(historyFile, 'utf8').trim() @@ -106,18 +92,12 @@ describe('testing starting with empty history file and adding entries', function const historyFile = './watcher.test.history' const scannedPath = './test/testFiles/' const fileContents = '' - let logStub, warnStub - beforeEach(function () { fs.writeFileSync(historyFile, '') - fs.mkdirSync(scannedPath, { recursive: true }) for (let i = 1; i <= 5; i++) { fs.writeFileSync(path.join(scannedPath, `file${i}.ckl`), fileContents) } - - logStub = sinon.stub(logger, 'info') - warnStub = sinon.stub(logger, 'warn') }) afterEach(function () { @@ -128,9 +108,6 @@ describe('testing starting with empty history file and adding entries', function fs.unlinkSync(path.join(scannedPath, file)) } fs.rmSync(scannedPath, { recursive: true }) - - logStub.restore() - warnStub.restore() }) it('should correctly identify the 5 new files and update the history file with all 5', async function () { @@ -145,82 +122,12 @@ describe('testing starting with empty history file and adding entries', function // create history file initHistory() - // start scanning - await startScanner() - - await new Promise(resolve => - setTimeout(resolve, options.historyWriteInterval) - ) - - // read the history file - const data = fs.readFileSync(historyFile, 'utf8') - const lines = data.split('\n').filter(line => line.trim() !== '') - - expect(lines.length).to.equal(5) - - const expectedHistoryEntries = [ - './test/testFiles/file1.ckl', - './test/testFiles/file2.ckl', - './test/testFiles/file3.ckl', - './test/testFiles/file4.ckl', - './test/testFiles/file5.ckl' - ] - - for (const entry of expectedHistoryEntries) { - expect(lines).to.include(entry) - } - }) -}) - -describe('testing starting with entries in history file that are also in the scanned directory', function () { - const historyFile = './watcher.test.history' - const scannedPath = './test/testFiles/' - const fileContents = '' - - let logStub, warnStub - - beforeEach(function () { - fs.writeFileSync( - historyFile, - './test/testFiles/TestScannedDirectory/file1.ckl\n./test/testFiles/TestScannedDirectory/file2.ckl\n' - ) - - fs.mkdirSync(scannedPath, { recursive: true }) - for (let i = 1; i <= 5; i++) { - fs.writeFileSync(path.join(scannedPath, `file${i}.ckl`), fileContents) - } - - logStub = sinon.stub(logger, 'info') - warnStub = sinon.stub(logger, 'warn') - }) - - afterEach(function () { - fs.unlinkSync(historyFile) - + const files = fs.readdirSync(scannedPath) for (const file of files) { - fs.unlinkSync(path.join(scannedPath, file)) + addToHistory(path.join(scannedPath, file)) } - fs.rmSync(scannedPath, { recursive: true }) - - logStub.restore() - warnStub.restore() - }) - it('should correctly return the two files previously in the history file aswell as the other three in the scanned directory.', async function () { - setOptions({ - historyFile: historyFile, - path: scannedPath, - scanInterval: 5000, - oneShot: true, - historyWriteInterval: 10 - }) - - // create history file - initHistory() - - // start scanning - await startScanner() await new Promise(resolve => setTimeout(resolve, options.historyWriteInterval) @@ -233,155 +140,11 @@ describe('testing starting with entries in history file that are also in the sca expect(lines.length).to.equal(5) const expectedHistoryEntries = [ - './test/testFiles/file1.ckl', - './test/testFiles/file2.ckl', - './test/testFiles/file3.ckl', - './test/testFiles/file4.ckl', - './test/testFiles/file5.ckl' - ] - - for (const entry of expectedHistoryEntries) { - expect(lines).to.include(entry) - } - }) -}) - -describe('testing starting with entries in history file that are NOT in the scanned directory', function () { - const historyFile = './watcher.test.history' - const scannedPath = './test/testFiles/' - const fileContents = '' - - let logStub, warnStub - - beforeEach(function () { - fs.writeFileSync( - historyFile, - './test/testFiles//file55.ckl\n./test/testFiles//file22.ckl\n' - ) - - fs.mkdirSync(scannedPath, { recursive: true }) - for (let i = 1; i <= 5; i++) { - fs.writeFileSync(path.join(scannedPath, `file${i}.ckl`), fileContents) - } - - logStub = sinon.stub(logger, 'info') - warnStub = sinon.stub(logger, 'warn') - }) - - afterEach(function () { - fs.unlinkSync(historyFile) - const files = fs.readdirSync(scannedPath) - for (const file of files) { - fs.unlinkSync(path.join(scannedPath, file)) - } - fs.rmSync(scannedPath, { recursive: true }) - logStub.restore() - warnStub.restore() - }) - - it('should correctly remove the 2 files history file then write the 5 in the scanned directory.', async function () { - setOptions({ - historyFile: historyFile, - path: scannedPath, - scanInterval: 5000, - oneShot: true, - historyWriteInterval: 10 - }) - - // create history file - initHistory() - - // start scanning - await startScanner() - - await new Promise(resolve => - setTimeout(resolve, options.historyWriteInterval) - ) - - // read the history file - const data = fs.readFileSync(historyFile, 'utf8') - const lines = data.split('\n').filter(line => line.trim() !== '') - - expect(lines.length).to.equal(5) - - const expectedHistoryEntries = [ - './test/testFiles/file1.ckl', - './test/testFiles/file2.ckl', - './test/testFiles/file3.ckl', - './test/testFiles/file4.ckl', - './test/testFiles/file5.ckl' - ] - - for (const entry of expectedHistoryEntries) { - expect(lines).to.include(entry) - } - }) -}) - -describe('testing starting with entries in history file that are not in the scanned directory and files that are in the scanned directory', function () { - const historyFile = './watcher.test.history' - const scannedPath = './test/testFiles/' - const fileContents = '' - - let logStub, warnStub - - beforeEach(function () { - fs.writeFileSync( - historyFile, - './test/testFiles/TestScannedDirectory/file55.ckl\n./test/testFiles/TestScannedDirectory/file22.ckl\n\n./test/testFiles/TestScannedDirectory/file1.ckl\n./test/testFiles/TestScannedDirectory/file2.ckl\n' - ) - - fs.mkdirSync(scannedPath, { recursive: true }) - for (let i = 1; i <= 5; i++) { - fs.writeFileSync(path.join(scannedPath, `file${i}.ckl`), fileContents) - } - - logStub = sinon.stub(logger, 'info') - warnStub = sinon.stub(logger, 'warn') - }) - - afterEach(function () { - fs.unlinkSync(historyFile) - const files = fs.readdirSync(scannedPath) - for (const file of files) { - fs.unlinkSync(path.join(scannedPath, file)) - } - fs.rmSync(scannedPath, { recursive: true }) - logStub.restore() - warnStub.restore() - }) - - it('should correctly remove the 2 files history file skip 2 files already in the history file and scanned directory and add one file to the history file', async function () { - setOptions({ - historyFile: historyFile, - path: scannedPath, - scanInterval: 5000, - oneShot: true, - historyWriteInterval: 50 - }) - - // create history file - initHistory() - - // start scanning - await startScanner() - - await new Promise(resolve => - setTimeout(resolve, options.historyWriteInterval) - ) - - // read the history file - const data = fs.readFileSync(historyFile, 'utf8') - const lines = data.split('\n').filter(line => line.trim() !== '') - - expect(lines.length).to.equal(5) - - const expectedHistoryEntries = [ - './test/testFiles/file1.ckl', - './test/testFiles/file2.ckl', - './test/testFiles/file3.ckl', - './test/testFiles/file4.ckl', - './test/testFiles/file5.ckl' + 'test/testFiles/file1.ckl', + 'test/testFiles/file2.ckl', + 'test/testFiles/file3.ckl', + 'test/testFiles/file4.ckl', + 'test/testFiles/file5.ckl' ] for (const entry of expectedHistoryEntries) { @@ -394,18 +157,13 @@ describe('testing starting with empty history file and slowly adding and removin this.timeout(5000) const historyFile = './watcher.test.history' const scannedPath = './test/testFiles/' - let logStub, warnStub beforeEach(function () { fs.writeFileSync(historyFile, '') - logStub = sinon.stub(logger, 'info') - warnStub = sinon.stub(logger, 'warn') }) afterEach(function () { fs.unlinkSync(historyFile) - logStub.restore() - warnStub.restore() }) it('should correctly remove the 2 files history file skip 2 files already in the history file and scanned directory and add one file to the history file', async function () { @@ -423,9 +181,7 @@ describe('testing starting with empty history file and slowly adding and removin addToHistory('./test/testFiles/file1.ckl') addToHistory('./test/testFiles/file2.ckl') - await new Promise(resolve => - setTimeout(resolve, options.historyWriteInterval) - ) + await new Promise(resolve => setTimeout(resolve, options.historyWriteInterval)) // read the history file const data = fs.readFileSync(historyFile, 'utf8') @@ -437,9 +193,7 @@ describe('testing starting with empty history file and slowly adding and removin removeFromHistory('./test/testFiles/file1.ckl') - await new Promise(resolve => - setTimeout(resolve, options.historyWriteInterval) - ) + await new Promise(resolve => setTimeout(resolve, options.historyWriteInterval)) // read the history file const data2 = fs.readFileSync(historyFile, 'utf8') @@ -454,18 +208,6 @@ describe('testing no history file mode', function () { const historyFile = null const scannedPath = './test/testFiles/' - let logStub, warnStub - - beforeEach(function () { - logStub = sinon.stub(logger, 'info') - warnStub = sinon.stub(logger, 'warn') - }) - - afterEach(function () { - logStub.restore() - warnStub.restore() - }) - it('should correctly run in no history file mode', async function () { setOptions({ historyFile: historyFile, @@ -481,9 +223,7 @@ describe('testing no history file mode', function () { addToHistory(file) - await new Promise(resolve => - setTimeout(resolve, options.historyWriteInterval) - ) + await new Promise(resolve => setTimeout(resolve, options.historyWriteInterval)) // expect no history file to be created expect(fs.existsSync(historyFile)).to.be.false @@ -493,9 +233,9 @@ describe('testing no history file mode', function () { describe('cleaning up', function () { after(async function () { setTimeout(() => { - process.exit(0); // Delayed exit to allow Mocha to output results - }, 1000); // Adjust time as necessary for your environment - }); + process.exit(0) // Delayed exit to allow Mocha to output results + }, 1000) // Adjust time as necessary for your environment + }) it('should clean up the history file', function () { const historyFilePath = './watcher.test.history'