Covered general patches with tests

This commit is contained in:
Ruben Vereecken 2014-07-23 20:00:28 +02:00
parent 2394bd8129
commit cde87e4fe5
5 changed files with 100 additions and 18 deletions

View file

@ -165,13 +165,33 @@ _.extend UserSchema.properties,
data: c.object {description: 'Cached LinkedIn data slurped from profile.', additionalProperties: true}
points: {type: 'number'}
activity: {type: 'object', description: 'Summary statistics about user activity', additionalProperties: c.activity}
stats: c.object {additionalProperties: true}, # TODO set to false after dev
gamesCompleted: type: 'integer'
articleEdits: type: 'integer'
levelEdits: type: 'integer'
levelSystemEdits: type: 'integer'
levelComponentEdits: type: 'integer'
thangTypeEdits: type: 'integer'
stats: c.object {additionalProperties: false},
gamesCompleted: c.int()
articleEdits: c.int()
levelEdits: c.int()
levelSystemEdits: c.int()
levelComponentEdits: c.int()
thangTypeEdits: c.int()
'stats.patchesSubmitted': c.int
description: 'Amount of patches submitted, not necessarily accepted'
'stats.patchesContributed': c.int
description: 'Amount of patches submitted and accepted'
'stats.patchesAccepted': c.int
description: 'Amount of patches accepted by the user as owner'
# The below patches only apply to those that actually got accepted
'stats.totalTranslationPatches': c.int()
'stats.totalMiscPatches': c.int()
'stats.articleTranslationPatches': c.int()
'stats.articleMiscPatches': c.int()
'stats.levelTranslationPatches': c.int()
'stats.levelMiscPatches': c.int()
'stats.levelComponentTranslationPatches': c.int()
'stats.levelComponentMiscPatches': c.int()
'stats.levelSystemTranslationPatches': c.int()
'stats.levelSystemMiscPatches': c.int()
'stats.thangTypeTranslationPatches': c.int()
'stats.thangTypeMiscPatches': c.int()
c.extendBasicProperties UserSchema, 'user'

View file

