summaryrefslogtreecommitdiff
path: root/packages/integrations/cloudflare/test
diff options
context:
space:
mode:
authorGravatar Matt Kane <m@mk.gg> 2025-03-31 10:05:44 +0100
committerGravatar GitHub <noreply@github.com> 2025-03-31 10:05:44 +0100
commita9aafec47a4d8a92c826663dca2f9850643651ec (patch)
treed77835a9b422ab431fc8fee2ec6f80efae639472 /packages/integrations/cloudflare/test
parent19bd710ad5dd993628626ebd37f75d4d339b08a9 (diff)
downloadastro-a9aafec47a4d8a92c826663dca2f9850643651ec.tar.gz
astro-a9aafec47a4d8a92c826663dca2f9850643651ec.tar.zst
astro-a9aafec47a4d8a92c826663dca2f9850643651ec.zip
feat(cloudflare): add KV session storage support (#13514)
* feat(cloudflare): add KV session storage support * Change code block language to JSONC * Comments * Use user-defined binding name * Use createCodegenDir * Remove unused import
Diffstat (limited to '')
-rw-r--r--packages/integrations/cloudflare/test/fixtures/astro-dev-platform/package.json2
-rw-r--r--packages/integrations/cloudflare/test/fixtures/astro-env/package.json2
-rw-r--r--packages/integrations/cloudflare/test/fixtures/sessions/astro.config.mjs15
-rw-r--r--packages/integrations/cloudflare/test/fixtures/sessions/package.json17
-rw-r--r--packages/integrations/cloudflare/test/fixtures/sessions/src/actions/index.ts36
-rw-r--r--packages/integrations/cloudflare/test/fixtures/sessions/src/middleware.ts49
-rw-r--r--packages/integrations/cloudflare/test/fixtures/sessions/src/pages/api.ts13
-rw-r--r--packages/integrations/cloudflare/test/fixtures/sessions/src/pages/cart.astro24
-rw-r--r--packages/integrations/cloudflare/test/fixtures/sessions/src/pages/destroy.ts6
-rw-r--r--packages/integrations/cloudflare/test/fixtures/sessions/src/pages/index.astro13
-rw-r--r--packages/integrations/cloudflare/test/fixtures/sessions/src/pages/regenerate.ts6
-rw-r--r--packages/integrations/cloudflare/test/fixtures/sessions/src/pages/update.ts10
-rw-r--r--packages/integrations/cloudflare/test/fixtures/sessions/wrangler.json22
-rw-r--r--packages/integrations/cloudflare/test/sessions.test.js93
14 files changed, 306 insertions, 2 deletions
diff --git a/packages/integrations/cloudflare/test/fixtures/astro-dev-platform/package.json b/packages/integrations/cloudflare/test/fixtures/astro-dev-platform/package.json
index a406e7c61..a39e254fa 100644
--- a/packages/integrations/cloudflare/test/fixtures/astro-dev-platform/package.json
+++ b/packages/integrations/cloudflare/test/fixtures/astro-dev-platform/package.json
@@ -7,6 +7,6 @@
"astro": "workspace:*"
},
"devDependencies": {
- "wrangler": "^3.112.0"
+ "wrangler": "^4.5.1"
}
}
diff --git a/packages/integrations/cloudflare/test/fixtures/astro-env/package.json b/packages/integrations/cloudflare/test/fixtures/astro-env/package.json
index d3b910161..4a9fd822b 100644
--- a/packages/integrations/cloudflare/test/fixtures/astro-env/package.json
+++ b/packages/integrations/cloudflare/test/fixtures/astro-env/package.json
@@ -7,6 +7,6 @@
"astro": "workspace:*"
},
"devDependencies": {
- "wrangler": "^4.4.1"
+ "wrangler": "^4.5.1"
}
}
diff --git a/packages/integrations/cloudflare/test/fixtures/sessions/astro.config.mjs b/packages/integrations/cloudflare/test/fixtures/sessions/astro.config.mjs
new file mode 100644
index 000000000..7d37e9762
--- /dev/null
+++ b/packages/integrations/cloudflare/test/fixtures/sessions/astro.config.mjs
@@ -0,0 +1,15 @@
+// @ts-check
+import { defineConfig } from 'astro/config';
+import cloudflare from '@astrojs/cloudflare';
+export default defineConfig({
+ output: 'server',
+ site: `http://example.com`,
+ adapter: cloudflare({
+ platformProxy: {
+ enabled: true,
+ },
+ }),
+ experimental: {
+ session: true,
+ }
+});
diff --git a/packages/integrations/cloudflare/test/fixtures/sessions/package.json b/packages/integrations/cloudflare/test/fixtures/sessions/package.json
new file mode 100644
index 000000000..d9a6f3af8
--- /dev/null
+++ b/packages/integrations/cloudflare/test/fixtures/sessions/package.json
@@ -0,0 +1,17 @@
+{
+ "name": "@test/astro-cloudflare-sessions",
+ "version": "0.0.0",
+ "private": true,
+ "dependencies": {
+ "@astrojs/cloudflare": "workspace:*"
+ },
+ "devDependencies": {
+ "astro": "workspace:*",
+ "wrangler": "^4.5.1"
+ },
+ "scripts": {
+ "build": "astro build",
+ "preview": "astro build && wrangler dev",
+ "start": "astro dev"
+ }
+}
diff --git a/packages/integrations/cloudflare/test/fixtures/sessions/src/actions/index.ts b/packages/integrations/cloudflare/test/fixtures/sessions/src/actions/index.ts
new file mode 100644
index 000000000..856f68ba8
--- /dev/null
+++ b/packages/integrations/cloudflare/test/fixtures/sessions/src/actions/index.ts
@@ -0,0 +1,36 @@
+import { defineAction } from 'astro:actions';
+import { z } from 'astro:schema';
+
+export const server = {
+ addToCart: defineAction({
+ accept: 'form',
+ input: z.object({ productId: z.string() }),
+ handler: async (input, context) => {
+ const cart: Array<string> = (await context.session.get('cart')) || [];
+ cart.push(input.productId);
+ await context.session.set('cart', cart);
+ return { cart, message: 'Product added to cart at ' + new Date().toTimeString() };
+ },
+ }),
+ getCart: defineAction({
+ handler: async (input, context) => {
+ return await context.session.get('cart');
+ },
+ }),
+ clearCart: defineAction({
+ accept: 'json',
+ handler: async (input, context) => {
+ await context.session.set('cart', []);
+ return { cart: [], message: 'Cart cleared at ' + new Date().toTimeString() };
+ },
+ }),
+ addUrl: defineAction({
+ input: z.object({ favoriteUrl: z.string().url() }),
+ handler: async (input, context) => {
+ const previousFavoriteUrl = await context.session.get<URL>('favoriteUrl');
+ const url = new URL(input.favoriteUrl);
+ context.session.set('favoriteUrl', url);
+ return { message: 'Favorite URL set to ' + url.href + ' from ' + (previousFavoriteUrl?.href ?? "nothing") };
+ }
+ })
+}
diff --git a/packages/integrations/cloudflare/test/fixtures/sessions/src/middleware.ts b/packages/integrations/cloudflare/test/fixtures/sessions/src/middleware.ts
new file mode 100644
index 000000000..7f56f11f3
--- /dev/null
+++ b/packages/integrations/cloudflare/test/fixtures/sessions/src/middleware.ts
@@ -0,0 +1,49 @@
+import { defineMiddleware } from 'astro:middleware';
+import { getActionContext } from 'astro:actions';
+
+const ACTION_SESSION_KEY = 'actionResult'
+
+export const onRequest = defineMiddleware(async (context, next) => {
+ // Skip requests for prerendered pages
+ if (context.isPrerendered) return next();
+
+ const { action, setActionResult, serializeActionResult } =
+ getActionContext(context);
+
+ console.log(action?.name)
+
+ const actionPayload = await context.session.get(ACTION_SESSION_KEY);
+
+ if (actionPayload) {
+ setActionResult(actionPayload.actionName, actionPayload.actionResult);
+ context.session.delete(ACTION_SESSION_KEY);
+ return next();
+ }
+
+ // If an action was called from an HTML form action,
+ // call the action handler and redirect to the destination page
+ if (action?.calledFrom === "form") {
+ const actionResult = await action.handler();
+
+ context.session.set(ACTION_SESSION_KEY, {
+ actionName: action.name,
+ actionResult: serializeActionResult(actionResult),
+ });
+
+
+ // Redirect back to the previous page on error
+ if (actionResult.error) {
+ const referer = context.request.headers.get("Referer");
+ if (!referer) {
+ throw new Error(
+ "Internal: Referer unexpectedly missing from Action POST request.",
+ );
+ }
+ return context.redirect(referer);
+ }
+ // Redirect to the destination page on success
+ return context.redirect(context.originPathname);
+ }
+
+ return next();
+});
diff --git a/packages/integrations/cloudflare/test/fixtures/sessions/src/pages/api.ts b/packages/integrations/cloudflare/test/fixtures/sessions/src/pages/api.ts
new file mode 100644
index 000000000..21793c78a
--- /dev/null
+++ b/packages/integrations/cloudflare/test/fixtures/sessions/src/pages/api.ts
@@ -0,0 +1,13 @@
+import type { APIRoute } from 'astro';
+
+export const GET: APIRoute = async (context) => {
+ const url = new URL(context.url, 'http://localhost');
+ let value = url.searchParams.get('set');
+ if (value) {
+ context.session.set('value', value);
+ } else {
+ value = await context.session.get('value');
+ }
+ const cart = await context.session.get('cart');
+ return Response.json({ value, cart });
+};
diff --git a/packages/integrations/cloudflare/test/fixtures/sessions/src/pages/cart.astro b/packages/integrations/cloudflare/test/fixtures/sessions/src/pages/cart.astro
new file mode 100644
index 000000000..e69a9e5e1
--- /dev/null
+++ b/packages/integrations/cloudflare/test/fixtures/sessions/src/pages/cart.astro
@@ -0,0 +1,24 @@
+---
+import { actions } from "astro:actions";
+
+const result = Astro.getActionResult(actions.addToCart);
+
+const cart = result?.data?.cart ?? await Astro.session.get('cart');
+const message = result?.data?.message ?? 'Add something to your cart!';
+---
+<p>Cart: <span id="cart">{JSON.stringify(cart)}</span></p>
+<p id="message">{message}</p>
+<form action={actions.addToCart} method="POST">
+ <input type="text" name="productId" value="shoe" />
+ <button type="submit">Add to Cart</button>
+</form>
+<input type="button" value="Clear Cart" id="clearCart" />
+<script>
+ import { actions } from "astro:actions";
+ async function clearCart() {
+ const result = await actions.clearCart({});
+ document.getElementById('cart').textContent = JSON.stringify(result.data.cart);
+ document.getElementById('message').textContent = result.data.message;
+ }
+ document.getElementById('clearCart').addEventListener('click', clearCart);
+</script>
diff --git a/packages/integrations/cloudflare/test/fixtures/sessions/src/pages/destroy.ts b/packages/integrations/cloudflare/test/fixtures/sessions/src/pages/destroy.ts
new file mode 100644
index 000000000..e83f6e4b6
--- /dev/null
+++ b/packages/integrations/cloudflare/test/fixtures/sessions/src/pages/destroy.ts
@@ -0,0 +1,6 @@
+import type { APIRoute } from 'astro';
+
+export const GET: APIRoute = async (context) => {
+ await context.session.destroy();
+ return Response.json({});
+};
diff --git a/packages/integrations/cloudflare/test/fixtures/sessions/src/pages/index.astro b/packages/integrations/cloudflare/test/fixtures/sessions/src/pages/index.astro
new file mode 100644
index 000000000..30d6a1618
--- /dev/null
+++ b/packages/integrations/cloudflare/test/fixtures/sessions/src/pages/index.astro
@@ -0,0 +1,13 @@
+---
+const value = await Astro.session.get('value');
+---
+<html lang="en">
+<head>
+ <meta charset="utf-8" />
+ <title>Hi</title>
+</head>
+
+<h1>Hi</h1>
+<p>{value}</p>
+<a href="/cart" style="font-size: 36px">🛒</a>
+</html>
diff --git a/packages/integrations/cloudflare/test/fixtures/sessions/src/pages/regenerate.ts b/packages/integrations/cloudflare/test/fixtures/sessions/src/pages/regenerate.ts
new file mode 100644
index 000000000..6f2240588
--- /dev/null
+++ b/packages/integrations/cloudflare/test/fixtures/sessions/src/pages/regenerate.ts
@@ -0,0 +1,6 @@
+import type { APIRoute } from 'astro';
+
+export const GET: APIRoute = async (context) => {
+ await context.session.regenerate();
+ return Response.json({});
+};
diff --git a/packages/integrations/cloudflare/test/fixtures/sessions/src/pages/update.ts b/packages/integrations/cloudflare/test/fixtures/sessions/src/pages/update.ts
new file mode 100644
index 000000000..71b058e75
--- /dev/null
+++ b/packages/integrations/cloudflare/test/fixtures/sessions/src/pages/update.ts
@@ -0,0 +1,10 @@
+import type { APIRoute } from 'astro';
+
+export const GET: APIRoute = async (context) => {
+ const previousObject = await context.session.get("key") ?? { value: "none" };
+ const previousValue = previousObject.value;
+ const sessionData = { value: "expected" };
+ context.session.set("key", sessionData);
+ sessionData.value = "unexpected";
+ return Response.json({previousValue});
+};
diff --git a/packages/integrations/cloudflare/test/fixtures/sessions/wrangler.json b/packages/integrations/cloudflare/test/fixtures/sessions/wrangler.json
new file mode 100644
index 000000000..479d56c32
--- /dev/null
+++ b/packages/integrations/cloudflare/test/fixtures/sessions/wrangler.json
@@ -0,0 +1,22 @@
+{
+ "name": "astro-cf-session",
+ "compatibility_date": "2024-11-01",
+ "compatibility_flags": [
+ "nodejs_compat"
+ ],
+ "main": "./dist/_worker.js/index.js",
+ "assets": {
+ "directory": "./dist",
+ "binding": "ASSETS"
+ },
+ "observability": {
+ "enabled": true
+ },
+ "kv_namespaces": [
+ {
+ "binding": "SESSION",
+ "id": "<SESSION_ID>"
+ }
+ ],
+ "upload_source_maps": true
+}
diff --git a/packages/integrations/cloudflare/test/sessions.test.js b/packages/integrations/cloudflare/test/sessions.test.js
new file mode 100644
index 000000000..ab0ddf466
--- /dev/null
+++ b/packages/integrations/cloudflare/test/sessions.test.js
@@ -0,0 +1,93 @@
+import assert from 'node:assert/strict';
+import { after, before, describe, it } from 'node:test';
+import * as devalue from 'devalue';
+import { fileURLToPath } from 'node:url';
+import { astroCli, wranglerCli } from './_test-utils.js';
+
+const root = new URL('./fixtures/sessions/', import.meta.url);
+
+describe('astro:env', () => {
+ let wrangler;
+
+ before(async () => {
+ await astroCli(fileURLToPath(root), 'build');
+
+ wrangler = wranglerCli(fileURLToPath(root));
+ await new Promise((resolve) => {
+ wrangler.stdout.on('data', (data) => {
+ // console.log('[stdout]', data.toString());
+ if (data.toString().includes('http://127.0.0.1:8788')) resolve();
+ });
+ wrangler.stderr.on('data', (_data) => {
+ // console.log('[stderr]', _data.toString());
+ });
+ });
+ });
+
+ after(() => {
+ wrangler.kill();
+ });
+
+ it('can regenerate session cookies upon request', async () => {
+ const firstResponse = await fetch('http://127.0.0.1:8788/regenerate', { method: 'GET' });
+ const firstHeaders = firstResponse.headers.get('set-cookie').split(',');
+ const firstSessionId = firstHeaders[0].split(';')[0].split('=')[1];
+
+ const secondResponse = await fetch('http://127.0.0.1:8788/regenerate', {
+ method: 'GET',
+ headers: {
+ cookie: `astro-session=${firstSessionId}`,
+ },
+ });
+ const secondHeaders = secondResponse.headers.get('set-cookie').split(',');
+ const secondSessionId = secondHeaders[0].split(';')[0].split('=')[1];
+ assert.notEqual(firstSessionId, secondSessionId);
+ });
+
+ it('can save session data by value', async () => {
+ const firstResponse = await fetch('http://127.0.0.1:8788/update', { method: 'GET' });
+ const firstValue = await firstResponse.json();
+ assert.equal(firstValue.previousValue, 'none');
+
+ const firstHeaders = firstResponse.headers.get('set-cookie').split(',');
+ const firstSessionId = firstHeaders[0].split(';')[0].split('=')[1];
+ const secondResponse = await fetch('http://127.0.0.1:8788/update', {
+ method: 'GET',
+ headers: {
+ cookie: `astro-session=${firstSessionId}`,
+ },
+ });
+ const secondValue = await secondResponse.json();
+ assert.equal(secondValue.previousValue, 'expected');
+ });
+
+ it('can save and restore URLs in session data', async () => {
+ const firstResponse = await fetch('http://127.0.0.1:8788/_actions/addUrl', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({ favoriteUrl: 'https://domain.invalid' }),
+ });
+
+ assert.equal(firstResponse.ok, true);
+ const firstHeaders = firstResponse.headers.get('set-cookie').split(',');
+ const firstSessionId = firstHeaders[0].split(';')[0].split('=')[1];
+
+ const data = devalue.parse(await firstResponse.text());
+ assert.equal(data.message, 'Favorite URL set to https://domain.invalid/ from nothing');
+ const secondResponse = await fetch('http://127.0.0.1:8788/_actions/addUrl', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ cookie: `astro-session=${firstSessionId}`,
+ },
+ body: JSON.stringify({ favoriteUrl: 'https://example.com' }),
+ });
+ const secondData = devalue.parse(await secondResponse.text());
+ assert.equal(
+ secondData.message,
+ 'Favorite URL set to https://example.com/ from https://domain.invalid/',
+ );
+ });
+});