# Testing the whole loop

The console's *Try it* button proves your endpoint's contract (delivery + idempotency).
This proves everything else — verification, binding, the cooldown, the reply bonus, and the
delivery worker's real retries — without a tweet.

## 1. Enroll a test user

`POST /v1/users` with `"test": true`. A test user holds no capacity seat, is always
enrolled, and is capped at 3 per app. Use a staging `productUserId` — rewards flow through
the real pipeline to your configured endpoint.

## 2. Simulate a post

```http
POST /v1/test/posts
Authorization: Bearer {your_api_key}
Content-Type: application/json

{ "appId": "…", "productUserId": "…", "replies": 15 }
```

Default `text` is a qualifying post (mention, tag, the user's code). The synthetic post runs
the real `verifyPost`: the first one binds the test user's synthetic account, later ones
exercise the repeat path, a second one inside the cooldown is refused with the real reason.
Send your own `text` to test the refusals — drop the tag, bury it in the back half.

`replies` settles the reply bonus immediately (same maths, same ledger, own
`bonus:` reference) instead of on day five.

`"revoke": true` drives a revocation through the same code the day-5 settlement runs: the
post is marked revoked and a `kind: "revocation"` notice is queued for your endpoint, signed
and retried like any delivery. That makes the revocation branch — the one your handler is
required to implement — the one you can actually exercise end to end. It settles the bonus at
zero, as a real revocation does, so send it without `replies`.

## 3. Watch it arrive

Both grants reach your endpoint through the delivery worker — bearer, signature, retries and
all — within about a minute, and show up in `GET /v1/events` and the dashboard's delivery
log marked TEST. The grants carry `kind: "post"` / `"reply_bonus"` like real traffic, so
your handler's real branches run; isolation is the test `productUserId` you chose.

Errors are `{ "error", "code" }`: `missing_field`, `invalid_app_id`,
`invalid_product_user_id`, `invalid_text`, `invalid_replies` (400), `unauthorized` (401),
`not_test_user` (403), `app_not_found`, `user_not_found` (404).