Skip to content

Commit 60dc4b8

Browse files
authored
fix(cloudflare): make local workers tests pass (#1598)
* fix(cloudflare): make local workers tests pass * fix(cloudflare): address review comments * fix(ci): pin actions and dedupe limits * fix(backend): restore device set channel rate limiting * fix(triggers): skip missing org for queued app_create * fix(tests): avoid device set same-channel limiter
1 parent d6a5603 commit 60dc4b8

File tree

19 files changed

+636
-553
lines changed

19 files changed

+636
-553
lines changed

.github/workflows/tests.yml

Lines changed: 24 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,18 @@ jobs:
1717
name: Run tests
1818
steps:
1919
- name: Cache Deno dependencies
20-
uses: actions/cache@v4
20+
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
2121
with:
2222
path: ${{ env.DENO_DIR }}
2323
key: my_cache_key
2424
- name: Checkout capgo
25-
uses: actions/checkout@v6
25+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
2626
- name: Setup bun
27-
uses: oven-sh/setup-bun@v2
27+
uses: oven-sh/setup-bun@3d267786b128fe76c2f16a390aa2448b815359f3 # v2
2828
with:
2929
bun-version: latest
3030
- name: Check for typos
31-
uses: crate-ci/[email protected]
31+
uses: crate-ci/typos@06d010dfe4c84fdab1a25ea02b57b3585018ba80 # v1.42.3
3232
- name: Show bun version
3333
run: bun --version
3434
- name: Show capgo version
@@ -53,7 +53,7 @@ jobs:
5353
# - name: Lint I18n
5454
# run: bunx @inlang/cli lint --project project.inlang
5555
- name: Install Supabase CLI
56-
uses: supabase/setup-cli@v1
56+
uses: supabase/setup-cli@b60b5899c73b63a2d2d651b1e90db8d4c9392f51 # v1
5757
with:
5858
version: latest
5959
- name: Show Supabase CLI version
@@ -64,7 +64,7 @@ jobs:
6464
run: supabase test db
6565
- name: Lint SQL
6666
run: supabase db lint -s public --fail-on warning
67-
- uses: JarvusInnovations/background-action@v1
67+
- uses: JarvusInnovations/background-action@2428e7b970a846423095c79d43f759abf979a635 # v1
6868
name: Bootstrap Edge server
6969
with:
7070
run: supabase functions serve &
@@ -89,39 +89,23 @@ jobs:
8989
working-directory: .
9090
- name: Run all backend and CLI tests
9191
run: bun run test:all
92-
# TODO: enable these tests when they are stable
93-
# - uses: JarvusInnovations/background-action@v1
94-
# name: Start Cloudflare Workers for testing
95-
# with:
96-
# run: |
97-
# chmod +x scripts/start-cloudflare-workers.sh
98-
# ./scripts/start-cloudflare-workers.sh
99-
# wait-on: |
100-
# http-get://127.0.0.1:8790/ok
101-
# http-get://127.0.0.1:8787/ok
102-
# http-get://127.0.0.1:8788/ok
103-
# http-get://127.0.0.1:8789/ok
104-
# tail: stderr,stdout
105-
# log-output-resume: stderr,stdout
106-
# wait-for: 2m
107-
# log-output: stderr,stdout
108-
# log-output-if: true
109-
# working-directory: .
110-
# - name: Verify D1 sync is ready
111-
# run: |
112-
# echo "Verifying D1 sync worker is operational..."
113-
# for i in {1..30}; do
114-
# response=$(curl -s -w "%{http_code}" -X POST http://127.0.0.1:8790/sync -H "x-webhook-signature: testsecret" -o /dev/null)
115-
# if [ "$response" = "200" ]; then
116-
# echo "✓ D1 sync is ready"
117-
# exit 0
118-
# fi
119-
# echo "Waiting for D1 sync... ($i/30) - HTTP $response"
120-
# sleep 2
121-
# done
122-
# echo "✗ D1 sync failed to become ready"
123-
# exit 1
124-
# - name: Run Cloudflare Workers plugin tests
125-
# run: bun run test:cloudflare:plugin
92+
- uses: JarvusInnovations/background-action@2428e7b970a846423095c79d43f759abf979a635 # v1
93+
name: Start Cloudflare Workers for testing
94+
with:
95+
run: |
96+
chmod +x scripts/start-cloudflare-workers.sh
97+
./scripts/start-cloudflare-workers.sh
98+
wait-on: |
99+
http-get://127.0.0.1:8787/ok
100+
http-get://127.0.0.1:8788/ok
101+
http-get://127.0.0.1:8789/ok
102+
tail: stderr,stdout
103+
log-output-resume: stderr,stdout
104+
wait-for: 2m
105+
log-output: stderr,stdout
106+
log-output-if: true
107+
working-directory: .
108+
- name: Run Cloudflare Workers backend tests
109+
run: bun run test:cloudflare:backend
126110
# - name: Run playwright tests
127111
# run: bun run test:front

CLOUDFLARE_TESTING.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ The application has three Cloudflare Workers:
3030

3131
1. Supabase must be running: `supabase start`
3232
2. Database must be seeded: `supabase db reset`
33-
3. Environment variables must be configured in `internal/cloudflare/.env.local`
33+
3. Environment variables must be configured in `cloudflare_workers/.env.local`
34+
Note: `./scripts/start-cloudflare-workers.sh` overrides the Supabase keys at runtime using `supabase status` to match your local instance, and also sets Cloudflare-local defaults like `CLOUDFLARE_FUNCTION_URL`.
3435
4. (Optional) For V2/D1 testing: Local D1 database must be synced (see V2/D1
3536
Testing section)
3637

@@ -112,7 +113,7 @@ correct worker based on the endpoint path.
112113

113114
1. **Port Configuration**: Cloudflare Workers run on different ports (8787,
114115
8788, 8789)
115-
2. **Environment Loading**: Uses `internal/cloudflare/.env.local` instead of
116+
2. **Environment Loading**: Uses `cloudflare_workers/.env.local` instead of
116117
Supabase secrets
117118
3. **Runtime**: Uses Cloudflare Workers runtime instead of Deno
118119
4. **Worker Separation**: API and Plugin endpoints are handled by separate

cloudflare_workers/api/index.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { env } from 'node:process'
2+
import { app as accept_invitation } from '../../supabase/functions/_backend/private/accept_invitation.ts'
23
import { app as admin_credits } from '../../supabase/functions/_backend/private/admin_credits.ts'
34
import { app as admin_stats } from '../../supabase/functions/_backend/private/admin_stats.ts'
45
import { app as channel_stats } from '../../supabase/functions/_backend/private/channel_stats.ts'
@@ -9,13 +10,17 @@ import { app as deleted_failed_version } from '../../supabase/functions/_backend
910
import { app as devices_priv } from '../../supabase/functions/_backend/private/devices.ts'
1011
import { app as events } from '../../supabase/functions/_backend/private/events.ts'
1112
import { app as groups } from '../../supabase/functions/_backend/private/groups.ts'
13+
import { app as invite_new_user_to_org } from '../../supabase/functions/_backend/private/invite_new_user_to_org.ts'
14+
import { app as latency } from '../../supabase/functions/_backend/private/latency.ts'
1215
import { app as log_as } from '../../supabase/functions/_backend/private/log_as.ts'
1316
import { app as plans } from '../../supabase/functions/_backend/private/plans.ts'
1417
import { app as publicStats } from '../../supabase/functions/_backend/private/public_stats.ts'
18+
import { app as set_org_email } from '../../supabase/functions/_backend/private/set_org_email.ts'
1519
import { app as stats_priv } from '../../supabase/functions/_backend/private/stats.ts'
1620
import { app as storeTop } from '../../supabase/functions/_backend/private/store_top.ts'
1721
import { app as stripe_checkout } from '../../supabase/functions/_backend/private/stripe_checkout.ts'
1822
import { app as stripe_portal } from '../../supabase/functions/_backend/private/stripe_portal.ts'
23+
import { app as validate_password_compliance } from '../../supabase/functions/_backend/private/validate_password_compliance.ts'
1924
import { app as verify_email_otp } from '../../supabase/functions/_backend/private/verify_email_otp.ts'
2025
import { app as apikey } from '../../supabase/functions/_backend/public/apikey/index.ts'
2126
import { app as appEndpoint } from '../../supabase/functions/_backend/public/app/index.ts'
@@ -27,6 +32,7 @@ import { app as ok } from '../../supabase/functions/_backend/public/ok.ts'
2732
import { app as organization } from '../../supabase/functions/_backend/public/organization/index.ts'
2833
import { app as replication } from '../../supabase/functions/_backend/public/replication.ts'
2934
import { app as statistics } from '../../supabase/functions/_backend/public/statistics/index.ts'
35+
import { app as webhooks } from '../../supabase/functions/_backend/public/webhooks/index.ts'
3036
import { app as cron_clean_orphan_images } from '../../supabase/functions/_backend/triggers/cron_clean_orphan_images.ts'
3137
import { app as cron_clear_versions } from '../../supabase/functions/_backend/triggers/cron_clear_versions.ts'
3238
import { app as cron_email } from '../../supabase/functions/_backend/triggers/cron_email.ts'
@@ -60,6 +66,7 @@ app.route('/channel', channel)
6066
app.route('/device', device)
6167
app.route('/organization', organization)
6268
app.route('/statistics', statistics)
69+
app.route('/webhooks', webhooks)
6370
app.route('/app', appEndpoint)
6471
app.route('/build', build)
6572
app.route('/replication', replication)
@@ -72,8 +79,12 @@ appPrivate.route('/credits', credits)
7279
appPrivate.route('/store_top', storeTop)
7380
appPrivate.route('/website_stats', publicStats)
7481
appPrivate.route('/config', config)
82+
appPrivate.route('/accept_invitation', accept_invitation)
7583
appPrivate.route('/devices', devices_priv)
7684
appPrivate.route('/log_as', log_as)
85+
appPrivate.route('/invite_new_user_to_org', invite_new_user_to_org)
86+
appPrivate.route('/set_org_email', set_org_email)
87+
appPrivate.route('/validate_password_compliance', validate_password_compliance)
7788
appPrivate.route('/admin_credits', admin_credits)
7889
appPrivate.route('/admin_stats', admin_stats)
7990
appPrivate.route('/stats', stats_priv)
@@ -83,12 +94,14 @@ appPrivate.route('/stripe_portal', stripe_portal)
8394
appPrivate.route('/verify_email_otp', verify_email_otp)
8495
appPrivate.route('/delete_failed_version', deleted_failed_version)
8596
appPrivate.route('/create_device', create_device)
97+
appPrivate.route('/latency', latency)
8698
appPrivate.route('/events', events)
8799
appPrivate.route('/groups', groups)
88100

89101
// Triggers
90102
const functionNameTriggers = 'triggers'
91103
const appTriggers = createHono(functionNameTriggers, version)
104+
appTriggers.route('/ok', ok)
92105
appTriggers.route('/cron_email', cron_email)
93106
appTriggers.route('/cron_clear_versions', cron_clear_versions)
94107
appTriggers.route('/cron_clean_orphan_images', cron_clean_orphan_images)

scripts/start-cloudflare-workers.sh

Lines changed: 70 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,81 +1,122 @@
11
#!/usr/bin/env bash
22

33
# Script to start Cloudflare Workers for testing
4-
# This script starts all workers (D1 Sync, API, Plugin, Files) in the background
4+
# This script starts all workers (API, Plugin, Files) in the background
55

66
set -e
77

8+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
9+
ROOT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
10+
811
echo "Starting Cloudflare Workers for testing..."
912

1013
# Colors for output
1114
GREEN='\033[0;32m'
1215
YELLOW='\033[1;33m'
1316
NC='\033[0m' # No Color
1417

18+
# Use the installed Supabase CLI in CI, fall back to bunx locally.
19+
if command -v supabase >/dev/null 2>&1; then
20+
SUPABASE_CLI="supabase"
21+
else
22+
SUPABASE_CLI="bunx supabase"
23+
fi
24+
25+
# Extract a single variable from `supabase status -o env`, preserving any '=' in values (JWT padding).
26+
get_supabase_status_var() {
27+
local key_regex="$1"
28+
# Output looks like: KEY="value" or KEY=value
29+
printf '%s\n' "${SUPA_ENV}" \
30+
| grep -E "^(${key_regex})=" \
31+
| head -n 1 \
32+
| sed -E 's/^[^=]+=//' \
33+
| sed -E 's/^"//; s/"$//'
34+
}
35+
36+
# Build a runtime env file with local Supabase keys so we don't commit secrets.
37+
BASE_ENV_FILE="${ROOT_DIR}/cloudflare_workers/.env.local"
38+
RUNTIME_ENV_FILE="$(mktemp "${TMPDIR:-/tmp}/capgo-cloudflare-env.XXXXXX")"
39+
chmod 600 "${RUNTIME_ENV_FILE}"
40+
if [ -f "${BASE_ENV_FILE}" ]; then
41+
cp "${BASE_ENV_FILE}" "${RUNTIME_ENV_FILE}"
42+
else
43+
echo -e "${YELLOW}Warning: ${BASE_ENV_FILE} not found - starting with empty base env${NC}"
44+
fi
45+
46+
SUPA_ENV="$(${SUPABASE_CLI} status -o env 2>/dev/null || true)"
47+
SUPABASE_URL_FROM_STATUS="$(get_supabase_status_var 'API_URL')"
48+
# Supabase CLI has historically emitted either SERVICE_ROLE_KEY/ANON_KEY or SECRET_KEY/PUBLISHABLE_KEY.
49+
SUPABASE_SERVICE_ROLE_KEY_FROM_STATUS="$(get_supabase_status_var 'SERVICE_ROLE_KEY|SECRET_KEY')"
50+
SUPABASE_ANON_KEY_FROM_STATUS="$(get_supabase_status_var 'ANON_KEY|PUBLISHABLE_KEY')"
51+
52+
# Allow overrides via environment, otherwise use supabase status output.
53+
SUPABASE_URL="${SUPABASE_URL:-${SUPABASE_URL_FROM_STATUS}}"
54+
SUPABASE_SERVICE_ROLE_KEY="${SUPABASE_SERVICE_ROLE_KEY:-${SUPABASE_SERVICE_ROLE_KEY_FROM_STATUS}}"
55+
SUPABASE_ANON_KEY="${SUPABASE_ANON_KEY:-${SUPABASE_ANON_KEY_FROM_STATUS}}"
56+
57+
if [ -z "${SUPABASE_SERVICE_ROLE_KEY}" ] || [ -z "${SUPABASE_ANON_KEY}" ] || [ -z "${SUPABASE_URL}" ]; then
58+
echo -e "${YELLOW}Missing Supabase keys for Cloudflare Workers.${NC}"
59+
echo "Ensure Supabase is running, or set SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY and SUPABASE_ANON_KEY in your environment."
60+
exit 1
61+
fi
62+
63+
# Cloudflare local testing defaults.
64+
CLOUDFLARE_FUNCTION_URL="${CLOUDFLARE_FUNCTION_URL:-http://127.0.0.1:8787}"
65+
STRIPE_WEBHOOK_SECRET="${STRIPE_WEBHOOK_SECRET:-testsecret}"
66+
67+
# In CI/linux, `host.docker.internal` is unreliable. Prefer localhost (mapped ports).
68+
S3_ENDPOINT_TO_USE="${S3_ENDPOINT:-127.0.0.1:9000}"
69+
70+
cat >> "${RUNTIME_ENV_FILE}" <<EOF
71+
SUPABASE_URL=${SUPABASE_URL}
72+
SUPABASE_SERVICE_ROLE_KEY=${SUPABASE_SERVICE_ROLE_KEY}
73+
SUPABASE_ANON_KEY=${SUPABASE_ANON_KEY}
74+
CLOUDFLARE_FUNCTION_URL=${CLOUDFLARE_FUNCTION_URL}
75+
STRIPE_WEBHOOK_SECRET=${STRIPE_WEBHOOK_SECRET}
76+
S3_ENDPOINT=${S3_ENDPOINT_TO_USE}
77+
EOF
78+
1579
# Kill any existing wrangler processes
1680
echo -e "${YELLOW}Cleaning up existing wrangler processes...${NC}"
1781
pkill -f "wrangler dev" || true
1882
sleep 2
1983

20-
# Wait a bit for the sync worker to start
21-
sleep 3
22-
2384
# Start API worker on port 8787
2485
echo -e "${GREEN}Starting API worker on port 8787...${NC}"
25-
(cd cloudflare_workers/api && bunx wrangler dev -c wrangler.jsonc --port 8787 --env-file=../.env.local --env=local --persist-to ../../.wrangler-shared) &
86+
(cd "${ROOT_DIR}/cloudflare_workers/api" && bunx wrangler dev -c wrangler.jsonc --port 8787 --env-file="${RUNTIME_ENV_FILE}" --env=local --persist-to "${ROOT_DIR}/.wrangler-shared") &
2687
API_PID=$!
2788

2889
# Wait a bit for the first worker to start
2990
sleep 3
3091

3192
# Start Plugin worker on port 8788
3293
echo -e "${GREEN}Starting Plugin worker on port 8788...${NC}"
33-
(cd cloudflare_workers/plugin && bunx wrangler dev -c wrangler.jsonc --port 8788 --env-file=../.env.local --env=local --persist-to ../../.wrangler-shared) &
94+
(cd "${ROOT_DIR}/cloudflare_workers/plugin" && bunx wrangler dev -c wrangler.jsonc --port 8788 --env-file="${RUNTIME_ENV_FILE}" --env=local --persist-to "${ROOT_DIR}/.wrangler-shared") &
3495
PLUGIN_PID=$!
3596

3697
# Wait a bit for the second worker to start
3798
sleep 3
3899

39100
# Start Files worker on port 8789
40101
echo -e "${GREEN}Starting Files worker on port 8789...${NC}"
41-
(cd cloudflare_workers/files && bunx wrangler dev -c wrangler.jsonc --port 8789 --env-file=../.env.local --env=local --persist-to ../../.wrangler-shared) &
102+
(cd "${ROOT_DIR}/cloudflare_workers/files" && bunx wrangler dev -c wrangler.jsonc --port 8789 --env-file="${RUNTIME_ENV_FILE}" --env=local --persist-to "${ROOT_DIR}/.wrangler-shared") &
42103
FILES_PID=$!
43104

44105
echo -e "${GREEN}All workers started!${NC}"
45-
echo "D1 Sync Worker PID: $SYNC_PID (http://127.0.0.1:8790)"
46106
echo "API Worker PID: $API_PID (http://127.0.0.1:8787)"
47107
echo "Plugin Worker PID: $PLUGIN_PID (http://127.0.0.1:8788)"
48108
echo "Files Worker PID: $FILES_PID (http://127.0.0.1:8789)"
49109
echo ""
50110

51-
# Queue initial data to D1 via PGMQ (production-like approach)
52-
echo -e "${GREEN}Queueing initial data for D1 sync...${NC}"
53-
psql postgresql://postgres:[email protected]:54322/postgres -f scripts/trigger-initial-d1-sync.sql > /dev/null 2>&1
54-
if [ $? -eq 0 ]; then
55-
echo -e "${GREEN}✓ Initial data queued to PGMQ${NC}"
56-
57-
# Trigger sync worker to process the queue
58-
echo -e "${GREEN}Triggering D1 sync worker...${NC}"
59-
curl -s -X POST http://127.0.0.1:8790/sync -H "x-webhook-signature: testsecret" > /dev/null 2>&1
60-
if [ $? -eq 0 ]; then
61-
echo -e "${GREEN}✓ D1 sync triggered successfully${NC}"
62-
sleep 2
63-
echo -e "${GREEN}✓ D1 database is now ready with initial data${NC}"
64-
else
65-
echo -e "${YELLOW}⚠ Warning: Failed to trigger D1 sync${NC}"
66-
fi
67-
else
68-
echo -e "${YELLOW}⚠ Warning: Failed to queue initial data${NC}"
69-
fi
70-
71111
echo ""
72112
echo "Press Ctrl+C to stop all workers"
73113

74114
# Function to cleanup on exit
75115
cleanup() {
76116
echo -e "\n${YELLOW}Stopping workers...${NC}"
77-
kill $SYNC_PID $API_PID $PLUGIN_PID $FILES_PID 2>/dev/null || true
117+
kill $API_PID $PLUGIN_PID $FILES_PID 2>/dev/null || true
78118
pkill -f "wrangler dev" || true
119+
rm -f "${RUNTIME_ENV_FILE}" 2>/dev/null || true
79120
echo -e "${GREEN}All workers stopped${NC}"
80121
}
81122

scripts/test-cloudflare-v2.sh

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
#!/usr/bin/env bash
22

3-
# Complete workflow for testing Cloudflare Workers with V2 (D1) enabled
3+
# Complete workflow for testing Cloudflare Workers locally.
4+
#
5+
# Note: Despite the "V2/D1" naming, this script currently validates the Cloudflare
6+
# Workers runtime integration (API/Plugin/Files) against the local Supabase
7+
# database. If/when a D1 sync worker is reintroduced, extend this script to run
8+
# the sync step before tests.
49

510
set -e
611

@@ -14,11 +19,7 @@ NC='\033[0m'
1419

1520
# 1. Reset and seed database
1621
echo -e "\n${YELLOW}Step 1: Resetting Supabase database...${NC}"
17-
supabase db reset
18-
19-
# 2. Sync to D1
20-
echo -e "\n${YELLOW}Step 2: Syncing data from Postgres to D1...${NC}"
21-
bun run scripts/sync-postgres-to-d1.ts
22+
PAGER=cat bunx supabase db reset
2223

2324
# 3. Start workers
2425
echo -e "\n${YELLOW}Step 3: Starting Cloudflare Workers...${NC}"

0 commit comments

Comments
 (0)