Просмотр исходного кода

Add endpoint to remove learned words

Simon Detheridge 7 лет назад
Родитель
Сommit
e7ab9f79a9

+ 1 - 0
services/spelling/app.js

@@ -30,6 +30,7 @@ server.del('/user/:user_id', SpellingAPIController.deleteDic)
 server.get('/user/:user_id', SpellingAPIController.getDic)
 server.post('/user/:user_id/check', SpellingAPIController.check)
 server.post('/user/:user_id/learn', SpellingAPIController.learn)
+server.post('/user/:user_id/unlearn', SpellingAPIController.unlearn)
 server.get('/status', (req, res) => res.send({ status: 'spelling api is up' }))
 
 server.get('/health_check', HealthCheckController.healthCheck)

+ 18 - 1
services/spelling/app/js/LearnedWordsManager.js

@@ -24,6 +24,22 @@ const LearnedWordsManager = {
     )
   },
 
+  unlearnWord(userToken, word, callback) {
+    if (callback == null) {
+      callback = () => {}
+    }
+    mongoCache.del(userToken)
+    return db.spellingPreferences.update(
+      {
+        token: userToken
+      },
+      {
+        $pull: { learnedWords: word }
+      },
+      callback
+    )
+  },
+
   getLearnedWords(userToken, callback) {
     if (callback == null) {
       callback = () => {}
@@ -61,6 +77,7 @@ const LearnedWordsManager = {
 
 const promises = {
   learnWord: promisify(LearnedWordsManager.learnWord),
+  unlearnWord: promisify(LearnedWordsManager.unlearnWord),
   getLearnedWords: promisify(LearnedWordsManager.getLearnedWords),
   deleteUsersLearnedWords: promisify(
     LearnedWordsManager.deleteUsersLearnedWords
@@ -70,7 +87,7 @@ const promises = {
 LearnedWordsManager.promises = promises
 
 module.exports = LearnedWordsManager
-;['learnWord', 'getLearnedWords'].map(method =>
+;['learnWord', 'unlearnWord', 'getLearnedWords'].map(method =>
   metrics.timeAsyncMethod(
     LearnedWordsManager,
     method,

+ 13 - 0
services/spelling/app/js/SpellingAPIController.js

@@ -49,6 +49,19 @@ module.exports = {
     })
   },
 
+  unlearn(req, res, next) {
+    metrics.inc('spelling-unlearn', 0.1)
+    const { token, word } = extractLearnRequestData(req)
+    logger.info({ token, word }, 'unlearning word')
+    SpellingAPIManager.unlearnWord(token, req.body, function(error) {
+      if (error != null) {
+        return next(error)
+      }
+      res.sendStatus(200)
+      next()
+    })
+  },
+
   deleteDic(req, res, next) {
     const { token, word } = extractLearnRequestData(req)
     logger.log({ token, word }, 'deleting user dictionary')

+ 14 - 0
services/spelling/app/js/SpellingAPIManager.js

@@ -30,6 +30,20 @@ const SpellingAPIManager = {
     return LearnedWordsManager.learnWord(token, request.word, callback)
   },
 
+  unlearnWord(token, request, callback) {
+    if (callback == null) {
+      callback = () => {}
+    }
+    if (request.word == null) {
+      return callback(new Error('malformed JSON'))
+    }
+    if (token == null) {
+      return callback(new Error('no token provided'))
+    }
+
+    return LearnedWordsManager.unlearnWord(token, request.word, callback)
+  },
+
   deleteDic(token, callback) {
     return LearnedWordsManager.deleteUsersLearnedWords(token, callback)
   },

+ 29 - 0
services/spelling/test/acceptance/js/LearnTest.js

@@ -19,6 +19,14 @@ const learnWord = word =>
     })
   })
 
+const unlearnWord = word =>
+  request.post({
+    url: `/user/${USER_ID}/unlearn`,
+    body: JSON.stringify({
+      word
+    })
+  })
+
 const deleteDict = () =>
   request.del({
     url: `/user/${USER_ID}`
@@ -51,3 +59,24 @@ describe('learning words', () => {
     expect(responseBody.misspellings.length).to.equals(1)
   })
 })
+
+describe('unlearning words', () => {
+  it('should return status 200 when posting a word successfully', async () => {
+    const response = await unlearnWord('anything')
+    expect(response.statusCode).to.equal(200)
+  })
+
+  it('should return misspellings after a word is unlearnt', async () => {
+    await learnWord('abv')
+
+    const response = await checkWord(['abv'])
+    const responseBody = JSON.parse(response.body)
+    expect(responseBody.misspellings.length).to.equals(0)
+
+    await unlearnWord('abv')
+
+    const response2 = await checkWord(['abv'])
+    const responseBody2 = JSON.parse(response2.body)
+    expect(responseBody2.misspellings.length).to.equals(1)
+  })
+})

+ 29 - 1
services/spelling/test/unit/js/LearnedWordsManagerTests.js

@@ -24,7 +24,7 @@ describe('LearnedWordsManager', function() {
     this.callback = sinon.stub()
     this.db = {
       spellingPreferences: {
-        update: sinon.stub().callsArg(3)
+        update: sinon.stub().yields()
       }
     }
     this.cache = {
@@ -80,6 +80,34 @@ describe('LearnedWordsManager', function() {
     })
   })
 
+  describe('unlearnWord', function() {
+    beforeEach(function() {
+      this.word = 'instanton'
+      return this.LearnedWordsManager.unlearnWord(
+        this.token,
+        this.word,
+        this.callback
+      )
+    })
+
+    it('should remove the word from the word list in the database', function() {
+      return expect(
+        this.db.spellingPreferences.update.calledWith(
+          {
+            token: this.token
+          },
+          {
+            $pull: { learnedWords: this.word }
+          }
+        )
+      ).to.equal(true)
+    })
+
+    return it('should call the callback', function() {
+      return expect(this.callback.called).to.equal(true)
+    })
+  })
+
   describe('getLearnedWords', function() {
     beforeEach(function() {
       this.wordList = ['apples', 'bananas', 'pears']

+ 53 - 0
services/spelling/test/unit/js/SpellingAPIManagerTests.js

@@ -21,6 +21,7 @@ describe('SpellingAPIManager', function() {
     this.LearnedWordsManager = {
       getLearnedWords: sinon.stub().callsArgWith(1, null, this.learnedWords),
       learnWord: sinon.stub().callsArg(2),
+      unlearnWord: sinon.stub().callsArg(2),
       promises: {
         getLearnedWords: sinon.stub().returns(promiseStub(this.learnedWords))
       }
@@ -229,4 +230,56 @@ describe('SpellingAPIManager', function() {
       })
     })
   })
+
+  describe('unlearnWord', function() {
+    describe('without a token', function() {
+      beforeEach(function(done) {
+        this.SpellingAPIManager.unlearnWord(null, { word: 'banana' }, error => {
+          this.error = error
+          done()
+        })
+      })
+
+      it('should return an error', function() {
+        expect(this.error).to.exist
+        expect(this.error).to.be.instanceof(Error)
+        expect(this.error.message).to.equal('no token provided')
+      })
+    })
+
+    describe('without a word', function() {
+      beforeEach(function(done) {
+        this.SpellingAPIManager.unlearnWord(this.token, {}, error => {
+          this.error = error
+          done()
+        })
+      })
+
+      it('should return an error', function() {
+        expect(this.error).to.exist
+        expect(this.error).to.be.instanceof(Error)
+        expect(this.error.message).to.equal('malformed JSON')
+      })
+    })
+
+    describe('with a word and a token', function() {
+      beforeEach(function(done) {
+        this.word = 'banana'
+        this.SpellingAPIManager.unlearnWord(
+          this.token,
+          { word: this.word },
+          error => {
+            this.error = error
+            done()
+          }
+        )
+      })
+
+      it('should call LearnedWordsManager.unlearnWord', function() {
+        this.LearnedWordsManager.unlearnWord
+          .calledWith(this.token, this.word)
+          .should.equal(true)
+      })
+    })
+  })
 })