---
kind: guide
zz: "0.8.0"
updated: 2026-10-10
state: live
rules: https://zriz.io/llms.txt
---
# Test that duplicate requests create one row

Send one request three times in a zriz pipeline, then count the rows in the database. One row proves that a retry makes no duplicate.

## The problem {#problem}

A client sends a request again after a timeout, or a user clicks two times. If the API stores each copy, the database gets duplicate rows. The status code does not show this: only the database does.

## The test {#test}

The example is a shop API with a MySQL database. A second sign-up with one e-mail must get `409`.

Start from a project that `zz init` made: the [quick start](https://zriz.io/docs.md) gives the commands. Add the action `register`, the database value, and the database resource. The connection string stays on your machine, because `SHOP_DB` is in `sensitive`.

`.zriz/resources/target.json`:

```json
{
  "type": "http",
  "description": "Your app under test",
  "base-url": "${env.TARGET_URL}",
  "headers": { "Content-Type": "application/json" },
  "actions": {
    "check": { "method": "GET", "path": "/api/health" },
    "register": { "method": "POST", "path": "/api/auth/register" }
  }
}
```

`.zriz/environments/local.json`:

```json
{
  "values": {
    "TARGET_URL": "http://localhost:9080",
    "SHOP_DB": "user:pass@tcp(localhost:3306)/shop"
  },
  "sensitive": ["SHOP_DB"]
}
```

`.zriz/resources/shopdb.json`:

```json
{
  "type": "sql",
  "connection": "${env.SHOP_DB}",
  "read-only": true,
  "actions": {
    "users-by-email": { "query": "SELECT id FROM users WHERE email = ${ctx.email}" }
  }
}
```

`.zriz/pipelines/register-three-times.json`:

```json
{
  "description": "Three same sign-ups make one row",
  "steps": [
    { "set": { "email": "u-${gen.uuid}@test.com" } },
    {
      "call": "target/register",
      "body": { "email": "${ctx.email}", "password": "secret123", "name": "Ann" },
      "expect": [["status", "==", 201]]
    },
    {
      "for": { "each": "attempt", "in": [2, 3] },
      "steps": [
        {
          "call": "target/register",
          "body": { "email": "${ctx.email}", "password": "secret123", "name": "Ann" },
          "expect": [["status", "==", 409], ["body.error", "==", "email already registered"]]
        }
      ]
    },
    {
      "call": "shopdb/users-by-email",
      "expect": [["row-count", "==", 1]]
    }
  ]
}
```

## Run it {#run}

```sh
zz run register-three-times
```

```text
"status":"pass"
```

## What it proves {#proves}

- `status == 201`, then `409` two times: the API accepts the first request and refuses each copy.
- `row-count == 1`: the database has one row after three requests. This check finds a duplicate that the status code hides.
- `"read-only": true`: the query of the test cannot write.

> Note: An API with an idempotency key answers the copy with the first result, not with `409`. Then expect the same status and the same id three times. The row check stays the same.

## Next {#next}

- [Test that a failed request writes no row](https://zriz.io/learn/test-failed-request-writes-no-row.md)
- [Test an API call and the database row](https://zriz.io/docs/api-and-database-test.md) gives each step for your own database.
- [Create a read-only Postgres user](https://zriz.io/docs/postgres-read-only-user.md)
