# Tabby simulator: integration scripts

Python scripts that exercise a **running** simulator over HTTP (and read its database through the `mysql` client). They are
not part of `mix test` (that only collects `*_test.exs`), they need no Python packages (standard library only), and they create
test payments, so run them against a development instance, never a shared or production one.

## What you need

- Python 3 and the `mysql` command-line client. The client reads the database login from `~/.my.cnf` (mode 600); the scripts never
  take a password on the command line.
- The server running with the simulator on (`TABBY_SIM_ENABLED=true`) and the migrations applied.
- For the webhook, expiry and admin scripts, a receiver for the webhooks (below).

Run every script from this folder.

## Environment variables

| Variable | Default | Used by |
|---|---|---|
| `TABBY_SIM_BASE` | `http://localhost:4030/tabby-sim` | all |
| `TABBY_SIM_KEY` | `Bearer sk_test_000000000` (the simulator's test key) | all |
| `TABBY_SIM_DB` | none, **required** | `webhooks_checks.py`, `expiry_checks.py`, `admin_checks.py` |
| `TABBY_SIM_RECEIVER` | `http://localhost:9199` | `webhooks_checks.py`, `expiry_checks.py`, `admin_checks.py` |
| `TABBY_SIM_RECEIVER_LOG` | `receiver.log` | the same three |
| `TABBY_SIM_ADMIN_TOKEN` | none, **required** | `admin_checks.py` |

## The webhook receiver

`webhook_receiver.py PORT` prints every POST it receives (time, path, the `X-Sign` header, the JSON body) and answers by path:
`/ok` 200, `/created` 201, `/fail` 500, `/slow` 200 after 70 s. The scripts read its output from `TABBY_SIM_RECEIVER_LOG`:

```bash
python3 webhook_receiver.py 9199 > receiver.log 2>&1 &
# ... run the scripts ...
kill %1        # stop it when you are done
```

## The scripts

| Script | Checks | Start the server with | Time |
|---|---|---|---|
| `hpp_checks.py` | Hosted payment page: redirects (with `?`/`&` and fragments), OTP, once-only status changes, expired link, escaping | `TABBY_SIM_ENABLED=true` | seconds |
| `hpp_race.py` | 12 simultaneous submits on one session: exactly one wins | the same | seconds |
| `payments_checks.py` | Retrieve, update, capture, refund, close, list (incl. the `new/captured/cancelled/refunded` filters), idempotency, 10-way parallel captures | the same | about 1 minute |
| `webhooks_checks.py quick` | Registration and its errors, the limit of 4, the full webhook flow, nothing sent for replays and failed calls, HTTP 201 counts as a failure | `TABBY_SIM_ENABLED=true` and **`TABBY_SIM_SEND_EXPIRED_WEBHOOK` unset** (it checks that Cancel sends none) | about 1 minute |
| `webhooks_checks.py retry` | A webhook that always answers 500 gets 5 attempts, 1 to 4 minutes apart, ends `discarded` | the same | about 14 minutes |
| `expiry_checks.py 1` | Two timers, one expired webhook with `expired_at`, the job stopping, nothing on a payment rejected up front | `TABBY_SIM_SESSION_TTL_MIN=1 TABBY_SIM_EXPIRE_AFTER_MIN=2 TABBY_SIM_SEND_EXPIRED_WEBHOOK=true` | about 5 minutes |
| `expiry_checks.py 2` | Expire on read with the job removed | the same | about 3 minutes |
| `admin_checks.py` | Admin protection, faults (409, 500, before/after), resend, out of order, force status, the update webhook | `TABBY_SIM_SESSION_TTL_MIN=5 TABBY_SIM_EXPIRE_AFTER_MIN=10 TABBY_SIM_SEND_EXPIRED_WEBHOOK=true TABBY_SIM_ADMIN_TOKEN=<your token>` | about 2 minutes |

Each script prints `PASS`/`FAIL` per check and `n/m passed` at the end.

## Side effects, so you know before you run them

- All create payments. `hpp_checks.py`, `hpp_race.py` and `payments_checks.py` use merchant code `ae`; the others use codes starting with `wh5`, `exp` or `adm` (and register webhooks for them, at most 4 per code).
- `admin_checks.py` deletes every row of `tabby_sim_faults` at the start and the end.
- `expiry_checks.py 2` deletes the simulator's own **pending** expiry jobs (`DaProductApp.TabbySim.ExpiryWorker`, states
  `available`, `scheduled`, `retryable`) from `oban_jobs`, and `admin_checks.py` does the same once and also moves one test
  payment's `expires_at` into the past. `oban_jobs` is shared with other applications: the statements are limited to that worker,
  but read them before you run the scripts on a database that others use.
- The expiry script's first run also expires every overdue `CREATED` payment already in the table.

## Not covered here

`admin_checks.py` does not cover T12: with `TABBY_SIM_ADMIN_TOKEN` unset, `/tabby-sim/admin` must be the same 404 as a path that never
existed. Restart the server without the variable and run `curl -i http://localhost:4030/tabby-sim/admin` by hand.
