1import * as Option from "effect/Option";
2import { describe, expect, it } from "vite-plus/test";
3import { EnvironmentId, type DeviceHostSummary } from "@t3tools/contracts";
4import {
5 checkDeviceHostConnections,
6 parseDeviceHostDraft,
7 deviceHostConnectionKey,
8 type DeviceHostCheck,
9} from "./deviceHostConnectionChecks";
10
11const host = { id: "mac", label: "Mac mini", target: "user@mac" };
12const ids = ["a", "b", "c", "d"].map((id) => EnvironmentId.make(id));
13const targets = ids.map((environmentId, index) => ({
14 environmentId,
15 label: environmentId,
16 connected: index !== 3,
17}));
18const summary: DeviceHostSummary = {
19 id: "mac",
20 label: "Mac mini",
21 kind: "ssh",
22 platforms: [{ platform: "ios", available: true }],
23 hubInstalled: false,
24 agentDeviceInstalled: false,
25};
26
27describe("device host connection checks", () => {
28 it("starts all connected environments and retains success, local, failure, and offline results", async () => {
29 const calls: string[] = [];
30 const pending = new Map<
31 string,
32 { resolve: (value: DeviceHostSummary) => void; reject: (error: Error) => void }
33 >();
34 const results = new Map<string, DeviceHostCheck>();
35 const run = checkDeviceHostConnections(
36 targets,
37 host,
38 (environmentId) => {
39 calls.push(environmentId);
40 return new Promise((resolve, reject) => pending.set(environmentId, { resolve, reject }));
41 },
42 (environmentId, result) => results.set(environmentId, result),
43 );
44 expect(calls).toEqual(ids.slice(0, 3));
45 expect(results.get(ids[0]!)).toEqual({ status: "pending" });
46 pending.get(ids[0]!)!.resolve(summary);
47 pending.get(ids[1]!)!.resolve({ ...summary, id: "local", kind: "local" });
48 pending.get(ids[2]!)!.reject(new Error("SSH key rejected"));
49 await run;
50 expect([...results.values()]).toEqual([
51 { status: "connected", platforms: summary.platforms },
52 { status: "local" },
53 { status: "failed", error: "SSH key rejected" },
54 { status: "failed", error: "Environment disconnected" },
55 ]);
56 });
57
58 it("does not reuse results after editing a destination or SSH options", () => {
59 const key = deviceHostConnectionKey(host);
60 for (const changed of [
61 { ...host, target: "other" },
62 { ...host, port: 2222 },
63 { ...host, identityFile: "~/.ssh/other" },
64 ]) {
65 expect(deviceHostConnectionKey(changed)).not.toBe(key);
66 }
67 expect(
68 deviceHostConnectionKey({ ...host, id: "another-environment-id", label: "Renamed" }),
69 ).toBe(key);
70 });
71 it("validates SSH targets and normalizes optional identity files through the host contract", () => {
72 for (const target of ["-invalid", "user@bad host", " "]) {
73 expect(parseDeviceHostDraft({ ...host, target })._tag).toBe("None");
74 }
75 for (const port of [0, 65536, 1.5]) {
76 expect(parseDeviceHostDraft({ ...host, port })._tag).toBe("None");
77 }
78 expect(parseDeviceHostDraft({ ...host, target: " user@mac ", identityFile: " " })).toEqual(
79 Option.some(host),
80 );
81 expect(parseDeviceHostDraft({ ...host, identityFile: " ~/.ssh/device " })).toEqual(
82 Option.some({ ...host, identityFile: "~/.ssh/device" }),
83 );
84 });
85});