Skip to content

Gemini3 iteration on extending coverage for models - #46

Merged
mshriver merged 1 commit into
mainfrom
models-coverage
Nov 24, 2025
Merged

mshriver merged 1 commit into
mainfrom
models-coverage

Conversation

@mshriver

@mshriver mshriver commented Nov 24, 2025

Copy link
Copy Markdown
Contributor

Summary by Sourcery

Improve model serialization behavior and significantly expand test coverage for core client models.

Bug Fixes:

  • Ensure model JSON serialization consistently excludes null fields while honoring field aliases.

Enhancements:

  • Refine to_json implementations for multiple models to use native Pydantic JSON dumping with alias support and null-value exclusion.

Tests:

  • Replace stub unittest-based tests with comprehensive pytest suites covering creation, enum validation, dict/JSON round-trips, alias handling, and None/null-field behavior for Result, Artifact, ArtifactList, Run, Project, Dashboard, Pagination, and Group models.

Copilot AI review requested due to automatic review settings November 24, 2025 13:43
@sourcery-ai

sourcery-ai Bot commented Nov 24, 2025

Copy link
Copy Markdown

Reviewer's Guide

Refactors several model tests from unittest stubs to pytest-based, behavior-focused tests while updating multiple Pydantic model to_json implementations to use model_dump_json with aliases and exclude_none semantics.

Class diagram for updated Pydantic model to_json implementations

classDiagram
    class PydanticBaseModel

    class Artifact {
        +to_json() str
        +to_str() str
        +to_dict() dict
        +from_json(json_str str) Self
    }

    class ArtifactList {
        +to_json() str
        +to_str() str
        +to_dict() dict
        +from_json(json_str str) Self
    }

    class Dashboard {
        +to_json() str
        +to_str() str
        +to_dict() dict
        +from_json(json_str str) Self
    }

    class Group {
        +to_json() str
        +to_str() str
        +to_dict() dict
        +from_json(json_str str) Self
    }

    class Pagination {
        +to_json() str
        +to_str() str
        +to_dict() dict
        +from_json(json_str str) Self
    }

    class Token {
        +to_json() str
        +to_str() str
        +to_dict() dict
        +from_json(json_str str) Self
    }

    class User {
        +to_json() str
        +to_str() str
        +to_dict() dict
        +from_json(json_str str) Self
    }

    class WidgetConfig {
        +to_json() str
        +to_str() str
        +to_dict() dict
        +from_json(json_str str) Self
    }

    class WidgetType {
        +to_json() str
        +to_str() str
        +to_dict() dict
        +from_json(json_str str) Self
    }

    Artifact --|> PydanticBaseModel
    ArtifactList --|> PydanticBaseModel
    Dashboard --|> PydanticBaseModel
    Group --|> PydanticBaseModel
    Pagination --|> PydanticBaseModel
    Token --|> PydanticBaseModel
    User --|> PydanticBaseModel
    WidgetConfig --|> PydanticBaseModel
    WidgetType --|> PydanticBaseModel
Loading

Flow diagram for new to_json behavior using model_dump_json

flowchart LR
    A["Call to_json on model instance"] --> B["Invoke model_dump_json with by_alias true and exclude_none true"]
    B --> C["Pydantic builds dict using field aliases"]
    C --> D["Pydantic removes fields with None values"]
    D --> E["Pydantic serializes to JSON string"]
    E --> F["Return JSON string from to_json"]
Loading

File-Level Changes

Change Details Files
Replace unittest stub tests with comprehensive pytest-style tests for core models, covering construction, serialization, deserialization, and edge cases.
  • Convert Result tests from unittest.TestCase to plain pytest class with function tests
  • Add coverage for Result field defaults, enum validation, dict/json roundtrips, and None-handling semantics
  • Convert ArtifactList, Run, Project, Dashboard, Artifact, Pagination, and Group tests to pytest and expand them to exercise to_dict, to_json, from_dict, from_json, to_str, and roundtrip behavior
  • Add tests that exercise alias behavior (e.g., Pagination pageSize), explicit None inclusion logic, and handling of non-dict inputs in from_dict implementations
