1import type { DesktopDiscoveredSshHost } from "@t3tools/contracts";
2import * as Cause from "effect/Cause";
3import { AtomRegistry } from "effect/reactivity";
4import * as AsyncResult from "effect/reactivity/AsyncResult";
5import { describe, expect, it, vi } from "vite-plus/test";
6
7import { createDesktopSshHostsStateAtom, filterDiscoveredSshHosts } from "./desktopSshHosts";
8
9const hosts: ReadonlyArray<DesktopDiscoveredSshHost> = [
10 {
11 alias: "devbox",
12 hostname: "devbox.local",
13 port: null,
14 source: "ssh-config",
15 username: null,
16 },
17];
18
19describe("filterDiscoveredSshHosts", () => {
20 const suggestions: ReadonlyArray<DesktopDiscoveredSshHost> = [
21 {
22 alias: "grape",
23 hostname: "grape",
24 port: null,
25 source: "known-hosts",
26 username: null,
27 },
28 {
29 alias: "Pinot",
30 hostname: "Pinot",
31 port: null,
32 source: "ssh-config",
33 username: null,
34 },
35 {
36 alias: "preprod",
37 hostname: "preprod",
38 port: 2222,
39 source: "ssh-config",
40 username: "deploy",
41 },
42 {
43 alias: "prod-z",
44 hostname: "prod-z",
45 port: null,
46 source: "ssh-config",
47 username: null,
48 },
49 {
50 alias: "prod-a",
51 hostname: "prod-a",
52 port: null,
53 source: "ssh-config",
54 username: null,
55 },
56 {
57 alias: "prod-m",
58 hostname: "prod-m",
59 port: null,
60 source: "ssh-config",
61 username: null,
62 },
63 ];
64
65 it.each(["", " "])("returns every host for an empty query %j", (query) => {
66 expect(filterDiscoveredSshHosts(suggestions, query)).toEqual(suggestions);
67 });
68
69 it("trims the query", () => {
70 expect(filterDiscoveredSshHosts(suggestions, " pi ")).toEqual([suggestions[1]]);
71 });
72
73 it("ranks alias prefix matches before substring matches", () => {
74 expect(filterDiscoveredSshHosts(suggestions, "prod")).toEqual([
75 suggestions[3],
76 suggestions[4],
77 suggestions[5],
78 suggestions[2],
79 ]);
80 });
81
82 it("preserves the original order within a match tier", () => {
83 expect(filterDiscoveredSshHosts(suggestions, "prod").slice(0, 3)).toEqual([
84 suggestions[3],
85 suggestions[4],
86 suggestions[5],
87 ]);
88 });
89
90 it("matches case-insensitively", () => {
91 expect(filterDiscoveredSshHosts(suggestions, "PINOT")).toEqual([suggestions[1]]);
92 });
93
94 it("returns an empty array when no hosts match", () => {
95 expect(filterDiscoveredSshHosts(suggestions, "merlot")).toEqual([]);
96 });
97});
98
99describe("desktopSshHostsState", () => {
100 it("retains discovered hosts when the settings screen remounts", async () => {
101 const discoverSshHosts = vi.fn(async () => hosts);
102 const atom = createDesktopSshHostsStateAtom(() => ({ discoverSshHosts }));
103 const registry = AtomRegistry.make();
104
105 const unmount = registry.mount(atom);
106 await vi.waitFor(() => {
107 expect(AsyncResult.value(registry.get(atom))).toEqual(
108 expect.objectContaining({ _tag: "Some", value: hosts }),
109 );
110 });
111 unmount();
112
113 const remount = registry.mount(atom);
114 expect(AsyncResult.value(registry.get(atom))).toEqual(
115 expect.objectContaining({ _tag: "Some", value: hosts }),
116 );
117 expect(discoverSshHosts).toHaveBeenCalledTimes(1);
118
119 remount();
120 registry.dispose();
121 });
122
123 it("retains the desktop bridge failure as the discovery error cause", async () => {
124 const cause = new Error("ssh config unavailable");
125 const atom = createDesktopSshHostsStateAtom(() => ({
126 discoverSshHosts: async () => Promise.reject(cause),
127 }));
128 const registry = AtomRegistry.make();
129 registry.mount(atom);
130
131 await vi.waitFor(() => expect(AsyncResult.isFailure(registry.get(atom))).toBe(true));
132 const result = registry.get(atom);
133 if (!AsyncResult.isFailure(result)) throw new Error("Expected SSH host discovery to fail.");
134
135 expect(Cause.squash(result.cause)).toEqual(
136 expect.objectContaining({
137 _tag: "DesktopSshDiscoveryError",
138 cause,
139 }),
140 );
141 registry.dispose();
142 });
143});