ministryofjustice/moj-product-dashboard

View on GitHub
dashboard/apps/dashboard/tests/test_task.py

Summary

Maintainability
F
1 wk
Test Coverage
from decimal import Decimal
from datetime import date

from django.test import TestCase
from model_mommy import mommy
from faker import Faker
import pytest

from dashboard.apps.dashboard.models import Person, Product, Task, Rate
from dashboard.libs.date_tools import parse_date, to_datetime


class TaskTimeSpentTestCase(TestCase):

    def setUp(self):
        # task_0 is a task spanning over 9 calendar days. with a
        # weekend and bank holiday in the middle, the number of
        # actual working days is 6. given the time for the task is 3 days,
        # 0.5 day is spent daily during each of the 6 working days.
        self.task_0 = Task.objects.create(
            name='task_0', person_id=0, product_id=0, float_id=0,
            start_date=date(2016, 4, 27),
            end_date=date(2016, 5, 5),
            days=3
        )

        # task_1 is a task for bank holiday over Easter holiday
        self.task_1 = Task.objects.create(
            name='day off over Easter', person_id=0, product_id=1,
            float_id=1,
            start_date=date(2016, 3, 25),
            end_date=date(2016, 3, 28),
            days=2
        )

    def test_no_time_window_specified(self):
        days = self.task_0.time_spent()
        self.assertEqual(days, self.task_0.days)

    def test_time_window_covers_entire_task_span(self):
        days = self.task_0.time_spent(start_date=date(2016, 4, 1),
                                      end_date=date(2016, 6, 1))
        self.assertEqual(days, self.task_0.days)

    def test_time_window_no_overlapping(self):
        days = self.task_0.time_spent(start_date=date(2016, 6, 1))
        self.assertEqual(days, 0)

        days = self.task_0.time_spent(end_date=date(2016, 4, 1))
        self.assertEqual(days, 0)

    def test_time_window_slices_head_of_task(self):
        days = self.task_0.time_spent(start_date=date(2016, 4, 15),
                                      end_date=date(2016, 4, 30))
        self.assertEqual(days, Decimal('1.5'))

    def test_time_window_slices_tail_of_task(self):
        days = self.task_0.time_spent(start_date=date(2016, 5, 3),
                                      end_date=date(2016, 6, 3))
        self.assertEqual(days, Decimal('1.5'))

    def test_bank_holiday(self):
        days = self.task_0.time_spent(start_date=date(2016, 4, 15),
                                      end_date=date(2016, 5, 2))
        self.assertEqual(days, Decimal('1.5'))

    def test_bank_holiday_only(self):
        days = self.task_1.time_spent(start_date=date(2016, 3, 25),
                                      end_date=date(2016, 3, 25))
        self.assertEqual(days, Decimal('0'))


class TaskMoneySpentTestCase(TestCase):

    def setUp(self):
        person = mommy.make(Person)
        mommy.make(Rate, start_date=date(2015, 1, 1), rate=Decimal('400'),
                   person=person)
        self.task_0 = mommy.make(
            Task, product=mommy.make(Product),
            person=person,
            start_date=date(2016, 6, 1),
            end_date=date(2016, 6, 10),
            days=8)

    def test_task_total_spending(self):
        assert self.task_0.people_costs() == 400 * 8

    def test_task_weekday_spending(self):
        assert self.task_0.people_costs(
            date(2016, 6, 1), date(2016, 6, 3)) == 400 * 3

    def test_task_weekend_spending_is_zero(self):
        assert self.task_0.people_costs(
            date(2016, 6, 4), date(2016, 6, 5)) == 0

    def test_task_weekday_plus_weekend_spending(self):
        assert self.task_0.people_costs(
            date(2016, 6, 1), date(2016, 6, 5)) == 400 * 3

    def test_task_spending_after_end_date_is_zero(self):
        assert self.task_0.people_costs(
            date(2016, 6, 11), date(2016, 6, 12)) == 0

    def test_task_spending_before_staart_date_is_zero(self):
        assert self.task_0.people_costs(
            date(2016, 5, 25), date(2016, 5, 31)) == 0


rate1 = Decimal('400')
rate2 = Decimal('500')
rate3 = Decimal('600')


