zuplo 7.4.4 → 7.4.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/articles/ci-cd-azure/local-testing.mdx +22 -4
- package/docs/articles/ci-cd-bitbucket/local-testing.mdx +22 -4
- package/docs/articles/ci-cd-circleci/local-testing.mdx +21 -4
- package/docs/articles/ci-cd-github/deploy-and-test.mdx +28 -13
- package/docs/articles/ci-cd-github/local-testing.mdx +36 -16
- package/docs/articles/ci-cd-gitlab/local-testing.mdx +22 -4
- package/docs/articles/github-deployment-testing.mdx +118 -31
- package/docs/articles/testing-getting-started.mdx +220 -0
- package/docs/articles/testing-preview-environments.mdx +148 -0
- package/docs/articles/testing-recipes.mdx +429 -0
- package/docs/articles/testing.mdx +138 -408
- package/docs/mcp-gateway/auth/configuring-auth0.mdx +6 -5
- package/docs/mcp-gateway/auth/configuring-clerk.mdx +4 -4
- package/docs/mcp-gateway/auth/configuring-cognito.mdx +5 -4
- package/docs/mcp-gateway/auth/configuring-entra.mdx +5 -4
- package/docs/mcp-gateway/auth/configuring-generic-oidc.mdx +8 -8
- package/docs/mcp-gateway/auth/configuring-google.mdx +4 -4
- package/docs/mcp-gateway/auth/configuring-keycloak.mdx +4 -3
- package/docs/mcp-gateway/auth/configuring-logto.mdx +4 -4
- package/docs/mcp-gateway/auth/configuring-okta.mdx +3 -3
- package/docs/mcp-gateway/auth/configuring-onelogin.mdx +3 -3
- package/docs/mcp-gateway/auth/configuring-ping.mdx +3 -3
- package/docs/mcp-gateway/auth/configuring-workos.mdx +5 -4
- package/docs/mcp-gateway/auth/manual-oauth-testing.mdx +8 -8
- package/docs/mcp-gateway/auth/overview.mdx +17 -17
- package/docs/mcp-gateway/auth/upstream-oauth.mdx +5 -5
- package/docs/mcp-gateway/code-config/local-development.mdx +14 -12
- package/docs/mcp-gateway/code-config/overview.mdx +9 -4
- package/docs/mcp-gateway/connect-clients/chatgpt.mdx +114 -56
- package/docs/mcp-gateway/how-it-works.mdx +11 -9
- package/docs/mcp-gateway/introduction.mdx +3 -1
- package/docs/mcp-gateway/quickstart-local.mdx +7 -7
- package/docs/mcp-gateway/reference.mdx +58 -25
- package/docs/mcp-gateway/server-registry.mdx +179 -0
- package/docs/mcp-gateway/test-clients.mdx +2 -2
- package/docs/mcp-server/custom-tools.mdx +32 -0
- package/docs/programmable-api/mcp-gateway-plugin.mdx +137 -0
- package/docs/programmable-api/mcp-sdk.mdx +240 -0
- package/docs/self-hosted/overview.md +2 -0
- package/package.json +5 -5
|
@@ -1,65 +1,97 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Testing Your API
|
|
3
|
-
sidebar_label:
|
|
3
|
+
sidebar_label: Overview
|
|
4
|
+
description:
|
|
5
|
+
Learn how `zuplo test` runs the same TypeScript test files against your local
|
|
6
|
+
dev server, preview deployments, and production to catch configuration defects
|
|
7
|
+
that unit tests miss.
|
|
4
8
|
---
|
|
5
9
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
Your gateway holds authentication, authorization, rate limits, routing, request
|
|
11
|
+
validation, and the shape of every error your consumers see. All of that is
|
|
12
|
+
configuration, and configuration is exactly what unit tests cannot reach. A unit
|
|
13
|
+
test can prove a JWT validation function rejects an expired token, but it cannot
|
|
14
|
+
prove the policy that calls that function is attached to the route your
|
|
15
|
+
customers hit.
|
|
11
16
|
|
|
12
|
-
|
|
17
|
+
`zuplo test` closes that gap. It runs plain TypeScript test files from your
|
|
18
|
+
repository against a live gateway, and it takes the target as a flag. The same
|
|
19
|
+
files run against your local dev server, against the preview deployment of your
|
|
20
|
+
branch, and against production.
|
|
13
21
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
22
|
+
```bash
|
|
23
|
+
npx zuplo test --endpoint http://localhost:9000
|
|
24
|
+
npx zuplo test --endpoint https://your-branch-abc123.zuplo.app
|
|
25
|
+
npx zuplo test --endpoint https://api.example.com --filter "smoke"
|
|
26
|
+
```
|
|
19
27
|
|
|
20
|
-
|
|
21
|
-
|
|
28
|
+
New to `zuplo test`? Start with
|
|
29
|
+
[Get started with zuplo test](./testing-getting-started.mdx), which takes a
|
|
30
|
+
fresh project to a green run.
|
|
22
31
|
|
|
23
|
-
##
|
|
32
|
+
## Same assertions, rising fidelity
|
|
24
33
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
34
|
+
Structure the suite as one set of assertions run at rising levels of fidelity.
|
|
35
|
+
Maintaining different tests per stage means the stage you trust least gets the
|
|
36
|
+
tests nobody maintains.
|
|
28
37
|
|
|
29
|
-
|
|
38
|
+
| Stage | When to use | Endpoint target |
|
|
39
|
+
| --------------------------------------------------------- | -------------------------------------------------------- | ------------------------------------ |
|
|
40
|
+
| Local dev | Fast feedback while developing | `http://localhost:9000` |
|
|
41
|
+
| [Preview environment](./testing-preview-environments.mdx) | Validate a change on a real deployment before merging | `https://<branch>-<id>.zuplo.app` |
|
|
42
|
+
| [CI/CD gate](./github-deployment-testing.mdx) | Block a merge when gateway behavior regresses | Deployment URL from your CI provider |
|
|
43
|
+
| Production smoke checks | A small, read-only subset after deploy and on a schedule | Your production URL |
|
|
30
44
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
45
|
+
All of them use the same test files and the same command. The only thing that
|
|
46
|
+
changes is `--endpoint` — and, for production, a `--filter` that narrows the run
|
|
47
|
+
to the read-only subset.
|
|
34
48
|
|
|
35
|
-
|
|
36
|
-
port with the `--port` flag. See the [`zuplo dev` reference](../cli/dev.mdx) for
|
|
37
|
-
all available options.
|
|
49
|
+
:::tip
|
|
38
50
|
|
|
39
|
-
|
|
51
|
+
Nothing with side effects belongs in the production subset. Name those tests
|
|
52
|
+
consistently (a `smoke:` prefix works well) and select them by name.
|
|
40
53
|
|
|
41
|
-
|
|
54
|
+
:::
|
|
42
55
|
|
|
43
|
-
|
|
44
|
-
npx zuplo test --endpoint http://localhost:9000
|
|
45
|
-
```
|
|
56
|
+
## What to test
|
|
46
57
|
|
|
47
|
-
The
|
|
48
|
-
|
|
58
|
+
The interesting assertions at a gateway are the rejections, because the happy
|
|
59
|
+
path hides the worst defects. A policy attached to the wrong route does not fail
|
|
60
|
+
— it succeeds for the wrong caller.
|
|
49
61
|
|
|
50
|
-
|
|
62
|
+
[Gateway test recipes](./testing-recipes.mdx) has a complete, copy-pasteable
|
|
63
|
+
file for each of these:
|
|
51
64
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
65
|
+
- **Auth rejections** — missing token, expired token, wrong audience, and a
|
|
66
|
+
valid token from one tenant asking for another tenant's object
|
|
67
|
+
- **Rate limits, deterministically** — a fresh bucket per test, a sequential
|
|
68
|
+
loop, and an assertion on `429` plus `Retry-After`
|
|
69
|
+
- **OpenAPI conformance** — validate live responses against the
|
|
70
|
+
`routes.oas.json` that defines the routes
|
|
71
|
+
- **The error contract** — `application/problem+json` and the RFC 9457 members
|
|
72
|
+
- **Routing and CORS** — unknown paths, legacy aliases, preflight responses
|
|
73
|
+
- **Test data hygiene** — keep a parallel suite from corrupting itself
|
|
55
74
|
|
|
56
|
-
|
|
75
|
+
## Local testing
|
|
76
|
+
|
|
77
|
+
Running against a local development server gives the fastest feedback loop.
|
|
78
|
+
Start the server with [`zuplo dev`](../cli/dev.mdx):
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
npx zuplo dev
|
|
82
|
+
```
|
|
57
83
|
|
|
58
|
-
|
|
84
|
+
The gateway starts on `http://localhost:9000` by default. In a second terminal:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
npx zuplo test --endpoint http://localhost:9000
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Test with Zuplo services locally
|
|
59
91
|
|
|
60
92
|
Some features, such as API key authentication and rate limiting, require a
|
|
61
|
-
connection to Zuplo cloud services. To
|
|
62
|
-
link your local project to an existing Zuplo project with
|
|
93
|
+
connection to Zuplo cloud services. To exercise those policies in local
|
|
94
|
+
development, link your local project to an existing Zuplo project with
|
|
63
95
|
[`zuplo link`](../cli/link.mdx):
|
|
64
96
|
|
|
65
97
|
```bash
|
|
@@ -67,8 +99,8 @@ npx zuplo link
|
|
|
67
99
|
```
|
|
68
100
|
|
|
69
101
|
Follow the prompts to select your account, project, and environment. This
|
|
70
|
-
creates a `.env.zuplo` file that the dev server reads automatically.
|
|
71
|
-
|
|
102
|
+
creates a `.env.zuplo` file that the dev server reads automatically. We
|
|
103
|
+
recommend selecting the **development** environment for local development.
|
|
72
104
|
|
|
73
105
|
:::warning
|
|
74
106
|
|
|
@@ -77,10 +109,10 @@ The `.env.zuplo` file can contain sensitive information. Add it to your
|
|
|
77
109
|
|
|
78
110
|
:::
|
|
79
111
|
|
|
80
|
-
Once linked,
|
|
81
|
-
|
|
82
|
-
[**Services**](https://portal.zuplo.com/+/account/project/services)
|
|
83
|
-
project and select **API Key Service** to create API key consumers
|
|
112
|
+
Once linked, the API Key Authentication policy works locally using the same API
|
|
113
|
+
key bucket as the linked environment. In the Zuplo Portal, open
|
|
114
|
+
[**Services**](https://portal.zuplo.com/+/account/project/services) in your
|
|
115
|
+
project and select **API Key Service** to create API key consumers. Then call
|
|
84
116
|
your local gateway with the generated key:
|
|
85
117
|
|
|
86
118
|
```bash
|
|
@@ -88,393 +120,83 @@ curl http://localhost:9000/your-route \
|
|
|
88
120
|
-H "Authorization: Bearer YOUR_API_KEY"
|
|
89
121
|
```
|
|
90
122
|
|
|
91
|
-
For more details see
|
|
92
|
-
[
|
|
123
|
+
For more details, see
|
|
124
|
+
[connecting to Zuplo services locally](./local-development-services.mdx).
|
|
93
125
|
|
|
94
|
-
###
|
|
126
|
+
### Set environment variables locally
|
|
95
127
|
|
|
96
128
|
Your local dev server does not have access to the environment variables
|
|
97
|
-
configured in the Zuplo Portal.
|
|
98
|
-
|
|
129
|
+
configured in the Zuplo Portal. Create a `.env` file in your project root
|
|
130
|
+
instead:
|
|
99
131
|
|
|
100
132
|
```text title=".env"
|
|
101
133
|
MY_BACKEND_URL=https://api.example.com
|
|
102
134
|
MY_SECRET=supersecret
|
|
103
135
|
```
|
|
104
136
|
|
|
105
|
-
The Zuplo CLI loads these variables automatically when you run `npx zuplo dev
|
|
106
|
-
See
|
|
107
|
-
[
|
|
108
|
-
for more information.
|
|
137
|
+
The Zuplo CLI loads these variables automatically when you run `npx zuplo dev`,
|
|
138
|
+
and the same file supplies fixture values to `zuplo test`. See
|
|
139
|
+
[configuring environment variables locally](./local-development-env-variables.mdx).
|
|
109
140
|
|
|
110
|
-
##
|
|
141
|
+
## Test before merge
|
|
111
142
|
|
|
112
|
-
Every branch pushed to your connected source control provider
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
143
|
+
Every branch pushed to your connected source control provider deploys as a
|
|
144
|
+
preview environment: a full Zuplo deployment on the same edge network as
|
|
145
|
+
production, at its own URL. Running your suite there is the highest-fidelity
|
|
146
|
+
check available before a change merges, and it needs no shared staging
|
|
147
|
+
environment.
|
|
116
148
|
|
|
117
|
-
|
|
149
|
+
- [Testing preview environments](./testing-preview-environments.mdx) — getting
|
|
150
|
+
the URL into your tests, per-environment services and secrets, and what to
|
|
151
|
+
scope where
|
|
152
|
+
- [Testing GitHub deployments](./github-deployment-testing.mdx) — trigger on the
|
|
153
|
+
`deployment_status` event and make the workflow a required status check
|
|
154
|
+
- [Custom CI/CD](./custom-ci-cd.mdx) — the same pattern for GitLab, Bitbucket,
|
|
155
|
+
Azure DevOps, and CircleCI
|
|
118
156
|
|
|
119
|
-
|
|
157
|
+
:::caution{title="Poll for readiness, never sleep"}
|
|
120
158
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
deployment, policies, and connected services, tests that pass here give high
|
|
127
|
-
confidence that the changes work in production.
|
|
128
|
-
|
|
129
|
-
### Combining local and remote testing
|
|
130
|
-
|
|
131
|
-
For maximum coverage, test locally first for fast iteration, then run the same
|
|
132
|
-
test suite against the deployed preview environment:
|
|
133
|
-
|
|
134
|
-
1. Start local development and run tests against `http://localhost:9000`.
|
|
135
|
-
2. Push your branch. Zuplo deploys a preview environment automatically.
|
|
136
|
-
3. Run `npx zuplo test --endpoint <preview-url>` to verify behavior on the real
|
|
137
|
-
edge deployment.
|
|
138
|
-
|
|
139
|
-
## CI/CD integration testing
|
|
140
|
-
|
|
141
|
-
Automated tests in your CI/CD pipeline ensure that every deployment is validated
|
|
142
|
-
before changes reach production. The `zuplo test` command works with any CI
|
|
143
|
-
system.
|
|
144
|
-
|
|
145
|
-
:::tip
|
|
146
|
-
|
|
147
|
-
The examples below use the Zuplo GitHub integration. If you prefer setting up
|
|
148
|
-
your own CI/CD for more fine-grained control, see
|
|
149
|
-
[Custom CI/CD](./custom-ci-cd.mdx).
|
|
159
|
+
Most deployment APIs report success when a deployment is _accepted_, not when
|
|
160
|
+
the gateway is serving traffic. Tests fired into that gap fail, get retried, and
|
|
161
|
+
the pipeline race gets misfiled as flaky tests. Poll a health route with a
|
|
162
|
+
bounded deadline before running the suite — the CI guides above all show the
|
|
163
|
+
loop.
|
|
150
164
|
|
|
151
165
|
:::
|
|
152
166
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
Using the Zuplo GitHub integration, tests can run after a deployment and block
|
|
156
|
-
pull requests from being merged. The Zuplo Git Integration sets
|
|
157
|
-
[Deployments](https://docs.github.com/en/rest/deployments/deployments) and
|
|
158
|
-
[Deployment Statuses](https://docs.github.com/en/rest/deployments/statuses) for
|
|
159
|
-
any push to a GitHub branch.
|
|
160
|
-
|
|
161
|
-
Here is a simple GitHub Action that uses the Zuplo CLI to run the tests after
|
|
162
|
-
the deployment is successful. Notice how the property
|
|
163
|
-
`github.event.deployment_status.environment_url` is set to the `API_URL`
|
|
164
|
-
environment variable. This is one way you can pass the URL where the preview
|
|
165
|
-
environment is deployed into your tests.
|
|
166
|
-
|
|
167
|
-
```yaml title="/.github/workflows/main.yaml"
|
|
168
|
-
name: Main
|
|
169
|
-
on: [deployment_status]
|
|
170
|
-
|
|
171
|
-
jobs:
|
|
172
|
-
test:
|
|
173
|
-
name: Test API Gateway
|
|
174
|
-
runs-on: ubuntu-latest
|
|
175
|
-
|
|
176
|
-
steps:
|
|
177
|
-
- uses: actions/checkout@v4
|
|
178
|
-
- name: Use Node.js
|
|
179
|
-
uses: actions/setup-node@v4
|
|
180
|
-
with:
|
|
181
|
-
node-version-file: ".nvmrc"
|
|
182
|
-
|
|
183
|
-
- name: Run Tests
|
|
184
|
-
# Useful properties 'environment', 'state', and 'environment_url'
|
|
185
|
-
run:
|
|
186
|
-
API_URL=${{ toJson(github.event.deployment_status.environment_url) }}
|
|
187
|
-
npx zuplo test --endpoint $API_URL
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
### Requiring status checks
|
|
191
|
-
|
|
192
|
-
[GitHub Branch protection](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/defining-the-mergeability-of-pull-requests/about-protected-branches)
|
|
193
|
-
can be set in order to enforce policies on when a Pull Request can be merged.
|
|
194
|
-
The example below sets the "Zuplo Deployment" and "Test API Gateway" as required
|
|
195
|
-
status that must pass.
|
|
196
|
-
|
|
197
|
-

|
|
198
|
-
|
|
199
|
-
When a developer tries to merge their pull request, they will see that the tests
|
|
200
|
-
haven't passed and the pull request can't be merged.
|
|
201
|
-
|
|
202
|
-

|
|
203
|
-
|
|
204
|
-
### Local testing in CI
|
|
205
|
-
|
|
206
|
-
You can also run tests against a local Zuplo server inside your CI pipeline
|
|
207
|
-
before deploying anywhere. This catches issues earlier and avoids deploying
|
|
208
|
-
broken changes.
|
|
209
|
-
|
|
210
|
-
```yaml title="/.github/workflows/local-test-then-deploy.yaml"
|
|
211
|
-
name: Local Test Then Deploy
|
|
212
|
-
on:
|
|
213
|
-
push:
|
|
214
|
-
branches:
|
|
215
|
-
- main
|
|
216
|
-
pull_request:
|
|
217
|
-
|
|
218
|
-
jobs:
|
|
219
|
-
local-test:
|
|
220
|
-
runs-on: ubuntu-latest
|
|
221
|
-
steps:
|
|
222
|
-
- uses: actions/checkout@v4
|
|
223
|
-
- uses: actions/setup-node@v4
|
|
224
|
-
with:
|
|
225
|
-
node-version: 24
|
|
226
|
-
- name: Install dependencies
|
|
227
|
-
run: npm install
|
|
228
|
-
- name: Start local server and run tests
|
|
229
|
-
run: |
|
|
230
|
-
npx zuplo dev &
|
|
231
|
-
DEV_PID=$!
|
|
232
|
-
echo "Waiting for local server to start..."
|
|
233
|
-
sleep 10
|
|
234
|
-
npx zuplo test --endpoint http://localhost:9000
|
|
235
|
-
kill $DEV_PID
|
|
236
|
-
|
|
237
|
-
deploy:
|
|
238
|
-
needs: local-test
|
|
239
|
-
runs-on: ubuntu-latest
|
|
240
|
-
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
|
241
|
-
env:
|
|
242
|
-
ZUPLO_API_KEY: ${{ secrets.ZUPLO_API_KEY }}
|
|
243
|
-
steps:
|
|
244
|
-
- uses: actions/checkout@v4
|
|
245
|
-
- uses: actions/setup-node@v4
|
|
246
|
-
with:
|
|
247
|
-
node-version: 24
|
|
248
|
-
- name: Install dependencies
|
|
249
|
-
run: npm install
|
|
250
|
-
- name: Deploy to Zuplo
|
|
251
|
-
run: npx zuplo deploy --api-key "$ZUPLO_API_KEY"
|
|
252
|
-
```
|
|
253
|
-
|
|
254
|
-
For CI/CD examples with other providers, see
|
|
255
|
-
[Custom GitHub Actions](./custom-ci-cd-github.mdx),
|
|
256
|
-
[GitLab CI/CD](./custom-ci-cd-gitlab.mdx), and
|
|
257
|
-
[CircleCI](./custom-ci-cd-circleci.mdx).
|
|
258
|
-
|
|
259
|
-
## Writing tests
|
|
260
|
-
|
|
261
|
-
Using Node.js and the Zuplo CLI, it's very easy to write tests that make
|
|
262
|
-
requests to your API using `fetch` and then validate expectations with `expect`
|
|
263
|
-
from [chai](https://www.chaijs.com/api/bdd/).
|
|
167
|
+
## Unit tests and mocking
|
|
264
168
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
const result = await response.text();
|
|
273
|
-
expect(result).to.equal(JSON.stringify("What zup?"));
|
|
274
|
-
});
|
|
275
|
-
});
|
|
276
|
-
```
|
|
277
|
-
|
|
278
|
-
Check out our
|
|
279
|
-
[other sample tests](https://github.com/zuplo/zup-cli-example-project/tree/main/tests)
|
|
280
|
-
to find one that matches your use-case.
|
|
281
|
-
|
|
282
|
-
:::tip
|
|
283
|
-
|
|
284
|
-
Your test files need to be under the `tests` folder and end with `.test.ts` to
|
|
285
|
-
be picked up by the Zuplo CLI.
|
|
286
|
-
|
|
287
|
-
:::
|
|
288
|
-
|
|
289
|
-
## Tips for writing tests
|
|
290
|
-
|
|
291
|
-
This section highlights some of the features of the Zuplo CLI that can help you
|
|
292
|
-
write and structure your tests. Check out our
|
|
293
|
-
[other sample tests](https://github.com/zuplo/zup-cli-example-project/tree/main/tests)
|
|
294
|
-
to find one that matches your use-case.
|
|
295
|
-
|
|
296
|
-
### Ignoring tests
|
|
297
|
-
|
|
298
|
-
You can use `.ignore` and `.only` to ignore or run only specific test. The full
|
|
299
|
-
example is at
|
|
300
|
-
[ignore-only.test.ts](https://github.com/zuplo/zup-cli-example-project/blob/main/tests/ignore-only.test.ts)
|
|
301
|
-
|
|
302
|
-
```js title="/tests/ignore-only.test.ts"
|
|
303
|
-
import { describe, it } from "@zuplo/test";
|
|
304
|
-
import { expect } from "chai";
|
|
305
|
-
|
|
306
|
-
/**
|
|
307
|
-
* This example how to use ignore and only.
|
|
308
|
-
*/
|
|
309
|
-
describe("Ignore and only test example", () => {
|
|
310
|
-
it.ignore("This is a failing test but it's been ignored", () => {
|
|
311
|
-
expect(1 + 4).to.equals(6);
|
|
312
|
-
});
|
|
313
|
-
|
|
314
|
-
// it.only("This is the only test that would run if it weren't commented out", () => {
|
|
315
|
-
// expect(1 + 4).to.equals(5);
|
|
316
|
-
// });
|
|
317
|
-
});
|
|
318
|
-
```
|
|
319
|
-
|
|
320
|
-
### Filtering tests
|
|
321
|
-
|
|
322
|
-
You can use the CLI to filter tests by name or regex. The full example is at
|
|
323
|
-
[filter.test.ts](https://github.com/zuplo/zup-cli-example-project/blob/main/tests/filter.test.ts)
|
|
324
|
-
|
|
325
|
-
```js title="/tests/filter.test.ts"
|
|
326
|
-
import { describe, it } from "@zuplo/test";
|
|
327
|
-
import { expect } from "chai";
|
|
328
|
-
|
|
329
|
-
/**
|
|
330
|
-
* This example shows how to filter the test by the name in the describe() function.
|
|
331
|
-
* You can run `zuplo test --filter '#labelA'`
|
|
332
|
-
* If you want to use regex, you can do `zuplo test --filter '/#label[Aa]/'`
|
|
333
|
-
*/
|
|
334
|
-
describe("[#labelA #labelB] Addition", () => {
|
|
335
|
-
it("should add positive numbers", () => {
|
|
336
|
-
expect(1 + 4).to.equals(5);
|
|
337
|
-
});
|
|
338
|
-
});
|
|
339
|
-
```
|
|
340
|
-
|
|
341
|
-
### Using environment variables in tests
|
|
342
|
-
|
|
343
|
-
You can pass environment variables to your tests by setting them before the
|
|
344
|
-
`zuplo test` command. Inside your test files, access them with `process.env`:
|
|
345
|
-
|
|
346
|
-
```bash
|
|
347
|
-
MY_API_KEY=zpka_abc123 npx zuplo test --endpoint http://localhost:9000
|
|
348
|
-
```
|
|
349
|
-
|
|
350
|
-
```ts title="/tests/auth.test.ts"
|
|
351
|
-
import { describe, it, TestHelper } from "@zuplo/test";
|
|
352
|
-
import { expect } from "chai";
|
|
353
|
-
|
|
354
|
-
describe("Authentication", () => {
|
|
355
|
-
it("should reject requests without an API key", async () => {
|
|
356
|
-
const response = await fetch(`${TestHelper.TEST_URL}/my-route`);
|
|
357
|
-
expect(response.status).to.equal(401);
|
|
358
|
-
});
|
|
359
|
-
|
|
360
|
-
it("should accept requests with a valid API key", async () => {
|
|
361
|
-
const response = await fetch(`${TestHelper.TEST_URL}/my-route`, {
|
|
362
|
-
headers: {
|
|
363
|
-
Authorization: `Bearer ${process.env.MY_API_KEY}`,
|
|
364
|
-
},
|
|
365
|
-
});
|
|
366
|
-
expect(response.status).to.equal(200);
|
|
367
|
-
});
|
|
368
|
-
});
|
|
369
|
-
```
|
|
370
|
-
|
|
371
|
-
## Writing integration tests
|
|
372
|
-
|
|
373
|
-
Integration tests verify that your gateway behaves correctly end-to-end,
|
|
374
|
-
including routing, policies, and backend connectivity. Because `zuplo test` runs
|
|
375
|
-
against a live endpoint (local or deployed), every test is inherently an
|
|
376
|
-
integration test.
|
|
377
|
-
|
|
378
|
-
### Testing response status and headers
|
|
379
|
-
|
|
380
|
-
```ts title="/tests/headers.test.ts"
|
|
381
|
-
import { describe, it, TestHelper } from "@zuplo/test";
|
|
382
|
-
import { expect } from "chai";
|
|
383
|
-
|
|
384
|
-
describe("Response headers", () => {
|
|
385
|
-
it("should include CORS headers", async () => {
|
|
386
|
-
const response = await fetch(`${TestHelper.TEST_URL}/my-route`, {
|
|
387
|
-
method: "OPTIONS",
|
|
388
|
-
headers: {
|
|
389
|
-
Origin: "https://example.com",
|
|
390
|
-
"Access-Control-Request-Method": "GET",
|
|
391
|
-
},
|
|
392
|
-
});
|
|
393
|
-
expect(response.headers.get("access-control-allow-origin")).to.exist;
|
|
394
|
-
});
|
|
395
|
-
|
|
396
|
-
it("should return JSON content type", async () => {
|
|
397
|
-
const response = await fetch(`${TestHelper.TEST_URL}/my-route`);
|
|
398
|
-
expect(response.headers.get("content-type")).to.include("application/json");
|
|
399
|
-
});
|
|
400
|
-
});
|
|
401
|
-
```
|
|
402
|
-
|
|
403
|
-
### Testing rate limiting
|
|
404
|
-
|
|
405
|
-
```ts title="/tests/rate-limit.test.ts"
|
|
406
|
-
import { describe, it, TestHelper } from "@zuplo/test";
|
|
407
|
-
import { expect } from "chai";
|
|
408
|
-
|
|
409
|
-
describe("Rate limiting", () => {
|
|
410
|
-
it("should include rate limit headers", async () => {
|
|
411
|
-
const response = await fetch(`${TestHelper.TEST_URL}/my-route`, {
|
|
412
|
-
headers: {
|
|
413
|
-
Authorization: `Bearer ${process.env.MY_API_KEY}`,
|
|
414
|
-
},
|
|
415
|
-
});
|
|
416
|
-
expect(response.headers.get("ratelimit-limit")).to.exist;
|
|
417
|
-
expect(response.headers.get("ratelimit-remaining")).to.exist;
|
|
418
|
-
});
|
|
419
|
-
});
|
|
420
|
-
```
|
|
421
|
-
|
|
422
|
-
### Testing request validation
|
|
423
|
-
|
|
424
|
-
```ts title="/tests/validation.test.ts"
|
|
425
|
-
import { describe, it, TestHelper } from "@zuplo/test";
|
|
426
|
-
import { expect } from "chai";
|
|
427
|
-
|
|
428
|
-
describe("Request validation", () => {
|
|
429
|
-
it("should reject invalid request bodies", async () => {
|
|
430
|
-
const response = await fetch(`${TestHelper.TEST_URL}/my-route`, {
|
|
431
|
-
method: "POST",
|
|
432
|
-
headers: { "Content-Type": "application/json" },
|
|
433
|
-
body: JSON.stringify({ invalid: "data" }),
|
|
434
|
-
});
|
|
435
|
-
expect(response.status).to.equal(400);
|
|
436
|
-
});
|
|
437
|
-
|
|
438
|
-
it("should accept valid request bodies", async () => {
|
|
439
|
-
const response = await fetch(`${TestHelper.TEST_URL}/my-route`, {
|
|
440
|
-
method: "POST",
|
|
441
|
-
headers: { "Content-Type": "application/json" },
|
|
442
|
-
body: JSON.stringify({ name: "Test", email: "test@example.com" }),
|
|
443
|
-
});
|
|
444
|
-
expect(response.status).to.equal(200);
|
|
445
|
-
});
|
|
446
|
-
});
|
|
447
|
-
```
|
|
448
|
-
|
|
449
|
-
## Unit Tests & Mocking
|
|
169
|
+
`zuplo test` suites and the gateway are separate processes that communicate over
|
|
170
|
+
HTTP. A test can mock anything in its own process — a helper that wraps `fetch`,
|
|
171
|
+
a clock, a fixture module — but nothing inside the gateway. To control what the
|
|
172
|
+
gateway sees, change the gateway instead: return canned responses with the
|
|
173
|
+
[Mock API policy](../policies/mock-api-inbound.mdx), disable a policy for test
|
|
174
|
+
traffic with the [Testing bypass policy](./bypass-policy-for-testing.mdx), or
|
|
175
|
+
point the route at your own stub server.
|
|
450
176
|
|
|
451
177
|
:::caution{title="Advanced"}
|
|
452
178
|
|
|
453
|
-
Custom testing can be complicated
|
|
454
|
-
|
|
179
|
+
Custom testing can be complicated. Use it to test your own logic rather than
|
|
180
|
+
trying to mock large portions of your gateway.
|
|
455
181
|
|
|
456
182
|
:::
|
|
457
183
|
|
|
458
|
-
|
|
184
|
+
You can use test frameworks like
|
|
459
185
|
[Mocha](https://github.com/zuplo/zuplo/tree/main/examples/test-mocks) and
|
|
460
|
-
mocking tools like [Sinon](https://sinonjs.org/) to unit
|
|
461
|
-
policies, or other modules.
|
|
462
|
-
on GitHub
|
|
186
|
+
mocking tools like [Sinon](https://sinonjs.org/) to unit test handlers,
|
|
187
|
+
policies, or other modules. For an example, see this
|
|
188
|
+
[sample on GitHub](https://github.com/zuplo/zuplo/tree/main/examples/test-mocks).
|
|
463
189
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
mocking will remain stable between versions.
|
|
190
|
+
Not everything in the Zuplo runtime can be mocked, and internal implementation
|
|
191
|
+
changes might cause mocking behavior to change or break without notice. Unlike
|
|
192
|
+
the public API, mocking is not guaranteed to remain stable between versions.
|
|
468
193
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
Zuplo runtime code.
|
|
194
|
+
If you must write unit tests, test your logic separately from the Zuplo runtime.
|
|
195
|
+
Write modules and functions that take all their arguments as input and return a
|
|
196
|
+
result, without depending on Zuplo runtime code.
|
|
473
197
|
|
|
474
198
|
For example, if you have a function that uses an environment variable and want
|
|
475
|
-
to unit test it
|
|
476
|
-
|
|
477
|
-
Don't do this:
|
|
199
|
+
to unit test it, don't do this:
|
|
478
200
|
|
|
479
201
|
```ts
|
|
480
202
|
import { environment } from "@zuplo/runtime";
|
|
@@ -485,7 +207,7 @@ export function myFunction() {
|
|
|
485
207
|
}
|
|
486
208
|
```
|
|
487
209
|
|
|
488
|
-
|
|
210
|
+
Do this instead:
|
|
489
211
|
|
|
490
212
|
```ts
|
|
491
213
|
export function myFunction(myVar: string) {
|
|
@@ -493,13 +215,13 @@ export function myFunction(myVar: string) {
|
|
|
493
215
|
}
|
|
494
216
|
```
|
|
495
217
|
|
|
496
|
-
Then write your test
|
|
218
|
+
Then write your test:
|
|
497
219
|
|
|
498
220
|
```ts
|
|
499
221
|
import { myFunction } from "./myFunction";
|
|
500
222
|
|
|
501
223
|
describe("myFunction", () => {
|
|
502
|
-
it("
|
|
224
|
+
it("returns Hello World", () => {
|
|
503
225
|
expect(myFunction("World")).to.equal("Hello World");
|
|
504
226
|
});
|
|
505
227
|
});
|
|
@@ -509,7 +231,7 @@ describe("myFunction", () => {
|
|
|
509
231
|
|
|
510
232
|
If you are running unit tests in a Node.js environment, you may need to polyfill
|
|
511
233
|
some globals. Zuplo itself doesn't run on Node.js, but because Zuplo is built on
|
|
512
|
-
standard
|
|
234
|
+
standard APIs, testing in Node.js is possible.
|
|
513
235
|
|
|
514
236
|
Node.js 24, the minimum version the Zuplo CLI supports, includes the `webcrypto`
|
|
515
237
|
module, which polyfills the `crypto` global. You must register this polyfill
|
|
@@ -521,3 +243,11 @@ if (typeof crypto === "undefined") {
|
|
|
521
243
|
globalThis.crypto = webcrypto;
|
|
522
244
|
}
|
|
523
245
|
```
|
|
246
|
+
|
|
247
|
+
## Related
|
|
248
|
+
|
|
249
|
+
- [Get started with zuplo test](./testing-getting-started.mdx)
|
|
250
|
+
- [Gateway test recipes](./testing-recipes.mdx)
|
|
251
|
+
- [Testing preview environments](./testing-preview-environments.mdx)
|
|
252
|
+
- [Testing GitHub deployments](./github-deployment-testing.mdx)
|
|
253
|
+
- [`zuplo test` CLI reference](../cli/test.mdx)
|