codecombat/spec/server/functional/article.spec.coffee
2016-03-18 17:05:21 -07:00

679 lines
No EOL
27 KiB
CoffeeScript

require '../common'
utils = require '../utils'
_ = require 'lodash'
Promise = require 'bluebird'
requestAsync = Promise.promisify(request, {multiArgs: true})
describe 'GET /db/article', ->
articleData1 = { name: 'Article 1', body: 'Article 1 body cow', i18nCoverage: [] }
articleData2 = { name: 'Article 2', body: 'Article 2 body moo' }
beforeEach utils.wrap (done) ->
yield utils.clearModels([Article])
@admin = yield utils.initAdmin()
yield utils.loginUser(@admin)
yield request.postAsync(getURL('/db/article'), { json: articleData1 })
yield request.postAsync(getURL('/db/article'), { json: articleData2 })
yield utils.logout()
done()
it 'returns an array of Article objects', utils.wrap (done) ->
[res, body] = yield request.getAsync { uri: getURL('/db/article'), json: true }
expect(body.length).toBe(2)
done()
it 'accepts a limit parameter', utils.wrap (done) ->
[res, body] = yield request.getAsync {uri: getURL('/db/article?limit=1'), json: true}
expect(body.length).toBe(1)
done()
it 'returns 422 for an invalid limit parameter', utils.wrap (done) ->
[res, body] = yield request.getAsync {uri: getURL('/db/article?limit=word'), json: true}
expect(res.statusCode).toBe(422)
done()
it 'accepts a skip parameter', utils.wrap (done) ->
[res, body] = yield request.getAsync {uri: getURL('/db/article?skip=1'), json: true}
expect(body.length).toBe(1)
[res, body] = yield request.getAsync {uri: getURL('/db/article?skip=2'), json: true}
expect(body.length).toBe(0)
done()
it 'returns 422 for an invalid skip parameter', utils.wrap (done) ->
[res, body] = yield request.getAsync {uri: getURL('/db/article?skip=???'), json: true}
expect(res.statusCode).toBe(422)
done()
it 'accepts a custom project parameter', utils.wrap (done) ->
[res, body] = yield request.getAsync {uri: getURL('/db/article?project=name,body'), json: true}
expect(body.length).toBe(2)
for doc in body
expect(_.size(_.xor(_.keys(doc), ['_id', 'name', 'body']))).toBe(0)
done()
it 'returns a default projection if project is "true"', utils.wrap (done) ->
[res, body] = yield request.getAsync {uri: getURL('/db/article?project=true'), json: true}
expect(res.statusCode).toBe(200)
expect(body.length).toBe(2)
expect(body[0].body).toBeUndefined()
expect(body[0].version).toBeDefined()
done()
it 'accepts custom filter parameters', utils.wrap (done) ->
yield utils.loginUser(@admin)
[res, body] = yield request.getAsync {uri: getURL('/db/article?filter[slug]="article-1"'), json: true}
expect(body.length).toBe(1)
done()
it 'ignores custom filter parameters for non-admins', utils.wrap (done) ->
user = yield utils.initUser()
yield utils.loginUser(user)
[res, body] = yield request.getAsync {uri: getURL('/db/article?filter[slug]="article-1"'), json: true}
expect(body.length).toBe(2)
done()
it 'accepts custom condition parameters', utils.wrap (done) ->
yield utils.loginUser(@admin)
[res, body] = yield request.getAsync {uri: getURL('/db/article?conditions[select]="slug body"'), json: true}
expect(body.length).toBe(2)
for doc in body
expect(_.size(_.xor(_.keys(doc), ['_id', 'slug', 'body']))).toBe(0)
done()
it 'ignores custom condition parameters for non-admins', utils.wrap (done) ->
user = yield utils.initUser()
yield utils.loginUser(user)
[res, body] = yield request.getAsync {uri: getURL('/db/article?conditions[select]="slug body"'), json: true}
expect(body.length).toBe(2)
for doc in body
expect(doc.name).toBeDefined()
done()
it 'allows non-admins to view by i18n-coverage', utils.wrap (done) ->
[res, body] = yield request.getAsync {uri: getURL('/db/article?view=i18n-coverage'), json: true}
expect(body.length).toBe(1)
expect(body[0].slug).toBe('article-1')
done()
it 'allows non-admins to search by text', utils.wrap (done) ->
[res, body] = yield request.getAsync {uri: getURL('/db/article?term=moo'), json: true}
expect(body.length).toBe(1)
expect(body[0].slug).toBe('article-2')
done()
describe 'POST /db/article', ->
articleData = { name: 'Article', body: 'Article', otherProp: 'not getting set' }
beforeEach utils.wrap (done) ->
yield utils.clearModels([Article])
@admin = yield utils.initAdmin({})
yield utils.loginUser(@admin)
[@res, @body] = yield request.postAsync {
uri: getURL('/db/article'), json: articleData
}
done()
it 'creates a new Article, returning 201', utils.wrap (done) ->
expect(@res.statusCode).toBe(201)
article = yield Article.findById(@body._id).exec()
expect(article).toBeDefined()
done()
it 'sets creator to the user who created it', ->
expect(@res.body.creator).toBe(@admin.id)
it 'sets original to _id', ->
body = @res.body
expect(body.original).toBe(body._id)
it 'returns 422 when no input is provided', utils.wrap (done) ->
[res, body] = yield request.postAsync { uri: getURL('/db/article') }
expect(res.statusCode).toBe(422)
done()
it 'allows you to set Article\'s editableProperties', ->
expect(@body.name).toBe('Article')
it 'ignores properties not included in editableProperties', ->
expect(@body.otherProp).toBeUndefined()
it 'returns 422 when properties do not pass validation', utils.wrap (done) ->
[res, body] = yield request.postAsync {
uri: getURL('/db/article'), json: { i18nCoverage: 9001 }
}
expect(res.statusCode).toBe(422)
expect(body.validationErrors).toBeDefined()
done()
it 'allows admins to create Articles', -> # handled in beforeEach
it 'allows artisans to create Articles', utils.wrap (done) ->
yield utils.clearModels([Article])
artisan = yield utils.initArtisan({})
yield utils.loginUser(artisan)
[res, body] = yield request.postAsync({uri: getURL('/db/article'), json: articleData })
expect(res.statusCode).toBe(201)
done()
it 'does not allow normal users to create Articles', utils.wrap (done) ->
yield utils.clearModels([Article])
user = yield utils.initUser({})
yield utils.loginUser(user)
[res, body] = yield request.postAsync({uri: getURL('/db/article'), json: articleData })
expect(res.statusCode).toBe(403)
done()
it 'does not allow anonymous users to create Articles', utils.wrap (done) ->
yield utils.clearModels([Article])
yield utils.logout()
[res, body] = yield request.postAsync({uri: getURL('/db/article'), json: articleData })
expect(res.statusCode).toBe(401)
done()
it 'does not allow creating Articles with reserved words', utils.wrap (done) ->
[res, body] = yield request.postAsync { uri: getURL('/db/article'), json: { name: 'Names' } }
expect(res.statusCode).toBe(422)
done()
it 'does not allow creating a second article of the same name', utils.wrap (done) ->
[res, body] = yield request.postAsync { uri: getURL('/db/article'), json: articleData }
expect(res.statusCode).toBe(409)
done()
describe 'GET /db/article/:handle', ->
articleData = { name: 'Some Name', body: 'Article' }
beforeEach utils.wrap (done) ->
yield utils.clearModels([Article])
@admin = yield utils.initAdmin({})
yield utils.loginUser(@admin)
[@res, @body] = yield request.postAsync {
uri: getURL('/db/article'), json: articleData
}
done()
it 'returns Article by id', utils.wrap (done) ->
[res, body] = yield request.getAsync {uri: getURL("/db/article/#{@body._id}"), json: true}
expect(res.statusCode).toBe(200)
expect(_.isObject(body)).toBe(true)
done()
it 'returns Article by slug', utils.wrap (done) ->
[res, body] = yield request.getAsync {uri: getURL("/db/article/some-name"), json: true}
expect(res.statusCode).toBe(200)
expect(_.isObject(body)).toBe(true)
done()
it 'returns not found if handle does not exist in the db', utils.wrap (done) ->
[res, body] = yield request.getAsync {uri: getURL("/db/article/dne"), json: true}
expect(res.statusCode).toBe(404)
done()
putTests = (method='PUT') ->
articleData = { name: 'Some Name', body: 'Article' }
beforeEach utils.wrap (done) ->
yield utils.clearModels([Article])
@admin = yield utils.initAdmin({})
yield utils.loginUser(@admin)
[@res, @body] = yield request.postAsync {
uri: getURL('/db/article'), json: articleData
}
done()
it 'edits editable Article properties', utils.wrap (done) ->
[res, body] = yield requestAsync {method: method, uri: getURL("/db/article/#{@body._id}"), json: { body: 'New body' }}
expect(body.body).toBe('New body')
done()
it 'updates the slug when the name is changed', utils.wrap (done) ->
[res, body] = yield requestAsync {method: method, uri: getURL("/db/article/#{@body._id}"), json: json = { name: 'New name' }}
expect(body.name).toBe('New name')
expect(body.slug).toBe('new-name')
done()
it 'does not allow normal artisan, non-admins to make changes', utils.wrap (done) ->
artisan = yield utils.initArtisan({})
yield utils.loginUser(artisan)
[res, body] = yield requestAsync {method: method, uri: getURL("/db/article/#{@body._id}"), json: { name: 'Another name' }}
expect(res.statusCode).toBe(403)
done()
describe 'PUT /db/article/:handle', -> putTests('PUT')
describe 'PATCH /db/article/:handle', -> putTests('PATCH')
describe 'POST /db/article/:handle/new-version', ->
articleData = { name: 'Article name', body: 'Article body', i18n: {} }
articleID = null
beforeEach utils.wrap (done) ->
yield utils.clearModels([Article])
@admin = yield utils.initAdmin({})
yield utils.loginUser(@admin)
[res, body] = yield request.postAsync { uri: getURL('/db/article'), json: articleData }
expect(res.statusCode).toBe(201)
articleID = body._id
done()
postNewVersion = Promise.promisify (json, expectedStatus=201, done) ->
if _.isFunction(expectedStatus)
done = expectedStatus
expectedStatus = 201
url = getURL("/db/article/#{articleID}/new-version")
request.post { uri: url, json: json }, (err, res, body) ->
expect(res.statusCode).toBe(expectedStatus)
done(err)
testArrayEqual = (given, expected) ->
expect(_.isEqual(given, expected)).toBe(true)
it 'creates a new major version, updating model and version properties', utils.wrap (done) ->
yield postNewVersion({ name: 'Article name', body: 'New body' })
yield postNewVersion({ name: 'New name', body: 'New new body' })
articles = yield Article.find()
expect(articles.length).toBe(3)
versions = (article.get('version') for article in articles)
articles = (article.toObject() for article in articles)
testArrayEqual(_.pluck(versions, 'major'), [0, 1, 2])
testArrayEqual(_.pluck(versions, 'minor'), [0, 0, 0])
testArrayEqual(_.pluck(versions, 'isLatestMajor'), [false, false, true])
testArrayEqual(_.pluck(versions, 'isLatestMinor'), [true, true, true])
testArrayEqual(_.pluck(articles, 'name'), ['Article name', 'Article name', 'New name'])
testArrayEqual(_.pluck(articles, 'body'), ['Article body', 'New body', 'New new body'])
testArrayEqual(_.pluck(articles, 'slug'), [undefined, undefined, 'new-name'])
testArrayEqual(_.pluck(articles, 'index'), [undefined, undefined, true])
done()
it 'works if there is no document with the appropriate version settings (new major)', utils.wrap (done) ->
article = yield Article.findById(articleID)
article.set({ 'version.isLatestMajor': false, 'version.isLatestMinor': false })
yield article.save()
yield postNewVersion({ name: 'Article name', body: 'New body' })
articles = yield Article.find()
expect(articles.length).toBe(2)
versions = (article.get('version') for article in articles)
articles = (article.toObject() for article in articles)
testArrayEqual(_.pluck(versions, 'major'), [0, 1])
testArrayEqual(_.pluck(versions, 'minor'), [0, 0])
testArrayEqual(_.pluck(versions, 'isLatestMajor'), [false, true])
testArrayEqual(_.pluck(versions, 'isLatestMinor'), [false, true]) # does not fix the old version's value
testArrayEqual(_.pluck(articles, 'body'), ['Article body', 'New body'])
testArrayEqual(_.pluck(articles, 'slug'), [undefined, 'article-name'])
testArrayEqual(_.pluck(articles, 'index'), [undefined, true])
done()
it 'creates a new minor version if version.major is included', utils.wrap (done) ->
yield postNewVersion({ name: 'Article name', body: 'New body', version: { major: 0 } })
yield postNewVersion({ name: 'Article name', body: 'New new body', version: { major: 0 } })
articles = yield Article.find()
expect(articles.length).toBe(3)
versions = (article.get('version') for article in articles)
articles = (article.toObject() for article in articles)
testArrayEqual(_.pluck(versions, 'major'), [0, 0, 0])
testArrayEqual(_.pluck(versions, 'minor'), [0, 1, 2])
testArrayEqual(_.pluck(versions, 'isLatestMajor'), [false, false, true])
testArrayEqual(_.pluck(versions, 'isLatestMinor'), [false, false, true])
testArrayEqual(_.pluck(articles, 'name'), ['Article name', 'Article name', 'Article name'])
testArrayEqual(_.pluck(articles, 'body'), ['Article body', 'New body', 'New new body'])
testArrayEqual(_.pluck(articles, 'slug'), [undefined, undefined, 'article-name'])
testArrayEqual(_.pluck(articles, 'index'), [undefined, undefined, true])
done()
it 'works if there is no document with the appropriate version settings (new minor)', utils.wrap (done) ->
article = yield Article.findById(articleID)
article.set({ 'version.isLatestMajor': false, 'version.isLatestMinor': false })
yield article.save()
yield postNewVersion({ name: 'Article name', body: 'New body', version: { major: 0 } })
articles = yield Article.find()
expect(articles.length).toBe(2)
versions = (article.get('version') for article in articles)
articles = (article.toObject() for article in articles)
testArrayEqual(_.pluck(versions, 'major'), [0, 0])
testArrayEqual(_.pluck(versions, 'minor'), [0, 1])
testArrayEqual(_.pluck(versions, 'isLatestMajor'), [false, false])
testArrayEqual(_.pluck(versions, 'isLatestMinor'), [false, true])
testArrayEqual(_.pluck(articles, 'body'), ['Article body', 'New body'])
testArrayEqual(_.pluck(articles, 'slug'), [undefined, 'article-name'])
testArrayEqual(_.pluck(articles, 'index'), [undefined, true])
done()
it 'allows adding new minor versions to old major versions', utils.wrap (done) ->
yield postNewVersion({ name: 'Article name', body: 'New body' })
yield postNewVersion({ name: 'Article name', body: 'New new body', version: { major: 0 } })
articles = yield Article.find()
expect(articles.length).toBe(3)
versions = (article.get('version') for article in articles)
articles = (article.toObject() for article in articles)
testArrayEqual(_.pluck(versions, 'major'), [0, 1, 0])
testArrayEqual(_.pluck(versions, 'minor'), [0, 0, 1])
testArrayEqual(_.pluck(versions, 'isLatestMajor'), [false, true, false])
testArrayEqual(_.pluck(versions, 'isLatestMinor'), [false, true, true])
testArrayEqual(_.pluck(articles, 'name'), ['Article name', 'Article name', 'Article name'])
testArrayEqual(_.pluck(articles, 'body'), ['Article body', 'New body', 'New new body'])
testArrayEqual(_.pluck(articles, 'slug'), [undefined, 'article-name', undefined])
testArrayEqual(_.pluck(articles, 'index'), [undefined, true, undefined])
done()
it 'unsets properties which are not included in the request', utils.wrap (done) ->
yield postNewVersion({ name: 'Article name', version: { major: 0 } })
articles = yield Article.find()
expect(articles.length).toBe(2)
expect(articles[1].get('body')).toBeUndefined()
done()
it 'works for artisans', utils.wrap (done) ->
yield utils.logout()
artisan = yield utils.initArtisan()
yield utils.loginUser(artisan)
yield postNewVersion({ name: 'Article name', body: 'New body' })
articles = yield Article.find()
expect(articles.length).toBe(2)
done()
it 'works for normal users submitting translations', utils.wrap (done) ->
yield utils.logout()
user = yield utils.initUser()
yield utils.loginUser(user)
yield postNewVersion({ name: 'Article name', body: 'Article body', i18n: { fr: { name: 'Le Article' }}}, 201)
articles = yield Article.find()
expect(articles.length).toBe(2)
done()
it 'does not work for normal users', utils.wrap (done) ->
yield utils.logout()
user = yield utils.initUser()
yield utils.loginUser(user)
yield postNewVersion({ name: 'Article name', body: 'New body' }, 403)
articles = yield Article.find()
expect(articles.length).toBe(1)
done()
it 'does not work for anonymous users', utils.wrap (done) ->
yield utils.logout()
yield postNewVersion({ name: 'Article name', body: 'New body' }, 401)
articles = yield Article.find()
expect(articles.length).toBe(1)
done()
it 'notifies watchers of changes', utils.wrap (done) ->
sendwithus = require '../../../server/sendwithus'
spyOn(sendwithus.api, 'send').and.callFake (context, cb) ->
expect(context.email_id).toBe(sendwithus.templates.change_made_notify_watcher)
expect(context.recipient.address).toBe('test@gmail.com')
done()
user = yield User({email: 'test@gmail.com', name: 'a user'}).save()
article = yield Article.findById(articleID)
article.set('watchers', article.get('watchers').concat([user.get('_id')]))
yield article.save()
yield postNewVersion({ name: 'Article name', body: 'New body', commitMessage: 'Commit message' })
it 'sends a notification to artisan and main Slack channels', utils.wrap (done) ->
slack = require '../../../server/slack'
spyOn(slack, 'sendSlackMessage')
yield postNewVersion({ name: 'Article name', body: 'New body' })
expect(slack.sendSlackMessage).toHaveBeenCalled()
done()
describe 'version fetching endpoints', ->
articleData = { name: 'Original version', body: 'Article body' }
articleOriginal = null
postNewVersion = Promise.promisify (json, expectedStatus=201, done) ->
if _.isFunction(expectedStatus)
done = expectedStatus
expectedStatus = 201
url = getURL("/db/article/#{articleOriginal}/new-version")
request.post { uri: url, json: json }, (err, res) ->
expect(res.statusCode).toBe(expectedStatus)
done(err)
beforeEach utils.wrap (done) ->
yield utils.clearModels([Article])
@admin = yield utils.initAdmin({})
yield utils.loginUser(@admin)
[res, body] = yield request.postAsync { uri: getURL('/db/article'), json: articleData }
expect(res.statusCode).toBe(201)
articleOriginal = body._id
yield postNewVersion({ name: 'Latest minor version', body: 'New body', version: {major: 0} })
yield postNewVersion({ name: 'Latest major version', body: 'New new body' })
done()
describe 'GET /db/article/:handle/version/:version', ->
it 'returns the latest version for the given original article when :version is empty', utils.wrap (done) ->
[res, body] = yield request.getAsync { uri: getURL("/db/article/#{articleOriginal}/version"), json: true }
expect(body.name).toBe('Latest major version')
done()
it 'returns the latest of a given major version when :version is X', utils.wrap (done) ->
[res, body] = yield request.getAsync { uri: getURL("/db/article/#{articleOriginal}/version/0"), json: true }
expect(body.name).toBe('Latest minor version')
done()
it 'returns a specific version when :version is X.Y', utils.wrap (done) ->
[res, body] = yield request.getAsync { uri: getURL("/db/article/#{articleOriginal}/version/0.0"), json: true }
expect(body.name).toBe('Original version')
done()
it 'returns 422 when the original value is invalid', utils.wrap (done) ->
[res, body] = yield request.getAsync { uri: getURL('/db/article/dne/version'), json: true }
expect(res.statusCode).toBe(422)
done()
it 'returns 404 when the original value cannot be found', utils.wrap (done) ->
[res, body] = yield request.getAsync { uri: getURL('/db/article/012345678901234567890123/version'), json: true }
expect(res.statusCode).toBe(404)
done()
describe 'GET /db/article/:handle/versions', ->
it 'returns an array of versions sorted by creation for the given original article', utils.wrap (done) ->
[res, body] = yield request.getAsync { uri: getURL("/db/article/#{articleOriginal}/versions"), json: true }
expect(body.length).toBe(3)
expect(body[0].name).toBe('Latest major version')
expect(body[1].name).toBe('Latest minor version')
expect(body[2].name).toBe('Original version')
done()
it 'projects most properties by default', utils.wrap (done) ->
[res, body] = yield request.getAsync { uri: getURL("/db/article/#{articleOriginal}/versions"), json: true }
expect(body[0].body).toBeUndefined()
done()
describe 'GET /db/article/:handle/files', ->
it 'returns an array of file metadata for the given original article', utils.wrap (done) ->
yield utils.clearModels([Article])
articleData = { name: 'Article', body: 'Article' }
admin = yield utils.initAdmin({})
yield utils.loginUser(admin)
[res, article] = yield request.postAsync { uri: getURL('/db/article'), json: articleData }
expect(res.statusCode).toBe(201)
[res, body] = yield request.postAsync(getURL('/file'), { json: {
url: getURL('/assets/main.html')
filename: 'test.html'
path: 'db/article/'+article.original
mimetype: 'text/html'
}})
[res, body] = yield request.getAsync(getURL('/db/article/'+article.original+'/files'), {json: true})
expect(body.length).toBe(1)
expect(body[0].filename).toBe('test.html')
expect(body[0].metadata.path).toBe('db/article/'+article.original)
done()
describe 'GET and POST /db/article/:handle/names', ->
articleData1 = { name: 'Article 1', body: 'Article 1 body' }
articleData2 = { name: 'Article 2', body: 'Article 2 body' }
it 'returns an object mapping ids to names', utils.wrap (done) ->
yield utils.clearModels([Article])
admin = yield utils.initAdmin({})
yield utils.loginUser(admin)
[res, article1] = yield request.postAsync(getURL('/db/article'), { json: articleData1 })
[res, article2] = yield request.postAsync(getURL('/db/article'), { json: articleData2 })
yield utils.logout()
[res, body] = yield request.getAsync { uri: getURL('/db/article/names?ids='+[article1._id, article2._id].join(',')), json: true }
expect(body.length).toBe(2)
expect(body[0].name).toBe('Article 1')
[res, body] = yield request.postAsync { uri: getURL('/db/article/names?ids='+[article1._id, article2._id].join(',')), json: true }
expect(body.length).toBe(2)
expect(body[0].name).toBe('Article 1')
done()
describe 'GET /db/article/:handle/patches', ->
it 'returns pending patches for the given original article', utils.wrap (done) ->
yield utils.clearModels([Article])
articleData = { name: 'Article', body: 'Article' }
admin = yield utils.initAdmin({})
yield utils.loginUser(admin)
[res, article] = yield request.postAsync { uri: getURL('/db/article'), json: articleData }
expect(res.statusCode).toBe(201)
[res, patch] = yield request.postAsync { uri: getURL('/db/patch'), json: {
delta: []
commitMessage: 'Test commit'
target: {
collection: 'article'
id: article._id
}
}}
[res, patches] = yield request.getAsync getURL("/db/article/#{article._id}/patches"), { json: true }
expect(res.statusCode).toBe(200)
expect(patches.length).toBe(1)
expect(patches[0]._id).toBe(patch._id)
done()
it 'returns 422 for invalid object ids', utils.wrap (done) ->
[res, body] = yield request.getAsync getURL("/db/article/invalid/patches"), { json: true }
expect(res.statusCode).toBe(422)
done()
describe 'POST /db/article/:handle/watchers', ->
it 'adds self to the list of watchers, and is idempotent', utils.wrap (done) ->
# create article
yield utils.clearModels([Article])
articleData = { name: 'Article', body: 'Article' }
admin = yield utils.initAdmin({})
yield utils.loginUser(admin)
[res, article] = yield request.postAsync { uri: getURL('/db/article'), json: articleData }
expect(res.statusCode).toBe(201)
# add new user as watcher
yield utils.logout()
user = yield utils.initUser()
yield utils.loginUser(user)
[res, article] = yield request.postAsync { uri: getURL("/db/article/#{article._id}/watchers"), json: true }
expect(res.statusCode).toBe(200)
expect(_.contains(article.watchers, user.id)).toBe(true)
# check idempotence, db
numWatchers = article.watchers.length
[res, article] = yield request.postAsync { uri: getURL("/db/article/#{article._id}/watchers"), json: true }
expect(res.statusCode).toBe(200)
expect(numWatchers).toBe(article.watchers.length)
article = yield Article.findById(article._id)
expect(_.last(article.get('watchers')).toString()).toBe(user.id)
done()
describe 'DELETE /db/article/:handle/watchers', ->
it 'removes self from the list of watchers, and is idempotent', utils.wrap (done) ->
# create article
yield utils.clearModels([Article])
articleData = { name: 'Article', body: 'Article' }
admin = yield utils.initAdmin({})
yield utils.loginUser(admin)
[res, article] = yield request.postAsync { uri: getURL('/db/article'), json: articleData }
expect(res.statusCode).toBe(201)
# add new user as watcher
yield utils.logout()
user = yield utils.initUser()
yield utils.loginUser(user)
[res, article] = yield request.postAsync { uri: getURL("/db/article/#{article._id}/watchers"), json: true }
expect(_.contains(article.watchers, user.id)).toBe(true)
# remove user as watcher
[res, article] = yield request.delAsync { uri: getURL("/db/article/#{article._id}/watchers"), json: true }
expect(res.statusCode).toBe(200)
expect(_.contains(article.watchers, user.id)).toBe(false)
# check idempotence, db
numWatchers = article.watchers.length
[res, article] = yield request.delAsync { uri: getURL("/db/article/#{article._id}/watchers"), json: true }
expect(res.statusCode).toBe(200)
expect(numWatchers).toBe(article.watchers.length)
article = yield Article.findById(article._id)
ids = (id.toString() for id in article.get('watchers'))
expect(_.contains(ids, user.id)).toBe(false)
done()