Skip to content

Commit d3a59c8

Browse files
committed
fix(ci): preserve hosted SCIM entitlement coverage
1 parent 4530ec9 commit d3a59c8

5 files changed

Lines changed: 44 additions & 8 deletions

File tree

‎.github/CI.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ Memory-heavy builds and browser jobs retain appropriately sized Blacksmith runne
3030

3131
Every HTTP and browser group consumes the same production build in an isolated job/database. Electron also uses that build and skips its development-route warm-up. Local HTTP and Electron development runs retain their dev-server behavior. Build inputs exclude test-only files so a harness edit can reuse an unchanged production build.
3232

33+
SCIM's production app uses a `sim.ai` fixture hostname mapped to loopback, since `NEXT_PUBLIC_FORCE_HOSTED` is ignored in production. Readiness, HTTP tests, and internal API requests still connect to loopback. The SCIM suite verifies that ending an Enterprise subscription rejects both owner administration and an existing bearer token, and that restoring it restores access.
34+
3335
## Coverage removed or consolidated
3436

3537
- SEO is plain HTTP, independent of browser and viewport. Its complete suite runs once in the SEO job, instead of repeating in each of four mobile shards.

‎.github/scripts/http-e2e.sh‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ realtime_pid=''
3333
realtime_tag=''
3434
realtime_log=''
3535
browsers_pid=''
36+
local_app_url=''
3637
# `dev` or `start`; a group sets it before start_app.
3738
app_server=${E2E_APP_SERVER:-dev}
3839

