Skip to content

Test suite refactor and CI#11

Merged
Wil-Collier merged 51 commits into
mainfrom
dev
May 17, 2026
Merged

Test suite refactor and CI#11
Wil-Collier merged 51 commits into
mainfrom
dev

Conversation

@Wil-Collier

Copy link
Copy Markdown
Collaborator

This pull request introduces several significant improvements to the project, including the addition of a comprehensive changelog, new CI workflows for standard and mutation testing, and the inclusion of licensing information. The changes focus on improving project documentation, test coverage, CI reliability, and legal clarity.

Project Documentation and Licensing:

  • Added a detailed CHANGELOG.md documenting all major features, bug fixes, behavioral changes, and internal refactors across releases, including recent custom-field handling and test suite overhauls.
  • Added the LICENSE file with the Apache License 2.0, clarifying the project's terms of use and distribution.

Continuous Integration (CI) Enhancements:

  • Introduced a new .github/workflows/ci.yml workflow running lint, type-check, unit, and integration tests across multiple Python and Pydantic versions, with coverage gating and concurrency controls.
  • Added a .github/workflows/mutation.yml workflow for advisory mutation testing using mutmut, which runs on PRs and uploads results as artifacts without blocking merges.

Testing and Makefile Improvements:

  • Updated the Makefile to include a new mut-quick target, and adjusted test commands to run both unit and contract tests, aligning local testing with CI.

Details by Theme:

Documentation and Licensing

  • Added CHANGELOG.md with exhaustive release notes, feature lists, bug fixes, and internal changes for all versions.
  • Added LICENSE (Apache 2.0) for clear open-source licensing.

Continuous Integration

  • Introduced .github/workflows/ci.yml for comprehensive CI, including linting, typing, unit, and integration tests with coverage enforcement.
  • Added .github/workflows/mutation.yml for non-blocking mutation testing and artifact upload.

Testing and Developer Experience

  • Enhanced Makefile to run both unit and contract tests, and added a mut-quick target for mutation testing.

- what: replace requests transport with httpx, retry/logging helpers, and eager resource managers
- what: move API objects onto Pydantic v2 with safe dirty tracking for extra fields
- why: prepare the client for clearer HTTP behavior, typed public surface checks, and safer model updates
- risk: breaking change for requests-specific session internals
- what: add the 0.2 changelog and refresh README examples/features
- why: explain the httpx, Pydantic, retry, logging, and compatibility changes
- risk: documentation only
- what: add GitHub Actions for lint, type-check, unit tests, and coverage
- what: update Makefile unit and coverage targets to include contract tests
- why: keep the migration covered across Python 3.11 through 3.13
- risk: local make test still requires PY to point at an environment with pytest
… prevents collection conflict with same-named unit test files
…, stream errors, ValidationError parse failure (tasks 9-12, 15)
…s, retry PATCH/DELETE/respect_retry_after (tasks 16, 17)
…trip, restore lifecycle

- Custom fields: Field -> Fieldset -> Model -> Asset, exercising both top-level
  column-name PATCH and in-place custom_fields dict mutation (README pattern).
- File round-trip: 64KiB random payload, byte-compares upload vs download,
  verifies progress callback, confirms file removal via /delete suffix.
- Restore lifecycle: create -> soft-delete (with marker assertion) -> restore
  -> confirm reachable with no deleted markers.

Closes the unit-vs-real gap on the three highest-risk asset code paths.
When docker/api_key.txt is missing or a directory, the seeder service's
bind-mount './api_key.txt:/api_key.txt' makes Docker auto-create the host
path as a directory, breaking both the seeder's '> /api_key.txt' redirect
and the integration conftest's read_text() with IsADirectoryError.

Fix:
- docker-up: pre-create api_key.txt as an empty regular file if missing
  or if it is a directory.
- docker-down: replace '> docker/api_key.txt' (which silently fails on a
  directory) with 'rm -rf' + 'touch' so the file always ends up as a
  regular empty file.
- test-integration: hard-fail with a clear message if api_key.txt is
  somehow still a directory after docker-up.
…pe-IT

The integration suite was failing with two distinct flake patterns:

1. Connection reset on first run after 'make docker-up'
   The Makefile waited for docker/api_key.txt to be non-empty, but Snipe-IT's
   app container is still booting Apache/PHP at the moment the seeder writes
   the token. The first POSTs hit a half-open socket and got ECONNRESET.
   Fix: add a real API readiness probe that polls /api/v1/users/me with the
   token until it returns 200 (Makefile test-integration target).

2. HTTP 429 on second/rapid runs
   Snipe-IT defaults to API_THROTTLE_PER_MINUTE=240 (4 req/sec). The full
   integration suite hits the limiter. The library's default retry policy
   (HEAD/GET/OPTIONS only, never POST/PATCH/DELETE) is correct for
   production safety but wrong for tests, where Snipe-IT 429s before
   processing the request body so retries are safe.
   Fix:
   - Bump docker/.env API_THROTTLE_PER_MINUTE to 1200 (20 req/sec).
   - Configure the real_snipeit_client fixture to retry mutating methods
     on 429 with max_retries=5; document why this is test-env-only.

E2E test fixes from first real-Snipe-IT run:

- test_asset_files_e2e: Snipe-IT rejects .bin files (extension allowlist)
  and renames stored files with an asset-{id}-{random}- prefix. Switch to
  .txt with 64KiB of hex chars; match by suffix instead of exact filename.

