def test_worktime_balance_with_employments(auth_client, django_assert_num_queries): # Calculate over one week start_date = date(2017, 3, 19) end_date = date(2017, 3, 26) employment = EmploymentFactory.create(user=auth_client.user, start_date=start_date, worktime_per_day=timedelta( hours=8, minutes=30), end_date=date(2017, 3, 23)) EmploymentFactory.create(user=auth_client.user, start_date=date(2017, 3, 24), worktime_per_day=timedelta(hours=8), end_date=None) # Overtime credit of 10 hours OvertimeCreditFactory.create(user=auth_client.user, date=start_date, duration=timedelta(hours=10, minutes=30)) # One public holiday during workdays PublicHolidayFactory.create(date=start_date, location=employment.location) # One public holiday on weekend PublicHolidayFactory.create(date=start_date + timedelta(days=1), location=employment.location) # 2x 10 hour reported worktime ReportFactory.create(user=auth_client.user, date=start_date + timedelta(days=3), duration=timedelta(hours=10)) ReportFactory.create(user=auth_client.user, date=start_date + timedelta(days=4), duration=timedelta(hours=10)) # one absence AbsenceFactory.create(user=auth_client.user, date=start_date + timedelta(days=5)) url = reverse('worktime-balance-detail', args=[ '{0}_{1}'.format(auth_client.user.id, end_date.strftime('%Y-%m-%d')) ]) with django_assert_num_queries(12): result = auth_client.get(url) assert result.status_code == status.HTTP_200_OK # 4 workdays 8.5 hours, 1 workday 8 hours, minus one holiday 8.5 # minutes 10.5 hours overtime credit expected_worktime = timedelta(hours=23) # 2 x 10 reports hours + 1 absence of 8 hours expected_reported = timedelta(hours=28) json = result.json() assert json['data']['attributes']['balance'] == ( duration_string(expected_reported - expected_worktime))
def test_absence_fill_worktime(auth_client): """Should create an absence which fills the worktime.""" date = datetime.date(2017, 5, 10) user = auth_client.user EmploymentFactory.create( user=user, start_date=date, worktime_per_day=datetime.timedelta(hours=8) ) type = AbsenceTypeFactory.create(fill_worktime=True) ReportFactory.create(user=user, date=date, duration=datetime.timedelta(hours=5)) data = { "data": { "type": "absences", "id": None, "attributes": {"date": date.strftime("%Y-%m-%d")}, "relationships": { "type": {"data": {"type": "absence-types", "id": type.id}} }, } } url = reverse("absence-list") response = auth_client.post(url, data) assert response.status_code == status.HTTP_201_CREATED json = response.json() assert json["data"]["attributes"]["duration"] == "03:00:00"
def test_redmine_report(db, freezer, mocker): """ Test redmine report. Simulate reports added on Friday 2017-07-28 and cronjob run on Monday 2017-07-31. """ redmine_instance = mocker.MagicMock() issue = mocker.MagicMock() redmine_instance.issue.get.return_value = issue redmine_class = mocker.patch('redminelib.Redmine') redmine_class.return_value = redmine_instance freezer.move_to('2017-07-28') report = ReportFactory.create(comment='ADSY <=> Other') report_hours = report.duration.total_seconds() / 3600 estimated_hours = report.task.project.estimated_time.total_seconds() / 3600 RedmineProject.objects.create(project=report.task.project, issue_id=1000) # report not attached to redmine ReportFactory.create() freezer.move_to('2017-07-31') call_command('redmine_report', options={'--last-days': '7'}) redmine_instance.issue.get.assert_called_once_with(1000) assert issue.custom_fields == [{'id': 0, 'value': report_hours}] assert 'Total hours: {0}'.format(report_hours) in issue.notes assert 'Estimated hours: {0}'.format(estimated_hours) in issue.notes assert 'Hours in last 7 days: {0}\n'.format(report_hours) in issue.notes assert '{0}\n'.format(report.comment) in issue.notes assert '{0}\n\n'.format(report.comment) not in issue.notes, ( 'Only one new line after report line') issue.save.assert_called_once_with()
def test_report_list(auth_client): user = auth_client.user ReportFactory.create(user=user) report = ReportFactory.create(user=user, duration=timedelta(hours=1)) url = reverse("report-list") response = auth_client.get( url, data={ "date": report.date, "user": user.id, "task": report.task_id, "project": report.task.project_id, "customer": report.task.project.customer_id, "include": ("user,task,task.project,task.project.customer,verified_by"), }, ) assert response.status_code == status.HTTP_200_OK json = response.json() assert len(json["data"]) == 1 assert json["data"][0]["id"] == str(report.id) assert json["meta"]["total-time"] == "01:00:00"
def test_notify_reviewers_with_cc_and_message(db, mailoutbox, cc, message): """Test time range 2017-7-1 till 2017-7-31.""" # a reviewer which will be notified reviewer_work = UserFactory.create() project_work = ProjectFactory.create() project_work.reviewers.add(reviewer_work) task_work = TaskFactory.create(project=project_work) ReportFactory.create(date=date(2017, 7, 1), task=task_work, verified_by=None) # a reviewer which doesn't have any unverfied reports reviewer_no_work = UserFactory.create() project_no_work = ProjectFactory.create() project_no_work.reviewers.add(reviewer_no_work) task_no_work = TaskFactory.create(project=project_no_work) ReportFactory.create(date=date(2017, 7, 1), task=task_no_work, verified_by=reviewer_no_work) call_command( "notify_reviewers_unverified", "--cc={0}".format(cc), "--message={0}".format(message), ) # checks assert len(mailoutbox) == 1 mail = mailoutbox[0] assert mail.to == [reviewer_work.email] url = ("http://localhost:4200/analysis?fromDate=2017-07-01&" "toDate=2017-07-31&reviewer=%d&editable=1") % reviewer_work.id assert url in mail.body assert message in mail.body assert mail.cc[0] == cc
def test_redmine_report(db, freezer, mocker): """ Test redmine report. Simulate reports added on Friday 2017-07-28 and cronjob run on Monday 2017-07-31. """ redmine_instance = mocker.MagicMock() issue = mocker.MagicMock() redmine_instance.issue.get.return_value = issue redmine_class = mocker.patch("redminelib.Redmine") redmine_class.return_value = redmine_instance freezer.move_to("2017-07-28") report = ReportFactory.create(comment="ADSY <=> Other") report_hours = report.duration.total_seconds() / 3600 estimated_hours = report.task.project.estimated_time.total_seconds() / 3600 RedmineProject.objects.create(project=report.task.project, issue_id=1000) # report not attached to redmine ReportFactory.create() freezer.move_to("2017-07-31") call_command("redmine_report", last_days=7) redmine_instance.issue.get.assert_called_once_with(1000) assert issue.custom_fields == [{"id": 0, "value": report_hours}] assert "Total hours: {0}".format(report_hours) in issue.notes assert "Estimated hours: {0}".format(estimated_hours) in issue.notes assert "Hours in last 7 days: {0}\n".format(report_hours) in issue.notes assert "{0}".format(report.comment) in issue.notes assert "{0}\n".format(report.review) in issue.notes assert ("{0}\n\n".format(report.comment) not in issue.notes), "Only one new line after report line" issue.save.assert_called_once_with()
def test_notify_supervisors(db, mailoutbox): """Test time range 2017-7-17 till 2017-7-23.""" start = date(2017, 7, 14) # supervisee with short time supervisee = UserFactory.create() supervisor = UserFactory.create() supervisee.supervisors.add(supervisor) EmploymentFactory.create(user=supervisee, start_date=start, percentage=100) workdays = rrule(DAILY, dtstart=start, until=date.today(), # range is excluding last byweekday=range(MO.weekday, FR.weekday + 1)) for dt in workdays: ReportFactory.create(user=supervisee, date=dt, duration=timedelta(hours=7)) call_command('notify_supervisors_shorttime') # checks assert len(mailoutbox) == 1 mail = mailoutbox[0] assert mail.to == [supervisor.email] body = mail.body assert 'Time range: 17.07.2017 - 23.07.2017\nRatio: 0.9' in body expected = ( '{0} 35.0/42.5 (Ratio 0.82 Delta -7.5 Balance -9.0)' ).format( supervisee.get_full_name() ) assert expected in body
def test_notify_reviewers(db, mailoutbox): """Test time range 2017-7-1 till 2017-7-31.""" # a reviewer which will be notified reviewer_work = UserFactory.create() project_work = ProjectFactory.create() project_work.reviewers.add(reviewer_work) task_work = TaskFactory.create(project=project_work) ReportFactory.create(date=date(2017, 7, 1), task=task_work, verified_by=None) # a reviewer which doesn't have any unverfied reports reviewer_no_work = UserFactory.create() project_no_work = ProjectFactory.create() project_no_work.reviewers.add(reviewer_no_work) task_no_work = TaskFactory.create(project=project_no_work) ReportFactory.create(date=date(2017, 7, 1), task=task_no_work, verified_by=reviewer_no_work) call_command('notify_reviewers_unverified') # checks assert len(mailoutbox) == 1 mail = mailoutbox[0] assert mail.to == [reviewer_work.email] url = ( 'http://localhost:4200/reschedule?from_date=2017-07-01&' 'to_date=2017-07-31&reviewer=%d' ) % reviewer_work.id assert url in mail.body
def test_absence_fill_worktime_reported_time_to_long(auth_client): """ Verify absence fill worktime is zero when reported time is too long. Too long is defined when reported time is longer than worktime per day. """ date = datetime.date(2017, 5, 10) user = auth_client.user EmploymentFactory.create( user=user, start_date=date, worktime_per_day=datetime.timedelta(hours=8) ) type = AbsenceTypeFactory.create(fill_worktime=True) ReportFactory.create( user=user, date=date, duration=datetime.timedelta(hours=8, minutes=30) ) data = { "data": { "type": "absences", "id": None, "attributes": {"date": date.strftime("%Y-%m-%d")}, "relationships": { "type": {"data": {"type": "absence-types", "id": type.id}} }, } } url = reverse("absence-list") response = auth_client.post(url, data) assert response.status_code == status.HTTP_201_CREATED json = response.json() assert json["data"]["attributes"]["duration"] == "00:00:00"
def test_user_worktime_balance(self): """Should calculate correct worktime balances.""" user = self.user employment = user.employments.get(end_date__isnull=True) # Calculate over one week start_date = date(2017, 3, 19) end_date = date(2017, 3, 26) employment.start_date = start_date employment.worktime_per_day = timedelta(hours=8) employment.save() # Overtime credit of 10 hours OvertimeCreditFactory.create(user=user, date=start_date, duration=timedelta(hours=10, minutes=30)) # One public holiday during workdays PublicHolidayFactory.create(date=start_date, location=employment.location) # One public holiday on weekend PublicHolidayFactory.create(date=start_date + timedelta(days=1), location=employment.location) url = reverse('user-detail', args=[user.id]) res = self.client.get('{0}?until={1}'.format( url, end_date.strftime('%Y-%m-%d'))) result = self.result(res) # 5 workdays minus one holiday minus 10 hours overtime credit expected_worktime = (4 * employment.worktime_per_day - timedelta(hours=10, minutes=30)) assert (result['data']['attributes']['worktime-balance'] == duration_string(timedelta() - expected_worktime)) # 2x 10 hour reported worktime ReportFactory.create(user=user, date=start_date + timedelta(days=3), duration=timedelta(hours=10)) ReportFactory.create(user=user, date=start_date + timedelta(days=4), duration=timedelta(hours=10)) AbsenceFactory.create(user=user, date=start_date + timedelta(days=5)) res2 = self.client.get('{0}?until={1}'.format( url, end_date.strftime('%Y-%m-%d'))) result2 = self.result(res2) assert (result2['data']['attributes']['worktime-balance'] == duration_string(timedelta(hours=28) - expected_worktime))
def test_user_delete_with_reports_superuser(superadmin_client): """Test that user with reports may not be deleted.""" user = UserFactory.create() ReportFactory.create(user=user) url = reverse('user-detail', args=[user.id]) response = superadmin_client.delete(url) assert response.status_code == status.HTTP_403_FORBIDDEN
def test_year_statistic_detail(auth_client): ReportFactory.create(duration=timedelta(hours=1), date=date(2015, 2, 28)) ReportFactory.create(duration=timedelta(hours=1), date=date(2015, 12, 31)) url = reverse('year-statistic-detail', args=[2015]) result = auth_client.get(url, data={'ordering': 'year'}) assert result.status_code == 200 json = result.json() assert json['data']['attributes']['duration'] == '02:00:00'
def test_report_list_filter_not_editable_superuser(superadmin_client): ReportFactory.create() url = reverse("report-list") response = superadmin_client.get(url, data={"editable": 0}) assert response.status_code == status.HTTP_200_OK json = response.json() assert len(json["data"]) == 0
def test_report_list_filter_id_empty(auth_client): """Test that empty id filter is ignored.""" ReportFactory.create() url = reverse("report-list") response = auth_client.get(url, data={"id": ""}) assert response.status_code == status.HTTP_200_OK json = response.json() assert len(json["data"]) == 1
def test_worktime_balance_partial(db): """ Test partial calculation of worktime balance. Partial is defined as a worktime balance of a time frame which is shorter than employment. """ employment = factories.EmploymentFactory.create( start_date=date(2010, 1, 1), end_date=None, worktime_per_day=timedelta(hours=8) ) user = employment.user # Calculate over one week start = date(2017, 3, 19) end = date(2017, 3, 26) # Overtime credit of 10.5 hours factories.OvertimeCreditFactory.create( user=user, date=start, duration=timedelta(hours=10, minutes=30) ) # One public holiday during workdays factories.PublicHolidayFactory.create( date=start, location=employment.location ) # One public holiday on weekend factories.PublicHolidayFactory.create( date=start + timedelta(days=1), location=employment.location ) # 5 workdays minus one holiday (32 hours) expected_expected = timedelta(hours=32) # reported 2 days each 10 hours for day in range(3, 5): ReportFactory.create( user=user, date=start + timedelta(days=day), duration=timedelta(hours=10) ) # 10 hours reported time + 10.5 overtime credit expected_reported = timedelta(hours=30, minutes=30) expected_balance = expected_reported - expected_expected reported, expected, balance = employment.calculate_worktime(start, end) assert expected == expected_expected assert reported == expected_reported assert balance == expected_balance
def test_report_list_filter_not_editable_owner(auth_client): user = auth_client.user ReportFactory.create(user=user) report = ReportFactory.create() url = reverse("report-list") response = auth_client.get(url, data={"editable": 0}) assert response.status_code == status.HTTP_200_OK json = response.json() assert len(json["data"]) == 1 assert json["data"][0]["id"] == str(report.id)
def test_report_list_filter_editable_owner(auth_client): user = auth_client.user report = ReportFactory.create(user=user) ReportFactory.create() url = reverse('report-list') response = auth_client.get(url, data={'editable': 1}) assert response.status_code == status.HTTP_200_OK json = response.json() assert len(json['data']) == 1 assert json['data'][0]['id'] == str(report.id)
def test_worktime_balance_longer(db): """Test calculation of worktime when frame is longer than employment.""" employment = factories.EmploymentFactory.create( start_date=date(2017, 3, 21), end_date=date(2017, 3, 27), worktime_per_day=timedelta(hours=8), ) user = employment.user # Calculate over one year start = date(2017, 1, 1) end = date(2017, 12, 31) # Overtime credit of 10.5 hours before employment factories.OvertimeCreditFactory.create(user=user, date=start, duration=timedelta(hours=10, minutes=30)) # Overtime credit of during employment factories.OvertimeCreditFactory.create(user=user, date=employment.start_date, duration=timedelta(hours=10, minutes=30)) # One public holiday during employment factories.PublicHolidayFactory.create(date=employment.start_date, location=employment.location) # One public holiday before employment started factories.PublicHolidayFactory.create(date=date(2017, 3, 20), location=employment.location) # 5 workdays minus one holiday (32 hours) expected_expected = timedelta(hours=32) # reported 2 days each 10 hours for day in range(3, 5): ReportFactory.create( user=user, date=employment.start_date + timedelta(days=day), duration=timedelta(hours=10), ) # reported time not on current employment ReportFactory.create(user=user, date=date(2017, 1, 5), duration=timedelta(hours=10)) # 10 hours reported time + 10.5 overtime credit expected_reported = timedelta(hours=30, minutes=30) expected_balance = expected_reported - expected_expected reported, expected, balance = employment.calculate_worktime(start, end) assert expected == expected_expected assert reported == expected_reported assert balance == expected_balance
def test_report_list_filter_verifier(auth_client): user = auth_client.user report = ReportFactory.create(verified_by=user) ReportFactory.create() url = reverse("report-list") response = auth_client.get(url, data={"verifier": user.id}) assert response.status_code == status.HTTP_200_OK json = response.json() assert len(json["data"]) == 1 assert json["data"][0]["id"] == str(report.id)
def test_project_statistic_list(auth_client, django_assert_num_queries): report = ReportFactory.create(duration=timedelta(hours=1)) ReportFactory.create(duration=timedelta(hours=2), task=report.task) report2 = ReportFactory.create(duration=timedelta(hours=4)) url = reverse("project-statistic-list") with django_assert_num_queries(5): result = auth_client.get(url, data={ "ordering": "duration", "include": "project,project.customer" }) assert result.status_code == 200 json = result.json() expected_json = [ { "type": "project-statistics", "id": str(report.task.project.id), "attributes": { "duration": "03:00:00" }, "relationships": { "project": { "data": { "id": str(report.task.project.id), "type": "projects" } } }, }, { "type": "project-statistics", "id": str(report2.task.project.id), "attributes": { "duration": "04:00:00" }, "relationships": { "project": { "data": { "id": str(report2.task.project.id), "type": "projects" } } }, }, ] assert json["data"] == expected_json assert len(json["included"]) == 4 assert json["meta"]["total-time"] == "07:00:00"
def test_user_absence_types_fill_worktime(self): absence_type = AbsenceTypeFactory.create(fill_worktime=True) employment = self.user.employments.get(end_date__isnull=True) employment.worktime_per_day = timedelta(hours=5) employment.start_date = date.today() - timedelta(days=1) employment.save() ReportFactory.create( user=self.user, date=date.today(), duration=timedelta(hours=4) ) AbsenceFactory.create(date=date.today(), user=self.user, type=absence_type) AbsenceFactory.create(date=date.today() - timedelta(days=1), user=self.user, type=absence_type) url = reverse('user-detail', args=[ self.user.id ]) res = self.client.get(url, {'include': 'user_absence_types'}) assert res.status_code == HTTP_200_OK result = self.result(res) rel = result['data']['relationships'] inc = result['included'] assert len(rel['user-absence-types']['data']) == 1 assert len(inc) == 1 assert ( inc[0]['id'] == '{0}-{1}'.format(self.user.id, absence_type.id) ) assert inc[0]['attributes']['credit'] is None assert inc[0]['attributes']['balance'] is None assert inc[0]['attributes']['used-days'] is None assert inc[0]['attributes']['used-duration'] == '06:00:00'
def test_user_statistic_list(auth_client): user = auth_client.user ReportFactory.create(duration=timedelta(hours=1), user=user) ReportFactory.create(duration=timedelta(hours=2), user=user) report = ReportFactory.create(duration=timedelta(hours=2)) url = reverse('user-statistic-list') result = auth_client.get(url, data={ 'ordering': 'duration', 'include': 'user' }) assert result.status_code == 200 json = result.json() expected_json = [{ 'type': 'user-statistics', 'id': str(report.user.id), 'attributes': { 'duration': '02:00:00' }, 'relationships': { 'user': { 'data': { 'id': str(report.user.id), 'type': 'users' } } } }, { 'type': 'user-statistics', 'id': str(user.id), 'attributes': { 'duration': '03:00:00' }, 'relationships': { 'user': { 'data': { 'id': str(user.id), 'type': 'users' } } } }] assert json['data'] == expected_json assert len(json['included']) == 2 assert json['meta']['total-time'] == '05:00:00'
def test_customer_statistic_list(auth_client, django_assert_num_queries): report = ReportFactory.create(duration=timedelta(hours=1)) ReportFactory.create(duration=timedelta(hours=2), task=report.task) report2 = ReportFactory.create(duration=timedelta(hours=4)) url = reverse('customer-statistic-list') with django_assert_num_queries(4): result = auth_client.get(url, data={ 'ordering': 'duration', 'include': 'customer' }) assert result.status_code == 200 json = result.json() expected_data = [{ 'type': 'customer-statistics', 'id': str(report.task.project.customer.id), 'attributes': { 'duration': '03:00:00' }, 'relationships': { 'customer': { 'data': { 'id': str(report.task.project.customer.id), 'type': 'customers' } } } }, { 'type': 'customer-statistics', 'id': str(report2.task.project.customer.id), 'attributes': { 'duration': '04:00:00' }, 'relationships': { 'customer': { 'data': { 'id': str(report2.task.project.customer.id), 'type': 'customers' } } } }] assert json['data'] == expected_data assert len(json['included']) == 2 assert json['meta']['total-time'] == '07:00:00'
def test_report_delete_superuser(superadmin_client): """Test that superuser may not delete reports of other users.""" report = ReportFactory.create() url = reverse("report-detail", args=[report.id]) response = superadmin_client.delete(url) assert response.status_code == status.HTTP_403_FORBIDDEN
def test_report_update_bulk(auth_client): task = TaskFactory.create() report = ReportFactory.create(user=auth_client.user) url = reverse("report-bulk") data = { "data": { "type": "report-bulks", "id": None, "relationships": { "task": { "data": { "type": "tasks", "id": task.id } } }, } } response = auth_client.post(url + "?editable=1", data) assert response.status_code == status.HTTP_204_NO_CONTENT report.refresh_from_db() assert report.task == task
def test_report_update_reviewer(auth_client): user = auth_client.user report = ReportFactory.create(user=user) report.task.project.reviewers.add(user) data = { "data": { "type": "reports", "id": report.id, "attributes": { "comment": "foobar" }, "relationships": { "verified-by": { "data": { "id": user.id, "type": "users" } } }, } } url = reverse("report-detail", args=[report.id]) response = auth_client.patch(url, data) assert response.status_code == status.HTTP_200_OK
def test_report_update_bulk_verify_reviewer(auth_client): user = auth_client.user report = ReportFactory.create(user=user) report.task.project.reviewers.add(user) url = reverse("report-bulk") data = { "data": { "type": "report-bulks", "id": None, "attributes": { "verified": True, "comment": "some comment" }, } } response = auth_client.post( url + "?editable=1&reviewer={0}".format(user.id), data) assert response.status_code == status.HTTP_204_NO_CONTENT report.refresh_from_db() assert report.verified_by == user assert report.comment == "some comment"
def test_report_delete(auth_client): user = auth_client.user report = ReportFactory.create(user=user) url = reverse("report-detail", args=[report.id]) response = auth_client.delete(url) assert response.status_code == status.HTTP_204_NO_CONTENT
def test_report_intersection_full(auth_client): report = ReportFactory.create() url = reverse("report-intersection") response = auth_client.get( url, data={ "ordering": "task__name", "task": report.task.id, "project": report.task.project.id, "customer": report.task.project.customer.id, "include": "task,customer,project", }, ) assert response.status_code == status.HTTP_200_OK json = response.json() pk = json["data"].pop("id") assert "task={0}".format(report.task.id) in pk assert "project={0}".format(report.task.project.id) in pk assert "customer={0}".format(report.task.project.customer.id) in pk included = json.pop("included") assert len(included) == 3 expected = { "data": { "type": "report-intersections", "attributes": { "comment": report.comment, "not-billable": False, "verified": False, "review": False, }, "relationships": { "customer": { "data": { "id": str(report.task.project.customer.id), "type": "customers", } }, "project": { "data": { "id": str(report.task.project.id), "type": "projects" } }, "task": { "data": { "id": str(report.task.id), "type": "tasks" } }, }, }, "meta": { "count": 1 }, } assert json == expected
def test_report_update_verified_and_review_reviewer(auth_client): user = auth_client.user report = ReportFactory.create(duration=timedelta(hours=2)) report.task.project.reviewers.add(user) data = { "data": { "type": "reports", "id": report.id, "attributes": { "review": True }, "relationships": { "verified-by": { "data": { "id": user.pk, "type": "users" } } }, } } url = reverse("report-detail", args=[report.id]) res = auth_client.patch(url, data) assert res.status_code == status.HTTP_400_BAD_REQUEST