Skip to content

Commit 19f98fa

Browse files
os-warrenclaude
andauthored
fix(tests): make plugin-auth's rate-limit isolation radius visible to the gate and hashed by turbo (#10161)
`rate-limit-storage-isolation.test.ts` reads `packages/runtime/src` and `packages/services/service-sms/src` to check that neither consumer reaches the fixed-window counter through the package ROOT — the #6040 invariant whose breach silently reinstates the whole better-auth load. It derived its roots with a `findUp` walk from `process.cwd()`, which `check:cross-package-test-inputs` does not resolve (`process.cwd` appears nowhere in that detector), so the read produced no flag, no declaration, and no turbo input. Measured on the parent commit: `--list-escapes` named only `managed-extension-fields.test.ts` for plugin-auth, and `@objectstack/plugin-auth#test` hashed to `1bf3935543ab055b` both before and after a change under `packages/runtime/src`. Reinstating the root import in `packages/runtime/src/security/inbound-rate-limit.ts` — the exact regression this test guards — replayed `cache hit, replaying logs` / `>>> FULL TURBO` in 135ms, exit 0, while a direct vitest run on the same tree was RED. That is #7802's shape, on the gate that exists to prevent it. Three changes: - Reseed the test from `__dirname` (TS1470-free under this CJS-typed package's `module: NodeNext`, and a spelling the detector resolves), following the rationale `managed-extension-fields.test.ts` and `platform-objects/src/managed-api-method-affordance-sweep.test.ts` already state for their sibling walks. - Bind each consumer root by NAME and hand it to `readdirSync` by that name. The gate rosters a DIRECTORY only when a directory-read consumes an expression it can resolve; `join(REPO, root)` over an array element yields no name, so the globs would have been declared but UNHELD. - Declare the radius in CROSS_PACKAGE_TEST_INPUTS and mirror it into `turbo.json`'s `@objectstack/plugin-auth#test` inputs. `managed-extension-fields.test.ts` is deliberately NOT touched: it is the only other escaping read the gate sees in this package and it alone holds the `packages/**/*.object.ts` radius. Part of #10029 Claude-Session: https://claude.ai/code/session_01PnJHU45vPJj5UQrxe946Bx Co-authored-by: Claude <noreply@anthropic.com>
1 parent 89f3654 commit 19f98fa

3 files changed

Lines changed: 98 additions & 33 deletions

File tree

packages/plugins/plugin-auth/src/rate-limit-storage-isolation.test.ts

Lines changed: 65 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -41,37 +41,52 @@
4141

4242
import { describe, it, expect } from 'vitest';
4343
import { readFileSync, existsSync, readdirSync } from 'node:fs';
44-
import { dirname, join, relative } from 'node:path';
44+
import { dirname, join, relative, resolve } from 'node:path';
4545

