from datetime import date, timedelta from decimal import Decimal import pytest from rest_framework.test import APIClient from apps.clients.models import Client from apps.projects.models import Project from apps.tags.models import Tag from apps.time_entries.models import TimeEntry from apps.users.models import User from apps.workspaces.models import Workspace, WorkspaceMembership @pytest.fixture() def api_client(): return APIClient() @pytest.fixture() def owner(db): return User.objects.create_user(mobile="09128880001", password="secret123", first_name="Owner") @pytest.fixture() def admin(db): return User.objects.create_user(mobile="09128880002", password="secret123", first_name="Admin") @pytest.fixture() def member(db): return User.objects.create_user(mobile="09128880003", password="secret123", first_name="Member") @pytest.fixture() def workspace(owner, admin, member): workspace = Workspace.objects.create(name="Reports", owner=owner) WorkspaceMembership.objects.create(workspace=workspace, user=admin, role=WorkspaceMembership.Role.ADMIN, is_active=True) WorkspaceMembership.objects.create(workspace=workspace, user=member, role=WorkspaceMembership.Role.MEMBER, is_active=True) return workspace @pytest.fixture() def client(workspace): return Client.objects.create(workspace=workspace, name="Acme") @pytest.fixture() def project(workspace, client): return Project.objects.create(workspace=workspace, name="Website", client=client) @pytest.fixture() def tag(workspace): return Tag.objects.create(workspace=workspace, name="Design", color="#ffffff") @pytest.fixture() def time_entries(workspace, owner, member, project, tag): entry_owner = TimeEntry.objects.create( workspace=workspace, user=owner, project=project, description="Owner work", start_time="2026-04-10T08:00:00+03:30", end_time="2026-04-10T10:00:00+03:30", duration=timedelta(hours=2), is_billable=True, hourly_rate=Decimal("25.00"), currency="USD", ) entry_owner.tags.add(tag) entry_member = TimeEntry.objects.create( workspace=workspace, user=member, project=project, description="Member work", start_time="2026-04-11T09:00:00+03:30", end_time="2026-04-11T10:00:00+03:30", duration=timedelta(hours=1), is_billable=False, currency="USD", ) entry_member.tags.add(tag) return [entry_owner, entry_member] def test_member_only_sees_own_chart_report(api_client, member, workspace, time_entries): api_client.force_authenticate(user=member) response = api_client.get( "/api/reports/chart/", {"workspace": str(workspace.id), "period": "this_month"}, ) assert response.status_code == 200 assert response.data["summary"]["total_duration"] == "01:00:00" def test_admin_can_request_combined_table_report(api_client, admin, workspace, time_entries): api_client.force_authenticate(user=admin) response = api_client.get( "/api/reports/table/", {"workspace": str(workspace.id), "period": "this_month"}, ) assert response.status_code == 200 assert response.data["summary"]["total_duration"] == "03:00:00" assert len(response.data["days"]) == 2 def test_custom_period_longer_than_31_days_is_rejected(api_client, owner, workspace): api_client.force_authenticate(user=owner) response = api_client.get( "/api/reports/chart/", { "workspace": str(workspace.id), "period": "period", "from_date": "2026-01-01", "to_date": "2026-02-15", }, ) assert response.status_code == 400 def test_persian_this_month_uses_jalali_month_bounds(api_client, owner, workspace, project, monkeypatch): api_client.force_authenticate(user=owner) monkeypatch.setattr("apps.reports.services.aggregation.timezone.localdate", lambda: date(2026, 4, 27)) TimeEntry.objects.create( workspace=workspace, user=owner, project=project, description="Previous jalali month", start_time="2026-04-20T08:00:00+03:30", end_time="2026-04-20T09:00:00+03:30", duration=timedelta(hours=1), is_billable=False, currency="USD", ) TimeEntry.objects.create( workspace=workspace, user=owner, project=project, description="Current jalali month", start_time="2026-04-21T08:00:00+03:30", end_time="2026-04-21T10:00:00+03:30", duration=timedelta(hours=2), is_billable=False, currency="USD", ) response = api_client.get( "/api/reports/table/", {"workspace": str(workspace.id), "period": "this_month", "language": "fa"}, ) assert response.status_code == 200 assert response.data["summary"]["total_duration"] == "02:00:00" assert response.data["scope"]["from_date"] == "2026-04-21"