Skip to content

Commit eb64b27

Browse files
authored
Merge branch 'dev' into fix-connected-accounts-ch-orphan-on-account-id-update
2 parents 91e4078 + bb277d3 commit eb64b27

40 files changed

Lines changed: 2150 additions & 341 deletions

File tree

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
# TODO: keep in sync with e2e-tests.yaml — this is a near-copy with the backend
2+
# started on the fallback port (8110) only, so the SDK exercises fallback logic.
3+
name: Runs E2E Fallback Tests
4+
5+
on:
6+
push:
7+
branches:
8+
- main
9+
- dev
10+
pull_request:
11+
12+
concurrency:
13+
group: ${{ github.workflow }}-${{ github.ref }}
14+
cancel-in-progress: ${{ github.ref != 'refs/heads/main' && github.ref != 'refs/heads/dev' }}
15+
16+
jobs:
17+
build:
18+
name: E2E Fallback Tests (Node ${{ matrix.node-version }})
19+
runs-on: ubicloud-standard-8
20+
env:
21+
NODE_ENV: test
22+
STACK_ENABLE_HARDCODED_PASSKEY_CHALLENGE_FOR_TESTING: yes
23+
STACK_DATABASE_CONNECTION_STRING: "postgres://postgres:PASSWORD-PLACEHOLDER--uqfEC1hmmv@localhost:8128/stackframe"
24+
STACK_EXTERNAL_DB_SYNC_MAX_DURATION_MS: "20000"
25+
STACK_EXTERNAL_DB_SYNC_DIRECT: "false"
26+
# SDK reads this as the primary URL, discovers hardcoded fallback to port 8110
27+
NEXT_PUBLIC_STACK_API_URL: "http://localhost:8102"
28+
# Tells js-helpers to omit explicit baseUrl so the SDK exercises fallback logic
29+
STACK_TEST_SDK_FALLBACK: "true"
30+
31+
strategy:
32+
matrix:
33+
node-version: [22.x]
34+
35+
steps:
36+
- uses: actions/checkout@v6
37+
38+
- name: Setup Node.js ${{ matrix.node-version }}
39+
uses: actions/setup-node@v6
40+
with:
41+
node-version: ${{ matrix.node-version }}
42+
43+
- name: Setup pnpm
44+
uses: pnpm/action-setup@v4
45+
46+
- name: Start Docker Compose in background
47+
uses: JarvusInnovations/background-action@v1.0.7
48+
with:
49+
run: docker compose -f docker/dependencies/docker.compose.yaml up --pull always -d &
50+
wait-on: /dev/null
51+
tail: true
52+
wait-for: 3s
53+
log-output-if: true
54+
55+
- name: Install dependencies
56+
run: pnpm install --frozen-lockfile
57+
58+
- name: Create .env.test.local files
59+
run: |
60+
cp apps/backend/.env.development apps/backend/.env.test.local
61+
cp apps/dashboard/.env.development apps/dashboard/.env.test.local
62+
cp apps/e2e/.env.development apps/e2e/.env.test.local
63+
cp docs/.env.development docs/.env.test.local
64+
cp examples/cjs-test/.env.development examples/cjs-test/.env.test.local
65+
cp examples/demo/.env.development examples/demo/.env.test.local
66+
cp examples/docs-examples/.env.development examples/docs-examples/.env.test.local
67+
cp examples/e-commerce/.env.development examples/e-commerce/.env.test.local
68+
cp examples/middleware/.env.development examples/middleware/.env.test.local
69+
cp examples/supabase/.env.development examples/supabase/.env.test.local
70+
cp examples/convex/.env.development examples/convex/.env.test.local
71+
72+
- name: Build
73+
run: pnpm build
74+
75+
- name: Wait on Postgres
76+
run: pnpm run wait-until-postgres-is-ready:pg_isready
77+
78+
- name: Wait on Inbucket
79+
run: pnpx wait-on tcp:localhost:8129
80+
81+
- name: Wait on Svix
82+
run: pnpx wait-on tcp:localhost:8113
83+
84+
- name: Wait on QStash
85+
run: pnpx wait-on tcp:localhost:8125
86+
87+
- name: Wait on ClickHouse
88+
run: pnpx wait-on http://localhost:8136/ping
89+
90+
- name: Initialize database
91+
run: pnpm run db:init
92+
93+
# Start backend ONLY on fallback port 8110 — primary port 8102 is intentionally left down
94+
# so the SDK exercises its fallback logic for every request.
95+
- name: Start stack-backend on fallback port (8110)
96+
uses: JarvusInnovations/background-action@v1.0.7
97+
with:
98+
run: pnpm -C apps/backend run with-env:test next start --port 8110 &
99+
wait-on: |
100+
http://localhost:8110
101+
tail: true
102+
wait-for: 30s
103+
log-output-if: true
104+
105+
- name: Start stack-dashboard in background
106+
uses: JarvusInnovations/background-action@v1.0.7
107+
with:
108+
run: pnpm run start:dashboard --log-order=stream &
109+
wait-on: |
110+
http://localhost:8101
111+
tail: true
112+
wait-for: 30s
113+
log-output-if: true
114+
115+
- name: Start mock-oauth-server in background
116+
uses: JarvusInnovations/background-action@v1.0.7
117+
with:
118+
run: pnpm run start:mock-oauth-server --log-order=stream &
119+
wait-on: |
120+
http://localhost:8110
121+
tail: true
122+
wait-for: 30s
123+
log-output-if: true
124+
125+
- name: Start run-email-queue in background
126+
uses: JarvusInnovations/background-action@v1.0.7
127+
with:
128+
run: pnpm -C apps/backend run run-email-queue --log-order=stream &
129+
wait-on: |
130+
http://localhost:8110
131+
tail: true
132+
wait-for: 30s
133+
log-output-if: true
134+
135+
- name: Start run-cron-jobs in background
136+
uses: JarvusInnovations/background-action@v1.0.7
137+
with:
138+
run: pnpm -C apps/backend run run-cron-jobs:test --log-order=stream &
139+
wait-on: |
140+
http://localhost:8110
141+
tail: true
142+
wait-for: 30s
143+
log-output-if: true
144+
145+
- name: Wait 10 seconds
146+
run: sleep 10
147+
148+
- name: Verify primary port 8102 is NOT running
149+
run: |
150+
if curl -s -o /dev/null -w "%{http_code}" http://localhost:8102/health 2>/dev/null | grep -q "200"; then
151+
echo "ERROR: Primary backend on port 8102 should NOT be running for fallback tests"
152+
exit 1
153+
fi
154+
echo "Confirmed: primary port 8102 is down, fallback tests will exercise SDK fallback logic"
155+
156+
# Only run JS SDK tests — these exercise the SDK's fallback logic.
157+
# Backend API tests use direct HTTP calls that don't go through fallback.
158+
# Exclude cross-domain-auth which hardcodes the primary URL.
159+
- name: Run SDK fallback tests
160+
run: pnpm -w run pre && cd apps/e2e && npx vitest run tests/js/ --exclude '**/{cross-domain-auth,oauth,email-template-existing-project}*'
161+
162+
- name: Print Docker Compose logs
163+
if: always()
164+
run: docker compose -f docker/dependencies/docker.compose.yaml logs

