Ctrl K
ComunidadGuías…

docs/testing.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
```
╔════════════════════════════════════════════════════════════════════════════╗
║                                                                            ║
║   TESTING                                                                  ║
║   Vitest 4 + Supertest + jsdom: 1661 tests across 79 files, no services.   ║
║                                                                            ║
╚════════════════════════════════════════════════════════════════════════════╝
```

# Testing

Cloud Codex has **1661 tests across 79 files** and runs the entire suite in
under 30 seconds with no MySQL, no SMTP, and no network. Backend tests use
**Vitest 4** + **Supertest** with the database, email transport, and
filesystem globally mocked. Frontend tests use **Vitest** + **jsdom** +
`@testing-library/react`.

```bash
cd cloudcodex
npm test               # both projects
npm run test:watch     # both projects, watch mode
npm run test:backend   # backend project only
npm run test:frontend  # frontend project only
npm run test:coverage  # both + v8 coverage report (HTML + lcov + text)
npm run test:integration  # opt-in: the live-MySQL project (needs a server, see below)
```

---

## Three default Vitest projects, one command

`vitest.config.js` defines four named projects. A single `npm test` runs the
three default ones, backend, frontend and design, together with the right
environment for each; the fourth, `integration`, is opt-in (next section).
Backend and frontend:

```
   ┌─────────────────────────────────────────────────────────────┐
   │                       npm test                              │
   │                          │                                  │
   │      ┌───────────────────┴───────────────────┐              │
   │      ▼                                       ▼              │
   │   project: backend                    project: frontend     │
   │   environment: node                   environment: jsdom    │
   │   setup: tests/setup.js               setup: tests/setup.frontend.js │
   │   include:                            include:              │
   │     tests/routes/**                     tests/src/**         │
   │     tests/middleware/**                                       │
   │     tests/services/**                                         │
   │     tests/helpers/**                                          │
   │     tests/extensions/**                                       │
   │     tests/*.test.{js,jsx}                                     │
   └─────────────────────────────────────────────────────────────┘
```

Both projects share the same coverage config so `npm run test:coverage`
produces one unified report.

The third default project, `design` (node, `tests/design/**`), runs the gates
of the vendored `cloud-city-design` package over `index.html` and `src/`: the
vendored copy against its SHA-256 manifest, literal colours, accent fill
shades in text positions and suppressed outlines against an exact ledger
(`tests/design/ledger.json`, which a fix lowers in the same change), `var()`
references to undeclared names, and the contrast of `src/codex.css`'s
bindings. `node tests/design/report.mjs` prints the ledger's live buckets.

The scripts name `--project backend --project frontend --project design` explicitly, and
`tests/test-projects.test.js` fails if a project the config declares (other
than `integration`) is missing from `test`, `test:watch` or `test:coverage`, so a new project
cannot silently drop out of the default run.

## The opt-in live-MySQL project

`npm run test:integration` runs `tests/integration/**` against a real MySQL 8.4
server rather than a mock. Each test file gets its own throwaway
`c2_it_<random>` schema built from `init.sql` and adopted by the migration
runner, and the run fails if any such schema is left behind.

Adoption records every migration file and runs none, so the per-file setup
alone proves only that `init.sql` builds on MySQL 8.4 and that the runner's
adoption check agrees with it. Migration SQL is executed by one file,
`tests/integration/upgrade-path.test.js`: it takes an `init.sql` build back to
the pre-runner state with the undo statements in
`tests/integration/pre-runner-state.js`, records the pre-runner baseline, lets
the runner apply every newer file for real, and requires the result to match a
fresh `init.sql` build (columns, indexes, constraints, checks and foreign
keys). A new migration file fails that test until it has an undo entry. The
upgrade runs on empty tables, so a migration's handling of existing rows is
not exercised, and the pre-runner files in `LEGACY_BASELINE` are never run.

```bash
# any MySQL 8.4 answering on 3306, for example a scratch container:
docker run -d --rm --name c2-it-mysql -p 127.0.0.1:3306:3306 \
  -e MYSQL_ROOT_PASSWORD=<pw> mysql:8.4.11
IT_DB_ROOT_PASSWORD=<pw> npm run test:integration   # IT_DB_HOST defaults to 127.0.0.1
```

CI runs it inside the required `Lint, test and build` job against a
`mysql:8.4.11` service container. See `docs/maps/build-test-and-ops.md` section 5
for the mechanism.

---

## What's covered, by area

