Repository navigation
Commit 4df6292
fix(core): os test --url with a trailing slash addresses the served routes (#28999)
<!-- claude-projects-attribution: {"github_login":"os-project-manager"}
-->
_Requested by **Leehom** · [project
thread](https://claude.ai/code/project/chan_01TenzsRopZj4jMbvjX8uz1X?thread=cmsg_01TenzsRopZj4jMbvjX8uz1XFBxyXPUfh7hY87x8YuedEk)_
Fixes #28946
Clause-②: no — no schema, type, export or route change; a `--url` that
answered every HTTP scenario 404 now reaches the served routes.
Metadata-protocol: none — nothing a metadata author writes is read
differently; `qa/*.test.json` suites are unchanged.
## 维护者速读
- 改了什么:`HttpTestAdapter` 在构造函数里把 base URL 末尾的斜杠一次性去掉,于是发现探针、记录动作
URL、`api_call` 相对路径和报错里的 URL 都读同一个规整后的 base。`api_call` 遇到 404/405
时,报错句末尾追加它实际请求的 `方法 + URL`,和记录动作已有的写法一致。
- 为什么改:`os test --url http://localhost:PORT/`(带末尾斜杠)会请求
`//api/v1/...`,服务端回 404 `ENDPOINT_NOT_FOUND`,每个 HTTP 场景都失败,而且 `api_call`
的报错不说它请求了哪个 URL,看起来像应用坏了。
- 风险与回滚:只动一个类的构造函数和一处报错文案;不加参数、不做 URL 校验,畸形 `--url` 行为不变。回滚即 revert。
- 先看:`packages/core/src/qa/http-adapter.ts` 的构造函数与 `rawApiCall`
末尾;`packages/core/src/qa/http-adapter.test.ts` 末尾新增的 describe 与改过的
`[#7983] an api_call failure ...` 用例。
- 两分钟验证:`pnpm --filter @objectstack/core exec vitest run
src/qa/http-adapter.test.ts`,预期 `Tests 52 passed (52)`。
- 需要你做的:无,席位落地。
## What changes
- **`packages/core/src/qa/http-adapter.ts`**: the constructor stores
`baseUrl` with its trailing slashes stripped (`private readonly
baseUrl`), once. Every URL the adapter builds reads that field: the
discovery probe, `collectionUrl` / `recordUrl`, `api_call`'s relative
target, and the record-actions provenance clause. `HttpTestAdapter` has
one caller (`packages/cli/src/commands/test.ts`, which hands it `--url`
verbatim), so normalising in the adapter covers it and any future
caller; `Test.run` is unchanged. No flag, no URL validation: a malformed
`--url` behaves as it did.
- `rawApiCall` hands `handleResponse` the clause `api_call addressed
METHOD URL`, so an `api_call` that fails 404 or 405 ends its existing
`HTTP Error STATUS: BODY` sentence with one added clause naming the URL
it addressed. The 404/405 gate is the one record actions already use:
other statuses reached a route, so the server's own message stays
undecorated. `handleResponse`'s parameter is renamed `mountNote` →
`addressNote`, since it now carries either.
- **`.changeset/28946-qa-base-url-trailing-slash.md`**:
`@objectstack/core` patch with both declaration lines. The CLI is
unchanged (its dist imports `@objectstack/core` externally), so it
carries no changeset.
## Tests
- New describe at the end of `http-adapter.test.ts`, three rows
(`http://localhost:3000/`, `http://localhost:3000//`,
`http://localhost:3000/prefix/`) × four cases, against a captured
`fetch`: the probe plus the record collection URL carry one slash; an
`api_call` relative target carries one slash; the record-actions clause
names the normalised base and probe; an `api_call` 404 names the
normalised URL.
- The existing `[#7983] an api_call failure is not decorated with a
mount it never used` pinned the exact undecorated 404 message. Per the
order's ruling it now asserts the added URL clause and still asserts no
mount clause. A new case pins that an `api_call` 422 stays undecorated.
- **Ablation 1** (`scripts/ablation-replace.mjs`, anchor hit once, blob
`3c016586` → `f4f31939`): the constructor kept `baseUrl` verbatim. 12
failed / 40 passed; for example, expected
`http://localhost:3000/api/v1/objects/crm_account`, received
`http://localhost:3000//api/v1/data/crm_account` (the doubled-slash
probe misses discovery, too). Restored: blob equals HEAD's, `git diff
HEAD` empty.
- **Ablation 2** (blob `3c016586` → `43092308`): `rawApiCall` passed no
clause. 4 failed / 48 passed (the rewritten #7983 case and the three
per-row `api_call` 404 cases). Restored the same way.
- The test imports `./http-adapter.js` from source, so no build sits on
the ablation path.
## Acceptance notes
- Measured on a stock showcase boot (`objectstack dev --seed-admin
--fresh`, admin token from `sign-in/email`) before and after, with the
showcase's `qa/platform-smoke.test.json` and a two-scenario suite (one
`create_record`, one `api_call`):
| `--url` | base `135054eb` | head `4ff51b55` |
|---|---|---|
| `http://localhost:PORT` | 2/2 pass | 2/2 pass |
| `http://localhost:PORT/` | 0/2, 404 | 2/2 pass |
| `http://localhost:PORT//` | 0/2, 404 | 2/2 pass |
| `http://localhost:PORT/prefix/` | 0/2, 404, record clause names
`.../prefix//api/v1/data` | 0/2, 404 (the server is not under a prefix),
and each failure names its URL: `api_call addressed GET
http://localhost:PORT/prefix/api/v1/health`; `record actions addressed
http://localhost:PORT/prefix/api/v1/data` |
- Before the fix, an `api_call` 404 printed only `HTTP Error 404:
{"success":false,"error":{"code":"ENDPOINT_NOT_FOUND","message":"Not
found"}}`, with no URL.
## Verification
All at head `4ff51b55` (base `135054eb`, `origin/main` when the branch
was cut).
<details><summary>Package suite, typecheck, lint (under the verify
lock)</summary>
- `pnpm --filter @objectstack/core test`: Test Files 129 passed (129),
Tests 2905 passed (2905).
- `pnpm --filter @objectstack/core typecheck` (`tsc --noEmit` plus
`check:test-typecheck` over `tsconfig.test.json`, which covers the test
file): exit 0.
- `pnpm exec eslint --no-inline-config packages/core`: exit 0.
- `test:repo` (core's `repo` vitest project) was not run locally: the
diff touches no census pin and the derivation does not name it. CI Test
Core runs it.
</details>
<details><summary>Derived gates (dispatch-gates --commands, 59
families)</summary>
- 58 run with exit 0, 1 NOT MEASURED. `dispatch-gates.mjs --ran`: "59
derived famil(ies) accounted for — 58 run, 1 NOT-MEASURED (1 DERIVED
from a recorded exit 3)".
- NOT MEASURED: `pnpm check:dual-build-cjs-loads`, exit 3 (PREREQUISITE
NOT MET: packages outside the showcase closure have no `dist`). CI runs
it.
- `check-plugin-teardown-shape.mjs --self-test` ran after `git fetch
--depth=1 origin 621a487` (the shallow-clone prerequisite).
</details>
CI check-runs: read by the seat on this head.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01FEkQ5nPQ5s2XpToMJtoBDm
---
_Generated by [Claude
Code](https://claude.ai/code/session_01FEkQ5nPQ5s2XpToMJtoBDm)_
Co-authored-by: Claude <noreply@anthropic.com>1 parent d8c611d commit 4df6292
3 files changed
Lines changed: 104 additions & 7 deletions
File tree
- .changeset
- packages/core/src/qa
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
361 | 361 | | |
362 | 362 | | |
363 | 363 | | |
364 | | - | |
| 364 | + | |
365 | 365 | | |
366 | 366 | | |
367 | 367 | | |
368 | 368 | | |
369 | 369 | | |
370 | | - | |
| 370 | + | |
| 371 | + | |
| 372 | + | |
| 373 | + | |
| 374 | + | |
| 375 | + | |
| 376 | + | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
371 | 380 | | |
372 | 381 | | |
373 | 382 | | |
| |||
452 | 461 | | |
453 | 462 | | |
454 | 463 | | |
| 464 | + | |
| 465 | + | |
| 466 | + | |
| 467 | + | |
| 468 | + | |
| 469 | + | |
| 470 | + | |
| 471 | + | |
| 472 | + | |
| 473 | + | |
| 474 | + | |
| 475 | + | |
| 476 | + | |
| 477 | + | |
| 478 | + | |
| 479 | + | |
| 480 | + | |
| 481 | + | |
| 482 | + | |
| 483 | + | |
| 484 | + | |
| 485 | + | |
| 486 | + | |
| 487 | + | |
| 488 | + | |
| 489 | + | |
| 490 | + | |
| 491 | + | |
| 492 | + | |
| 493 | + | |
| 494 | + | |
| 495 | + | |
| 496 | + | |
| 497 | + | |
| 498 | + | |
| 499 | + | |
| 500 | + | |
| 501 | + | |
| 502 | + | |
| 503 | + | |
| 504 | + | |
| 505 | + | |
| 506 | + | |
| 507 | + | |
| 508 | + | |
| 509 | + | |
| 510 | + | |
| 511 | + | |
| 512 | + | |
| 513 | + | |
| 514 | + | |
| 515 | + | |
| 516 | + | |
| 517 | + | |
| 518 | + | |
| 519 | + | |
| 520 | + | |
| 521 | + | |
| 522 | + | |
| 523 | + | |
| 524 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
100 | 100 | | |
101 | 101 | | |
102 | 102 | | |
103 | | - | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
104 | 117 | | |
105 | 118 | | |
106 | 119 | | |
| |||
370 | 383 | | |
371 | 384 | | |
372 | 385 | | |
373 | | - | |
| 386 | + | |
| 387 | + | |
374 | 388 | | |
375 | 389 | | |
376 | | - | |
| 390 | + | |
377 | 391 | | |
378 | 392 | | |
379 | 393 | | |
380 | 394 | | |
381 | | - | |
| 395 | + | |
| 396 | + | |
382 | 397 | | |
383 | 398 | | |
384 | 399 | | |
| |||
388 | 403 | | |
389 | 404 | | |
390 | 405 | | |
391 | | - | |
| 406 | + | |
392 | 407 | | |
393 | 408 | | |
394 | 409 | | |
| |||
0 commit comments