Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 4 additions & 4 deletions .changelog/unreleased/fixed-577-guard-decides-by-target.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
a mock value: an assignment to a member of an imported module object, or to a
guarded global (`globalThis.*`, `Date.*`, `process.*` except `process.env`,
`console.*`, `Bun.*`, and the bare snapshot names such as `fetch`), must go
through `patchShared` however the value was computed. A destructured or
property-derived alias no longer hides such a patch, and a name that an
enclosing scope declares is not treated as the global. An alias of a guarded
global root is treated as that root.
through `patchShared` however the value was computed. Object bindings with
identifier or string-literal keys and property-derived aliases are tracked.
Computed binding keys and array bindings of guarded roots are out of scope.
A local binding that does not alias a guarded root is not treated as that global.
An assignment whose member target is rooted in anything other than an
identifier, `this`, or an object or array literal (for example
`getG().fetch = ...`) fails whatever the value. Destructuring targets are not
Expand Down
9 changes: 9 additions & 0 deletions .changelog/unreleased/fixed-603-restore-guard-coverage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
- **The cli restore guard uses shared test discovery and refuses patches after cleanup.**
Discovery uses `discoverTestFiles` over the cli package root. The loaded-set
assertion checks files recorded by the preload when the assertion runs.
Guarded-root aliases preserve selected properties in object bindings with
identifier or string-literal keys, including nested bindings; computed keys
and array bindings are out of scope. Values extracted from `process.env`
are not guarded-root aliases. Aliases resolve through lexical scopes,
including hoisted `var` bindings. Files parse according to their extension.
The patch factory throws `PatchAfterCleanupError` after cleanup ran.
8 changes: 1 addition & 7 deletions packages/cli/test/agent-seed.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@ const patchShared = createPatchShared();
/**
* ops-31.3 — tps agent create onboarding seed tests
*/
import { describe, test, expect, beforeAll, afterAll } from "bun:test";
import { describe, test, expect, beforeAll } from "bun:test";
import { generateKeyPairSync } from "node:crypto";
import { writeFileSync, unlinkSync, existsSync, mkdirSync } from "node:fs";
import { tmpdir, homedir } from "node:os";
import { join } from "node:path";

let _savedFetch: typeof globalThis.fetch;
const TEST_AGENT_ID = `test-seed-agent-${process.pid}`;
const TEST_IDENTITY_DIR = join(tmpdir(), `tps-test-identity-${process.pid}`);

Expand All @@ -25,7 +24,6 @@ const SEED_RESPONSE = {
};

beforeAll(() => {
_savedFetch = globalThis.fetch;
mkdirSync(TEST_IDENTITY_DIR, { recursive: true });
// Pre-generate test Ed25519 key so createAgent doesn't try to write to real identity dir
const { privateKey, publicKey } = generateKeyPairSync("ed25519", {
Expand All @@ -36,10 +34,6 @@ beforeAll(() => {
writeFileSync(join(TEST_IDENTITY_DIR, `${TEST_AGENT_ID}.pub`), publicKey);
});

afterAll(() => {
patchShared(globalThis, "fetch", _savedFetch);
});

describe("ops-31.3: FlairClient.seedAgent()", () => {
test("sends correct payload to /AgentSeed", async () => {
let capturedBody: any;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Not named *.test.ts, so no suite discovers it.
const { env } = process;
env.KEY = "x";
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Not named *.test.ts, so no suite discovers it.
const { process } = globalThis;
process.exit = (() => {}) as never;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const { KEY: value, nested: { child } } = process.env;
value.method = () => "changed";
child.method = () => "changed";
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
const el = <div/>; globalThis.fetch = () => {};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
const el = <div/>; globalThis.fetch = () => {};
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { target } from "./direct-module-target.js";
function outer() {
for (var alias = target; false;) {}
alias.method = () => "changed";
}
outer();
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { target } from "./direct-module-target.js";
function outer() {
for (var alias = target; false;) {}
function inner() { alias.method = () => "changed"; }
inner();
}
outer();
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// cli#603 red fixture: an alias of an imported module object declared in an
// outer scope is still that module object inside an inner scope, so the write
// must report direct-assignment-needs-restore.
// Not named *.test.ts, so no suite discovers it.
import * as mod from "./direct-module-target.js";

const alias = mod;

function inner() {
alias.method = () => "not-a-mock";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// cli#603 green fixture: a local that shadows a module-object alias's name is
// not that alias, so a non-mock write on it is not a patch.
// Not named *.test.ts, so no suite discovers it.
import * as mod from "./direct-module-target.js";

const alias = mod;

function inner(alias: { method(): string }) {
alias.method = () => "not-a-mock";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// cli#603 green fixture: a local that shadows an imported module object's name
// is not that module object, so a non-mock write on it is not a patch.
// Not named *.test.ts, so no suite discovers it.
import * as mod from "./direct-module-target.js";

function local(mod: { method(): string }) {
mod.method = () => "not-a-mock";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const { process: { env } } = globalThis;
env.KEY = "x";
const { globalThis: { process: p } } = globalThis;
const { env: environment } = p;
environment.KEY = "x";
p.exit = () => {};
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const { process: p } = globalThis;
const { env } = p;
env.KEY = "x";
const { KEY: value } = p.env;
value.method = () => "changed";
p.exit = () => {};
Loading
Loading