Skip to content
Back to skills

Cdn Cache Purge Tests

ASecurity

Wraps CDN cache-purge testing patterns for Cloudflare (POST /zones/{zone_id}/purge_cache, single-file / everything / cache-tags / hostname / prefix), Fastly (POST purge-by-key / purge-all, surrogate-keys via Surrogate-Key header), and CloudFront (CreateInvalidation API + paths). Covers end-to-end test patterns (write origin → trigger purge → assert edge serves fresh), purge-propagation delay testing (typically 1-30s globally), surrogate-key + cache-tag patterns for group-purge, and Cache-Stat...

  • 10 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added October 12, 2026
testingpythonbashnodeawstestinggitapidocumentation

Works with

  • cli
  • api

Security analysis

A100/100

Pro scans all 4 files and shows the line behind each finding

Scanned October 12, 2026

npx -y skills add testland/qa --skill cdn-cache-purge-tests --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Cdn Cache Purge Tests?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Cdn Cache Purge Tests
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/testland-cdn-cache-purge-tests/badge)](https://www.skillsdirectory.com/skills/testland-cdn-cache-purge-tests)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: cdn-cache-purge-tests
description: "Wraps CDN cache-purge testing patterns for Cloudflare (POST /zones/{zone_id}/purge_cache, single-file / everything / cache-tags / hostname / prefix), Fastly (POST purge-by-key / purge-all, surrogate-keys via Surrogate-Key header), and CloudFront (CreateInvalidation API + paths). Covers end-to-end test patterns (write origin → trigger purge → assert edge serves fresh), purge-propagation delay testing (typically 1-30s globally), surrogate-key + cache-tag patterns for group-purge, and Cache-Status header verification (cf-cache-status: HIT/MISS/BYPASS). Also owns the client tier: browser-side Cache-Control verification with Playwright (served-from-cache via CDP, ETag 304 round-trips, Workbox service-worker strategies, reload semantics) in references/browser-cache-control.md. Use when designing or auditing CDN cache-invalidation workflows or browser-tier caching behaviour in E2E tests."
---

# cdn-cache-purge-tests

## Overview

CDN cache-purge tests verify that the
write-origin-then-invalidate-edge sequence works end-to-end - 
the most-likely-broken cache integration in real deployments.

Per
[developers.cloudflare.com/cache/how-to/purge-cache/](https://developers.cloudflare.com/cache/how-to/purge-cache/),
Cloudflare offers five purge methods (Single-file, Everything,
Cache-tags, Hostname, Prefix). Fastly's surrogate-key pattern
and CloudFront's invalidation API offer similar shapes.

## When to use

- Verifying a new purge integration works end-to-end.
- Regression-testing the write-origin-then-purge sequence.
- Auditing existing purge logic before a deploy.
- Investigating "users see stale content after a write."

## Authoring

### Cloudflare - purge by URL

```bash
curl -X POST \
  "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/purge_cache" \
  -H "Authorization: Bearer ${CF_API_TOKEN}" \
  -H "Content-Type: application/json" \
  --data '{"files":["https://example.com/api/users/1"]}'
```

Response:

```json
{ "success": true, "result": { "id": "...." } }
```

### Cloudflare - purge by cache-tag (Enterprise)

```bash
curl -X POST "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/purge_cache" \
  -H "Authorization: Bearer ${CF_API_TOKEN}" \
  --data '{"tags":["user-1","posts-feed"]}'
```

The response headers from the origin must have included
`Cache-Tag: user-1, posts-feed` for this to work.

### Fastly - purge by surrogate-key

```bash
curl -X POST "https://api.fastly.com/service/${SERVICE_ID}/purge/${SURROGATE_KEY}" \
  -H "Fastly-Key: ${FASTLY_API_TOKEN}"
```

Origin responses include `Surrogate-Key: user-1 posts-feed` (space-separated).

### CloudFront - invalidation

```bash
aws cloudfront create-invalidation \
  --distribution-id ${DIST_ID} \
  --paths "/api/users/1" "/api/users/1/*"
```

Cloud-side wait + propagation. Returns an InvalidationId; poll
status via `get-invalidation`.

## Running - end-to-end test

The canonical purge test:

```python
import requests, time

def test_write_then_purge_serves_fresh():
    # 1. Origin write
    origin_response = requests.post(
        "https://origin.example.com/api/users/1",
        json={"name": "Alice"},
        headers={"Authorization": f"Bearer {ADMIN_TOKEN}"},
    )
    assert origin_response.status_code == 200

    # 2. Verify edge has the *old* value (might or might not - cache hit)
    edge_before = requests.get("https://example.com/api/users/1")
    cache_status_before = edge_before.headers.get("cf-cache-status")

    # 3. Trigger purge
    purge = requests.post(
        f"https://api.cloudflare.com/client/v4/zones/{ZONE_ID}/purge_cache",
        headers={"Authorization": f"Bearer {CF_API_TOKEN}"},
        json={"files": ["https://example.com/api/users/1"]},
    )
    assert purge.json()["success"]

    # 4. Wait for global propagation (Cloudflare typically <30s)
    time.sleep(10)

    # 5. Verify edge fetches fresh from origin
    edge_after = requests.get("https://example.com/api/users/1")
    cache_status_after = edge_after.headers.get("cf-cache-status")

    # Either MISS (just fetched) or HIT (re-cached fresh value)
    assert cache_status_after in ("MISS", "HIT", "EXPIRED")
    assert edge_after.json()["name"] == "Alice"
```

### Cache-Status header verification

| Header | CDN | Common values |
|---|---|---|
| `cf-cache-status` | Cloudflare | HIT, MISS, EXPIRED, BYPASS, DYNAMIC, REVALIDATED |
| `x-cache` | Fastly | HIT, MISS, HIT-CLUSTER, HIT-CLUSTER-WAIT |
| `x-cache` | CloudFront | Hit from cloudfront, Miss from cloudfront |
| `age` | RFC 9111 standard | Seconds since cached |

### Multi-region propagation

```python
EDGES = [
    "https://example.com",     # default
    "https://eu.example.com",  # geo-routed
    "https://ap.example.com",
]

def test_purge_propagates_globally():
    # Pre-cache in each region
    for url in EDGES:
        requests.get(url + "/api/users/1")

    # Trigger purge
    purge_url(API, "/api/users/1")

    # Wait for global propagation
    time.sleep(30)

    # Verify each region serves fresh
    for url in EDGES:
        r = requests.get(url + "/api/users/1")
        assert r.json()["name"] == "Alice"
```

## Client-tier cache-control

The purge test proves the edge refreshes; the client tier can still serve
stale from the browser's own cache. Browser-side verification - asserting
the browser acts on `Cache-Control` as intended (served-from-cache via
CDP, ETag `If-None-Match` → 304 round-trips, Workbox service-worker
strategies, normal-vs-hard reload semantics) with Playwright across the
Chromium / Firefox / WebKit matrix - is in
[references/browser-cache-control.md](references/browser-cache-control.md),
with deeper recipes in
[references/playwright-cache-recipes.md](references/playwright-cache-recipes.md).
Asserting only which header the server emits needs no browser - keep that
in the project's HTTP-level runner.

## Parsing results

| Field | Use |
|---|---|
| `cf-cache-status: HIT` | Served from edge cache |
| `cf-cache-status: MISS` | Origin pull just happened |
| `cf-cache-status: EXPIRED` | Stale; revalidated from origin |
| `cf-cache-status: BYPASS` | Cache deliberately skipped (e.g., uncacheable response) |
| `cf-cache-status: DYNAMIC` | Not cached at all |
| `age: N` | Per RFC 9111: seconds since cached |

For tests: assert on the **transition** (HIT → MISS after purge),
not on the absolute state.

## CI integration

```yaml
jobs:
  cdn-purge-smoke:
    if: github.event_name == 'deployment_status' && github.event.deployment_status.state == 'success'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - name: Write to origin
        env:
          ADMIN_TOKEN: ${{ secrets.ADMIN_TOKEN }}
        run: ./scripts/write-test-fixture.sh
      - name: Purge edge
        env:
          CF_API_TOKEN: ${{ secrets.CF_API_TOKEN }}
        run: ./scripts/purge-test-paths.sh
      - name: Verify edge serves fresh
        run: pytest tests/cdn/test_purge_propagation.py
```

## Anti-patterns

| Anti-pattern | Why it fails | Fix |
|---|---|---|
| `time.sleep(60)` between purge + assertion | Slow; sometimes shorter / sometimes longer | Poll `cf-cache-status` until MISS (timeout 30s) |
| Test purge against prod | Pollutes prod cache; rate-limited | Dedicated test domain + zone |
| Purge-everything for smoke test | Massive cache flush; alarming for ops | Single-file or tag-based |
| Don't test multi-region | Edge in test region; user in another sees stale | Verify across regions |
| No `Cache-Tag` / `Surrogate-Key` on origin | Group-purge has nothing to target | Origin must set tags |
| Use only cf-cache-status to verify fresh | Could be HIT of newly-purged fresh fetch | Compare response body to known fresh state |
| Skip purge-key naming review | Hot keys (`all`, `feed`) become noisy | Per-resource tagging strategy |
| Assume purge is synchronous | Cloudflare: <30s; CloudFront: minutes | Plan for async; poll |

## Limitations

- **Per-vendor behaviour differs.** Cloudflare and Fastly purge
  in seconds; CloudFront invalidations take longer. Test against
  the actual vendor in use.
- **Purge rate limits.** Cloudflare has per-zone tag-purge limits;
  Fastly has API-call rate limits. Tests can hit these.
- **Edge node consistency.** Even within a CDN, edge nodes refresh
  at slightly different times. Cross-edge tests need tolerance.
- **Doesn't test the origin's behaviour.** If origin sends
  uncacheable responses, purge does nothing. Pair with origin
  Cache-Control assertions in the project's HTTP-level runner and the
  browser-tier tests in
  [references/browser-cache-control.md](references/browser-cache-control.md).

## References

- Cloudflare cache-purge methods:
  [developers.cloudflare.com/cache/how-to/purge-cache/](https://developers.cloudflare.com/cache/how-to/purge-cache/).
- Cloudflare purge API:
  [developers.cloudflare.com/api/operations/zone-purge](https://developers.cloudflare.com/api/operations/zone-purge).
- Fastly purge docs:
  [fastly.com/documentation/reference/api/purging](https://www.fastly.com/documentation/reference/api/purging/).
- CloudFront CreateInvalidation:
  [docs.aws.amazon.com/cloudfront/latest/APIReference/API_CreateInvalidation.html](https://docs.aws.amazon.com/cloudfront/latest/APIReference/API_CreateInvalidation.html).
- Client-tier tests:
  [references/browser-cache-control.md](references/browser-cache-control.md),
  [references/playwright-cache-recipes.md](references/playwright-cache-recipes.md).
- Companion catalog:
  `cache-coherence-patterns-reference` (incl. stampede + SWR/SIE in its
  references/).
- Sibling tools:
  `redis-cache-tests`,
  `varnish-test-vtc-syntax`.

Files in this skill

  • .tessl-plugin/plugin.json1 KB
  • SKILL.md9.4 KB
  • references/browser-cache-control.md4.4 KB
  • references/playwright-cache-recipes.md3.4 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.