repos
/ timestrap-django master

timestrap-django

mirror archived upstream

Time tracking you can host anywhere, built on Django. Full export support in multiple formats and easily extensible.

djangodockerhandcodedpythonself-hostedtime-trackingtimetracker

8.2 KB · 218 lines · Python Raw History
  1from datetime import timedelta
  2from decimal import Decimal
  3from io import StringIO
  4from csv import DictReader
  5
  6from django.test import TestCase
  7from django.test import Client as HttpClient
  8from django.contrib.auth.models import User
  9from django.core.management import call_command
 10
 11from faker import Factory
 12
 13from conf.models import Site, SitePermission
 14
 15from .models import Client, Project, Entry, Task
 16from .utils import parse_duration, duration_string, duration_decimal
 17
 18
 19fake = Factory.create()
 20
 21
 22class ClientTestCase(TestCase):
 23    def setUp(self):
 24        call_command("migrate", verbosity=0)
 25
 26    def test_client_created(self):
 27        Client.objects.create(name="Timestrap")
 28        client = Client.objects.get(name="Timestrap")
 29        self.assertEqual(client.name, "Timestrap")
 30
 31    def test_client_created_unicode(self):
 32        Client.objects.create(name="Юникод")
 33        client = Client.objects.get(name="Юникод")
 34        self.assertEqual(client.name, "Юникод")
 35
 36    def test_client_site_relationship(self):
 37        Site.objects.create(domain="test.site", name="Test Site")
 38        client = Client.objects.create(name="Client on time.strap")
 39        self.assertEqual(client.sites.get().name, "Timestrap")
 40        client = Client.objects.create(name="Client on test.site")
 41        client.sites.set(Site.objects.filter(domain="test.site"))
 42        client.save()
 43        self.assertEqual(client.sites.get().name, "Test Site")
 44
 45
 46class ProjectTestCase(TestCase):
 47    def setUp(self):
 48        call_command("migrate", verbosity=0)
 49        self.user = User.objects.first()
 50        self.client = Client.objects.create(name="Timestrap")
 51
 52    def test_project_created(self):
 53        Project.objects.create(client=self.client, name="Testing")
 54        project = Project.objects.get(name="Testing")
 55        self.assertEqual(project.name, "Testing")
 56        self.assertEqual(project.client, self.client)
 57
 58    def test_project_created_unicode(self):
 59        Project.objects.create(client=self.client, name="Юникод")
 60        project = Project.objects.get(name="Юникод")
 61        self.assertEqual(project.name, "Юникод")
 62        self.assertEqual(project.client, self.client)
 63
 64    def test_project_estimate(self):
 65        project = Project.objects.create(
 66            client=self.client, name="Testing", estimate=5000.00
 67        )
 68        task = Task.objects.create(name="Testing", hourly_rate=100.00)
 69        Entry.objects.create(
 70            project=project,
 71            user=self.user,
 72            task=task,
 73            duration=timedelta(hours=10),
 74            note="Creating tests for the core app",
 75        )
 76        self.assertEqual(project.get_percent_done(), 20)
 77
 78
 79class EntryTestCase(TestCase):
 80    def setUp(self):
 81        call_command("migrate", verbosity=0)
 82        self.user = User.objects.first()
 83        client = Client.objects.create(name="Timestrap")
 84        self.project = Project.objects.create(client=client, name="Testing")
 85
 86        Entry.objects.create(
 87            project=self.project,
 88            user=self.user,
 89            duration=timedelta(hours=1),
 90            note="Creating tests for the core app",
 91        )
 92        Entry.objects.create(
 93            project=self.project,
 94            user=self.user,
 95            duration=timedelta(hours=2),
 96            note="Continue creating tests for the core app",
 97        )
 98
 99    def test_entry_created(self):