.github/workflows/restart-dev-and-test-with-custom-base-port.yaml

Lines changed: 0 additions & 59 deletions
This file was deleted.

.github/workflows/restart-dev-and-test.yaml

Lines changed: 0 additions & 58 deletions
This file was deleted.

apps/backend/.env.development

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ STACK_EMAIL_SENDER=noreply@example.com
5656

5757
STACK_ACCESS_TOKEN_EXPIRATION_TIME=60s
5858

59-
STACK_DEFAULT_EMAIL_CAPACITY_PER_HOUR=10000
59+
STACK_DEFAULT_EMAIL_CAPACITY_PER_HOUR=100000
6060

6161
STACK_SVIX_SERVER_URL=http://localhost:${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}13
6262
STACK_SVIX_API_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2NTUxNDA2MzksImV4cCI6MTk3MDUwMDYzOSwibmJmIjoxNjU1MTQwNjM5LCJpc3MiOiJzdml4LXNlcnZlciIsInN1YiI6Im9yZ18yM3JiOFlkR3FNVDBxSXpwZ0d3ZFhmSGlyTXUifQ.En8w77ZJWbd0qrMlHHupHUB-4cx17RfzFykseg95SUk

apps/backend/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"with-env:dev": "dotenv -c development --",
1212
"with-env:prod": "dotenv -c production --",
1313
"with-env:test": "dotenv -c test --",
14-
"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\"",
14+
"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\"",
1515
"dev:inspect": "STACK_BACKEND_DEV_EXTRA_ARGS=\"--inspect\" pnpm run dev",
1616
"dev:profile": "STACK_BACKEND_DEV_EXTRA_ARGS=\"--experimental-cpu-prof\" pnpm run dev",
1717
"build": "pnpm run codegen && next build",

