Regression from #62: verify_ssl true used to resolve to the system CA
bundle path, so corporate CAs installed in the OS worked; after the
unification true became requests' default (certifi), breaking setups
with a corporate CA in the system store.
Now verify_ssl true injects truststore, so requests verifies against
the OS trust store on any platform. verify_ssl false / custom CA path
behavior is unchanged. Tests mock truststore via an autouse fixture to
keep the pytest process free of global ssl mutation.
Refs #62
Issues not returned by issue.filter (no access / deleted) and failures
of the time entry activities lookup were silently swallowed: the report
was built from partial data without telling the user.
Now both cases emit a warning to stderr (project ⚠️ style, #28):
- skipped issue IDs and total lost hours after issue matching;
- activities lookup failure (still returns {} and falls back to
activity names from time entries).
These are warnings, not errors: the report is still built from
available data and the exit code is unchanged.
Closes#61
Redmine user.filter(login=...) performs an inexact substring search, but
_resolve_user_id took users[0].id unconditionally, so a report could
silently be built for the wrong user (e.g. 'ivanov' matching 'ivanov2').
Now only exact (case-sensitive) login matches are considered: exactly one
match resolves to its id, multiple matches raise an ambiguity error,
no match falls through to the name lookup and then to a 'not found'
error — symmetric with the --user-name resolution.
Closes#60
Dedup with precision=datetime crashed with TypeError: redminelib returns
naive created_on/updated_on while the cutoff from _compute_dedup_cutoff
is aware UTC. Normalize at a single point: _parse_datetime now always
returns an aware datetime (naive treated as UTC), matching its docstring.
The dedup cutoff is normalized the same way, and any residual comparison
TypeError is wrapped in RedmineAPIError instead of leaking raw.
Also fix --commit saving last_used.to as naive local time; it now stores
aware UTC (datetime.now(timezone.utc)) so the next run computes a correct
aware cutoff.
Closes#58
The trap mocked issue.filter to return only issue 2, so result == [2]
would pass even with a wrong fix that suppresses TypeError but keeps all
entries. Now the API mock returns both issues; only correct filtering
of the pre-cutoff time entry keeps the result at [2].
Refs #67, #58
- tests/test_cli.py: fix 26 fetch_issues_with_spent_time mocks to return
3-element tuples (issue, hours, activities) matching the real signature
- pyproject.toml: add [tool.pytest.ini_options] with testpaths and
pythonpath so bare pytest works
- tests/test_client.py: add pagination test (>100 time entries arriving
in pages, hours aggregated across all pages)
- tests/test_formatters.py: add structural tests — XLSX full header row
and grand total row via openpyxl, HTML thead with all columns and
tbody row count
- add strict xfail trap-tests for known bugs: #58 (naive created_on vs
aware dedup cutoff TypeError) and #59 (parse_date_range rejects
datetime range from dynamic+datetime period)
Closes#67
#43 — Имя файла и пути по умолчанию:
- resolve_output_path() в yaml_config.py: резолвит --output с учётом
output.dir, filename_template, default_format из YAML-конфига
- Bare format (xlsx/odt/...) → путь по шаблону
- Без расширения → автодописывается default_format (.xlsx)
- Config.get_output_dir/filename/default_format()
#47 — datetime precision и дедупликация:
- period.last_used.from/to в YAML-конфиге и AppConfig
- period.precision: date|datetime
- _compute_dedup_cutoff() в cli.py: при precision=datetime вычисляет
cutoff из period.last_used.to
- _parse_datetime() + AND-логика дедупликации в client.py:
запись исключается если created_on И updated_on < cutoff
- Пропуск issues без часов после дедупликации
Closes#43Closes#47
- Load time entry activity names from Redmine enumeration.
- Aggregate hours per issue and per activity in fetch_issues_with_spent_time.
- Add --by-activity CLI flag and propagate it through report builder,
summary, and all formatters (console, CSV, HTML, JSON, ODT).
- Keep backward-compatible 2-tuple input in build_grouped_report.
- Bump version to 1.8.0.
Closes#41
- Add user_id parameter to fetch_issues_with_spent_time().
- Support numeric ID, login, or full name resolution.
- Reject ambiguous names and unknown users with clear messages.
- Add CLI flags: --user-id, --user-login, --user-name.
- Only allow one user flag at a time.
- Add ResourceNotFoundError handling.
- Update README with usage examples.
- Add tests for user resolution and CLI flags.
Closes#40
- Introduce RedmineAPIError with human-friendly messages.
- Distinguish AuthError, ForbiddenError, HTTP status codes, timeouts
and connection errors in client.py.
- Update CLI to print the readable message instead of generic
"Redmine API error: ...".
- Log original exception with traceback when --debug is enabled.
- Add tests for all error paths and CLI output.
Closes#39
python-redmine exposes as a context manager, while the
actual requests.Session lives in . Mounting the
retry adapter on the wrong object caused:
'function' object has no attribute 'mount'
on startup. Update the related test to mock the real session.
- #24: Mount HTTPAdapter with Retry(total=3, backoff_factor=0.5) for
HTTP 429/500/502/503/504 on the Redmine session, set 30s timeout
- #21: Split large issue_id lists into chunks of 100 to avoid exceeding
URL length limits on reverse proxies
Closes#24, #21