1import { expect, it } from "@effect/vitest";
2import * as Effect from "effect/Effect";
3import * as Deferred from "effect/Deferred";
4import * as Fiber from "effect/Fiber";
5import { Deadline } from "../vendor/vite-plus-remote-cache-cloudflare/src/streams.ts";
6import * as Layer from "effect/Layer";
7import * as Logger from "effect/Logger";
8import { vi } from "vite-plus/test";
9import { Observations } from "../vendor/vite-plus-remote-cache-cloudflare/src/observations.ts";
10import * as Admission from "./Admission.ts";
11import * as Cache from "./Cache.ts";
12import * as Savings from "./Savings.ts";
13
14vi.mock("../vendor/vite-plus-remote-cache-cloudflare/src/database.ts", () => ({
15 getScope: async () => ({}),
16 selectBlob: async () => ({ blob_object: "artifact", blob_size: 4 }),
17 cleanup: async () => {},
18}));
19
20it.effect("includes successful blob downloads in exported R2 usage", () =>
21 Effect.gen(function* () {
22 const get = vi.fn(async () => ({ size: 4, body: new Blob(["data"]).stream() }));
23 const bindings = {
24 developerKeys: JSON.stringify({
25 julius: "210eb3d2c67a7207923e1999751300a87a2dd22839b258b80c3d805a6fe64984",
26 }),
27 ARTIFACTS: { get },
28 readLimit: { limit: async () => ({ success: true }) },
29 } as unknown as Cache.Bindings["Service"];
30 using observations = vi.spyOn(Observations.prototype, "toJSON");
31 const response = yield* Effect.gen(function* () {
32 const cache = yield* Cache.Cache;
33 return yield* cache.handle(
34 new Request(
35 "https://cache.example/projects/t3code/blob/12345678-1234-1234-1234-123456789abc",
36 {
37 headers: {
38 authorization: `Basic ${btoa("julius:fixture-token-012345678901234567890123456789")}`,
39 },
40 },
41 ),
42 { waitUntil: () => {} },
43 );
44 }).pipe(
45 Effect.provide(
46 Cache.layer.pipe(
47 Layer.provide([
48 Layer.succeed(Cache.Bindings, bindings),
49 Admission.layer,
50 Layer.succeed(Savings.Savings, {
51 record: () => Effect.void,
52 emitTotals: () => Effect.void,
53 cleanup: Effect.void,
54 }),
55 ]),
56 ),
57 ),
58 );
59 expect(response.status).toBe(200);
60 expect(yield* Effect.promise(() => response.text())).toBe("data");
61 expect(get).toHaveBeenCalledWith("artifact");
62 expect(observations.mock.results.at(-1)?.value.r2_operations).toBe(1);
63 }),
64);
65
66it.effect("returns 503 immediately when authenticated requests fill all read permits", () =>
67 Effect.gen(function* () {
68 const entered = yield* Deferred.make<void>();
69 const logs: unknown[] = [];
70 const get = vi.fn(() => {
71 if (get.mock.calls.length === 4) Deferred.doneUnsafe(entered, Effect.void);
72 return new Promise(() => {});
73 });
74 const bindings = {
75 developerKeys: JSON.stringify({
76 julius: "210eb3d2c67a7207923e1999751300a87a2dd22839b258b80c3d805a6fe64984",
77 }),
78 ARTIFACTS: { get },
79 readLimit: { limit: async () => ({ success: true }) },
80 } as unknown as Cache.Bindings["Service"];
81 yield* Effect.gen(function* () {
82 const cache = yield* Cache.Cache;
83 const request = () =>
84 cache.handle(
85 new Request(
86 "https://cache.example/projects/t3code/blob/12345678-1234-1234-1234-123456789abc",
87 {
88 headers: {
89 authorization: `Basic ${btoa("julius:fixture-token-012345678901234567890123456789")}`,
90 },
91 },
92 ),
93 { waitUntil: () => {} },
94 );
95 const pending = yield* Effect.all(
96 Array.from({ length: 4 }, () => request().pipe(Effect.forkChild)),
97 );
98 yield* Deferred.await(entered);
99 const response = yield* request();
100 expect(response.status).toBe(503);
101 expect(yield* Effect.promise(() => response.text())).toBe("Service unavailable");
102 expect(logs).toContainEqual(["Cache request failed", { reason: "concurrency_limit" }]);
103 expect(get).toHaveBeenCalledTimes(4);
104 yield* Effect.forEach(pending, Fiber.interrupt);
105 }).pipe(
106 Effect.provide([
107 Cache.layer.pipe(
108 Layer.provide([
109 Layer.succeed(Cache.Bindings, bindings),
110 Admission.layer,
111 Layer.succeed(Savings.Savings, {
112 record: () => Effect.void,
113 emitTotals: () => Effect.void,
114 cleanup: Effect.void,
115 }),
116 ]),
117 ),
118 Logger.layer([Logger.make(({ message }) => logs.push(message))], {
119 mergeWithExisting: false,
120 }),
121 ]),
122 );
123 }),
124);
125
126it.effect("aborts the upstream deadline before disposing it on interruption", () =>
127 Effect.gen(function* () {
128 const entered = yield* Deferred.make<void>();
129 const cancelled: boolean[] = [];
130 const originalDispose = Deadline.prototype.dispose;
131 using _dispose = vi.spyOn(Deadline.prototype, "dispose").mockImplementation(function (
132 this: Deadline,
133 ) {
134 try {
135 this.check();
136 cancelled.push(false);
137 } catch {
138 cancelled.push(true);
139 }
140 originalDispose.call(this);
141 });
142 const bindings = {
143 developerKeys: JSON.stringify({
144 julius: "210eb3d2c67a7207923e1999751300a87a2dd22839b258b80c3d805a6fe64984",
145 }),
146 ARTIFACTS: {
147 get: () => {
148 Deferred.doneUnsafe(entered, Effect.void);
149 return new Promise(() => {});
150 },
151 },
152 readLimit: { limit: async () => ({ success: true }) },
153 } as unknown as Cache.Bindings["Service"];
154 yield* Effect.gen(function* () {
155 const cache = yield* Cache.Cache;
156 const fiber = yield* cache
157 .handle(
158 new Request(
159 "https://cache.example/projects/t3code/blob/12345678-1234-1234-1234-123456789abc",
160 {
161 headers: {
162 authorization: `Basic ${btoa("julius:fixture-token-012345678901234567890123456789")}`,
163 },
164 },
165 ),
166 { waitUntil: () => {} },
167 )
168 .pipe(Effect.forkChild);
169 yield* Deferred.await(entered);
170 yield* Fiber.interrupt(fiber);
171 expect(cancelled).toEqual([true]);
172 }).pipe(
173 Effect.provide(
174 Cache.layer.pipe(
175 Layer.provide([
176 Layer.succeed(Cache.Bindings, bindings),
177 Admission.layer,
178 Layer.succeed(Savings.Savings, {
179 record: () => Effect.void,
180 emitTotals: () => Effect.void,
181 cleanup: Effect.void,
182 }),
183 ]),
184 ),
185 ),
186 );
187 }),
188);
189
190it.effect("does not charge authenticated budgets for anonymous or malformed requests", () => {
191 const limit = vi.fn(async (_options: { key: string }) => ({ success: true }));
192 return Effect.gen(function* () {
193 const cache = yield* Cache.Cache;
194 for (const operation of [
195 "fetch",
196 "store",
197 "telemetry",
198 "blob/12345678-1234-1234-1234-123456789abc",
199 ]) {
200 for (const method of ["GET", "POST"]) {
201 yield* cache.handle(
202 new Request(`https://cache.example/projects/t3code/${operation}`, { method }),
203 { waitUntil: () => {} },
204 );
205 }
206 }
207 expect(limit).not.toHaveBeenCalled();
208 const headers = {
209 authorization: `Basic ${btoa("julius:fixture-token-012345678901234567890123456789")}`,
210 };
211 yield* cache.handle(
212 new Request("https://cache.example/projects/t3code/telemetry", {
213 method: "POST",
214 headers,
215 body: "{}",
216 }),
217 { waitUntil: () => {} },
218 );
219 yield* cache.handle(
220 new Request(
221 "https://cache.example/projects/t3code/blob/12345678-1234-1234-1234-123456789abc",
222 { headers },
223 ),
224 { waitUntil: () => {} },
225 );
226 expect(limit.mock.calls).toEqual([[{ key: "t3code:telemetry" }], [{ key: "t3code:blob" }]]);
227 }).pipe(
228 Effect.provide(
229 Cache.layer.pipe(
230 Layer.provide([
231 Layer.succeed(Cache.Bindings, {
232 developerKeys: JSON.stringify({
233 julius: "210eb3d2c67a7207923e1999751300a87a2dd22839b258b80c3d805a6fe64984",
234 }),
235 readerKeys: "{}",
236 readLimit: { limit },
237 storeLimit: { limit },
238 ARTIFACTS: { get: async () => ({ size: 4, body: new Blob(["data"]).stream() }) },
239 } as unknown as Cache.Bindings["Service"]),
240 Admission.layer,
241 Layer.succeed(Savings.Savings, {
242 record: () => Effect.void,
243 emitTotals: () => Effect.void,
244 cleanup: Effect.void,
245 }),
246 ]),
247 ),
248 ),
249 );
250});
251
252it.effect("authenticates setup probes without recording empty savings", () => {
253 const record = vi.fn(() => Effect.void);
254 const emitTotals = vi.fn(() => Effect.void);
255 return Effect.gen(function* () {
256 const cache = yield* Cache.Cache;
257 const response = yield* cache.handle(
258 new Request("https://cache.example/projects/t3code/telemetry", {
259 method: "POST",
260 headers: {
261 authorization: `Basic ${btoa("julius:fixture-token-012345678901234567890123456789")}`,
262 },
263 body: JSON.stringify({
264 id: "a".repeat(64),
265 recordedAt: 0,
266 host: "setup-host",
267 platform: "linux",
268 arch: "x64",
269 ci: false,
270 runId: "setup",
271 tasks: [],
272 }),
273 }),
274 { waitUntil: () => {} },
275 );
276 expect(response.status).toBe(204);
277 expect(record).not.toHaveBeenCalled();
278 expect(emitTotals).not.toHaveBeenCalled();
279 }).pipe(
280 Effect.provide(
281 Cache.layer.pipe(
282 Layer.provide([
283 Layer.succeed(Cache.Bindings, {
284 developerKeys: JSON.stringify({
285 julius: "210eb3d2c67a7207923e1999751300a87a2dd22839b258b80c3d805a6fe64984",
286 }),
287 readLimit: { limit: async () => ({ success: true }) },
288 } as unknown as Cache.Bindings["Service"]),
289 Admission.layer,
290 Layer.succeed(Savings.Savings, { record, emitTotals, cleanup: Effect.void }),
291 ]),
292 ),
293 ),
294 );
295});
296
297it.effect("keeps artifacts private and limits CI reader credentials to reads", () =>
298 Effect.gen(function* () {
299 const cache = yield* Cache.Cache;
300 const context = { waitUntil: () => {} };
301 const blob = "https://cache.example/projects/t3code/blob/12345678-1234-1234-1234-123456789abc";
302 for (const url of [blob, "https://cache.example/projects/t3code/fetch"]) {
303 const method = url === blob ? "GET" : "POST";
304 expect((yield* cache.handle(new Request(url, { method }), context)).status).toBe(401);
305 expect(
306 (yield* cache.handle(
307 new Request(url, {
308 method,
309 headers: { authorization: `Basic ${btoa("ci-reader:wrong")}` },
310 }),
311 context,
312 )).status,
313 ).toBe(403);
314 }
315 const headers = {
316 authorization: `Basic ${btoa("ci-reader:fixture-token-012345678901234567890123456789")}`,
317 };
318 expect((yield* cache.handle(new Request(blob, { headers }), context)).status).toBe(200);
319 expect(
320 (yield* cache.handle(
321 new Request("https://cache.example/projects/t3code/store", { method: "POST", headers }),
322 context,
323 )).status,
324 ).toBe(403);
325 }).pipe(
326 Effect.provide(
327 Cache.layer.pipe(
328 Layer.provide([
329 Layer.succeed(Cache.Bindings, {
330 developerKeys: "{}",
331 readerKeys: JSON.stringify({
332 "ci-reader": "210eb3d2c67a7207923e1999751300a87a2dd22839b258b80c3d805a6fe64984",
333 }),
334 ARTIFACTS: { get: vi.fn(async () => ({ size: 4, body: new Blob(["data"]).stream() })) },
335 readLimit: { limit: async () => ({ success: true }) },
336 storeLimit: { limit: async () => ({ success: true }) },
337 } as unknown as Cache.Bindings["Service"]),
338 Admission.layer,
339 Layer.succeed(Savings.Savings, {
340 record: () => Effect.void,
341 emitTotals: () => Effect.void,
342 cleanup: Effect.void,
343 }),
344 ]),
345 ),
346 ),
347 ),
348);
349
350it.effect("rejects anonymous and invalid Basic requests before admission", () =>
351 Effect.gen(function* () {
352 const cache = yield* Cache.Cache;
353 for (const operation of ["store", "telemetry", "fetch"]) {
354 for (const authorization of [undefined, `Basic ${btoa("attacker:invalid")}`]) {
355 const response = yield* cache.handle(
356 new Request(`https://cache.example/projects/t3code/${operation}`, {
357 method: "POST",
358 headers: authorization ? { authorization } : {},
359 }),
360 { waitUntil: () => {} },
361 );
362 expect(response.status).toBe(authorization ? 403 : 401);
363 }
364 }
365 }).pipe(
366 Effect.provide(
367 Cache.layer.pipe(
368 Layer.provide([
369 Layer.succeed(Cache.Bindings, {
370 developerKeys: "{}",
371 readerKeys: "{}",
372 } as Cache.Bindings["Service"]),
373 Layer.succeed(Admission.Admission, {
374 withPermit: () => Effect.die("Unauthenticated request reached admission"),
375 }),
376 Layer.succeed(Savings.Savings, {
377 record: () => Effect.void,
378 emitTotals: () => Effect.void,
379 cleanup: Effect.void,
380 }),
381 ]),
382 ),
383 ),
384 ),
385);