apps/backend/src/app/api/latest/auth/password/sign-up/route.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { createAuthTokens } from "@/lib/tokens";
44
import { getRequestContextAndBotChallengeAssessment, botChallengeFlowRequestSchemaFields } from "@/lib/turnstile";
55
import { createOrUpgradeAnonymousUserWithRules } from "@/lib/users";
66
import { createSmartRouteHandler } from "@/route-handlers/smart-route-handler";
7-
import { runAsynchronouslyAndWaitUntil } from "@/utils/vercel";
7+
import { runAsynchronouslyAndWaitUntil } from "@/utils/background-tasks";
88
import { KnownErrors } from "@stackframe/stack-shared";
99
import { getPasswordError } from "@stackframe/stack-shared/dist/helpers/password";
1010
import { adaptSchema, clientOrHigherAuthTypeSchema, emailVerificationCallbackUrlSchema, passwordSchema, signInEmailSchema, yupNumber, yupObject, yupString } from "@stackframe/stack-shared/dist/schema-fields";
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { describe, expect, it } from 'vitest';
2+
import { parseAndValidateConfig } from './route';
3+
4+
describe('parseAndValidateConfig', () => {
5+
it('should parse a single entry with probability 1', () => {
6+
const result = parseAndValidateConfig({
7+
"1": ["https://api.stack-auth.com"],
8+
});
9+
expect(result).toEqual([
10+
{ probability: 1, urls: ["https://api.stack-auth.com"] },
11+
]);
12+
});
13+
14+
it('should parse multiple entries', () => {
15+
const result = parseAndValidateConfig({
16+
"0.7": ["https://api.stack-auth.com", "https://api2.stack-auth.com"],
17+
"0.3": ["https://api2.stack-auth.com", "https://api.stack-auth.com"],
18+
});
19+
expect(result).toEqual([
20+
{ probability: 0.7, urls: ["https://api.stack-auth.com", "https://api2.stack-auth.com"] },
21+
{ probability: 0.3, urls: ["https://api2.stack-auth.com", "https://api.stack-auth.com"] },
22+
]);
23+
});
24+
25+
it('should allow probabilities summing to less than 1', () => {
26+
const result = parseAndValidateConfig({
27+
"0.5": ["https://api.stack-auth.com"],
28+
"0.3": ["https://api2.stack-auth.com"],
29+
});
30+
expect(result).toHaveLength(2);
31+
});
32+
33+
it('should reject non-object input', () => {
34+
expect(() => parseAndValidateConfig("string")).toThrow("must be a JSON object");
35+
expect(() => parseAndValidateConfig(null)).toThrow("must be a JSON object");
36+
expect(() => parseAndValidateConfig([])).toThrow("must be a JSON object");
37+
expect(() => parseAndValidateConfig(42)).toThrow("must be a JSON object");
38+
});
39+
40+
it('should reject empty object', () => {
41+
expect(() => parseAndValidateConfig({})).toThrow("at least one entry");
42+
});
43+
44+
it('should reject invalid probability keys', () => {
45+
expect(() => parseAndValidateConfig({ "abc": ["https://a.com"] })).toThrow("must be a number between 0 and 1");
46+
expect(() => parseAndValidateConfig({ "-0.1": ["https://a.com"] })).toThrow("must be a number between 0 and 1");
47+
expect(() => parseAndValidateConfig({ "1.5": ["https://a.com"] })).toThrow("must be a number between 0 and 1");
48+
});
49+
50+
it('should reject probabilities summing to more than 1', () => {
51+
expect(() => parseAndValidateConfig({
52+
"0.6": ["https://api.stack-auth.com"],
53+
"0.5": ["https://api2.stack-auth.com"],
54+
})).toThrow("exceeds 1");
55+
});
56+
57+
it('should reject invalid URL values', () => {
58+
expect(() => parseAndValidateConfig({ "1": ["not-a-url"] })).toThrow();
59+
});
60+
61+
it('should reject empty URL arrays', () => {
62+
expect(() => parseAndValidateConfig({ "1": [] })).toThrow();
63+
});
64+
65+
it('should reject non-array values', () => {
66+
expect(() => parseAndValidateConfig({ "1": "https://api.stack-auth.com" })).toThrow();
67+
});
68+
});

0 commit comments

Comments
 (0)