mirror of
https://github.com/overleaf/overleaf.git
synced 2025-04-09 11:35:47 +00:00
Merge pull request #10938 from overleaf/em-esm-spelling
Migrate spelling to ES modules GitOrigin-RevId: 4a200c8d1c28be44027cc8a3097e42575ab6593f
This commit is contained in:
parent
77c0802035
commit
f6c1e2738d
21 changed files with 263 additions and 312 deletions
4
package-lock.json
generated
4
package-lock.json
generated
|
@ -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"
|
||||
},
|
||||
|
|
|
@ -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
|
||||
})
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
26
services/spelling/app/js/server.js
Normal file
26
services/spelling/app/js/server.js
Normal file
|
@ -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)
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 () {
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
|
|
|
@ -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 () {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 },
|
||||
})
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
|
|
|
@ -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(
|
||||
|
|
Loading…
Add table
Reference in a new issue