1import { describe, expect, it } from "vite-plus/test";
2import { AuthFilesystemReadScope, AuthOrchestrationReadScope } from "@t3tools/contracts";
3
4import {
5 canPreloadBrowsePath,
6 createBrowseNavigationCoordinator,
7 filterFilesystemBrowseEntries,
8 getFilesystemBrowsePath,
9 resolveFilesystemReadAccess,
10} from "./filesystem.ts";
11
12/** A server that already splits scopes; it never falls back to a parent grant. */
13const SPLIT_SCOPES_SERVER = { serverUpdateScope: "environment:maintain" } as const;
14
15describe("filesystem read access", () => {
16 it("waits for the initial catalog before declaring a missing environment disconnected", () => {
17 expect(
18 resolveFilesystemReadAccess({
19 isCatalogReady: false,
20 connection: null,
21 session: null,
22 sessionError: null,
23 }),
24 ).toEqual({ canReadFiles: false, isPending: true, error: null });
25 });
26
27 it("stops waiting when the loaded catalog has no matching environment", () => {
28 expect(
29 resolveFilesystemReadAccess({
30 isCatalogReady: true,
31 connection: null,
32 session: null,
33 sessionError: null,
34 }),
35 ).toEqual({
36 canReadFiles: false,
37 isPending: false,
38 error: "This environment is not connected.",
39 });
40 });
41
42 it.each(["available", "offline", "error"] as const)(
43 "stops waiting for an unresolved session when the connection is %s",
44 (phase) => {
45 expect(
46 resolveFilesystemReadAccess({
47 isCatalogReady: true,
48 connection: { phase, error: null },
49 session: null,
50 sessionError: null,
51 }),
52 ).toEqual({
53 canReadFiles: false,
54 isPending: false,
55 error: "This environment is not connected.",
56 });
57 },
58 );
59
60 it.each(["connected", "connecting", "reconnecting"] as const)(
61 "waits for the session check while %s",
62 (phase) => {
63 expect(
64 resolveFilesystemReadAccess({
65 isCatalogReady: true,
66 connection: { phase, error: null },
67 session: null,
68 sessionError: null,
69 }),
70 ).toEqual({ canReadFiles: false, isPending: true, error: null });
71 },
72 );
73
74 it("reports the transport failure when the session cannot be checked", () => {
75 expect(
76 resolveFilesystemReadAccess({
77 isCatalogReady: true,
78 connection: { phase: "error", error: "The relay is unavailable." },
79 session: null,
80 sessionError: null,
81 }),
82 ).toEqual({ canReadFiles: false, isPending: false, error: "The relay is unavailable." });
83 });
84
85 it.each([false, true])(
86 "preserves a cached file grant offline with catalog ready=%s",
87 (isCatalogReady) => {
88 const input = {
89 isCatalogReady,
90 connection: { phase: "offline", error: null },
91 session: { authenticated: true, scopes: [AuthFilesystemReadScope] },
92 sessionError: null,
93 } as const;
94 expect(resolveFilesystemReadAccess(input)).toEqual({
95 canReadFiles: true,
96 isPending: false,
97 error: null,
98 });
99 expect(
100 resolveFilesystemReadAccess({ ...input, sessionError: "The session has expired." }),
101 ).toEqual({ canReadFiles: false, isPending: false, error: "The session has expired." });
102 },
103 );
104
105 it.each([
106 { authenticated: true, scopes: [AuthOrchestrationReadScope], auth: SPLIT_SCOPES_SERVER },
107 { authenticated: false, scopes: [AuthFilesystemReadScope], auth: SPLIT_SCOPES_SERVER },
108 ] as const)("does not infer file access from an ungranted session", (session) => {
109 expect(
110 resolveFilesystemReadAccess({
111 isCatalogReady: true,
112 connection: { phase: "connected", error: null },
113 session,
114 sessionError: null,
115 }),
116 ).toEqual({ canReadFiles: false, isPending: false, error: null });
117 });
118
119 it("falls back to the orchestration grant on a server that predates filesystem:read", () => {
120 expect(
121 resolveFilesystemReadAccess({
122 isCatalogReady: true,
123 connection: { phase: "connected", error: null },
124 session: { authenticated: true, scopes: [AuthOrchestrationReadScope], auth: {} },
125 sessionError: null,
126 }),
127 ).toEqual({ canReadFiles: true, isPending: false, error: null });
128 });
129});
130
131describe("filesystem browse model", () => {
132 it("derives the browse target and navigation state", () => {
133 expect(getFilesystemBrowsePath("~/projects/t3")).toEqual({
134 isBrowsing: true,
135 directoryPath: "~/projects/",
136 filterQuery: "t3",
137 parentPath: "~/",
138 canBrowseUp: true,
139 });
140 expect(getFilesystemBrowsePath("C:\\Users\\test", "MacIntel").isBrowsing).toBe(false);
141 expect(getFilesystemBrowsePath("~/projects/", "", false).isBrowsing).toBe(false);
142 });
143
144 it("filters names, hidden directories, and exact matches consistently", () => {
145 const entries = [
146 { name: ".config", fullPath: "/Users/test/.config" },
147 { name: "Code", fullPath: "/Users/test/Code" },
148 { name: "codething", fullPath: "/Users/test/codething" },
149 ];
150
151 expect(filterFilesystemBrowseEntries(entries, "co")).toEqual({
152 visibleEntries: entries.slice(1, 3),
153 exactEntry: null,
154 });
155 expect(filterFilesystemBrowseEntries(entries, "").visibleEntries).toEqual(entries.slice(1));
156 expect(filterFilesystemBrowseEntries(entries, ".").visibleEntries).toEqual(entries.slice(0, 1));
157 expect(filterFilesystemBrowseEntries(entries, "Code").exactEntry).toEqual(entries[1]);
158 });
159});
160
161describe("browse navigation", () => {
162 it("only commits the latest valid navigation", async () => {
163 const navigation = createBrowseNavigationCoordinator();
164 const first = Promise.withResolvers<void>();
165 const second = Promise.withResolvers<void>();
166 const commits: string[] = [];
167 const commit = (name: string) => () => commits.push(name);
168 const firstRun = navigation.run(() => first.promise, commit("first"));
169 const secondRun = navigation.run(() => second.promise, commit("second"));
170
171 second.resolve();
172 await expect(secondRun).resolves.toBe(true);
173 first.resolve();
174 await expect(firstRun).resolves.toBe(false);
175
176 const invalidated = Promise.withResolvers<void>();
177 const invalidatedRun = navigation.run(() => invalidated.promise, commit("stale"));
178 navigation.invalidate();
179 invalidated.resolve();
180
181 await expect(invalidatedRun).resolves.toBe(false);
182 expect(commits).toEqual(["second"]);
183 });
184
185 it("only preloads connected environments", () => {
186 expect(canPreloadBrowsePath("connected")).toBe(true);
187 expect(canPreloadBrowsePath("offline")).toBe(false);
188 expect(canPreloadBrowsePath("reconnecting")).toBe(false);
189 expect(canPreloadBrowsePath(null)).toBe(false);
190 });
191});