test(update): load real filesystem dependencies in swap fixture

The VM swap fixture inferred incomplete builtin exports from source text. A mixed default and named node:fs import failed during linking, before three backup-retention cases could exercise the filesystem owner. The owner's new mutation and removal dependencies were also missing from the fixture closure.

Expose real builtin and pinned filesystem namespaces and load the complete production filesystem closure. Keep unrelated service seams rejecting unexpected calls and retain the existing cases, assertions, and deadlines.
This commit is contained in:
Peter Steinberger 2026-09-24 18:23:59 -07:00
parent ef2dd32351
commit 691d2e5cd4
No known key found for this signature in database

View file

@ -17,6 +17,11 @@ export async function createDiskSwap(sourceRoot, base) {
).version;
assert.equal(installed, expected, "filesystem dependency must match the candidate manifest");
const atomic = await import(pathToFileURL(require.resolve("@openclaw/fs-safe/atomic")).href);
const fsSafe = new Map();
for (const subpath of ["config", "errors", "root"]) {
const specifier = `@openclaw/fs-safe/${subpath}`;
fsSafe.set(specifier, await import(pathToFileURL(require.resolve(specifier)).href));
}
const unexpected = [];
// Logging, failure-fact presentation, and manifest parsing are bounded seams.
// Package fingerprints, rename/copy/removal, transaction policy and deadlines
@ -53,6 +58,10 @@ export async function createDiskSwap(sourceRoot, base) {
"infra/package-update-local-overrides",
"infra/package-update-swap-contract",
"infra/update-npm-prefix",
"infra/mutation-authority",
"infra/fs-safe-remove",
"infra/fs-safe-defaults",
"infra/errno",
"utils/absolute-deadline",
];
const modules = new Map(),
@ -66,10 +75,14 @@ export async function createDiskSwap(sourceRoot, base) {
format: "esm",
tsconfigRaw: { compilerOptions: { verbatimModuleSyntax: true } },
}).code;
modules.set(
path.basename(name) + ".js",
new vm.SourceTextModule(code, { context, identifier: filename }),
);
const mod = new vm.SourceTextModule(code, { context, identifier: filename });
modules.set(path.basename(name) + ".js", mod);
// Native namespaces must survive mixed, namespace, and side-effect imports.
for (const specifier of mod.dependencySpecifiers) {
if (specifier.startsWith("node:") || fsSafe.has(specifier)) {
external.set(specifier, new Set());
}
}
for (const match of code.matchAll(
/(?:import|export)\s*\{([^}]+)\}\s*from\s*["']([^"']+)["']/gs,
)) {
@ -94,8 +107,8 @@ export async function createDiskSwap(sourceRoot, base) {
if (modules.has(path.basename(specifier))) {
continue;
}
const names = [...namesSet];
const builtin = specifier.startsWith("node:") ? await import(specifier) : undefined;
const native = specifier.startsWith("node:") ? await import(specifier) : fsSafe.get(specifier);
const names = native ? Object.keys(native) : [...namesSet];
stubs.set(
specifier,
new vm.SyntheticModule(
@ -104,8 +117,8 @@ export async function createDiskSwap(sourceRoot, base) {
for (const name of names) {
this.setExport(
name,
builtin
? builtin[name]
native
? native[name]
: Object.hasOwn(values, name)
? values[name]
: function () {