# Testing

Test everything by running it. Don't claim that code works, or that a problem exists, without having seen it.

## Running a site

- `serve.sh` in the project runs `luan luan:http/serve.luan src`, serving `src/` at `http://localhost:8080/`, and logs to `err`. Read `err` for errors, including browser errors that came through `error_log.js`.
- Restart the server after changing Luan code; loaded modules are cached.
- `luan luan:http/serve.luan src 8081` serves on another port, for example to run an untouched copy next to a changed one. Don't change the project's `serve.sh` for this, or any other project file for your testing: make your own scripts in a scratch directory.
- A domain like `chat.localhost` reaches the same local server, so a test can use the domain a site expects.
- Stop a server by its process id. `luan` is a bash script, so the Java server is a child of the process you started: `kill $(pgrep -P $pid) $pid`. Don't use `pkill -f` with a pattern: it can match the shell running the command and kill it. To find a server you didn't start, list Java processes with `pgrep -x java` and check each one's `/proc/<pid>/cmdline`.
- A site's data stays in `src/private/local/` between runs. Usually that's what you want. For a truly clean run, such as a test that counts records, stop the server and delete `src/private/local/` (and the caught mail) first.
- Put test pages and modified copies in a scratch directory, not in the project's working copy.
- If a project has no real Luan code, it can be served with any static file server, but it must send `charset=utf-8` for `.html`, `.js` and `.css`, as Luan's server does, or non-ASCII text comes out garbled.

## Looking at pages

- Use a headless browser such as Playwright's Chromium. Read the rendered text of `div[content]`, take screenshots, click, fill in forms, and listen for `pageerror` events.
- If the test machine can't reach a CDN, answer those requests with `page.route`, using the same file fetched another way (for example with `npm pack`).
- A fresh browser context has an empty cache, like a first-time visitor. A bug that depends on loading, such as an image without a size, may not show in a browser that has the files cached. Say so when reporting it.
- Don't wait for `networkidle` on a page with a server-sent event stream; it never comes.
- To check that a change doesn't alter how pages look, serve the old version on a second port, take full-page screenshots of every page in both, and compare them pixel by pixel.
- To check that a refactor keeps behavior, run the same actions against the old and new versions and compare the final DOM and the sequence of callbacks.

## Sites with users and mail

- Sites that send mail need a mail server. Run the mail catcher in this skill's `scripts` folder, `python3 <skill folder>/scripts/mail_sink.py mail 2525`, which saves each message as a file in `mail/`, and point the site's `private/Config.luan` at it: `mail_server = {host="127.0.0.1", port=2525}`, with no username. Luan's mail uses SSL only on port 465, so use another port. A standard server like `aiosmtpd` won't do, because Luan's mail sender doesn't format `MAIL FROM` strictly; the catcher accepts anything. Run it in the background, and restart it if it was stopped.
- Register test users through the real flow, read the login links from the saved mail, and log each user in in its own browser context, so that two users can interact.
- For quick checks, `curl -c file` saves a login's cookies and `-b file` sends them. Send a `Referer` from the site (`-e http://localhost:8080/`), or the Referer check refuses the request.
- Check access control with a third user who isn't part of the data being protected.

## Security checks

- Test code that maps a request path to a file with `curl --path-as-is`, using both `..` and `%2e%2e`. Browsers remove `..` before sending a request, so they never show the problem.
- Test values echoed into a page with markup in them, like `"><script>alert(1)</script>`, and watch for the script running in the browser.

## Finding dead CSS

Collect every selector from `document.styleSheets` (follow `@import` rules through `rule.styleSheet`, and look inside `@media` rules), and run `querySelectorAll` on each, with `:hover` removed, in every state of the page: each view, dialogs open, and each kind of content. A selector that never matches is dead. Some rules only match uncommon data, so set that data up before calling a rule dead.

## Running Luan code directly

To find out what a library function does, write a short script and run it:

```lua
local Io = require "luan:Io.luan"
local Parsers = require "luan:Parsers.luan"
Io.print( Parsers.json_string{nil} )
```

`luan test.luan` prints `[]`.
