Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
f457fc1
Update dependencies and enhance Cloud Run support
mantrakp04 Apr 2, 2026
5ca107c
Move Dockerfile to docker/backend
N2D4 Apr 2, 2026
490cab6
Add .gcloudignore file to exclude specific files from Google Cloud de…
mantrakp04 Apr 2, 2026
23e760d
Remove .gcloudignore file and simplify Dockerfile installation comman…
mantrakp04 Apr 2, 2026
42747da
Refactor imports to use background-tasks utility
mantrakp04 Apr 2, 2026
ee150a9
Implement OpenTelemetry shutdown handling and enhance database connec…
mantrakp04 Apr 2, 2026
eb0627a
Add E2E Fallback Tests Workflow and Update Environment Configurations
mantrakp04 Apr 3, 2026
6e18155
Refactor fallback URL handling and enhance backend API resilience
mantrakp04 Apr 3, 2026
8cbaae6
Merge branch 'dev' into gcp-stuff
mantrakp04 Apr 3, 2026
cd6e7e4
Merge branch 'dev' into gcp-stuff
mantrakp04 Apr 3, 2026
0c1cfa8
Refactor OpenTelemetry integration and cleanup
mantrakp04 Apr 3, 2026
a5efcad
Merge branch 'dev' into gcp-stuff
mantrakp04 Apr 3, 2026
573f69e
Enhance SDK fallback logic and update test configurations
mantrakp04 Apr 3, 2026
01a6652
Update e2e fallback tests command for improved execution
mantrakp04 Apr 3, 2026
a10b0f1
Update e2e fallback tests command to exclude additional test files
mantrakp04 Apr 3, 2026
a5788ff
Add Cloud Build configuration for Docker image build and deployment
mantrakp04 Apr 3, 2026
41cdf3b
Refine SDK fallback tests command to streamline exclusions
mantrakp04 Apr 3, 2026
f9efe6b
Remove cloudbuild.yaml configuration file for Docker image build and …
mantrakp04 Apr 3, 2026
f45e081
Refactor StackClientInterface for improved URL fallback handling
mantrakp04 Apr 3, 2026
3546663
Refactor prisma-client and background-tasks for improved SIGTERM hand…
mantrakp04 Apr 3, 2026
0640a72
Merge branch 'dev' into gcp-stuff
mantrakp04 Apr 6, 2026
50fd34a
Remove unnecessary ESLint disable comment in background-tasks.tsx for…
mantrakp04 Apr 6, 2026
d47d395
Merge branch 'dev' into gcp-stuff
mantrakp04 Apr 8, 2026
c6e5b92
Merge branch 'dev' into gcp-stuff
mantrakp04 Apr 8, 2026
3228ca2
Merge branch 'dev' into gcp-stuff
mantrakp04 Apr 9, 2026
25be3f2
Merge branch 'dev' into gcp-stuff
mantrakp04 Apr 9, 2026
e23330a
Merge branch 'dev' into gcp-stuff
mantrakp04 Apr 9, 2026
fe1fc15
Merge branch 'dev' into gcp-stuff
mantrakp04 Apr 10, 2026
5d95240
Refactor Cloud Run IP handling in getBrowserEndUserInfo and enhance S…
mantrakp04 Apr 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add E2E Fallback Tests Workflow and Update Environment Configurations
- Introduced a new GitHub Actions workflow for end-to-end fallback tests, ensuring the SDK properly exercises fallback logic when the primary backend is down.
- Updated environment configuration files across multiple applications to include fallback API URLs.
- Enhanced the backend's package.json to support fallback logic in development mode.
- Added client-side components and pages for testing fallback scenarios in the demo application.
- Improved the StackClientInterface to handle fallback URLs and implement sticky fallback behavior.

These changes enhance the testing framework and improve the SDK's resilience in handling backend failures.
  • Loading branch information
mantrakp04 committed Apr 3, 2026
commit eb0627adca3729f73ccee77eebad6444f5919bb5
168 changes: 168 additions & 0 deletions .github/workflows/e2e-fallback-tests.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
# TODO: keep in sync with e2e-tests.yaml — this is a near-copy with the backend
# started on the fallback port (8110) only, so the SDK exercises fallback logic.
name: Runs E2E Fallback Tests

on:
push:
branches:
- main
- dev
pull_request:

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' && github.ref != 'refs/heads/dev' }}

