236 lines
9.5 KiB
Python
236 lines
9.5 KiB
Python
# Copyright The IETF Trust 2016-2020, All Rights Reserved
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
import calendar
|
|
import datetime
|
|
import json
|
|
|
|
from mock import patch
|
|
from pyquery import PyQuery
|
|
from requests import Response
|
|
|
|
import debug # pyflakes:ignore
|
|
|
|
from django.urls import reverse as urlreverse
|
|
|
|
from ietf.utils.test_utils import login_testing_unauthorized, TestCase
|
|
import ietf.stats.views
|
|
|
|
|
|
from ietf.group.factories import RoleFactory
|
|
from ietf.meeting.factories import MeetingFactory
|
|
from ietf.person.factories import PersonFactory
|
|
from ietf.review.factories import ReviewRequestFactory, ReviewerSettingsFactory, ReviewAssignmentFactory
|
|
from ietf.stats.models import MeetingRegistration
|
|
from ietf.stats.tasks import fetch_meeting_attendance_task
|
|
from ietf.stats.utils import get_meeting_registration_data, FetchStats, fetch_attendance_from_meetings
|
|
from ietf.utils.timezone import date_today
|
|
|
|
|
|
class StatisticsTests(TestCase):
|
|
def test_stats_index(self):
|
|
url = urlreverse(ietf.stats.views.stats_index)
|
|
r = self.client.get(url)
|
|
self.assertEqual(r.status_code, 200)
|
|
|
|
def test_document_stats(self):
|
|
r = self.client.get(urlreverse("ietf.stats.views.document_stats"))
|
|
self.assertRedirects(r, urlreverse("ietf.stats.views.stats_index"))
|
|
|
|
|
|
def test_meeting_stats(self):
|
|
r = self.client.get(urlreverse("ietf.stats.views.meeting_stats"))
|
|
self.assertRedirects(r, urlreverse("ietf.stats.views.stats_index"))
|
|
|
|
|
|
def test_known_country_list(self):
|
|
# check redirect
|
|
url = urlreverse(ietf.stats.views.known_countries_list)
|
|
|
|
r = self.client.get(url)
|
|
self.assertEqual(r.status_code, 200)
|
|
self.assertContains(r, "United States")
|
|
|
|
def test_review_stats(self):
|
|
reviewer = PersonFactory()
|
|
review_req = ReviewRequestFactory(state_id='assigned')
|
|
ReviewAssignmentFactory(review_request=review_req, state_id='assigned', reviewer=reviewer.email_set.first())
|
|
RoleFactory(group=review_req.team,name_id='reviewer',person=reviewer)
|
|
ReviewerSettingsFactory(team=review_req.team, person=reviewer)
|
|
PersonFactory(user__username='plain')
|
|
|
|
# check redirect
|
|
url = urlreverse(ietf.stats.views.review_stats)
|
|
|
|
login_testing_unauthorized(self, "secretary", url)
|
|
|
|
completion_url = urlreverse(ietf.stats.views.review_stats, kwargs={ "stats_type": "completion" })
|
|
|
|
r = self.client.get(url)
|
|
self.assertEqual(r.status_code, 302)
|
|
self.assertTrue(completion_url in r["Location"])
|
|
|
|
self.client.logout()
|
|
self.client.login(username="plain", password="plain+password")
|
|
r = self.client.get(completion_url)
|
|
self.assertEqual(r.status_code, 403)
|
|
|
|
# check tabular
|
|
self.client.login(username="secretary", password="secretary+password")
|
|
for stats_type in ["completion", "results", "states"]:
|
|
url = urlreverse(ietf.stats.views.review_stats, kwargs={ "stats_type": stats_type })
|
|
r = self.client.get(url)
|
|
self.assertEqual(r.status_code, 200)
|
|
q = PyQuery(r.content)
|
|
if stats_type != "results":
|
|
self.assertTrue(q('.review-stats td:contains("1")'))
|
|
|
|
# check stacked chart
|
|
expected_date = date_today().replace(day=1)
|
|
expected_js_timestamp = calendar.timegm(expected_date.timetuple()) * 1000
|
|
url = urlreverse(ietf.stats.views.review_stats, kwargs={ "stats_type": "time" })
|
|
url += "?team={}".format(review_req.team.acronym)
|
|
r = self.client.get(url)
|
|
self.assertEqual(r.status_code, 200)
|
|
self.assertEqual(json.loads(r.context['data']), [
|
|
{"label": "in time", "color": "#3d22b3", "data": [[expected_js_timestamp, 0]]},
|
|
{"label": "late", "color": "#b42222", "data": [[expected_js_timestamp, 0]]}
|
|
])
|
|
q = PyQuery(r.content)
|
|
self.assertTrue(q('#stats-time-graph'))
|
|
|
|
# check non-stacked chart
|
|
url = urlreverse(ietf.stats.views.review_stats, kwargs={ "stats_type": "time" })
|
|
url += "?team={}".format(review_req.team.acronym)
|
|
url += "&completion=not_completed"
|
|
r = self.client.get(url)
|
|
self.assertEqual(r.status_code, 200)
|
|
self.assertEqual(json.loads(r.context['data']), [{"color": "#3d22b3", "data": [[expected_js_timestamp, 0]]}])
|
|
q = PyQuery(r.content)
|
|
self.assertTrue(q('#stats-time-graph'))
|
|
|
|
# check reviewer level
|
|
url = urlreverse(ietf.stats.views.review_stats, kwargs={ "stats_type": "completion", "acronym": review_req.team.acronym })
|
|
r = self.client.get(url)
|
|
self.assertEqual(r.status_code, 200)
|
|
q = PyQuery(r.content)
|
|
self.assertTrue(q('.review-stats td:contains("1")'))
|
|
|
|
@patch('requests.get')
|
|
def test_get_meeting_registration_data(self, mock_get):
|
|
'''Test function to get reg data. Confirm leading/trailing spaces stripped'''
|
|
person = PersonFactory()
|
|
data = {
|
|
'LastName': person.last_name() + ' ',
|
|
'FirstName': person.first_name(),
|
|
'Company': 'ABC',
|
|
'Country': 'US',
|
|
'Email': person.email().address,
|
|
'RegType': 'onsite',
|
|
'TicketType': 'week_pass',
|
|
'CheckedIn': 'True',
|
|
}
|
|
data2 = data.copy()
|
|
data2['RegType'] = 'hackathon'
|
|
response_a = Response()
|
|
response_a.status_code = 200
|
|
response_a._content = json.dumps([data, data2]).encode('utf8')
|
|
# second response one less record, it's been deleted
|
|
response_b = Response()
|
|
response_b.status_code = 200
|
|
response_b._content = json.dumps([data]).encode('utf8')
|
|
# mock_get.return_value = response
|
|
mock_get.side_effect = [response_a, response_b]
|
|
meeting = MeetingFactory(type_id='ietf', date=datetime.date(2016, 7, 14), number="96")
|
|
get_meeting_registration_data(meeting)
|
|
query = MeetingRegistration.objects.filter(
|
|
first_name=person.first_name(),
|
|
last_name=person.last_name(),
|
|
country_code='US')
|
|
self.assertEqual(query.count(), 2)
|
|
self.assertEqual(query.filter(reg_type='onsite').count(), 1)
|
|
self.assertEqual(query.filter(reg_type='hackathon').count(), 1)
|
|
onsite = query.get(reg_type='onsite')
|
|
self.assertEqual(onsite.ticket_type, 'week_pass')
|
|
self.assertEqual(onsite.checkedin, True)
|
|
# call a second time to test delete
|
|
get_meeting_registration_data(meeting)
|
|
query = MeetingRegistration.objects.filter(meeting=meeting, email=person.email())
|
|
self.assertEqual(query.count(), 1)
|
|
self.assertEqual(query.filter(reg_type='onsite').count(), 1)
|
|
self.assertEqual(query.filter(reg_type='hackathon').count(), 0)
|
|
|
|
@patch('requests.get')
|
|
def test_get_meeting_registration_data_duplicates(self, mock_get):
|
|
'''Test that get_meeting_registration_data does not create duplicate
|
|
MeetingRegistration records
|
|
'''
|
|
person = PersonFactory()
|
|
data = {
|
|
'LastName': person.last_name() + ' ',
|
|
'FirstName': person.first_name(),
|
|
'Company': 'ABC',
|
|
'Country': 'US',
|
|
'Email': person.email().address,
|
|
'RegType': 'onsite',
|
|
'TicketType': 'week_pass',
|
|
'CheckedIn': 'True',
|
|
}
|
|
data2 = data.copy()
|
|
data2['RegType'] = 'hackathon'
|
|
response = Response()
|
|
response.status_code = 200
|
|
response._content = json.dumps([data, data2, data]).encode('utf8')
|
|
mock_get.return_value = response
|
|
meeting = MeetingFactory(type_id='ietf', date=datetime.date(2016, 7, 14), number="96")
|
|
self.assertEqual(MeetingRegistration.objects.count(), 0)
|
|
get_meeting_registration_data(meeting)
|
|
query = MeetingRegistration.objects.all()
|
|
self.assertEqual(query.count(), 2)
|
|
|
|
@patch("ietf.stats.utils.get_meeting_registration_data")
|
|
def test_fetch_attendance_from_meetings(self, mock_get_mtg_reg_data):
|
|
mock_meetings = [object(), object(), object()]
|
|
mock_get_mtg_reg_data.side_effect = (
|
|
(1, 2, 3),
|
|
(4, 5, 6),
|
|
(7, 8, 9),
|
|
)
|
|
stats = fetch_attendance_from_meetings(mock_meetings)
|
|
self.assertEqual(
|
|
[mock_get_mtg_reg_data.call_args_list[n][0][0] for n in range(3)],
|
|
mock_meetings,
|
|
)
|
|
self.assertEqual(
|
|
stats,
|
|
[
|
|
FetchStats(1, 2, 3),
|
|
FetchStats(4, 5, 6),
|
|
FetchStats(7, 8, 9),
|
|
]
|
|
)
|
|
|
|
|
|
class TaskTests(TestCase):
|
|
@patch("ietf.stats.tasks.fetch_attendance_from_meetings")
|
|
def test_fetch_meeting_attendance_task(self, mock_fetch_attendance):
|
|
today = date_today()
|
|
meetings = [
|
|
MeetingFactory(type_id="ietf", date=today - datetime.timedelta(days=1)),
|
|
MeetingFactory(type_id="ietf", date=today - datetime.timedelta(days=2)),
|
|
MeetingFactory(type_id="ietf", date=today - datetime.timedelta(days=3)),
|
|
]
|
|
mock_fetch_attendance.return_value = [FetchStats(1,2,3), FetchStats(1,2,3)]
|
|
|
|
fetch_meeting_attendance_task()
|
|
self.assertEqual(mock_fetch_attendance.call_count, 1)
|
|
self.assertCountEqual(mock_fetch_attendance.call_args[0][0], meetings[0:2])
|
|
|
|
# test handling of RuntimeError
|
|
mock_fetch_attendance.reset_mock()
|
|
mock_fetch_attendance.side_effect = RuntimeError
|
|
fetch_meeting_attendance_task()
|
|
self.assertTrue(mock_fetch_attendance.called)
|
|
# Good enough that we got here without raising an exception
|