feat(FN-3409): add shell regression matrix for mobile, desktop, and dashboa

The merge completes a multi-step shell regression hardening effort spanning desktop, mobile, and dashboard entry points, adding 12 test files or test expansions to lock down the shell preload and native contracts. A companion fix exempts internal coordination tools (`list_agents`, `delegate_task`, e

Fusion-Task-Id: FN-3409
This commit is contained in:
Fusion
2026-05-08 01:53:58 -07:00
committed by gsxdsm
parent ae7a607972
commit 8e839fc950
11 changed files with 209 additions and 8 deletions

View File

@@ -42,6 +42,45 @@ describe("connection-profiles", () => {
);
});
it("updates existing saved profile by id", async () => {
const { saveShellProfile, listShellProfiles } = await import("../plugins/connection-profiles.js");
const profile = await saveShellProfile({ name: "Prod", serverUrl: "https://fusion.example.com", authToken: "old" });
const updated = await saveShellProfile({
id: profile.id,
name: "Production",
serverUrl: "https://fusion.example.com/root/",
authToken: "new",
});
expect(updated.id).toBe(profile.id);
expect(updated.name).toBe("Production");
expect(updated.serverUrl).toBe("https://fusion.example.com/root");
expect(updated.authToken).toBe("new");
const profiles = await listShellProfiles();
expect(profiles).toHaveLength(1);
expect(profiles[0]?.id).toBe(profile.id);
});
it("switches active profile and restores state across module re-init", async () => {
const { saveShellProfile, setActiveShellProfile, loadShellProfiles } = await import("../plugins/connection-profiles.js");
const first = await saveShellProfile({ name: "Prod", serverUrl: "https://fusion.example.com" });
const second = await saveShellProfile({ name: "Staging", serverUrl: "https://staging.example.com" });
await setActiveShellProfile(first.id);
const switched = await setActiveShellProfile(second.id);
expect(switched.activeProfileId).toBe(second.id);
expect(switched.profiles.find((profile) => profile.id === second.id)?.lastUsedAt).toBeTruthy();
vi.resetModules();
const reloadedModule = await import("../plugins/connection-profiles.js");
const reloaded = await reloadedModule.loadShellProfiles();
expect(reloaded.activeProfileId).toBe(second.id);
expect(reloaded.profiles).toHaveLength(2);
});
it("clears active profile when deleted", async () => {
const { saveShellProfile, setActiveShellProfile, loadShellProfiles, deleteShellProfile } = await import("../plugins/connection-profiles.js");

View File

@@ -1,4 +1,5 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { buildMobileShellHandoff } from "../plugins/shell-handoff.js";
const state = {
activeProfileId: null as string | null,
@@ -8,12 +9,12 @@ const state = {
vi.mock("../plugins/connection-profiles.js", () => ({
loadShellProfiles: vi.fn(async () => state),
listShellProfiles: vi.fn(async () => state.profiles),
saveShellProfile: vi.fn(async (profile: { name: string; serverUrl: string }) => {
saveShellProfile: vi.fn(async (profile: { name: string; serverUrl: string; authToken?: string | null }) => {
const saved = {
id: "p1",
name: profile.name,
serverUrl: profile.serverUrl,
authToken: null,
authToken: profile.authToken ?? null,
createdAt: "2026-01-01T00:00:00.000Z",
updatedAt: "2026-01-01T00:00:00.000Z",
lastUsedAt: null,
@@ -68,6 +69,41 @@ describe("MobileNativeShellBridge", () => {
(globalThis as { window?: Window }).window = originalWindow;
});
it("returns state and listProfiles from persisted storage", async () => {
const { MobileNativeShellBridge } = await import("../plugins/native-shell.js");
const bridge = new MobileNativeShellBridge(scanner as never);
const profile = await bridge.saveProfile({ name: "Prod", serverUrl: "https://fusion.example.com" });
await bridge.setActiveProfile(profile.id);
const stateSnapshot = await bridge.getState();
const profiles = await bridge.listProfiles();
expect(stateSnapshot.host).toBe("mobile-shell");
expect(stateSnapshot.activeProfileId).toBe(profile.id);
expect(profiles).toHaveLength(1);
expect(profiles[0]?.id).toBe(profile.id);
});
it("supports QR onboarding handoff with optional auth token", async () => {
scanner.scanConnection.mockResolvedValueOnce({ serverUrl: "https://fusion.example.com", authToken: "token-123" });
const { MobileNativeShellBridge } = await import("../plugins/native-shell.js");
const bridge = new MobileNativeShellBridge(scanner as never);
const scan = await bridge.startQrScan();
const saved = await bridge.saveProfile({ name: "QR Remote", serverUrl: scan.serverUrl, authToken: scan.authToken ?? null });
const state = await bridge.setActiveProfile(saved.id);
const handoff = buildMobileShellHandoff(state);
expect(scan).toEqual({ serverUrl: "https://fusion.example.com", authToken: "token-123" });
expect(handoff.kind).toBe("remote-launch");
if (handoff.kind === "remote-launch") {
const url = new URL(handoff.url);
expect(url.searchParams.get("profileId")).toBe(saved.id);
expect(url.searchParams.get("token")).toBe("token-123");
}
});
it("rejects desktop mode switch", async () => {
const { MobileNativeShellBridge } = await import("../plugins/native-shell.js");
const bridge = new MobileNativeShellBridge(scanner as never);

View File

@@ -17,6 +17,21 @@ describe("qr-scanner", () => {
expect(parsed).toEqual({ serverUrl: "https://fusion.example.com", authToken: "abc" });
});
it("throws for empty payload", () => {
expect(() => parseQrConnectionPayload(" ")).toThrow("QR scan returned empty payload");
});
it("throws for invalid payload", () => {
expect(() => parseQrConnectionPayload("not-a-fusion-connection")).toThrow(
"QR payload is not a valid Fusion connection payload",
);
});
it("throws when scanner is unavailable", async () => {
const scanner = new QrScanner();
await expect(scanner.scanConnection()).rejects.toThrow("QR scanner is not available on this platform");
});
it("uses adapter scanning", async () => {
const scanner = new QrScanner({ scan: vi.fn(async () => "https://fusion.example.com") });
await expect(scanner.scanConnection()).resolves.toEqual({ serverUrl: "https://fusion.example.com", authToken: null });