Skip to content

tests(changelog): rewrite get_data_from_entries coverage to ALL-and-ONLY mock style#4517

Closed
Copilot wants to merge 2 commits into
copilot/add-per-entry-rst-directory-readerfrom
copilot/refactor-changelog-tests-to-mock
Closed

tests(changelog): rewrite get_data_from_entries coverage to ALL-and-ONLY mock style#4517
Copilot wants to merge 2 commits into
copilot/add-per-entry-rst-directory-readerfrom
copilot/refactor-changelog-tests-to-mock

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented May 14, 2026

This refines the AChangelog.get_data_from_entries test additions from PR #4501 by removing real filesystem-based tests and aligning them with the file’s established mock-only unit-test convention. Production behavior remains unchanged; only test strategy/style is updated.

  • Reworked 7 get_data_from_entries tests to mock-only

    • Replaced tmp_path / real dirs / real file I/O with MagicMock paths and entry_dir.glob(...) stubs.
    • Modeled entry files via mocked path objects (parent.name, stem, read_text) while keeping stem as real strings so .count() / .split() execute naturally.
  • Aligned assertions with canonical “ALL and ONLY” style

    • Patched and asserted exact calls for module dependencies used by the method:
      • utils.from_yaml
      • typing (typing.Change, dict type aliases)
      • cast
      • sorted (to explicitly lock ordering semantics)
    • Added strict call_args / call_args_list assertions for success and failure paths.
  • Preserved branch/behavior coverage from the original intent

    • Happy path + arbitrary section grouping.
    • Filename separator validation errors (no_separator, a__b__c) with ChangelogParseError message checks including path interpolation.
    • YAML load error behavior via mocked utils.from_yaml.side_effect (propagate generic errors / wrap known parse/type-cast errors).
    • Empty directory result and stable ordering behavior based on sorted iteration.
path = MagicMock()
path.parent.name = "bug_fixes"
path.stem = "jwt__bar_fix"
path.read_text.return_value = "Fixed jwt.\n"

entry_dir.glob.return_value = [path]
m_sorted.return_value = [path]

abstract.AChangelog.get_data_from_entries(yaml_path, entry_dir)

assert m_typing.Change.call_args == [(path.read_text.return_value,), {}]
assert m_cast.call_args == [(
    m_typing.ChangelogDict,
    {"date": "Pending", "bug_fixes": [{"area": "jwt", "change": m_typing.Change.return_value}]}
), {}]
Original prompt

Context

This refines PR #4501 (branch copilot/add-per-entry-rst-directory-reader), which adds AChangelog.get_data_from_entries for the per-entry changelog layout.

The implementation in py/envoy.base.utils/envoy/base/utils/abstract/project/changelog.py is fine and must not change in this PR.

The tests added in py/envoy.base.utils/tests/test_abstract_project_changelogs.py use real filesystem (tmp_path) with real .write_text / .glob / .read_text calls. That does not match the project's testing conventions — the rest of this file is exclusively mock-based, "ALL and ONLY" style unit testing (see existing test_abstract_changelog_get_data immediately above the new tests as the canonical example: it patches cast, utils.from_yaml, typing via the patches fixture and asserts on call_args exactly).

This PR rewrites the new tests to that style. No production code changes.

What to do

Rewrite the seven new test functions to be fully mocked

Currently in py/envoy.base.utils/tests/test_abstract_project_changelogs.py (added by PR #4501):

  • test_abstract_changelog_get_data_from_entries_happy_path
  • test_abstract_changelog_get_data_from_entries_arbitrary_section
  • test_abstract_changelog_get_data_from_entries_missing_separator
  • test_abstract_changelog_get_data_from_entries_multiple_separators
  • test_abstract_changelog_get_data_from_entries_missing_yaml
  • test_abstract_changelog_get_data_from_entries_empty_dir
  • test_abstract_changelog_get_data_from_entries_stable_ordering

All of these currently use tmp_path, real directories, real files. Rewrite them all so that:

  1. No real filesystem. No tmp_path, no .mkdir(), no .write_text(), no .read_text() against real paths. Everything via MagicMock / the patches fixture.

  2. Patch the same module-level dependencies the existing test_abstract_changelog_get_data patches, namely (where relevant):

    • cast
    • utils.from_yaml
    • typing
    • plus any new ones needed: exceptions is already imported at module level in the tests file, so you can either patch it or use the real one; match the convention of test_abstract_changelog_get_data which uses the real exceptions.ChangelogParseError to assert on.
  3. Mock entry_dir.glob(...) to return a deterministic iterable of mocked path objects. Each mocked path needs:

    • path.parent.name → the section name
    • path.stem → the area__slug (or whatever malformed variant the test exercises)
    • path.read_text.return_value → the change text
    • path.split(...) etc. is NOT needed — the production code calls path.stem.split(ENTRY_SEPARATOR, 1), so set path.stem to a real string (e.g. "oauth2__foo_fix") so that .split / .count work naturally on it. Stems are strings; you do not need to mock .split / .count separately.
  4. Assert on call_args / call_args_list exactly the way test_abstract_changelog_get_data does — e.g. assert that utils.from_yaml was called with (yaml_path, m_typing.ChangelogSourceDict), assert that cast was called with (m_typing.ChangelogDict, <expected dict>), assert that typing.Change was called for each entry's content, etc. The point of "ALL and ONLY" is that every observable call is asserted and nothing else happens.

  5. Use iters / parametrisation where it reduces duplication, matching the patterns already in this file. Not mandatory if it makes a single-case test harder to read; use judgement consistent with surrounding tests.

  6. yaml_path and entry_dir should be MagicMock() instances passed directly into abstract.AChangelog.get_data_from_entries(yaml_path, entry_dir). Do not construct real pathlib.Path objects.

  7. For the "missing yaml" case (test_abstract_changelog_get_data_from_entries_missing_yaml): the current implementation lets FileNotFoundError propagate raw from utils.from_yaml. Mirror the style of test_abstract_changelog_get_data which parametrises raises across [None, Exception, yaml.reader.ReaderError, exceptions.TypeCastingError]. Either:

    • Add an equivalent parametrised test for get_data_from_entries covering the same set of raise classes from utils.from_yaml and the same two-branch behaviour (re-raise as ChangelogParseError for known errors, propagate Exception raw), OR
    • Keep a single explicit test per raise class, matching whichever style is least intrusive.

    The existing PR's test_..._missing_yaml should be replaced by tests that mock utils.from_yaml.side_effect rather than relying on a real missing file.

  8. For the "missing separator" / "multiple separators" cases: drive these by setting path.stem to "no_separator" / "a__b__c" on a mocked path returned by the mocked glob. Assert exceptions.ChangelogParseError is raised and the path's string repr (or whatever the production code interpolates — currently f"...: {path}") is in the message; since path is a MagicMock...

This pull request was created from Copilot chat.

Copilot AI changed the title [WIP] Refactor new tests for changelog to use mock testing style tests(changelog): rewrite get_data_from_entries coverage to ALL-and-ONLY mock style May 14, 2026
Copilot finished work on behalf of phlax May 14, 2026 15:44
Copilot AI requested a review from phlax May 14, 2026 15:44
@phlax phlax closed this May 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants