mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-31 07:35:24 +00:00
feat: convert ssh tunnels into a plugin
This commit is contained in:
1 parent
c85c1fdb86
commit
1682861853
220 files changed
+36928
-11374
No files matched your search
@@ -0,0 +1,101 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
mergeTunnelStatuses,
|
||||
parseServerSentEvents,
|
||||
setTunnelsApi,
|
||||
subscribeTunnelStatuses,
|
||||
} from "../../src/frontend/api";
|
||||
|
||||
afterEach(() => {
|
||||
setTunnelsApi(null);
|
||||
});
|
||||
|
||||
describe("mergeTunnelStatuses", () => {
|
||||
it("keeps local state when a remote tunnel with the same name differs", () => {
|
||||
// Controls always target the local backend, so its view wins.
|
||||
expect(
|
||||
mergeTunnelStatuses(
|
||||
{ shared: { connected: true, status: "connected" } },
|
||||
{
|
||||
shared: { connected: false, status: "disconnected" },
|
||||
remoteOnly: { connected: true, status: "connected" },
|
||||
},
|
||||
),
|
||||
).toEqual({
|
||||
shared: { connected: true, status: "connected" },
|
||||
remoteOnly: { connected: true, status: "connected" },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("parseServerSentEvents", () => {
|
||||
it("splits complete events and hands back a partial one", () => {
|
||||
const events: Array<{ event: string; data: string }> = [];
|
||||
const rest = parseServerSentEvents(
|
||||
': keepalive\n\nevent: statuses\ndata: {"a":1}\n\nevent: stat',
|
||||
(event) => events.push(event),
|
||||
);
|
||||
expect(events).toEqual([{ event: "statuses", data: '{"a":1}' }]);
|
||||
expect(rest).toBe("event: stat");
|
||||
});
|
||||
});
|
||||
|
||||
function streamOf(chunks: string[]): Response {
|
||||
const encoder = new TextEncoder();
|
||||
const body = new ReadableStream<Uint8Array>({
|
||||
start(controller) {
|
||||
for (const chunk of chunks) controller.enqueue(encoder.encode(chunk));
|
||||
},
|
||||
});
|
||||
return { ok: true, body } as unknown as Response;
|
||||
}
|
||||
|
||||
describe("subscribeTunnelStatuses", () => {
|
||||
it("streams statuses from the plugin's event stream and merges remote ones", async () => {
|
||||
setTunnelsApi({
|
||||
defaults: { baseURL: "http://localhost:30001" },
|
||||
} as never);
|
||||
const fetchImpl = vi.fn(async (_url: string) =>
|
||||
streamOf([
|
||||
`event: statuses\ndata: ${JSON.stringify({ shared: { connected: true, status: "connected" } })}\n\n`,
|
||||
]),
|
||||
);
|
||||
const onStatuses = vi.fn();
|
||||
|
||||
const stop = subscribeTunnelStatuses(onStatuses, undefined, {
|
||||
fetchImpl: fetchImpl as unknown as typeof fetch,
|
||||
fetchRemote: async () => ({
|
||||
shared: { connected: false, status: "disconnected" },
|
||||
remoteOnly: { connected: true, status: "connected" },
|
||||
}),
|
||||
});
|
||||
|
||||
await vi.waitFor(() =>
|
||||
expect(onStatuses).toHaveBeenLastCalledWith({
|
||||
shared: { connected: true, status: "connected" },
|
||||
remoteOnly: { connected: true, status: "connected" },
|
||||
}),
|
||||
);
|
||||
expect(fetchImpl.mock.calls[0][0]).toBe(
|
||||
"http://localhost:30001/plugin-api/tunnels/status/stream",
|
||||
);
|
||||
stop();
|
||||
});
|
||||
|
||||
it("falls back to polling the status route when there is no stream url", async () => {
|
||||
const get = vi.fn(async () => ({
|
||||
data: { t: { connected: true, status: "connected" } },
|
||||
}));
|
||||
setTunnelsApi({ get } as never);
|
||||
const onStatuses = vi.fn();
|
||||
|
||||
const stop = subscribeTunnelStatuses(onStatuses);
|
||||
await vi.waitFor(() =>
|
||||
expect(onStatuses).toHaveBeenCalledWith({
|
||||
t: { connected: true, status: "connected" },
|
||||
}),
|
||||
);
|
||||
expect(get).toHaveBeenCalledWith("/status");
|
||||
stop();
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user