fix(FN-2055): harden node test utilities and import resolution
- Convert seed-sample-nodes into a test-only helper and remove direct execution against the real central database - Replace private-looking host examples with RFC 5737 test-net addresses in node connection docs and ConnectNodeModal expectations - Add Vitest aliases for @fusion/core and @fusion/engine to stabilize cross-package test imports - Load AgentReflectionService via local engine source path in in-process runtime to avoid alias resolution failures
This commit is contained in:
@@ -1,22 +1,17 @@
|
||||
/**
|
||||
* Seed script for populating the central database with sample nodes.
|
||||
* Test utility for populating a CentralCore instance with sample nodes.
|
||||
*
|
||||
* This script creates a realistic set of nodes (1 local + 5 remote) for
|
||||
* visual testing of the multi-node dashboard.
|
||||
* Used exclusively in tests — creates 1 local + 5 remote nodes
|
||||
* for testing the multi-node dashboard. Must only be called with
|
||||
* test-scoped CentralCore instances (temp directories).
|
||||
*
|
||||
* Usage:
|
||||
* - Direct execution (seeds real central database):
|
||||
* npx tsx packages/core/src/__tests__/seed-sample-nodes.ts
|
||||
*
|
||||
* - As a module (for tests):
|
||||
* import { seedSampleNodes } from "./seed-sample-nodes";
|
||||
* await seedSampleNodes(central);
|
||||
* Usage (tests only):
|
||||
* import { seedSampleNodes } from "./seed-sample-nodes";
|
||||
* await seedSampleNodes(central); // central backed by temp directory
|
||||
*/
|
||||
|
||||
import { CentralCore } from "../central-core.js";
|
||||
import type { NodeConfig, NodeStatus } from "../types.js";
|
||||
import { homedir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
|
||||
/** Sample remote nodes to create for visual testing */
|
||||
const SAMPLE_REMOTE_NODES = [
|
||||
@@ -112,59 +107,3 @@ export async function seedSampleNodes(central: CentralCore): Promise<NodeConfig[
|
||||
|
||||
return nodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Seed the real central database and print results.
|
||||
* Use this when running directly via tsx.
|
||||
*/
|
||||
async function main(): Promise<void> {
|
||||
console.log("\n🌐 Seeding sample nodes into central database...\n");
|
||||
|
||||
const central = new CentralCore();
|
||||
await central.init();
|
||||
|
||||
try {
|
||||
const nodes = await seedSampleNodes(central);
|
||||
|
||||
console.log("\n📊 Registered nodes:\n");
|
||||
console.log("┌─────────────────────────────────────────┬────────┬──────────────────────────────┬─────────┐");
|
||||
console.log("│ Name │ Type │ URL │ Status │");
|
||||
console.log("├─────────────────────────────────────────┼────────┼──────────────────────────────┼─────────┤");
|
||||
|
||||
for (const node of nodes) {
|
||||
const type = node.type.padEnd(6);
|
||||
const name = node.name.slice(0, 39).padEnd(39);
|
||||
const url = (node.url ?? "-").slice(0, 28).padEnd(28);
|
||||
const status = node.status.padEnd(7);
|
||||
console.log(`│ ${name} │ ${type} │ ${url} │ ${status} │`);
|
||||
}
|
||||
|
||||
console.log("└─────────────────────────────────────────┴────────┴──────────────────────────────┴─────────┘");
|
||||
|
||||
// Summary stats
|
||||
const total = nodes.length;
|
||||
const localCount = nodes.filter((n) => n.type === "local").length;
|
||||
const remoteCount = nodes.filter((n) => n.type === "remote").length;
|
||||
const onlineCount = nodes.filter((n) => n.status === "online").length;
|
||||
const offlineCount = nodes.filter((n) => n.status === "offline").length;
|
||||
const errorCount = nodes.filter((n) => n.status === "error").length;
|
||||
const connectingCount = nodes.filter((n) => n.status === "connecting").length;
|
||||
|
||||
console.log("\n📈 Summary:");
|
||||
console.log(` Total nodes: ${total}`);
|
||||
console.log(` Local: ${localCount}, Remote: ${remoteCount}`);
|
||||
console.log(` Online: ${onlineCount}, Offline: ${offlineCount}, Error: ${errorCount}, Connecting: ${connectingCount}`);
|
||||
console.log(`\n✅ Database: ${central.getDatabasePath()}\n`);
|
||||
} finally {
|
||||
await central.close();
|
||||
}
|
||||
}
|
||||
|
||||
// Run if executed directly
|
||||
const isMainModule = process.argv[1]?.endsWith("seed-sample-nodes.ts");
|
||||
if (isMainModule) {
|
||||
main().catch((err) => {
|
||||
console.error("\n❌ Seeding failed:", err);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ export type ConnectionErrorType =
|
||||
export interface ConnectionResult {
|
||||
/** Whether the connection test succeeded */
|
||||
success: boolean;
|
||||
/** Normalized URL (e.g., "http://192.168.1.100:3000") */
|
||||
/** Normalized URL (e.g., "http://192.0.2.10:3000") */
|
||||
url: string;
|
||||
/** Latency in milliseconds for the successful health check */
|
||||
latencyMs?: number;
|
||||
@@ -40,7 +40,7 @@ export interface ConnectionResult {
|
||||
}
|
||||
|
||||
export interface ConnectionOptions {
|
||||
/** IP address or hostname (e.g., "192.168.1.100" or "my-server.local") */
|
||||
/** IP address or hostname (e.g., "192.0.2.10" or "my-server.local") */
|
||||
host: string;
|
||||
/** Port number (1-65535) */
|
||||
port: number;
|
||||
|
||||
@@ -198,7 +198,7 @@ export function ConnectNodeModal({ open, onClose, onConnected, addToast, onSubmi
|
||||
type="text"
|
||||
value={host}
|
||||
onChange={(event) => setHost(event.target.value)}
|
||||
placeholder="192.168.1.100 or my-server.local"
|
||||
placeholder="192.0.2.10 or my-server.local"
|
||||
disabled={isSubmitting}
|
||||
aria-invalid={Boolean(errors.host)}
|
||||
/>
|
||||
|
||||
@@ -44,7 +44,7 @@ describe("ConnectNodeModal", () => {
|
||||
|
||||
expect(screen.getByLabelText("Connect to Node")).toBeInTheDocument();
|
||||
expect(screen.getByPlaceholderText("Build Server")).toBeInTheDocument();
|
||||
expect(screen.getByPlaceholderText("192.168.1.100 or my-server.local")).toBeInTheDocument();
|
||||
expect(screen.getByPlaceholderText("192.0.2.10 or my-server.local")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("does not render when closed", () => {
|
||||
@@ -57,7 +57,7 @@ describe("ConnectNodeModal", () => {
|
||||
render(<ConnectNodeModal {...defaultProps} />);
|
||||
|
||||
// Fill in host but not name
|
||||
fireEvent.change(screen.getByPlaceholderText("192.168.1.100 or my-server.local"), {
|
||||
fireEvent.change(screen.getByPlaceholderText("192.0.2.10 or my-server.local"), {
|
||||
target: { value: "192.168.1.100" },
|
||||
});
|
||||
|
||||
@@ -72,7 +72,7 @@ describe("ConnectNodeModal", () => {
|
||||
render(<ConnectNodeModal {...defaultProps} />);
|
||||
|
||||
// Get the host input directly
|
||||
const hostInput = screen.getByPlaceholderText("192.168.1.100 or my-server.local");
|
||||
const hostInput = screen.getByPlaceholderText("192.0.2.10 or my-server.local");
|
||||
expect(hostInput).toBeInTheDocument();
|
||||
|
||||
// Host should be empty initially
|
||||
@@ -85,7 +85,7 @@ describe("ConnectNodeModal", () => {
|
||||
fireEvent.change(screen.getByPlaceholderText("Build Server"), {
|
||||
target: { value: "Test Node" },
|
||||
});
|
||||
fireEvent.change(screen.getByPlaceholderText("192.168.1.100 or my-server.local"), {
|
||||
fireEvent.change(screen.getByPlaceholderText("192.0.2.10 or my-server.local"), {
|
||||
target: { value: "192.168.1.100" },
|
||||
});
|
||||
|
||||
@@ -103,7 +103,7 @@ describe("ConnectNodeModal", () => {
|
||||
it("shows URL preview as host and port are filled", () => {
|
||||
render(<ConnectNodeModal {...defaultProps} />);
|
||||
|
||||
fireEvent.change(screen.getByPlaceholderText("192.168.1.100 or my-server.local"), {
|
||||
fireEvent.change(screen.getByPlaceholderText("192.0.2.10 or my-server.local"), {
|
||||
target: { value: "192.168.1.100" },
|
||||
});
|
||||
|
||||
@@ -113,7 +113,7 @@ describe("ConnectNodeModal", () => {
|
||||
it("updates URL preview when port changes", () => {
|
||||
render(<ConnectNodeModal {...defaultProps} />);
|
||||
|
||||
fireEvent.change(screen.getByPlaceholderText("192.168.1.100 or my-server.local"), {
|
||||
fireEvent.change(screen.getByPlaceholderText("192.0.2.10 or my-server.local"), {
|
||||
target: { value: "my-server.local" },
|
||||
});
|
||||
|
||||
@@ -127,7 +127,7 @@ describe("ConnectNodeModal", () => {
|
||||
it("strips protocol from host in URL preview", () => {
|
||||
render(<ConnectNodeModal {...defaultProps} />);
|
||||
|
||||
fireEvent.change(screen.getByPlaceholderText("192.168.1.100 or my-server.local"), {
|
||||
fireEvent.change(screen.getByPlaceholderText("192.0.2.10 or my-server.local"), {
|
||||
target: { value: "https://my-server.local" },
|
||||
});
|
||||
|
||||
@@ -146,7 +146,7 @@ describe("ConnectNodeModal", () => {
|
||||
fireEvent.change(screen.getByPlaceholderText("Build Server"), {
|
||||
target: { value: "Test Node" },
|
||||
});
|
||||
fireEvent.change(screen.getByPlaceholderText("192.168.1.100 or my-server.local"), {
|
||||
fireEvent.change(screen.getByPlaceholderText("192.0.2.10 or my-server.local"), {
|
||||
target: { value: "192.168.1.100" },
|
||||
});
|
||||
|
||||
@@ -171,7 +171,7 @@ describe("ConnectNodeModal", () => {
|
||||
fireEvent.change(screen.getByPlaceholderText("Build Server"), {
|
||||
target: { value: "Test Node" },
|
||||
});
|
||||
fireEvent.change(screen.getByPlaceholderText("192.168.1.100 or my-server.local"), {
|
||||
fireEvent.change(screen.getByPlaceholderText("192.0.2.10 or my-server.local"), {
|
||||
target: { value: "invalid-host" },
|
||||
});
|
||||
|
||||
@@ -189,7 +189,7 @@ describe("ConnectNodeModal", () => {
|
||||
fireEvent.change(screen.getByPlaceholderText("Build Server"), {
|
||||
target: { value: "Test Node" },
|
||||
});
|
||||
fireEvent.change(screen.getByPlaceholderText("192.168.1.100 or my-server.local"), {
|
||||
fireEvent.change(screen.getByPlaceholderText("192.0.2.10 or my-server.local"), {
|
||||
target: { value: "192.168.1.100" },
|
||||
});
|
||||
|
||||
@@ -226,7 +226,7 @@ describe("ConnectNodeModal", () => {
|
||||
fireEvent.change(screen.getByPlaceholderText("Build Server"), {
|
||||
target: { value: "Custom Node" },
|
||||
});
|
||||
fireEvent.change(screen.getByPlaceholderText("192.168.1.100 or my-server.local"), {
|
||||
fireEvent.change(screen.getByPlaceholderText("192.0.2.10 or my-server.local"), {
|
||||
target: { value: "192.168.1.100" },
|
||||
});
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ export default defineConfig({
|
||||
resolve: {
|
||||
alias: {
|
||||
"@fusion/core": resolve(__dirname, "../core/src/index.ts"),
|
||||
"@fusion/engine": resolve(__dirname, "../engine/src/index.ts"),
|
||||
},
|
||||
},
|
||||
test: {
|
||||
|
||||
@@ -292,10 +292,10 @@ export class InProcessRuntime
|
||||
}
|
||||
|
||||
// 5c. Initialize AgentReflectionService (requires agentStore and reflectionStore)
|
||||
let reflectionService: import("@fusion/engine").AgentReflectionService | undefined;
|
||||
let reflectionService: import("../agent-reflection.js").AgentReflectionService | undefined;
|
||||
if (agentStoreForReflection && reflectionStoreForService) {
|
||||
try {
|
||||
const { AgentReflectionService: AgentReflectionServiceClass } = await import("@fusion/engine");
|
||||
const { AgentReflectionService: AgentReflectionServiceClass } = await import("../agent-reflection.js");
|
||||
reflectionService = new AgentReflectionServiceClass({
|
||||
agentStore: agentStoreForReflection,
|
||||
taskStore: this.taskStore,
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
import { defineConfig } from "vitest/config";
|
||||
import { availableParallelism } from "node:os";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const defaultMaxWorkers = Math.max(1, Math.min(2, Math.ceil(availableParallelism() / 8)));
|
||||
const maxWorkers = Number.parseInt(process.env.VITEST_MAX_WORKERS ?? String(defaultMaxWorkers), 10);
|
||||
const coreSourceEntry = fileURLToPath(new URL("../core/src/index.ts", import.meta.url));
|
||||
|
||||
export default defineConfig({
|
||||
resolve: {
|
||||
alias: {
|
||||
"@fusion/core": coreSourceEntry,
|
||||
},
|
||||
},
|
||||
test: {
|
||||
include: ["src/**/*.test.ts", "src/**/*.test.tsx"],
|
||||
passWithNoTests: true,
|
||||
|
||||
Reference in New Issue
Block a user