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,21 +3,32 @@ const logger = require('@overleaf/logger')
const metrics = require('@overleaf/metrics')
const { promisify } = require('util')
const OError = require('@overleaf/o-error')
const Settings = require('@overleaf/settings')
const { InvalidError } = require('../Errors/Errors')
const LearnedWordsManager = {
learnWord(userToken, word, callback) {
return db.spellingPreferences.updateOne(
{
token: userToken,
},
{
$addToSet: { learnedWords: word },
},
{
upsert: true,
},
callback
)
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,
},
{
$addToSet: { learnedWords: word },
},
{
upsert: true,
},
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) {
db.spellingPreferences.deleteOne({ token: userToken }, callback)
},

View file

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

View file

@ -5,6 +5,7 @@ const modulePath = require('path').join(
__dirname,
'/../../../../app/src/Features/Spelling/LearnedWordsManager'
)
const { InvalidError } = require('../../../../app/src/Features/Errors/Errors')
describe('LearnedWordsManager', function () {
beforeEach(function () {
@ -13,6 +14,7 @@ describe('LearnedWordsManager', function () {
this.db = {
spellingPreferences: {
updateOne: sinon.stub().yields(),
findOne: sinon.stub().yields(null, ['pear']),
},
}
this.LearnedWordsManager = SandboxedModule.require(modulePath, {
@ -22,34 +24,56 @@ describe('LearnedWordsManager', function () {
timeAsyncMethod: sinon.stub(),
inc: sinon.stub(),
},
'@overleaf/settings': {
maxDictionarySize: 20,
},
},
})
})
describe('learnWord', function () {
beforeEach(function () {
this.word = 'instanton'
this.LearnedWordsManager.learnWord(this.token, this.word, this.callback)
describe('under size limit', function () {
beforeEach(function () {
this.word = 'instanton'
this.LearnedWordsManager.learnWord(this.token, this.word, this.callback)
})
it('should insert the word in the word list in the database', function () {
expect(
this.db.spellingPreferences.updateOne.calledWith(
{
token: this.token,
},
{
$addToSet: { learnedWords: this.word },
},
{
upsert: true,
}
)
).to.equal(true)
})
it('should call the callback without error', function () {
sinon.assert.called(this.callback)
expect(this.callback.lastCall.args.length).to.equal(0)
})
})
it('should insert the word in the word list in the database', function () {
expect(
this.db.spellingPreferences.updateOne.calledWith(
{
token: this.token,
},
{
$addToSet: { learnedWords: this.word },
},
{
upsert: true,
}
)
).to.equal(true)
})
describe('over size limit', function () {
beforeEach(function () {
this.word = 'superlongwordthatwillgobeyondthelimit'
this.LearnedWordsManager.learnWord(this.token, this.word, this.callback)
})
it('should call the callback', function () {
expect(this.callback.called).to.equal(true)
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 () {
beforeEach(function () {
this.db.spellingPreferences.deleteOne = sinon.stub().callsArgWith(1)