diff --git a/src/woffu_client/cli.py b/src/woffu_client/cli.py index 4bba701..957410e 100644 --- a/src/woffu_client/cli.py +++ b/src/woffu_client/cli.py @@ -68,11 +68,34 @@ def main() -> None: ) # ---- get_status ---- - subparsers.add_parser( + st_parser = subparsers.add_parser( "get-status", help="Get current status and current day's \ total amount of worked hours", ) + st_period = st_parser.add_mutually_exclusive_group() + st_period.add_argument( + "--week", + dest="period", + action="store_const", + const="week", + help="Get current week's total hours", + ) + st_period.add_argument( + "--month", + dest="period", + action="store_const", + const="month", + help="Get current month's total hours", + ) + st_period.add_argument( + "--year", + dest="period", + action="store_const", + const="year", + help="Get current year's total hours", + ) + st_parser.set_defaults(period="today") # ---- sign ---- sign_parser = subparsers.add_parser( @@ -144,7 +167,7 @@ def main() -> None: sys.exit(1) case "get-status": try: - client.get_status() + client.get_status(period=args.period) except Exception as e: print(f"❌ Error retrieving status: {e}", file=sys.stderr) case "sign": diff --git a/src/woffu_client/woffu_api_client.py b/src/woffu_client/woffu_api_client.py index f51c444..202d066 100644 --- a/src/woffu_client/woffu_api_client.py +++ b/src/woffu_client/woffu_api_client.py @@ -4,6 +4,7 @@ """ from __future__ import annotations +import calendar import csv import json import logging @@ -15,6 +16,7 @@ from getpass import getpass from operator import itemgetter from pathlib import Path +from typing import Optional from tzlocal import get_localzone @@ -417,9 +419,14 @@ def get_sign_requests(self, date: str) -> dict | list: return {} def get_status( - self, only_running_clock: bool = False, - ) -> tuple[timedelta, bool]: + self, only_running_clock: bool = False, period: str = "today", + ) -> tuple[timedelta, Optional[bool]]: """Return the total amount of worked hours and current sign status.""" + # Retrieve info for the period flags, e.g., --week, --month, --year + if period != "today": + return self._get_status_period(period), None + + # Get current sign-in status and worked hours for today signs_in_day = self.get(url=f"{self._woffu_api_url}/api/signs").json() # Initialize a timer and the running clock boolean @@ -487,6 +494,41 @@ def get_status( ) return total_time, running_clock + def _get_status_period(self, period: str) -> timedelta: + today = datetime.now().date() + match period: + case "week": + from_date = today - timedelta(days=today.weekday()) + to_date = from_date + timedelta(days=6) + case "month": + from_date = today.replace(day=1) + last_day = calendar.monthrange(today.year, today.month)[1] + to_date = today.replace(day=last_day) + case "year": + from_date = today.replace(month=1, day=1) + to_date = today.replace(month=12, day=31) + + diary_json = self.get( + url=f"https://{self._domain}/api/svc/core/diariesquery/users/\ +{self._user_id}/diaries/summary/presence", + params={ + "fromDate": from_date, + "toDate": to_date, + "showHidden": False, + }, + ).json() + + h, m = map(int, diary_json["totalWorkedTimeFormatted"]["values"]) + logger.info( + "Hours worked this {}: {:02d}:{:02d}".format(period, h, m), + ) + total_time = timedelta(hours=h, minutes=m) + + h, m = diary_json["totalWorkingTimeFormatted"]["values"] + logger.info(f"Hours scheduled for this {period}: {h}:{m}") + + return total_time + def sign(self, type: str = "") -> HTTPResponse | None: """ Sign in/out on Woffu. diff --git a/tests/test_cli.py b/tests/test_cli.py index 37de677..444795c 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -67,23 +67,65 @@ def test_download_all_documents_failure(self, mock_client_cls): self.assertIn("❌ Error downloading files: Boom!", error_output) @patch("src.woffu_client.cli.WoffuAPIClient") - def test_get_status_success(self, mock_client_cls): - """Test status retrieval success.""" + def test_get_status_today(self, mock_client_cls): + """Test get-status without a flag (today).""" + self._test_get_status(mock_client_cls, None) + + @patch("src.woffu_client.cli.WoffuAPIClient") + def test_get_status_week(self, mock_client_cls): + """Test get-status with --week flag.""" + self._test_get_status(mock_client_cls, "--week") + + @patch("src.woffu_client.cli.WoffuAPIClient") + def test_get_status_month(self, mock_client_cls): + """Test get-status with --month flag.""" + self._test_get_status(mock_client_cls, "--month") + + @patch("src.woffu_client.cli.WoffuAPIClient") + def test_get_status_year(self, mock_client_cls): + """Test get-status with --year flag.""" + self._test_get_status(mock_client_cls, "--year") + + def _test_get_status(self, mock_client_cls, flag): mock_client = mock_client_cls.return_value mock_client.get_status.return_value = None - with patch.object(sys, "argv", ["cli", "get-status"]): + argv = ["cli", "get-status"] + if flag: + argv.append(flag) + with patch.object(sys, "argv", argv): cli.main() - mock_client.get_status.assert_called_once() @patch("src.woffu_client.cli.WoffuAPIClient") - def test_get_status_failure(self, mock_client_cls): - """Test status retrieval failure.""" + def test_get_status_failure_today(self, mock_client_cls): + """Test failure for get-status without a flag (today).""" + self._test_get_status_failure(mock_client_cls, None) + + @patch("src.woffu_client.cli.WoffuAPIClient") + def test_get_status_failure_week(self, mock_client_cls): + """Test failure for get-status with --week flag.""" + self._test_get_status_failure(mock_client_cls, "--week") + + @patch("src.woffu_client.cli.WoffuAPIClient") + def test_get_status_failure_month(self, mock_client_cls): + """Test failure for get-status with --month flag.""" + self._test_get_status_failure(mock_client_cls, "--month") + + @patch("src.woffu_client.cli.WoffuAPIClient") + def test_get_status_failure_year(self, mock_client_cls): + """Test failure for get-status with --year flag.""" + self._test_get_status_failure(mock_client_cls, "--year") + + def _test_get_status_failure(self, mock_client_cls, flag): mock_client = mock_client_cls.return_value mock_client.get_status.side_effect = Exception("status failed") - with patch.object(sys, "argv", ["cli", "get-status"]): + argv = ["cli", "get-status"] + if flag: + argv.append(flag) + + with patch.object(sys, "argv", argv): cli.main() error_output = cast(StringIO, sys.stderr).getvalue() diff --git a/tests/test_woffu_api_client.py b/tests/test_woffu_api_client.py index d890751..2502d4c 100644 --- a/tests/test_woffu_api_client.py +++ b/tests/test_woffu_api_client.py @@ -405,6 +405,39 @@ def test_get_diary_hour_types_missing_key(self, mock_get): result = self.client._get_diary_hour_types("2025-09-12") self.assertEqual(result, {}) + @patch.object(WoffuAPIClient, "get") + def test_get_status_period_week(self, mock_get): + """Test get-status --week.""" + self._test_get_status_period(mock_get, "week") + + @patch.object(WoffuAPIClient, "get") + def test_get_status_period_month(self, mock_get): + """Test get-status --month.""" + self._test_get_status_period(mock_get, "month") + + @patch.object(WoffuAPIClient, "get") + def test_get_status_period_year(self, mock_get): + """Test get-status --year.""" + self._test_get_status_period(mock_get, "year") + + def _test_get_status_period(self, mock_get, period): + mock_get.return_value.status = 200 + mock_get.return_value.json.return_value = { + "totalWorkingTimeFormatted": { + "resource": "_HoursMinutesFormatted", + "values": ["225", "0"], + }, + "totalWorkedTimeFormatted": { + "resource": "_HoursMinutesFormatted", + "values": ["42", "24"], + }, + } + + total, running = self.client.get_status(period=period) + self.assertIsInstance(total, timedelta) + self.assertIsNone(running) + mock_get.assert_called_once() + @patch.object(WoffuAPIClient, "get") def test_download_document_file_exists(self, mock_get): """Test download_document skips download if file already exists."""