mirror of
https://github.com/openclaw/openclaw.git
synced 2026-09-28 05:54:09 +08:00
fix(ui): keep Control UI code off the state database schema loader (#152130)
Import the existing record and fresh-module helpers directly in Control UI unit tests. Add a transitive static import guard to the boundary lane and keep the unchanged Gateway config round-trip proof in the Node test lane. The reported suites pass before and after #152053 (0485ca29d6), then fail atd69a4fa9ca(#151652): the plugin SDK fixture barrel starts re-exporting runtime system-event helpers and reaches the state database through config. Preserve all assertions and the server schema loader's file-asset contract. Main failure: https://github.com/openclaw/openclaw/actions/runs/35382740723 Related: #152053, #151652, #152099
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
import path from "node:path";
|
||||
import { expect, it } from "vitest";
|
||||
import { listGitTrackedFiles } from "../src/test-utils/repo-files.js";
|
||||
import { findSourceImportBackedges } from "./helpers/source-import-closure.js";
|
||||
import { uiNodeDrivenBrowserTestFiles } from "./vitest/vitest.ui-paths.mjs";
|
||||
|
||||
const repoRoot = path.resolve(import.meta.dirname, "..");
|
||||
|
||||
it("keeps Control UI runtime imports off the state database", () => {
|
||||
const files = listGitTrackedFiles({ repoRoot, pathspecs: ["ui/src", "src/state"] });
|
||||
if (!files) {
|
||||
throw new Error("Cannot list Control UI import boundary sources");
|
||||
}
|
||||
// Node/E2E harnesses may exercise real Gateway owners outside the browser.
|
||||
const entries = files.filter(
|
||||
(file) =>
|
||||
file.startsWith("ui/src/") &&
|
||||
file.endsWith(".ts") &&
|
||||
!file.startsWith("ui/src/e2e/") &&
|
||||
!file.endsWith(".e2e.test.ts") &&
|
||||
!file.endsWith(".node.test.ts") &&
|
||||
!uiNodeDrivenBrowserTestFiles.includes(file),
|
||||
);
|
||||
const forbidden = files.filter((file) =>
|
||||
/^src\/state\/openclaw-state-(?:db.*|schema)\.ts$/u.test(file),
|
||||
);
|
||||
|
||||
expect(entries.length).toBeGreaterThan(0);
|
||||
expect(forbidden.length).toBeGreaterThan(0);
|
||||
expect(findSourceImportBackedges(entries, forbidden)).toEqual([]);
|
||||
});
|
||||
@@ -20,7 +20,19 @@ if (errors.length) {
|
||||
);
|
||||
}
|
||||
|
||||
function resolveSourceModule(importer: string, specifier: string): string | undefined {
|
||||
function resolveSourceModule(importer: string, rawSpecifier: string): string | undefined {
|
||||
// Vite query suffixes select asset handling without changing the source file.
|
||||
const specifier = rawSpecifier.split("?", 1)[0]!;
|
||||
// Browser stylesheets are assets, but misspelled paths still fail this guard.
|
||||
if (
|
||||
specifier.startsWith(".") &&
|
||||
specifier.endsWith(".css") &&
|
||||
fs
|
||||
.statSync(path.resolve(path.dirname(importer), specifier), { throwIfNoEntry: false })
|
||||
?.isFile()
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
// This guard owns repository source, including workspace package and SDK aliases.
|
||||
// Node builtins and installed external packages are terminal dependencies.
|
||||
const mapped = Object.keys(options.paths ?? {}).some((pattern) => {
|
||||
@@ -104,9 +116,17 @@ function staticDependencies(file: string): string[] {
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
export function findSourceImportBackedges(entry: string, forbidden: readonly string[]): string[] {
|
||||
const pending = [{ file: path.join(repoRoot, entry), parents: [] as string[] }];
|
||||
export function findSourceImportBackedges(
|
||||
entry: string | readonly string[],
|
||||
forbidden: readonly string[],
|
||||
): string[] {
|
||||
const entries = typeof entry === "string" ? [entry] : entry;
|
||||
const pending = entries.map((file) => ({
|
||||
file: path.join(repoRoot, file),
|
||||
parents: [] as string[],
|
||||
}));
|
||||
const visited = new Set<string>();
|
||||
const forbiddenFiles = new Set(forbidden);
|
||||
const violations: string[] = [];
|
||||
for (const { file, parents } of pending) {
|
||||
if (visited.has(file)) {
|
||||
@@ -114,7 +134,7 @@ export function findSourceImportBackedges(entry: string, forbidden: readonly str
|
||||
}
|
||||
visited.add(file);
|
||||
const chain = [...parents, file];
|
||||
if (forbidden.includes(path.relative(repoRoot, file).split(path.sep).join("/"))) {
|
||||
if (forbiddenFiles.has(path.relative(repoRoot, file).split(path.sep).join("/"))) {
|
||||
violations.push(chain.map((part) => path.relative(repoRoot, part)).join(" -> "));
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ export const boundaryTestFiles = [
|
||||
"src/infra/package-json.test.ts",
|
||||
"src/infra/path-env.test.ts",
|
||||
"src/infra/stable-node-path.test.ts",
|
||||
"test/control-ui-import-boundary.test.ts",
|
||||
"test/extension-import-boundaries.test.ts",
|
||||
"test/extension-test-boundary.test.ts",
|
||||
"test/plugin-extension-import-boundary.test.ts",
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
// @vitest-environment node
|
||||
// Control UI tests cover translate behavior.
|
||||
import { importFreshModule } from "openclaw/plugin-sdk/test-fixtures";
|
||||
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { importFreshModule } from "../../../../src/plugin-sdk/test-helpers/import-fresh.js";
|
||||
import { createStorageMock } from "../../test-helpers/storage.ts";
|
||||
import * as translate from "../lib/translate.ts";
|
||||
import { ar } from "../locales/ar.ts";
|
||||
|
||||
Reference in New Issue
Block a user