jobs:
build:
name: E2E Fallback Tests (Node ${{ matrix.node-version }})
runs-on: ubicloud-standard-8
env:
NODE_ENV: test
STACK_ENABLE_HARDCODED_PASSKEY_CHALLENGE_FOR_TESTING: yes
STACK_DATABASE_CONNECTION_STRING: "postgres://postgres:PASSWORD-PLACEHOLDER--uqfEC1hmmv@localhost:8128/stackframe"
STACK_EXTERNAL_DB_SYNC_MAX_DURATION_MS: "20000"
STACK_EXTERNAL_DB_SYNC_DIRECT: "false"

strategy:
matrix:
node-version: [22.x]

steps:
- uses: actions/checkout@v6

- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node-version }}

- name: Setup pnpm
uses: pnpm/action-setup@v4

- name: Start Docker Compose in background
uses: JarvusInnovations/background-action@v1.0.7
with:
run: docker compose -f docker/dependencies/docker.compose.yaml up --pull always -d &
wait-on: /dev/null
tail: true
wait-for: 3s
log-output-if: true

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Create .env.test.local files
run: |
cp apps/backend/.env.development apps/backend/.env.test.local
cp apps/dashboard/.env.development apps/dashboard/.env.test.local
cp apps/e2e/.env.development apps/e2e/.env.test.local
cp docs/.env.development docs/.env.test.local
cp examples/cjs-test/.env.development examples/cjs-test/.env.test.local
cp examples/demo/.env.development examples/demo/.env.test.local
cp examples/docs-examples/.env.development examples/docs-examples/.env.test.local
cp examples/e-commerce/.env.development examples/e-commerce/.env.test.local
cp examples/middleware/.env.development examples/middleware/.env.test.local
cp examples/supabase/.env.development examples/supabase/.env.test.local
cp examples/convex/.env.development examples/convex/.env.test.local

- name: Configure fallback backend URL
run: |
echo "NEXT_PUBLIC_STACK_FALLBACK_API_URL=http://localhost:8110" >> apps/backend/.env.test.local
echo "NEXT_PUBLIC_STACK_FALLBACK_API_URL=http://localhost:8110" >> apps/dashboard/.env.test.local
echo "NEXT_PUBLIC_STACK_FALLBACK_API_URL=http://localhost:8110" >> apps/e2e/.env.test.local
echo "NEXT_PUBLIC_STACK_FALLBACK_API_URL=http://localhost:8110" >> examples/demo/.env.test.local
echo "STACK_BACKEND_BASE_URL=http://localhost:8110" >> apps/e2e/.env.test.local

- name: Build
run: pnpm build

- name: Wait on Postgres
run: pnpm run wait-until-postgres-is-ready:pg_isready

- name: Wait on Inbucket
run: pnpx wait-on tcp:localhost:8129

- name: Wait on Svix
run: pnpx wait-on tcp:localhost:8113

- name: Wait on QStash
run: pnpx wait-on tcp:localhost:8125

- name: Wait on ClickHouse
run: pnpx wait-on http://localhost:8136/ping

- name: Initialize database
run: pnpm run db:init

# Start backend ONLY on fallback port 8110 — primary port 8102 is intentionally left down
# so the SDK exercises its fallback logic for every request.
- name: Start stack-backend on fallback port (8110)
uses: JarvusInnovations/background-action@v1.0.7
with:
run: pnpm -C apps/backend run with-env:test next start --port 8110 --log-order=stream &
wait-on: |
http://localhost:8110
tail: true
wait-for: 30s
log-output-if: true

- name: Start stack-dashboard in background
uses: JarvusInnovations/background-action@v1.0.7
with:
run: pnpm run start:dashboard --log-order=stream &
wait-on: |
http://localhost:8101
tail: true
wait-for: 30s
log-output-if: true

- name: Start mock-oauth-server in background
uses: JarvusInnovations/background-action@v1.0.7
with:
run: pnpm run start:mock-oauth-server --log-order=stream &
wait-on: |
http://localhost:8110
tail: true
wait-for: 30s
log-output-if: true

- name: Start run-email-queue in background
uses: JarvusInnovations/background-action@v1.0.7
with:
run: pnpm -C apps/backend run run-email-queue --log-order=stream &
wait-on: |
http://localhost:8110
tail: true
wait-for: 30s
log-output-if: true

- name: Start run-cron-jobs in background
uses: JarvusInnovations/background-action@v1.0.7
with:
run: pnpm -C apps/backend run run-cron-jobs:test --log-order=stream &
wait-on: |
http://localhost:8110
tail: true
wait-for: 30s
log-output-if: true

