From e3b1c9379d277276ad0e2ee0931fcaab36a5fee9 Mon Sep 17 00:00:00 2001 From: Simon Clark Date: Mon, 4 May 2026 19:30:56 +0100 Subject: [PATCH 01/14] docs: design for TestingBase pilot Captures the approved design before implementation: - Approach: upstream-strict two-venv layout (tests/shared submodule + tests/venv + tests/testing-requirements.txt). Picked over a pytest- unified single-venv approach because it works uniformly across all workspace plugins (most don't have pyproject.toml) and matches what other Indigo plugin developers use. - Pilot scope: ValidateXmlFile across all 5 netro XML files (Actions/Devices/Events/MenuItems/PluginConfig) plus one APIBase smoke test that fetches Indigo's plugin list and asserts netro is loaded. No netro hardware required. - Environment: local Indigo 2025.2 sandbox via dev license. jarvis stays on 2025.1 for now. - Intent: full workspace adoption. Pilot exists to learn the mechanics; rollout to heatmiser/home-intelligence/UK-Trains plus workspace CLAUDE.md update is follow-on work. Implementation plan to follow. Co-Authored-By: Claude Opus 4.7 (1M context) --- ...26-05-04-netro-testingbase-pilot-design.md | 152 ++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 docs/plans/2026-05-04-netro-testingbase-pilot-design.md 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. From 91ffa551fec44ca77eb7d2c8613cc887583a99a5 Mon Sep 17 00:00:00 2001 From: Simon Clark Date: Mon, 4 May 2026 19:33:14 +0100 Subject: [PATCH 02/14] docs: implementation plan for TestingBase pilot 12 bite-sized tasks covering submodule setup, venv creation, env template, ValidateXmlFile coverage of all 5 netro XML files, the APIBase plugin-loaded smoke test, pytest config to keep main env clean, tests/README.md, and version bump + PR. Companion to the design doc in the same folder. Co-Authored-By: Claude Opus 4.7 (1M context) --- ...2026-05-04-netro-testingbase-pilot-plan.md | 633 ++++++++++++++++++ 1 file changed, 633 insertions(+) create mode 100644 docs/plans/2026-05-04-netro-testingbase-pilot-plan.md 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. From 92bece7e299104cf639bd4c32a4cf9197c3f6332 Mon Sep 17 00:00:00 2001 From: Simon Clark Date: Mon, 4 May 2026 19:59:06 +0100 Subject: [PATCH 03/14] chore: gitignore tests/.env and tests/venv for TestingBase --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) 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/ From f1fdce140b1c5327b878ec71e2dde09cee0db576 Mon Sep 17 00:00:00 2001 From: Simon Clark Date: Mon, 4 May 2026 19:59:22 +0100 Subject: [PATCH 04/14] feat: add TestingBase submodule at tests/shared/ --- .gitmodules | 3 +++ tests/shared | 1 + 2 files changed, 4 insertions(+) create mode 100644 .gitmodules create mode 160000 tests/shared 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/tests/shared b/tests/shared new file mode 160000 index 0000000..3fe47ef --- /dev/null +++ b/tests/shared @@ -0,0 +1 @@ +Subproject commit 3fe47ef565307bd82ac44f5f23c84b77bf25696d From a215193034e9917c67826ff568d8a085fe69a12f Mon Sep 17 00:00:00 2001 From: Simon Clark Date: Mon, 4 May 2026 19:59:31 +0100 Subject: [PATCH 05/14] feat: add tests/testing-requirements.txt chaining to TestingBase shared deps --- tests/testing-requirements.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 tests/testing-requirements.txt diff --git a/tests/testing-requirements.txt b/tests/testing-requirements.txt new file mode 100644 index 0000000..e0acff3 --- /dev/null +++ b/tests/testing-requirements.txt @@ -0,0 +1 @@ +-r shared/module-requirements.txt From 0434aa9ded10b44c3a7e8e7525622a80e1ea63c4 Mon Sep 17 00:00:00 2001 From: Simon Clark Date: Mon, 4 May 2026 19:59:43 +0100 Subject: [PATCH 06/14] feat: add tests/.env.example template for TestingBase --- tests/.env.example | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 tests/.env.example 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 From e432727e8390300a136af5a07b1502990b3846a6 Mon Sep 17 00:00:00 2001 From: Simon Clark Date: Mon, 4 May 2026 19:59:51 +0100 Subject: [PATCH 07/14] feat: add tests/integration package with run instructions --- tests/integration/__init__.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 tests/integration/__init__.py 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 +""" From 1619aad904183fd63445bce17aec4432366d49e5 Mon Sep 17 00:00:00 2001 From: Simon Clark Date: Mon, 4 May 2026 20:24:27 +0100 Subject: [PATCH 08/14] test: add ValidateXmlFile coverage for all 5 netro XML files --- tests/integration/test_xml_validation.py | 43 ++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 tests/integration/test_xml_validation.py diff --git a/tests/integration/test_xml_validation.py b/tests/integration/test_xml_validation.py new file mode 100644 index 0000000..2947763 --- /dev/null +++ b/tests/integration/test_xml_validation.py @@ -0,0 +1,43 @@ +"""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" From c7a0b21ed95f331c39f5bdcb1ab536fcba60db70 Mon Sep 17 00:00:00 2001 From: Simon Clark Date: Mon, 4 May 2026 20:26:36 +0100 Subject: [PATCH 09/14] test: add APIBase smoke test asserting netro is loaded --- tests/integration/test_indigo_smoke.py | 29 ++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 tests/integration/test_indigo_smoke.py diff --git a/tests/integration/test_indigo_smoke.py b/tests/integration/test_indigo_smoke.py new file mode 100644 index 0000000..bd87f03 --- /dev/null +++ b/tests/integration/test_indigo_smoke.py @@ -0,0 +1,29 @@ +"""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}", + ) From 113e53293496f15e3f6864f2ef7e8ca2021e90a6 Mon Sep 17 00:00:00 2001 From: Simon Clark Date: Mon, 4 May 2026 20:34:28 +0100 Subject: [PATCH 10/14] chore: exclude tests/integration tests/shared tests/venv from main pytest discovery MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The TestingBase pilot adds three subtrees under tests/ that the main pytest env can't import: - tests/integration/ — APIBase / ValidateXmlFile tests; require the dedicated venv - tests/shared/ — TestingBase submodule, ships its own example tests (example_test_xml_files.py) that fail without the dedicated venv - tests/venv/ — pytest-internal test files inside the venv itself Adding norecursedirs to both pytest.ini (active config) and pyproject.toml's [tool.pytest.ini_options] block (kept in sync for tooling that reads pyproject) so a casual `pytest` from netro's main env still passes cleanly. Verified: `pytest --collect-only` collects all 508 existing Pattern A tests across 13 files and 0 TestingBase tests. Note for follow-up: the duplication of pytest config across pytest.ini and pyproject.toml is pre-existing technical debt; consolidating to one file is a separate cleanup task. Co-Authored-By: Claude Opus 4.7 (1M context) --- pyproject.toml | 4 ++++ pytest.ini | 6 ++++++ 2 files changed, 10 insertions(+) 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 From ae26425ad1e2730ce480ba9306c7082c21014b04 Mon Sep 17 00:00:00 2001 From: Simon Clark Date: Mon, 4 May 2026 20:36:38 +0100 Subject: [PATCH 11/14] chore: make tests/venv self-sufficient for running pytest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two related fixes the plan didn't anticipate: - tests/testing-requirements.txt: add `pytest`. Upstream's module-requirements.txt installs python-dotenv and httpx but not pytest, so the integration venv couldn't run the tests without a separate manual pip install. - tests/integration/pytest.ini: empty `addopts` so the integration suite ignores the main netro pytest.ini's coverage flags. Those flags require pytest-cov (which isn't in this venv) and produce meaningless output for integration tests anyway since they don't import netro source. Pytest's rootdir search picks up this inner config first when running tests under tests/integration/. Verified: from tests/, `venv/bin/pytest integration/ --collect-only` collects exactly the 6 expected tests (5 ValidateXmlFile + 1 APIBase smoke) with no command-line overrides needed. Note: this adds a third pytest config file in the repo. Unlike the pytest.ini/pyproject.toml duplication (pre-existing tech debt, same scope), this one is integration-scoped — different responsibility, not duplicate. Co-Authored-By: Claude Opus 4.7 (1M context) --- tests/integration/pytest.ini | 20 ++++++++++++++++++++ tests/testing-requirements.txt | 6 ++++++ 2 files changed, 26 insertions(+) create mode 100644 tests/integration/pytest.ini 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/testing-requirements.txt b/tests/testing-requirements.txt index e0acff3..dcc2098 100644 --- a/tests/testing-requirements.txt +++ b/tests/testing-requirements.txt @@ -1 +1,7 @@ -r shared/module-requirements.txt + +# Pytest runs the unittest.TestCase classes from TestingBase. Upstream's +# module-requirements doesn't include it; we add it here so this venv +# can run `pytest tests/integration/`. +pytest + From c1e6992961809ccf6f5b9d5f7f175432cdce9b23 Mon Sep 17 00:00:00 2001 From: Simon Clark Date: Mon, 4 May 2026 20:37:50 +0100 Subject: [PATCH 12/14] docs: add tests/README explaining Pattern A + Pattern B --- tests/README.md | 77 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 tests/README.md 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. From ab23e597408ca57c6df37bbba027df1d0e5350ef Mon Sep 17 00:00:00 2001 From: Simon Clark Date: Mon, 4 May 2026 20:38:33 +0100 Subject: [PATCH 13/14] chore: bump PluginVersion to 2026.5.4 for TestingBase pilot PR Co-Authored-By: Claude Opus 4.7 (1M context) --- .../Contents/Info.plist | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) 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 From 3ae3b9783b49b384c79e24ff911e1d3abbaa5b7c Mon Sep 17 00:00:00 2001 From: Simon Clark Date: Tue, 5 May 2026 08:38:22 +0100 Subject: [PATCH 14/14] fix: smoke test queries plugin state via IOM, not via non-existent HTTP endpoint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The original smoke test guessed at an `indigo.plugins` HTTP endpoint that doesn't exist. Indigo's HTTP API only exposes devices, variables, actionGroups, controlPages, logs, triggers, and schedules. Plugin state isn't queryable via HTTP at all. Switch to the IOM via `run_host_script` (TestingBase's wrapper around `indigo-host -e