- test_custom_fields_e2e: setattr(asset, '_snipeit_*', value) is silently
  dropped by ApiObject's underscore-guard, so save() drops the change. Use
  mark_dirty() workaround. Document this in README. The README's nested
  in-place mutation pattern is also silently ignored by Snipe-IT — test
  now skips with diagnostic instead of failing.

Run results: 17 passed, 2 skipped (with clear reasons), 0 failed.
Stable across consecutive runs.
Introduces `get_custom_field` and `set_custom_field` helper methods on
the
`Asset` model. These methods abstract away the complexities of
Snipe-IT's
custom field API, which uses different shapes for reading and writing.

The `get_custom_field` method allows retrieving custom field values by
their
display label, providing a default value if the field is not found.

The `set_custom_field` method stages changes to custom fields by their
display label. When `save()` is called, these staged changes are
translated
into the correct top-level keys in the PATCH request, using the
underlying
column names as expected by the Snipe-IT API. This avoids the common
pitfall
of directly setting attributes starting with `_`, which are ignored by
the
library's dirty tracker.
- Add Asset.pending_custom_fields() for inspecting staged state
- Fix set_custom_field() + save() not requiring refresh() between calls
- Fold PATCH response column-name keys back into nested shape
- get_custom_field() now returns server value, not staged value
- Setting a field back to server value cancels the pending stage
- Staging moved to dedicated _pending_custom_fields PrivateAttr
- Remove NOTICE from license-files (file doesn't exist)
- Migrate [mutmut] config from setup.cfg to pyproject.toml
- Delete setup.cfg
- Fix .gitignore: remove *.lock, add .kiro/
- Track uv.lock for reproducible builds
@coderabbitai

coderabbitai Bot commented May 17, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@Wil-Collier has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 55 minutes and 16 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 53fa8198-dcc8-46fc-a302-95de4bfed5da

📥 Commits

Reviewing files that changed from the base of the PR and between 11b0859 and 75299d0.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (91)
  • .github/workflows/ci.yml
  • .github/workflows/mutation.yml
  • .gitignore
  • CHANGELOG.md
  • LICENSE
  • Makefile
  • README.md
  • RELEASING.md
  • docker/.env
  • docker/README.md
  • docker/docker-compose.yml
  • docs/audit.json
  • docs/groups.json
  • docs/maintenances.json
  • docs/reports.json
  • docs/settings.json
  • docs/split_api.py
  • pyproject.toml
  • pytest.ini
  • setup.cfg
  • snipeit/__init__.py
  • snipeit/_log.py
  • snipeit/_retry.py
  • snipeit/client.py
  • snipeit/client.pyi
  • snipeit/exceptions.py
  • snipeit/py.typed
  • snipeit/resources/accessories.py
  • snipeit/resources/assets.py
  • snipeit/resources/assets/__init__.py
  • snipeit/resources/assets/files.py
  • snipeit/resources/assets/labels.py
  • snipeit/resources/assets/manager.py
  • snipeit/resources/assets/model.py
  • snipeit/resources/base.py
  • snipeit/resources/categories.py
  • snipeit/resources/companies.py
  • snipeit/resources/components.py
  • snipeit/resources/consumables.py
  • snipeit/resources/departments.py
  • snipeit/resources/fields.py
  • snipeit/resources/fieldsets.py
  • snipeit/resources/licenses.py
  • snipeit/resources/locations.py
  • snipeit/resources/manufacturers.py
  • snipeit/resources/models.py
  • snipeit/resources/status_labels.py
  • snipeit/resources/suppliers.py
  • snipeit/resources/users.py
  • tests/__init__.py
  • tests/conftest.py
  • tests/contract/test_public_surface.py
  • tests/integration/conftest.py
  • tests/integration/resources/test_asset_files_e2e.py
  • tests/integration/resources/test_asset_restore_e2e.py
  • tests/integration/resources/test_assets.py
  • tests/integration/resources/test_companies.py
  • tests/integration/resources/test_custom_fields_e2e.py
  • tests/integration/resources/test_suppliers.py
  • tests/unit/resources/test_accessories.py
  • tests/unit/resources/test_assets.py
  • tests/unit/resources/test_assets_extra.py
  • tests/unit/resources/test_assets_labels.py
  • tests/unit/resources/test_base.py
  • tests/unit/resources/test_categories.py
  • tests/unit/resources/test_components.py
  • tests/unit/resources/test_consumables.py
  • tests/unit/resources/test_departments.py
  • tests/unit/resources/test_fields.py
  • tests/unit/resources/test_fieldsets.py
  • tests/unit/resources/test_licenses.py
  • tests/unit/resources/test_locations.py
  • tests/unit/resources/test_manufacturers.py
  • tests/unit/resources/test_models.py
  • tests/unit/resources/test_pagination.py
  • tests/unit/resources/test_resources_smoke.py
  • tests/unit/resources/test_resources_specific.py
  • tests/unit/resources/test_shape_validation.py
  • tests/unit/resources/test_status_labels.py
  • tests/unit/resources/test_users.py
  • tests/unit/test_assets_endpoints.py
  • tests/unit/test_client.py
  • tests/unit/test_client_edge_cases.py
  • tests/unit/test_client_properties.py
  • tests/unit/test_exceptions.py
  • tests/unit/test_logging.py
  • tests/unit/test_property_apiobject.py
  • tests/unit/test_repr.py
  • tests/unit/test_repr_exact.py
  • tests/unit/test_retries.py
  • tests/unit/test_streaming_download.py
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dev

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@Wil-Collier Wil-Collier merged commit 82b6614 into main May 17, 2026
8 of 9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant