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.
Files changed (40) hide show
  1. package/docs/articles/ci-cd-azure/local-testing.mdx +22 -4
  2. package/docs/articles/ci-cd-bitbucket/local-testing.mdx +22 -4
  3. package/docs/articles/ci-cd-circleci/local-testing.mdx +21 -4
  4. package/docs/articles/ci-cd-github/deploy-and-test.mdx +28 -13
  5. package/docs/articles/ci-cd-github/local-testing.mdx +36 -16
  6. package/docs/articles/ci-cd-gitlab/local-testing.mdx +22 -4
  7. package/docs/articles/github-deployment-testing.mdx +118 -31
  8. package/docs/articles/testing-getting-started.mdx +220 -0
  9. package/docs/articles/testing-preview-environments.mdx +148 -0
  10. package/docs/articles/testing-recipes.mdx +429 -0
  11. package/docs/articles/testing.mdx +138 -408
  12. package/docs/mcp-gateway/auth/configuring-auth0.mdx +6 -5
  13. package/docs/mcp-gateway/auth/configuring-clerk.mdx +4 -4
  14. package/docs/mcp-gateway/auth/configuring-cognito.mdx +5 -4
  15. package/docs/mcp-gateway/auth/configuring-entra.mdx +5 -4
  16. package/docs/mcp-gateway/auth/configuring-generic-oidc.mdx +8 -8
  17. package/docs/mcp-gateway/auth/configuring-google.mdx +4 -4
  18. package/docs/mcp-gateway/auth/configuring-keycloak.mdx +4 -3
  19. package/docs/mcp-gateway/auth/configuring-logto.mdx +4 -4
  20. package/docs/mcp-gateway/auth/configuring-okta.mdx +3 -3
  21. package/docs/mcp-gateway/auth/configuring-onelogin.mdx +3 -3
  22. package/docs/mcp-gateway/auth/configuring-ping.mdx +3 -3
  23. package/docs/mcp-gateway/auth/configuring-workos.mdx +5 -4
  24. package/docs/mcp-gateway/auth/manual-oauth-testing.mdx +8 -8
  25. package/docs/mcp-gateway/auth/overview.mdx +17 -17
  26. package/docs/mcp-gateway/auth/upstream-oauth.mdx +5 -5
  27. package/docs/mcp-gateway/code-config/local-development.mdx +14 -12
  28. package/docs/mcp-gateway/code-config/overview.mdx +9 -4
  29. package/docs/mcp-gateway/connect-clients/chatgpt.mdx +114 -56
  30. package/docs/mcp-gateway/how-it-works.mdx +11 -9
  31. package/docs/mcp-gateway/introduction.mdx +3 -1
  32. package/docs/mcp-gateway/quickstart-local.mdx +7 -7
  33. package/docs/mcp-gateway/reference.mdx +58 -25
  34. package/docs/mcp-gateway/server-registry.mdx +179 -0
  35. package/docs/mcp-gateway/test-clients.mdx +2 -2
  36. package/docs/mcp-server/custom-tools.mdx +32 -0
  37. package/docs/programmable-api/mcp-gateway-plugin.mdx +137 -0
  38. package/docs/programmable-api/mcp-sdk.mdx +240 -0
  39. package/docs/self-hosted/overview.md +2 -0
  40. package/package.json +5 -5
@@ -1,6 +1,9 @@
1
1
  ---
2
- title: "Azure Pipelines: Local Testing in CI"
3
- sidebar_label: Local Testing in CI
2
+ title: "Azure Pipelines: Local testing in CI"
3
+ sidebar_label: Local testing in CI
4
+ description:
5
+ Start a local Zuplo dev server inside Azure Pipelines, poll it until it
6
+ answers, run your test suite, and only deploy if every test passes.
4
7
  ---
5
8
 
6
9
  Test against a local Zuplo server before deploying anywhere.
@@ -29,7 +32,16 @@ stages:
29
32
  - script: |
30
33
  npx zuplo dev &
31
34
  DEV_PID=$!
32
- sleep 10
35
+ # Poll until the server answers — never a fixed sleep
36
+ deadline=$((SECONDS + 60))
37
+ until curl --fail --silent --output /dev/null http://localhost:9000/health; do
38
+ if (( SECONDS >= deadline )); then
39
+ echo "Local server did not start within 60s" >&2
40
+ kill $DEV_PID
41
+ exit 1
42
+ fi
43
+ sleep 1
44
+ done
33
45
  npx zuplo test --endpoint http://localhost:9000
34
46
  kill $DEV_PID
35
47
  displayName: "Start local server and run tests"
@@ -54,6 +66,12 @@ stages:
54
66
 
55
67
  Local tests run first. Only if they pass does deployment proceed.
56
68
 
57
- ## Next Steps
69
+ The readiness poll replaces a fixed `sleep`. A sleep is a guess about startup
70
+ time that is wrong in both directions, and it is the most common reason a
71
+ gateway test suite "needs" a retry. The example polls `/health` — add an
72
+ unauthenticated health route if your gateway does not have one. See
73
+ [health checks](../health-checks.mdx).
74
+
75
+ ## Next steps
58
76
 
59
77
  - Add [multi-stage deployment](./multi-stage-deployment.mdx) with staging
@@ -1,6 +1,9 @@
1
1
  ---
2
- title: "Bitbucket Pipelines: Local Testing in CI"
3
- sidebar_label: Local Testing in CI
2
+ title: "Bitbucket Pipelines: Local testing in CI"
3
+ sidebar_label: Local testing in CI
4
+ description:
5
+ Start a local Zuplo dev server inside Bitbucket Pipelines, poll it until it
6
+ answers, run your test suite, and only deploy if every test passes.
4
7
  ---
5
8
 
6
9
  Test against a local Zuplo server before deploying anywhere.
@@ -16,7 +19,16 @@ pipelines:
16
19
  script:
17
20
  - npm install
18
21
  - npx zuplo dev &
19
- - sleep 10
22
+ # Poll until the server answers — never a fixed sleep
23
+ - |
24
+ deadline=$((SECONDS + 60))
25
+ until curl --fail --silent --output /dev/null http://localhost:9000/health; do
26
+ if (( SECONDS >= deadline )); then
27
+ echo "Local server did not start within 60s" >&2
28
+ exit 1
29
+ fi
30
+ sleep 1
31
+ done
20
32
  - npx zuplo test --endpoint http://localhost:9000
21
33
  - kill %1
22
34
 
@@ -29,6 +41,12 @@ pipelines:
29
41
 
30
42
  Local tests run first. Only if they pass does deployment proceed.
31
43
 
32
- ## Next Steps
44
+ The readiness poll replaces a fixed `sleep`. A sleep is a guess about startup
45
+ time that is wrong in both directions, and it is the most common reason a
46
+ gateway test suite "needs" a retry. The example polls `/health` — add an
47
+ unauthenticated health route if your gateway does not have one. See
48
+ [health checks](../health-checks.mdx).
49
+
50
+ ## Next steps
33
51
 
34
52
  - Add [multi-stage deployment](./multi-stage-deployment.mdx) with staging
@@ -1,6 +1,9 @@
1
1
  ---
2
- title: "CircleCI: Local Testing in CI"
3
- sidebar_label: Local Testing in CI
2
+ title: "CircleCI: Local testing in CI"
3
+ sidebar_label: Local testing in CI
4
+ description:
5
+ Start a local Zuplo dev server inside CircleCI, poll it until it answers, run
6
+ your test suite, and only deploy if every test passes.
4
7
  ---
5
8
 
6
9
  Test against a local Zuplo server before deploying anywhere.
@@ -19,7 +22,15 @@ jobs:
19
22
  name: Start local server and run tests
20
23
  command: |