test/test_result.py
test/test_artifact_list.py
test/test_run.py
test/test_project.py
test/test_dashboard.py
test/test_artifact.py
test/test_pagination.py
test/test_group.py
Update to_json implementations for several Pydantic models to use model_dump_json with alias and exclude_none semantics.
  • Change Artifact.to_json to call self.model_dump_json(by_alias=True, exclude_none=True) instead of json.dumps(self.to_dict())
  • Apply the same model_dump_json(by_alias=True, exclude_none=True) pattern to ArtifactList, Dashboard, Group, Pagination, Token, User, WidgetConfig, and WidgetType models
  • Remove outdated TODO comments referencing future pydantic v2 migration now that model_dump_json is used
ibutsu_client/models/artifact.py
ibutsu_client/models/artifact_list.py
ibutsu_client/models/dashboard.py
ibutsu_client/models/group.py
ibutsu_client/models/pagination.py
ibutsu_client/models/token.py
ibutsu_client/models/user.py
ibutsu_client/models/widget_config.py
ibutsu_client/models/widget_type.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@codecov

codecov Bot commented Nov 24, 2025

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 57.22%. Comparing base (b20ea06) to head (1a12e6e).
⚠️ Report is 1 commits behind head on main.

❌ Your project status has failed because the head coverage (57.22%) is below the target coverage (85.00%). You can increase the head coverage or adjust the target coverage.

Additional details and impacted files
@@             Coverage Diff             @@
##             main      #46       +/-   ##
===========================================
+ Coverage   46.43%   57.22%   +10.78%     
===========================================
  Files          58       58               
  Lines        4783     4783               
  Branches      496      496               
