Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions .github/workflows/docs.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: docs

on:
push:
branches:
- main
paths:
- "docs/**"
- ".github/workflows/docs.yaml"
workflow_dispatch:

# Allow one concurrent deployment, cancelling in-progress runs.
concurrency:
group: pages
cancel-in-progress: true

jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1

- name: Set up Node.js
uses: labd/gh-actions-typescript/pnpm-install@e7a21fb56b52bf2d91616e34f0bc3f4f9a821b62 # main

- name: Build docs
run: pnpm --filter @labdigital/commercetools-mock-docs build

- name: Upload Pages artifact
uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3.0.1
with:
path: docs/dist

deploy:
needs: build
runs-on: ubuntu-latest
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,21 @@ and use the `registerHandlers` method to register the handlers on this server.

This allows you to use the same server for mocking other API's as well.

## Documentation

Full documentation is available at
**https://labd.github.io/commercetools-node-mock/** — getting started, a
configuration reference, usage guides (seeding, querying, predicates, search,
authentication), storage backends, running as a server, recipes and the full
API reference.

The documentation site lives in [`docs/`](./docs) and is built with
[Astro Starlight](https://starlight.astro.build/):

```bash
pnpm --filter @labdigital/commercetools-mock-docs dev
```

## Installation

```bash
Expand Down
8 changes: 7 additions & 1 deletion biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@
},
"files": {
"ignoreUnknown": false,
"includes": ["**/src/**/*", "!**/vendor/**/*", "!**/dist/**/*", "!coverage"]
"includes": [
"**/src/**/*",
"!**/vendor/**/*",
"!**/dist/**/*",
"!coverage",
"!docs/**/*"
]
},
"formatter": {
"enabled": true,
Expand Down
9 changes: 9 additions & 0 deletions docs/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# build output
dist/
# generated types
.astro/
# dependencies
node_modules/
# environment variables
.env
.env.production
107 changes: 107 additions & 0 deletions docs/astro.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// @ts-check
import starlight from "@astrojs/starlight";
import { defineConfig } from "astro/config";
import starlightLlmsTxt from "starlight-llms-txt";

const REPO = "https://github.com/labd/commercetools-node-mock";

// https://astro.build/config
export default defineConfig({
// Project GitHub Pages site: https://labd.github.io/commercetools-node-mock/
site: "https://labd.github.io",
base: "/commercetools-node-mock",
integrations: [
starlight({
title: "@labdigital/commercetools-mock",
description:
"A comprehensive mock of the commercetools REST API for testing " +
"TypeScript codebases. Seed data, run assertions, and verify your " +
"commercetools integrations without hitting a real project.",
logo: {
src: "./src/assets/logo.svg",
replacesTitle: false,
},
favicon: "/favicon.svg",
social: [
{
icon: "github",
label: "GitHub",
href: REPO,
},
],
editLink: {
baseUrl: `${REPO}/edit/main/docs/`,
},
lastUpdated: true,
plugins: [
starlightLlmsTxt({
projectName: "@labdigital/commercetools-mock",
description:
"A comprehensive mock of the commercetools REST API for testing " +
"TypeScript codebases with msw, vitest and the commercetools SDK.",
}),
],
sidebar: [
{
label: "Getting started",
items: [
{ label: "Introduction", slug: "getting-started/introduction" },
{ label: "Installation", slug: "getting-started/installation" },
{ label: "Quick start", slug: "getting-started/quick-start" },
{ label: "How it works", slug: "getting-started/how-it-works" },
],
},
{
label: "Configuration",
items: [
{ label: "Options", slug: "configuration/options" },
{ label: "Authentication", slug: "configuration/authentication" },
],
},
{
label: "Usage",
items: [
{ label: "Seeding data", slug: "usage/seeding-data" },
{ label: "Querying data", slug: "usage/querying-data" },
{ label: "msw integration", slug: "usage/msw" },
],
},
{
label: "Storage backends",
items: [
{ label: "Overview", slug: "storage/overview" },
{ label: "In-memory", slug: "storage/in-memory" },
{ label: "SQLite", slug: "storage/sqlite" },
{ label: "Custom backends", slug: "storage/custom" },
],
},
{
label: "Running as a server",
items: [
{ label: "Standalone server", slug: "server/standalone" },
{ label: "Docker image", slug: "server/docker" },
],
},
{
label: "Recipes",
items: [
{ label: "Vitest setup", slug: "recipes/vitest-setup" },
{ label: "Testing a cart flow", slug: "recipes/cart-flow" },
{ label: "Custom types & fields", slug: "recipes/custom-types" },
{ label: "Fixtures with fishery", slug: "recipes/fixtures" },
],
},
{
label: "Reference",
items: [
{ label: "API coverage & limitations", slug: "reference/resources" },
{ label: "CommercetoolsMock", slug: "reference/commercetools-mock" },
{ label: "ProjectAPI", slug: "reference/project-api" },
{ label: "Storage classes", slug: "reference/storage" },
{ label: "Errors", slug: "reference/errors" },
],
},
],
}),
],
});
19 changes: 19 additions & 0 deletions docs/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "@labdigital/commercetools-mock-docs",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "astro dev",
"start": "astro dev",
"build": "astro build",
"preview": "astro preview",
"astro": "astro"
},
"dependencies": {
"@astrojs/starlight": "^0.34.0",
"astro": "^5.6.0",
"sharp": "^0.34.0",
"starlight-llms-txt": "^0.6.0"
}
}
6 changes: 6 additions & 0 deletions docs/public/favicon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions docs/src/assets/evolve.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
29 changes: 29 additions & 0 deletions docs/src/assets/labdigital.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions docs/src/assets/logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions docs/src/content.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { defineCollection } from "astro:content";
import { docsLoader } from "@astrojs/starlight/loaders";
import { docsSchema } from "@astrojs/starlight/schema";

export const collections = {
docs: defineCollection({ loader: docsLoader(), schema: docsSchema() }),
};
120 changes: 120 additions & 0 deletions docs/src/content/docs/configuration/authentication.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
---
title: Authentication
description: How the mock's OAuth2 server behaves, the enableAuthentication and validateCredentials options, token endpoints and customer/anonymous grants.
---

The mock ships a working OAuth2 server so the commercetools SDK's token flows
succeed. By default authentication is **relaxed** — tokens are issued but not
required or verified — which is what most tests want. You can tighten it with
two options.

## The two options

| Option | Default | Effect |
| --- | --- | --- |
| `enableAuthentication` | `false` | When `true`, API routes require the request to be checked for a bearer token. When `false`, API requests need no `Authorization` header at all. |
| `validateCredentials` | `false` | When `true` *and* `enableAuthentication` is on, a bearer token that was actually issued by the mock is required. When `false`, tokens are optional and never verified. |

### Behaviour matrix (API/data requests)

| `enableAuthentication` | `validateCredentials` | Behaviour |
| --- | --- | --- |
| `false` (default) | *(any)* | No auth enforced. No `Authorization` header needed on API requests. |
| `true` | `false` | Token optional and unverified. If a recognised token is sent, the associated `clientId` is recorded on `createdBy`/`lastModifiedBy`. |
| `true` | `true` | A bearer token issued by the mock is **required**. Missing or unknown token → `401 invalid_token`. |

:::note
`enableAuthentication` only guards the **API** routes. The `/oauth/*` token
endpoints always require HTTP Basic client credentials, even when
authentication is disabled (see the gotchas below).
:::

## Token endpoints

The OAuth2 server is mounted under `/oauth` and exposes:

| Endpoint | Grant types | Purpose |
| --- | --- | --- |
| `POST /oauth/token` | `client_credentials`, `refresh_token` | Standard client and refresh tokens. |
| `POST /oauth/{projectKey}/customers/token` | `password` | Customer (password) sign-in token. |
| `POST /oauth/{projectKey}/in-store/key={storeKey}/customers/token` | `password` | In-store customer token. |
| `POST /oauth/{projectKey}/anonymous/token` | `client_credentials` | Anonymous session token. |

All of these require an HTTP **Basic** `Authorization` header carrying the client
id/secret — but see the gotcha: **the id/secret values are not validated**, any
non-empty pair is accepted.

### Issued token shape

```json
{
"access_token": "<random base64>",
"token_type": "Bearer",
"expires_in": 172800,
"scope": "<requested scope, or \"todo\" if none>",
"refresh_token": "my-project-<random>"
}
```

Tokens are held in memory and are accessible from your test through
`ctMock.authStore()`.

## Client credentials flow

With the default options this "just works" — the SDK requests a token, the mock
issues one, and API requests succeed without any bearer check. To require valid
tokens:

```typescript
const ctMock = new CommercetoolsMock({
defaultProjectKey: 'my-project',
enableAuthentication: true,
validateCredentials: true,
})
```

Now every API request must carry a bearer token that the mock issued (i.e. the
SDK must have completed a token request first). Requests without one get
`401 invalid_token`.

## Customer (password) grant

The customer token endpoint validates the supplied `username`/`password`
against the customers that exist in the project. Create the customer first
through the API:

```typescript
await apiRoot
.customers()
.post({ body: { email: 'jane@example.com', password: 's3cret' } })
.execute()
```

A matching customer yields a token whose scope carries `customer_id:<id>`; no
match returns `400 invalid_customer_account_credentials`.

## Anonymous grant

`POST /oauth/{projectKey}/anonymous/token` issues a token with an
`anonymous_id:<uuid>` appended to the scope — useful for testing anonymous cart
flows.

## Gotchas & limitations

These differ from real commercetools and are worth knowing when writing tests:

- **Client id/secret are never verified.** Any non-empty Basic credentials are
accepted by the token endpoints. Use `validateCredentials` if you want to
assert that a token was obtained before API calls.
- **`enableAuthentication` does not protect the token endpoints** — they always
require a Basic header regardless.
- **`expires_in` is always `172800` (48h) and is never enforced.** Token expiry
cannot be simulated; `validateCredentials` only checks that a token exists.
- **Scope defaults to the literal `"todo"`** for client tokens when none is
requested.
- **`/me` endpoints do not consume the token's `customer_id` scope.** Customer
identity for `/me` routes comes from the request payload (e.g. login), not from
the bearer token. Per-customer data isolation on `/me` is therefore not
enforced based on the token.

See also: [API coverage & limitations](/commercetools-node-mock/reference/resources/).
Loading
Loading