Skip to content
File

Blob: src/workerd/api/tests/cf-cache-control-test.js

javascript184 lines
1// Copyright (c) 2025 Cloudflare, Inc.
2// Licensed under the Apache 2.0 license found in the LICENSE file or at:
3// https://opensource.org/licenses/Apache-2.0
4 
5// Tests for cf.cacheControl mutual exclusion, synthesis, and additional cache settings.
6 
7import assert from 'node:assert';
8 
9// Tests for cf.cacheControl mutual exclusion and synthesis.
10// These tests run regardless of cache option flag since cacheControl is always available.
11export const cacheControlMutualExclusion = {
12 async test(ctrl, env, ctx) {
13 // cacheControl + cacheTtl → TypeError at construction time
14 assert.throws(
15 () =>
16 new Request('https://example.org', {
17 cf: { cacheControl: 'max-age=300', cacheTtl: 300 },
18 }),
19 {
20 name: 'TypeError',
21 message: /cacheControl.*cacheTtl.*mutually exclusive/,
22 }
23 );
24 
25 // cacheControl alone should succeed
26 {
27 const req = new Request('https://example.org', {
28 cf: { cacheControl: 'public, max-age=3600' },
29 });
30 assert.ok(req.cf);
31 }
32 
33 // cacheTtl alone should succeed
34 {
35 const req = new Request('https://example.org', {
36 cf: { cacheTtl: 300 },
37 });
38 assert.ok(req.cf);
39 }
40 
41 // cacheControl + cacheTtlByStatus should succeed (not mutually exclusive)
42 {
43 const req = new Request('https://example.org', {
44 cf: {
45 cacheControl: 'public, max-age=3600',
46 cacheTtlByStatus: { '200-299': 86400 },
47 },
48 });
49 assert.ok(req.cf);
50 }
51 
52 // cacheControl with undefined cacheTtl should succeed (only non-undefined triggers conflict)
53 {
54 const req = new Request('https://example.org', {
55 cf: { cacheControl: 'max-age=300', cacheTtl: undefined },
56 });
57 assert.ok(req.cf);
58 }
59 },
60};
61 
62export const cacheControlWithCacheOption = {
63 async test(ctrl, env, ctx) {
64 if (!env.CACHE_ENABLED) return;
65 
66 // cache option + cf.cacheControl → TypeError at construction time
67 assert.throws(
68 () =>
69 new Request('https://example.org', {
70 cache: 'no-store',
71 cf: { cacheControl: 'no-cache' },
72 }),
73 {
74 name: 'TypeError',
75 message: /cacheControl.*cannot be used together with the.*cache/,
76 }
77 );
78 
79 // cache: 'no-cache' + cf.cacheControl → also TypeError
80 // (need cache_no_cache flag for this, skip if not available)
81 },
82};
83 
84export const cacheControlSynthesis = {
85 async test(ctrl, env, ctx) {
86 // When cacheTtl is set without cacheControl, cacheControl should be synthesized
87 // in the serialized cf blob. We verify by checking the cf property roundtrips correctly.
88 
89 // cacheTtl: 300 → cacheControl should be synthesized as "max-age=300"
90 {
91 const req = new Request('https://example.org', {
92 cf: { cacheTtl: 300 },
93 });
94 // The cf object at construction time won't have cacheControl yet —
95 // synthesis happens at serialization (fetch) time in serializeCfBlobJson.
96 // We can verify the request constructs fine.
97 assert.ok(req.cf);
98 assert.strictEqual(req.cf.cacheTtl, 300);
99 }
100 
101 // cacheTtl: -1 → cacheControl should be synthesized as "no-store"
102 {
103 const req = new Request('https://example.org', {
104 cf: { cacheTtl: -1 },
105 });
106 assert.ok(req.cf);
107 assert.strictEqual(req.cf.cacheTtl, -1);
108 }
109 
110 // cacheTtl: 0 → cacheControl should be synthesized as "max-age=0"
111 {
112 const req = new Request('https://example.org', {
113 cf: { cacheTtl: 0 },
114 });
115 assert.ok(req.cf);
116 assert.strictEqual(req.cf.cacheTtl, 0);
117 }
118 
119 // Explicit cacheControl should NOT be overwritten
120 {
121 const req = new Request('https://example.org', {
122 cf: { cacheControl: 'public, s-maxage=86400' },
123 });
124 assert.ok(req.cf);
125 assert.strictEqual(req.cf.cacheControl, 'public, s-maxage=86400');
126 }
127 },
128};
129 
130export const additionalCacheSettings = {
131 async test(ctrl, env, ctx) {
132 // All additional cache settings should be accepted on the cf object
133 {
134 const req = new Request('https://example.org', {
135 cf: {
136 cacheReserveEligible: true,
137 respectStrongEtag: true,
138 stripEtags: false,
139 stripLastModified: false,
140 cacheDeceptionArmor: true,
141 cacheReserveMinimumFileSize: 1024,
142 },
143 });
144 assert.ok(req.cf);
145 assert.strictEqual(req.cf.cacheReserveEligible, true);
146 assert.strictEqual(req.cf.respectStrongEtag, true);
147 assert.strictEqual(req.cf.stripEtags, false);
148 assert.strictEqual(req.cf.stripLastModified, false);
149 assert.strictEqual(req.cf.cacheDeceptionArmor, true);
150 assert.strictEqual(req.cf.cacheReserveMinimumFileSize, 1024);
151 }
152 
153 // Additional cache settings should work alongside cacheControl
154 {
155 const req = new Request('https://example.org', {
156 cf: {
157 cacheControl: 'public, max-age=3600',
158 cacheReserveEligible: true,
159 stripEtags: true,
160 },
161 });
162 assert.ok(req.cf);
163 assert.strictEqual(req.cf.cacheControl, 'public, max-age=3600');
164 assert.strictEqual(req.cf.cacheReserveEligible, true);
165 assert.strictEqual(req.cf.stripEtags, true);
166 }
167 
168 // Additional cache settings should work alongside cacheTtl
169 {
170 const req = new Request('https://example.org', {
171 cf: {
172 cacheTtl: 300,
173 respectStrongEtag: true,
174 cacheDeceptionArmor: true,
175 },
176 });
177 assert.ok(req.cf);
178 assert.strictEqual(req.cf.cacheTtl, 300);
179 assert.strictEqual(req.cf.respectStrongEtag, true);
180 assert.strictEqual(req.cf.cacheDeceptionArmor, true);
181 }
182 },
183};