Merge branch 'master' into production

This commit is contained in:
Matt Lott 2016-06-17 15:42:38 -07:00
commit 3cf602c5cf
41 changed files with 562 additions and 588 deletions

View file

@ -28,7 +28,7 @@ before_script:
- "sleep 15" # to give node a chance to start - "sleep 15" # to give node a chance to start
script: script:
- "./node_modules/karma/bin/karma start --browsers Firefox --single-run --reporters progress" - "./node_modules/karma/bin/karma start --browsers Firefox --single-run --reporters dots"
- "npm run jasmine" - "npm run jasmine"
notifications: notifications:

View file

@ -12,6 +12,8 @@ app = require 'core/application'
World = require 'lib/world/world' World = require 'lib/world/world'
utils = require 'core/utils' utils = require 'core/utils'
LOG = false
# This is an initial stab at unifying loading and setup into a single place which can # This is an initial stab at unifying loading and setup into a single place which can
# monitor everything and keep a LoadingScreen visible overall progress. # monitor everything and keep a LoadingScreen visible overall progress.
# #
@ -147,7 +149,7 @@ module.exports = class LevelLoader extends CocoClass
@listenToOnce @opponentSession, 'sync', @loadDependenciesForSession @listenToOnce @opponentSession, 'sync', @loadDependenciesForSession
loadDependenciesForSession: (session) -> loadDependenciesForSession: (session) ->
console.log "Loading dependencies for session: ", session console.log "Loading dependencies for session: ", session if LOG
if me.id isnt session.get 'creator' if me.id isnt session.get 'creator'
session.patch = session.save = -> console.error "Not saving session, since we didn't create it." session.patch = session.save = -> console.error "Not saving session, since we didn't create it."
else if codeLanguage = utils.getQueryVariable 'codeLanguage' else if codeLanguage = utils.getQueryVariable 'codeLanguage'
@ -172,11 +174,11 @@ module.exports = class LevelLoader extends CocoClass
@consolidateFlagHistory() if @session.loaded @consolidateFlagHistory() if @session.loaded
if @level.get('type', true) in ['course'] # course-ladder is hard to handle because there's 2 sessions if @level.get('type', true) in ['course'] # course-ladder is hard to handle because there's 2 sessions
heroConfig = me.get('heroConfig') heroConfig = me.get('heroConfig')
console.log "Course mode, loading custom hero: ", heroConfig console.log "Course mode, loading custom hero: ", heroConfig if LOG
return if not heroConfig return if not heroConfig
url = "/db/thang.type/#{heroConfig.thangType}/version" url = "/db/thang.type/#{heroConfig.thangType}/version"
if heroResource = @maybeLoadURL(url, ThangType, 'thang') if heroResource = @maybeLoadURL(url, ThangType, 'thang')
console.log "Pushing resource: ", heroResource console.log "Pushing resource: ", heroResource if LOG
@worldNecessities.push heroResource @worldNecessities.push heroResource
@sessionDependenciesRegistered[session.id] = true @sessionDependenciesRegistered[session.id] = true
return return
@ -345,7 +347,7 @@ module.exports = class LevelLoader extends CocoClass
true true
onWorldNecessitiesLoaded: -> onWorldNecessitiesLoaded: ->
console.log "World necessities loaded." console.log "World necessities loaded." if LOG
@initWorld() @initWorld()
@supermodel.clearMaxProgress() @supermodel.clearMaxProgress()
@trigger 'world-necessities-loaded' @trigger 'world-necessities-loaded'
@ -374,7 +376,7 @@ module.exports = class LevelLoader extends CocoClass
onSupermodelLoaded: -> onSupermodelLoaded: ->
return if @destroyed return if @destroyed
console.log 'SuperModel for Level loaded in', new Date().getTime() - @t0, 'ms' console.log 'SuperModel for Level loaded in', new Date().getTime() - @t0, 'ms' if LOG
@loadLevelSounds() @loadLevelSounds()
@denormalizeSession() @denormalizeSession()
@ -482,7 +484,7 @@ module.exports = class LevelLoader extends CocoClass
@world.difficulty = Math.max 0, @world.difficulty - 1 # Show the difficulty they won, not the next one. @world.difficulty = Math.max 0, @world.difficulty - 1 # Show the difficulty they won, not the next one.
serializedLevel = @level.serialize(@supermodel, @session, @opponentSession) serializedLevel = @level.serialize(@supermodel, @session, @opponentSession)
@world.loadFromLevel serializedLevel, false @world.loadFromLevel serializedLevel, false
console.log 'World has been initialized from level loader.' console.log 'World has been initialized from level loader.' if LOG
# Initial Sound Loading # Initial Sound Loading

View file