- name: Wait 10 seconds
run: sleep 10

- name: Verify primary port 8102 is NOT running
run: |
if curl -s -o /dev/null -w "%{http_code}" http://localhost:8102/health 2>/dev/null | grep -q "200"; then
echo "ERROR: Primary backend on port 8102 should NOT be running for fallback tests"
exit 1
fi
echo "Confirmed: primary port 8102 is down, fallback tests will exercise SDK fallback logic"

- name: Run tests
run: pnpm test run

- name: Verify data integrity
run: pnpm run verify-data-integrity --no-bail

- name: Print Docker Compose logs
if: always()
run: docker compose -f docker/dependencies/docker.compose.yaml logs
1 change: 1 addition & 0 deletions apps/backend/.env.development
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
NEXT_PUBLIC_STACK_API_URL=http://localhost:${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}02
NEXT_PUBLIC_STACK_FALLBACK_API_URL=http://localhost:${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}10
NEXT_PUBLIC_STACK_DASHBOARD_URL=http://localhost:${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}01
NEXT_PUBLIC_STACK_HOSTED_HANDLER_DOMAIN_SUFFIX=.localhost:${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}09
NEXT_PUBLIC_STACK_IS_LOCAL_EMULATOR=false
Expand Down
2 changes: 1 addition & 1 deletion apps/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"with-env:dev": "dotenv -c development --",
"with-env:prod": "dotenv -c production --",
"with-env:test": "dotenv -c test --",
"dev": "concurrently -n \"dev,codegen,prisma-studio,email-queue,cron-jobs\" -k \"next dev --port ${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}02 ${STACK_BACKEND_DEV_EXTRA_ARGS:-}\" \"pnpm run codegen:watch\" \"pnpm run prisma-studio\" \"pnpm run run-email-queue\" \"pnpm run run-cron-jobs\"",
"dev": "BACKEND_PORT=${STACK_DEV_FALLBACK_BACKEND:+${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}10} && BACKEND_PORT=${BACKEND_PORT:-${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}02} && concurrently -n \"dev,codegen,prisma-studio,email-queue,cron-jobs\" -k \"next dev --port $BACKEND_PORT ${STACK_BACKEND_DEV_EXTRA_ARGS:-}\" \"pnpm run codegen:watch\" \"pnpm run prisma-studio\" \"pnpm run run-email-queue\" \"pnpm run run-cron-jobs\"",
"dev:inspect": "STACK_BACKEND_DEV_EXTRA_ARGS=\"--inspect\" pnpm run dev",
"dev:profile": "STACK_BACKEND_DEV_EXTRA_ARGS=\"--experimental-cpu-prof\" pnpm run dev",
"build": "pnpm run codegen && next build",
Expand Down
1 change: 1 addition & 0 deletions apps/dashboard/.env.development
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
NEXT_PUBLIC_STACK_API_URL=http://localhost:${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}02
NEXT_PUBLIC_STACK_FALLBACK_API_URL=http://localhost:${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}10
NEXT_PUBLIC_STACK_DOCS_BASE_URL=http://localhost:${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}04
NEXT_PUBLIC_STACK_HOSTED_HANDLER_DOMAIN_SUFFIX=.localhost:${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}09
NEXT_PUBLIC_STACK_IS_LOCAL_EMULATOR=false
Expand Down
1 change: 1 addition & 0 deletions examples/demo/.env.development
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Contains the credentials for the internal project of Stack's default development environment setup.
# Do not use in a production environment, instead replace it with actual values gathered from https://app.stack-auth.com.
NEXT_PUBLIC_STACK_API_URL=http://localhost:${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}02
NEXT_PUBLIC_STACK_FALLBACK_API_URL=http://localhost:${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}10
NEXT_PUBLIC_STACK_PROJECT_ID=internal
NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY=this-publishable-client-key-is-for-local-development-only
STACK_SECRET_SERVER_KEY=this-secret-server-key-is-for-local-development-only
Expand Down
107 changes: 107 additions & 0 deletions examples/demo/src/app/fallback-test/client.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
"use client";

import { useStackApp, useUser } from "@stackframe/stack";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { useCallback, useEffect, useRef, useState } from "react";

type LogEntry = {
time: number;
msg: string;
ok: boolean;
};