===========================================
+ Hits         2221     2737      +516     
+ Misses       2521     1996      -525     
- Partials       41       50        +9     
Flag Coverage Δ
unittests 57.22% <100.00%> (+10.78%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
ibutsu_client/models/account_recovery.py 100.00% <100.00%> (+41.37%) ⬆️
ibutsu_client/models/account_registration.py 100.00% <100.00%> (+40.00%) ⬆️
ibutsu_client/models/account_reset.py 100.00% <100.00%> (+40.00%) ⬆️
ibutsu_client/models/artifact.py 100.00% <100.00%> (+34.28%) ⬆️
ibutsu_client/models/artifact_list.py 97.50% <100.00%> (+47.50%) ⬆️
ibutsu_client/models/create_token.py 100.00% <100.00%> (+43.75%) ⬆️
ibutsu_client/models/credentials.py 100.00% <100.00%> (+40.00%) ⬆️
ibutsu_client/models/dashboard.py 100.00% <100.00%> (+34.28%) ⬆️
ibutsu_client/models/dashboard_list.py 97.50% <100.00%> (+47.50%) ⬆️
ibutsu_client/models/group.py 100.00% <100.00%> (+38.70%) ⬆️
... and 21 more

... and 3 files with indirect coverage changes


Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update b20ea06...1a12e6e. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes - here's some feedback:

  • Several model test classes repeat very similar patterns (e.g., creation/to_dict/to_json/from_dict/from_json/roundtrip); consider using pytest parametrization or helper functions/fixtures to reduce duplication and keep the tests easier to maintain.
  • You switched several models’ to_json implementations to use model_dump_json(by_alias=True, exclude_none=True); if any of those models’ to_dict methods perform custom transformations beyond alias/None handling, consider reusing that logic (or centralizing it) so dict and JSON representations remain consistent.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Several model test classes repeat very similar patterns (e.g., creation/to_dict/to_json/from_dict/from_json/roundtrip); consider using pytest parametrization or helper functions/fixtures to reduce duplication and keep the tests easier to maintain.
- You switched several models’ to_json implementations to use model_dump_json(by_alias=True, exclude_none=True); if any of those models’ to_dict methods perform custom transformations beyond alias/None handling, consider reusing that logic (or centralizing it) so dict and JSON representations remain consistent.

## Individual Comments

### Comment 1
<location> `test/test_result.py:80` </location>
<code_context>
+            r = Result(result=res)
+            assert r.result == res
+
+    def test_result_validate_enum_invalid(self):
+        """Test invalid enum values for result field raises ValueError"""
+        with pytest.raises(ValueError, match="must be one of enum values"):
+            Result(result="invalid_status")
+
</code_context>

<issue_to_address>
**suggestion (testing):** The enum invalid-value test is brittle because it asserts on the exact error message text.

Matching the full error string makes this test fragile against minor wording or validation changes in pydantic. Consider either asserting only that a ValueError is raised (no match argument) or using a broader regex (e.g. just "enum") so the test still verifies invalid values are rejected without depending on the exact message text.

```suggestion
        # Use a broad match to avoid brittleness on exact error message wording
        with pytest.raises(ValueError, match="enum"):
```
</issue_to_address>

### Comment 2
<location> `test/test_pagination.py:17` </location>
<code_context>
-
-if __name__ == "__main__":
-    unittest.main()
+class TestPagination:
+    """Pagination model tests"""
+
</code_context>

<issue_to_address>
**issue (testing):** There are no tests covering the updated `to_json` behavior for several other models (Token, User, WidgetConfig, WidgetType), even though their implementations were changed similarly.

Since these models now share the same updated `to_json` implementation, please add tests for Token, User, WidgetConfig, and WidgetType similar to the existing pagination tests (e.g., `test_pagination_to_json` and the `*_none_values_excluded` cases). Those tests should confirm that the JSON is valid, uses field aliases where applicable, and excludes `None` values while still allowing explicitly required `None` fields when dictated by the model’s logic.
</issue_to_address>

### Comment 3
<location> `test/test_artifact_list.py:55-66` </location>
<code_context>
+        assert "pagination" in artifact_list_dict
+        assert artifact_list_dict["pagination"]["page"] == 1
+
+    def test_artifact_list_to_json(self):
+        """Test ArtifactList to_json conversion"""
+        artifact = Artifact(filename="json.txt")
+        artifact_list = ArtifactList(artifacts=[artifact])
+        artifact_list_json = artifact_list.to_json()
+
+        assert isinstance(artifact_list_json, str)
+        parsed = json.loads(artifact_list_json)
+        assert len(parsed["artifacts"]) == 1
+        assert parsed["artifacts"][0]["filename"] == "json.txt"
+
+    def test_artifact_list_from_dict(self):
</code_context>

<issue_to_address>
**suggestion (testing):** Consider adding a JSON serialization test for a completely empty ArtifactList to cover the new `exclude_none` behavior.

Since `to_json` now calls `model_dump_json(..., exclude_none=True)`, please add a test for an empty `ArtifactList()` (both `artifacts` and `pagination` as `None`). This should verify that the resulting JSON omits these keys entirely, ensuring behavior is correct for consumers that depend on absent empty fields.

```suggestion
    def test_artifact_list_to_json(self):
        """Test ArtifactList to_json conversion"""
        artifact = Artifact(filename="json.txt")
        artifact_list = ArtifactList(artifacts=[artifact])
        artifact_list_json = artifact_list.to_json()

        assert isinstance(artifact_list_json, str)
        parsed = json.loads(artifact_list_json)
        assert len(parsed["artifacts"]) == 1
        assert parsed["artifacts"][0]["filename"] == "json.txt"

    def test_artifact_list_to_json_empty(self):
        """Test ArtifactList to_json conversion for an empty list with exclude_none behavior"""
        artifact_list = ArtifactList()  # artifacts=None, pagination=None by default
        artifact_list_json = artifact_list.to_json()

        assert isinstance(artifact_list_json, str)
        parsed = json.loads(artifact_list_json)

        # With exclude_none=True, optional None fields should be omitted entirely
        assert "artifacts" not in parsed
        assert "pagination" not in parsed

    def test_artifact_list_from_dict(self):
```
</issue_to_address>

### Comment 4
<location> `test/test_result.py:74-76` </location>
<code_context>

</code_context>

<issue_to_address>
**issue (code-quality):** Avoid loops in tests. ([`no-loop-in-tests`](https://docs.sourcery.ai/Reference/Rules-and-In-Line-Suggestions/Python/Default-Rules/no-loop-in-tests))

<details><summary>Explanation</summary>Avoid complex code, like loops, in test functions.

Google's software engineering guidelines says:
"Clear tests are trivially correct upon inspection"
To reach that avoid complex code in tests:
* loops
* conditionals

Some ways to fix this:

* Use parametrized tests to get rid of the loop.
* Move the complex logic into helpers.
* Move the complex part into pytest fixtures.

> Complexity is most often introduced in the form of logic. Logic is defined via the imperative parts of programming languages such as operators, loops, and conditionals. When a piece of code contains logic, you need to do a bit of mental computation to determine its result instead of just reading it off of the screen. It doesn't take much logic to make a test more difficult to reason about.

Software Engineering at Google / [Don't Put Logic in Tests](https://abseil.io/resources/swe-book/html/ch12.html#donapostrophet_put_logic_in_tests)
</details>
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread test/test_result.py
Comment thread test/test_pagination.py
Comment thread test/test_artifact_list.py
Comment thread test/test_result.py

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR modernizes and extends test coverage for model classes in the ibutsu_client package. The changes migrate from unittest to pytest-based tests and update model implementations to use pydantic v2's model_dump_json method.

  • Converted 7 test files from unittest to pytest with comprehensive test coverage
  • Updated 9 model files to use pydantic v2's model_dump_json method
  • Added extensive testing for serialization, deserialization, and edge cases

Reviewed changes

Copilot reviewed 41 out of 41 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
test/test_run.py Converted to pytest with comprehensive tests for Run model serialization/deserialization
test/test_result.py Converted to pytest with enum validation and comprehensive coverage for Result model
test/test_project.py Converted to pytest with tests for Project model operations
test/test_pagination.py Converted to pytest with alias-aware tests for Pagination model
test/test_group.py Converted to pytest with basic Group model tests
test/test_dashboard.py Converted to pytest with Dashboard model tests
test/test_artifact_list.py Converted to pytest with nested model tests for ArtifactList
test/test_artifact.py Converted to pytest with Artifact model tests
ibutsu_client/models/widget_type.py Updated to_json to use model_dump_json with exclude_none
ibutsu_client/models/widget_config.py Updated to_json to use model_dump_json with exclude_none
ibutsu_client/models/user.py Updated to_json to use model_dump_json with exclude_none
ibutsu_client/models/token.py Updated to_json to use model_dump_json with exclude_none
ibutsu_client/models/pagination.py Updated to_json to use model_dump_json with exclude_none
ibutsu_client/models/group.py Updated to_json to use model_dump_json with exclude_none
ibutsu_client/models/dashboard.py Updated to_json to use model_dump_json with exclude_none
ibutsu_client/models/artifact_list.py Updated to_json to use model_dump_json with exclude_none
ibutsu_client/models/artifact.py Updated to_json to use model_dump_json with exclude_none

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread ibutsu_client/models/widget_type.py
Comment thread ibutsu_client/models/widget_config.py
Comment thread ibutsu_client/models/user.py
Comment thread ibutsu_client/models/token.py
Comment thread ibutsu_client/models/pagination.py
Comment thread ibutsu_client/models/group.py
Comment thread ibutsu_client/models/dashboard.py
Comment thread ibutsu_client/models/artifact_list.py
Comment thread ibutsu_client/models/artifact.py
Address a bunch of TODOs since pydantic v2 is integrated now

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 65 out of 65 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@mshriver
mshriver merged commit 0774c30 into main Nov 24, 2025
13 of 14 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.

2 participants