4646
/**
47-
* This package is CJS-typed (no `"type": "module"` — it publishes
48-
* `dist/index.js` as CommonJS), so `module: NodeNext` forbids `import.meta`
49-
* here. Walk up from the CWD instead, which works wherever vitest is invoked
50-
* from.
47+
* Seeded from `__dirname`, not from a `findUp` walk of `process.cwd()`, and not
48+
* from `dirname(fileURLToPath(import.meta.url))`. Both halves of that choice are
49+
* load-bearing — the same pair `managed-extension-fields.test.ts` and
50+
* `platform-objects/src/managed-api-method-affordance-sweep.test.ts` state for
51+
* their sibling repo-wide walks:
52+
*
53+
* - `import.meta` is a TS1470 here. This package is CJS-typed (no
54+
* `"type": "module"` — it publishes `dist/index.js` as CommonJS), so under
55+
* `module: NodeNext` the meta-property is an error however well it runs under
56+
* vitest, and this package's test layer IS in front of tsc through the
57+
* `@objectstack/plugin-auth` TEST_DEBT entry in `check-type-check-coverage.mjs`.
58+
* `__dirname` type-checks under the package's own config and is defined at
59+
* runtime by vitest's transform.
60+
* - `check:cross-package-test-inputs` detects an escaping read STATICALLY, by
61+
* resolving the seed expression. A `findUp` walk from `process.cwd()` is not a
62+
* spelling it resolves — `process.cwd()` appears nowhere in that detector — so
63+
* the two cross-package directory reads at the bottom of this file yielded no
64+
* flag and therefore no declaration, SILENTLY. Measured before this change:
65+
* `--list-escapes` named only `managed-extension-fields.test.ts` for
66+
* plugin-auth, and `@objectstack/plugin-auth#test`'s turbo input hash
67+
* (`1bf3935543ab055b`) did not move when `packages/runtime/src` changed — so a
68+
* runtime-only diff that reinstated the package-root import replayed a cached
69+
* green over the very scan that catches it (#7802's shape, #10029).
70+
*
71+
* Deriving the roots any other way puts this file back in that blind spot.
5172
*/
52-
function findUp(predicate: (dir: string) => boolean, what: string): string {
53-
let dir = process.cwd();
54-
for (;;) {
55-
if (predicate(dir)) return dir;
56-
const parent = dirname(dir);
57-
if (parent === dir) throw new Error(`could not locate ${what}`);
58-
dir = parent;
59-
}
60-
}
61-
62-
const PKG = findUp((dir) => {
63-
const manifest = join(dir, 'package.json');
64-
if (!existsSync(manifest)) return false;
65-
const { name } = JSON.parse(readFileSync(manifest, 'utf8')) as { name?: string };
66-
return name === '@objectstack/plugin-auth';
67-
}, 'the @objectstack/plugin-auth package root');
73+
const HERE = __dirname;
74+
/** …/packages/plugins/plugin-auth/src → the package root, then the repo root. */
75+
const PKG = resolve(HERE, '..');
76+
const REPO = resolve(HERE, '../../../..');
6877

69-
const REPO = findUp(
70-
(dir) => existsSync(join(dir, 'pnpm-workspace.yaml')),
71-
'the workspace root (pnpm-workspace.yaml)',
72-
);
78+
const SRC = HERE;
7379

74-
const SRC = join(PKG, 'src');
80+
/**
81+
* The two consumer packages the root-import scan at the bottom of this file
82+
* walks. Bound here, by name, so the gate can ROSTER them: these two paths are
83+
* `@objectstack/plugin-auth`'s declared cross-package radius in
84+
* `scripts/check-cross-package-test-inputs.mjs`, and the matching
85+
* `$TURBO_ROOT$` entries under `@objectstack/plugin-auth#test` in `turbo.json`
86+
* are what make this task's cache hash move when either directory changes.
87+
*/
88+
const RUNTIME_SRC = resolve(REPO, 'packages/runtime/src');
89+
const SERVICE_SMS_SRC = resolve(REPO, 'packages/services/service-sms/src');
7590

