Initial commit: AetherForge Linux (forge-mesh) v0.1.0-dev
Some checks failed
Test / test (push) Has been cancelled

This commit is contained in:
drjones
2026-07-04 09:31:23 +00:00
commit 3678b199d0
154 changed files with 21714 additions and 0 deletions

View File

@@ -0,0 +1,52 @@
import { vi } from "vitest";
export class MockEventSource {
static instances: MockEventSource[] = [];
url: string;
onopen: (() => void) | null = null;
onmessage: ((event: { data: string }) => void) | null = null;
onerror: (() => void) | null = null;
closed = false;
constructor(url: string) {
this.url = url;
MockEventSource.instances.push(this);
}
close() {
this.closed = true;
}
/** Test helper: simulate successful connection */
simulateOpen() {
this.onopen?.();
}
/** Test helper: push SSE data */
simulateMessage(data: string) {
this.onmessage?.({ data });
}
/** Test helper: simulate connection error */
simulateError() {
this.onerror?.();
}
static reset() {
MockEventSource.instances = [];
}
static latest(): MockEventSource | undefined {
return MockEventSource.instances.at(-1);
}
}
export function installMockEventSource() {
MockEventSource.reset();
vi.stubGlobal("EventSource", MockEventSource);
}
export function uninstallMockEventSource() {
vi.unstubAllGlobals();
MockEventSource.reset();
}

View File

@@ -0,0 +1,57 @@
import { vi } from "vitest";
type MessageHandler = (event: { data: string }) => void;
export class MockWebSocket {
static instances: MockWebSocket[] = [];
static CONNECTING = 0;
static OPEN = 1;
static CLOSING = 2;
static CLOSED = 3;
url: string;
readyState = MockWebSocket.CONNECTING;
onopen: (() => void) | null = null;
onmessage: MessageHandler | null = null;
onerror: (() => void) | null = null;
onclose: (() => void) | null = null;
constructor(url: string) {
this.url = url;
MockWebSocket.instances.push(this);
queueMicrotask(() => {
this.readyState = MockWebSocket.OPEN;
this.onopen?.();
});
}
send = vi.fn();
close() {
this.readyState = MockWebSocket.CLOSED;
this.onclose?.();
}
/** Test helper: push a JSON message to listeners */
simulateMessage(data: unknown) {
this.onmessage?.({ data: JSON.stringify(data) });
}
static reset() {
MockWebSocket.instances = [];
}
static latest(): MockWebSocket | undefined {
return MockWebSocket.instances.at(-1);
}
}
export function installMockWebSocket() {
MockWebSocket.reset();
vi.stubGlobal("WebSocket", MockWebSocket);
}
export function uninstallMockWebSocket() {
vi.unstubAllGlobals();
MockWebSocket.reset();
}

View File

@@ -0,0 +1,38 @@
import type { FleetResponse } from "@/types/fleet";
export const MOCK_API_FLEET: FleetResponse = {
totalHashrate: 55_000_000,
onlineCount: 2,
hosts: [
{
id: "host-001",
hostname: "forge-node-alpha",
ip: "10.0.1.12",
arch: "linux/amd64",
hashrate: 30_000_000,
tier: 2,
tierName: "Bundled xmrig",
tierState: "active",
algo: "rx/0",
uptimeSec: 7200,
lastSeen: new Date().toISOString(),
clearance: 2,
online: true,
},
{
id: "host-002",
hostname: "mesh-worker-beta",
ip: "10.0.1.47",
arch: "linux/amd64",
hashrate: 25_000_000,
tier: 3,
tierName: "GPU lolMiner",
tierState: "probing",
algo: "kawpow",
uptimeSec: 1800,
lastSeen: new Date().toISOString(),
clearance: 1,
online: true,
},
],
};

View File