@ -19,6 +19,7 @@ me.date = (ext) -> combine({type: ['object', 'string'], format: 'date-time'}, ex
# should just be string (Mongo ID), but sometimes mongoose turns them into objects representing those, so we are lenient
me.objectId = (ext) -> schema = combine({type: ['object', 'string']}, ext)
me.url = (ext) -> combine({type: 'string', format: 'url', pattern: urlPattern}, ext)
me.int = (ext) -> if ext then combine {type: 'integer'}, ext else {type: 'integer'}
PointSchema = me.object {title: 'Point', description: 'An {x, y} coordinate point.', format: 'point2d', required: ['x', 'y']},
x: {title: 'x', description: 'The x coordinate.', type: 'number', 'default': 15}

View file

@ -26,10 +26,12 @@ whenAllFinished = ->
process.exit()
async.series [
(c) -> report UserHandler.recalculateAsync, 'gamesCompleted', c
(c) -> report UserHandler.recalculateAsync, 'articleEdits', c
(c) -> report UserHandler.recalculateAsync, 'levelEdits', c
(c) -> report UserHandler.recalculateAsync, 'levelComponentEdits', c
(c) -> report UserHandler.recalculateAsync, 'levelSystemEdits', c
(c) -> report UserHandler.recalculateAsync, 'thangTypeEdits', c
# Misc
(c) -> report UserHandler.recalculateStats, 'gamesCompleted', c
# Edits
(c) -> report UserHandler.recalculateStats, 'articleEdits', c
(c) -> report UserHandler.recalculateStats, 'levelEdits', c
(c) -> report UserHandler.recalculateStats, 'levelComponentEdits', c
(c) -> report UserHandler.recalculateStats, 'levelSystemEdits', c
(c) -> report UserHandler.recalculateStats, 'thangTypeEdits', c
], whenAllFinished

View file

@ -393,9 +393,10 @@ UserHandler = class UserHandler extends Handler
return done(new Error 'Could not resolve statKey for model') unless statKey?
User.find {}, (err, users) ->
async.eachSeries users, ((user, doneWithUser) ->
userID = user.get('_id').toHexString()
userObjectID = user.get('_id')
userStringID = userObjectID.toHexString()
model.count {creator: userID}, (err, count) ->
model.count {$or: [creator: userObjectID, creator: userStringID]}, (err, count) ->
if count
update = $set: {}
update.$set[statKey] = count
@ -442,7 +443,38 @@ UserHandler = class UserHandler extends Handler
ThangType = require '../levels/thangs/ThangType'
countEdits ThangType, done
recalculateAsync: (statName, done) =>
patchesContributed: (done) ->
Patch = require '../patches/Patch'
User.find {}, (err, users) ->
async.eachSeries users, ((user, doneWithUser) ->
userObjectID = user.get('_id')
userStringID = userObjectID.toHexString()
Patch.count {$or: [{creator: userObjectID}, {creator: userStringID}], 'status': 'accepted'}, (err, count) ->
update = if count then {$set: 'stats.patchesContributed': count} else {$unset: 'stats.patchesContributed': ''}
User.findByIdAndUpdate user.get('_id'), update, (err) ->
log.error err if err?
doneWithUser()
), done
patchesSubmitted: (done) ->
Patch = require '../patches/Patch'
User.find {}, (err, users) ->
async.eachSeries users, ((user, doneWithUser) ->
userObjectID = user.get('_id')
userStringID = userObjectID.toHexString()
Patch.count {$or: [{creator: userObjectID}, {creator: userStringID}]}, (err, count) ->
update = if count then {$set: 'stats.patchesSubmitted': count} else {$unset: 'stats.patchesSubmitted': ''}
User.findByIdAndUpdate user.get('_id'), update, (err) ->
log.error err if err?
doneWithUser()
), done
recalculateStats: (statName, done) =>
return new Error 'Recalculation handler not found' unless statName of @statRecalculators
@statRecalculators[statName] done
@ -450,7 +482,7 @@ UserHandler = class UserHandler extends Handler
return @sendForbiddenError(res) unless req.user.isAdmin()
log.debug 'recalculate'
return @sendNotFoundError(res) unless statName of @statRecalculators
@recalculateAsync statName
@recalculateStats statName
@sendAccepted res, {}
module.exports = new UserHandler()

View file

@ -2,6 +2,9 @@ require '../common'
describe '/db/patch', ->
request = require 'request'
async = require 'async'
UserHandler = require '../../../server/users/user_handler'
it 'clears the db first', (done) ->
clearModels [User, Article, Patch], (err) ->
throw err if err
@ -112,7 +115,7 @@ describe '/db/patch', ->
it 'keeps track of amount of submitted and accepted patches', (done) ->
loginJoe (joe) ->
User.findById joe.get('id'), (err, guy) ->
User.findById joe.get('_id'), (err, guy) ->
expect(err).toBeNull()
expect(guy.get 'stats.patchesSubmitted').toBe 1
expect(guy.get 'stats.patchesContributed').toBe 1
@ -121,6 +124,25 @@ describe '/db/patch', ->
expect(guy.get 'stats.totalTranslationPatches').toBeUndefined()
done()
it 'recalculates amount of submitted and accepted patches', (done) ->
loginJoe (joe) ->
console.log joe
User.findById joe.get('_id'), (err, joe) ->
expect(joe.get 'stats.patchesSubmitted').toBe 1
joe.update {$unset: stats: ''}, (err) ->
UserHandler.modelClass.findById joe.get('_id'), (err, joe) ->
expect(err).toBeNull()
expect(joe.get 'stats').toBeUndefined()
async.parallel [
(done) -> UserHandler.recalculateStats 'patchesContributed', done
(done) -> UserHandler.recalculateStats 'patchesSubmitted', done
], (err) ->
expect(err).toBeNull()
UserHandler.modelClass.findById joe.get('_id'), (err, joe) ->
expect(joe.get 'stats.patchesContributed').toBe 1
expect(joe.get 'stats.patchesSubmitted').toBe 1
done()
it 'does not allow the recipient to withdraw the pull request', (done) ->
loginAdmin ->
statusURL = getURL("/db/patch/#{patches[0]._id}/status")
@ -129,3 +151,8 @@ describe '/db/patch', ->
Patch.findOne({}).exec (err, article) ->
expect(article.get('status')).toBe 'accepted'
done()