21
24
  npx zuplo dev &
22
- sleep 10
25
+ # Poll until the server answers — never a fixed sleep
26
+ deadline=$((SECONDS + 60))
27
+ until curl --fail --silent --output /dev/null http://localhost:9000/health; do
28
+ if (( SECONDS >= deadline )); then
29
+ echo "Local server did not start within 60s" >&2
30
+ exit 1
31
+ fi
32
+ sleep 1
33
+ done
23
34
  npx zuplo test --endpoint http://localhost:9000
24
35
  kill %1
25
36
 
@@ -45,6 +56,12 @@ workflows:
45
56
 
46
57
  Local tests run first. Only if they pass does deployment proceed.
47
58
 
48
- ## Next Steps
59
+ The readiness poll replaces a fixed `sleep`. A sleep is a guess about startup
60
+ time that is wrong in both directions, and it is the most common reason a
61
+ gateway test suite "needs" a retry. The example polls `/health` — add an
62
+ unauthenticated health route if your gateway does not have one. See
63
+ [health checks](../health-checks.mdx).
64
+
65
+ ## Next steps
49
66
 
50
67
  - Add [multi-stage deployment](./multi-stage-deployment.mdx) with staging
@@ -1,6 +1,10 @@
1
1
  ---
2
- title: "GitHub Actions: Deploy and Test"
3
- sidebar_label: Deploy and Test
2
+ title: "GitHub Actions: Deploy and test"
3
+ sidebar_label: Deploy and test
4
+ description:
5
+ Deploy to Zuplo from a GitHub Actions workflow, capture the deployment URL,
6
+ and run your test suite against the live environment to validate changes
7
+ before they reach production.
4
8
  ---
5
9
 
6
10
  Run your test suite against the deployed environment to validate changes before
@@ -54,42 +58,53 @@ jobs:
54
58
 
55
59
  This workflow:
56
60
 
57
- 1. Deploys to Zuplo and captures the deployment URL
58
- 2. Runs your test suite against the live deployment
59
- 3. Fails the workflow if any tests fail
61
+ 1. Deploys to Zuplo and captures the deployment URL.
62
+ 2. Runs your test suite against the live deployment.
63
+ 3. Fails the workflow if any tests fail.
60
64
 
61
65
  The deploy step passes `--environment` explicitly because this workflow runs on
62
66
  both `push` and `pull_request` events. The expression
63
67
  `${{ github.head_ref || github.ref_name }}` resolves to the branch name on
64
68
  either trigger, so every run for a branch updates the same environment. Without
65
69
  it, `pull_request` runs create a second environment named after the PR merge ref
66
- instead of your branch see
67
- [PR Preview Environments](./pr-preview-environments.mdx) for details.
70
+ instead of your branch. For details, see
71
+ [PR preview environments](./pr-preview-environments.mdx).
68
72
 