@@ -78,7 +79,8 @@ start_app() {
7879
status_log="$report_dir/$name-http-status.log"
7980
fi
8081
app_tag="$name-$GITHUB_RUN_ID-$GITHUB_RUN_ATTEMPT-$$"
81-
export NEXT_PUBLIC_APP_URL="http://127.0.0.1:$port"
82+
local_app_url="http://127.0.0.1:$port"
83+
export NEXT_PUBLIC_APP_URL="${E2E_APP_PUBLIC_URL:-$local_app_url}"
8284
export BETTER_AUTH_URL="$NEXT_PUBLIC_APP_URL"
8385
export DISABLE_TELEMETRY=true NEXT_TELEMETRY_DISABLED=1
8486
if [ "$app_server" = start ]; then
@@ -96,7 +98,7 @@ start_app() {
9698
--port "$port" > "$server_log" 2>&1 &
9799
fi
98100
server_pid=$!
99-
wait_ready "$server_pid" "$NEXT_PUBLIC_APP_URL/api/health" "$label app"
101+
wait_ready "$server_pid" "$local_app_url/api/health" "$label app"
100102
}
101103

102104
# start_realtime <name> <port>: the Socket.IO server the app's browser clients connect to, so pages
@@ -143,12 +145,12 @@ case "$group" in
143145
export DB_TX_TRIPWIRE=throw
144146
export NEXT_PUBLIC_CHAT_DISABLED=true
145147
start_app scim 3017 SCIM record-http-status
146-
SCIM_E2E_BASE_URL="$NEXT_PUBLIC_APP_URL" \
148+
SCIM_E2E_BASE_URL="$local_app_url" \
147149
SCIM_E2E_DATABASE_URL="$DATABASE_URL" \
148150
SCIM_E2E_AUTH_SECRET="$BETTER_AUTH_SECRET" \
149151
SCIM_E2E_REPORT_PATH="$report_dir/scim-e2e-report.json" \
150152
bun run test:scim:e2e
151-
VERSION_COMPARE_E2E_BASE_URL="$NEXT_PUBLIC_APP_URL" \
153+
VERSION_COMPARE_E2E_BASE_URL="$local_app_url" \
152154
VERSION_COMPARE_E2E_DATABASE_URL="$DATABASE_URL" \
153155
VERSION_COMPARE_E2E_AUTH_SECRET="$BETTER_AUTH_SECRET" \
154156
VERSION_COMPARE_E2E_REPORT_PATH="$report_dir/version-compare-http-report.json" \

‎.github/workflows/checks.yml‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,9 @@ jobs:
228228
DATABASE_URL: postgresql://postgres:postgres@127.0.0.1:5432/sim_test
229229
BETTER_AUTH_SECRET: http-e2e-ci-secret-at-least-32-characters
230230
E2E_APP_SERVER: start
231+
E2E_APP_PUBLIC_URL: ${{ matrix.group == 'scim' && 'http://scim-e2e.sim.ai:3017' || '' }}
232+
INTERNAL_API_BASE_URL: ${{ matrix.group == 'scim' && 'http://127.0.0.1:3017' || '' }}
233+
TRUSTED_ORIGINS: ${{ matrix.group == 'scim' && 'http://127.0.0.1:3017' || '' }}
231234
PROJECT_FILES_E2E_APP_SERVER: start
232235
ENCRYPTION_KEY: '0000000000000000000000000000000000000000000000000000000000000000'
233236

@@ -275,6 +278,10 @@ jobs:
275278
cd "$addon_dir"
276279
bun run install
277280
281+
- name: Map the hosted SCIM fixture to loopback
282+
if: matrix.group == 'scim'
283+
run: printf '127.0.0.1 scim-e2e.sim.ai\n' | sudo tee -a /etc/hosts
284+
278285
# No step timeout: the job's bound covers a hang without cutting a slow but healthy suite
279286
# short of writing its report.
280287
- name: Run end-to-end suites

‎apps/sim/ee/scim/TESTING.md‎

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@ provisioning service.
99
## Requirements
1010

1111
- Bun and the repository dependencies installed.
12-
- A running local app configured for Enterprise SCIM, using a dedicated local
12+
- A running local app configured for hosted Enterprise SCIM with billing enabled, using a dedicated local
1313
PostgreSQL database with all migrations applied (including `0325`). Leave
1414
Redis unconfigured so the app uses PostgreSQL for rate-limit storage.
15+
For `next dev`, set `NEXT_PUBLIC_FORCE_HOSTED=true` and `BILLING_ENABLED=true`.
1516
- The app's local `BETTER_AUTH_SECRET`, at least 32 characters long.
1617
- An app URL using HTTP on `localhost`, `127.0.0.1`, or `::1`. The database must
1718
also use a loopback host, and its name must include `test` or `scim`.
@@ -53,6 +54,8 @@ timeout.
5354

5455
- Discovery, session-authenticated configuration, credential hashing, tenant
5556
isolation, bearer authentication, content type, and malformed JSON errors.
57+
- Hosted Enterprise entitlement loss rejects both session-authenticated administration
58+
and an existing bearer token; restoring the subscription restores both paths.
5659
- User and group creation, reads, replacement, PATCH, deletion, conflict errors,
5760
stable pagination, count-only queries, and group/member attribute projection.
5861
- Case-insensitive user lookup, external IDs, secondary/work/primary email
@@ -78,10 +81,14 @@ timeout.
7881

7982
The `e2e (scim)` job in `.github/workflows/checks.yml`
8083
provisions its own database through `db:migrate`, starts a local Next.js app
81-
with hosted Enterprise configuration, and runs the HTTP suite above. It has its
84+
with hosted Enterprise configuration, and runs the HTTP suite above. The production
85+
app's public URL uses `scim-e2e.sim.ai`, mapped to loopback on that runner; the
86+
tests and internal API requests still connect to `127.0.0.1`. Production ignores
87+
`NEXT_PUBLIC_FORCE_HOSTED`, so setting that flag alone cannot provide hosted coverage.
88+
It has its
8289
own database because the hosted app runs background usage replay against it.
83-
Readiness is bounded to 300 seconds, since the first request cold-compiles the
84-
app; the server is stopped when the step exits. A failure prints the server log
90+
Readiness is bounded to 600 seconds for either production or local development
91+
startup; the server is stopped when the step exits. A failure prints the server log
8592
tail and uploads the credential-free scenario report, an allowlist of HTTP
8693
status log lines, and the local app's server log. The CI app uses only fixture
8794
secrets.

‎apps/sim/scripts/test-scim-e2e.ts‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,24 @@ async function run() {
282282
}
283283
)
284284

285+
await check(
286+
'Hosted SCIM rejects administration and bearer access after Enterprise entitlement ends',
287+
async () => {
288+
await admin(primary)
289+
await scim(token, '/Users')
290+
await sql`update subscription set status = 'canceled' where reference_id = ${primary.orgId}`
291+
try {
292+
const denied = await admin(primary, '', { expected: 403 })
293+
assert.match(string(denied.body.error), /active enterprise subscription/)
294+
await scim(token, '/Users', { expected: 401 })
295+
} finally {
296+
await sql`update subscription set status = 'active' where reference_id = ${primary.orgId}`
297+
}
298+
await admin(primary)
299+
await scim(token, '/Users')
300+
}
301+
)
302+
285303
await check(
286304
'Protocol authentication, malformed JSON, and content-type errors are SCIM responses',
287305
async () => {

0 commit comments

Comments
 (0)