@pytest.mark.parametrize("sday,eday,expected", [
    (17, 17, rate1 * 1),
    (17, 18, rate1 * 2),
    (17, 19, rate1 * 3),
    (17, 20, rate1 * 4),
    (17, 21, rate1 * 4),
    (17, 22, rate1 * 4),
    (17, 23, rate1 * 4 + rate2 * 1),
    (17, 24, rate1 * 4 + rate2 * 2),
    (17, 25, rate1 * 4 + rate2 * 3),
    (17, 26, rate1 * 4 + rate2 * 4),
    (17, 27, rate1 * 4 + rate2 * 5),
    (17, 28, rate1 * 4 + rate2 * 5),
    (17, 29, rate1 * 4 + rate2 * 5),
    (17, 30, rate1 * 4 + rate2 * 5),
    (17, 31, rate1 * 4 + rate2 * 5 + rate3),

    (22, 22, rate2 * 0),
    (22, 23, rate2 * 1),
    (22, 24, rate2 * 2),
    (22, 25, rate2 * 3),
    (22, 26, rate2 * 4),
    (22, 27, rate2 * 5),
    (22, 28, rate2 * 5),
    (22, 29, rate2 * 5),
    (22, 30, rate2 * 5),
    (22, 31, rate2 * 5 + rate3),
])
@pytest.mark.django_db
def test_multiple_rates(sday, eday, expected):
    """
                    May 2016
               Su Mo Tu We Th Fr Sa
    400/day     1  2  3  4  5  6  7
    400/day     8  9 10 11 12 13 14
    400/day    15 16 17 18 19 20 21
    500/day    22 23 24 25 26 27 28
    600/day    29 30 31

    task starts from 17th, lasts for 10 working days and ends on 31st.
    30th is a bank holiday.
    """
    person = mommy.make(Person)
    mommy.make(Rate, start_date=date(2016, 5, 1), rate=rate1,
               person=person)  # 4 days of the task
    mommy.make(Rate, start_date=date(2016, 5, 22), rate=rate2,
               person=person)  # 5 days of the task
    mommy.make(Rate, start_date=date(2016, 5, 29), rate=rate3,
               person=person)  # 1 day of the task
    task_0 = mommy.make(
        Task, product=mommy.make(Product),
        person=person,
        start_date=date(2016, 5, 17),
        end_date=date(2016, 5, 31),
        days=10)

    people_cost = task_0.people_costs(
        date(2016, 5, sday), date(2016, 5, eday)
    )
    assert round(people_cost, 0) == expected


