From f6c1e2738d02437e018e9fe7276d9afc8d255aa2 Mon Sep 17 00:00:00 2001 From: Eric Mc Sween Date: Tue, 20 Dec 2022 07:11:25 -0500 Subject: [PATCH] Merge pull request #10938 from overleaf/em-esm-spelling Migrate spelling to ES modules GitOrigin-RevId: 4a200c8d1c28be44027cc8a3097e42575ab6593f --- package-lock.json | 4 +- services/spelling/app.js | 63 ++++-------- services/spelling/app/js/ASpell.js | 98 ++++++++++--------- services/spelling/app/js/ASpellWorker.js | 14 ++- services/spelling/app/js/ASpellWorkerPool.js | 14 ++- .../spelling/app/js/HealthCheckController.js | 66 ++++++------- .../spelling/app/js/SpellingAPIController.js | 47 +++++---- .../spelling/app/js/SpellingAPIManager.js | 33 +++---- services/spelling/app/js/server.js | 26 +++++ ...ings.defaults.js => settings.defaults.cjs} | 0 services/spelling/package.json | 7 +- .../spelling/test/acceptance/js/CheckTest.js | 4 +- .../test/acceptance/js/HealthCheckTest.js | 4 +- services/spelling/test/acceptance/js/Init.js | 7 +- .../spelling/test/acceptance/js/StatusTest.js | 4 +- .../test/acceptance/js/helpers/request.js | 16 ++- services/spelling/test/setup.js | 19 +--- .../spelling/test/stress/js/stressTest.js | 6 +- services/spelling/test/unit/js/ASpellTests.js | 25 ++--- .../test/unit/js/ASpellWorkerTests.js | 64 ++++++------ .../test/unit/js/SpellingAPIManagerTests.js | 54 +++++----- 21 files changed, 263 insertions(+), 312 deletions(-) create mode 100644 services/spelling/app/js/server.js rename services/spelling/config/{settings.defaults.js => settings.defaults.cjs} (100%) diff --git a/package-lock.json b/package-lock.json index d12e66767a..45bd22f98c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37903,8 +37903,8 @@ "devDependencies": { "chai": "^4.3.6", "chai-as-promised": "^7.1.1", + "esmock": "^2.1.0", "mocha": "^8.4.0", - "sandboxed-module": "2.0.4", "sinon": "^9.2.4" } }, @@ -48268,11 +48268,11 @@ "bunyan": "^1.8.15", "chai": "^4.3.6", "chai-as-promised": "^7.1.1", + "esmock": "^2.1.0", "express": "^4.17.1", "lru-cache": "^5.1.1", "mocha": "^8.4.0", "request": "^2.88.2", - "sandboxed-module": "2.0.4", "sinon": "^9.2.4", "underscore": "1.13.1" }, diff --git a/services/spelling/app.js b/services/spelling/app.js index 70364afefe..9aaff3ff5f 100644 --- a/services/spelling/app.js +++ b/services/spelling/app.js @@ -1,51 +1,22 @@ -/* - * decaffeinate suggestions: - * DS102: Remove unnecessary code created because of implicit returns - * DS103: Rewrite code to no longer use __guard__ - * DS207: Consider shorter variations of null checks - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md - */ -const metrics = require('@overleaf/metrics') -metrics.initialize('spelling') +import Settings from '@overleaf/settings' +import logger from '@overleaf/logger' +import { app } from './app/js/server.js' +import * as ASpell from './app/js/ASpell.js' -const Settings = require('@overleaf/settings') -const logger = require('@overleaf/logger') -logger.initialize('spelling') -if ((Settings.sentry != null ? Settings.sentry.dsn : undefined) != null) { - logger.initializeErrorReporting(Settings.sentry.dsn) -} -metrics.memory.monitor(logger) +const { host = 'localhost', port = 3005 } = Settings.internal?.spelling ?? {} -const SpellingAPIController = require('./app/js/SpellingAPIController') -const express = require('express') -const app = express() -metrics.injectMetricsRoute(app) -const bodyParser = require('body-parser') -const HealthCheckController = require('./app/js/HealthCheckController') +ASpell.startCacheDump() -app.use(bodyParser.json({ limit: '2mb' })) -app.use(metrics.http.monitor(logger)) +const server = app.listen(port, host, function (error) { + if (error) { + throw error + } + logger.info({ host, port }, 'spelling HTTP server starting up') +}) -app.post('/user/:user_id/check', SpellingAPIController.check) -app.get('/status', (req, res) => res.send({ status: 'spelling api is up' })) - -app.get('/health_check', HealthCheckController.healthCheck) - -const settings = - Settings.internal && Settings.internal.spelling - ? Settings.internal.spelling - : undefined -const host = settings && settings.host ? settings.host : 'localhost' -const port = settings && settings.port ? settings.port : 3005 - -if (!module.parent) { - // application entry point, called directly - app.listen(port, host, function (error) { - if (error != null) { - throw error - } - return logger.debug(`spelling starting up, listening on ${host}:${port}`) +process.on('SIGTERM', () => { + ASpell.stopCacheDump() + server.close(() => { + logger.info({ host, port }, 'spelling HTTP server closed') }) -} - -module.exports = app +}) diff --git a/services/spelling/app/js/ASpell.js b/services/spelling/app/js/ASpell.js index 69926bc83a..edd55c6b3c 100644 --- a/services/spelling/app/js/ASpell.js +++ b/services/spelling/app/js/ASpell.js @@ -7,14 +7,16 @@ * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -const ASpellWorkerPool = require('./ASpellWorkerPool') -const LRU = require('lru-cache') -const logger = require('@overleaf/logger') -const fs = require('fs') -const settings = require('@overleaf/settings') -const Path = require('path') -const { promisify } = require('util') -const OError = require('@overleaf/o-error') +import fs from 'node:fs' +import Path from 'node:path' +import { promisify } from 'node:util' +import LRU from 'lru-cache' +import logger from '@overleaf/logger' +import settings from '@overleaf/settings' +import OError from '@overleaf/o-error' +import { ASpellWorkerPool } from './ASpellWorkerPool.js' + +let ASPELL_TIMEOUT = 10000 const OneMinute = 60 * 1000 const opts = { max: 10000, maxAge: OneMinute * 60 * 10 } @@ -23,6 +25,8 @@ const cache = new LRU(opts) const cacheFsPath = Path.resolve(settings.cacheDir, 'spell.cache') const cacheFsPathTmp = cacheFsPath + '.tmp' +const WorkerPool = new ASpellWorkerPool() + // load any existing cache try { const oldCache = fs.readFileSync(cacheFsPath) @@ -33,24 +37,31 @@ try { ) } -// write the cache every 30 minutes -const cacheDump = setInterval(function () { - const dump = JSON.stringify(cache.dump()) - return fs.writeFile(cacheFsPathTmp, dump, function (err) { - if (err != null) { - logger.debug(OError.tag(err, 'error writing cache file')) - fs.unlink(cacheFsPathTmp, () => {}) - } else { - fs.rename(cacheFsPathTmp, cacheFsPath, err => { - if (err) { - logger.error(OError.tag(err, 'error renaming cache file')) - } else { - logger.debug({ len: dump.length, cacheFsPath }, 'wrote cache file') - } - }) - } - }) -}, 30 * OneMinute) +let cacheDumpInterval +export function startCacheDump() { + // write the cache every 30 minutes + cacheDumpInterval = setInterval(function () { + const dump = JSON.stringify(cache.dump()) + return fs.writeFile(cacheFsPathTmp, dump, function (err) { + if (err != null) { + logger.debug(OError.tag(err, 'error writing cache file')) + fs.unlink(cacheFsPathTmp, () => {}) + } else { + fs.rename(cacheFsPathTmp, cacheFsPath, err => { + if (err) { + logger.error(OError.tag(err, 'error renaming cache file')) + } else { + logger.debug({ len: dump.length, cacheFsPath }, 'wrote cache file') + } + }) + } + }) + }, 30 * OneMinute) +} + +export function stopCacheDump() { + clearInterval(cacheDumpInterval) +} class ASpellRunner { checkWords(language, words, callback) { @@ -159,33 +170,28 @@ class ASpellRunner { words = Object.keys(newWord) if (words.length) { - return WorkerPool.check(language, words, ASpell.ASPELL_TIMEOUT, callback) + return WorkerPool.check(language, words, ASPELL_TIMEOUT, callback) } else { return callback(null, '') } } } -const ASpell = { - // The description of how to call aspell from another program can be found here: - // http://aspell.net/man-html/Through-A-Pipe.html - checkWords(language, words, callback) { - if (callback == null) { - callback = () => {} - } - const runner = new ASpellRunner() - return runner.checkWords(language, words, callback) - }, - ASPELL_TIMEOUT: 10000, +// The description of how to call aspell from another program can be found here: +// http://aspell.net/man-html/Through-A-Pipe.html +export function checkWords(language, words, callback) { + if (callback == null) { + callback = () => {} + } + const runner = new ASpellRunner() + return runner.checkWords(language, words, callback) } -const promises = { - checkWords: promisify(ASpell.checkWords), +export const promises = { + checkWords: promisify(checkWords), } -ASpell.promises = promises - -module.exports = ASpell - -const WorkerPool = new ASpellWorkerPool() -module.exports.cacheDump = cacheDump +// for tests +export function setTimeout(timeout) { + ASPELL_TIMEOUT = timeout +} diff --git a/services/spelling/app/js/ASpellWorker.js b/services/spelling/app/js/ASpellWorker.js index 86ea15c84f..1c3adc8761 100644 --- a/services/spelling/app/js/ASpellWorker.js +++ b/services/spelling/app/js/ASpellWorker.js @@ -7,15 +7,15 @@ * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -const childProcess = require('child_process') -const logger = require('@overleaf/logger') -const metrics = require('@overleaf/metrics') -const _ = require('underscore') -const OError = require('@overleaf/o-error') +import childProcess from 'node:child_process' +import logger from '@overleaf/logger' +import metrics from '@overleaf/metrics' +import _ from 'underscore' +import OError from '@overleaf/o-error' const BATCH_SIZE = 100 -class ASpellWorker { +export class ASpellWorker { constructor(language) { this.language = language this.count = 0 @@ -238,5 +238,3 @@ class ASpellWorker { return this.pipe.stdin.write(command + '\n') } } - -module.exports = ASpellWorker diff --git a/services/spelling/app/js/ASpellWorkerPool.js b/services/spelling/app/js/ASpellWorkerPool.js index cc08f0cef4..d2c43bd283 100644 --- a/services/spelling/app/js/ASpellWorkerPool.js +++ b/services/spelling/app/js/ASpellWorkerPool.js @@ -7,13 +7,13 @@ * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -const ASpellWorker = require('./ASpellWorker') -const _ = require('underscore') -const logger = require('@overleaf/logger') -const metrics = require('@overleaf/metrics') -const OError = require('@overleaf/o-error') +import _ from 'underscore' +import logger from '@overleaf/logger' +import metrics from '@overleaf/metrics' +import OError from '@overleaf/o-error' +import { ASpellWorker } from './ASpellWorker.js' -class ASpellWorkerPool { +export class ASpellWorkerPool { static initClass() { this.prototype.MAX_REQUESTS = 100 * 1024 this.prototype.MAX_WORKERS = 32 @@ -112,5 +112,3 @@ class ASpellWorkerPool { } } ASpellWorkerPool.initClass() - -module.exports = ASpellWorkerPool diff --git a/services/spelling/app/js/HealthCheckController.js b/services/spelling/app/js/HealthCheckController.js index 453184a518..0be86a68be 100644 --- a/services/spelling/app/js/HealthCheckController.js +++ b/services/spelling/app/js/HealthCheckController.js @@ -1,39 +1,37 @@ -const request = require('request') -const logger = require('@overleaf/logger') -const settings = require('@overleaf/settings') -const OError = require('@overleaf/o-error') +import request from 'request' +import logger from '@overleaf/logger' +import settings from '@overleaf/settings' +import OError from '@overleaf/o-error' -module.exports = { - healthCheck(req, res) { - const opts = { - url: `http://localhost:3005/user/${settings.healthCheckUserId}/check`, - json: { - words: ['helllo'], - language: 'en', - }, - timeout: 1000 * 20, +export function healthCheck(req, res) { + const opts = { + url: `http://localhost:3005/user/${settings.healthCheckUserId}/check`, + json: { + words: ['helllo'], + language: 'en', + }, + timeout: 1000 * 20, + } + return request.post(opts, function (err, response, body) { + if (err != null) { + return res.sendStatus(500) } - return request.post(opts, function (err, response, body) { - if (err != null) { - return res.sendStatus(500) - } - const misspellings = - body && body.misspellings ? body.misspellings[0] : undefined - const numberOfSuggestions = - misspellings && misspellings.suggestions - ? misspellings.suggestions.length - : 0 + const misspellings = + body && body.misspellings ? body.misspellings[0] : undefined + const numberOfSuggestions = + misspellings && misspellings.suggestions + ? misspellings.suggestions.length + : 0 - if (numberOfSuggestions > 10) { - logger.debug('health check passed') - res.sendStatus(200) - } else { - logger.err( - new OError('health check failed', { body, numberOfSuggestions }) - ) - res.sendStatus(500) - } - }) - }, + if (numberOfSuggestions > 10) { + logger.debug('health check passed') + res.sendStatus(200) + } else { + logger.err( + new OError('health check failed', { body, numberOfSuggestions }) + ) + res.sendStatus(500) + } + }) } diff --git a/services/spelling/app/js/SpellingAPIController.js b/services/spelling/app/js/SpellingAPIController.js index c71301ec49..62e218835c 100644 --- a/services/spelling/app/js/SpellingAPIController.js +++ b/services/spelling/app/js/SpellingAPIController.js @@ -1,31 +1,28 @@ -const SpellingAPIManager = require('./SpellingAPIManager') -const logger = require('@overleaf/logger') -const metrics = require('@overleaf/metrics') -const OError = require('@overleaf/o-error') +import logger from '@overleaf/logger' +import metrics from '@overleaf/metrics' +import OError from '@overleaf/o-error' +import * as SpellingAPIManager from './SpellingAPIManager.js' function extractCheckRequestData(req) { - const token = req.params ? req.params.user_id : undefined - const wordCount = - req.body && req.body.words ? req.body.words.length : undefined + const token = req.params?.user_id + const wordCount = req.body?.words?.length return { token, wordCount } } -module.exports = { - check(req, res) { - metrics.inc('spelling-check', 0.1) - const { token, wordCount } = extractCheckRequestData(req) - logger.debug({ token, wordCount }, 'running check') - SpellingAPIManager.runRequest(token, req.body, function (error, result) { - if (error != null) { - logger.error( - OError.tag(error, 'error processing spelling request', { - user_id: token, - wordCount, - }) - ) - return res.sendStatus(500) - } - res.send(result) - }) - }, +export function check(req, res) { + metrics.inc('spelling-check', 0.1) + const { token, wordCount } = extractCheckRequestData(req) + logger.debug({ token, wordCount }, 'running check') + SpellingAPIManager.runRequest(token, req.body, (error, result) => { + if (error != null) { + logger.error( + OError.tag(error, 'error processing spelling request', { + user_id: token, + wordCount, + }) + ) + return res.sendStatus(500) + } + res.send(result) + }) } diff --git a/services/spelling/app/js/SpellingAPIManager.js b/services/spelling/app/js/SpellingAPIManager.js index 336037a880..9414f86259 100644 --- a/services/spelling/app/js/SpellingAPIManager.js +++ b/services/spelling/app/js/SpellingAPIManager.js @@ -6,31 +6,26 @@ * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -const ASpell = require('./ASpell') -const { callbackify } = require('util') -const OError = require('@overleaf/o-error') +import { callbackify } from 'node:util' +import OError from '@overleaf/o-error' +import * as ASpell from './ASpell.js' // The max number of words checked in a single request const REQUEST_LIMIT = 10000 -const SpellingAPIManager = {} +export const promises = {} -const promises = { - async runRequest(token, request) { - if (!request.words) { - throw new OError('malformed JSON') - } - const lang = request.language || 'en' +promises.runRequest = async (token, request) => { + if (!request.words) { + throw new OError('malformed JSON') + } + const lang = request.language || 'en' - // only the first 10K words are checked - const wordSlice = request.words.slice(0, REQUEST_LIMIT) + // only the first 10K words are checked + const wordSlice = request.words.slice(0, REQUEST_LIMIT) - const misspellings = await ASpell.promises.checkWords(lang, wordSlice) - return { misspellings } - }, + const misspellings = await ASpell.promises.checkWords(lang, wordSlice) + return { misspellings } } -SpellingAPIManager.runRequest = callbackify(promises.runRequest) -SpellingAPIManager.promises = promises - -module.exports = SpellingAPIManager +export const runRequest = callbackify(promises.runRequest) diff --git a/services/spelling/app/js/server.js b/services/spelling/app/js/server.js new file mode 100644 index 0000000000..fcd89fb72e --- /dev/null +++ b/services/spelling/app/js/server.js @@ -0,0 +1,26 @@ +import metrics from '@overleaf/metrics' +import Settings from '@overleaf/settings' +import logger from '@overleaf/logger' +import express from 'express' +import bodyParser from 'body-parser' +import * as SpellingAPIController from './SpellingAPIController.js' +import * as HealthCheckController from './HealthCheckController.js' + +metrics.initialize('spelling') +logger.initialize('spelling') +if (Settings.sentry?.dsn != null) { + logger.initializeErrorReporting(Settings.sentry.dsn) +} +metrics.memory.monitor(logger) + +export const app = express() + +metrics.injectMetricsRoute(app) + +app.use(bodyParser.json({ limit: '2mb' })) +app.use(metrics.http.monitor(logger)) + +app.post('/user/:user_id/check', SpellingAPIController.check) +app.get('/status', (req, res) => res.send({ status: 'spelling api is up' })) + +app.get('/health_check', HealthCheckController.healthCheck) diff --git a/services/spelling/config/settings.defaults.js b/services/spelling/config/settings.defaults.cjs similarity index 100% rename from services/spelling/config/settings.defaults.js rename to services/spelling/config/settings.defaults.cjs diff --git a/services/spelling/package.json b/services/spelling/package.json index 65e76da252..960d63c977 100644 --- a/services/spelling/package.json +++ b/services/spelling/package.json @@ -3,12 +3,13 @@ "description": "A JSON API wrapper around aspell", "private": true, "main": "app.js", + "type": "module", "scripts": { "compile:app": "([ -e app/coffee ] && coffee -m $COFFEE_OPTIONS -o app/js -c app/coffee || echo 'No CoffeeScript folder to compile') && ( [ -e app.coffee ] && coffee -m $COFFEE_OPTIONS -c app.coffee || echo 'No CoffeeScript app to compile')", "start": "node $NODE_APP_OPTIONS app.js", - "test:acceptance:_run": "mocha --recursive --reporter spec --timeout 15000 --exit $@ test/acceptance/js", + "test:acceptance:_run": "LOG_LEVEL=fatal mocha --loader=esmock --recursive --reporter spec --timeout 15000 --exit $@ test/acceptance/js", "test:acceptance": "npm run test:acceptance:_run -- --grep=$MOCHA_GREP", - "test:unit:_run": "mocha --recursive --reporter spec $@ test/unit/js", + "test:unit:_run": "LOG_LEVEL=fatal mocha --loader=esmock --recursive --reporter spec $@ test/unit/js", "test:unit": "npm run test:unit:_run -- --grep=$MOCHA_GREP", "compile:unit_tests": "[ ! -e test/unit/coffee ] && echo 'No unit tests to compile' || coffee -o test/unit/js -c test/unit/coffee", "compile:acceptance_tests": "[ ! -e test/acceptance/coffee ] && echo 'No acceptance tests to compile' || coffee -o test/acceptance/js -c test/acceptance/coffee", @@ -37,8 +38,8 @@ "devDependencies": { "chai": "^4.3.6", "chai-as-promised": "^7.1.1", + "esmock": "^2.1.0", "mocha": "^8.4.0", - "sandboxed-module": "2.0.4", "sinon": "^9.2.4" } } diff --git a/services/spelling/test/acceptance/js/CheckTest.js b/services/spelling/test/acceptance/js/CheckTest.js index 76cb4f1774..0096209068 100644 --- a/services/spelling/test/acceptance/js/CheckTest.js +++ b/services/spelling/test/acceptance/js/CheckTest.js @@ -1,5 +1,5 @@ -const { expect } = require('chai') -const request = require('./helpers/request') +import { expect } from 'chai' +import * as request from './helpers/request.js' const USER_ID = 101 diff --git a/services/spelling/test/acceptance/js/HealthCheckTest.js b/services/spelling/test/acceptance/js/HealthCheckTest.js index 6bf551e3bb..634bea825a 100644 --- a/services/spelling/test/acceptance/js/HealthCheckTest.js +++ b/services/spelling/test/acceptance/js/HealthCheckTest.js @@ -1,5 +1,5 @@ -const { expect } = require('chai') -const request = require('./helpers/request') +import { expect } from 'chai' +import * as request from './helpers/request.js' describe('/health_check', function () { it('should return 200', async function () { diff --git a/services/spelling/test/acceptance/js/Init.js b/services/spelling/test/acceptance/js/Init.js index 33f454e6b5..bb1f18730f 100644 --- a/services/spelling/test/acceptance/js/Init.js +++ b/services/spelling/test/acceptance/js/Init.js @@ -1,5 +1,6 @@ -const App = require('../../../app.js') -const { PORT } = require('./helpers/request') +import { app } from '../../../app/js/server.js' +import { PORT } from './helpers/request.js' + before(function (done) { - return App.listen(PORT, 'localhost', done) + return app.listen(PORT, 'localhost', done) }) diff --git a/services/spelling/test/acceptance/js/StatusTest.js b/services/spelling/test/acceptance/js/StatusTest.js index 15a28592bc..1208df3cef 100644 --- a/services/spelling/test/acceptance/js/StatusTest.js +++ b/services/spelling/test/acceptance/js/StatusTest.js @@ -1,5 +1,5 @@ -const { expect } = require('chai') -const request = require('./helpers/request') +import { expect } from 'chai' +import * as request from './helpers/request.js' describe('/status', function () { it('should return 200', async function () { diff --git a/services/spelling/test/acceptance/js/helpers/request.js b/services/spelling/test/acceptance/js/helpers/request.js index 3010e5abf7..40d7b16421 100644 --- a/services/spelling/test/acceptance/js/helpers/request.js +++ b/services/spelling/test/acceptance/js/helpers/request.js @@ -1,10 +1,11 @@ -const { promisify } = require('util') +import { promisify } from 'util' +import Request from 'request' -const PORT = 3005 +export const PORT = 3005 const BASE_URL = `http://${process.env.HTTP_TEST_HOST || 'localhost'}:${PORT}` -const request = require('request').defaults({ +const request = Request.defaults({ baseUrl: BASE_URL, headers: { 'Content-Type': 'application/json', @@ -12,9 +13,6 @@ const request = require('request').defaults({ followRedirect: false, }) -module.exports = { - PORT, - get: promisify(request.get), - post: promisify(request.post), - del: promisify(request.del), -} +export const get = promisify(request.get) +export const post = promisify(request.post) +export const del = promisify(request.del) diff --git a/services/spelling/test/setup.js b/services/spelling/test/setup.js index e95a300d30..1f9af3f1cf 100644 --- a/services/spelling/test/setup.js +++ b/services/spelling/test/setup.js @@ -1,21 +1,4 @@ -const chai = require('chai') -const SandboxedModule = require('sandboxed-module') +import chai from 'chai' // Chai configuration chai.should() - -// SandboxedModule configuration -SandboxedModule.configure({ - requires: { - '@overleaf/logger': { - debug() {}, - log() {}, - info() {}, - warn() {}, - err() {}, - error() {}, - fatal() {}, - }, - }, - globals: { Buffer, JSON, console, process }, -}) diff --git a/services/spelling/test/stress/js/stressTest.js b/services/spelling/test/stress/js/stressTest.js index df5fa32d18..e9a97138a8 100644 --- a/services/spelling/test/stress/js/stressTest.js +++ b/services/spelling/test/stress/js/stressTest.js @@ -13,9 +13,9 @@ // send P correct words and Q incorrect words // generate incorrect words by qq+random -const async = require('async') -const request = require('request') -const fs = require('fs') +import fs from 'node:fs' +import async from 'async' +import request from 'request' // created with // aspell -d en dump master | aspell -l en expand | shuf -n 150000 > words.txt diff --git a/services/spelling/test/unit/js/ASpellTests.js b/services/spelling/test/unit/js/ASpellTests.js index b8bbb3940e..f21c839db1 100644 --- a/services/spelling/test/unit/js/ASpellTests.js +++ b/services/spelling/test/unit/js/ASpellTests.js @@ -8,22 +8,17 @@ * DS102: Remove unnecessary code created because of implicit returns * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -const { expect, assert } = require('chai') -const SandboxedModule = require('sandboxed-module') +import { expect, assert } from 'chai' +import esmock from 'esmock' describe('ASpell', function () { - beforeEach(function () { - return (this.ASpell = SandboxedModule.require('../../../app/js/ASpell', { - requires: { - '@overleaf/metrics': { - gauge() {}, - inc() {}, - }, + beforeEach(async function () { + this.ASpell = await esmock('../../../app/js/ASpell', { + '@overleaf/metrics': { + gauge() {}, + inc() {}, }, - })) - }) - afterEach(function () { - clearInterval(this.ASpell.cacheDump) + }) }) describe('a correctly spelled word', function () { @@ -114,7 +109,7 @@ describe('ASpell', function () { return describe('when the request times out', function () { beforeEach(function (done) { const words = __range__(0, 1000, true).map(i => 'abcdefg') - this.ASpell.ASPELL_TIMEOUT = 1 + this.ASpell.setTimeout(1) this.start = Date.now() return this.ASpell.checkWords('en', words, (error, result) => { expect(error).to.exist @@ -128,7 +123,7 @@ describe('ASpell', function () { // or the CI server. return it('should return in reasonable time', function () { const delta = Date.now() - this.start - return delta.should.be.below(this.ASpell.ASPELL_TIMEOUT + 1000) + return delta.should.be.below(1000) }) }) }) diff --git a/services/spelling/test/unit/js/ASpellWorkerTests.js b/services/spelling/test/unit/js/ASpellWorkerTests.js index 561f343c32..b10507289f 100644 --- a/services/spelling/test/unit/js/ASpellWorkerTests.js +++ b/services/spelling/test/unit/js/ASpellWorkerTests.js @@ -1,45 +1,39 @@ -/* eslint-disable - no-undef -*/ -const sinon = require('sinon') -const { expect } = require('chai') -const SandboxedModule = require('sandboxed-module') -const EventEmitter = require('events') +import sinon from 'sinon' +import { expect } from 'chai' +import esmock from 'esmock' +import EventEmitter from 'events' describe('ASpellWorker', function () { - beforeEach(function () { - this.child_process = {} - return (this.ASpellWorker = SandboxedModule.require( - '../../../app/js/ASpellWorker', - { - requires: { - '@overleaf/metrics': { - gauge() {}, - inc() {}, - }, - child_process: this.child_process, - }, - } - )) + beforeEach(async function () { + this.pipe = { + stdout: new EventEmitter(), + stderr: { on: sinon.stub() }, + stdin: { on: sinon.stub() }, + on: sinon.stub(), + pid: 12345, + } + this.pipe.stdout.setEncoding = sinon.stub() + this.child_process = { + spawn: sinon.stub().returns(this.pipe), + } + const { ASpellWorker } = await esmock('../../../app/js/ASpellWorker', { + '@overleaf/metrics': { + gauge() {}, + inc() {}, + }, + child_process: this.child_process, + }) + this.ASpellWorker = ASpellWorker }) describe('creating a worker', function () { beforeEach(function () { - this.pipe = { - stdout: new EventEmitter(), - stderr: { on: sinon.stub() }, - stdin: { on: sinon.stub() }, - on: sinon.stub(), - pid: 12345, - } - this.child_process.spawn = sinon.stub().returns(this.pipe) - this.pipe.stdout.setEncoding = sinon.stub() - worker = new this.ASpellWorker('en') + this.worker = new this.ASpellWorker('en') }) describe('with normal aspell output', function () { beforeEach(function () { - this.callback = worker.callback = sinon.stub() + this.callback = this.worker.callback = sinon.stub() this.pipe.stdout.emit('data', '& hello\n') this.pipe.stdout.emit('data', '& world\n') this.pipe.stdout.emit('data', 'en\n') @@ -56,7 +50,7 @@ describe('ASpellWorker', function () { describe('with the aspell end marker split across chunks', function () { beforeEach(function () { - this.callback = worker.callback = sinon.stub() + this.callback = this.worker.callback = sinon.stub() this.pipe.stdout.emit('data', '& hello\n') this.pipe.stdout.emit('data', '& world\ne') this.pipe.stdout.emit('data', 'n\n') @@ -73,7 +67,7 @@ describe('ASpellWorker', function () { describe('with the aspell end marker newline split across chunks', function () { beforeEach(function () { - this.callback = worker.callback = sinon.stub() + this.callback = this.worker.callback = sinon.stub() this.pipe.stdout.emit('data', '& hello\n') this.pipe.stdout.emit('data', '& world\n') this.pipe.stdout.emit('data', 'en') @@ -90,7 +84,7 @@ describe('ASpellWorker', function () { describe('with everything split across chunks', function () { beforeEach(function () { - this.callback = worker.callback = sinon.stub() + this.callback = this.worker.callback = sinon.stub() '& hello\n& world\nen\n& goodbye'.split('').forEach(x => { this.pipe.stdout.emit('data', x) }) diff --git a/services/spelling/test/unit/js/SpellingAPIManagerTests.js b/services/spelling/test/unit/js/SpellingAPIManagerTests.js index 47d731c08e..bfee383573 100644 --- a/services/spelling/test/unit/js/SpellingAPIManagerTests.js +++ b/services/spelling/test/unit/js/SpellingAPIManagerTests.js @@ -1,48 +1,38 @@ /* eslint-disable handle-callback-err */ -const sinon = require('sinon') -const { expect } = require('chai') -const SandboxedModule = require('sandboxed-module') -const modulePath = require('path').join( - __dirname, - '../../../app/js/SpellingAPIManager' -) +import sinon from 'sinon' +import { expect } from 'chai' +import esmock from 'esmock' + +const MODULE_PATH = '../../../app/js/SpellingAPIManager' const promiseStub = val => new Promise(resolve => resolve(val)) describe('SpellingAPIManager', function () { - beforeEach(function () { + beforeEach(async function () { this.token = 'user-id-123' - this.ASpell = {} - this.SpellingAPIManager = SandboxedModule.require(modulePath, { - requires: { - './ASpell': this.ASpell, - '@overleaf/settings': { ignoredMisspellings: ['ShareLaTeX'] }, + this.nonLearnedWords = ['some', 'words', 'htat', 'are', 'speled', 'rong'] + this.allWords = this.nonLearnedWords + this.misspellings = [ + { index: 2, suggestions: ['that'] }, + { index: 4, suggestions: ['spelled'] }, + { index: 5, suggestions: ['wrong', 'ring'] }, + ] + this.misspellingsWithoutLearnedWords = this.misspellings.slice(0, 3) + this.ASpell = { + checkWords: sinon.stub().yields(null, this.misspellings), + promises: { + checkWords: sinon.stub().returns(promiseStub(this.misspellings)), }, + } + this.SpellingAPIManager = await esmock(MODULE_PATH, { + '../../../app/js/ASpell.js': this.ASpell, + '@overleaf/settings': { ignoredMisspellings: ['ShareLaTeX'] }, }) }) describe('runRequest', function () { - beforeEach(function () { - this.nonLearnedWords = ['some', 'words', 'htat', 'are', 'speled', 'rong'] - this.allWords = this.nonLearnedWords - this.misspellings = [ - { index: 2, suggestions: ['that'] }, - { index: 4, suggestions: ['spelled'] }, - { index: 5, suggestions: ['wrong', 'ring'] }, - ] - this.misspellingsWithoutLearnedWords = this.misspellings.slice(0, 3) - - this.ASpell.checkWords = (lang, word, callback) => { - callback(null, this.misspellings) - } - this.ASpell.promises = { - checkWords: sinon.stub().returns(promiseStub(this.misspellings)), - } - sinon.spy(this.ASpell, 'checkWords') - }) - describe('with sensible JSON', function () { beforeEach(function (done) { this.SpellingAPIManager.runRequest(