| Area                              | Files in `tests/`             | Notes                                  |
|-----------------------------------|--------------------------------|----------------------------------------|
| HTTP routes                       | `tests/routes/*.test.js`       | Supertest against the Express app      |
| Middleware                        | `tests/middleware/*.test.js`   | `auth.js`, `permissions.js`            |
| Services (collab, email, notif)   | `tests/services/*.test.js`     | Includes WebSocket + email pipelines   |
| Route helpers                     | `tests/helpers/*.test.js`      | `ownership`, `shared`, `images`, `mentions`, `activity` |
| Tiptap extensions                 | `tests/extensions/*.test.js`   | e.g. `Mention`                         |
| Framework files                   | `tests/{app,server,mysql_connect}.test.js` | App wiring, startup, DB pool |
| Frontend pure logic               | `tests/src/{editorUtils,userPrefs,util}*.test.js(x)` | jsdom + RTL  |
| Frontend hooks                    | `tests/src/hooks/*.test.jsx`   | `useCollab`, `useNotificationChannel`, `useGitHubLink`, `useGitHubStatus`, `usePresence`, `useClickOutside` |
| Frontend reusable components      | `tests/src/components/*.test.jsx` | Bell, Item, Toast, Dialog, Form, Manager, Tree, etc. |
| Frontend libs                     | `tests/src/lib/*.test.js`      | e.g. `githubDiff`                      |

Pages (`src/pages/*.jsx`) are **out of scope** for unit tests today — the
giants like `Editor.jsx` and `GitHubPage.jsx` need refactoring that extracts
logic into testable hooks before they're worth unit-testing.

---

## What gets mocked

`tests/setup.js` (backend) replaces these globally so tests run without
external dependencies:

- `mysql_connect.js` → in-memory `c2_query` mock (fixtures stub queries)
- `services/email.js` → `sendEmail` recorded, never sent
- `sharp` → image transforms become no-ops (returns the input buffer)
- `fs` writes for avatars / doc-images → recorded but never hit disk

`tests/setup.frontend.js` (frontend) registers `@testing-library/jest-dom`
matchers and resets `localStorage`, `sessionStorage`, and the DOM between
tests.

`tests/helpers.js` exports common fixtures (`TEST_USER`, `ADMIN_USER`) and
auth helpers (`mockAuthenticated`, `mockUnauthenticated`, `resetMocks`).

See [`cloudcodex/tests/README.md`](../cloudcodex/tests/README.md) for the
full set of patterns and examples.

---

## Coverage thresholds (CI gate)

`vitest.config.js` enforces per-glob coverage thresholds. The **global
floor** is set just below the currently achieved coverage so normal churn
doesn't block CI, but a meaningful regression will. **Security-critical
modules** are locked at higher floors:

| Glob                      | Lines | Statements | Branches | Functions |
|---------------------------|-------|------------|----------|-----------|
| **global floor**          |  43   |  40        |  33      |  26       |
| `routes/helpers/**`       |  88   |  85        |  65      |  90       |
| `middleware/**`           |  80   |  78        |  70      |  70       |
| `routes/auth.js`          |  85   |  85        |  82      |  95       |
| `routes/admin.js`         |  90   |  90        |  88      |  90       |
| `routes/archives.js`      |  90   |  88        |  75      |  90       |
| `routes/comments.js`      |  92   |  90        |  85      |  95       |
| `routes/documents.js`     |  95   |  92        |  88      |  88       |
| `routes/notifications.js` |  95   |  95        |  88      |  95       |
| `routes/squads.js`        |  85   |  75        |  65      |  95       |
| `routes/watches.js`       |  85   |  85        |  73      |  95       |
| `routes/doc-images-serve.js` | 95 | 95 | 92 | 95 |
| `services/email.js`       |  95   |  95        |  70      |  95       |
| `services/email-templates.js` | 95 | 90 | 90 | 95 |
| `services/notifications.js` | 90 | 88 | 80 | 88 |
| `services/collab.js`      |  65   |  65        |  50      |  75       |
| `mysql_connect.js`        |  85   |  85        |  80      |  90       |
| `src/editorUtils.js`      |  95   |  95        |  88      |  95       |
| `src/userPrefs.js`        |  95   |  95        |  80      |  95       |
| `src/lib/**`              |  90   |  90        |  70      |  80       |

Hooks and other frontend modules each have their own targeted floors —
see `vitest.config.js` for the canonical list.

> The global floor is intentionally low because pages (`src/pages/*`) and
> some heavy components are unmeasured. **Don't read it as the project's
> health number** — read the per-glob thresholds for the modules that
> matter.

---

## Adding tests with new code

The convention: **a route, service, helper, middleware, hook, pure-JS
utility, or reusable component change requires a matching test update in
the same shape as its neighbors.** Tests mirror the source tree:

```
   routes/foo.js            →  tests/routes/foo.test.js
   services/foo.js          →  tests/services/foo.test.js
   middleware/foo.js        →  tests/middleware/foo.test.js
   routes/helpers/foo.js    →  tests/helpers/foo.test.js
   src/foo.js               →  tests/src/foo.test.js
   src/components/Foo.jsx   →  tests/src/components/Foo.test.jsx
   src/hooks/useFoo.js      →  tests/src/hooks/useFoo.test.jsx
```

Pages are explicitly out of scope until the underlying logic is hookified.