@pytest.mark.parametrize("month, day, expected", [
    (4, 24, 0),
    (4, 25, 0),
    (4, 26, 0),
    (4, 27, 0),
    (4, 28, 0),
    (4, 29, 0),
    (4, 30, 0),
    (5, 1, 0),
    (5, 2, 0),
    (5, 3, rate1 * Decimal('0.5')),
    (5, 4, rate1 * Decimal('0.5') * 2),
    (5, 5, rate1 * Decimal('0.5') * 3),
    (5, 6, rate1 * Decimal('0.5') * 4),
    (5, 7, rate1 * Decimal('0.5') * 4),
    (5, 8, rate1 * Decimal('0.5') * 4),
    (5, 9, rate1 * Decimal('0.5') * 4),
    (5, 10, rate1 * Decimal('0.5') * 5),
    (5, 11, rate1 * Decimal('0.5') * 6),
    (5, 12, rate1 * Decimal('0.5') * 7),
    (5, 13, rate1 * Decimal('0.5') * 8),
    (5, 14, rate1 * Decimal('0.5') * 8),
    (5, 15, rate1 * Decimal('0.5') * 8),
    (5, 16, rate1 * Decimal('0.5') * 8),
    (5, 17, rate1 * Decimal('0.5') * 8 + rate2 * Decimal('0.5')),
    (5, 18, rate1 * Decimal('0.5') * 8 + rate2 * Decimal('0.5') * 2),
    (5, 19, rate1 * Decimal('0.5') * 8 + rate2 * Decimal('0.5') * 3),
    (5, 20, rate1 * Decimal('0.5') * 8 + rate2 * Decimal('0.5') * 4),
    (5, 21, rate1 * Decimal('0.5') * 8 + rate2 * Decimal('0.5') * 4),
    (5, 22, rate1 * Decimal('0.5') * 8 + rate2 * Decimal('0.5') * 4),
    (5, 23, rate1 * Decimal('0.5') * 8 + rate2 * Decimal('0.5') * 4),
    (5, 24, rate1 * Decimal('0.5') * 8 + rate2 * Decimal('0.5') * 4 + rate3 * Decimal('0.5')),
    (5, 25, rate1 * Decimal('0.5') * 8 + rate2 * Decimal('0.5') * 4 + rate3 * Decimal('0.5') * 2),
    (5, 26, rate1 * Decimal('0.5') * 8 + rate2 * Decimal('0.5') * 4 + rate3 * Decimal('0.5') * 3),
    (5, 27, rate1 * Decimal('0.5') * 8 + rate2 * Decimal('0.5') * 4 + rate3 * Decimal('0.5') * 4),
    (5, 28, rate1 * Decimal('0.5') * 8 + rate2 * Decimal('0.5') * 4 + rate3 * Decimal('0.5') * 4),
    (5, 29, rate1 * Decimal('0.5') * 8 + rate2 * Decimal('0.5') * 4 + rate3 * Decimal('0.5') * 4),
    (5, 30, rate1 * Decimal('0.5') * 8 + rate2 * Decimal('0.5') * 4 + rate3 * Decimal('0.5') * 4),
    (5, 31, rate1 * Decimal('0.5') * 8 + rate2 * Decimal('0.5') * 4 + rate3 * Decimal('0.5') * 4),

])
@pytest.mark.django_db
def test_weekly_repeat_task_multiple_rates(month, day, expected):
    """
                    Apr 2016
               Su Mo Tu We Th Fr Sa
                            1  2
                3  4  5  6  7  8  9
               10 11 12 13 14 15 16
               17 18 19 20 21 22 23
    400/day    24 25 26 27 28 29 30

                    May 2016
               Su Mo Tu We Th Fr Sa
    400/day     1  2  3  4  5  6  7
    400/day     8  9 10 11 12 13 14
    500/day    15 16 17 18 19 20 21
    600/day    22 23 24 25 26 27 28
    600/day    29 30 31

    task starts from 24/04, 4 hours/day for 4 days and repeat 5 times

    calculation starts from 01/05
    """
    person = mommy.make(Person)
    mommy.make(Rate, start_date=date(2016, 4, 24), rate=rate1, person=person)
    mommy.make(Rate, start_date=date(2016, 5, 15), rate=rate2, person=person)
    mommy.make(Rate, start_date=date(2016, 5, 22), rate=rate3, person=person)
    task_0 = mommy.make(
        Task, product=mommy.make(Product),
        person=person,
        start_date=date(2016, 4, 26),
        end_date=date(2016, 4, 29),
        repeat_state=1,
        repeat_end=date(2016, 5, 24),
        days=2)

    people_cost = task_0.people_costs(
        date(2016, 4, 24), date(2016, month, day),
        calculation_start_date=date(2016, 5, 1)
    )
    assert round(people_cost, 0) == expected


class TaskString(TestCase):

    def test_task_without_name(self):
        task_without_name = mommy.make(
            Task, product=mommy.make(Product, name='product 0'),
            person=mommy.make(Person, name='John'),
            start_date=date(2016, 6, 1),
            end_date=date(2016, 6, 10),
            days=8)
        expected = 'John on product 0 from 2016-06-01 to 2016-06-10 for 8 days'
        assert str(task_without_name) == expected

    def test_task_with_name(self):
        task_with_name = mommy.make(
            Task,
            name='task 0',
            product=mommy.make(Product, name='product 0'),
            person=mommy.make(Person, name='John'),
            start_date=date(2016, 6, 1),
            end_date=date(2016, 6, 10),
            days=8)
        expected = (
            'task 0 - John on product 0'
            ' from 2016-06-01 to 2016-06-10 for 8 days')
        assert str(task_with_name) == expected

    def test_weekly_repeat_task(self):
        task_with_name = mommy.make(
            Task,
            name='task 0',
            product=mommy.make(Product, name='product 0'),
            person=mommy.make(Person, name='John'),
            start_date=date(2016, 6, 1),
            end_date=date(2016, 6, 10),
            repeat_state=1,
            repeat_end=date(2016, 7, 1),
            days=8)
        expected = (
            'task 0 - John on product 0'
            ' from 2016-06-01 to 2016-06-10 for 8 days'
            ' and repeat weekly until 2016-07-01')
        assert str(task_with_name) == expected


year_start = parse_date('2016-01-01')
first_half_end = parse_date('2016-06-30')
second_half_start = parse_date('2016-07-01')
year_end = parse_date('2016-12-31')