@@ -0,0 +1,89 @@
import { http, HttpResponse } from "msw";
import { MOCK_API_FLEET } from "./fleet";
const MOCK_MINING_PROFILE = {
wallet_address: "48edfHu7V9Z3N9z6M7x8K2pL4qR5sT6uV7wX8yZ9aB0cD1eF2gH3jK4mN5oP6qR",
tiers: [
{ tier: 1, name: "OCI podman", enabled: true },
{ tier: 2, name: "Bundled xmrig", enabled: true },
{ tier: 3, name: "GPU lolMiner", enabled: true },
{ tier: 4, name: "Stratum-direct fallback", enabled: false },
],
};
const MOCK_BUILDS = {
builds: [
{
id: "build-001",
os: "linux",
arch: "amd64",
version: "0.1.0-dev",
checksum: "abc123def4567890abcdef1234567890",
signature: "sig-ed25519",
public: true,
created_at: "2026-06-07T12:00:00Z",
},
{
id: "build-002",
os: "linux",
arch: "arm64",
version: "0.1.0-dev",
checksum: "fedcba0987654321fedcba0987654321",
signature: "sig-ed25519",
public: false,
created_at: "2026-06-06T18:30:00Z",
},
],
};
const MOCK_DROPPER = {
dropper_url: "http://localhost:8989/get",
install_url: "http://localhost:8989/install.sh",
one_liner:
"FORGE_MESH_FLEET_SECRET=change-me-fleet-secret bash <(curl -fsSL http://localhost:8989/install.sh)",
fleet_secret: "change-me-fleet-secret",
public_key: "abcdef1234567890abcdef1234567890",
has_build: true,
version: "0.1.0-dev",
};
export const handlers = [
http.get("/api/v1/fleet", () => HttpResponse.json(MOCK_API_FLEET)),
http.post("/api/v1/ws/ticket", () => HttpResponse.json({ ticket: "test-ws-ticket" })),
http.get("/api/v1/dropper", () => HttpResponse.json(MOCK_DROPPER)),
http.get("/api/v1/forge/builds", () => HttpResponse.json(MOCK_BUILDS)),
http.post("/api/v1/forge/builds/trigger", () => HttpResponse.json({ ok: true, message: "Build queued" })),
http.get("/api/v1/policy/mining-profile", () => HttpResponse.json(MOCK_MINING_PROFILE)),
http.put("/api/v1/policy/mining-profile", () => HttpResponse.json({ ok: true })),
http.put("/api/v1/policy/wallet", () => HttpResponse.json({ ok: true })),
http.post("/api/v1/fleet/:hostId/mining-profile", async ({ request, params }) => {
const body = (await request.json()) as { wallet_address?: string };
return HttpResponse.json({
id: `profile-${params.hostId}`,
wallet_address: body.wallet_address ?? MOCK_MINING_PROFILE.wallet_address,
});
}),
http.get("/api/v1/crucible/history", () => HttpResponse.json({ jobs: [] })),
http.get("/api/v1/operator/me", () => HttpResponse.json({ clearance_level: 4 })),
http.post("/api/v1/crucible/batch", async ({ request }) => {
const body = (await request.json()) as { command?: string; all?: boolean };
if (!body.command) {
return HttpResponse.json({ error: "command required" }, { status: 400 });
}
return HttpResponse.json(
{
id: "batch-test-001",
command: body.command,
status: "completed",
results: [
{
host_id: "host-001",
hostname: "forge-node-alpha",
status: "dispatched",
},
],
},
{ status: 202 },
);
}),
];

View File

@@ -0,0 +1,4 @@
import { setupServer } from "msw/node";
import { handlers } from "./handlers";
export const server = setupServer(...handlers);

View File

@@ -0,0 +1,20 @@
import "@testing-library/jest-dom/vitest";
import { cleanup } from "@testing-library/react";
import { afterAll, afterEach, beforeAll } from "vitest";
import { server } from "./mocks/server";
if (!HTMLElement.prototype.scrollTo) {
HTMLElement.prototype.scrollTo = function scrollTo() {
/* jsdom polyfill */
};
}
beforeAll(() => server.listen({ onUnhandledRequest: "error" }));
afterEach(() => {
cleanup();
server.resetHandlers();
sessionStorage.clear();
});
afterAll(() => server.close());

View File

@@ -0,0 +1,47 @@
import { render, type RenderOptions } from "@testing-library/react";
import { MemoryRouter, type MemoryRouterProps } from "react-router-dom";
import type { ReactElement, ReactNode } from "react";
import { setStoredAuth } from "@/lib/auth";
interface TestProvidersProps {
children: ReactNode;
routerProps?: MemoryRouterProps;
authenticated?: boolean;
}
export function TestProviders({
children,
routerProps = { initialEntries: ["/dashboard"] },
authenticated = true,
}: TestProvidersProps) {
if (authenticated) {
setStoredAuth({ username: "operator", password: "test-pass" });
}
return <MemoryRouter {...routerProps}>{children}</MemoryRouter>;
}
export function renderWithProviders(
ui: ReactElement,
options: RenderOptions & { routerProps?: MemoryRouterProps; authenticated?: boolean } = {},
) {
const { routerProps, authenticated, ...renderOptions } = options;
return render(ui, {
wrapper: ({ children }) => (
<TestProviders routerProps={routerProps} authenticated={authenticated}>
{children}
</TestProviders>
),
...renderOptions,
});
}
export function renderAppRoutes(initialPath: string, authenticated = true) {
// Lazy import to avoid circular deps in some test setups
// eslint-disable-next-line @typescript-eslint/no-require-imports
const App = require("@/App").default as () => ReactElement;
return renderWithProviders(<App />, {
routerProps: { initialEntries: [initialPath] },
authenticated,
});
}