> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stateset.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Integration Testing

> Run isolated tests against the embedded engine, verify failure invariants, and distinguish local coverage from hosted API and provider checks.

Start with tests whose effects you can inspect. The suite below uses the real
**`@stateset/embedded` 1.35.1** package and Node's built-in test runner to verify stock
reservation, rejection without partial writes, and payment retry identity. It requires no
hosted service, API key, or test framework dependency.

## Choose what you are testing

| Layer | What to exercise | What a passing test establishes |
| - | - | - |
| Application logic | Your mapping and decision functions, with controlled inputs | Your code handles those inputs |
| Embedded integration | Published native engine with fresh local databases | The installed binding and engine satisfy the asserted behavior |
| Hosted API integration | Actual service host, key, request, and response | That service accepted the request in that workspace |
| External workflow | Provider, worker, webhook, and resulting records | The observed cross-system operation completed |

A mocked HTTP response tests your client handling. It does not verify a route, authentication
scope, remote schema, or external side effect. Likewise, completing a local payment record does
not establish that a provider charged a card.

## Install the test dependency

Use Node.js **20.20.0+** and npm **10+**:

```bash theme={null}
mkdir stateset-integration-tests
cd stateset-integration-tests
npm init -y
npm install --save-dev @stateset/embedded@1.35.1
```

Commit the resulting `package-lock.json` in your application and use `npm ci` in CI. See
[SDK installation](/guides/sdk-installation) for native-module and platform troubleshooting.

## Run the complete suite

Save this as `commerce.test.mjs`:

```javascript theme={null}
import test from 'node:test';
import assert from 'node:assert/strict';
import { Commerce } from '@stateset/embedded';

async function fixture() {
  // Each test gets a separate database and can reuse the same fixture values.
  const commerce = new Commerce(':memory:');
  const customer = await commerce.customers.create({
    email: 'ada@example.com', firstName: 'Ada', lastName: 'Lovelace',
  });
  await commerce.inventory.createItem({
    sku: 'MUG-001', name: 'Ceramic mug', initialQuantity: 3,
  });
  const orderInput = {
    customerId: customer.id,
    currency: 'USD',
    stockPolicy: 'reject_if_insufficient',
    items: [{ sku: 'MUG-001', name: 'Ceramic mug', quantity: 2, unitPrice: '12.50' }],
  };
  return { commerce, customer, orderInput };
}

async function stockQuantities(commerce) {
  const stock = await commerce.inventory.getStock('MUG-001');
  assert.ok(stock);
  return [stock.totalOnHand, stock.totalAllocated, stock.totalAvailable];
}

test('cancelling an order restores its stock allocation', async () => {
  const { commerce, orderInput } = await fixture();
  const order = await commerce.orders.createExact(orderInput);
  assert.equal(order.totalAmountExact, '25.00');
  assert.deepEqual(await stockQuantities(commerce), ['3', '2', '1']);

  const cancelled = await commerce.orders.cancel(order.id);
  assert.equal(cancelled.status, 'cancelled');
  assert.deepEqual(await stockQuantities(commerce), ['3', '0', '3']);
  assert.equal(await commerce.orders.count(), 1);
});

test('an oversized order leaves no order or stock allocation', async () => {
  const { commerce, orderInput } = await fixture();
  await assert.rejects(
    () => commerce.orders.createExact({
      ...orderInput,
      items: [{ ...orderInput.items[0], quantity: 4 }],
    }),
    { code: 'INSUFFICIENT_STOCK' },
  );
  assert.equal(await commerce.orders.count(), 0);
  assert.deepEqual(await stockQuantities(commerce), ['3', '0', '3']);
});

test('replaying a payment creation reuses the original record', async () => {
  const { commerce, customer, orderInput } = await fixture();
  const order = await commerce.orders.createExact(orderInput);
  const input = {
    customerId: customer.id,
    orderId: order.id,
    amount: order.totalAmountExact,
    currency: 'USD',
    paymentMethod: 'card',
    idempotencyKey: `payment-${order.id}-1`,
  };
  const first = await commerce.payments.createExact(input);
  const replay = await commerce.payments.createExact(input);
  assert.equal(replay.id, first.id);
  assert.equal(replay.amountExact, '25.00');
  assert.equal(replay.status, 'pending');
  assert.equal(await commerce.payments.count(), 1);
});
```

