Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 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/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "wb-plc",
"description": "Wiren Board controller skills — operate, configure, troubleshoot WB hardware via SSH and wb-cli.",
"version": "1.12.0",
"version": "1.13.0",
"author": {
"name": "Wiren Board",
"url": "https://wirenboard.com"
Expand Down
13 changes: 13 additions & 0 deletions debian/changelog
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
wb-cli (1.8.14) stable; urgency=medium

* wb-plc plugin bumped to 1.13.0. wb-dev: new references/testing.md —
how tests are organized in WB Python services (tests/ layout with
golden files in tests/data, pytest black-box style, WB fixture
patterns: fake sysroot, factory mocks, autouse state reset; run
levels: local pytest, pybuild during .deb build, Jenkins coverage
flags). Patterns distilled from wb-cloud-agent, wb-nm-helper,
wb-diag-collect, wb-ai-skills. SKILL.md gets a Testing section
pointing at it. 1.8.13 is left for the open mirrored-constants PR.

-- Ilya Tikhonyuk <ilya.tikhonyuk@wirenboard.com> Tue, 15 Jul 2026 14:00:00 +0300

wb-cli (1.8.12) stable; urgency=medium

* wb-plc plugin bumped to 1.12.0. New wb-rca skill: root-cause
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "wb-cli"
version = "1.8.12"
version = "1.8.14"
description = "Command-line interface to a Wiren Board controller"
requires-python = ">=3.9"
authors = [{name = "Wiren Board Team", email = "info@wirenboard.com"}]
Expand Down
6 changes: 6 additions & 0 deletions wb-plc/skills/wb-dev/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,12 @@ Canonical configs (clang-format, pyproject.toml, find-python-files) live in <htt

For naming rules, exact CLI invocations and config paths — see **`references/codestyle.md`**.

## Testing

Tests are pytest, live in `tests/` at the repo root (with `__init__.py` — pybuild discovers the suite by package), and mock everything at the process boundary: no live MQTT broker, network, or hardware in unit tests. Fixture data is captured from real controllers into `tests/data/`, not hand-invented. The suite runs at three levels: locally (`pytest`), by pybuild during the .deb build, and in CI via the Jenkinsfile coverage flags (`defaultRunCoverage`, `defaultCoverageMin`).

For the layout, WB fixture patterns (fake sysroot in `tmp_path`, factory mocks, autouse state reset, golden files), run commands and coverage thresholds — see **`references/testing.md`**.

## MQTT conventions and MQTT-RPC

Every integration on WB must expose state through the `/devices/<id>/controls/<id>` topic space; the web UI, wb-rules, and other services consume that namespace. All `/meta` topics are retained; `/meta/error` doubles as the LWT. Specific typed controls (`temperature`, `voltage`) are deprecated — use `type: value` + `units`.
Expand Down
86 changes: 86 additions & 0 deletions wb-plc/skills/wb-dev/references/testing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# Testing WB Python services

How tests are organized in Wiren Board Python projects. The service template
(`wb-python-service-template`) does not ship tests — the patterns below are distilled from
production repos: `wb-cloud-agent`, `wb-nm-helper`, `wb-diag-collect`, `wb-ai-skills`.
Process rules (what must be tested before a PR) live in the `wb-git`/`wb-development` skills;
this is the engineering how-to.

## Layout

```
tests/
├── __init__.py ← required: pybuild discovers the suite by package
├── conftest.py ← shared fixtures for the whole suite
├── data/ ← captured real-world inputs + expected outputs
│ ├── wb-mqtt-serial.conf
│ └── wb-mqtt-serial.conf.filtered ← golden file: expected result next to input
└── test_<module>.py ← one file per production module
```

- `tests/` sits at the repo root, next to the production package.
- One `test_<module>.py` per production module (`wb-nm-helper`: `test_connection_checker.py`,
`test_dns_resolver.py`, ...). Plain pytest functions, no test classes.
- `tests/data/` holds **captured artefacts from real controllers** (configs, RPC responses,
`/proc` contents) — not hand-invented minimal samples. Expected outputs live next to inputs
as golden files (`wb-diag-collect`: `20bridge.conf` → `20bridge.conf.filtered`).

## Test style

Black-box through the public API: call the function, assert the returned value or the raised
exception — not internal call sequences (same principle as `fw-unittests` for firmware).

```python
def test_do_curl_success_response(settings, mock_subprocess):
mock_subprocess(status.OK, '{"result": "success"}')
data, code = do_curl(settings)
assert data == {"result": "success"}
assert code == 200

def test_do_curl_invalid_method(settings):
with pytest.raises(ValueError, match="Invalid method"):
do_curl(settings, method="invalid")
```

## Fixtures — the WB patterns

Everything at the process boundary is mocked; tests never touch a real broker, network,
hardware, or systemd.

| Pattern | What it solves | Example |
|---|---|---|
| **Factory fixture** | One fixture builds parameterized mocks | `shell_returning(rc, stdout, stderr)` returns a configured `MagicMock` (`wb-ai-skills`) |
| **Fake sysroot in `tmp_path`** | Code reads controller paths (`/etc/wb-release`, `/proc/uptime`) | fixture copies captured files from `tests/data/` into `tmp_path` mirroring the real layout, code gets the root injected (`wb-ai-skills` `controller_root`) |
| **`autouse` state reset** | Module-level state leaks between tests | clear module dicts/threads before and after each test (`wb-cloud-agent` `_clear_metrics_monitor_state`) |
| **`patch` at the boundary** | subprocess / curl / MQTT client / file paths | `patch("...METRICS_COLLECTOR_TEMPLATE_PATH", str(tmp_file))`, `patch` on `subprocess.run` (`wb-cloud-agent`) |
| **`monkeypatch` for CLI args** | Testing `main()` entry points | `monkeypatch.setattr(sys, "argv", [...])` (`wb-cloud-agent` `set_argv`) |

Shared settings objects come from a `settings` fixture constructing the service's real config
class with test values — not a dict imitation.

## Running

| Where | How |
|---|---|
| Locally (devenv/venv) | `pytest`; with coverage — commands from `codestyle/python.ru.md` (`pytest --cov --cov-config=.coveragerc --cov-fail-under=<N>`) |
| During .deb build | pybuild runs the suite automatically (`tests/` is a package); for coverage inside `wbdev ndeb`/`cdeb`: `export WBDEV_PYBUILD_TEST_ARGS="--cov ... --cov-fail-under=<N>"` |
| CI (Jenkins) | flags in the one-line Jenkinsfile, e.g. `buildDebSbuild defaultRunPythonChecks: true, defaultAngryPylint: true, defaultRunCoverage: true, defaultCoverageMin: "59", defaultDoCoverallsReporting: true` |

- `defaultCoverageMin` is per-repo (`wb-cloud-agent`: 59, `wb-nm-helper`: 80) — when adding
tests to an existing repo, don't lower it; when bootstrapping, set a reachable floor and
raise it as coverage grows.
- Every Python repo carries `tests/test_version.py` (from `package-bootstrap`) locking
`debian/changelog` ↔ `pyproject.toml` ↔ `__init__.py` — don't remove it, it fails the build
on version drift.

## What NOT to do

- **No real I/O in unit tests** — no live MQTT broker, no network, no SSH to a controller.
Integration tests against real hardware are a separate story (custom Jenkinsfile, see
`package-bootstrap` "When to ask the user").
- **Don't edit a failing test to make it pass** — surface it; the user decides whether the
test or the production code is wrong (per `wb-development` coder rules).
- **Don't hand-invent fixture data** when a real controller is available — capture the real
config/output into `tests/data/` and cut it down.
- **Don't skip the docstring** on a non-trivial test (multi-step setup, async) — reviewers
flag it (`code-review-orchestrator` project rules).
2 changes: 1 addition & 1 deletion wb_cli/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "1.8.12"
__version__ = "1.8.14"
Loading