69
- ## Writing Tests
73
+ ## Write tests
74
+
75
+ `describe`, `it`, and `TestHelper` come from `@zuplo/test`, which a Zuplo
76
+ project already has through the `zuplo` package. Assertions use
77
+ [chai](https://www.chaijs.com/api/bdd/), which a new project does **not**
78
+ include. Install it first:
79
+
80
+ ```bash
81
+ npm install --save-dev chai @types/chai
82
+ ```
70
83
 
71
84
  Place test files in the `tests` folder with the `.test.ts` extension:
72
85
 
73
86
  ```typescript title="tests/api.test.ts"
74
- import { describe, it } from "@zuplo/test";
87
+ import { describe, it, TestHelper } from "@zuplo/test";
75
88
  import { expect } from "chai";
76
89
 
77
90
  describe("API", () => {
78
91
  it("returns 200 for health check", async () => {
79
- const response = await fetch(`${ZUPLO_TEST_URL}/health`);
92
+ const response = await fetch(`${TestHelper.TEST_URL}/health`);
80
93
  expect(response.status).to.equal(200);
81
94
  });
82
95
 
83
96
  it("requires authentication", async () => {
84
- const response = await fetch(`${ZUPLO_TEST_URL}/protected`);
97
+ const response = await fetch(`${TestHelper.TEST_URL}/protected`);
85
98
  expect(response.status).to.equal(401);
86
99
  });
87
100
  });
88
101
  ```
89
102
 
90
- The `ZUPLO_TEST_URL` variable is automatically set to the `--endpoint` value.
103
+ `TestHelper.TEST_URL` is the value passed to `--endpoint`, so the same files run
104
+ against every environment. For the full setup, see
105
+ [Get started with zuplo test](../testing-getting-started.mdx).
91
106
 
92
- ## Next Steps
107
+ ## Next steps
93
108
 
94
109
  - Add [PR preview environments](./pr-preview-environments.mdx) with automatic
95
110
  cleanup
@@ -1,6 +1,9 @@
1
1
  ---
2
- title: "GitHub Actions: Local Testing in CI"
3
- sidebar_label: Local Testing in CI
2
+ title: "GitHub Actions: Local testing in CI"
3
+ sidebar_label: Local testing in CI
4
+ description:
5
+ Start a local Zuplo dev server inside a GitHub Actions workflow, poll it until
6
+ it answers, run your test suite, and only deploy if every test passes.
4
7
  ---
5
8
 
6
9
  Run tests against a local Zuplo development server before deploying anywhere.
@@ -35,9 +38,19 @@ jobs:
35
38
  npx zuplo dev &
36
39
  DEV_PID=$!
37
40
 
38
- # Wait for server to be ready
41
+ # Poll until the server answers, with a hard deadline. Never use a
42
+ # fixed sleep: it is flaky when the runner is slow and wasted CI
43
+ # minutes when it is fast.
39
44
  echo "Waiting for local server to start..."
40
- sleep 10
45
+ deadline=$((SECONDS + 60))
46
+ until curl --fail --silent --output /dev/null http://localhost:9000/health; do
47
+ if (( SECONDS >= deadline )); then
48
+ echo "Local server did not start within 60s" >&2
49
+ kill $DEV_PID
50
+ exit 1
51
+ fi
52
+ sleep 1
53
+ done
41
54
 
42
55
  # Run tests against local server
43
56
  npx zuplo test --endpoint http://localhost:9000
@@ -71,20 +84,27 @@ jobs:
71
84
 
72
85
  This workflow:
73
86
 
74
- 1. Starts a local Zuplo server in the CI environment
75
- 2. Runs your test suite against localhost
76
- 3. Only proceeds to deployment if local tests pass
77
- 4. Deploys to Zuplo (only on pushes to main)
87
+ 1. Starts a local Zuplo server in the CI environment.
88
+ 2. Polls it until it answers, with a bounded deadline.
89
+ 3. Runs your test suite against localhost.
90
+ 4. Proceeds to deployment only if local tests pass.
91
+ 5. Deploys to Zuplo (only on pushes to main).
78
92
 
79
- ## Why Test Locally First?
93
+ The poll replaces a fixed `sleep`. A sleep is a guess about startup time that is
94
+ wrong in both directions, and it is the most common reason a gateway test suite
95
+ "needs" a retry. The example polls `/health` — add an unauthenticated health
96
+ route if your gateway does not have one. See
97
+ [health checks](../health-checks.mdx).
80
98
 
81
- - **Faster feedback** Local tests run without waiting for deployment
82
- - **Catch syntax errors** — The local server validates your configuration
83
- - **Test policies** — Verify authentication, rate limiting, and other policies
84
- work correctly
85
- - **No wasted deployments** — Don't deploy changes that will fail tests
99
+ ## Why test locally first
86
100
 
87
- ## Combining with Remote Tests
101
+ - **Faster feedback** local tests run without waiting for deployment.
102
+ - **Catch syntax errors** — the local server validates your configuration.
103
+ - **Test policies** — verify authentication, rate limiting, and other policies
104
+ work correctly.
105
+ - **No wasted deployments** — don't deploy changes that fail tests.
106
+
107
+ ## Combine with remote tests
88
108
 
89
109
  For maximum confidence, test both locally and against the deployed environment:
90
110
 
@@ -98,7 +118,7 @@ jobs:
98
118
  # ... deploy and run tests against live environment ...
99
119
  ```
100
120
 
101
- ## Next Steps
121
+ ## Next steps
102
122
 
103
123
  - Add [PR preview environments](./pr-preview-environments.mdx) for review
104
124
  - Set up [multi-stage deployment](./multi-stage-deployment.mdx) with staging
@@ -1,6 +1,9 @@
1
1
  ---
2
- title: "GitLab CI/CD: Local Testing in CI"
3
- sidebar_label: Local Testing in CI
2
+ title: "GitLab CI/CD: Local testing in CI"
3
+ sidebar_label: Local testing in CI
4
+ description:
5
+ Start a local Zuplo dev server inside GitLab CI/CD, poll it until it answers,
6
+ run your test suite, and only deploy if every test passes.
4
7
  ---
5
8
 
6
9
  Test against a local Zuplo server before deploying anywhere.
@@ -17,7 +20,16 @@ local-test:
17
20
  script:
18
21
  - npm install
19
22
  - npx zuplo dev &
20
- - sleep 10
23
+ # Poll until the server answers, with a hard deadline — never a fixed sleep
24
+ - |
25
+ deadline=$((SECONDS + 60))
26
+ until curl --fail --silent --output /dev/null http://localhost:9000/health; do
27
+ if (( SECONDS >= deadline )); then
28
+ echo "Local server did not start within 60s" >&2
29
+ exit 1
30
+ fi
31
+ sleep 1
32
+ done
21
33
  - npx zuplo test --endpoint http://localhost:9000
22
34
  - kill %1
23
35
 
@@ -34,6 +46,12 @@ deploy:
34
46
 
35
47
  Local tests run first. Only if they pass does deployment proceed.
36
48
 
37
- ## Next Steps
49
+ The readiness poll replaces a fixed `sleep`. A sleep is a guess about startup
50
+ time that is wrong in both directions, and it is the most common reason a
51
+ gateway test suite "needs" a retry. The example polls `/health` — add an
52
+ unauthenticated health route if your gateway does not have one. See
53
+ [health checks](../health-checks.mdx).
54
+
55
+ ## Next steps
38
56
 
39
57
  - Add [multi-stage deployment](./multi-stage-deployment.mdx) with staging
@@ -1,27 +1,31 @@
1
1
  ---
2
- title: Testing GitHub Deployments
3
- sidebar_label: Testing Deployments
2
+ title: Test GitHub deployments
3
+ sidebar_label: Test deployments
4
+ description:
5
+ Trigger a GitHub Actions workflow on Zuplo's deployment_status event, run your
6
+ test suite against the preview URL, and make the result a required status
7
+ check so a gateway regression blocks the merge.
4
8
  ---
5
9
 
6
10
  Run your test suite automatically after every Zuplo deployment without replacing
7
11
  the built-in GitHub integration. This approach uses GitHub's `deployment_status`
8
- event to trigger tests after Zuplo finishes deploying.
12
+ event to trigger tests after Zuplo finishes deploying, and makes the result a
13
+ required status check so a gateway regression blocks the merge.
9
14
 
10
- ## Why This Approach?
15
+ ## Why trigger on deployment_status
11
16
 
12
- Zuplo's GitHub integration already handles deployments perfectly — every push
13
- deploys automatically with status checks in GitHub. Rather than replacing this
14
- with custom CI/CD, you can extend it by running tests after each deployment
15
- completes.
17
+ Zuplo's GitHub integration handles deployments — every push deploys
18
+ automatically with status checks in GitHub. Rather than replacing this with
19
+ custom CI/CD, extend it by running tests after each deployment completes.
16
20
 
17
21
  This gives you:
18
22
 
19
- - **Automatic deployments** — Keep the built-in integration
20
- - **Post-deploy testing** — Run tests against the live environment
21
- - **PR checks** — Tests block merging until they pass
22
- - **No duplicate deploys** — Tests run after Zuplo deploys, not instead of
23
+ - **Automatic deployments** — keep the built-in integration
24
+ - **Post-deploy testing** — run tests against the live environment
25
+ - **PR checks** — tests block merging until they pass
26
+ - **No duplicate deploys** — tests run after Zuplo deploys, not instead of
23
27
 
24
- ## Setup
28
+ ## Set up the workflow
25
29
 
26
30
  Create a workflow that triggers on the `deployment_status` event:
27
31
 
@@ -33,11 +37,14 @@ on:
33
37
 
34
38
  jobs:
35
39
  test:
36
- # Only run when Zuplo deployment succeeds
40
+ name: Test API Gateway
41
+ # Only run when a Zuplo deployment succeeds and reports a URL
37
42
  if: |
38
43
  github.event.deployment_status.state == 'success' &&
39
44
  github.event.deployment_status.environment_url != ''
40
45
  runs-on: ubuntu-latest
46
+ env:
47
+ API_URL: ${{ github.event.deployment_status.environment_url }}
41
48
  steps:
42
49
  - uses: actions/checkout@v4
43
50
 
@@ -46,29 +53,74 @@ jobs:
46
53
  node-version: 24
47
54
 
48
55
  - name: Install dependencies
49
- run: npm install
56
+ run: npm ci
57
+
58
+ # A deployment reporting success is not the same as a gateway
59
+ # serving traffic. Poll with a hard deadline instead of sleeping.
60
+ - name: Wait for the gateway to be ready
61
+ run: |
62
+ deadline=$((SECONDS + 120))
63
+ until curl --fail --silent --output /dev/null "$API_URL/health"; do
64
+ if (( SECONDS >= deadline )); then
65
+ echo "Gateway at $API_URL not ready after 120s" >&2
66
+ exit 1
67
+ fi
68
+ sleep 2
69
+ done
50
70
 
51
71
  - name: Run tests
52
- run:
53
- npx zuplo test --endpoint ${{
54
- github.event.deployment_status.environment_url }}
72
+ run: npx zuplo test --endpoint "$API_URL"
55
73
  ```
56
74
 
57
- ## How It Works
75
+ The workflow reads the deployment URL once into `API_URL` and reuses it, so the
76
+ readiness check and the test run target the same environment.
58
77
 
59
- 1. You push code to GitHub
60
- 2. Zuplo's integration deploys automatically
61
- 3. Zuplo reports deployment status back to GitHub
62
- 4. The `deployment_status` event triggers your workflow
63
- 5. Your tests run against the deployed environment URL
64
- 6. Test results appear as a check on the commit/PR
78
+ :::caution{title="Poll for readiness, never sleep"}
79
+
80
+ `deployment_status` fires when the deployment is _accepted_, which can be
81
+ shortly before the environment is warm. A fixed `sleep` is a guess that is wrong
82
+ in both directions flaky when the system is slow, wasted CI minutes when it is
83
+ fast. Tests fired into that gap fail, get retried, and the pipeline race gets
84
+ misfiled as flaky tests.
85
+
86
+ If your gateway has no unauthenticated health route, add one. See
87
+ [health checks](./health-checks.mdx).
88
+
89
+ :::
90
+
91
+ ## How it works
92
+
93
+ 1. You push code to GitHub.
94
+ 2. Zuplo's integration deploys automatically.
95
+ 3. Zuplo reports deployment status back to GitHub.
96
+ 4. The `deployment_status` event triggers your workflow.
97
+ 5. The workflow polls the deployment until it answers.
98
+ 6. Your tests run against the deployed environment URL.
99
+ 7. Test results appear as a check on the commit or pull request.
65
100
 
66
101
  The `environment_url` from the deployment status contains your Zuplo environment
67
102
  URL, so tests always run against the correct environment.
68
103
 
69
- ## Filtering by Environment
104
+ ## Pass fixture credentials
105
+
106
+ Tests that exercise authenticated routes need fixture tokens or API keys. Keep
107
+ them in GitHub Actions secrets and pass them as environment variables. Inside
108
+ the test they are available on `TestHelper.environment`:
70
109
 
71
- To only test specific environments (like staging or production):
110
+ ```yaml
111
+ - name: Run tests
112
+ env:
113
+ TENANT_A_JWT: ${{ secrets.PREVIEW_TENANT_A_JWT }}
114
+ run: npx zuplo test --endpoint "$API_URL"
115
+ ```
116
+
117
+ API key buckets and rate limit state are per environment, so the credentials
118
+ must belong to the environment being tested. See
119
+ [testing preview environments](./testing-preview-environments.mdx).
120
+
121
+ ## Filter by environment
122
+
123
+ To test specific environments only, such as staging or production:
72
124
 
73
125
  ```yaml
74
126
  jobs:
@@ -79,15 +131,44 @@ jobs:
79
131
  # ...
80
132
  ```
81
133
 
82
- ## Adding to PR Checks
134
+ Against production, run only the read-only smoke subset rather than the whole
135
+ suite:
136
+
137
+ ```yaml
138
+ - name: Run smoke tests
139
+ run: npx zuplo test --endpoint "$API_URL" --filter "smoke"
140
+ ```
141
+
142
+ ## Add to PR checks
83
143
 
84
144
  GitHub automatically shows deployment status checks on pull requests. Your test
85
145
  workflow results appear alongside them, giving reviewers confidence that both
86
146
  deployment and tests succeeded.
87
147
 
88
- ## When to Use Custom CI/CD Instead
148
+ To make the check blocking, add the workflow's job name to
149
+ [branch protection](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/defining-the-mergeability-of-pull-requests/about-protected-branches)
150
+ as a required status check. The example below marks both "Zuplo Deployment" and
151
+ "Test API Gateway" as required.
152
+
153
+ ![Require status checks](../../public/media/testing/a1d7c322-125d-4d80-add0-fbfb65ccfea1.png)
154
+
155
+ A developer who tries to merge before the tests pass sees the merge blocked.
156
+
157
+ ![Test failure](../../public/media/testing/3f3292a3-075c-4568-afb2-00c24e704f03.png)
89
158
 
90
- This approach works great when you want to:
159
+ :::warning
160
+
161
+ Do not add a blanket retry to the test job. If a test needs a rerun to pass, it
162
+ has a shared resource, a sleep, or the pipeline is reporting readiness before
163
+ the gateway is serving. A retry converts a debuggable signal into permanent
164
+ background noise. See [gateway test recipes](./testing-recipes.mdx) for the
165
+ determinism rules.
166
+
167
+ :::
168
+
169
+ ## When to use custom CI/CD
170
+
171
+ This approach works well when you want to:
91
172
 
92
173
  - Keep automatic deployments
93
174
  - Run tests after deploy
@@ -96,6 +177,12 @@ This approach works great when you want to:
96
177
  Consider [custom GitHub Actions](./custom-ci-cd-github.mdx) if you need:
97
178
 
98
179
  - Approval gates before production
99
- - Multi-stage deployments (staging production)
180
+ - Multi-stage deployments (staging then production)
100
181
  - Tests that must pass before any deployment
101
182
  - Tag-based or release-based deployments
183
+
184
+ ## Related
185
+
186
+ - [Testing overview](./testing.mdx)
187
+ - [Get started with zuplo test](./testing-getting-started.mdx)
188
+ - [Testing preview environments](./testing-preview-environments.mdx)