```bash theme={null}
node --test commerce.test.mjs
```

**Success:** the runner reports **3 tests, 3 passed, 0 failed**, and exits with status `0`.
Timing and output formatting vary by Node version. An assertion failure produces a nonzero
exit code, so CI can fail the build.

## Assert what remains unchanged

The rejection test checks three things: the expected `INSUFFICIENT_STOCK` code, no created
order, and unchanged stock. Checking only that an exception occurred could pass on an unrelated
error or miss a partial write.

The replay test checks both the returned ID and the stored payment count. A successful second
response alone would not establish that the engine reused the first record.

Keep those assertions when adapting the suite. Add coverage for your own mapping, retry, and
reconciliation decisions, rather than replacing state assertions with log messages.

## Keep fixtures isolated

Every test creates its own `Commerce(':memory:')`. It does not use your application's database,
share a mutable customer across tests, or depend on another test running first.

For file-backed persistence tests, allocate a unique temporary directory, use an explicit
database path in each process, and read the record after the writer exits. The
[persisted quickstart](/stateset-icommerce/stateset-icommerce-quickstart) demonstrates that
second-process check. Its `quickstart.db` is a teaching filename; do not share one fixed path
among parallel test workers.

## Add the suite to CI

After committing `commerce.test.mjs`, `package.json`, and `package-lock.json`, a GitHub Actions
job can run:

```yaml theme={null}
name: Commerce integration tests
on: [push, pull_request]
jobs:
  commerce:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '22'
          cache: npm
      - run: npm ci --no-audit --no-fund
      - run: node --test commerce.test.mjs
```

This job needs no service credentials. A native-module install failure is a setup failure;
fix it before interpreting the result as a commerce behavior regression.

## Test a hosted service separately

Use the [API directory](/api-reference/introduction) to select the service's actual base URL,
authentication header, and key. There is no shared sandbox URL or key covering all engines.

For a first read check, the [developer guide](/development#make-a-first-request-from-your-application)
contains JavaScript, Python, and curl requests for the ResponseCX workspace. A successful read
verifies access; it does not establish write scope or agent activation.

For write tests, use an explicitly selected test workspace and fixtures you own. Save returned
IDs, verify persisted fields, and track cleanup according to the endpoint's supported lifecycle.
Check idempotency support per operation before testing retries. Do not wrap an entire test in an
automatic retry loop: that can hide the original failure and create additional resources.

## Troubleshooting

| Failure | First check |
| - | - |
| Native module cannot load | Runtime, OS/architecture, and installed optional dependencies |
| Unexpected `CONFLICT` | Whether tests share a database or fixture identity |
| Assertion expects a number but gets a string | Exact money and stock quantity fields are strings in these examples |
| Rejection test passes for the wrong reason | Assert the specific error code and resulting stored state |
| Local test passes but remote call fails | Remote host, key scope, schema, and workspace are separate contracts |
| A provider action appears missing | The local tests do not call external payment or fulfillment providers |

## Next steps

* [Integration test plan](/guides/comprehensive-testing-guide): verify restart recovery and plan hosted and asynchronous checks.
* [Error handling and retries](/guides/error-handling-best-practices): test expected rejections and preserve unexpected failures.
* [Customer workflow](/guides/customers-quickstart): test email reuse, explicit updates, and addresses.
* [Inventory workflow](/guides/inventory-quickstart): verify manual holds and adjustments.
* [Payment and refund workflow](/guides/payments-quickstart): add refundable-balance and pending-refund checks.
* [Cross-engine errors](/api-reference/errors-across-engines): use the error contract for the interface you call.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.