100        entry = Entry.objects.get(duration=timedelta(hours=1))
101        self.assertEqual(entry.note, "Creating tests for the core app")
102        entries = Entry.objects.filter(duration__lte=timedelta(hours=1, minutes=30))
103        self.assertEqual(len(entries), 1)
104
105    def test_entry_site_relationship(self):
106        site = Site.objects.create(domain="test.site", name="Test Site")
107        entry = Entry.objects.get(duration=timedelta(hours=1))
108        self.assertEqual(entry.site.name, "Timestrap")
109        entry.site = site
110        entry.save()
111        self.assertEqual(entry.site.name, "Test Site")
112
113    def test_parse_duration(self):
114        duration = parse_duration("3.25")
115        self.assertEqual(duration, timedelta(hours=3, minutes=15))
116        duration = parse_duration("2:34")
117        self.assertEqual(duration, timedelta(hours=2, minutes=34))
118        duration = parse_duration("0:05")
119        self.assertEqual(duration, timedelta(hours=0, minutes=5))
120        duration = parse_duration("5")
121        self.assertEqual(duration, timedelta(hours=5))
122        self.assertEqual(None, parse_duration("wut"))
123
124    def test_duration_string(self):
125        duration = duration_string(timedelta(hours=1, minutes=30))
126        self.assertEqual(duration, "1:30")
127        duration = duration_string(timedelta(hours=3, minutes=2))
128        self.assertEqual(duration, "3:02")
129        duration = duration_string(None)
130        self.assertAlmostEqual(duration, "0:00")
131
132    def test_duration_decimal(self):
133        # TODO: Determine is assertAlmostEqual is appropriate here.
134        duration = duration_decimal(timedelta(hours=2, minutes=3))
135        self.assertAlmostEqual(duration, Decimal(2.05))
136        duration = duration_decimal(timedelta(hours=5, minutes=15))
137        self.assertAlmostEqual(duration, Decimal(5.25))
138        duration = duration_decimal(None)
139        self.assertAlmostEqual(duration, Decimal(0))
140
141
142class ReportsTestCase(TestCase):
143    def setUp(self):
144        call_command("migrate", verbosity=0)
145        self.c = HttpClient()
146
147        fake_user = fake.simple_profile()
148        fake_password = fake.password()
149        user = User.objects.create_superuser(
150            fake_user["username"], fake_user["mail"], fake_password
151        )
152        site_permission = SitePermission.objects.create(user=user)
153        site_permission.sites.set(Site.objects.filter(id=1))
154        site_permission.save()
155
156        self.c.login(username=fake_user["username"], password=fake_password)
157
158        call_command("fake", verbosity=0, iterations=1)
159
160    def assert_entries_by_params(self, params):
161        url = "/reports/export/?"
162        for param, value in params.items():
163            url += "{0}={1}&".format(param, value)
164        report = self.c.get(url)
165        lines = DictReader(StringIO(report.content.decode("utf-8")))
166        for line in lines:
167            self.filter_exported_entry(line).delete()
168        self.assertFalse(Entry.objects.filter(**params).exists())
169
170    def filter_exported_entry(self, line):
171        # TODO: Improve this for better reporting when assertions fail.
172        entries = Entry.objects.filter(
173            date=line["date"],
174            duration=parse_duration(line["duration"]),
175            note=line["note"],
176            project__name=line["project__name"],
177            user__username=line["user__username"],
178        )
179        self.assertEqual(len(entries), 1)
180        return entries[0]
181
182    def test_export_response(self):
183        report = self.c.get("/reports/export/")
184        self.assertEqual(report.status_code, 200)
185        self.assertEqual(report.get("Content-Type"), "text/csv")
186        self.assertEqual(
187            report.get("Content-Disposition"), 'attachment; filename="report.csv"'
188        )
189
190    def test_export_all(self):
191        report = self.c.get("/reports/export/")
192        lines = DictReader(StringIO(report.content.decode("utf-8")))
193        for line in lines:
194            self.filter_exported_entry(line).delete()
195        # The above should have deleted _all_ entries
196        self.assertFalse(Entry.objects.all().exists())
197
198    def test_export_formats(self):
199        for f in ["csv", "xls", "tsv", "ods", "json", "yaml", "html"]:
200            report = self.c.get("/reports/export/?export_format={0}".format(f))
201            self.assertEqual(report.status_code, 200)
202            self.assertEqual(report.get("Content-Type"), "text/{0}".format(f))
203
204    def test_export_param_project(self):
205        self.assert_entries_by_params({"project": Project.objects.first().id})
206
207    def test_export_param_client(self):
208        self.assert_entries_by_params({"project__client": Client.objects.first().id})
209
210    def test_export_param_user(self):
211        self.assert_entries_by_params({"user": User.objects.first().id})
212
213    def test_export_param_date_range(self):
214        dates = sorted([Entry.objects.first().date, Entry.objects.last().date])
215        self.assert_entries_by_params(
216            {"date__gte": str(dates[0]), "date__lte": str(dates[1])}
217        )