Skip to content

refactor(mock): consolidate eleven per-module MockBase classes into a shared base - #4372

Merged
springfall2008 merged 7 commits into
mainfrom
refactor/shared-mock-base
Jul 29, 2026
Merged

refactor(mock): consolidate eleven per-module MockBase classes into a shared base#4372
springfall2008 merged 7 commits into
mainfrom
refactor/shared-mock-base

Conversation

@springfall2008

Copy link
Copy Markdown
Owner

Summary

Eleven production modules each defined a near-identical MockBase class — a stand-in for the PredBat base object, used only to drive each module's standalone command-line harness (python fox.py --key=...). Roughly 90% of each copy was identical; the divergence was incidental drift from copy-paste, not intent.

This replaces all eleven with a single apps/predbat/mock_base.py:

  • 5 plain importsdeye, enphase, fox, solis, teslemetry
  • 5 subclassesaxle, gecloud, octopus (cache roots), gecloud (mock HA interface), sigenergy (readonly= seeding), solax (UTC clock)
  • 1 aliaskraken (KrakenMockBase = MockBase)

Net: 588 duplicated lines removed, replaced by a ~125-line shared module plus ~40 lines of subclasses.

Latent bugs fixed

The drift had hidden four real defects on the CLI-harness path, none of which had automated coverage:

  1. fox and solax had a narrow get_arg(key, default) — any call routed through ComponentBase.get_arg, which passes indirect/combine/attribute/index/domain/can_override/required_unit as keywords, would raise TypeError.
  2. gecloud's getattr(self.base, "record_status", None) guard was always falsy — the mock had no record_status, so cloud auth-denied was silently never reported from the CLI.
  3. fox, kraken and solis built a naive midnight_utc from datetime.now() alongside a timezone-aware now_utc, so now_utc - midnight_utc raised TypeError: can't subtract offset-naive and offset-aware datetimes.
  4. The most-copied dashboard_item mutated its caller's dictattributes["options"] = "..." was applied to the caller's own dict, which was then handed to set_state_wrapper and stored corrupted. kraken and sigenergy already avoided this with a display copy; the shared version adopts that.

Two further behaviours were unified deliberately: get_arg/set_arg now persist to and read from self.args everywhere (previously nine modules returned the bare default, with self.args empty in practice, so results are unchanged), and set_arg(key, None) now deletes the key to match userinterface.py.

Tests

New apps/predbat/tests/test_mock_base.py — 17 tests registered in TEST_REGISTRY. Beyond the per-method coverage, two are worth calling out:

  • test_mock_base_covers_component_base_contract binds a real ComponentBase subclass to a MockBase and exercises every property and delegate, so it fails with AttributeError if someone adds a self.base.<x> dereference the mock doesn't cover. Verified non-vacuous: deleting an attribute makes it raise.
  • test_mock_base_reexport_identity asserts the five re-export modules expose the shared class by identity and the five subclassing modules are subclasses of it — catching a botched future edit to any of those import lines.

kraken.py's import json and deye.py's from datetime import datetime became unused and were removed (verified with ruff F401).

Verification

  • Full suite: ./run_all → exit 0, "All tests passed (total time: 102.38s)"
  • ./run_pre_commit → all 10 hooks green

Notes

Out of scope by design: the test-local MockBase classes in tests/ (different surface, already shared where it matters) and web_mcp.py's inline mock (unrelated surface — no get_state_wrapper/get_arg at all).

Separately worth knowing: interrogate is configured with fail-under = 100 in pyproject.toml but is not wired into .pre-commit-config.yaml, so the 100% docstring rule in CLAUDE.md has never actually been gate-enforced. This branch satisfies it regardless — flagging as a pre-existing repo issue, not something to fix here.

Design and plan: docs/superpowers/specs/2026-07-29-shared-mock-base-design.md, docs/superpowers/plans/2026-07-29-shared-mock-base.md

🤖 Generated with Claude Code

springfall2008 and others added 7 commits July 29, 2026 17:30
Eleven production modules each define a near-identical MockBase used only
to drive standalone command-line runs. Design consolidates them onto a
single apps/predbat/mock_base.py, with three small subclasses for the
modules that genuinely differ (axle/octopus config_root, sigenergy
readonly) and an alias for KrakenMockBase.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Planning surfaced three factual errors in the spec, now fixed:
- gecloud needs a subclass, not a plain re-export: it supplies ha_interface
  (dereferenced at gecloud.py:1037) as well as its own config_root.
- solax needs a subclass to keep local_tz = timezone.utc rather than the
  machine's local timezone.
- The split is 5 re-exports / 5 subclasses / 1 alias, not 7/3/1.

