> For the complete documentation index, see [llms.txt](https://docs.layeronecloud.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.layeronecloud.com/platform/testing.md).

# Testing and QC

Work is not done until all three pass:

## 13.1 Required after any code change <a href="#id-131-required-after-any-code-change" id="id-131-required-after-any-code-change"></a>

Work is not done until all three pass:

```bash
.venv/bin/python manage.py test
.venv/bin/python scripts/smoke_all_pages.py
.venv/bin/python manage.py makemigrations --check --dry-run
```

(Windows: `.\.venv\Scripts\python.exe …`.)

The smoke script renders **every parameterless URL** as anonymous, client and admin and fails on any 5xx, or on a 403 for the admin role. Permission-denied tracebacks for restricted routes are expected — only its final `FAILURES:` list indicates failure. Run `scripts/audit_admin_client_links.py` when changing navigation, route composition or shared layouts, and `sh scripts/build_css.sh` after touching Tailwind utilities.

Also run the JavaScript regression scripts under `scripts/` when changing the shell, previews, Aegis graphs or the client workspace.

## 13.2 How the suite runs <a href="#id-132-how-the-suite-runs" id="id-132-how-the-suite-runs"></a>

**Pass no flags.** `config/test_runner.py` is wired in as `TEST_RUNNER` and configures the run itself, so every invocation gets it — a single label, the full suite, an IDE, CI. It does three things:

* **parallel across cores** (Django caps processes at the number of `TestCase` classes, so a one-class run stays serial and pays no cloning cost);
* **MD5 password hashing**, test-only (so creating users is not paying production PBKDF2);
* **migrations replayed once, not per run**: the migrated database is kept as a template and each run works on its own copy.

**Do not add `--parallel` or `--keepdb`** to a command or a document — they are already on, and a second opinion in a shell script is how one of these silently stops applying. The escape hatches: `--parallel 1` for a cross-test interaction or with `--pdb`; `--fresh-db` when a killed run may have left the template half-built. A schema change does **not** need `--fresh-db`: the template carries a digest of every migration file and rebuilds itself when one changes, including one edited in place, which `--keepdb` alone cannot notice.

**Two runs at once is safe and expected** — each takes a private copy of the template. Do not "simplify" that into sharing one file: it was tried, and the second run died mid-migration with `disk I/O error`.

## 13.3 Writing a test <a href="#id-133-writing-a-test" id="id-133-writing-a-test"></a>

The Django suite is a small set of **feature tests** (hard cap **500**) covering the main behaviours: identity, money, VPS, hosting, the client API, tickets, and public status. Page renders belong to `scripts/smoke_all_pages.py`. A new test earns its place by failing for a reason no other test — and not the smoke script — would fail for.

Match the surrounding style: plain `TestCase`, `reverse()` for URLs, `force_login`, explicit assertions on status codes and database state. `apps/core/tests/test_suite_integrity.py` caps the collected size and asserts:

* **One test per behaviour, not per assertion.** Several assertions about one behaviour belong in one test; the same assertion against several inputs belongs in one test with `subTest`.
* **No two tests may have the same body** (decorators count as part of the body, docstrings do not).
* **`SimpleTestCase` when nothing touches the database.**
* **Shared fixtures go in a test-free mixin.** A concrete case that inherits another case's tests re-runs them (enforced).
* **Do not re-assert what `smoke_all_pages.py` covers.** A test whose only claim is "this page returns 200" adds nothing. **A test that an&#x20;*****attacker*****&#x20;gets 403/404 is the opposite and is required.**
* **Security-sensitive changes** (auth, billing, webhooks, ownership) need a negative test: the attacker path returns 403/404 and **no state changes**.
* **Do not test the framework, the language, nav copy, or CSS class names.**

**A test may assert on a rendered string only when the string&#x20;*****is*****&#x20;the behaviour:** a security boundary (a secret that must not appear), a contract (a canonical URL, a JSON-LD type, a `Cache-Control` header), or a business value (a dollar amount).

## 13.4 What automated checks do not establish <a href="#id-134-what-automated-checks-do-not-establish" id="id-134-what-automated-checks-do-not-establish"></a>

Repeated verbatim from the phase history because it keeps being assumed otherwise:

* **A passing provider simulation is not production certification.** Local tests do not establish live Proxmox, CloudLinux, sudo ABI, CageFS, mail deliverability or scan throughput behaviour.
* **Rendered and structural checks are not pixel-level browser QA**, and a browser sweep on synthetic data is not a claim about live infrastructure health.
* **An accepted node request is not a running job**, a configured credential is not a proven connection, and a queued repair is not proof of stopped power.
* Disposable-image tests are an engineering release check, **not runtime feature authority.**

***


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.layeronecloud.com/platform/testing.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
