Skip to content

docs: improve unit tests of httpbin demo charm#2254

Merged
dwilding merged 21 commits intocanonical:mainfrom
dwilding:httpbin-collect-status
Jan 14, 2026
Merged

docs: improve unit tests of httpbin demo charm#2254
dwilding merged 21 commits intocanonical:mainfrom
dwilding:httpbin-collect-status

Conversation

@dwilding
Copy link
Contributor

@dwilding dwilding commented Dec 23, 2025

This PR makes several improvements to the unit tests of our httpbin demo charm:

  • Include tests for the status reporting logic, per httpbin-demo: Add unit tests to fully exercise the status reporting #2059
  • Make the config-changed tests focused on config, not also testing the Pebble connection
  • Refactor the config-changed tests. Parameterize valid and invalid values of log-level.
  • Remove the Arrange, Act, Assert comments from all but the first (pebble-ready) test, as I feel the other tests don't really benefit from the comments

I also made a couple of small changes elsewhere:

  • Used ops.pebble instead of importing pebble from ops
  • Changed the charm code to set a different status message if log-level is empty
  • Slightly refactored the charm code, after a suggestion from James
  • Linked to the httpbin unit tests from the "See more" section of the Harness migration guide (preview)

@dwilding dwilding linked an issue Dec 23, 2025 that may be closed by this pull request
@dwilding dwilding requested a review from Copilot December 23, 2025 08:05
Copy link
Contributor

Copilot AI left a comment

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 improves the unit tests for the httpbin demo charm by making them more comprehensive and maintainable. The changes include refactoring tests to use pytest's parametrization feature, adding new test cases for edge conditions, and reorganizing the code for better clarity.

  • Introduced pytest parametrization to reduce test duplication and improve coverage of log level validation
  • Added new test cases for service status conditions (inactive service, missing service, unavailable container)
  • Refactored charm code to use else block for clearer control flow in service status checking

Reviewed changes

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

File Description
examples/httpbin-demo/tests/unit/test_charm.py Refactored existing tests to use parametrization, added new edge case tests for service status, and improved test naming and documentation
examples/httpbin-demo/src/charm.py Refactored service status checking logic to use else block instead of early return pattern

đź’ˇ Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@dwilding dwilding requested a review from Copilot December 23, 2025 09:58
Copy link
Contributor

Copilot AI left a comment

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 2 out of 2 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.

Comment on lines 69 to 86
@@ -82,6 +80,10 @@ def _on_collect_status(self, event: ops.CollectStatusEvent):
# It's technically possible (but unlikely) for Pebble to have an internal error.
logger.error("Unable to fetch service info from Pebble")
raise
else:
if not service.is_running():
# We can connect to Pebble in the container, but the service hasn't started yet.
event.add_status(ops.MaintenanceStatus("waiting for workload"))
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I refactored this part of the charm code after James's comment on similar code elsewhere: #2062 (comment)

@dwilding dwilding marked this pull request as ready for review December 23, 2025 10:39
# Assert:
assert isinstance(state_out.unit_status, testing.BlockedStatus)
assert invalid_level in state_out.unit_status.message
assert f"'{user_log_level}'" in state_out.unit_status.message
Copy link
Contributor

Choose a reason for hiding this comment

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

Minor: this assertion doesn't do all that much when the test is parametrised with an empty string. A pair of quotes could come from anywhere.

P.S. this could also be written as:

Suggested change
assert f"'{user_log_level}'" in state_out.unit_status.message
assert f"{user_log_level!r}" in state_out.unit_status.message

though I'm not sure which is easier to understand.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point, thanks. How about this - I've changed the charm code to give a different error message when the config value is empty. Then we can split into two (unparameterized) tests:

def test_config_changed_empty():
    """Test a config-changed event when the config is invalid."""
    ctx = testing.Context(HttpbinDemoCharm)
    container = testing.Container(CONTAINER_NAME, can_connect=True)
    state_in = testing.State(containers={container}, config={"log-level": ""})
    state_out = ctx.run(ctx.on.config_changed(), state_in)
    assert isinstance(state_out.unit_status, testing.BlockedStatus)
    assert state_out.unit_status.message.startswith("Empty log level.")


def test_config_changed_invalid():
    """Test a config-changed event when the config is invalid."""
    ctx = testing.Context(HttpbinDemoCharm)
    container = testing.Container(CONTAINER_NAME, can_connect=True)
    state_in = testing.State(containers={container}, config={"log-level": "foobar"})
    state_out = ctx.run(ctx.on.config_changed(), state_in)
    assert isinstance(state_out.unit_status, testing.BlockedStatus)
    assert state_out.unit_status.message.startswith("Invalid log level: 'foobar'.")

Copy link
Contributor

@james-garner-canonical james-garner-canonical left a comment

Choose a reason for hiding this comment

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

Approving because these are nice improvements that would be fine to merge as-is.

I've commented on some small things I'd change, but feel free to disagree.

Comment on lines 45 to 46
if not log_level:
raise ValueError(f"Empty log level. Valid values are: {', '.join(valid_log_levels)}.")
Copy link
Contributor

Choose a reason for hiding this comment

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

This is nice but seems unnecessary since we'd get a ValueError with a reasonable helpful message anyway: Invalid log level: ''.

Better to keep the code simpler with one less special case? But see also my comment on your thread with Dima.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Which comment on the thread with Dima? (Sorry if I'm totally missing it!)

Copy link
Contributor

Choose a reason for hiding this comment

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

Ugh, I think I didn't submit it correctly and it's lost now.

My thoughts were that the new message.startswith("Invalid log level: '{user_log_level}'.") form should take care of the concern about an empty string coming from anywhere, so a single test could be fine.

I don't really mind the separate tests, but I'm not a big fan of the (imo unnecessary) separate error case.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I get you, thanks. I agree that message.startswith alone is a better change than splitting into two code paths. I've reverted to a single test_config_changed_invalid test with a parameterised value.

@dwilding dwilding merged commit 8520d82 into canonical:main Jan 14, 2026
57 checks passed
@dwilding dwilding deleted the httpbin-collect-status branch January 14, 2026 03:18
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.

httpbin-demo: Add unit tests to fully exercise the status reporting

3 participants