Пример #1
0
    def test_aggregate_queues_tasks(self):
        cron = Cron(self.internal_api)
        queue = taskqueue.Queue(name='default')

        # Since this test inherits a populated stub datastore, some tasks
        # have already been queued. Set that as the baseline.
        baseline = queue.fetch_statistics().tasks

        # Now aggregate the entities that have been pre-populated, which
        # includes one classroom and one cohort. This should queue 3 tasks: one
        # classroom roster, one cohort roster, and one cohort schedule.
        cron.aggregate()

        post_aggregation = queue.fetch_statistics().tasks
        self.assertEqual(post_aggregation - baseline, 3)
Пример #2
0
    def test_aggregate_handles_duplicates(self):
        """If multiple progress pd, aggregator chooses the largest one."""
        # Create w/o the api to intentionally create duplicates
        pd_id1 = 'Pd_1.' + self.student.id
        pd_id2 = 'Pd_2.' + self.student.id
        pd_id3 = 'Pd_3.' + self.student.id
        pd_id4 = 'Pd_4.' + self.student.id
        pd1 = Pd(key_name=pd_id1,
                 id=pd_id1,
                 parent=self.student,
                 scope=self.student.id,
                 program=self.program.id,
                 activity_ordinal=1,
                 variable='s1__progress',
                 value='66',
                 public=True)
        pd2 = Pd(key_name=pd_id2,
                 id=pd_id2,
                 parent=self.student,
                 scope=self.student.id,
                 program=self.program.id,
                 activity_ordinal=1,
                 variable='s1__progress',
                 value='33',
                 public=True)
        pd3 = Pd(key_name=pd_id3,
                 id=pd_id3,
                 parent=self.student,
                 scope=self.student.id,
                 program=self.program.id,
                 activity_ordinal=2,
                 variable='s2__progress',
                 value='100',
                 public=True)
        pd4 = Pd(key_name=pd_id4,
                 id=pd_id4,
                 parent=self.student,
                 scope=self.student.id,
                 program=self.program.id,
                 activity_ordinal=2,
                 variable='s2__progress',
                 value='66',
                 public=True)

        # Put them in a confusing order on purpose, to try to get the
        # aggregator to process them from largest to smallest.
        db.put([pd1, pd3])
        db.put([pd2, pd4])

        # Prove that there are duplicates.
        duplicates = self.student_api.get('pd', {}, ancestor=self.student)
        self.assertEquals(len(duplicates), 4)

        # Aggregate and check results.
        cron = Cron(self.internal_api)
        cron.aggregate()
        student = db.get(self.student.key())
        self.assertEquals(student.aggregation_data, {
            1: {
                'progress': 66
            },
            2: {
                'progress': 100
            }
        })
        # Student should also have COM for s2 b/c they hit 100.
        self.assertEquals(student.get_status_code(2), 'COM')
