Skip to content

Commit 6b14f07

Browse files
authored
Merge pull request #439 from ucdjs/bridge-improvements
2 parents c593695 + 5334e8c commit 6b14f07

39 files changed

+1253
-114
lines changed

packages/cli/test/cmd/store/analyze.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ describe("store analyze command", () => {
195195
expect(version16).toHaveProperty("counts");
196196
});
197197

198-
it("should show complete status for store with all files", async () => {
198+
it.todo("should show complete status for store with all files", async () => {
199199
const storePath = await testdir();
200200

201201
const singleFileTree = [{

packages/fs-bridge/eslint.config.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@ export default luxass({
55
type: "lib",
66
pnpm: true,
77
}).append({
8-
ignores: ["src/bridges/node.ts", ...GLOB_TESTS],
8+
ignores: [
9+
"src/bridges/node.ts",
10+
"playgrounds/**",
11+
...GLOB_TESTS,
12+
],
913
rules: {
1014
"no-restricted-imports": ["error", {
1115
patterns: [

packages/fs-bridge/package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
".": "./dist/index.mjs",
2727
"./bridges/http": "./dist/bridges/http.mjs",
2828
"./bridges/node": "./dist/bridges/node.mjs",
29+
"./errors": "./dist/errors.mjs",
2930
"./package.json": "./package.json"
3031
},
3132
"main": "./dist/index.mjs",
@@ -42,7 +43,9 @@
4243
"dev": "tsdown --watch",
4344
"clean": "git clean -xdf dist node_modules",
4445
"lint": "eslint .",
45-
"typecheck": "tsc --noEmit"
46+
"typecheck": "tsc --noEmit",
47+
"playground:node": "tsx --tsconfig=./tsconfig.json ./playgrounds/node-playground.ts",
48+
"playground:http": "tsx --tsconfig=./tsconfig.json ./playgrounds/http-playground.ts"
4649
},
4750
"dependencies": {
4851
"@luxass/utils": "catalog:prod",
@@ -57,6 +60,7 @@
5760
},
5861
"devDependencies": {
5962
"@luxass/eslint-config": "catalog:linting",
63+
"@luxass/msw-utils": "catalog:prod",
6064
"@ucdjs-internal/shared": "workspace:*",
6165
"@ucdjs-tooling/tsconfig": "workspace:*",
6266
"@ucdjs-tooling/tsdown-config": "workspace:*",
Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
/* eslint-disable no-console, antfu/no-top-level-await */
2+
/**
3+
* fs-bridge HTTP Playground
4+
*
5+
* This playground verifies the HTTP fs-bridge works correctly against
6+
* the real UCD.js API. It tests read, exists, and listdir operations.
7+
*
8+
* Configure with: FS_BRIDGE_HTTP_BASE_URL env var
9+
* Run with: pnpm playground:http
10+
*/
11+
12+
import process from "node:process";
13+
import { assertCapability } from "../src";
14+
import HTTPFileSystemBridge from "../src/bridges/http";
15+
16+
interface TestCase {
17+
description: string;
18+
run: () => Promise<void>;
19+
}
20+
21+
const BASE_URL = process.env.FS_BRIDGE_HTTP_BASE_URL || "https://api.ucdjs.dev/api/v1/files";
22+
23+
console.log("fs-bridge HTTP Playground\n");
24+
console.log("=".repeat(60));
25+
console.log(`\nBase URL: ${BASE_URL}\n`);
26+
27+
const bridge = HTTPFileSystemBridge({ baseUrl: BASE_URL });
28+
29+
console.log("=".repeat(60));
30+
31+
const testCases: TestCase[] = [
32+
// Capability tests (HTTP bridge is read-only)
33+
{
34+
description: "Bridge does NOT have write capability",
35+
async run() {
36+
if (bridge.optionalCapabilities.write) throw new Error("Should not have write");
37+
try {
38+
assertCapability(bridge, "write");
39+
throw new Error("Should have thrown");
40+
} catch (err) {
41+
if (err instanceof Error && err.message === "Should have thrown") throw err;
42+
}
43+
},
44+
},
45+
{
46+
description: "Bridge does NOT have mkdir capability",
47+
async run() {
48+
if (bridge.optionalCapabilities.mkdir) throw new Error("Should not have mkdir");
49+
try {
50+
assertCapability(bridge, "mkdir");
51+
throw new Error("Should have thrown");
52+
} catch (err) {
53+
if (err instanceof Error && err.message === "Should have thrown") throw err;
54+
}
55+
},
56+
},
57+
{
58+
description: "Bridge does NOT have rm capability",
59+
async run() {
60+
if (bridge.optionalCapabilities.rm) throw new Error("Should not have rm");
61+
try {
62+
assertCapability(bridge, "rm");
63+
throw new Error("Should have thrown");
64+
} catch (err) {
65+
if (err instanceof Error && err.message === "Should have thrown") throw err;
66+
}
67+
},
68+
},
69+
70+
// Read tests
71+
{
72+
description: "Read 16.0.0/ReadMe.txt",
73+
async run() {
74+
const content = await bridge.read("16.0.0/ReadMe.txt");
75+
if (typeof content !== "string") throw new Error("Content should be string");
76+
if (content.length === 0) throw new Error("Content should not be empty");
77+
if (!content.includes("Unicode")) throw new Error("Should mention Unicode");
78+
},
79+
},
80+
{
81+
description: "Read with / prefix",
82+
async run() {
83+
const content = await bridge.read("/16.0.0/ReadMe.txt");
84+
if (!content.includes("Unicode")) throw new Error("Should work with / prefix");
85+
},
86+
},
87+
{
88+
description: "Read non-existent file throws",
89+
async run() {
90+
try {
91+
await bridge.read("16.0.0/NonExistent12345.txt");
92+
throw new Error("Should have thrown");
93+
} catch (err) {
94+
if (err instanceof Error && err.message === "Should have thrown") throw err;
95+
}
96+
},
97+
},
98+
{
99+
description: "Read trailing slash throws",
100+
async run() {
101+
try {
102+
await bridge.read("16.0.0/ReadMe.txt/");
103+
throw new Error("Should have thrown");
104+
} catch (err) {
105+
if (err instanceof Error && err.message === "Should have thrown") throw err;
106+
}
107+
},
108+
},
109+
110+
// Exists tests
111+
{
112+
description: "Exists returns true for 16.0.0/ReadMe.txt",
113+
async run() {
114+
if (!(await bridge.exists("16.0.0/ReadMe.txt"))) throw new Error("Should exist");
115+
},
116+
},
117+
{
118+
description: "Exists returns false for non-existent",
119+
async run() {
120+
if (await bridge.exists("16.0.0/NonExistent12345.txt")) throw new Error("Should not exist");
121+
},
122+
},
123+
{
124+
description: "Exists with / prefix",
125+
async run() {
126+
if (!(await bridge.exists("/16.0.0/ReadMe.txt"))) throw new Error("Should exist");
127+
},
128+
},
129+
130+
// Listdir tests
131+
{
132+
description: "Listdir 16.0.0 returns entries",
133+
async run() {
134+
const entries = await bridge.listdir("16.0.0");
135+
if (!Array.isArray(entries)) throw new Error("Should return array");
136+
if (entries.length === 0) throw new Error("Should have entries");
137+
const hasReadme = entries.some((e) => e.name === "ReadMe.txt");
138+
if (!hasReadme) throw new Error("Should contain ReadMe.txt");
139+
},
140+
},
141+
{
142+
description: "Listdir shallow has empty children",
143+
async run() {
144+
const entries = await bridge.listdir("16.0.0");
145+
const dir = entries.find((e) => e.type === "directory");
146+
if (dir && dir.type === "directory" && dir.children.length > 0) {
147+
throw new Error("Shallow should have empty children");
148+
}
149+
},
150+
},
151+
{
152+
description: "Listdir recursive populates children",
153+
async run() {
154+
const entries = await bridge.listdir("16.0.0/ucd", true);
155+
// Note: this may not find a dir with children if ucd has no subdirs
156+
if (!Array.isArray(entries)) throw new Error("Should return array");
157+
},
158+
},
159+
{
160+
description: "Listdir non-existent returns empty array",
161+
async run() {
162+
const entries = await bridge.listdir("NonExistentVersion12345");
163+
if (!Array.isArray(entries) || entries.length !== 0) {
164+
throw new Error("Should return empty array");
165+
}
166+
},
167+
},
168+
{
169+
description: "Listdir with / prefix",
170+
async run() {
171+
const entries = await bridge.listdir("/16.0.0");
172+
if (entries.length === 0) throw new Error("Should have entries");
173+
},
174+
},
175+
176+
// Unsupported operations throw
177+
{
178+
description: "Write operation throws",
179+
async run() {
180+
try {
181+
await bridge.write?.("test.txt", "content");
182+
throw new Error("Should have thrown");
183+
} catch (err) {
184+
if (err instanceof Error && err.message === "Should have thrown") throw err;
185+
}
186+
},
187+
},
188+
{
189+
description: "Mkdir operation throws",
190+
async run() {
191+
try {
192+
await bridge.mkdir?.("new-dir");
193+
throw new Error("Should have thrown");
194+
} catch (err) {
195+
if (err instanceof Error && err.message === "Should have thrown") throw err;
196+
}
197+
},
198+
},
199+
{
200+
description: "Rm operation throws",
201+
async run() {
202+
try {
203+
await bridge.rm?.("test.txt");
204+
throw new Error("Should have thrown");
205+
} catch (err) {
206+
if (err instanceof Error && err.message === "Should have thrown") throw err;
207+
}
208+
},
209+
},
210+
211+
// Bridge metadata
212+
{
213+
description: "Bridge has correct metadata",
214+
async run() {
215+
if (bridge.meta.name !== "HTTP File System Bridge") throw new Error("Wrong name");
216+
if (typeof bridge.meta.description !== "string") throw new Error("Missing description");
217+
},
218+
},
219+
220+
// Complex workflow
221+
{
222+
description: "Discover and read UnicodeData.txt",
223+
async run() {
224+
const entries = await bridge.listdir("16.0.0/ucd");
225+
const unicodeData = entries.find((e) => e.name === "UnicodeData.txt");
226+
if (!unicodeData) throw new Error("UnicodeData.txt not found");
227+
if (unicodeData.type !== "file") throw new Error("Should be a file");
228+
229+
const exists = await bridge.exists("16.0.0/ucd/UnicodeData.txt");
230+
if (!exists) throw new Error("Should exist");
231+
232+
const content = await bridge.read("16.0.0/ucd/UnicodeData.txt");
233+
if (!content.includes(";")) throw new Error("Should contain semicolons");
234+
},
235+
},
236+
];
237+
238+
console.log("\nRunning test cases:\n");
239+
240+
let passed = 0;
241+
let failed = 0;
242+
243+
for (const testCase of testCases) {
244+
console.log(` ${testCase.description}... `);
245+
246+
try {
247+
await testCase.run();
248+
console.log("PASS");
249+
passed++;
250+
} catch (error) {
251+
console.log("FAIL");
252+
console.log(` Error: ${(error as Error).message}`);
253+
failed++;
254+
}
255+
}
256+
257+
console.log(`\n${"=".repeat(60)}`);
258+
console.log(`\nResults: ${passed} passed, ${failed} failed\n`);
259+
260+
if (failed > 0) {
261+
process.exit(1);
262+
}

0 commit comments

Comments
 (0)