diff --git a/.gitignore b/.gitignore index ea877b9..9363903 100644 --- a/.gitignore +++ b/.gitignore @@ -49,3 +49,7 @@ test_results/ docs/ requirements-dev.txt + +# TestingBase — gitignored test creds and dedicated venv +tests/.env +tests/venv/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..06c05a5 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "tests/shared"] + path = tests/shared + url = https://github.com/IndigoDomotics/TestingBase.git diff --git a/Netro Sprinklers.indigoPlugin/Contents/Info.plist b/Netro Sprinklers.indigoPlugin/Contents/Info.plist index de6c0a8..d90bf15 100644 --- a/Netro Sprinklers.indigoPlugin/Contents/Info.plist +++ b/Netro Sprinklers.indigoPlugin/Contents/Info.plist @@ -2,18 +2,10 @@ - PluginVersion - 2026.5.5 - ServerApiVersion - 3.6 - IwsApiVersion - 1.0.0 CFBundleDisplayName Netro Smart Sprinklers CFBundleIdentifier com.simons-plugins.netro - CFBundleVersion - 2.0.0 CFBundleShortVersionString 2.0 CFBundleURLTypes @@ -23,6 +15,8 @@ https://github.com/simons-plugins/netro-indigo + CFBundleVersion + 2.0.0 GithubInfo GithubRepo @@ -30,5 +24,11 @@ GithubUser simons-plugins + IwsApiVersion + 1.0.0 + PluginVersion + 2026.5.6 + ServerApiVersion + 3.6 diff --git a/docs/plans/2026-05-04-netro-testingbase-pilot-design.md b/docs/plans/2026-05-04-netro-testingbase-pilot-design.md new file mode 100644 index 0000000..72f7d6d --- /dev/null +++ b/docs/plans/2026-05-04-netro-testingbase-pilot-design.md @@ -0,0 +1,152 @@ +# Netro TestingBase pilot — design + +**Date:** 2026-05-04 +**Status:** Approved (awaiting implementation plan) + +## Context + +Indigo 2025.2 ships [`TestingBase`](https://github.com/IndigoDomotics/TestingBase) — a shared submodule for live HTTP integration tests against a running Indigo server. The framework provides `APIBase` (a `unittest.TestCase` subclass that opens an `httpx` session to Indigo's HTTP API) and `ValidateXmlFile` (offline schema validation for plugin XML files). + +The workspace's existing test pattern is `unittest.mock`-based pytest tests against in-process plugin code (Pattern A — see `netro/tests/conftest.py` for the canonical example). TestingBase introduces a complementary pattern (Pattern B) that exercises the live runtime. + +This pilot adopts Pattern B in netro as a learning vehicle for workspace-wide adoption. **Intent: full adoption signal.** Once the pilot lands successfully, the pattern rolls out to other production-critical plugins (`heatmiser`, `home-intelligence`, `UK-Trains`) and the workspace `CLAUDE.md` is updated to make TestingBase the standard for live integration tests. + +## Goal + +Stand up TestingBase in netro using the upstream-strict layout, exercise both halves of the framework end-to-end against local Indigo 2025.2, and capture the friction so we know what we're signing up for at scale. + +## Approach + +**Two-venv layout, upstream-strict.** Pattern A continues to use netro's main pyproject.toml env. TestingBase tests live in `tests/integration/` and run from a separate `tests/venv/` populated from `tests/testing-requirements.txt`. This matches the convention used in IndigoDomotics' own repos and is the only layout that works uniformly across all workspace plugins (most don't have pyproject.toml). + +**Live test = plugin-load smoke.** One `APIBase` test that fetches Indigo's plugin list and asserts netro is loaded and enabled. Exercises auth, connectivity, and plugin discovery in a single call — no netro hardware or device fixtures required. + +**XML validation = full coverage.** All five netro XML files (`Actions.xml`, `Devices.xml`, `Events.xml`, `MenuItems.xml`, `PluginConfig.xml`) get a `ValidateXmlFile` subclass. These are pure offline schema checks — they don't need Indigo running. + +## Architecture + +``` +netro/ +├── tests/ # existing — Pattern A (13 tests) +│ ├── conftest.py +│ ├── test_*.py × 13 +│ ├── shared/ # NEW — TestingBase submodule +│ ├── .env # NEW — gitignored, real creds +│ ├── .env.example # NEW — committed template +│ ├── testing-requirements.txt # NEW — chains to shared/module-requirements.txt +│ ├── venv/ # NEW — gitignored, separate venv +│ └── integration/ # NEW — TestingBase test files +│ ├── __init__.py +│ ├── test_xml_validation.py # 5× ValidateXmlFile classes +│ └── test_indigo_smoke.py # 1× APIBase smoke test +├── pyproject.toml # unchanged — main env stays for Pattern A +└── .gitignore # add tests/.env, tests/venv/ +``` + +## Components + +### `tests/shared/` (submodule) + +Pinned to `IndigoDomotics/TestingBase@main`. Read-only — never edited locally. Per upstream's explicit warning, contributors needing changes go through upstream. + +### `tests/testing-requirements.txt` + +``` +-r shared/module-requirements.txt +``` + +Nothing else for the pilot. Future tests can add deps below this line. + +### `tests/.env.example` + +Committed template, mirrors upstream `ENV_TEMPLATE`. Real values go in `tests/.env` (gitignored). Key variables: + +``` +shared.GOOD_API_KEY= +shared.URL_PREFIX=https://localhost:8176 +shared.API_PREFIX=/v2/api +shared.PLUGIN_ID=com.simons-plugins.netro +shared.LOGGING_LEVEL=logging.INFO +shared.PLUGIN_RESTART_WAIT_TIME=5 +shared.PAUSE_AFTER_UPDATE=0.0 +shared.RESTART_IN_DEBUGGER=false +``` + +### `tests/integration/test_xml_validation.py` + +Five classes, one per XML file. Each subclasses `ValidateXmlFile, APIBase` (in that MRO order — upstream is explicit). `server_plugin_dir_path` resolved relatively: + +```python +import os +from shared import APIBase, ValidateXmlFile + +PLUGIN_ROOT = os.path.abspath(os.path.join( + os.path.dirname(__file__), + "../../Netro Sprinklers.indigoPlugin/Contents/Server Plugin", +)) + +class TestActionsXml(ValidateXmlFile, APIBase): + server_plugin_dir_path = PLUGIN_ROOT + file_name = "Actions.xml" +``` + +(Repeat for Devices.xml, Events.xml, MenuItems.xml, PluginConfig.xml.) + +### `tests/integration/test_indigo_smoke.py` + +One class, one test: + +```python +from shared import APIBase + +class TestNetroPluginLoaded(APIBase): + def test_plugin_is_loaded_and_enabled(self): + # Fetch Indigo's plugin list and assert netro is present + enabled. + # Uses APIBase's send_simple_command / get_indigo_object helpers + # against the URL_PREFIX from .env. + ... +``` + +Exact API call resolved during implementation — `APIBase` wraps Indigo's HTTP API and the upstream README hints at `send_simple_command` for plugin-level interactions. + +## Data flow + +1. Developer activates the pilot venv: `source tests/venv/bin/activate`. +2. Runs `pytest tests/integration/`. +3. Pytest auto-discovers `unittest.TestCase` subclasses (TestingBase classes). +4. Each test class's `__init__` (inherited from `APIBase`) loads `tests/.env` via `python-dotenv`. +5. `ValidateXmlFile` tests read the XML file from disk and validate the schema. No network. +6. `APIBase` smoke test opens an `httpx` session to `https://localhost:8176/v2/api`, fetches the plugin list, asserts. + +## Error handling + +- **Submodule not initialized**: `from shared import APIBase` fails at import. Fix: `git submodule update --init`. Documented in `tests/integration/__init__.py` and a future `tests/README.md`. +- **Missing `tests/.env`**: TestingBase fails fast at setUp with a clear error (per upstream). +- **Wrong API key / Indigo not running**: smoke test fails with HTTP error in setUp; XML validation tests still pass (they don't touch Indigo). +- **netro plugin not loaded**: smoke test asserts `loaded=true` and fails informatively. + +## Testing the pilot + +- All 13 existing Pattern A tests must continue to pass via main env: `pytest tests/` (with `tests/integration/` excluded by pyproject.toml's pytest config). +- New integration tests pass against local Indigo 2025.2: `source tests/venv/bin/activate && pytest tests/integration/` shows 6/6 green (5 XML + 1 smoke). +- `git submodule status` shows `tests/shared` initialised and pinned. +- `tests/.env.example` is committed; `tests/.env` is not. + +## Out of scope + +- **CI integration** — deferred. Pilot establishes the local pattern. CI access to Indigo (or a running Indigo in CI) is a follow-up. +- **More than one APIBase test** — the framework smoke is enough to validate the integration. Substantive netro-specific live tests (device states, action invocation) come after rollout. +- **Migrating Pattern A tests** — Pattern A and B do different things. They coexist; nothing migrates. +- **Workspace rollout** — separate work after this pilot lands and we know it works. + +## Open verification questions for implementation + +- Does TestingBase's `APIBase` work on a fresh Indigo 2025.2 with a new API key issued via Indigo's preferences? (Probably yes — verifying this is part of the pilot's value.) +- What's the exact `send_*` call to fetch the plugin list and assert load state? Resolved by reading `shared/classes.py` once the submodule is in. + +## Success criteria + +- 6/6 integration tests green against local Indigo 2025.2. +- 13/13 Pattern A tests still green. +- Design and resulting friction documented well enough to inform workspace rollout. +- PR opened, reviewed, merged. diff --git a/docs/plans/2026-05-04-netro-testingbase-pilot-plan.md b/docs/plans/2026-05-04-netro-testingbase-pilot-plan.md new file mode 100644 index 0000000..e97a5ba --- /dev/null +++ b/docs/plans/2026-05-04-netro-testingbase-pilot-plan.md @@ -0,0 +1,633 @@ +# Netro TestingBase pilot — implementation plan + +> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. + +**Goal:** Set up TestingBase as the live-integration test framework in netro, with all 5 XML files validated and 1 plugin-loaded smoke test green against local Indigo 2025.2. + +**Architecture:** Upstream-strict two-venv layout. Submodule at `tests/shared/`, separate `tests/venv/`, deps from `tests/testing-requirements.txt`. New tests live in `tests/integration/`. Pattern A pytest tests are unaffected. + +**Tech Stack:** Python 3.13, `unittest.TestCase` (via TestingBase's `APIBase`), `httpx`, `python-dotenv`, pytest as the runner. + +**Companion design doc:** `docs/plans/2026-05-04-netro-testingbase-pilot-design.md` — read first if you need context. + +**Branch:** `feat/testingbase-pilot` (already created; design doc already committed). + +--- + +### Task 1: Update .gitignore + +**Files:** +- Modify: `.gitignore` + +**Step 1:** Read current `.gitignore` to find a good insertion point (look for the existing `docs/` line at L50 for style reference). + +**Step 2:** Append at the end of `.gitignore`: + +``` +# TestingBase — gitignored test creds and dedicated venv +tests/.env +tests/venv/ +``` + +**Step 3:** Verify nothing currently tracked matches: + +```bash +git ls-files tests/.env tests/venv 2>&1 +``` + +Expected: empty output (nothing tracked at those paths). + +**Step 4:** Commit. + +```bash +git add .gitignore +git commit -m "chore: gitignore tests/.env and tests/venv for TestingBase" +``` + +--- + +### Task 2: Add TestingBase as a submodule + +**Files:** +- Create: `.gitmodules` (auto-generated by `git submodule add`) +- Create: `tests/shared/` (submodule) + +**Step 1:** Add the submodule. + +```bash +git submodule add https://github.com/IndigoDomotics/TestingBase.git tests/shared +``` + +Expected: clones the repo into `tests/shared/`, creates `.gitmodules`. + +**Step 2:** Confirm submodule status. + +```bash +git submodule status +``` + +Expected: shows `tests/shared` initialised at the upstream HEAD commit. + +**Step 3:** Verify the upstream files are present. + +```bash +ls tests/shared/ +``` + +Expected: `__init__.py`, `classes.py`, `constants.py`, `utils.py`, `module-requirements.txt`, `ENV_TEMPLATE`, `README.md`. + +**Step 4:** Commit. + +```bash +git add .gitmodules tests/shared +git commit -m "feat: add TestingBase submodule at tests/shared/" +``` + +--- + +### Task 3: Create `tests/testing-requirements.txt` + +**Files:** +- Create: `tests/testing-requirements.txt` + +**Step 1:** Create the file with this exact content: + +``` +-r shared/module-requirements.txt +``` + +Nothing else. The pilot's only deps are TestingBase's own — no extras. + +**Step 2:** Confirm `tests/shared/module-requirements.txt` exists and lists at least `python-dotenv` and `httpx`. + +```bash +cat tests/shared/module-requirements.txt +``` + +Expected: contains `python-dotenv` and `httpx`. (If upstream changes, the requirements still chain through `-r`.) + +**Step 3:** Commit. + +```bash +git add tests/testing-requirements.txt +git commit -m "feat: add tests/testing-requirements.txt chaining to TestingBase shared deps" +``` + +--- + +### Task 4: Create `tests/.env.example` + +**Files:** +- Create: `tests/.env.example` + +**Step 1:** Create with this content (mirrors upstream `ENV_TEMPLATE`, populated with netro-specific defaults): + +``` +# Copy to tests/.env (gitignored) and fill in your local values. +# Format: shared.=. Booleans are "true" or "false". + +# An Indigo HTTP API key. Get one from Indigo's Server menu → +# "API Keys…", or from your indigodomo.com account. +shared.GOOD_API_KEY=YOUR-API-KEY-HERE + +# Where Indigo is running. Default is the local server. +shared.URL_PREFIX=https://localhost:8176 + +# API version path — Indigo 2025.2 uses /v2/api. +shared.API_PREFIX=/v2/api + +# Bundle identifier of the plugin under test (used by APIBase.restart_plugin). +shared.PLUGIN_ID=com.simons-plugins.netro + +# Logging level for the test run. +shared.LOGGING_LEVEL=logging.INFO + +# Wait this many seconds after restarting the plugin under test. +shared.PLUGIN_RESTART_WAIT_TIME=5 + +# Pause after a state-changing call so the change can propagate before the +# next assertion. 0.0 = no pause. +shared.PAUSE_AFTER_UPDATE=0.0 + +# Whether to restart the plugin in the debugger. +shared.RESTART_IN_DEBUGGER=false +``` + +**Step 2:** Commit. + +```bash +git add tests/.env.example +git commit -m "feat: add tests/.env.example template for TestingBase" +``` + +--- + +### Task 5: Create `tests/venv/` and install dependencies (local — not committed) + +**Files:** +- Create (untracked): `tests/venv/` + +**Step 1:** Create the venv with Python 3.13. + +```bash +/Library/Frameworks/Python.framework/Versions/3.13/bin/python3 -m venv tests/venv +``` + +If the path is different on this Mac (we previously found 3.13 wasn't installed system-wide), use whichever 3.13 the local Indigo 2025.2 install brought in. As a fallback, Homebrew's 3.13: `/opt/homebrew/opt/python@3.13/bin/python3.13 -m venv tests/venv`. + +**Step 2:** Activate and install. + +```bash +source tests/venv/bin/activate +pip install --upgrade pip +pip install -r tests/testing-requirements.txt +``` + +Expected: `httpx`, `python-dotenv`, and any transitive deps install successfully. + +**Step 3:** Verify imports resolve from the new venv. + +```bash +python -c "from shared import APIBase, ValidateXmlFile; print('OK')" +``` + +Expected: prints `OK`. + +(This step has no commit — `tests/venv/` is gitignored.) + +--- + +### Task 6: Create `tests/.env` (local — not committed) + +**Files:** +- Create (untracked): `tests/.env` + +**Step 1:** In Indigo 2025.2's UI, generate a fresh API key (Server menu → "API Keys…" → "New API Key"). Note the value — it's only shown once. + +**Step 2:** Copy the example. + +```bash +cp tests/.env.example tests/.env +``` + +**Step 3:** Edit `tests/.env` and replace `YOUR-API-KEY-HERE` with the real key. Leave the rest at defaults if the local Indigo is on `https://localhost:8176`. + +**Step 4:** Verify `python-dotenv` loads it. + +```bash +source tests/venv/bin/activate +python -c "import os; from dotenv import load_dotenv; load_dotenv('tests/.env'); print(os.getenv('shared.GOOD_API_KEY')[:8] + '…')" +``` + +Expected: prints first 8 characters of your API key followed by `…`. Confirms the file is read and the variable is exposed. + +(No commit — `tests/.env` is gitignored.) + +--- + +### Task 7: Create `tests/integration/__init__.py` + +**Files:** +- Create: `tests/integration/__init__.py` + +**Step 1:** Create the file with this content: + +```python +"""TestingBase live-integration tests for netro. + +Run from the dedicated venv (not netro's main env): + + source tests/venv/bin/activate + pytest tests/integration/ + +Requires `tests/.env` to exist with a valid Indigo API key. See +`tests/.env.example` for the template, and the `docs/plans/` +design doc for the full pilot context. + +If you see `ModuleNotFoundError: No module named 'shared'`, the +TestingBase submodule isn't initialised. Run: + + git submodule update --init +""" +``` + +**Step 2:** Commit. + +```bash +git add tests/integration/__init__.py +git commit -m "feat: add tests/integration package with run instructions" +``` + +--- + +### Task 8: Write the XML validation test file + +**Files:** +- Create: `tests/integration/test_xml_validation.py` + +**Step 1:** Create the file with this exact content: + +```python +"""ValidateXmlFile coverage for all 5 netro plugin XML files. + +These tests don't need Indigo running — they validate the XML +files on disk against TestingBase's bundled schema. Catch broken +XML before it reaches end users. +""" +import os + +from shared import APIBase, ValidateXmlFile + +# Resolve the Server Plugin directory relative to this test file so the +# tests run on any machine. +PLUGIN_ROOT = os.path.abspath( + os.path.join( + os.path.dirname(__file__), + "../../Netro Sprinklers.indigoPlugin/Contents/Server Plugin", + ) +) + + +class TestActionsXml(ValidateXmlFile, APIBase): + server_plugin_dir_path = PLUGIN_ROOT + file_name = "Actions.xml" + + +class TestDevicesXml(ValidateXmlFile, APIBase): + server_plugin_dir_path = PLUGIN_ROOT + file_name = "Devices.xml" + + +class TestEventsXml(ValidateXmlFile, APIBase): + server_plugin_dir_path = PLUGIN_ROOT + file_name = "Events.xml" + + +class TestMenuItemsXml(ValidateXmlFile, APIBase): + server_plugin_dir_path = PLUGIN_ROOT + file_name = "MenuItems.xml" + + +class TestPluginConfigXml(ValidateXmlFile, APIBase): + server_plugin_dir_path = PLUGIN_ROOT + file_name = "PluginConfig.xml" +``` + +**Step 2:** Run the tests. + +```bash +source tests/venv/bin/activate +cd tests && pytest integration/test_xml_validation.py -v +cd .. +``` + +Expected: all 5 test classes' validation tests pass. The exact test method names come from `ValidateXmlFile`'s implementation (run with `-v` to see them). + +**Step 3:** If any fail, the failing XML file has a real schema problem — fix the XML, not the test. Then re-run. + +**Step 4:** Commit. + +```bash +git add tests/integration/test_xml_validation.py +git commit -m "test: add ValidateXmlFile coverage for all 5 netro XML files" +``` + +--- + +### Task 9: Write the plugin-loaded smoke test + +**Files:** +- Create: `tests/integration/test_indigo_smoke.py` + +**Step 1:** Confirm the right Indigo HTTP API endpoint for plugins. From upstream `tests/shared/classes.py`, `APIBase.get_indigo_object(endpoint, obj_id)` builds `{url_prefix}/v2/api/indigo.{endpoint}` (and appends `/{obj_id}` if given). So `get_indigo_object("plugins")` fetches all plugins and returns a list of dicts. We'll filter for our `PLUGIN_ID`. + +**Step 2:** Create the test file with this content: + +```python +"""Plugin-loaded smoke test using TestingBase's APIBase. + +Asserts the netro plugin is loaded and enabled in the running +Indigo server. Validates auth, connectivity, and plugin +discovery in one call. No netro hardware required. +""" +from shared import APIBase + + +class TestNetroPluginLoaded(APIBase): + def test_netro_is_loaded_and_enabled(self): + plugins = self.get_indigo_object("plugins") + self.assertIsInstance(plugins, list, "plugins endpoint must return a list") + netro = next( + (p for p in plugins if p.get("pluginId") == self.plugin_id), + None, + ) + self.assertIsNotNone( + netro, + f"netro plugin {self.plugin_id} not found in Indigo's plugin list. " + f"Loaded plugin IDs: {sorted(p.get('pluginId', '?') for p in plugins)}", + ) + # Indigo's plugin object exposes 'enabled' and 'isRunning' (or similar). + # If the exact key differs in Indigo 2025.2, the assertion message + # will tell us what keys are actually there. + self.assertTrue( + netro.get("enabled", False), + f"netro plugin found but not enabled. Plugin object: {netro}", + ) +``` + +**Step 3:** Run only this test file, with verbose output so we can see what Indigo actually returns if the assertions fail: + +```bash +source tests/venv/bin/activate +cd tests && pytest integration/test_indigo_smoke.py -v -s +cd .. +``` + +Expected: passes. If it fails on `enabled`, the assertion message will show the actual plugin dict keys — adjust the key name (e.g., to `isEnabled` or `isRunning`) and re-run. **Do not silently weaken the assertion** — find the right key. + +**Step 4:** Once green, commit. + +```bash +git add tests/integration/test_indigo_smoke.py +git commit -m "test: add APIBase smoke test asserting netro is loaded" +``` + +--- + +### Task 10: Configure pytest to exclude `tests/integration/` by default + +**Files:** +- Modify: `pyproject.toml` (find the `[tool.pytest.ini_options]` section, or `pytest.ini` if that's where it lives) + +**Step 1:** Read current pytest config. + +```bash +grep -A20 "pytest" pyproject.toml +``` + +Note the existing config — particularly any `testpaths` or `addopts` keys. + +**Step 2:** Add `tests/integration` to pytest's `norecursedirs` (so the main env's pytest doesn't try to discover and import TestingBase tests, which would fail in that env without `from shared import …`): + +If `[tool.pytest.ini_options]` exists, add: +```toml +norecursedirs = ["tests/integration", "tests/shared", "tests/venv"] +``` + +If it doesn't, add the whole block. + +**Step 3:** Verify Pattern A still runs cleanly from netro's main env. + +```bash +deactivate 2>/dev/null # leave the integration venv if active +pytest tests/ -v +``` + +Expected: 13 Pattern A tests collected, 0 from `tests/integration/`. All pass (or whatever the existing baseline is — should not regress). + +**Step 4:** Verify integration tests still run from the integration venv. + +```bash +source tests/venv/bin/activate +cd tests && pytest integration/ -v +cd .. +``` + +Expected: all 6 integration tests (5 XML + 1 smoke) pass. + +**Step 5:** Commit. + +```bash +git add pyproject.toml +git commit -m "chore: exclude tests/integration tests/shared tests/venv from main pytest discovery" +``` + +--- + +### Task 11: Add a tests/README.md + +**Files:** +- Create: `tests/README.md` + +**Step 1:** Create with this content: + +```markdown +# Netro tests + +Two test patterns coexist here: + +## Pattern A — pytest with mocked Indigo (existing) + +`unittest.mock`-based tests in `tests/test_*.py`. Run from netro's +main env: + +```bash +pytest tests/ +``` + +Fast, no Indigo runtime, runs in CI. + +## Pattern B — TestingBase live integration + +Lives in `tests/integration/` and uses the [TestingBase submodule](https://github.com/IndigoDomotics/TestingBase) +at `tests/shared/`. Runs against a live Indigo server. + +### One-time setup + +```bash +git submodule update --init +python3.13 -m venv tests/venv +source tests/venv/bin/activate +pip install -r tests/testing-requirements.txt +cp tests/.env.example tests/.env +# edit tests/.env, fill in your Indigo API key +``` + +### Running + +```bash +source tests/venv/bin/activate +pytest tests/integration/ +``` + +### What's tested + +- `tests/integration/test_xml_validation.py` — ValidateXmlFile for + Actions.xml, Devices.xml, Events.xml, MenuItems.xml, + PluginConfig.xml. Schema validation, no Indigo runtime needed. +- `tests/integration/test_indigo_smoke.py` — APIBase smoke test + asserting netro is loaded and enabled in the running Indigo + server. + +### Updating TestingBase + +The submodule tracks upstream `main`. Pull changes with: + +```bash +git submodule update --recursive --remote tests/shared +``` + +Per upstream's README, never edit `tests/shared/` locally. Open an +issue at https://github.com/IndigoDomotics/TestingBase if you need +changes. +``` + +**Step 2:** Commit. + +```bash +git add tests/README.md +git commit -m "docs: add tests/README explaining Pattern A + Pattern B" +``` + +--- + +### Task 12: Bump PluginVersion + open PR + +**Files:** +- Modify: `Netro Sprinklers.indigoPlugin/Contents/Info.plist` + +**Step 1:** Read current version. + +```bash +plutil -p "Netro Sprinklers.indigoPlugin/Contents/Info.plist" | grep PluginVersion +``` + +Expected: shows current version (`2026.5.3` per the audit memo). + +**Step 2:** Bump patch version (e.g., `2026.5.3 → 2026.5.4`). This is a test-infra change with zero user-facing behaviour, so patch is right per the workspace's version-bump-scope rule. + +```bash +plutil -replace PluginVersion -string "2026.5.4" "Netro Sprinklers.indigoPlugin/Contents/Info.plist" +``` + +(Adjust the new version if `2026.5.3` is no longer current — just bump the rightmost number.) + +**Step 3:** Verify. + +```bash +plutil -p "Netro Sprinklers.indigoPlugin/Contents/Info.plist" | grep PluginVersion +``` + +**Step 4:** Commit. + +```bash +git add "Netro Sprinklers.indigoPlugin/Contents/Info.plist" +git commit -m "chore: bump PluginVersion to 2026.5.4 for TestingBase pilot PR" +``` + +**Step 5:** Push the branch. + +```bash +git push -u origin feat/testingbase-pilot +``` + +**Step 6:** Open PR. Use a HEREDOC so formatting is preserved: + +```bash +gh pr create --title "feat: TestingBase pilot — submodule + XML validation + plugin smoke (2026.5.4)" --body "$(cat <<'EOF' +## Summary + +First adoption of [TestingBase](https://github.com/IndigoDomotics/TestingBase) in the workspace. Establishes the **upstream-strict two-venv layout** as the workspace standard for live integration tests, validated end-to-end on local Indigo 2025.2. + +## What's added + +- \`tests/shared/\` — TestingBase submodule pinned to upstream main +- \`tests/testing-requirements.txt\` — chains to \`shared/module-requirements.txt\` +- \`tests/.env.example\` — committed template; real \`.env\` is gitignored +- \`tests/integration/test_xml_validation.py\` — ValidateXmlFile coverage for all 5 netro XML files (Actions/Devices/Events/MenuItems/PluginConfig) +- \`tests/integration/test_indigo_smoke.py\` — single APIBase test asserting netro is loaded and enabled +- \`tests/README.md\` — Pattern A vs Pattern B run instructions +- \`pyproject.toml\` — excludes \`tests/integration\`, \`tests/shared\`, \`tests/venv\` from main-env pytest discovery +- \`.gitignore\` — adds \`tests/.env\` and \`tests/venv/\` + +Design and rationale: \`docs/plans/2026-05-04-netro-testingbase-pilot-design.md\`. + +## Test plan + +- [x] \`pytest tests/\` (main env) — all 13 Pattern A tests still green; integration tests are not collected +- [x] \`source tests/venv/bin/activate && pytest tests/integration/\` — 6/6 green (5 XML + 1 smoke) +- [x] Smoke test fetches Indigo's plugin list and confirms \`com.simons-plugins.netro\` is loaded and enabled +- [x] \`git submodule status\` shows \`tests/shared\` initialised at the upstream commit pinned in \`.gitmodules\` + +## Out of scope + +- CI integration for Pattern B — deferred (CI doesn't currently have an Indigo to talk to) +- Rolling out the same setup to other workspace plugins — separate work after this lands +- Workspace \`CLAUDE.md\` standard update — separate PR after this lands +EOF +)" +``` + +**Step 7:** Watch CI. + +```bash +gh pr checks --watch +``` + +Wait for green. Do NOT merge — wait for explicit user go-ahead per workspace standards. + +--- + +## Verification (run before reporting "done") + +```bash +# Pattern A still green from main env +deactivate 2>/dev/null +pytest tests/ -v +# Expected: 13 tests pass, 0 from tests/integration/ + +# Pattern B green from integration venv +source tests/venv/bin/activate +cd tests && pytest integration/ -v && cd .. +# Expected: 6 tests pass (5 XML + 1 smoke) + +# Submodule pinned correctly +git submodule status +# Expected: shows tests/shared initialised at upstream HEAD + +# Branch is pushed and CI is green +gh pr checks +# Expected: all checks pass +``` + +If any of these fail, fix the failure — do not mark the plan complete. diff --git a/pyproject.toml b/pyproject.toml index 35daebd..e8a0072 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,3 +44,7 @@ testpaths = ["tests"] python_files = "test_*.py" python_classes = "Test*" python_functions = "test_*" +# TestingBase lives in tests/venv/ and tests/shared/ (submodule). Don't +# recurse there from the main env — they need their own venv to import +# `shared`. tests/integration/ runs only via that venv too. +norecursedirs = ["tests/integration", "tests/shared", "tests/venv"] diff --git a/pytest.ini b/pytest.ini index 27865af..938c7e7 100644 --- a/pytest.ini +++ b/pytest.ini @@ -9,6 +9,12 @@ python_functions = test_* # Test paths testpaths = tests +# TestingBase lives in its own venv (tests/venv/) and the submodule +# (tests/shared/). Don't recurse there from the main env — `from shared +# import APIBase` will fail without the dedicated deps. Likewise +# tests/integration/ runs only via that venv. +norecursedirs = tests/integration tests/shared tests/venv + # Output options addopts = -v diff --git a/tests/.env.example b/tests/.env.example new file mode 100644 index 0000000..82b742a --- /dev/null +++ b/tests/.env.example @@ -0,0 +1,28 @@ +# Copy to tests/.env (gitignored) and fill in your local values. +# Format: shared.=. Booleans are "true" or "false". + +# An Indigo HTTP API key. Get one from Indigo's Server menu → +# "API Keys…", or from your indigodomo.com account. +shared.GOOD_API_KEY=YOUR-API-KEY-HERE + +# Where Indigo is running. Default is the local server. +shared.URL_PREFIX=https://localhost:8176 + +# API version path — Indigo 2025.2 uses /v2/api. +shared.API_PREFIX=/v2/api + +# Bundle identifier of the plugin under test (used by APIBase.restart_plugin). +shared.PLUGIN_ID=com.simons-plugins.netro + +# Logging level for the test run. +shared.LOGGING_LEVEL=logging.INFO + +# Wait this many seconds after restarting the plugin under test. +shared.PLUGIN_RESTART_WAIT_TIME=5 + +# Pause after a state-changing call so the change can propagate before the +# next assertion. 0.0 = no pause. +shared.PAUSE_AFTER_UPDATE=0.0 + +# Whether to restart the plugin in the debugger. +shared.RESTART_IN_DEBUGGER=false diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..d615b0a --- /dev/null +++ b/tests/README.md @@ -0,0 +1,77 @@ +# Netro tests + +Two test patterns coexist here. + +## Pattern A — pytest with mocked Indigo (default, runs in CI) + +`unittest.mock`-based tests in `tests/test_*.py`. Run from netro's main env: + +```bash +pytest tests/ +``` + +Fast, no Indigo runtime. The repo's main `pytest.ini` covers this layer +(coverage flags, markers, etc.). + +`tests/integration/`, `tests/shared/`, and `tests/venv/` are excluded from +discovery via `norecursedirs` in both `pytest.ini` and `pyproject.toml`. + +## Pattern B — TestingBase live integration + +Lives in `tests/integration/` and uses the [TestingBase submodule](https://github.com/IndigoDomotics/TestingBase) +at `tests/shared/`. Tests subclass `APIBase` (`unittest.TestCase` + `httpx`) +and exercise a running Indigo server. **Requires Indigo installed locally** +— `APIBase.setUpClass` spawns `/usr/local/indigo/indigo-host` to read the +install path, so tests can only run on a developer machine with Indigo +present (not in CI without an Indigo install). + +### One-time setup + +```bash +git submodule update --init # if you cloned without --recurse-submodules +/Library/Frameworks/Python.framework/Versions/3.13/bin/python3 -m venv tests/venv +source tests/venv/bin/activate +pip install -r tests/testing-requirements.txt # installs httpx + python-dotenv + pytest +cp tests/.env.example tests/.env +# edit tests/.env, set shared.GOOD_API_KEY to a real Indigo API key +# (Indigo Server menu → "API Keys…" → New API Key) +``` + +### Running + +```bash +source tests/venv/bin/activate +cd tests +pytest integration/ # picks up tests/integration/pytest.ini +``` + +`tests/integration/pytest.ini` makes the integration directory its own +pytest rootdir. That isolates this suite from the main `pytest.ini`'s +coverage flags (which require `pytest-cov`, which the integration venv +deliberately doesn't install — coverage of TestingBase tests against live +Indigo isn't meaningful). + +### What's tested + +- `tests/integration/test_xml_validation.py` — 5 `ValidateXmlFile` classes + covering Actions.xml, Devices.xml, Events.xml, MenuItems.xml, + PluginConfig.xml. Schema validation only; no Indigo runtime needed for + the validation itself, but `APIBase.setUpClass` still runs (it reads + the Indigo install folder). +- `tests/integration/test_indigo_smoke.py` — one `APIBase` test asserting + `com.simons-plugins.netro` appears in Indigo's plugin list and is + enabled. If your Indigo 2025.2 plugin object uses a different field + name than `enabled`, the assertion message will dump the full plugin + dict so you can adjust the test. + +### Updating TestingBase + +The submodule tracks upstream `main`. Pull changes with: + +```bash +git submodule update --recursive --remote tests/shared +``` + +Per upstream's README, never edit `tests/shared/` locally. File issues +upstream at https://github.com/IndigoDomotics/TestingBase if you need +changes. diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py new file mode 100644 index 0000000..ff2f548 --- /dev/null +++ b/tests/integration/__init__.py @@ -0,0 +1,16 @@ +"""TestingBase live-integration tests for netro. + +Run from the dedicated venv (not netro's main env): + + source tests/venv/bin/activate + pytest tests/integration/ + +Requires `tests/.env` to exist with a valid Indigo API key. See +`tests/.env.example` for the template, and the `docs/plans/` +design doc for the full pilot context. + +If you see `ModuleNotFoundError: No module named 'shared'`, the +TestingBase submodule isn't initialised. Run: + + git submodule update --init +""" diff --git a/tests/integration/pytest.ini b/tests/integration/pytest.ini new file mode 100644 index 0000000..a9b1233 --- /dev/null +++ b/tests/integration/pytest.ini @@ -0,0 +1,20 @@ +[pytest] +# Pytest config for the TestingBase integration suite (Pattern B). +# +# The netro repo's main pytest.ini at the repo root sets `addopts` with +# coverage flags for Pattern A. Those flags require pytest-cov, which the +# integration venv doesn't install (coverage of TestingBase tests against +# live Indigo isn't meaningful — they don't import netro source). +# +# A pytest.ini here makes this directory its own rootdir, isolating the +# integration suite from the main env's addopts. Run with: +# +# source ../venv/bin/activate +# pytest # picks up THIS config +# +# Or from netro repo root: +# +# pytest tests/integration/ # pytest finds this config via rootdir search + +# Empty addopts — no coverage, no strict markers from the main config. +addopts = diff --git a/tests/integration/test_indigo_smoke.py b/tests/integration/test_indigo_smoke.py new file mode 100644 index 0000000..1e68391 --- /dev/null +++ b/tests/integration/test_indigo_smoke.py @@ -0,0 +1,55 @@ +"""Plugin-loaded smoke test using TestingBase's APIBase + run_host_script. + +Asserts the netro plugin is loaded and enabled in the running Indigo +server. Indigo's HTTP API does not expose plugin state — only devices, +variables, actionGroups, controlPages, logs, triggers, schedules — so +plugin queries go through the IOM via `indigo-host -e