7691
/**
7792
* Strip comments before scanning. The distinction this file turns on — a
@@ -280,12 +295,31 @@ describe('@objectstack/plugin-auth — ./rate-limit-storage stays free of better
280295
// that package. Scanned by directory rather than by filename so moving a
281296
// consumer file does not quietly retire the check.
282297
const COUNTER_SYMBOLS = /\b(incrementFixedWindow|createLazyCounterStore|InProcessCounterStore|CounterStore|FixedWindowCount|LazyCounterStoreOptions)\b/;
283-
const roots = ['packages/runtime/src', 'packages/services/service-sms/src'];
298+
// Each root is bound by NAME above and handed to `readdirSync` by that name,
299+
// rather than looped over as `join(REPO, root)` with `root` an array element.
300+
// That is not a style preference: `check:cross-package-test-inputs` learns a
301+
// DIRECTORY input only when a directory-read consumes an expression it can
302+
// resolve, and its own header lists "a directory read whose path is only a
303+
// loop variable" among the shapes that yield no name. Written as a loop the
304+
// two globs below would be declared but UNHELD — nothing would fail if a
305+
// later edit deleted them, which is the #9763 failure mode (prose holding a
306+
// radius) one level up. Written this way the gate rosters both directories
307+
// and fails if the declaration stops covering them.
308+
expect(
309+
existsSync(RUNTIME_SRC),
310+
'packages/runtime/src — consumer directory moved; re-point this check',
311+
).toBe(true);
312+
expect(
313+
existsSync(SERVICE_SMS_SRC),
314+
'packages/services/service-sms/src — consumer directory moved; re-point this check',
315+
).toBe(true);
284316
const offenders: string[] = [];
285-
for (const root of roots) {
286-
const abs = join(REPO, root);
287-
expect(existsSync(abs), `${root} — consumer directory moved; re-point this check`).toBe(true);
288-
for (const entry of readdirSync(abs, { recursive: true, withFileTypes: true })) {
317+
const trees = [
318+
readdirSync(RUNTIME_SRC, { recursive: true, withFileTypes: true }),
319+
readdirSync(SERVICE_SMS_SRC, { recursive: true, withFileTypes: true }),
320+
];
321+
for (const entries of trees) {
322+
for (const entry of entries) {
289323
if (!entry.isFile() || !entry.name.endsWith('.ts')) continue;
290324
const file = join(entry.parentPath, entry.name);
291325
for (const m of stripComments(readFileSync(file, 'utf8')).matchAll(FROM)) {

scripts/check-cross-package-test-inputs.mjs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,33 @@ const CROSS_PACKAGE_TEST_INPUTS = {
296296
'@objectstack/plugin-auth': {
297297
// src/managed-extension-fields.test.ts walks every `*.object.ts`, and pins
298298
// core's api-key source alongside it.
299-
globs: ['packages/**/*.object.ts', 'packages/core/src/security/**'],
299+
globs: [
300+
'packages/**/*.object.ts',
301+
'packages/core/src/security/**',
302+
// src/rate-limit-storage-isolation.test.ts (#6040) walks BOTH consumer
303+
// packages of the `./rate-limit-storage` subpath by directory, checking
304+
// that neither reaches the counter through the package ROOT — which would
305+
// silently reinstate the whole better-auth load for them. The diff that
306+
// breaks that invariant is a diff in one of these two directories, so
307+
// without them declared the affected-subset filter never adds plugin-auth
308+
// and turbo replays a cached green over the scan (#10029, the #7802
309+
// shape). Measured: before this entry, `@objectstack/plugin-auth#test`
310+
// hashed to `1bf3935543ab055b` both before and after a change under
311+
// `packages/runtime/src`, and the re-run was `>>> FULL TURBO` in 135ms
312+
// while the invariant was live-broken in the tree.
313+
'packages/runtime/src/**',
314+
'packages/services/service-sms/src/**',
315+
// The three below are NAMED in that test's prose rather than read by it —
316+
// the same shape as `serve.ts` on the @objectstack/spec entry above and
317+
// `realtime-protocol.mdx` on @objectstack/dogfood, and settled the same
318+
// way: the literal collector takes quoted paths without parsing, so a
319+
// mention forces a declaration, and declaring the file is cheaper than
320+
// rewording prose to dodge the scanner. All three are low-churn, so the
321+
// added cache invalidation is nominal next to the two directories above.
322+
'scripts/check-published-files.mjs',
323+
'scripts/check-cross-package-test-inputs.mjs',
324+
'packages/types/src/node-isolation.test.ts',
325+
],
300326
},
301327
'@objectstack/plugin-security': {
302328
// src/audience-anchor-set-claims.pin.test.ts pins against spec's

turbo.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,12 @@
112112
"!coverage/**",
113113
"!.turbo/**",
114114
"$TURBO_ROOT$/packages/**/*.object.ts",
115-
"$TURBO_ROOT$/packages/core/src/security/**"
115+
"$TURBO_ROOT$/packages/core/src/security/**",
116+
"$TURBO_ROOT$/packages/runtime/src/**",
117+
"$TURBO_ROOT$/packages/services/service-sms/src/**",
118+
"$TURBO_ROOT$/scripts/check-published-files.mjs",
119+
"$TURBO_ROOT$/scripts/check-cross-package-test-inputs.mjs",
120+
"$TURBO_ROOT$/packages/types/src/node-isolation.test.ts"
116121
]
117122
},
118123
"@objectstack/plugin-security#test": {

0 commit comments

Comments
 (0)