class TestTaskManager(TestCase):

    def setUp(self):
        self.first_half_year_tasks = []
        self.second_half_year_tasks = []

        fake = Faker()

        # create 20 tasks for the 1st half year
        # 10 repeating (if the start date and end date
        # allow for repetition) and 10 not repeating
        for _ in range(10):
            rand_sd = fake.date_time_between(
                to_datetime(year_start),
                to_datetime(first_half_end)
            )
            rand_ed = fake.date_time_between(
                rand_sd,
                to_datetime(first_half_end)
            )
            try:
                rand_repeat_end = fake.date_time_between(
                    rand_ed,
                    to_datetime(first_half_end) - (rand_ed - rand_sd),
                )
            except ValueError:
                rand_repeat_end = None
            non_repeating_task = mommy.make(
                Task,
                start_date=rand_sd.date(),
                end_date=rand_ed.date()
            )
            self.first_half_year_tasks.append(non_repeating_task)
            if rand_repeat_end:
                repeating_task = mommy.make(
                    Task,
                    start_date=rand_sd.date(),
                    end_date=rand_ed.date(),
                    repeat_end=rand_repeat_end.date(),
                    repeat_state=1
                )
                self.first_half_year_tasks.append(repeating_task)

        # create 20 tasks for the 2nd half year
        # 10 repeating (if the start date and end date
        # allow for repetition) and 10 not repeating
        for _ in range(10):
            rand_sd = fake.date_time_between(
                to_datetime(second_half_start),
                to_datetime(year_end)
            )
            rand_ed = fake.date_time_between(
                rand_sd,
                to_datetime(year_end))
            try:
                rand_repeat_end = fake.date_time_between(
                    rand_ed,
                    to_datetime(year_end) - (rand_ed - rand_sd),
                )
            except ValueError:
                rand_repeat_end = None

            non_repeating_task = mommy.make(
                Task,
                start_date=rand_sd.date(),
                end_date=rand_ed.date()
            )
            self.second_half_year_tasks.append(non_repeating_task)
            if rand_repeat_end:
                repeating_task = mommy.make(
                    Task,
                    start_date=rand_sd.date(),
                    end_date=rand_ed.date(),
                    repeat_end=rand_repeat_end.date(),
                    repeat_state=1
                )

                self.second_half_year_tasks.append(repeating_task)

    @staticmethod
    def assert_tasks_equals(tasks1, tasks2):
        t1_ids = [t.id for t in tasks1]
        t2_ids = [t.id for t in tasks2]
        assert sorted(t1_ids) == sorted(t2_ids)

    def test_all_tasks(self):
        all_tasks = self.first_half_year_tasks + self.second_half_year_tasks
        self.assert_tasks_equals(Task.objects.all(), all_tasks)

    def test_between(self):
        # with both start_date and end_date
        first_half_year_tasks = Task.objects.between(
            year_start, first_half_end)
        second_half_year_tasks = Task.objects.between(
            second_half_start, year_end)

        self.assert_tasks_equals(
            first_half_year_tasks, self.first_half_year_tasks)
        self.assert_tasks_equals(
            second_half_year_tasks, self.second_half_year_tasks)

        # without start_date and end_date means no filtering
        all_tasks = Task.objects.between()
        print(len(all_tasks))
        self.assert_tasks_equals(
            all_tasks,
            self.first_half_year_tasks + self.second_half_year_tasks)

        # without start_date
        all_tasks = Task.objects.between(end_date=year_end)
        self.assert_tasks_equals(
            all_tasks,
            self.first_half_year_tasks + self.second_half_year_tasks)
        first_half_year_tasks = Task.objects.between(
            end_date=first_half_end)
        self.assert_tasks_equals(
            first_half_year_tasks, self.first_half_year_tasks)
        # without end_date
        all_tasks = Task.objects.between(start_date=year_start)
        self.assert_tasks_equals(
            all_tasks,
            self.first_half_year_tasks + self.second_half_year_tasks)
        second_half_year_tasks = Task.objects.between(
            start_date=second_half_start)

        self.assert_tasks_equals(
            second_half_year_tasks, self.second_half_year_tasks)


@pytest.mark.parametrize("sday,eday,expected", [
    ('2017-03-27', '2017-03-31', 5),
    ('2017-03-27', '2017-04-07', 10),
    ('2017-01-01', '2017-03-22', 0),
    ('2019-05-01', '2019-05-22', 0),
])
@pytest.mark.django_db
def test_repeat_tasks(sday, eday, expected):
    start_date = date(2017, 3, 27)
    end_date = date(2017, 3, 31)
    repeat_end = date(2018, 4, 30)
    task = mommy.make(
        Task,
        start_date=start_date,
        end_date=end_date,
        days=5,
        repeat_state=1,
        repeat_end=repeat_end
    )
    assert task.time_spent(parse_date(sday), parse_date(eday)) == expected
    assert task.effective_end_date == repeat_end + (end_date - start_date)