Also documents a fourth behaviour fix: fox, kraken and solis build a naive
midnight_utc alongside an aware now_utc, so subtracting them raises. The
shared base derives midnight_utc from the aware now_utc.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds apps/predbat/mock_base.py providing the superset of the PredBat base
surface that ComponentBase dereferences, with unit tests. No module uses it
yet; migrations follow.
…lemetry

These five need no module-specific state, so each drops its local copy in
favour of a plain import.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
axle, gecloud and octopus keep their own cache roots, gecloud its mock HA
interface, solax its UTC clock and sigenergy its readonly seeding. kraken
aliases the shared class and drops the now-unused json import.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ssertions

CSpell flagged the abbreviation dflt; spell it out to keep the workspace
dictionary free of ad-hoc shorthand.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Add a contract test that binds a real ComponentBase subclass to MockBase
  and exercises every property/delegate method, so an AttributeError fires
  if a future self.base dereference in component_base.py outgrows the mock
  (the old attribute_superset test only compared two hardcoded lists).
- Fix MockBase.set_arg to pop the key on a None value, matching
  userinterface.py's Fetch.set_arg semantics instead of storing None.
- Add coverage asserting deye/enphase/fox/solis/teslemetry re-export the
  identical shared MockBase, and axle/gecloud/octopus/sigenergy/solax
  subclass it.
- Widen MockBase.log to accept hass.py's quiet keyword.
- Correct two factual errors in the design spec: the octopus.py
  record_status calls are on the Octopus mixin (Output provides the
  method), not OctopusAPI/ComponentBase; and deye's old dashboard_item
  printed no attributes at all, so it gains attribute printing rather than
  merely losing the options elision.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 29, 2026 17:42

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 consolidates eleven near-identical per-module MockBase CLI harness classes into a single shared apps/predbat/mock_base.py, reducing duplication and fixing drift-induced harness-only defects across vendor modules while keeping existing call sites intact.

Changes:

  • Added a shared MockBase implementation (apps/predbat/mock_base.py) that satisfies the ComponentBase “base” contract and unifies previously divergent behaviours (e.g., get_arg signature breadth, timezone-aware midnight_utc, non-mutating dashboard_item, set_arg(None) deletion).
  • Refactored 11 production modules to either re-export, subclass, or alias the shared MockBase (including KrakenMockBase aliasing and removing now-unused imports).
  • Added and registered a focused unit-test suite (apps/predbat/tests/test_mock_base.py) to lock down the shared mock’s contract and module wiring, plus added design/plan docs for traceability.

Reviewed changes

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

Show a summary per file
File Description
docs/superpowers/specs/2026-07-29-shared-mock-base-design.md Design spec documenting scope, contract, and intentional behaviour unifications.
docs/superpowers/plans/2026-07-29-shared-mock-base.md Implementation plan detailing migration steps and verification approach.
apps/predbat/mock_base.py New shared MockBase used by standalone CLI harnesses; implements the superset contract and fixes drift bugs.
apps/predbat/tests/test_mock_base.py New tests validating MockBase contract vs ComponentBase, wiring/identity across modules, and key behavioural guarantees.
apps/predbat/unit_test.py Registers the new mock_base test suite in TEST_REGISTRY.
apps/predbat/axle.py Replaces local mock with a small subclass of the shared base (custom cache root).
apps/predbat/deye.py Replaces local mock with a direct re-export of the shared base; removes now-unused datetime import.
apps/predbat/enphase.py Replaces local mock with a direct re-export of the shared base.
apps/predbat/fox.py Replaces local mock with a direct re-export of the shared base (fixes prior narrow get_arg).
apps/predbat/gecloud.py Replaces local mock with a subclass of the shared base (custom cache root + mock HA interface).
apps/predbat/kraken.py Replaces KrakenMockBase with an alias to the shared base and removes unused json import.
apps/predbat/octopus.py Replaces local mock with a subclass of the shared base (custom cache root).
apps/predbat/sigenergy.py Replaces local mock with a subclass of the shared base and preserves readonly seeding behaviour.
apps/predbat/solax.py Replaces local mock with a subclass pinned to UTC (fixes prior narrow get_arg).
apps/predbat/solis.py Replaces local mock with a direct re-export of the shared base (fixes prior naive midnight_utc).
apps/predbat/teslemetry.py Replaces local mock with a direct re-export of the shared base.

@springfall2008
springfall2008 merged commit 5b6f20f into main Jul 29, 2026
3 checks passed
@springfall2008
springfall2008 deleted the refactor/shared-mock-base branch July 29, 2026 18: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.

2 participants