npm package: @progalaxyelabs/stonescriptphp-api-tester
Description: API test infrastructure for StoneScriptPHP projects — coverage validation and test execution
Author: ProGalaxy eLabs Private Limited
Latest version: 0.1.1

# @progalaxyelabs/stonescriptphp-api-tester

API test infrastructure for StoneScriptPHP projects — coverage validation and test execution.

Zero npm dependencies. Uses only Node.js built-ins.

## Installation

```bash
npm install @progalaxyelabs/stonescriptphp-api-tester
```

Or use directly via npx:

```bash
npx @progalaxyelabs/stonescriptphp-api-tester validate
npx @progalaxyelabs/stonescriptphp-api-tester run --flow=smoke
```

## Commands

### `api-tester validate`

Static analysis — cross-references your testplan.json against routes.php and the generated TypeScript api-client to find coverage gaps.

```bash
api-tester validate [options]
```

**Options:**

| Option | Default | Description |
|--------|---------|-------------|
| `--testplan=<path>` | `./api/tests/testplan.json` | Path to testplan.json |
| `--client=<path>` | `./api/client/src/index.ts` | Path to api-client index.ts |
| `--routes=<path>` | `./api/src/config/routes.php` | Path to routes.php |
| `--verbose` | | Show all matches, not just failures |
| `--json` | | Output as JSON (for CI pipelines) |

**Coverage checks performed:**

1. **API Client -> Testplan** (MUST be 100%): Every endpoint in the TypeScript client must appear in testplan.json
2. **Routes.php -> Testplan** (MUST be 100%): Every route in routes.php must appear in testplan.json
3. **Testplan -> API Client** (informational): Shows testplan endpoints not in api-client (expected for untyped routes)
4. **Graph Coverage** (MUST be 100%): Every endpoint ID must appear in at least one graph edge
5. **Flow Coverage** (SHOULD be 100%): Every endpoint ID should appear in at least one flow

**Exit codes:** `0` = all mandatory checks pass, `1` = coverage gaps found.

### `api-tester run`

Runtime test execution — sends real HTTP requests following flows defined in testplan.json.

```bash
api-tester run [options]
```

**Options:**

| Option | Default | Description |
|--------|---------|-------------|
| `--testplan=<path>` | `./api/tests/testplan.json` | Path to testplan.json |
| `--base-url=<url>` | `http://localhost:3000` | Base URL for API |
| `--flow=<name>` | | Flow name to run, or `all` for all flows |
| `--endpoint=<id>` | | Run a single endpoint by ID (for debugging) |
| `--dry-run` | | Print what would happen without making HTTP calls |
| `--verbose` | | Show request/response bodies |
| `--token=<token>` | | Pre-obtained auth token (skip login) |
| `--email=<email>` | | Email for login step |
| `--password=<pass>` | | Password for login step |
| `--platform=<name>` | | Platform name for auth login |

**Examples:**

```bash
# Run the smoke flow
api-tester run --flow=smoke --base-url=http://localhost:3011

# Run all flows with auth credentials
api-tester run --flow=all --email=test@example.com --password=TestPass1 --platform=myapp

# Debug a single endpoint
api-tester run --endpoint=15 --verbose --base-url=http://localhost:3011

# Dry run — see what would execute without making requests
api-tester run --flow=mvp --dry-run

# Use a pre-obtained token
api-tester run --flow=smoke --token=eyJhbG...
```

## testplan.json Format

The testplan is a JSON file that describes your API surface, how endpoints relate to each other, and how to test them.

```json
{
  "meta": {
    "version": "1.0",
    "total_endpoints": 128,
    "api_base": "http://localhost:3011"
  },
  "endpoints": [
    {
      "id": 1,
      "method": "GET",
      "path": "/health",
      "name": "system.health",
      "group": "system",
      "auth_required": false,
      "in_api_client": false,
      "implementation_status": "implemented-untyped",
      "sample_request": null,
      "expected_status": 200,
      "response_shape": ["status", "service", "timestamp"]
    },
    {
      "id": 89,
      "method": "POST",
      "path": "/invoices",
      "name": "invoices.create",
      "group": "invoices",
      "auth_required": true,
      "in_api_client": true,
      "implementation_status": "implemented-typed",
      "sample_request": {
        "invoice_number": "INV-{{timestamp}}",
        "invoice_date": "2026-01-15",
        "distributor_id": "{{distributor_id}}",
        "items": []
      },
      "expected_status": 200,
      "response_shape": ["invoice_id"]
    }
  ],
  "graph": [
    {
      "from": 89,
      "to": 29,
      "carry": {
        "invoice_id": "response.data.invoice_id"
      },
      "note": "Create invoice, then fetch it by ID"
    }
  ],
  "flows": {
    "smoke": [1, 2, 3],
    "mvp": [14, 107, 105, 89, 91]
  }
}
```

### Endpoint fields

| Field | Type | Description |
|-------|------|-------------|
| `id` | number | Unique endpoint identifier (referenced in graph/flows) |
| `method` | string | HTTP method: GET, POST, PUT, DELETE |
| `path` | string | URL path with `:param` placeholders |
| `name` | string | Dot-notation name (e.g., `invoices.create`) |
| `group` | string | Logical group for organization |
| `auth_required` | boolean | Whether the endpoint requires a JWT |
| `in_api_client` | boolean | Whether this endpoint is in the TypeScript api-client |
| `implementation_status` | string | `implemented-typed`, `implemented-untyped`, or `missing` |
| `sample_request` | object/null | Request body template with `{{placeholder}}` support |
| `expected_status` | number | Expected HTTP status code |
| `response_shape` | string[] | Array of field names expected in response data |

### Graph edges

Graph edges define data dependencies between endpoints. When endpoint `from` succeeds, values are extracted from its response and carried to subsequent steps.

The `carry` object maps variable names to dot-notation paths into the response:

```json
{
  "from": 89,
  "to": 29,
  "carry": {
    "invoice_id": "response.data.invoice_id"
  }
}
```

After step 89 (POST /invoices), the `invoice_id` is extracted from the response and used to resolve `:id` in step 29 (GET /invoices/:id).

### Flows

Flows are named sequences of endpoint IDs executed in order. Each flow represents a business workflow (e.g., create distributor -> create item -> create invoice -> create bill).

### Placeholders

`sample_request` fields support `{{placeholder}}` syntax:

| Placeholder | Resolves to |
|-------------|-------------|
| `{{timestamp}}` | Current Unix timestamp |
| `{{email}}` | Generated test email |
| `{{any_carried_key}}` | Value carried from a previous step |

Path parameters (`:id`, `:slug`) are resolved from carried data automatically.

## StoneScriptPHP Awareness

The runner understands the StoneScriptPHP response format:

```json
{"status": "ok", "message": "", "data": { ... }}
```

When validating `response_shape`, the runner checks `body.data` (not the wrapper) if the response has `status: "ok"`. This matches how StoneScriptPHP wraps all responses via `res_ok()` and `res_error()`.

## Requirements

- Node.js >= 18.0.0 (uses native `fetch`)
- Zero npm dependencies

## License

MIT