Пример #3
0
    def test_aggregate(self):
        """Test aggregation of pds to activities."""
        cron = Cron(self.internal_api)

        # To insulate the expected aggregation stats from changes to the
        # populate script, we'll create a separate cohort and classroom. For
        # larger things we'll rely on the stuff set by the populate script,
        # e.g. self.program.
        cohort = self.researcher_api.create(
            'cohort', {
                'name': 'DGN 2015',
                'code': 'lion mackerel',
                'program': self.program.id,
                'school': self.school.id,
            })
        self.researcher_api.associate('set_owner', self.school_admin, cohort)
        classroom = self.school_admin_api.create(
            'classroom', {
                'name': "English 201",
                'user': self.school_admin.id,
                'program': self.program.id,
                'cohort': cohort.id,
            })
        student_activities = self.school_admin_api.init_activities(
            'student',
            self.school_admin.id,
            self.program.id,
            cohort_id=cohort.id,
            classroom_id=classroom.id)
        db.get([cohort.key(), classroom.key()])
        db.get([a.key() for a in student_activities])

        # To test aggregating across multiple users, we'll need several
        # students
        student_params = {'user_type': 'student', 'classroom': classroom.id}

        mystery_finisher = self.public_api.create('user', student_params)
        absentee = self.public_api.create('user', student_params)
        refusee = self.public_api.create('user', student_params)
        expelee = self.public_api.create('user', student_params)
        mr_perfect = self.public_api.create('user', student_params)
        non_finisher = self.public_api.create('user', student_params)
        wrong_name = self.public_api.create('user', student_params)

        # This student will be in another classroom, and we won't update her,
        # proving that cohort aggregation re-queries more than just the changed
        # stuff.
        other_classroom = self.school_admin_api.create(
            'classroom', {
                'name': "English 202",
                'user': self.school_admin.id,
                'program': self.program.id,
                'cohort': cohort.id,
            })
        other_student_activities = self.school_admin_api.init_activities(
            'student',
            self.school_admin.id,
            self.program.id,
            cohort_id=cohort.id,
            classroom_id=other_classroom.id)
        other_student = self.public_api.create('user', {
            'user_type': 'student',
            'classroom': other_classroom.id
        })

        students = [
            mystery_finisher, absentee, refusee, expelee, mr_perfect,
            non_finisher, wrong_name
        ]
        student_keys = [s.key() for s in students]

        others = [other_student, other_classroom] + other_student_activities
        other_keys = [e.key() for e in others]

        ### Aggregate initial state

        # Assume and simulate that enough time passes between data recording
        # and cron execution that entities become consistent.
        db.get(student_keys)
        db.get(other_keys)

        cron.aggregate()

        # Every student have the same aggregation data for both activities
        # because no one has done anything yet. So just loop and check against
        # the same reference.
        for s in db.get(student_keys):
            self.assertFalse(s.certified)
            self.assertEqual(s.aggregation_data, {})

        # Both activities should be the same also
        a1, a2 = db.get([a.key() for a in student_activities])
        correct_stats = {
            'total_students': 7,
            'certified_students': 0,
            'certified_study_eligible_dict': {
                'n': 0,
                'completed': 0,
                'makeup_eligible': 0,
                'makeup_ineligible': 0,
                'uncoded': 0
            },
        }
        self.assertEqual(a1.aggregation_data, correct_stats)
        self.assertEqual(a2.aggregation_data, correct_stats)

        # The other activities should look like this (this is the last time
        # we'll have to check it because we won't be changing it any more):
        a1, a2 = db.get([a.key() for a in other_student_activities])
        correct_stats = {
            'total_students': 1,
            'certified_students': 0,
            'certified_study_eligible_dict': {
                'n': 0,
                'completed': 0,
                'makeup_eligible': 0,
                'makeup_ineligible': 0,
                'uncoded': 0
            },
        }
        self.assertEqual(a1.aggregation_data, correct_stats)
        self.assertEqual(a2.aggregation_data, correct_stats)

        # Check cohort (has our seven plus one other)
        cohort = db.get(cohort.key())
        correct_cohort_stats = {
            'unscheduled': 2,
            'scheduled': 0,
            'behind': 0,
            'completed': 0,
            'incomplete_rosters': 2,
            'total_students': 8,
            'certified_students': 0,
            'certified_study_eligible_dict': {
                'n': 0,
                'completed': 0,
                'makeup_eligible': 0,
                'makeup_ineligible': 0,
                'uncoded': 0
            },
        }
        self.assertEqual(cohort.aggregation_data[1], correct_cohort_stats)
        self.assertEqual(cohort.aggregation_data[2], correct_cohort_stats)

        ### Pretend the school admin just certified some students and aggregate
        ### again.

        # NOT changing mystery_finisher proves that the aggregator re-queries
        # for unchanged users associated with the same activity.
        certified_students = [
            absentee, refusee, expelee, mr_perfect, non_finisher
        ]
        for s in certified_students:
            s.certified = True
        db.put(certified_students)

        # Assume and simulate that enough time passes between data recording
        # and cron execution that entities become consistent.
        db.get(student_keys)

        cron.aggregate()

        # Every student should be the same for both activities.
        for s in db.get(student_keys):
            self.assertEqual(s.aggregation_data, {})

        # Both activities should be the same also
        a1, a2 = db.get([a.key() for a in student_activities])
        correct_stats = {
            'total_students': 7,
            'certified_students': 5,
            'certified_study_eligible_dict': {
                'n': 5,
                'completed': 0,
                'makeup_eligible': 0,
                'makeup_ineligible': 0,
                'uncoded': 5
            },
        }
        self.assertEqual(a1.aggregation_data, correct_stats)
        self.assertEqual(a2.aggregation_data, correct_stats)

        # Check cohort
        cohort = db.get(cohort.key())
        correct_cohort_stats = {
            'unscheduled': 2,
            'scheduled': 0,
            'behind': 0,
            'completed': 0,
            'incomplete_rosters': 2,
            'total_students': 8,
            'certified_students': 5,
            'certified_study_eligible_dict': {
                'n': 5,
                'completed': 0,
                'makeup_eligible': 0,
                'makeup_ineligible': 0,
                'uncoded': 5
            },
        }
        self.assertEqual(cohort.aggregation_data[1], correct_cohort_stats)
        self.assertEqual(cohort.aggregation_data[2], correct_cohort_stats)

        ### Simulate the first session, with two students absent and one who
        ### doesn't finish. Also schedule the first activity.

        absentee.status_codes[1] = 'A'  # absent
        refusee.status_codes[1] = 'PR'  # parent refusal
        expelee.status_codes[1] = 'E'  # expelled
        wrong_name.status_codes[1] = 'MWN'  # merge: wrong name
        db.put([absentee, refusee, expelee, wrong_name])

        progress_pds = []
        pd_params = {
            'variable': 's1__progress',
            'program': self.program.id,
            'activity': student_activities[0].id,
            'activity_ordinal': 1,
        }
        # Progress on activity 1 for those who finished.
        for s in [mr_perfect, mystery_finisher, wrong_name]:
            pd_params['value'] = '100'
            pd_params['scope'] = s.id
            progress_pds.append(Api(s).create('pd', pd_params))
        # Progress on activity 1 for those who didn't finish.
        pd_params['value'] = '50'
        pd_params['scope'] = non_finisher.id
        progress_pds.append(Api(non_finisher).create('pd', pd_params))

        a1.scheduled_date = datetime.date.today()
        a1.put()

        # Assume and simulate that enough time passes between data recording
        # and cron execution that entities become consistent.
        db.get([pd.key() for pd in progress_pds] +
               [absentee.key(),
                refusee.key(),
                expelee.key(),
                a1.key()])

        cron.aggregate()

        # Check that user stats are right.
        correct_stats = [
            {
                'progress': 100
            },  # mystery_finisher
            None,  # absentee
            None,  # refusee
            None,  # expelee
            {
                'progress': 100
            },  # mr_perfect
            {
                'progress': 50
            },  # non_finisher
            {
                'progress': 100
            },  # wrong_name
        ]
        for index, s in enumerate(students):
            s = db.get(s.key())
            if correct_stats[index] is None:
                self.assertEqual(s.aggregation_data, {})
            else:
                self.assertEqual(s.aggregation_data[1], correct_stats[index])

        # Check that activity stats are right.
        a1 = db.get(student_activities[0].key())
        correct_stats = {
            # Total has decreased b/c MWN students are dropped from the counts
            # completely. This is because they're not really a person, they're
            # a duplicate representation of a different real person.
            'total_students': 6,
            'certified_students': 5,
            'certified_study_eligible_dict': {
                'n': 4,
                'completed': 1,
                'makeup_eligible': 1,
                'makeup_ineligible': 1,
                'uncoded': 1
            },
        }
        self.assertEqual(a1.aggregation_data, correct_stats)
        # Activity 2 shouldn't register any of the progress we've made on
        # activity 1.
        a2 = db.get(student_activities[1].key())
        correct_stats = {
            'total_students': 6,
            'certified_students': 5,
            'certified_study_eligible_dict': {
                'n': 5,
                'completed': 0,
                'makeup_eligible': 0,
                'makeup_ineligible': 0,
                'uncoded': 5
            },
        }
        self.assertEqual(a2.aggregation_data, correct_stats)

        # Check cohort (again, similar, but with a larger 'all' total).
        cohort = db.get(cohort.key())
        correct_cohort_stats = {
            1: {
                'unscheduled': 1,
                'scheduled': 1,
                'behind': 0,
                'completed': 0,
                'incomplete_rosters': 2,
                'total_students': 7,
                'certified_students': 5,
                'certified_study_eligible_dict': {
                    'n': 4,
                    'completed': 1,
                    'makeup_eligible': 1,
                    'makeup_ineligible': 1,
                    'uncoded': 1
                },
            },
            2: {
                'unscheduled': 2,
                'scheduled': 0,
                'behind': 0,
                'completed': 0,
                'incomplete_rosters': 2,
                'total_students': 7,
                'certified_students': 5,
                'certified_study_eligible_dict': {
                    'n': 5,
                    'completed': 0,
                    'makeup_eligible': 0,
                    'makeup_ineligible': 0,
                    'uncoded': 5
                },
            }
        }
        self.assertEqual(cohort.aggregation_data, correct_cohort_stats)
