1import * as Cloudflare from "@/Cloudflare/index.ts";
2import * as Test from "@/Test/Alchemy";
3import { expect } from "alchemy-test";
4import * as Effect from "effect/Effect";
5import { MinimumLogLevel } from "effect/References";
6import * as pathe from "pathe";
7import PresignRemoteWorker, {
8 PresignRemoteBucket,
9} from "./fixtures/presign/remote-worker.ts";
10import { presignRoundTrip } from "./fixtures/presign/roundtrip.ts";
11
12const { test } = Test.make({
13 providers: Cloudflare.providers(),
14});
15
16const logLevel = Effect.provideService(
17 MinimumLogLevel,
18 process.env.DEBUG ? "Debug" : "Info",
19);
20
21/**
22 * Deployed: the Worker mints presigned URLs with S3 credentials derived from
23 * a scoped API token, and an unauthenticated client uploads/downloads
24 * directly against R2's S3 endpoint.
25 */
26test.provider(
27 "deployed Worker presigns PUT and GET URLs for R2",
28 (stack) =>
29 Effect.gen(function* () {
30 yield* stack.destroy();
31
32 const deployed = yield* stack.deploy(
33 Effect.gen(function* () {
34 const bucket = yield* PresignRemoteBucket;
35 const worker = yield* PresignRemoteWorker;
36 return { bucket, worker };
37 }),
38 );
39
40 const { putUrl, getUrl } = yield* presignRoundTrip(
41 deployed.worker.url!,
42 "uploads/deployed file.txt",
43 );
44 for (const url of [putUrl, getUrl]) {
45 const parsed = new URL(url);
46 expect(parsed.hostname).toMatch(/\.r2\.cloudflarestorage\.com$/);
47 expect(
48 parsed.pathname.startsWith(`/${deployed.bucket.bucketName}/`),
49 ).toBe(true);
50 }
51
52 yield* stack.destroy();
53 }).pipe(logLevel),
54 {
55 tags: [
56 "provider:cloudflare",
57 "provider:cloudflare:r2",
58 "provider:cloudflare:worker",
59 ],
60 timeout: 180_000,
61 },
62);
63
64/**
65 * Deployed async Worker: `Cloudflare.R2.S3Credentials` injects token-derived
66 * credentials as a secret, and `aws4fetch` presigns against R2.
67 */
68test.provider(
69 "deployed async Worker presigns with S3Credentials",
70 (stack) =>
71 Effect.gen(function* () {
72 yield* stack.destroy();
73
74 const deployed = yield* stack.deploy(
75 Effect.gen(function* () {
76 const bucket = yield* Cloudflare.R2.Bucket("PresignAsyncBucket", {
77 forceDestroy: true,
78 });
79 const worker = yield* Cloudflare.Worker("PresignAsyncWorker", {
80 main: pathe.resolve(
81 import.meta.dirname,
82 "fixtures/presign/async-worker.ts",
83 ),
84 env: {
85 BUCKET: bucket,
86 BUCKET_S3: Cloudflare.R2.S3Credentials(bucket, {
87 access: "read-write",
88 }),
89 },
90 });
91 return { bucket, worker };
92 }),
93 );
94
95 const { putUrl } = yield* presignRoundTrip(
96 deployed.worker.url!,
97 "uploads/async deployed.txt",
98 );
99 expect(new URL(putUrl).hostname).toMatch(/\.r2\.cloudflarestorage\.com$/);
100
101 yield* stack.destroy();
102 }).pipe(logLevel),
103 {
104 tags: [
105 "provider:cloudflare",
106 "provider:cloudflare:r2",
107 "provider:cloudflare:worker",
108 ],
109 timeout: 180_000,
110 },
111);