Эх сурвалжийг харах

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

Add Dictionary Size Limit

GitOrigin-RevId: f3b8be11de5a1480c8bc1a7fe26e9d67bd047757
Timothée Alby 4 жил өмнө
parent
commit
1b4d675b0a

+ 37 - 12
services/web/app/src/Features/Spelling/LearnedWordsManager.js

@@ -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)
   },

+ 2 - 0
services/web/config/settings.defaults.js

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

+ 56 - 20
services/web/test/unit/src/Spelling/LearnedWordsManagerTests.js

@@ -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 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 call the callback', function () {
-      expect(this.callback.called).to.equal(true)
+    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 () {
     beforeEach(function () {
       this.db.spellingPreferences.deleteOne = sinon.stub().callsArgWith(1)