codecombat/spec/server/functional/patch.spec.coffee
Scott Erickson be1c1323ac Patch fixes
* Partially revert GET /db/:collection/:handle/patches, as it returned limited results for versioned docs
* Fix POST /db/:collection/:handle/patch to:
   * normalize diffs based on latest doc version
   * handle empty deltas
   * not swallow thrown errors due to _.curry
   * Set 'target.original' correctly for versioned collections
2016-09-16 16:02:56 -07:00

198 lines
7.7 KiB
CoffeeScript

require '../common'
User = require '../../../server/models/User'
Article = require '../../../server/models/Article'
Patch = require '../../../server/models/Patch'
request = require '../request'
utils = require '../utils'
co = require 'co'
Promise = require 'bluebird'
makeArticle = utils.wrap (done) ->
@creator = yield utils.initAdmin()
yield utils.loginUser(@creator)
@article = yield utils.makeArticle()
@json = {
commitMessage: 'Accept this patch!'
delta: { name: ['test'] }
target:
id: @article.id
collection: 'article'
}
@url = utils.getURL('/db/patch')
@user = yield utils.initUser()
yield utils.loginUser(@user)
done()
describe 'POST /db/patch', ->
beforeEach utils.wrap (done) ->
yield utils.clearModels([User, Patch, Article])
done()
beforeEach makeArticle
it 'allows someone to submit a patch to something they don\'t control', utils.wrap (done) ->
[res, body] = yield request.postAsync { @url, @json }
expect(res.statusCode).toBe(201)
expect(body.target.original).toBe(@article.get('original').toString())
expect(body.target.version.major).toBeDefined()
expect(body.target.version.minor).toBeDefined()
expect(body.status).toBe('pending')
expect(body.created).toBeDefined()
expect(body.creator).toBe(@user.id)
done()
it 'adds a patch to the target document', utils.wrap (done) ->
[res, body] = yield request.postAsync { @url, @json }
article = yield Article.findById(@article.id)
expect(article.get('patches').length).toBe(1)
done()
it 'is always based on the latest document', utils.wrap (done) ->
@json.delta = {i18n: [{de: {name:'German translation'}}]}
[res, body] = yield request.postAsync { @url, @json }
expect(res.statusCode).toBe(201)
expect(res.body.status).toBe('accepted')
[res, body] = yield request.postAsync { @url, @json }
expect(res.statusCode).toBe(422) # should be a no-change
done()
it 'shows up in patch requests', utils.wrap (done) ->
[res, body] = yield request.postAsync { @url, @json }
patchID = res.body._id
url = utils.getURL("/db/article/#{@article.id}/patches")
[res, body] = yield request.getAsync { url, json: true }
expect(res.statusCode).toBe(200)
expect(body.length).toBe(1)
expect(body[0]._id).toBe(patchID)
done()
it 'accepts all patchable collections', utils.wrap (done) ->
admin = yield utils.initAdmin()
yield utils.loginUser(admin)
targets = [
{ collection: 'achievement', modelPromise: utils.makeAchievement() }
{ collection: 'article', modelPromise: utils.makeArticle() }
{ collection: 'campaign', modelPromise: utils.makeCampaign() }
{ collection: 'course', modelPromise: utils.makeCourse() }
{ collection: 'level', modelPromise: utils.makeLevel() }
{ collection: 'level_component', modelPromise: utils.makeLevelComponent() }
{ collection: 'level_system', modelPromise: utils.makeLevelSystem() }
{ collection: 'poll', modelPromise: utils.makePoll() }
{ collection: 'thang_type', modelPromise: utils.makeThangType() }
]
# concisely test everything in parallel
promises = targets.map((target) => co =>
model = yield target.modelPromise
json = {
commitMessage: 'Accept this patch!'
delta: { name: ['test'] }
target:
id: model.id
collection: target.collection
}
[res, body] = yield request.postAsync { @url, json }
expect(res.statusCode).toBe(201)
)
yield promises
count = yield Patch.count()
expect(count).toBe(targets.length) # make sure all patches got created
done()
describe 'PUT /db/:collection/:handle/watch', ->
beforeEach makeArticle
it 'adds the user to the list of watchers idempotently when body is {on: true}', utils.wrap (done) ->
url = getURL("/db/article/#{@article.id}/watch")
[res, body] = yield request.putAsync({url, json: {on: true}})
expect(body.watchers[1]).toBeDefined()
expect(_.last(body.watchers)).toBe(@user.id)
previousWatchers = body.watchers
[res, body] = yield request.putAsync({url, json: {on: true}})
expect(_.isEqual(previousWatchers, body.watchers)).toBe(true)
done()
it 'removes user from the list of watchers when body is {on: false}', utils.wrap (done) ->
url = getURL("/db/article/#{@article.id}/watch")
[res, body] = yield request.putAsync({url, json: {on: true}})
expect(_.contains(body.watchers, @user.id)).toBe(true)
[res, body] = yield request.putAsync({url, json: {on: false}})
expect(_.contains(body.watchers, @user.id)).toBe(false)
done()
describe 'PUT /db/patch/:handle/status', ->
beforeEach makeArticle
beforeEach utils.wrap (done) ->
[res, body] = yield request.postAsync { url: utils.getURL('/db/patch'), @json }
@patchID = body._id
@url = utils.getURL("/db/patch/#{@patchID}/status")
done()
it 'withdraws the submitter\'s patch', utils.wrap (done) ->
[res, body] = yield request.putAsync {@url, json: {status: 'withdrawn'}}
expect(res.statusCode).toBe(200)
expect(body.status).toBe('withdrawn')
yield new Promise((resolve) -> setTimeout(resolve, 50))
article = yield Article.findById(@article.id)
expect(article.get('patches').length).toBe(0)
done()
it 'does not allow the submitter to reject or accept the pull request', utils.wrap (done) ->
[res, body] = yield request.putAsync {@url, json: {status: 'rejected'}}
expect(res.statusCode).toBe(403)
[res, body] = yield request.putAsync {@url, json: {status: 'accepted'}}
expect(res.statusCode).toBe(403)
patch = yield Patch.findById(@patchID)
expect(patch.get('status')).toBe('pending')
done()
it 'allows the recipient to accept or reject the pull request', utils.wrap (done) ->
yield utils.loginUser(@creator)
[res, body] = yield request.putAsync {@url, json: {status: 'rejected'}}
expect(res.statusCode).toBe(200)
patch = yield Patch.findById(@patchID)
expect(patch.get('status')).toBe 'rejected'
[res, body] = yield request.putAsync {@url, json: {status: 'accepted'}}
expect(body.status).toBe('accepted')
expect(body.acceptor).toBe(@creator.id)
done()
it 'keeps track of amount of submitted and accepted patches', utils.wrap (done) ->
yield utils.loginUser(@creator)
[res, body] = yield request.putAsync {@url, json: {status: 'accepted'}}
expect(res.statusCode).toBe(200)
yield new Promise((resolve) -> setTimeout(resolve, 100))
user = yield User.findById(@user.id)
expect(user.get 'stats.patchesSubmitted').toBe 1
expect(user.get 'stats.patchesContributed').toBe 1
expect(user.get 'stats.totalMiscPatches').toBe 1
expect(user.get 'stats.articleMiscPatches').toBe 1
expect(user.get 'stats.totalTranslationPatches').toBeUndefined()
done()
it 'does not allow the recipient to withdraw the pull request', utils.wrap (done) ->
yield utils.loginUser(@creator)
[res, body] = yield request.putAsync {@url, json: {status: 'withdrawn'}}
expect(res.statusCode).toBe(403)
done()
it 'only allows artisans and admins to set patch status for courses', utils.wrap (done) ->
submitter = yield utils.initUser()
course = yield utils.makeCourse()
patch = new Patch({
delta: { name: 'test' }
target: { collection: 'course', id: course._id, original: course._id }
creator: submitter._id
status: 'pending'
commitMessage: '...'
})
yield patch.save()
anotherUser = yield utils.initUser()
yield utils.loginUser(anotherUser)
json = { status: 'rejected' }
[res, body] = yield request.putAsync({ url: utils.getURL("/db/patch/#{patch.id}/status"), json})
expect(res.statusCode).toBe(403)
done()