feat(FN-2976): detect and display external Tailscale funnel tunnels in remo
Merges external Tailscale funnel detection (FN-2976) — adds types, detection logic, status inclusion, kill flow, and a dedicated UI panel for funnel processes started outside Fusion — alongside custom AI providers API routes and a new settings UI section (FN-2965). Fusion-Task-Id: FN-2976
This commit is contained in:
@@ -251,6 +251,49 @@ describe("remote access provider/lifecycle contracts", () => {
|
||||
}));
|
||||
});
|
||||
|
||||
it("returns 200 for remote status when managed tunnel is stopped", async () => {
|
||||
const store = createMockStore({
|
||||
getSettings: vi.fn().mockResolvedValue({
|
||||
remoteAccess: buildRemoteAccessSettings({ activeProvider: "tailscale" }),
|
||||
}),
|
||||
});
|
||||
const { app } = createApp({ store });
|
||||
|
||||
const status = await REQUEST(app, "GET", "/api/remote/status");
|
||||
|
||||
expect(status.status).toBe(200);
|
||||
expect(status.body).toEqual(expect.objectContaining({ state: "stopped" }));
|
||||
});
|
||||
|
||||
it("omits externalTunnel detection when managed tunnel is running", async () => {
|
||||
const engine = {
|
||||
getRemoteTunnelManager: vi.fn().mockReturnValue({
|
||||
getStatus: vi.fn().mockReturnValue({ state: "running", provider: "tailscale", url: "https://live.ts.net/", lastError: null }),
|
||||
}),
|
||||
getRemoteTunnelRestoreDiagnostics: vi.fn().mockReturnValue(null),
|
||||
detectExternalTunnel: vi.fn(),
|
||||
};
|
||||
const { app } = createApp({ engine });
|
||||
|
||||
const status = await REQUEST(app, "GET", "/api/remote/status");
|
||||
|
||||
expect(status.status).toBe(200);
|
||||
expect(status.body.externalTunnel).toBeNull();
|
||||
expect(engine.detectExternalTunnel).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("calls engine killExternalTunnel via kill-external endpoint", async () => {
|
||||
const engine = {
|
||||
killExternalTunnel: vi.fn().mockResolvedValue(undefined),
|
||||
};
|
||||
const { app } = createApp({ engine });
|
||||
|
||||
const result = await REQUEST(app, "POST", "/api/remote/tunnel/kill-external", {});
|
||||
|
||||
expect(result.status).toBe(200);
|
||||
expect(result.body).toEqual({ ok: true });
|
||||
});
|
||||
|
||||
it("installs cloudflared via endpoint and returns install command metadata", async () => {
|
||||
const { app } = createApp();
|
||||
|
||||
|
||||
@@ -380,8 +380,8 @@ describe("createServer health and headless mode", () => {
|
||||
|
||||
const dashStatusBody = dashStatus.body as Record<string, unknown>;
|
||||
const headlessStatusBody = headlessStatus.body as Record<string, unknown>;
|
||||
expect(Object.keys(dashStatusBody).sort()).toEqual(["cloudflaredAvailable", "lastError", "lastErrorCode", "provider", "restore", "state", "url"]);
|
||||
expect(Object.keys(headlessStatusBody).sort()).toEqual(["cloudflaredAvailable", "lastError", "lastErrorCode", "provider", "restore", "state", "url"]);
|
||||
expect(Object.keys(dashStatusBody).sort()).toEqual(["cloudflaredAvailable", "externalTunnel", "lastError", "lastErrorCode", "provider", "restore", "state", "url"]);
|
||||
expect(Object.keys(headlessStatusBody).sort()).toEqual(["cloudflaredAvailable", "externalTunnel", "lastError", "lastErrorCode", "provider", "restore", "state", "url"]);
|
||||
expect(headlessRoot.status).toBe(404);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user