export function FallbackTestClient() {
const app = useStackApp();
const user = useUser();
const pathname = usePathname();
const [log, setLog] = useState<LogEntry[]>([]);
const [running, setRunning] = useState(false);
const renderCount = useRef(0);
renderCount.current++;

const addLog = useCallback((msg: string, ok: boolean) => {
setLog(prev => [...prev, { time: Date.now(), msg, ok }]);
}, []);

const runTests = useCallback(async () => {
setLog([]);
setRunning(true);

// Test 1: getProject
{
const start = Date.now();
try {
const project = await app.getProject();
addLog(`getProject: ${project.id} (${Date.now() - start}ms)`, true);
} catch (e: any) {
addLog(`getProject FAILED: ${e.message?.slice(0, 80)} (${Date.now() - start}ms)`, false);
}
}

// Test 2: useUser
addLog(`useUser: ${user ? user.primaryEmail ?? user.id : "(not signed in)"}`, true);

// Test 3: 5x getProject to show sticky latency
{
const times: number[] = [];
for (let i = 0; i < 5; i++) {
const start = Date.now();
try {
await app.getProject();
times.push(Date.now() - start);
} catch {
times.push(-1);
}
}
const avg = times.filter(t => t >= 0).reduce((a, b) => a + b, 0) / times.filter(t => t >= 0).length;
addLog(`getProject x5: [${times.map(t => t >= 0 ? `${t}ms` : "FAIL").join(", ")}] avg=${Math.round(avg)}ms`, times.every(t => t >= 0));
}

setRunning(false);
}, [app, user, addLog]);

useEffect(() => {
void runTests();
Comment thread
vercel[bot] marked this conversation as resolved.
Outdated
}, []); // eslint-disable-line react-hooks/exhaustive-deps

return (
<div>
<section style={{ marginBottom: 24, padding: 16, background: "#f0f8ff", borderRadius: 8 }}>
<h2 style={{ marginTop: 0 }}>Client-side</h2>

<div style={{ marginBottom: 12, fontSize: 12, color: "#666" }}>
<strong>Debug:</strong> renders={renderCount.current} | pathname={pathname}
</div>

<div style={{ marginBottom: 16, fontFamily: "monospace", fontSize: 13, lineHeight: 1.8 }}>
{log.map((entry, i) => (
<div key={i} style={{ color: entry.ok ? "#2a7" : "#c33" }}>
{entry.ok ? "OK" : "ERR"} {entry.msg}
</div>
))}
{log.length === 0 && <div style={{ color: "#999" }}>Running...</div>}
</div>

<div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
<button onClick={() => void runTests()} disabled={running} style={{ padding: "6px 14px", cursor: running ? "wait" : "pointer" }}>
{running ? "Running..." : "Re-run"}
</button>
</div>
</section>

<section style={{ padding: 16, background: "#fff8f0", borderRadius: 8 }}>
<h2 style={{ marginTop: 0 }}>SPA Navigation Test</h2>
<p style={{ fontSize: 13, color: "#666" }}>
Click these links (client-side navigation) and come back.
If sticky fallback persists, requests after navigating back should still be fast.
</p>
<div style={{ display: "flex", gap: 12 }}>
<Link href="/" style={{ color: "#06c" }}>Home</Link>
<Link href="/fallback-test" style={{ color: "#06c" }}>This page (reload)</Link>
<Link href="/settings" style={{ color: "#06c" }}>Settings</Link>
</div>
</section>
</div>
);
}
22 changes: 22 additions & 0 deletions examples/demo/src/app/fallback-test/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { stackServerApp } from "src/stack";
import { FallbackTestClient } from "./client";

export default async function FallbackTestPage() {
const serverStart = Date.now();
const user = await stackServerApp.getUser();
const project = await stackServerApp.getProject();
const serverDuration = Date.now() - serverStart;

return (
<div style={{ fontFamily: "monospace", padding: 32, maxWidth: 900 }}>
<h1>SDK Fallback Test</h1>

<section style={{ marginBottom: 24, padding: 16, background: "#f5f5f5", borderRadius: 8 }}>
<h2 style={{ marginTop: 0 }}>Server-side (RSC)</h2>
<pre>{JSON.stringify({ projectId: project.id, projectName: project.displayName, user: user?.primaryEmail ?? user?.id ?? null, duration: `${serverDuration}ms` }, null, 2)}</pre>
</section>

<FallbackTestClient />
</div>
);
}
Loading
Loading