Пример #4
0
    def test_aggregate_more_than_thirty_classrooms(self):
        """Aggregation uses an IN filter, which breaks App Engine when it has
        more than 30 elements in it. There should be code to handle this."""

        cron = Cron(self.internal_api)

        # To insulate the expected aggregation stats from changes to the
        # populate script, we'll create a separate cohort and classroom. For
        # larger things we'll rely on the stuff set by the populate script,
        # e.g. self.program.
        cohort = self.researcher_api.create(
            'cohort', {
                'name': 'DGN 2015',
                'code': 'lion mackerel',
                'program': self.program.id,
                'school': self.school.id,
            })
        db.get(cohort.key())
        self.researcher_api.associate('set_owner', self.school_admin, cohort)

        # Create 31 different students in as many different classrooms.
        classrooms, student_activities, students = [], [], []
        for x in range(30):
            c = self.school_admin_api.create(
                'classroom', {
                    'name': "English " + str(x),
                    'user': self.school_admin.id,
                    'program': self.program.id,
                    'cohort': cohort.id,
                })
            c = db.get(c.key())

            acts = self.school_admin_api.init_activities('student',
                                                         self.school_admin.id,
                                                         self.program.id,
                                                         cohort_id=cohort.id,
                                                         classroom_id=c.id)
            acts = db.get([a.key() for a in acts])

            s = self.public_api.create('user', {
                'user_type': 'student',
                'classroom': c.id
            })
            classrooms.append(c)
            student_activities += acts
            students.append(s)

        # Bring them all into full db consistency.
        db.get([s.key() for s in students])

        cron.aggregate()

        # All activities should show one student.
        student_activities = db.get([a.key() for a in student_activities])
        correct_stats = {
            'total_students': 1,
            'certified_students': 0,
            'certified_study_eligible_dict': {
                'n': 0,
                'completed': 0,
                'makeup_eligible': 0,
                'makeup_ineligible': 0,
                'uncoded': 0
            },
        }
        for a in student_activities:
            self.assertEqual(a.aggregation_data, correct_stats)
