Merge pull request #6690 from overleaf/ta-max-dictionary-size

Add Dictionary Size Limit

GitOrigin-RevId: f3b8be11de5a1480c8bc1a7fe26e9d67bd047757
This commit is contained in:
Timothée Alby 2022-02-10 10:52:31 +01:00 committed by Copybot
parent 01f63e810a
commit 1b4d675b0a
3 changed files with 95 additions and 32 deletions

View file

@ -3,10 +3,20 @@ const logger = require('@overleaf/logger')
const metrics = require('@overleaf/metrics') const metrics = require('@overleaf/metrics')
const { promisify } = require('util') const { promisify } = require('util')
const OError = require('@overleaf/o-error') const OError = require('@overleaf/o-error')
const Settings = require('@overleaf/settings')
const { InvalidError } = require('../Errors/Errors')
const LearnedWordsManager = { const LearnedWordsManager = {
learnWord(userToken, word, callback) { learnWord(userToken, word, callback) {
return db.spellingPreferences.updateOne( LearnedWordsManager.getLearnedWordsSize(userToken, (error, wordsSize) => {
if (error != null) {
return callback(OError.tag(error))
}
const wordSize = Buffer.from(word).length
if (wordsSize + wordSize > Settings.maxDictionarySize) {
return callback(new InvalidError('Max dictionary size reached'))
}
db.spellingPreferences.updateOne(
{ {
token: userToken, token: userToken,
}, },
@ -18,6 +28,7 @@ const LearnedWordsManager = {
}, },
callback callback
) )
})
}, },
unlearnWord(userToken, word, callback) { unlearnWord(userToken, word, callback) {
@ -52,6 +63,20 @@ const LearnedWordsManager = {
) )
}, },
getLearnedWordsSize(userToken, callback) {
db.spellingPreferences.findOne(
{ token: userToken },
function (error, preferences) {
if (error != null) {
return callback(OError.tag(error))
}
const words = (preferences && preferences.learnedWords) || []
const wordsSize = Buffer.from(JSON.stringify(words)).length
callback(null, wordsSize)
}
)
},
deleteUsersLearnedWords(userToken, callback) { deleteUsersLearnedWords(userToken, callback) {
db.spellingPreferences.deleteOne({ token: userToken }, callback) db.spellingPreferences.deleteOne({ token: userToken }, callback)
}, },

View file

@ -423,6 +423,8 @@ module.exports = {
'zh-CN': '简体中文', 'zh-CN': '简体中文',
}, },
maxDictionarySize: 1024 * 1024, // 1 MB
// Password Settings // Password Settings
// ----------- // -----------
// These restrict the passwords users can use when registering // These restrict the passwords users can use when registering

View file

@ -5,6 +5,7 @@ const modulePath = require('path').join(
__dirname, __dirname,
'/../../../../app/src/Features/Spelling/LearnedWordsManager' '/../../../../app/src/Features/Spelling/LearnedWordsManager'
) )
const { InvalidError } = require('../../../../app/src/Features/Errors/Errors')
describe('LearnedWordsManager', function () { describe('LearnedWordsManager', function () {
beforeEach(function () { beforeEach(function () {
@ -13,6 +14,7 @@ describe('LearnedWordsManager', function () {
this.db = { this.db = {
spellingPreferences: { spellingPreferences: {
updateOne: sinon.stub().yields(), updateOne: sinon.stub().yields(),
findOne: sinon.stub().yields(null, ['pear']),
}, },
} }
this.LearnedWordsManager = SandboxedModule.require(modulePath, { this.LearnedWordsManager = SandboxedModule.require(modulePath, {
@ -22,11 +24,15 @@ describe('LearnedWordsManager', function () {
timeAsyncMethod: sinon.stub(), timeAsyncMethod: sinon.stub(),
inc: sinon.stub(), inc: sinon.stub(),
}, },
'@overleaf/settings': {
maxDictionarySize: 20,
},
}, },
}) })
}) })
describe('learnWord', function () { describe('learnWord', function () {
describe('under size limit', function () {
beforeEach(function () { beforeEach(function () {
this.word = 'instanton' this.word = 'instanton'
this.LearnedWordsManager.learnWord(this.token, this.word, this.callback) this.LearnedWordsManager.learnWord(this.token, this.word, this.callback)
@ -48,8 +54,26 @@ describe('LearnedWordsManager', function () {
).to.equal(true) ).to.equal(true)
}) })
it('should call the callback', function () { it('should call the callback without error', function () {
expect(this.callback.called).to.equal(true) sinon.assert.called(this.callback)
expect(this.callback.lastCall.args.length).to.equal(0)
})
})
describe('over size limit', function () {
beforeEach(function () {
this.word = 'superlongwordthatwillgobeyondthelimit'
this.LearnedWordsManager.learnWord(this.token, this.word, this.callback)
})
it('should not insert the word in the word list in the database', function () {
expect(this.db.spellingPreferences.updateOne.notCalled).to.equal(true)
})
it('should call the callback with error', function () {
sinon.assert.called(this.callback)
expect(this.callback.lastCall.args[0]).to.be.instanceof(InvalidError)
})
}) })
}) })
@ -100,6 +124,18 @@ describe('LearnedWordsManager', function () {
}) })
}) })
describe('getLearnedWordsSize', function () {
it('should return the word list size in the callback', function () {
this.db.spellingPreferences.findOne = (conditions, callback) => {
callback(null, {
learnedWords: ['apples', 'bananas', 'pears', 'bananas'],
})
}
this.LearnedWordsManager.getLearnedWordsSize(this.token, this.callback)
sinon.assert.calledWith(this.callback, null, 38)
})
})
describe('deleteUsersLearnedWords', function () { describe('deleteUsersLearnedWords', function () {
beforeEach(function () { beforeEach(function () {
this.db.spellingPreferences.deleteOne = sinon.stub().callsArgWith(1) this.db.spellingPreferences.deleteOne = sinon.stub().callsArgWith(1)