Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"name": "indigo",
"source": "./",
"description": "Indigo home automation development toolkit \u2014 plugin development, API integration, and control page building",
"version": "1.9.3",
"version": "1.9.4",
"repository": "https://github.com/simons-plugins/indigo-claude-plugin",
"license": "MIT",
"keywords": [
Expand Down
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "indigo",
"version": "1.9.3",
"version": "1.9.4",
"description": "Indigo home automation development toolkit \u2014 plugin development, API integration, and control page building",
"repository": "https://github.com/simons-plugins/indigo-claude-plugin"
}
90 changes: 89 additions & 1 deletion docs/plugin-dev/patterns/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,96 @@ Use both for any non-trivial plugin: A on every commit, B before each release.

---

## TestingBase conventions (from upstream docs)

The upstream [TestingBase repo](https://github.com/IndigoDomotics/TestingBase) is the **authoritative source for TestingBase usage** — its `README.md` and `example_test_xml_files.py` document the public API. `/indigo:dev` covers Indigo plugin SDK questions; for TestingBase-specific questions, read upstream first.

Two conventions from upstream that aren't intuitive at first read:

### `run_host_script` uses `return`, not `print`

Per upstream README:

> `run_host_script(script: str) -> str` — this will accept an Indigo Python script, run it against the IPH, and **return whatever value the script returns**.

Indigo wraps your script as a function body; `print()` inside it goes to Indigo's event log, not the subprocess stdout. Use `return` to send a value back:

```python
# WRONG — print goes to event log, run_host_script returns ""
script = "print(indigo.server.getInstallFolderPath())"

# RIGHT — return value comes back via stdout
script = "return indigo.server.getInstallFolderPath()"
```

Upstream's own `tests/shared/utils.py:get_install_folder` uses the same `return` convention. Result is always a string — JSON-encode in the script and decode in the test if you need richer types.

### Plugin state is not on the HTTP API — use the IOM

Per upstream README, `run_host_script` is *"useful for testing functions in the IOM that don't have a corresponding API method"*. Plugin state is one such case.

Indigo's HTTP API (`/v2/api/indigo.<endpoint>`) only exposes `devices`, `variables`, `actionGroups`, `controlPages`, `logs`, `triggers`, `schedules` — Indigo itself returns this list when you hit an invalid endpoint. There is no `indigo.plugins` endpoint. To query `isEnabled`, `isRunning`, or `isInstalled`, go through the IOM:

```python
from shared import APIBase
from shared.utils import run_host_script

class TestPluginLoaded(APIBase):
def test_loaded(self):
script = (
f"plugin = indigo.server.getPlugin('{self.plugin_id}')\n"
f"if plugin is None:\n"
f" return 'state=missing'\n"
f"return f'state=found|enabled={{plugin.isEnabled()}}|running={{plugin.isRunning()}}'\n"
)
result = run_host_script(script)
self.assertIn("enabled=True", result, f"indigo-host returned: {result!r}")
```

The plugin object's API (`isInstalled()`, `isEnabled()`, `isRunning()`, plus several properties) is in `/indigo:dev` → `docs/plugin-dev/api/iom/command-namespaces.md` under "Plugin Object Access". That's the SDK side; this section is about how to *invoke* it from a TestingBase test.

---

## Setup notes (from real-world adoption, not upstream docs)

These aren't in TestingBase's documentation — they're observations from this workspace's first adoption. Treat them as field notes; your environment may differ.

### `/usr/local/indigo/` perms can block your user from executing `indigo-host`

On at least one fresh install of Indigo 2025.2 we saw `/usr/local/indigo/` shipped as `drwxrwx--- root:wheel`. A regular user (not in `wheel`) can't even traverse into the directory to execute `indigo-host`. Symptom: `PermissionError: [Errno 13] Permission denied: '/usr/local/indigo/indigo-host'` raised from `subprocess.run` inside `setUpClass`, before any test code runs.

If you hit this, the one-time fix is:

```bash
sudo chmod 0755 /usr/local/indigo/ # makes the dir traversable + listable
```

The binaries inside stay `0770 root:admin` so only admin-group users can run them. macOS primary users are in `admin` by default. Whether this perm pattern is universal across all 2025.2 installs or installer-state-dependent is unclear — most users will never hit this since they won't be freshly setting up a developer machine. Documenting the fix here for the few who do.

### "Reflector Not Found" from a fresh reflector → restart Indigo Server

When you configure a new reflector in Indigo's preferences, the reflector address resolves immediately on the internet, but indigodomo.com's reflector service won't recognise it as live until Indigo Server is restarted. Until then, `httpx` returns HTTP 200 with the public indigodomo.com "Indigo Reflector Not Found" HTML error page. The test will fail with assertion errors that look like JSON-parse failures.

Fix: restart Indigo Server after enabling the reflector. Same applies if you change the reflector subdomain.

This is an Indigo Server quirk, not a TestingBase issue — but worth knowing because the failure mode is confusing (the test sees a 200, not a 404).

### `URL_PREFIX` must match your Indigo Server's HTTPS configuration

The upstream `ENV_TEMPLATE` shows `shared.URL_PREFIX=https://localhost:8176` as the default. Whether that's correct depends on how your Indigo Server is configured:

- **If Indigo allows HTTP on localhost** (the typical out-of-box setup): use `http://localhost:8176`. The default `https://` form will give an SSL handshake timeout because the local IWS isn't terminating TLS.
- **If you've enabled "force HTTPS" in Indigo's Server preferences**, or you're hitting a Reflector: use `https://...`.
- **If you're using a Reflector** (e.g. `https://yourname.indigodomo.net`): always `https://`.

The right `URL_PREFIX` is whatever protocol your specific server is configured to accept — check Indigo's Server preferences if unsure. Match it; the upstream default is reasonable for the Reflector case but assumes a particular config for localhost.

---

## References

- TestingBase upstream: https://github.com/IndigoDomotics/TestingBase
- TestingBase upstream (authoritative for TestingBase usage): https://github.com/IndigoDomotics/TestingBase
- TestingBase upstream `example_test_xml_files.py`: canonical `ValidateXmlFile` patterns
- Plugin HTTP API (consumed by Pattern B): see `/indigo:api`
- Plugin lifecycle (what you'd typically test): see `concepts/plugin-lifecycle.md`
- Plugin Object Access (the IOM surface for plugin queries): `docs/plugin-dev/api/iom/command-namespaces.md` — "Plugin Object Access" section
Loading