Пример #5
0
    def test_aggregation_does_not_change_modified_time(self):
        cron = Cron(self.internal_api)

        # Create a fake set of data to aggregate: A cohort, classroom,
        # student activities, a student, and a pd value for that student.
        cohort = self.researcher_api.create(
            'cohort', {
                'name': 'DGN 2015',
                'code': 'lion mackerel',
                'program': self.program.id,
                'school': self.school.id,
            })
        self.researcher_api.associate('set_owner', self.school_admin, cohort)
        classroom = self.school_admin_api.create(
            'classroom', {
                'name': "English 201",
                'user': self.school_admin.id,
                'program': self.program.id,
                'cohort': cohort.id,
            })
        student_activities = self.school_admin_api.init_activities(
            'student',
            self.school_admin.id,
            self.program.id,
            cohort_id=cohort.id,
            classroom_id=classroom.id)
        db.get([cohort.key(), classroom.key()])
        db.get([a.key() for a in student_activities])
        student_params = {'user_type': 'student', 'classroom': classroom.id}
        student = self.public_api.create('user', student_params)
        student = db.get(student.key())
        pd_params = {
            'variable': 's1__progress',
            'program': self.program.id,
            'activity': student_activities[0].id,
            'activity_ordinal': 1,
            'value': 100,
            'scope': student.id,
        }
        pd = Api(student).create('pd', pd_params)
        db.get(pd.key())

        # First prove that modified times ARE set in this context for normal
        # writes. The student's progress has NOT been written to the user
        # entity yet.
        modified_before = student.modified
        time.sleep(0.1)
        student.first_name = 'mister'
        student.put()
        modified_after = db.get(student.key()).modified
        self.assertEquals(student.aggregation_data, {
            1: {
                'progress': None
            },
            2: {
                'progress': None
            }
        })
        self.assertNotEqual(modified_before, modified_after)

        # Now aggregate, which should write the pd's progress value to the
        # user, but should NOT update the user's modified time.
        modified_before = modified_after
        time.sleep(0.1)
        cron.aggregate()
        student = db.get(student.key())
        modified_after = db.get(student.key()).modified
        self.assertEquals(student.aggregation_data, {
            1: {
                'progress': 100
            },
            2: {
                'progress': None
            }
        })
        self.assertEquals(modified_before, modified_after)