Add endpoint to remove learned words

This commit is contained in:
Simon Detheridge 2019-07-20 14:04:08 +01:00
parent 134ff0b29d
commit e7ab9f79a9
7 changed files with 157 additions and 2 deletions

View file

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

View file

@ -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) { getLearnedWords(userToken, callback) {
if (callback == null) { if (callback == null) {
callback = () => {} callback = () => {}
@ -61,6 +77,7 @@ const LearnedWordsManager = {
const promises = { const promises = {
learnWord: promisify(LearnedWordsManager.learnWord), learnWord: promisify(LearnedWordsManager.learnWord),
unlearnWord: promisify(LearnedWordsManager.unlearnWord),
getLearnedWords: promisify(LearnedWordsManager.getLearnedWords), getLearnedWords: promisify(LearnedWordsManager.getLearnedWords),
deleteUsersLearnedWords: promisify( deleteUsersLearnedWords: promisify(
LearnedWordsManager.deleteUsersLearnedWords LearnedWordsManager.deleteUsersLearnedWords
@ -70,7 +87,7 @@ const promises = {
LearnedWordsManager.promises = promises LearnedWordsManager.promises = promises
module.exports = LearnedWordsManager module.exports = LearnedWordsManager
;['learnWord', 'getLearnedWords'].map(method => ;['learnWord', 'unlearnWord', 'getLearnedWords'].map(method =>
metrics.timeAsyncMethod( metrics.timeAsyncMethod(
LearnedWordsManager, LearnedWordsManager,
method, method,

View file

@ -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) { deleteDic(req, res, next) {
const { token, word } = extractLearnRequestData(req) const { token, word } = extractLearnRequestData(req)
logger.log({ token, word }, 'deleting user dictionary') logger.log({ token, word }, 'deleting user dictionary')

View file

@ -30,6 +30,20 @@ const SpellingAPIManager = {
return LearnedWordsManager.learnWord(token, request.word, callback) 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) { deleteDic(token, callback) {
return LearnedWordsManager.deleteUsersLearnedWords(token, callback) return LearnedWordsManager.deleteUsersLearnedWords(token, callback)
}, },

View file

@ -19,6 +19,14 @@ const learnWord = word =>
}) })
}) })
const unlearnWord = word =>
request.post({
url: `/user/${USER_ID}/unlearn`,
body: JSON.stringify({
word
})
})
const deleteDict = () => const deleteDict = () =>
request.del({ request.del({
url: `/user/${USER_ID}` url: `/user/${USER_ID}`
@ -51,3 +59,24 @@ describe('learning words', () => {
expect(responseBody.misspellings.length).to.equals(1) 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)
})
})

View file

@ -24,7 +24,7 @@ describe('LearnedWordsManager', function() {
this.callback = sinon.stub() this.callback = sinon.stub()
this.db = { this.db = {
spellingPreferences: { spellingPreferences: {
update: sinon.stub().callsArg(3) update: sinon.stub().yields()
} }
} }
this.cache = { 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() { describe('getLearnedWords', function() {
beforeEach(function() { beforeEach(function() {
this.wordList = ['apples', 'bananas', 'pears'] this.wordList = ['apples', 'bananas', 'pears']

View file

@ -21,6 +21,7 @@ describe('SpellingAPIManager', function() {
this.LearnedWordsManager = { this.LearnedWordsManager = {
getLearnedWords: sinon.stub().callsArgWith(1, null, this.learnedWords), getLearnedWords: sinon.stub().callsArgWith(1, null, this.learnedWords),
learnWord: sinon.stub().callsArg(2), learnWord: sinon.stub().callsArg(2),
unlearnWord: sinon.stub().callsArg(2),
promises: { promises: {
getLearnedWords: sinon.stub().returns(promiseStub(this.learnedWords)) 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)
})
})
})
}) })