@ -12,20 +12,18 @@ module.exports =
continue if not instance continue if not instance
instance.numCompleted = 0 instance.numCompleted = 0
instance.started = false instance.started = false
levels = classroom.getLevels({courseID: course.id, withoutLadderLevels: true}) levels = classroom.getLevels({courseID: course.id})
for userID in instance.get('members') for userID in instance.get('members')
instance.started ||= _.any levels.models, (level) -> instance.started ||= _.any levels.models, (level) ->
return false if level.isLadder()
session = _.find classroom.sessions.models, (session) -> session = _.find classroom.sessions.models, (session) ->
session.get('creator') is userID and session.get('level').original is level.get('original') session.get('creator') is userID and session.get('level').original is level.get('original')
session? session?
levelCompletes = _.map levels.models, (level) -> levelCompletes = _.map levels.models, (level) ->
return true if level.isLadder()
#TODO: Hella slow! Do the mapping first! #TODO: Hella slow! Do the mapping first!
session = _.find classroom.sessions.models, (session) -> sessions = _.filter classroom.sessions.models, (session) ->
session.get('creator') is userID and session.get('level').original is level.get('original') session.get('creator') is userID and session.get('level').original is level.get('original')
# sessionMap[userID][level].completed() # sessionMap[userID][level].completed()
session?.completed() _.find(sessions, (s) -> s.completed())
if _.every levelCompletes if _.every levelCompletes
instance.numCompleted += 1 instance.numCompleted += 1
@ -34,14 +32,14 @@ module.exports =
for course, courseIndex in courses.models for course, courseIndex in courses.models
instance = courseInstances.findWhere({ courseID: course.id, classroomID: classroom.id }) instance = courseInstances.findWhere({ courseID: course.id, classroomID: classroom.id })
continue if not instance continue if not instance
levels = classroom.getLevels({courseID: course.id, withoutLadderLevels: true}) levels = classroom.getLevels({courseID: course.id})
for level, levelIndex in levels.models for level, levelIndex in levels.models
userIDs = [] userIDs = []
for user in students.models for user in students.models
userID = user.id userID = user.id
session = _.find classroom.sessions.models, (session) -> sessions = _.filter classroom.sessions.models, (session) ->
session.get('creator') is userID and session.get('level').original is level.get('original') session.get('creator') is userID and session.get('level').original is level.get('original')
if not session?.completed() if not _.find(sessions, (s) -> s.completed())
userIDs.push userID userIDs.push userID
if userIDs.length > 0 if userIDs.length > 0
users = _.map userIDs, (id) -> users = _.map userIDs, (id) ->
@ -61,16 +59,16 @@ module.exports =
courseIndex = courses.models.length - courseIndex - 1 #compensate for reverse courseIndex = courses.models.length - courseIndex - 1 #compensate for reverse
instance = courseInstances.findWhere({ courseID: course.id, classroomID: classroom.id }) instance = courseInstances.findWhere({ courseID: course.id, classroomID: classroom.id })
continue if not instance continue if not instance
levels = classroom.getLevels({courseID: course.id, withoutLadderLevels: true}) levels = classroom.getLevels({courseID: course.id})
levelModels = levels.models.slice() levelModels = levels.models.slice()
for level, levelIndex in levelModels.reverse() # for level, levelIndex in levelModels.reverse() #
levelIndex = levelModels.length - levelIndex - 1 #compensate for reverse levelIndex = levelModels.length - levelIndex - 1 #compensate for reverse
userIDs = [] userIDs = []
for user in students.models for user in students.models
userID = user.id userID = user.id
session = _.find classroom.sessions.models, (session) -> sessions = _.filter classroom.sessions.models, (session) ->
session.get('creator') is userID and session.get('level').original is level.get('original') session.get('creator') is userID and session.get('level').original is level.get('original')
if session?.completed() # if _.find(sessions, (s) -> s.completed()) #
userIDs.push userID userIDs.push userID
if userIDs.length > 0 if userIDs.length > 0
users = _.map userIDs, (id) -> users = _.map userIDs, (id) ->
@ -91,7 +89,7 @@ module.exports =
conceptData[classroom.id] = {} conceptData[classroom.id] = {}
for course, courseIndex in courses.models for course, courseIndex in courses.models
levels = classroom.getLevels({courseID: course.id, withoutLadderLevels: true}) levels = classroom.getLevels({courseID: course.id})
for level in levels.models for level in levels.models
levelID = level.get('original') levelID = level.get('original')
@ -102,16 +100,16 @@ module.exports =
for concept in level.get('concepts') for concept in level.get('concepts')
for userID in classroom.get('members') for userID in classroom.get('members')
session = _.find classroom.sessions.models, (session) -> sessions = _.filter classroom.sessions.models, (session) ->
session.get('creator') is userID and session.get('level').original is levelID session.get('creator') is userID and session.get('level').original is levelID
if not session # haven't gotten to this level yet, but might have completed others before if _.size(sessions) is 0 # haven't gotten to this level yet, but might have completed others before
for concept in level.get('concepts') for concept in level.get('concepts')
conceptData[classroom.id][concept].completed = false conceptData[classroom.id][concept].completed = false
if session # have gotten to the level and at least started it if _.size(sessions) > 0 # have gotten to the level and at least started it
for concept in level.get('concepts') for concept in level.get('concepts')
conceptData[classroom.id][concept].started = true conceptData[classroom.id][concept].started = true
if not session?.completed() # level started but not completed if not _.find(sessions, (s) -> s.completed()) # level started but not completed
for concept in level.get('concepts') for concept in level.get('concepts')
conceptData[classroom.id][concept].completed = false conceptData[classroom.id][concept].completed = false
conceptData conceptData
@ -139,7 +137,7 @@ module.exports =
continue continue
progressData[classroom.id][course.id] = { completed: true, started: false } # to be updated progressData[classroom.id][course.id] = { completed: true, started: false } # to be updated
levels = classroom.getLevels({courseID: course.id, withoutLadderLevels: true}) levels = classroom.getLevels({courseID: course.id})
for level in levels.models for level in levels.models
levelID = level.get('original') levelID = level.get('original')
progressData[classroom.id][course.id][levelID] = { progressData[classroom.id][course.id][levelID] = {
@ -154,12 +152,12 @@ module.exports =
courseProgress = progressData[classroom.id][course.id] courseProgress = progressData[classroom.id][course.id]
courseProgress[userID] ?= { completed: true, started: false, levelsCompleted: 0 } # Only set it the first time through a user courseProgress[userID] ?= { completed: true, started: false, levelsCompleted: 0 } # Only set it the first time through a user
courseProgress[levelID][userID] = { completed: true, started: false } # These don't matter, will always be set courseProgress[levelID][userID] = { completed: true, started: false } # These don't matter, will always be set
session = _.find classroom.sessions.models, (session) -> sessions = _.filter classroom.sessions.models, (session) ->
session.get('creator') is userID and session.get('level').original is levelID session.get('creator') is userID and session.get('level').original is levelID
courseProgress[levelID][userID].session = session
if not session # haven't gotten to this level yet, but might have completed others before courseProgress[levelID][userID].session = _.find(sessions, (s) -> s.completed()) or _.first(sessions)
if _.size(sessions) is 0 # haven't gotten to this level yet, but might have completed others before
courseProgress.started ||= false #no-op courseProgress.started ||= false #no-op
courseProgress.completed = false courseProgress.completed = false
courseProgress[userID].started ||= false #no-op courseProgress[userID].started ||= false #no-op
@ -169,22 +167,23 @@ module.exports =
courseProgress[levelID][userID].started = false courseProgress[levelID][userID].started = false
courseProgress[levelID][userID].completed = false courseProgress[levelID][userID].completed = false
if session # have gotten to the level and at least started it if _.size(sessions) > 0 # have gotten to the level and at least started it
courseProgress.started = true courseProgress.started = true
courseProgress[userID].started = true courseProgress[userID].started = true
courseProgress[levelID].started = true courseProgress[levelID].started = true
courseProgress[levelID][userID].started = true courseProgress[levelID][userID].started = true
courseProgress[levelID][userID].lastPlayed = new Date(session.get('changed')) courseProgress[levelID][userID].lastPlayed = new Date(Math.max(_.map(sessions, 'changed')))
courseProgress[levelID].numStarted += 1 courseProgress[levelID].numStarted += 1
if session?.completed() # have finished this level if _.find(sessions, (s) -> s.completed()) # have finished this level
courseProgress.completed &&= true #no-op courseProgress.completed &&= true #no-op
courseProgress[userID].completed &&= true #no-op courseProgress[userID].completed &&= true #no-op
courseProgress[userID].levelsCompleted += 1 courseProgress[userID].levelsCompleted += 1
courseProgress[levelID].completed &&= true #no-op courseProgress[levelID].completed &&= true #no-op
# courseProgress[levelID].numCompleted += 1 # courseProgress[levelID].numCompleted += 1
courseProgress[levelID][userID].completed = true courseProgress[levelID][userID].completed = true
courseProgress[levelID][userID].dateFirstCompleted = new Date(session.get('dateFirstCompleted') || session.get('changed')) dates = (s.get('dateFirstCompleted') || s.get('changed') for s in sessions)
courseProgress[levelID][userID].dateFirstCompleted = new Date(Math.max(dates...))
else # level started but not completed else # level started but not completed
courseProgress.completed = false courseProgress.completed = false
courseProgress[userID].completed = false courseProgress[userID].completed = false

View file

@ -815,6 +815,7 @@
more_info_1: "Our" more_info_1: "Our"
more_info_2: "teachers forum" more_info_2: "teachers forum"
more_info_3: "is a good place to connect with fellow educators who are using CodeCombat." more_info_3: "is a good place to connect with fellow educators who are using CodeCombat."
licenses_needed: "Licenses needed"
teachers_quote: teachers_quote:
name: "Demo Form" name: "Demo Form"

View file

@ -14,7 +14,7 @@ module.exports = nativeDescription: "français", englishDescription: "French", t
for_developers: "Pour développeurs" # Not currently shown on home page. for_developers: "Pour développeurs" # Not currently shown on home page.
or_ipad: "Ou télécharger pour iPad" or_ipad: "Ou télécharger pour iPad"
new_home: new_home:
slogan: "Le jeu le plus engageant pour apprendre la programmation." slogan: "Le jeu le plus engageant pour apprendre la programmation."
classroom_edition: "Édition Classe:" classroom_edition: "Édition Classe:"
learn_to_code: "Apprend à programmer:" learn_to_code: "Apprend à programmer:"

View file

@ -124,10 +124,11 @@ class CocoModel extends Backbone.Model
validate: -> validate: ->
errors = @getValidationErrors() errors = @getValidationErrors()
if errors?.length if errors?.length
console.debug "Validation failed for #{@constructor.className}: '#{@get('name') or @}'." unless application.testing
for error in errors console.debug "Validation failed for #{@constructor.className}: '#{@get('name') or @}'."
console.debug "\t", error.dataPath, ':', error.message for error in errors
console.trace?() console.debug "\t", error.dataPath, ':', error.message
console.trace?()
return errors return errors
save: (attrs, options) -> save: (attrs, options) ->
@ -188,7 +189,6 @@ class CocoModel extends Backbone.Model
keys.push key keys.push key
return unless keys.length return unless keys.length
console.debug 'Patching', @get('name') or @, keys
@save(attrs, options) @save(attrs, options)
fetch: (options) -> fetch: (options) ->

View file

@ -41,7 +41,7 @@ module.exports = class LevelSession extends CocoModel
@get('submittedCodeLanguage')? and @get('team')? @get('submittedCodeLanguage')? and @get('team')?
completed: -> completed: ->
@get('state')?.complete || false @get('state')?.complete || @get('submitted') || false
shouldAvoidCorruptData: (attrs) -> shouldAvoidCorruptData: (attrs) ->
return false unless me.team is 'humans' return false unless me.team is 'humans'

View file

@ -10,7 +10,6 @@ block content
a(href="/account", data-i18n="nav.account") a(href="/account", data-i18n="nav.account")
li.active(data-i18n="account.payments") li.active(data-i18n="account.payments")
- console.log('render', view.payments.size())
if view.payments.size() if view.payments.size()
table.table.table-striped table.table.table-striped
tr tr

View file

@ -101,9 +101,6 @@ mixin addCredits
button#request-sent-btn.btn-lg.btn.btn-forest(disabled=true, data-i18n="teacher.request_sent") button#request-sent-btn.btn-lg.btn.btn-forest(disabled=true, data-i18n="teacher.request_sent")
else else
p(data-i18n="teacher.num_enrollments_needed") p(data-i18n="teacher.num_enrollments_needed")
div.m-t-2
input#students-input.enrollment-count.text-center(value=view.state.get('numberOfStudents') type='number')
strong(data-i18n="teacher.credits")
p.m-y-2(data-i18n="teacher.get_enrollments_blurb") p.m-y-2(data-i18n="teacher.get_enrollments_blurb")
button#contact-us-btn.btn-lg.btn.btn-forest(data-i18n="contribute.contact_us_url") button#contact-us-btn.btn-lg.btn.btn-forest(data-i18n="contribute.contact_us_url")

View file

@ -300,7 +300,7 @@ mixin courseProgressTab
mixin courseOverview mixin courseOverview
- var course = state.get('selectedCourse') - var course = state.get('selectedCourse')
- var levels = view.classroom.getLevels({courseID: course.id, withoutLadderLevels: true}).models - var levels = view.classroom.getLevels({courseID: course.id}).models
.course-overview-row .course-overview-row
.course-title.student-name .course-title.student-name
span= course.get('name') span= course.get('name')
@ -318,7 +318,7 @@ mixin studentLevelsRow(student)
div.student-email.small-details= student.get('email') div.student-email.small-details= student.get('email')
div.student-levels-progress div.student-levels-progress
- var course = state.get('selectedCourse') - var course = state.get('selectedCourse')
- var levels = view.classroom.getLevels({courseID: course.id, withoutLadderLevels: true}).models - var levels = view.classroom.getLevels({courseID: course.id}).models
each level, index in levels each level, index in levels
- var progress = state.get('progressData').get({ classroom: view.classroom, course: course, level: level, user: student }) - var progress = state.get('progressData').get({ classroom: view.classroom, course: course, level: level, user: student })
+studentLevelProgressDot(progress, level, index+1, session) +studentLevelProgressDot(progress, level, index+1, session)

View file

@ -1,5 +1,7 @@
extends /templates/core/modal-base-flat extends /templates/core/modal-base-flat
//- TODO: i18n
block modal-header-content block modal-header-content
.text-center .text-center
h3 Contact Our Classroom Team h3 Contact Our Classroom Team
@ -11,26 +13,36 @@ block modal-body-content
- var sent = view.state.get('sendingState') === 'sent'; - var sent = view.state.get('sendingState') === 'sent';
- var values = view.state.get('formValues'); - var values = view.state.get('formValues');
- var errors = view.state.get('formErrors'); - var errors = view.state.get('formErrors');
.form-group(class=errors.name ? 'has-error' : '')
label.control-label(for="name" data-i18n="general.name")
+formErrors(errors.name)
input.form-control(name="name", type="text", value=values.name || '', tabindex=1, disabled=sending || sent)
.form-group(class=errors.email ? 'has-error' : '') .form-group(class=errors.email ? 'has-error' : '')
label.control-label(for="email" data-i18n="general.email") label.control-label(for="email" data-i18n="general.email")
+formErrors(errors.email) +formErrors(errors.email)
input.form-control(name="email", type="email", value=values.email || '', tabindex=1, disabled=sending || sent) input.form-control(name="email", type="email", value=values.email || '', tabindex=1, disabled=sending || sent)
.form-group(class=errors.licensesNeeded ? 'has-error' : '')
label.control-label(for="licensesNeeded" data-i18n="teachers.licenses_needed")
+formErrors(errors.licensesNeeded)
input.form-control(name="licensesNeeded", type="text", value=values.licensesNeeded || '', tabindex=1, disabled=sending || sent)
.form-group(class=errors.message ? 'has-error' : '') .form-group(class=errors.message ? 'has-error' : '')
label.control-label(for="message" data-i18n="general.message") label.control-label(for="message" data-i18n="general.message")
+formErrors(errors.message) +formErrors(errors.message)
textarea.form-control(name="message", tabindex=1 disabled=sending || sent)= values.message textarea.form-control(name="message", tabindex=1 disabled=sending || sent)= values.message
if view.state.get('sendingState') === 'error' if view.state.get('sendingState') === 'error'
.alert.alert-danger Could not send message. .alert.alert-danger Could not send message.
if sent if sent
.alert.alert-success Message sent! .alert.alert-success Message sent!
.text-right .text-right
button#submit-btn.btn.btn-navy.btn-lg(type='submit' disabled=sending || sent) Submit button#submit-btn.btn.btn-navy.btn-lg(type='submit' disabled=sending || sent) Submit
block modal-footer block modal-footer
mixin formErrors(errors) mixin formErrors(errors)

View file

@ -14,7 +14,6 @@ module.exports = class EnrollmentsView extends RootView
template: template template: template
events: events:
'input #students-input': 'onInputStudentsInput'
'click #enroll-students-btn': 'onClickEnrollStudentsButton' 'click #enroll-students-btn': 'onClickEnrollStudentsButton'
'click #how-to-enroll-link': 'onClickHowToEnrollLink' 'click #how-to-enroll-link': 'onClickHowToEnrollLink'
'click #contact-us-btn': 'onClickContactUsButton' 'click #contact-us-btn': 'onClickContactUsButton'
@ -96,17 +95,8 @@ module.exports = class EnrollmentsView extends RootView
@openModalView(new HowToEnrollModal()) @openModalView(new HowToEnrollModal())
onClickContactUsButton: -> onClickContactUsButton: ->
window.tracker?.trackEvent 'Classes Licenses Contact Us', category: 'Teachers', enrollmentsNeeded: @state.get('numberOfStudents'), ['Mixpanel'] window.tracker?.trackEvent 'Classes Licenses Contact Us', category: 'Teachers', ['Mixpanel']
@openModalView(new TeachersContactModal({ enrollmentsNeeded: @state.get('numberOfStudents') })) @openModalView(new TeachersContactModal())
onInputStudentsInput: ->
input = @$('#students-input').val()
if input isnt "" and (parseFloat(input) isnt parseInt(input) or _.isNaN parseInt(input))
@$('#students-input').val(@state.get('numberOfStudents'))
else
@state.set({'numberOfStudents': Math.max(parseInt(@$('#students-input').val()) or 0, 0)}, {silent: true}) # do not re-render
numberOfStudentsIsValid: -> 0 < @get('numberOfStudents') < 100000
onClickEnrollStudentsButton: -> onClickEnrollStudentsButton: ->
window.tracker?.trackEvent 'Classes Licenses Enroll Students', category: 'Teachers', ['Mixpanel'] window.tracker?.trackEvent 'Classes Licenses Enroll Students', category: 'Teachers', ['Mixpanel']

View file

@ -7,20 +7,22 @@ contact = require 'core/contact'
module.exports = class TeachersContactModal extends ModalView module.exports = class TeachersContactModal extends ModalView
id: 'teachers-contact-modal' id: 'teachers-contact-modal'
template: require 'templates/teachers/teachers-contact-modal' template: require 'templates/teachers/teachers-contact-modal'
defaultLicenses: 15
events: events:
'submit form': 'onSubmitForm' 'submit form': 'onSubmitForm'
initialize: (options={}) -> initialize: (options={}) ->
@state = new State({ @state = new State({
formValues: { formValues: {
name: ''
email: '' email: ''
licensesNeeded: @defaultLicenses
message: '' message: ''
} }
formErrors: {} formErrors: {}
sendingState: 'standby' # 'sending', 'sent', 'error' sendingState: 'standby' # 'sending', 'sent', 'error'
}) })
@enrollmentsNeeded = options.enrollmentsNeeded or '-'
@trialRequests = new TrialRequests() @trialRequests = new TrialRequests()
@supermodel.trackRequest @trialRequests.fetchOwn() @supermodel.trackRequest @trialRequests.fetchOwn()
@state.on 'change', @render, @ @state.on 'change', @render, @
@ -28,41 +30,46 @@ module.exports = class TeachersContactModal extends ModalView
onLoaded: -> onLoaded: ->
trialRequest = @trialRequests.first() trialRequest = @trialRequests.first()
props = trialRequest?.get('properties') or {} props = trialRequest?.get('properties') or {}
message = """ name = if props.firstName and props.lastName then "#{props.firstName} #{props.lastName}" else me.get('name') ? ''
Name of School/District: #{props.organization or ''}
Your Name: #{props.name || ''}
Enrollments Needed: #{@enrollmentsNeeded}
Message: Hi CodeCombat! I want to learn more about the Classroom experience and get licenses so that my students can access Computer Science 2 and on.
"""
email = props.email or me.get('email') or '' email = props.email or me.get('email') or ''
@state.set('formValues', { email, message }) message = """
Hi CodeCombat! I want to learn more about the Classroom experience and get licenses so that my students can access Computer Science 2 and on.
Name of School/District: #{props.organization or ''}
Role: #{props.role or ''}
Phone Number: #{props.phoneNumber or ''}
"""
@state.set('formValues', { name, email, licensesNeeded: @defaultLicenses, message })
super() super()
onSubmitForm: (e) -> onSubmitForm: (e) ->
e.preventDefault() e.preventDefault()
return if @state.get('sendingState') is 'sending' return if @state.get('sendingState') is 'sending'
formValues = forms.formToObject @$el formValues = forms.formToObject @$el
@state.set('formValues', formValues) @state.set('formValues', formValues)
formErrors = {} formErrors = {}
if not forms.validateEmail(formValues.email) unless formValues.name
formErrors.name = 'Name required.'
unless forms.validateEmail(formValues.email)
formErrors.email = 'Invalid email.' formErrors.email = 'Invalid email.'
if not formValues.message unless parseInt(formValues.licensesNeeded) > 0
formErrors.licensesNeeded = 'Licenses needed is required.'
unless formValues.message
formErrors.message = 'Message required.' formErrors.message = 'Message required.'
@state.set({ formErrors, formValues, sendingState: 'standby' }) @state.set({ formErrors, formValues, sendingState: 'standby' })
return unless _.isEmpty(formErrors) return unless _.isEmpty(formErrors)
@state.set('sendingState', 'sending') @state.set('sendingState', 'sending')
data = _.extend({ country: me.get('country'), recipientID: 'schools@codecombat.com', enrollmentsNeeded: @enrollmentsNeeded }, formValues) data = _.extend({ country: me.get('country'), recipientID: 'schools@codecombat.com' }, formValues)
contact.send({ contact.send({
data data
context: @ context: @
success: -> success: ->
@state.set({ sendingState: 'sent' }) @state.set({ sendingState: 'sent' })
me.set('enrollmentRequestSent', true) me.set('enrollmentRequestSent', true)
setTimeout(=> setTimeout(=>
@hide?() @hide?()
, 3000) , 3000)
error: -> @state.set({ sendingState: 'error' }) error: -> @state.set({ sendingState: 'error' })

View file

@ -2,7 +2,8 @@ winston = require 'winston'
module.exports.setup = -> module.exports.setup = ->
winston.remove(winston.transports.Console) winston.remove(winston.transports.Console)
winston.add(winston.transports.Console, if not global.testing
colorize: true, winston.add(winston.transports.Console,
timestamp: true colorize: true,
) timestamp: true
)

View file

@ -17,6 +17,8 @@ module.exports.addDelightedUser = addDelightedUser = (user, trialRequest) ->
testGroupNumber: user.get('testGroupNumber') testGroupNumber: user.get('testGroupNumber')
gender: user.get('gender') gender: user.get('gender')
lastLevel: user.get('lastLevel') lastLevel: user.get('lastLevel')
state: if props.nces_id and props.country is 'USA' then props.state else 'other'
@postPeople(form) @postPeople(form)
module.exports.postPeople = (form) -> module.exports.postPeople = (form) ->

View file

@ -158,7 +158,7 @@ class EarnedAchievementHandler extends Handler
onFinished = -> onFinished = ->
t1 = new Date().getTime() t1 = new Date().getTime()
runningTime = ((t1-t0)/1000/60/60).toFixed(2) runningTime = ((t1-t0)/1000/60/60).toFixed(2)
console.log "we finished in #{runningTime} hours" log.info "we finished in #{runningTime} hours"
callback arguments... callback arguments...
filter = {} filter = {}
@ -278,7 +278,7 @@ class EarnedAchievementHandler extends Handler
#log.debug "Incrementing score for these achievements with #{newTotalPoints - previousPoints}" #log.debug "Incrementing score for these achievements with #{newTotalPoints - previousPoints}"
pointDelta = newTotalPoints - previousPoints pointDelta = newTotalPoints - previousPoints
pctDone = (100 * usersFinished / total).toFixed(2) pctDone = (100 * usersFinished / total).toFixed(2)
console.log "Updated points to #{newTotalPoints} (#{if pointDelta < 0 then '' else '+'}#{pointDelta}) for #{user.get('name') or '???'} (#{user.get('_id')}) (#{pctDone}%)" log.info "Updated points to #{newTotalPoints} (#{if pointDelta < 0 then '' else '+'}#{pointDelta}) for #{user.get('name') or '???'} (#{user.get('_id')}) (#{pctDone}%)"
if recalculatingAll if recalculatingAll
update = {$set: {points: newTotalPoints, 'earned.gems': 0, 'earned.heroes': [], 'earned.items': [], 'earned.levels': []}} update = {$set: {points: newTotalPoints, 'earned.gems': 0, 'earned.heroes': [], 'earned.items': [], 'earned.levels': []}}
else else

View file

@ -34,7 +34,7 @@ PaymentHandler = class PaymentHandler extends Handler
super arguments... super arguments...
logPaymentError: (req, msg) -> logPaymentError: (req, msg) ->
console.warn "Payment Error: #{req.user.get('slug')} (#{req.user._id}): '#{msg}'" log.warn "Payment Error: #{req.user.get('slug')} (#{req.user._id}): '#{msg}'"
makeNewInstance: (req) -> makeNewInstance: (req) ->
payment = super(req) payment = super(req)

View file

@ -21,7 +21,7 @@ recipientCouponID = 'free'
class SubscriptionHandler extends Handler class SubscriptionHandler extends Handler
logSubscriptionError: (user, msg) -> logSubscriptionError: (user, msg) ->
console.warn "Subscription Error: #{user.get('slug')} (#{user._id}): '#{msg}'" log.warn "Subscription Error: #{user.get('slug')} (#{user._id}): '#{msg}'"
getByRelationship: (req, res, args...) -> getByRelationship: (req, res, args...) ->
return @getStripeEvents(req, res) if args[1] is 'stripe_events' return @getStripeEvents(req, res) if args[1] is 'stripe_events'

View file

@ -119,7 +119,7 @@ UserHandler = class UserHandler extends Handler
log.error "Database error setting user name: #{err}" if err log.error "Database error setting user name: #{err}" if err
return callback(res: 'Database error.', code: 500) if err return callback(res: 'Database error.', code: 500) if err
r = {message: 'is already used by another account', property: 'name'} r = {message: 'is already used by another account', property: 'name'}
console.log 'Another user exists' if otherUser log.info 'Another user exists' if otherUser
return callback({res: r, code: 409}) if otherUser return callback({res: r, code: 409}) if otherUser
user.set('name', req.body.name) user.set('name', req.body.name)
callback(null, req, user) callback(null, req, user)
@ -775,7 +775,7 @@ UserHandler = class UserHandler extends Handler
else else
update = $unset: {} update = $unset: {}
update.$unset[statKey] = '' update.$unset[statKey] = ''
console.log "... updating #{userStringID} patches #{statKey} to #{count}, #{usersTotal} players found so far." if count log.info "... updating #{userStringID} patches #{statKey} to #{count}, #{usersTotal} players found so far." if count
User.findByIdAndUpdate user.get('_id'), update, (err) -> User.findByIdAndUpdate user.get('_id'), update, (err) ->
log.error err if err? log.error err if err?
doneWithUser() doneWithUser()
@ -801,7 +801,7 @@ UserHandler = class UserHandler extends Handler
update = {} update = {}
update[method] = {} update[method] = {}
update[method][statName] = count or '' update[method][statName] = count or ''
console.log "... updating #{user.get('_id')} patches #{JSON.stringify(query)} #{statName} to #{count}, #{usersTotal} players found so far." if count log.info "... updating #{user.get('_id')} patches #{JSON.stringify(query)} #{statName} to #{count}, #{usersTotal} players found so far." if count
User.findByIdAndUpdate user.get('_id'), update, doneUpdatingUser User.findByIdAndUpdate user.get('_id'), update, doneUpdatingUser
userStream = User.find({anonymous: false}).sort('_id').stream() userStream = User.find({anonymous: false}).sort('_id').stream()
@ -865,7 +865,7 @@ UserHandler = class UserHandler extends Handler
update = {} update = {}
update[method] = {} update[method] = {}
update[method][statName] = count or '' update[method][statName] = count or ''
console.log "... updating #{userStringID} patches #{query} to #{count}, #{usersTotal} players found so far." if count log.info "... updating #{userStringID} patches #{query} to #{count}, #{usersTotal} players found so far." if count
User.findByIdAndUpdate user.get('_id'), update, doneWithUser User.findByIdAndUpdate user.get('_id'), update, doneWithUser
statRecalculators: statRecalculators:
@ -883,7 +883,7 @@ UserHandler = class UserHandler extends Handler
--numberRunning --numberRunning
userStream.resume() userStream.resume()
if streamFinished and usersFinished is usersTotal if streamFinished and usersFinished is usersTotal
console.log "----------- Finished recalculating statistics for gamesCompleted for #{usersFinished} players. -----------" log.info "----------- Finished recalculating statistics for gamesCompleted for #{usersFinished} players. -----------"
done?() done?()
userStream.on 'error', (err) -> log.error err userStream.on 'error', (err) -> log.error err
userStream.on 'close', -> streamFinished = true userStream.on 'close', -> streamFinished = true
@ -895,7 +895,7 @@ UserHandler = class UserHandler extends Handler
LevelSession.count {creator: userID, 'state.complete': true}, (err, count) -> LevelSession.count {creator: userID, 'state.complete': true}, (err, count) ->
update = if count then {$set: 'stats.gamesCompleted': count} else {$unset: 'stats.gamesCompleted': ''} update = if count then {$set: 'stats.gamesCompleted': count} else {$unset: 'stats.gamesCompleted': ''}
console.log "... updating #{userID} gamesCompleted to #{count}, #{usersTotal} players found so far." if Math.random() < 0.001 log.info "... updating #{userID} gamesCompleted to #{count}, #{usersTotal} players found so far." if Math.random() < 0.001
User.findByIdAndUpdate user.get('_id'), update, doneWithUser User.findByIdAndUpdate user.get('_id'), update, doneWithUser
articleEdits: (done) -> articleEdits: (done) ->

View file

@ -22,7 +22,7 @@ module.exports =
fetchByCode: wrap (req, res, next) -> fetchByCode: wrap (req, res, next) ->
code = req.query.code code = req.query.code
return next() unless code return next() unless code
classroom = yield Classroom.findOne({ code: code.toLowerCase().replace(/ /g, '') }).select('name ownerID aceConfig') classroom = yield Classroom.findOne({ code: code.toLowerCase().replace(RegExp(' ', 'g') , '') }).select('name ownerID aceConfig')
if not classroom if not classroom
log.debug("classrooms.fetchByCode: Couldn't find Classroom with code: #{code}") log.debug("classrooms.fetchByCode: Couldn't find Classroom with code: #{code}")
throw new errors.NotFound('Classroom not found.') throw new errors.NotFound('Classroom not found.')
@ -104,7 +104,7 @@ module.exports =
members = classroom.get('members') or [] members = classroom.get('members') or []
members = members.slice(memberSkip, memberSkip + memberLimit) members = members.slice(memberSkip, memberSkip + memberLimit)
dbqs = [] dbqs = []
select = 'state.complete level creator playtime changed dateFirstCompleted' select = 'state.complete level creator playtime changed dateFirstCompleted submitted'
for member in members for member in members
dbqs.push(LevelSession.find({creator: member.toHexString()}).select(select).exec()) dbqs.push(LevelSession.find({creator: member.toHexString()}).select(select).exec())
results = yield dbqs results = yield dbqs
@ -170,7 +170,7 @@ module.exports =
if req.user.isTeacher() if req.user.isTeacher()
log.debug("classrooms.join: Cannot join a classroom as a teacher: #{req.user.id}") log.debug("classrooms.join: Cannot join a classroom as a teacher: #{req.user.id}")
throw new errors.Forbidden('Cannot join a classroom as a teacher') throw new errors.Forbidden('Cannot join a classroom as a teacher')
code = req.body.code.toLowerCase().replace(/ /g, '') code = req.body.code.toLowerCase().replace(RegExp(' ', 'g'), '')
classroom = yield Classroom.findOne({code: code}) classroom = yield Classroom.findOne({code: code})
if not classroom if not classroom
log.debug("classrooms.join: Classroom not found with code #{code}") log.debug("classrooms.join: Classroom not found with code #{code}")

View file

@ -39,7 +39,7 @@ module.exports =
throw new errors.NotFound('Level original ObjectId not found in that course') throw new errors.NotFound('Level original ObjectId not found in that course')
if not nextLevelOriginal if not nextLevelOriginal
res.status(200).send({}) return res.status(200).send({})
dbq = Level.findOne({original: mongoose.Types.ObjectId(nextLevelOriginal)}) dbq = Level.findOne({original: mongoose.Types.ObjectId(nextLevelOriginal)})

View file

@ -28,6 +28,9 @@ AnalyticsLogEventSchema.statics.logEvent = (user, event, properties={}) ->
doc.save() doc.save()
analyticsMongoose = mongoose.createConnection "mongodb://#{config.mongo.analytics_host}:#{config.mongo.analytics_port}/#{config.mongo.analytics_db}" unless config.proxy
analyticsMongoose = mongoose.createConnection()
module.exports = AnalyticsLogEvent = analyticsMongoose.model('analytics.log.event', AnalyticsLogEventSchema, config.mongo.analytics_collection) analyticsMongoose.open "mongodb://#{config.mongo.analytics_host}:#{config.mongo.analytics_port}/#{config.mongo.analytics_db}", (error) ->
console.log "Couldnt connect to analytics", error
module.exports = AnalyticsLogEvent = analyticsMongoose.model('analytics.log.event', AnalyticsLogEventSchema, config.mongo.analytics_collection)

View file

@ -350,7 +350,6 @@ UserSchema.pre('save', (next) ->
Classroom = require './Classroom' Classroom = require './Classroom'
if @isTeacher() and not @wasTeacher if @isTeacher() and not @wasTeacher
Classroom.update({members: @_id}, {$pull: {members: @_id}}, {multi: true}).exec (err, res) -> Classroom.update({members: @_id}, {$pull: {members: @_id}}, {multi: true}).exec (err, res) ->
console.log 'removed self from all classrooms as a member', err, res
if email = @get('email') if email = @get('email')
@set('emailLower', email.toLowerCase()) @set('emailLower', email.toLowerCase())
if name = @get('name') if name = @get('name')

View file

@ -25,11 +25,11 @@ module.exports.setup = (app) ->
createMailContent = (req, fromAddress, done) -> createMailContent = (req, fromAddress, done) ->
country = req.body.country country = req.body.country
enrollmentsNeeded = req.body.enrollmentsNeeded licensesNeeded = req.body.licensesNeeded
message = req.body.message message = req.body.message
user = req.user user = req.user
subject = switch subject = switch
when enrollmentsNeeded then "#{enrollmentsNeeded} Licenses needed for #{fromAddress}" when licensesNeeded then "#{licensesNeeded} Licenses needed for #{fromAddress}"
when req.body.subject then req.body.subject when req.body.subject then req.body.subject
else "Contact Us Form: #{fromAddress}" else "Contact Us Form: #{fromAddress}"
level = if user?.get('points') > 0 then Math.floor(5 * Math.log((1 / 100) * (user.get('points') + 100))) + 1 else 0 level = if user?.get('points') > 0 then Math.floor(5 * Math.log((1 / 100) * (user.get('points') + 100))) + 1 else 0

View file

@ -14,7 +14,10 @@ module.exports.setup = (app) ->
app.get('/auth/unsubscribe', mw.auth.unsubscribe) app.get('/auth/unsubscribe', mw.auth.unsubscribe)
app.get('/auth/whoami', mw.auth.whoAmI) app.get('/auth/whoami', mw.auth.whoAmI)
app.all('/db/*', mw.auth.checkHasUser()) app.delete('/db/*', mw.auth.checkHasUser())
app.patch('/db/*', mw.auth.checkHasUser())
app.post('/db/*', mw.auth.checkHasUser())
app.put('/db/*', mw.auth.checkHasUser())
Achievement = require '../models/Achievement' Achievement = require '../models/Achievement'
app.get('/db/achievement', mw.achievements.fetchByRelated, mw.rest.get(Achievement)) app.get('/db/achievement', mw.achievements.fetchByRelated, mw.rest.get(Achievement))

View file

@ -84,7 +84,7 @@ setupExpressMiddleware = (app) ->
app.use express.compress filter: (req, res) -> app.use express.compress filter: (req, res) ->
return false if req.headers.host is 'codecombat.com' # CloudFlare will gzip it for us on codecombat.com return false if req.headers.host is 'codecombat.com' # CloudFlare will gzip it for us on codecombat.com
compressible res.getHeader('Content-Type') compressible res.getHeader('Content-Type')
else else if not global.testing
express.logger.format('dev', developmentLogging) express.logger.format('dev', developmentLogging)
app.use(express.logger('dev')) app.use(express.logger('dev'))
app.use(express.static(path.join(__dirname, 'public'), maxAge: 0)) # CloudFlare overrides maxAge, and we don't want local development caching. app.use(express.static(path.join(__dirname, 'public'), maxAge: 0)) # CloudFlare overrides maxAge, and we don't want local development caching.

View file

@ -39,11 +39,22 @@ if (database.generateMongoConnectionString() !== dbString) {
jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000 * 10; // for long Stripe tests jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000 * 10; // for long Stripe tests
require('../server/common'); // Make sure global testing functions are set up require('../server/common'); // Make sure global testing functions are set up
// Ignore Stripe/Nocking erroring
console.error = function() {
try {
if(arguments[1].stack.indexOf('An error occurred with our connection to Stripe') > -1)
return;
}
catch (e) { }
console.log.apply(console, arguments);
};
var initialized = false; var initialized = false;
beforeEach(function(done) { beforeEach(function(done) {
if (initialized) { if (initialized) {
return done(); return done();
} }
console.log('/spec/helpers/helper.js - Initializing spec environment...');
var async = require('async'); var async = require('async');
async.series([ async.series([
@ -103,6 +114,7 @@ beforeEach(function(done) {
process.exit(1); process.exit(1);
} }
initialized = true; initialized = true;
console.log('/spec/helpers/helper.js - Done');
done(); done();
}); });
}); });

View file

@ -1,7 +1,7 @@
# import this at the top of every file so we're not juggling connections # import this at the top of every file so we're not juggling connections
# and common libraries are available # and common libraries are available
console.log 'IT BEGINS' console.log '/spec/server/common.coffee - Setting up spec globals...'
if process.env.COCO_MONGO_HOST if process.env.COCO_MONGO_HOST
throw Error('Tests may not run with production environment') throw Error('Tests may not run with production environment')
@ -60,7 +60,7 @@ unittest.getUser = (name, email, password, done, force) ->
return done(unittest.users[email]) if unittest.users[email] and not force return done(unittest.users[email]) if unittest.users[email] and not force
request.post getURL('/auth/logout'), -> request.post getURL('/auth/logout'), ->
request.get getURL('/auth/whoami'), -> request.get getURL('/auth/whoami'), ->
req = request.post(getURL('/db/user'), (err, response, body) -> req = request.post({url: getURL('/db/user'), json: {email, password}}, (err, response, body) ->
throw err if err throw err if err
User.findOne({email: email}).exec((err, user) -> User.findOne({email: email}).exec((err, user) ->
throw err if err throw err if err
@ -70,9 +70,6 @@ unittest.getUser = (name, email, password, done, force) ->
wrapUpGetUser(email, user, done) wrapUpGetUser(email, user, done)
) )
) )
form = req.form()
form.append('email', email)
form.append('password', password)
wrapUpGetUser = (email, user, done) -> wrapUpGetUser = (email, user, done) ->
unittest.users[email] = user unittest.users[email] = user
@ -139,58 +136,48 @@ GLOBAL.loginNewUser = (done) ->
email = "#{name}@me.com" email = "#{name}@me.com"
request.post getURL('/auth/logout'), -> request.post getURL('/auth/logout'), ->
unittest.getUser name, email, password, (user) -> unittest.getUser name, email, password, (user) ->
req = request.post(getURL('/auth/login'), (error, response) -> json = {username: email, password}
req = request.post({url: getURL('/auth/login'), json}, (error, response) ->
expect(response.statusCode).toBe(200) expect(response.statusCode).toBe(200)
done(user) done(user)
) )
form = req.form()
form.append('username', email)
form.append('password', password)
, true , true
GLOBAL.loginJoe = (done) -> GLOBAL.loginJoe = (done) ->
request.post getURL('/auth/logout'), -> request.post getURL('/auth/logout'), ->
unittest.getNormalJoe (user) -> unittest.getNormalJoe (user) ->
req = request.post(getURL('/auth/login'), (error, response) -> json = {username: 'normal@jo.com', password: 'food'}
req = request.post({url: getURL('/auth/login'), json}, (error, response) ->
expect(response.statusCode).toBe(200) expect(response.statusCode).toBe(200)
done(user) done(user)
) )
form = req.form()
form.append('username', 'normal@jo.com')
form.append('password', 'food')
GLOBAL.loginSam = (done) -> GLOBAL.loginSam = (done) ->
request.post getURL('/auth/logout'), -> request.post getURL('/auth/logout'), ->
unittest.getOtherSam (user) -> unittest.getOtherSam (user) ->
req = request.post(getURL('/auth/login'), (error, response) -> json = { username: 'other@sam.com', password: 'beer'}
req = request.post({url: getURL('/auth/login'), json}, (error, response) ->
expect(response.statusCode).toBe(200) expect(response.statusCode).toBe(200)
done(user) done(user)
) )
form = req.form()
form.append('username', 'other@sam.com')
form.append('password', 'beer')
GLOBAL.loginAdmin = (done) -> GLOBAL.loginAdmin = (done) ->
request.post getURL('/auth/logout'), -> request.post getURL('/auth/logout'), ->
unittest.getAdmin (user) -> unittest.getAdmin (user) ->
req = request.post(getURL('/auth/login'), (error, response) -> json = { username: 'admin@afc.com', password: '80yqxpb38j' }
req = request.post({url: getURL('/auth/login'), json}, (error, response) ->
expect(response.statusCode).toBe(200) expect(response.statusCode).toBe(200)
done(user) done(user)
) )
form = req.form()
form.append('username', 'admin@afc.com')
form.append('password', '80yqxpb38j')
# find some other way to make the admin object an admin... maybe directly? # find some other way to make the admin object an admin... maybe directly?
GLOBAL.loginUser = (user, done) -> GLOBAL.loginUser = (user, done) ->
request.post getURL('/auth/logout'), -> request.post getURL('/auth/logout'), ->
req = request.post(getURL('/auth/login'), (error, response) -> json = { username: user.get('email'), password: user.get('name') }
req = request.post({ url: getURL('/auth/login'), json}, (error, response) ->
expect(response.statusCode).toBe(200) expect(response.statusCode).toBe(200)
done(user) done(user)
) )
form = req.form()
form.append('username', user.get('email'))
form.append('password', user.get('name'))
GLOBAL.logoutUser = (done) -> GLOBAL.logoutUser = (done) ->
request.post getURL('/auth/logout'), -> request.post getURL('/auth/logout'), ->
@ -213,3 +200,4 @@ _drop = (done) ->
GLOBAL.resetUserIDCounter = (number=0) -> GLOBAL.resetUserIDCounter = (number=0) ->
User.idCounter = number User.idCounter = number
console.log '/spec/server/common.coffee - Done'

View file

@ -4,23 +4,23 @@ request = require '../request'
User = require '../../../server/models/User' User = require '../../../server/models/User'
# TODO: need to update this test since /contact calls external Close.io API now # TODO: need to update this test since /contact calls external Close.io API now
xdescribe 'POST /contact', -> #xdescribe 'POST /contact', ->
#
beforeEach utils.wrap (done) -> # beforeEach utils.wrap (done) ->
spyOn(sendwithus.api, 'send') # spyOn(sendwithus.api, 'send')
@teacher = yield utils.initUser({role: 'teacher'}) # @teacher = yield utils.initUser({role: 'teacher'})
yield utils.loginUser(@teacher) # yield utils.loginUser(@teacher)
done() # done()
#
describe 'when recipientID is "schools@codecombat.com"', -> # describe 'when recipientID is "schools@codecombat.com"', ->
it 'sends to that email', utils.wrap (done) -> # it 'sends to that email', utils.wrap (done) ->
[res, body] = yield request.postAsync({url: getURL('/contact'), json: { # [res, body] = yield request.postAsync({url: getURL('/contact'), json: {
sender: 'some@email.com' # sender: 'some@email.com'
message: 'A message' # message: 'A message'
recipientID: 'schools@codecombat.com' # recipientID: 'schools@codecombat.com'
}}) # }})
expect(sendwithus.api.send).toHaveBeenCalled() # expect(sendwithus.api.send).toHaveBeenCalled()
user = yield User.findById(@teacher.id) # user = yield User.findById(@teacher.id)
yield new Promise((resolve) -> setTimeout(resolve, 10)) # yield new Promise((resolve) -> setTimeout(resolve, 10))
expect(user.get('enrollmentRequestSent')).toBe(true) # expect(user.get('enrollmentRequestSent')).toBe(true)
done() # done()

View file

@ -1,178 +1,178 @@
require '../common' #require '../common'
#
# Doesn't work on Travis. Need to figure out why, probably by having the ## Doesn't work on Travis. Need to figure out why, probably by having the
# url not depend on some external resource. ## url not depend on some external resource.
mongoose = require 'mongoose' #mongoose = require 'mongoose'
request = require '../request' #request = require '../request'
#
xdescribe '/file', -> #xdescribe '/file', ->
url = getURL('/file') # url = getURL('/file')
files = [] # files = []
options = { # options = {
uri: url # uri: url
json: { # json: {
# url: 'http://scotterickson.info/images/where-are-you.jpg' # # url: 'http://scotterickson.info/images/where-are-you.jpg'
url: 'http://fc07.deviantart.net/fs37/f/2008/283/5/1/Chu_Chu_Pikachu_by_angelishi.gif' # url: 'http://fc07.deviantart.net/fs37/f/2008/283/5/1/Chu_Chu_Pikachu_by_angelishi.gif'
filename: 'where-are-you.jpg' # filename: 'where-are-you.jpg'
mimetype: 'image/jpeg' # mimetype: 'image/jpeg'
description: 'None!' # description: 'None!'
} # }
} # }
filepath = 'tmp/file' # TODO Warning hard coded path !!! # filepath = 'tmp/file' # TODO Warning hard coded path !!!
#
jsonOptions= { # jsonOptions= {
path: 'my_path' # path: 'my_path'
postName: 'my_buffer' # postName: 'my_buffer'
filename: 'ittybitty.data' # filename: 'ittybitty.data'
mimetype: 'application/octet-stream' # mimetype: 'application/octet-stream'
description: 'rando-info' # description: 'rando-info'
# my_buffer_url: 'http://scotterickson.info/images/where-are-you.jpg' # # my_buffer_url: 'http://scotterickson.info/images/where-are-you.jpg'
my_buffer_url: 'http://fc07.deviantart.net/fs37/f/2008/283/5/1/Chu_Chu_Pikachu_by_angelishi.gif' # my_buffer_url: 'http://fc07.deviantart.net/fs37/f/2008/283/5/1/Chu_Chu_Pikachu_by_angelishi.gif'
} # }
#
allowHeader = 'GET, POST' # allowHeader = 'GET, POST'
#
it 'preparing test : deletes all the files first', (done) -> # it 'preparing test : deletes all the files first', (done) ->
dropGridFS -> # dropGridFS ->
done() # done()
#
it 'can\'t be created if invalid (property path is required)', (done) -> # it 'can\'t be created if invalid (property path is required)', (done) ->
func = (err, res, body) -> # func = (err, res, body) ->
expect(res.statusCode).toBe(422) # expect(res.statusCode).toBe(422)
done() # done()
#
loginAdmin -> # loginAdmin ->
request.post(options, func) # request.post(options, func)
#
it 'can be created by an admin', (done) -> # it 'can be created by an admin', (done) ->
func = (err, res, body) -> # func = (err, res, body) ->
expect(res.statusCode).toBe(200) # expect(res.statusCode).toBe(200)
expect(body._id).toBeDefined() # expect(body._id).toBeDefined()
expect(body.filename).toBe(options.json.filename) # expect(body.filename).toBe(options.json.filename)
expect(body.contentType).toBe(options.json.mimetype) # expect(body.contentType).toBe(options.json.mimetype)
expect(body.length).toBeDefined() # expect(body.length).toBeDefined()
expect(body.uploadDate).toBeDefined() # expect(body.uploadDate).toBeDefined()
expect(body.metadata).toBeDefined() # expect(body.metadata).toBeDefined()
expect(body.metadata.name).toBeDefined() # expect(body.metadata.name).toBeDefined()
expect(body.metadata.path).toBe(options.json.path) # expect(body.metadata.path).toBe(options.json.path)
expect(body.metadata.creator).toBeDefined() # expect(body.metadata.creator).toBeDefined()
expect(body.metadata.description).toBe(options.json.description) # expect(body.metadata.description).toBe(options.json.description)
expect(body.md5).toBeDefined() # expect(body.md5).toBeDefined()
files.push(body) # files.push(body)
done() # done()
#
options.json.path = filepath # options.json.path = filepath
request.post(options, func) # request.post(options, func)
#
it 'can be read by an admin.', (done) -> # it 'can be read by an admin.', (done) ->
request.get {uri: url+'/'+files[0]._id}, (err, res) -> # request.get {uri: url+'/'+files[0]._id}, (err, res) ->
expect(res.statusCode).toBe(200) # expect(res.statusCode).toBe(200)
expect(res.headers['content-type']).toBe(files[0].contentType) # expect(res.headers['content-type']).toBe(files[0].contentType)
done() # done()
#
it 'returns 404 for missing files', (done) -> # it 'returns 404 for missing files', (done) ->
id = '000000000000000000000000' # id = '000000000000000000000000'
request.get {uri: url+'/'+id}, (err, res) -> # request.get {uri: url+'/'+id}, (err, res) ->
expect(res.statusCode).toBe(404) # expect(res.statusCode).toBe(404)
done() # done()
#
it 'returns 404 for invalid ids', (done) -> # it 'returns 404 for invalid ids', (done) ->
request.get {uri: url+'/thiswillnotwork'}, (err, res) -> # request.get {uri: url+'/thiswillnotwork'}, (err, res) ->
expect(res.statusCode).toBe(404) # expect(res.statusCode).toBe(404)
done() # done()
#
it 'can be created directly with form parameters', (done) -> # it 'can be created directly with form parameters', (done) ->
options2 = { # options2 = {
uri: url # uri: url
} # }
#
func = (err, res, body) -> # func = (err, res, body) ->
expect(res.statusCode).toBe(200) # expect(res.statusCode).toBe(200)
body = JSON.parse(body) # body = JSON.parse(body)
expect(body._id).toBeDefined() # expect(body._id).toBeDefined()
expect(body.filename).toBe(jsonOptions.filename) # expect(body.filename).toBe(jsonOptions.filename)
expect(body.contentType).toBe(jsonOptions.mimetype) # expect(body.contentType).toBe(jsonOptions.mimetype)
expect(body.length).toBeDefined() # expect(body.length).toBeDefined()
expect(body.uploadDate).toBeDefined() # expect(body.uploadDate).toBeDefined()
expect(body.metadata).toBeDefined() # expect(body.metadata).toBeDefined()
expect(body.metadata.name).toBeDefined() # expect(body.metadata.name).toBeDefined()
expect(body.metadata.path).toBe(jsonOptions.path) # expect(body.metadata.path).toBe(jsonOptions.path)
expect(body.metadata.creator).toBeDefined() # expect(body.metadata.creator).toBeDefined()
expect(body.metadata.description).toBe(jsonOptions.description) # expect(body.metadata.description).toBe(jsonOptions.description)
expect(body.md5).toBeDefined() # expect(body.md5).toBeDefined()
files.push(body) # files.push(body)
done() # done()
#
# the only way I could figure out how to get request to do what I wanted... # # the only way I could figure out how to get request to do what I wanted...
r = request.post(options2, func) # r = request.post(options2, func)
form = r.form() # form = r.form()
form.append('path', jsonOptions.path) # form.append('path', jsonOptions.path)
form.append('postName', jsonOptions.postName) # form.append('postName', jsonOptions.postName)
form.append('filename', jsonOptions.filename) # form.append('filename', jsonOptions.filename)
form.append('mimetype', jsonOptions.mimetype) # form.append('mimetype', jsonOptions.mimetype)
form.append('description', jsonOptions.description) # form.append('description', jsonOptions.description)
form.append('my_buffer', request(jsonOptions.my_buffer_url)) # form.append('my_buffer', request(jsonOptions.my_buffer_url))
#
it 'created directly, can be read', (done) -> # it 'created directly, can be read', (done) ->
request.get {uri: url+'/'+files[1]._id}, (err, res) -> # request.get {uri: url+'/'+files[1]._id}, (err, res) ->
expect(res.statusCode).toBe(200) # expect(res.statusCode).toBe(200)
expect(res.headers['content-type']).toBe(files[1].contentType) # expect(res.headers['content-type']).toBe(files[1].contentType)
done() # done()
#
it 'does not overwrite existing files', (done) -> # it 'does not overwrite existing files', (done) ->
options.json.description = 'Face' # options.json.description = 'Face'
#
func = (err, res, body) -> # func = (err, res, body) ->
expect(res.statusCode).toBe(409) # expect(res.statusCode).toBe(409)
collection = mongoose.connection.db.collection('media.files') # collection = mongoose.connection.db.collection('media.files')
collection.find({}).toArray (err, results) -> # collection.find({}).toArray (err, results) ->
# ittybitty.data, and just one Where are you.jpg # # ittybitty.data, and just one Where are you.jpg
expect(results.length).toBe(2) # expect(results.length).toBe(2)
for f in results # for f in results
expect(f.metadata.description).not.toBe('Face') # expect(f.metadata.description).not.toBe('Face')
done() # done()
#
request.post(options, func) # request.post(options, func)
#
it 'does overwrite existing files if force is true', (done) -> # it 'does overwrite existing files if force is true', (done) ->
options.json.force = 'true' # TODO ask why it's a string and not a boolean ? # options.json.force = 'true' # TODO ask why it's a string and not a boolean ?
#
func = (err, res, body) -> # func = (err, res, body) ->
expect(res.statusCode).toBe(200) # expect(res.statusCode).toBe(200)
collection = mongoose.connection.db.collection('media.files') # collection = mongoose.connection.db.collection('media.files')
collection.find({}).toArray (err, results) -> # collection.find({}).toArray (err, results) ->
# ittybitty.data, and just one Where are you.jpg # # ittybitty.data, and just one Where are you.jpg
expect(results.length).toBe(2) # expect(results.length).toBe(2)
hit = false # hit = false
for f in results # for f in results
hit = true if f.metadata.description is 'Face' # hit = true if f.metadata.description is 'Face'
expect(hit).toBe(true) # expect(hit).toBe(true)
done() # done()
#
request.post(options, func) # request.post(options, func)
#
it ' can\'t be requested with HTTP PATCH method', (done) -> # it ' can\'t be requested with HTTP PATCH method', (done) ->
request {method: 'patch', uri: url}, (err, res) -> # request {method: 'patch', uri: url}, (err, res) ->
expect(res.statusCode).toBe(405) # expect(res.statusCode).toBe(405)
expect(res.headers.allow).toBe(allowHeader) # expect(res.headers.allow).toBe(allowHeader)
done() # done()
#
it ' can\'t be requested with HTTP PUT method', (done) -> # it ' can\'t be requested with HTTP PUT method', (done) ->
request.put {uri: url}, (err, res) -> # request.put {uri: url}, (err, res) ->
expect(res.statusCode).toBe(405) # expect(res.statusCode).toBe(405)
expect(res.headers.allow).toBe(allowHeader) # expect(res.headers.allow).toBe(allowHeader)
done() # done()
#
it ' can\'t be requested with HTTP HEAD method', (done) -> # it ' can\'t be requested with HTTP HEAD method', (done) ->
request.head {uri: url}, (err, res) -> # request.head {uri: url}, (err, res) ->
expect(res.statusCode).toBe(405) # expect(res.statusCode).toBe(405)
expect(res.headers.allow).toBe(allowHeader) # expect(res.headers.allow).toBe(allowHeader)
done() # done()
#
it ' can\'t be requested with HTTP DEL method', (done) -> # it ' can\'t be requested with HTTP DEL method', (done) ->
request.del {uri: url}, (err, res) -> # request.del {uri: url}, (err, res) ->
expect(res.statusCode).toBe(405) # expect(res.statusCode).toBe(405)
expect(res.headers.allow).toBe(allowHeader) # expect(res.headers.allow).toBe(allowHeader)
done() # done()
#
# TODO: test server errors, see what they do ## TODO: test server errors, see what they do

View file

@ -23,14 +23,14 @@ describe '/db/level.session', ->
# TODO Tried to mimic what happens on the site. Why is this even so hard to do. # TODO Tried to mimic what happens on the site. Why is this even so hard to do.
# Right now it's even possible to create ownerless sessions through POST # Right now it's even possible to create ownerless sessions through POST
xit 'allows users to create level sessions through PATCH', (done) -> # xit 'allows users to create level sessions through PATCH', (done) ->
loginJoe (joe) -> # loginJoe (joe) ->
request {method: 'patch', uri: url + mongoose.Types.ObjectId(), json: session}, (err, res, body) -> # request {method: 'patch', uri: url + mongoose.Types.ObjectId(), json: session}, (err, res, body) ->
expect(err).toBeNull() # expect(err).toBeNull()
expect(res.statusCode).toBe 200 # expect(res.statusCode).toBe 200
console.log body # console.log body
expect(body.creator).toEqual joe.get('_id').toHexString() # expect(body.creator).toEqual joe.get('_id').toHexString()
done() # done()
# Should remove this as soon as the PATCH test case above works # Should remove this as soon as the PATCH test case above works
it 'create a level session', (done) -> it 'create a level session', (done) ->

View file

@ -1,29 +0,0 @@
require '../common'
config = require '../../../server_config'
nockUtils = require('../nock-utils')
request = require '../request'
xdescribe 'nock-utils', ->
afterEach nockUtils.teardownNock
describe 'a test using setupNock', ->
it 'records and plays back third-party requests, but not localhost requests', (done) ->
nockUtils.setupNock 'nock-test.json', (err, nockDone) ->
request.get { uri: getURL('/db/level') }, (err) ->
expect(err).toBeNull()
t0 = new Date().getTime()
request.get { uri: 'http://zombo.com/' }, (err) ->
console.log 'cached speed', new Date().getTime() - t0
expect(err).toBeNull()
nockDone()
done()
describe 'another, sibling test that does not use setupNock', ->
it 'is proceeds normally', (done) ->
request.get { uri: getURL('/db/level') }, (err) ->
expect(err).toBeNull()
t0 = new Date().getTime()
request.get { uri: 'http://zombo.com/' }, (err) ->
console.log 'uncached speed', new Date().getTime() - t0
expect(err).toBeNull()
done()

View file

@ -544,7 +544,7 @@ describe '/db/prepaid', ->
logoutUser () -> logoutUser () ->
fetchPrepaid joeCode, (err, res) -> fetchPrepaid joeCode, (err, res) ->
expect(err).toBeNull() expect(err).toBeNull()
expect(res.statusCode).toEqual(401) expect(res.statusCode).toEqual(403)
done() done()
it 'User can fetch a prepaid code', (done) -> it 'User can fetch a prepaid code', (done) ->

View file

@ -1,24 +1,24 @@
require '../common' #require '../common'
request = require '../request' #request = require '../request'
#
describe 'queue', -> #describe 'queue', ->
someURL = getURL('/queue/') # someURL = getURL('/queue/')
allowHeader = 'GET, POST, PUT' # allowHeader = 'GET, POST, PUT'
#
xit 'can\'t be requested with HTTP PATCH method', (done) -> # xit 'can\'t be requested with HTTP PATCH method', (done) ->
request {method: 'patch', uri: someURL}, (err, res, body) -> # request {method: 'patch', uri: someURL}, (err, res, body) ->
expect(res.statusCode).toBe(405) # expect(res.statusCode).toBe(405)
expect(res.headers.allow).toBe(allowHeader) # expect(res.headers.allow).toBe(allowHeader)
done() # done()
#
xit 'can\'t be requested with HTTP HEAD method', (done) -> # xit 'can\'t be requested with HTTP HEAD method', (done) ->
request.head {uri: someURL}, (err, res, body) -> # request.head {uri: someURL}, (err, res, body) ->
expect(res.statusCode).toBe(405) # expect(res.statusCode).toBe(405)
expect(res.headers.allow).toBe(allowHeader) # expect(res.headers.allow).toBe(allowHeader)
done() # done()
#
xit 'can\'t be requested with HTTP DELETE method', (done) -> # xit 'can\'t be requested with HTTP DELETE method', (done) ->
request.del {uri: someURL}, (err, res, body) -> # request.del {uri: someURL}, (err, res, body) ->
expect(res.statusCode).toBe(405) # expect(res.statusCode).toBe(405)
expect(res.headers.allow).toBe(allowHeader) # expect(res.headers.allow).toBe(allowHeader)
done() # done()

View file

@ -1441,62 +1441,62 @@ describe 'Subscriptions', ->
nockDone() nockDone()
done() done()
xit 'Unsubscribed user1 subscribes 13 users, unsubcribes 2', (done) -> # xit 'Unsubscribed user1 subscribes 13 users, unsubcribes 2', (done) ->
nockUtils.setupNock 'sub-test-34.json', (err, nockDone) -> # nockUtils.setupNock 'sub-test-34.json', (err, nockDone) ->
# TODO: Hits the Stripe error 'Request rate limit exceeded'. # # TODO: Hits the Stripe error 'Request rate limit exceeded'.
# TODO: Need a better test for 12+ bulk discounts. Or, we could update the bulk disount logic. # # TODO: Need a better test for 12+ bulk discounts. Or, we could update the bulk disount logic.
# TODO: verify interim invoices? # # TODO: verify interim invoices?
recipientCount = 13 # recipientCount = 13
recipientsToVerify = [0, 1, 10, 11, 12] # recipientsToVerify = [0, 1, 10, 11, 12]
recipients = new SubbedRecipients recipientCount, recipientsToVerify # recipients = new SubbedRecipients recipientCount, recipientsToVerify
#
# Create recipients # # Create recipients
recipients.createRecipients -> # recipients.createRecipients ->
expect(recipients.length()).toEqual(recipientCount) # expect(recipients.length()).toEqual(recipientCount)
#
stripe.tokens.create { # stripe.tokens.create {
card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' } # card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
}, (err, token) -> # }, (err, token) ->
#
# Create sponsor user # # Create sponsor user
loginNewUser (user1) -> # loginNewUser (user1) ->
#
# Subscribe recipients # # Subscribe recipients
recipients.subRecipients user1, token, -> # recipients.subRecipients user1, token, ->
User.findById user1.id, (err, user1) -> # User.findById user1.id, (err, user1) ->
#
# Unsubscribe first recipient # # Unsubscribe first recipient
unsubscribeRecipient user1, recipients.get(0), -> # unsubscribeRecipient user1, recipients.get(0), ->
User.findById user1.id, (err, user1) -> # User.findById user1.id, (err, user1) ->
#
stripeInfo = user1.get('stripe') # stripeInfo = user1.get('stripe')
expect(stripeInfo.recipients.length).toEqual(recipientCount - 1) # expect(stripeInfo.recipients.length).toEqual(recipientCount - 1)
verifyNotSponsoring user1.id, recipients.get(0).id, -> # verifyNotSponsoring user1.id, recipients.get(0).id, ->
verifyNotRecipient recipients.get(0).id, -> # verifyNotRecipient recipients.get(0).id, ->
stripe.customers.retrieveSubscription stripeInfo.customerID, stripeInfo.sponsorSubscriptionID, (err, subscription) -> # stripe.customers.retrieveSubscription stripeInfo.customerID, stripeInfo.sponsorSubscriptionID, (err, subscription) ->
expect(err).toBeNull() # expect(err).toBeNull()
expect(subscription).not.toBeNull() # expect(subscription).not.toBeNull()
expect(subscription.quantity).toEqual(getUnsubscribedQuantity(recipientCount - 1)) # expect(subscription.quantity).toEqual(getUnsubscribedQuantity(recipientCount - 1))
#
# Unsubscribe last recipient # # Unsubscribe last recipient
unsubscribeRecipient user1, recipients.get(recipientCount - 1), -> # unsubscribeRecipient user1, recipients.get(recipientCount - 1), ->
User.findById user1.id, (err, user1) -> # User.findById user1.id, (err, user1) ->
stripeInfo = user1.get('stripe') # stripeInfo = user1.get('stripe')
expect(stripeInfo.recipients.length).toEqual(recipientCount - 2) # expect(stripeInfo.recipients.length).toEqual(recipientCount - 2)
verifyNotSponsoring user1.id, recipients.get(recipientCount - 1).id, -> # verifyNotSponsoring user1.id, recipients.get(recipientCount - 1).id, ->
verifyNotRecipient recipients.get(recipientCount - 1).id, -> # verifyNotRecipient recipients.get(recipientCount - 1).id, ->
stripe.customers.retrieveSubscription stripeInfo.customerID, stripeInfo.sponsorSubscriptionID, (err, subscription) -> # stripe.customers.retrieveSubscription stripeInfo.customerID, stripeInfo.sponsorSubscriptionID, (err, subscription) ->
expect(err).toBeNull() # expect(err).toBeNull()
expect(subscription).not.toBeNull() # expect(subscription).not.toBeNull()
numSponsored = recipientCount - 2 # numSponsored = recipientCount - 2
if numSponsored <= 1 # if numSponsored <= 1
expect(subscription.quantity).toEqual(subPrice) # expect(subscription.quantity).toEqual(subPrice)
else if numSponsored <= 11 # else if numSponsored <= 11
expect(subscription.quantity).toEqual(subPrice + (numSponsored - 1) * subPrice * 0.8) # expect(subscription.quantity).toEqual(subPrice + (numSponsored - 1) * subPrice * 0.8)
else # else
expect(subscription.quantity).toEqual(subPrice + 10 * subPrice * 0.8 + (numSponsored - 11) * subPrice * 0.6) # expect(subscription.quantity).toEqual(subPrice + 10 * subPrice * 0.8 + (numSponsored - 11) * subPrice * 0.6)
nockDone() # nockDone()
done() # done()
describe 'APIs', -> describe 'APIs', ->
subscriptionURL = getURL('/db/subscription') subscriptionURL = getURL('/db/subscription')
@ -1694,7 +1694,6 @@ describe 'Subscriptions', ->
token: token.id token: token.id
timestamp: new Date() timestamp: new Date()
request.put {uri: "#{subscriptionURL}/-/year_sale", json: requestBody, headers: headers }, (err, res) -> request.put {uri: "#{subscriptionURL}/-/year_sale", json: requestBody, headers: headers }, (err, res) ->
console.log err
expect(err).toBeNull() expect(err).toBeNull()
nockDone() nockDone()
done() done()

View file

@ -11,16 +11,13 @@ describe 'POST /db/user', ->
createAnonNameUser = (name, done)-> createAnonNameUser = (name, done)->
request.post getURL('/auth/logout'), -> request.post getURL('/auth/logout'), ->
request.get getURL('/auth/whoami'), -> request.get getURL('/auth/whoami'), ->
req = request.post(getURL('/db/user'), (err, response) -> req = request.post({ url: getURL('/db/user'), json: {name}}, (err, response) ->
expect(response.statusCode).toBe(200) expect(response.statusCode).toBe(200)
request.get getURL('/auth/whoami'), (request, response, body) -> request.get { url: getURL('/auth/whoami'), json: true }, (request, response, body) ->
res = JSON.parse(response.body) expect(body.anonymous).toBeTruthy()
expect(res.anonymous).toBeTruthy() expect(body.name).toEqual(name)
expect(res.name).toEqual(name)
done() done()
) )
form = req.form()
form.append('name', name)
it 'preparing test : clears the db first', (done) -> it 'preparing test : clears the db first', (done) ->
clearModels [User], (err) -> clearModels [User], (err) ->
@ -77,16 +74,13 @@ describe 'POST /db/user', ->
createAnonNameUser('Jim', done) createAnonNameUser('Jim', done)
it 'should allow setting existing user name to anonymous user', (done) -> it 'should allow setting existing user name to anonymous user', (done) ->
req = request.post(getURL('/db/user'), (err, response, body) -> req = request.post({url: getURL('/db/user'), json: {email: 'new@user.com', password: 'new'}}, (err, response, body) ->
expect(response.statusCode).toBe(200) expect(response.statusCode).toBe(200)
request.get getURL('/auth/whoami'), (request, response, body) -> request.get getURL('/auth/whoami'), (request, response, body) ->
res = JSON.parse(response.body) res = JSON.parse(response.body)
expect(res.anonymous).toBeFalsy() expect(res.anonymous).toBeFalsy()
createAnonNameUser 'Jim', done createAnonNameUser 'Jim', done
) )
form = req.form()
form.append('email', 'new@user.com')
form.append('password', 'new')
describe 'PUT /db/user', -> describe 'PUT /db/user', ->
@ -103,23 +97,22 @@ describe 'PUT /db/user', ->
it 'denies requests to edit someone who is not joe', (done) -> it 'denies requests to edit someone who is not joe', (done) ->
unittest.getAdmin (admin) -> unittest.getAdmin (admin) ->
req = request.put getURL(urlUser), request.put {url: getURL(urlUser), json: {_id: admin.id}}, (err, res) ->
(err, res) ->
expect(res.statusCode).toBe(403) expect(res.statusCode).toBe(403)
done() done()
req.form().append('_id', admin.id)
it 'denies invalid data', (done) -> it 'denies invalid data', (done) ->
unittest.getNormalJoe (joe) -> unittest.getNormalJoe (joe) ->
req = request.put getURL(urlUser), json = {
(err, res) -> _id: joe.id
email: 'farghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlar
ghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghl'
}
request.put { url: getURL(urlUser), json }, (err, res) ->
expect(res.statusCode).toBe(422) expect(res.statusCode).toBe(422)
expect(res.body.indexOf('too long')).toBeGreaterThan(-1) expect(res.body[0].message.indexOf('too long')).toBeGreaterThan(-1)
done() done()
form = req.form()
form.append('_id', joe.id)
form.append('email', 'farghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlar
ghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghl')
it 'does not allow normals to edit their permissions', utils.wrap (done) -> it 'does not allow normals to edit their permissions', utils.wrap (done) ->
user = yield utils.initUser() user = yield utils.initUser()
@ -132,47 +125,45 @@ ghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghl
loginAdmin -> done() loginAdmin -> done()
it 'denies non-existent ids', (done) -> it 'denies non-existent ids', (done) ->
req = request.put getURL(urlUser), json = {
(err, res) -> _id: '513108d4cb8b610000000004',
email: 'perfectly@good.com'
}
request.put {url: getURL(urlUser), json}, (err, res) ->
expect(res.statusCode).toBe(404) expect(res.statusCode).toBe(404)
done() done()
form = req.form()
form.append('_id', '513108d4cb8b610000000004')
form.append('email', 'perfectly@good.com')
it 'denies if the email being changed is already taken', (done) -> it 'denies if the email being changed is already taken', (done) ->
unittest.getNormalJoe (joe) -> unittest.getNormalJoe (joe) ->
unittest.getAdmin (admin) -> unittest.getAdmin (admin) ->
req = request.put getURL(urlUser), (err, res) -> json = { _id: admin.id, email: joe.get('email').toUpperCase() }
request.put { url: getURL(urlUser), json }, (err, res) ->
expect(res.statusCode).toBe(409) expect(res.statusCode).toBe(409)
expect(res.body.indexOf('already used')).toBeGreaterThan(-1) expect(res.body.message.indexOf('already used')).toBeGreaterThan(-1)
done() done()
form = req.form()
form.append('_id', String(admin._id))
form.append('email', joe.get('email').toUpperCase())
it 'does not care if you include your existing name', (done) -> it 'does not care if you include your existing name', (done) ->
unittest.getNormalJoe (joe) -> unittest.getNormalJoe (joe) ->
req = request.put getURL(urlUser+'/'+joe._id), (err, res) -> json = { _id: joe._id, name: 'Joe' }
request.put { url: getURL(urlUser+'/'+joe._id), json }, (err, res) ->
expect(res.statusCode).toBe(200) expect(res.statusCode).toBe(200)
done() done()
form = req.form()
form.append('_id', String(joe._id))
form.append('name', 'Joe')
it 'accepts name and email changes', (done) -> it 'accepts name and email changes', (done) ->
unittest.getNormalJoe (joe) -> unittest.getNormalJoe (joe) ->
req = request.put getURL(urlUser), (err, res) -> json = {
_id: joe.id
email: 'New@email.com'
name: 'Wilhelm'
}
request.put { url: getURL(urlUser), json }, (err, res) ->
expect(res.statusCode).toBe(200) expect(res.statusCode).toBe(200)
unittest.getUser('Wilhelm', 'New@email.com', 'null', (joe) -> unittest.getUser('Wilhelm', 'New@email.com', 'null', (joe) ->
expect(joe.get('name')).toBe('Wilhelm') expect(joe.get('name')).toBe('Wilhelm')
expect(joe.get('emailLower')).toBe('new@email.com') expect(joe.get('emailLower')).toBe('new@email.com')
expect(joe.get('email')).toBe('New@email.com') expect(joe.get('email')).toBe('New@email.com')
done()) done())
form = req.form()
form.append('_id', String(joe._id))
form.append('email', 'New@email.com')
form.append('name', 'Wilhelm')
it 'should not allow two users with the same name slug', (done) -> it 'should not allow two users with the same name slug', (done) ->
loginSam (sam) -> loginSam (sam) ->
@ -189,7 +180,8 @@ ghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghl
it 'should silently rename an anonymous user if their name conflicts upon signup', (done) -> it 'should silently rename an anonymous user if their name conflicts upon signup', (done) ->
request.post getURL('/auth/logout'), -> request.post getURL('/auth/logout'), ->
request.get getURL('/auth/whoami'), -> request.get getURL('/auth/whoami'), ->
req = request.post getURL('/db/user'), (err, response) -> json = { name: 'admin' }
request.post { url: getURL('/db/user'), json }, (err, response) ->
expect(response.statusCode).toBe(200) expect(response.statusCode).toBe(200)
request.get getURL('/auth/whoami'), (err, response) -> request.get getURL('/auth/whoami'), (err, response) ->
expect(err).toBeNull() expect(err).toBeNull()
@ -205,8 +197,6 @@ ghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghl
expect(finalGuy.name).not.toEqual guy.name expect(finalGuy.name).not.toEqual guy.name
expect(finalGuy.name.length).toBe guy.name.length + 1 expect(finalGuy.name.length).toBe guy.name.length + 1
done() done()
form = req.form()
form.append('name', 'admin')
it 'should be able to unset a slug by setting an empty name', (done) -> it 'should be able to unset a slug by setting an empty name', (done) ->
loginSam (sam) -> loginSam (sam) ->
@ -467,13 +457,13 @@ describe 'PUT /db/user/-/remain-teacher', ->
describe 'GET /db/user', -> describe 'GET /db/user', ->
it 'logs in as admin', (done) -> it 'logs in as admin', (done) ->
req = request.post(getURL('/auth/login'), (error, response) -> json = {
username: 'admin@afc.com'
password: '80yqxpb38j'
}
request.post { url: getURL('/auth/login'), json }, (error, response) ->
expect(response.statusCode).toBe(200) expect(response.statusCode).toBe(200)
done() done()
)
form = req.form()
form.append('username', 'admin@afc.com')
form.append('password', '80yqxpb38j')
it 'get schema', (done) -> it 'get schema', (done) ->
request.get {uri: getURL(urlUser+'/schema')}, (err, res, body) -> request.get {uri: getURL(urlUser+'/schema')}, (err, res, body) ->
@ -523,7 +513,7 @@ describe 'GET /db/user', ->
# TODO Ruben should be able to fetch other users but probably with restricted data access # TODO Ruben should be able to fetch other users but probably with restricted data access
# Add to the test case above an extra data check # Add to the test case above an extra data check
xit 'can fetch another user with restricted fields' # xit 'can fetch another user with restricted fields'
describe 'GET /db/user/:handle', -> describe 'GET /db/user/:handle', ->

View file

@ -77,4 +77,4 @@ module.exports.teardownNock = ->
before = (scope) -> before = (scope) ->
scope.body = (body) -> true scope.body = (body) -> true
Promise.promisifyAll(module.exports) Promise.promisifyAll(module.exports)

View file

@ -1,77 +1,77 @@
GLOBAL._ = require 'lodash' #GLOBAL._ = require 'lodash'
#
require '../common' #require '../common'
AnalyticsUsersActive = require '../../../server/models/AnalyticsUsersActive' #AnalyticsUsersActive = require '../../../server/models/AnalyticsUsersActive'
LevelSession = require '../../../server/models/LevelSession' #LevelSession = require '../../../server/models/LevelSession'
User = require '../../../server/models/User' #User = require '../../../server/models/User'
mongoose = require 'mongoose' #mongoose = require 'mongoose'
#
# TODO: these tests have some rerun/cleanup issues ## TODO: these tests have some rerun/cleanup issues
# TODO: add tests for purchase, payment, subscribe, unsubscribe, and earned achievements ## TODO: add tests for purchase, payment, subscribe, unsubscribe, and earned achievements
#
# TODO: AnalyticsUsersActive collection isn't currently used. ## TODO: AnalyticsUsersActive collection isn't currently used.
# TODO: Will remove these tests if we end up ripping out the disabled saveActiveUser calls. ## TODO: Will remove these tests if we end up ripping out the disabled saveActiveUser calls.
#
describe 'Analytics', -> #describe 'Analytics', ->
#
xit 'registered user', (done) -> # xit 'registered user', (done) ->
clearModels [AnalyticsUsersActive], (err) -> # clearModels [AnalyticsUsersActive], (err) ->
expect(err).toBeNull() # expect(err).toBeNull()
user = new User # user = new User
permissions: [] # permissions: []
name: "Fred" + Math.floor(Math.random() * 10000) # name: "Fred" + Math.floor(Math.random() * 10000)
user.save (err) -> # user.save (err) ->
expect(err).toBeNull() # expect(err).toBeNull()
userID = mongoose.Types.ObjectId(user.get('_id')) # userID = mongoose.Types.ObjectId(user.get('_id'))
AnalyticsUsersActive.find {creator : userID}, (err, activeUsers) -> # AnalyticsUsersActive.find {creator : userID}, (err, activeUsers) ->
expect(activeUsers.length).toEqual(0) # expect(activeUsers.length).toEqual(0)
user.register -> # user.register ->
AnalyticsUsersActive.find {creator : userID}, (err, activeUsers) -> # AnalyticsUsersActive.find {creator : userID}, (err, activeUsers) ->
expect(err).toBeNull() # expect(err).toBeNull()
expect(activeUsers.length).toEqual(1) # expect(activeUsers.length).toEqual(1)
expect(activeUsers[0]?.get('event')).toEqual('register') # expect(activeUsers[0]?.get('event')).toEqual('register')
done() # done()
#
xit 'level completed', (done) -> # xit 'level completed', (done) ->
clearModels [AnalyticsUsersActive], (err) -> # clearModels [AnalyticsUsersActive], (err) ->
expect(err).toBeNull() # expect(err).toBeNull()
unittest.getNormalJoe (joe) -> # unittest.getNormalJoe (joe) ->
userID = mongoose.Types.ObjectId(joe.get('_id')) # userID = mongoose.Types.ObjectId(joe.get('_id'))
session = new LevelSession # session = new LevelSession
name: 'Beat Gandalf' # name: 'Beat Gandalf'
levelID: 'lotr' # levelID: 'lotr'
permissions: simplePermissions # permissions: simplePermissions
state: complete: false # state: complete: false
creator: userID # creator: userID
session.save (err) -> # session.save (err) ->
expect(err).toBeNull() # expect(err).toBeNull()
AnalyticsUsersActive.find {creator : userID}, (err, activeUsers) -> # AnalyticsUsersActive.find {creator : userID}, (err, activeUsers) ->
expect(activeUsers.length).toEqual(0) # expect(activeUsers.length).toEqual(0)
session.set 'state', complete: true # session.set 'state', complete: true
session.save (err) -> # session.save (err) ->
expect(err).toBeNull() # expect(err).toBeNull()
AnalyticsUsersActive.find {creator : userID}, (err, activeUsers) -> # AnalyticsUsersActive.find {creator : userID}, (err, activeUsers) ->
expect(err).toBeNull() # expect(err).toBeNull()
expect(activeUsers.length).toEqual(1) # expect(activeUsers.length).toEqual(1)
expect(activeUsers[0]?.get('event')).toEqual('level-completed/lotr') # expect(activeUsers[0]?.get('event')).toEqual('level-completed/lotr')
done() # done()
#
xit 'level playtime', (done) -> # xit 'level playtime', (done) ->
clearModels [AnalyticsUsersActive], (err) -> # clearModels [AnalyticsUsersActive], (err) ->
expect(err).toBeNull() # expect(err).toBeNull()
unittest.getNormalJoe (joe) -> # unittest.getNormalJoe (joe) ->
userID = mongoose.Types.ObjectId(joe.get('_id')) # userID = mongoose.Types.ObjectId(joe.get('_id'))
session = new LevelSession # session = new LevelSession
name: 'Beat Gandalf' # name: 'Beat Gandalf'
levelID: 'lotr' # levelID: 'lotr'
permissions: simplePermissions # permissions: simplePermissions
playtime: 60 # playtime: 60
creator: userID # creator: userID
session.save (err) -> # session.save (err) ->
expect(err).toBeNull() # expect(err).toBeNull()
AnalyticsUsersActive.find {creator : userID}, (err, activeUsers) -> # AnalyticsUsersActive.find {creator : userID}, (err, activeUsers) ->
expect(err).toBeNull() # expect(err).toBeNull()
expect(activeUsers.length).toEqual(1) # expect(activeUsers.length).toEqual(1)
expect(activeUsers[0]?.get('event')).toEqual('level-playtime/lotr') # expect(activeUsers[0]?.get('event')).toEqual('level-playtime/lotr')
done() # done()
#

View file

@ -36,7 +36,6 @@ describe 'CoursesHelper', ->
describe 'progressData.get({classroom, course})', -> describe 'progressData.get({classroom, course})', ->
it 'returns object with .completed=true and .started=true', -> it 'returns object with .completed=true and .started=true', ->
progressData = helper.calculateAllProgress(@classrooms, @courses, @courseInstances, @members) progressData = helper.calculateAllProgress(@classrooms, @courses, @courseInstances, @members)
console.log 'progress data?', progressData
progress = progressData.get {@classroom, @course} progress = progressData.get {@classroom, @course}
expect(progress.completed).toBe true expect(progress.completed).toBe true
expect(progress.started).toBe true expect(progress.started).toBe true

View file

@ -66,16 +66,6 @@ describe 'EnrollmentsView', ->
fail('There should be an #action-col, other tests depend on it.') fail('There should be an #action-col, other tests depend on it.')
describe '"Get Licenses" area', -> describe '"Get Licenses" area', ->
describe '"Contact Us" button', ->
it 'opens a TeachersContactModal, passing in the number of licenses', ->
spyOn(@view, 'openModalView')
@view.state.set('numberOfStudents', 20)
@view.$('#contact-us-btn').click()
expect(view.openModalView).toHaveBeenCalled()
args = view.openModalView.calls.argsFor(0)
expect(args[0] instanceof TeachersContactModal).toBe(true)
expect(args[0].enrollmentsNeeded).toBe(20)
describe 'when the teacher has made contact', -> describe 'when the teacher has made contact', ->
beforeEach -> beforeEach ->

View file

@ -4,18 +4,28 @@ factories = require 'test/app/factories'
describe 'TeachersContactModal', -> describe 'TeachersContactModal', ->
beforeEach (done) -> beforeEach (done) ->
@modal = new TeachersContactModal({ enrollmentsNeeded: 10 }) @modal = new TeachersContactModal()
@modal.render() @modal.render()
trialRequests = new TrialRequests([factories.makeTrialRequest()]) trialRequests = new TrialRequests([factories.makeTrialRequest()])
@modal.trialRequests.fakeRequests[0].respondWith({ status: 200, responseText: trialRequests.stringify() }) @modal.trialRequests.fakeRequests[0].respondWith({ status: 200, responseText: trialRequests.stringify() })
@modal.supermodel.once('loaded-all', done) @modal.supermodel.once('loaded-all', done)
jasmine.demoModal(@modal) jasmine.demoModal(@modal)
it 'shows an error when the name is empty and the form is submitted', ->
@modal.$('input[name="name"]').val('')
@modal.$('form').submit()
expect(@modal.$('input[name="name"]').closest('.form-group').hasClass('has-error')).toBe(true)
it 'shows an error when the email is invalid and the form is submitted', -> it 'shows an error when the email is invalid and the form is submitted', ->
@modal.$('input[name="email"]').val('not an email') @modal.$('input[name="email"]').val('not an email')
@modal.$('form').submit() @modal.$('form').submit()
expect(@modal.$('input[name="email"]').closest('.form-group').hasClass('has-error')).toBe(true) expect(@modal.$('input[name="email"]').closest('.form-group').hasClass('has-error')).toBe(true)
it 'shows an error when licensesNeeded is not > 0 and the form is submitted', ->
@modal.$('input[name="licensesNeeded"]').val('')
@modal.$('form').submit()
expect(@modal.$('input[name="licensesNeeded"]').closest('.form-group').hasClass('has-error')).toBe(true)
it 'shows an error when the message is empty and the form is submitted', -> it 'shows an error when the message is empty and the form is submitted', ->
@modal.$('textarea[name="message"]').val('') @modal.$('textarea[name="message"]').val('')
@modal.$('form').submit() @modal.$('form').submit()