@uploadista/client-browser 0.0.20-beta.9 → 0.1.0-beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +8 -7
- package/src/__tests__/setup.ts +135 -0
- package/src/framework-utils.test.ts +519 -0
- package/src/http-client.test.ts +402 -0
- package/src/http-client.ts +11 -0
- package/src/services/abort-controller-factory.test.ts +163 -0
- package/src/services/checksum-service.test.ts +70 -0
- package/src/services/create-browser-services.test.ts +248 -0
- package/src/services/file-reader.test.ts +171 -0
- package/src/services/fingerprint-service.test.ts +123 -0
- package/src/services/id-generation/id-generation.test.ts +54 -0
- package/src/services/platform-service.test.ts +169 -0
- package/src/services/storage/local-storage-service.test.ts +168 -0
- package/src/services/storage/session-storage-service.test.ts +168 -0
- package/src/services/websocket-factory.test.ts +245 -0
- package/src/utils/hash-util.test.ts +84 -0
- package/vitest.config.ts +3 -1
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { createBrowserWebSocketFactory } from "./websocket-factory";
|
|
3
|
+
|
|
4
|
+
// Mock WebSocket
|
|
5
|
+
class MockWebSocket {
|
|
6
|
+
static CONNECTING = 0;
|
|
7
|
+
static OPEN = 1;
|
|
8
|
+
static CLOSING = 2;
|
|
9
|
+
static CLOSED = 3;
|
|
10
|
+
|
|
11
|
+
readyState = MockWebSocket.CONNECTING;
|
|
12
|
+
url: string;
|
|
13
|
+
onopen: (() => void) | null = null;
|
|
14
|
+
onclose: ((event: { code: number; reason: string }) => void) | null = null;
|
|
15
|
+
onerror: ((event: { message: string }) => void) | null = null;
|
|
16
|
+
onmessage: ((event: { data: string }) => void) | null = null;
|
|
17
|
+
|
|
18
|
+
private sentMessages: (string | Uint8Array)[] = [];
|
|
19
|
+
|
|
20
|
+
constructor(url: string) {
|
|
21
|
+
this.url = url;
|
|
22
|
+
// Simulate connection opening
|
|
23
|
+
setTimeout(() => {
|
|
24
|
+
this.readyState = MockWebSocket.OPEN;
|
|
25
|
+
this.onopen?.();
|
|
26
|
+
}, 0);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
send(data: string | Uint8Array): void {
|
|
30
|
+
this.sentMessages.push(data);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
close(code?: number, reason?: string): void {
|
|
34
|
+
this.readyState = MockWebSocket.CLOSING;
|
|
35
|
+
setTimeout(() => {
|
|
36
|
+
this.readyState = MockWebSocket.CLOSED;
|
|
37
|
+
this.onclose?.({ code: code ?? 1000, reason: reason ?? "" });
|
|
38
|
+
}, 0);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Test helpers
|
|
42
|
+
simulateMessage(data: string): void {
|
|
43
|
+
this.onmessage?.({ data });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
simulateError(message: string): void {
|
|
47
|
+
this.onerror?.({ message });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
getSentMessages(): (string | Uint8Array)[] {
|
|
51
|
+
return this.sentMessages;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
describe("createBrowserWebSocketFactory", () => {
|
|
56
|
+
let originalWebSocket: typeof WebSocket;
|
|
57
|
+
|
|
58
|
+
beforeEach(() => {
|
|
59
|
+
originalWebSocket = globalThis.WebSocket;
|
|
60
|
+
(globalThis as unknown as { WebSocket: typeof MockWebSocket }).WebSocket =
|
|
61
|
+
MockWebSocket;
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
afterEach(() => {
|
|
65
|
+
globalThis.WebSocket = originalWebSocket;
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("should create a WebSocket factory", () => {
|
|
69
|
+
const factory = createBrowserWebSocketFactory();
|
|
70
|
+
expect(factory).toBeDefined();
|
|
71
|
+
expect(factory.create).toBeDefined();
|
|
72
|
+
expect(typeof factory.create).toBe("function");
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe("create", () => {
|
|
76
|
+
it("should create a WebSocket connection", () => {
|
|
77
|
+
const factory = createBrowserWebSocketFactory();
|
|
78
|
+
const ws = factory.create("wss://api.example.com/ws");
|
|
79
|
+
|
|
80
|
+
expect(ws).toBeDefined();
|
|
81
|
+
expect(ws.readyState).toBe(0); // CONNECTING
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("should have CONNECTING, OPEN, CLOSING, CLOSED constants", () => {
|
|
85
|
+
const factory = createBrowserWebSocketFactory();
|
|
86
|
+
const ws = factory.create("wss://api.example.com/ws");
|
|
87
|
+
|
|
88
|
+
expect(ws.CONNECTING).toBe(0);
|
|
89
|
+
expect(ws.OPEN).toBe(1);
|
|
90
|
+
expect(ws.CLOSING).toBe(2);
|
|
91
|
+
expect(ws.CLOSED).toBe(3);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe("WebSocket events", () => {
|
|
96
|
+
it("should call onopen when connection opens", async () => {
|
|
97
|
+
const factory = createBrowserWebSocketFactory();
|
|
98
|
+
const ws = factory.create("wss://api.example.com/ws");
|
|
99
|
+
|
|
100
|
+
const onopen = vi.fn();
|
|
101
|
+
ws.onopen = onopen;
|
|
102
|
+
|
|
103
|
+
// Wait for connection to open
|
|
104
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
105
|
+
|
|
106
|
+
expect(onopen).toHaveBeenCalled();
|
|
107
|
+
expect(ws.readyState).toBe(1); // OPEN
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("should call onclose when connection closes", async () => {
|
|
111
|
+
const factory = createBrowserWebSocketFactory();
|
|
112
|
+
const ws = factory.create("wss://api.example.com/ws");
|
|
113
|
+
|
|
114
|
+
const onclose = vi.fn();
|
|
115
|
+
ws.onclose = onclose;
|
|
116
|
+
|
|
117
|
+
// Wait for connection to open
|
|
118
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
119
|
+
|
|
120
|
+
// Close the connection
|
|
121
|
+
ws.close(1000, "Normal closure");
|
|
122
|
+
|
|
123
|
+
// Wait for close event
|
|
124
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
125
|
+
|
|
126
|
+
expect(onclose).toHaveBeenCalledWith({
|
|
127
|
+
code: 1000,
|
|
128
|
+
reason: "Normal closure",
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("should call onerror when error occurs", async () => {
|
|
133
|
+
const factory = createBrowserWebSocketFactory();
|
|
134
|
+
const ws = factory.create("wss://api.example.com/ws");
|
|
135
|
+
|
|
136
|
+
const onerror = vi.fn();
|
|
137
|
+
ws.onerror = onerror;
|
|
138
|
+
|
|
139
|
+
// Wait for connection to open
|
|
140
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
141
|
+
|
|
142
|
+
// Simulate error using the mock helper
|
|
143
|
+
const nativeWs = (ws as unknown as { native: MockWebSocket }).native;
|
|
144
|
+
if (nativeWs && "simulateError" in nativeWs) {
|
|
145
|
+
nativeWs.simulateError("Test error");
|
|
146
|
+
} else {
|
|
147
|
+
// Fallback: call onerror directly
|
|
148
|
+
ws.onerror?.({ message: "WebSocket error" });
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
expect(onerror).toHaveBeenCalled();
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("should call onmessage when message received", async () => {
|
|
155
|
+
const factory = createBrowserWebSocketFactory();
|
|
156
|
+
const ws = factory.create("wss://api.example.com/ws");
|
|
157
|
+
|
|
158
|
+
const onmessage = vi.fn();
|
|
159
|
+
ws.onmessage = onmessage;
|
|
160
|
+
|
|
161
|
+
// Wait for connection to open
|
|
162
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
163
|
+
|
|
164
|
+
// Simulate message
|
|
165
|
+
const nativeWs = (ws as unknown as { native: MockWebSocket }).native;
|
|
166
|
+
if (nativeWs && "simulateMessage" in nativeWs) {
|
|
167
|
+
nativeWs.simulateMessage('{"type": "test"}');
|
|
168
|
+
} else {
|
|
169
|
+
// Fallback
|
|
170
|
+
ws.onmessage?.({ data: '{"type": "test"}' });
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
expect(onmessage).toHaveBeenCalledWith({ data: '{"type": "test"}' });
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
describe("send", () => {
|
|
178
|
+
it("should send string data", async () => {
|
|
179
|
+
const factory = createBrowserWebSocketFactory();
|
|
180
|
+
const ws = factory.create("wss://api.example.com/ws");
|
|
181
|
+
|
|
182
|
+
// Wait for connection to open
|
|
183
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
184
|
+
|
|
185
|
+
ws.send('{"type": "subscribe"}');
|
|
186
|
+
|
|
187
|
+
// Verify message was sent (would check mock in real scenario)
|
|
188
|
+
expect(ws.readyState).toBe(1); // OPEN
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it("should send binary data", async () => {
|
|
192
|
+
const factory = createBrowserWebSocketFactory();
|
|
193
|
+
const ws = factory.create("wss://api.example.com/ws");
|
|
194
|
+
|
|
195
|
+
// Wait for connection to open
|
|
196
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
197
|
+
|
|
198
|
+
const binaryData = new Uint8Array([1, 2, 3, 4]);
|
|
199
|
+
ws.send(binaryData);
|
|
200
|
+
|
|
201
|
+
expect(ws.readyState).toBe(1); // OPEN
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
describe("close", () => {
|
|
206
|
+
it("should close with default code", async () => {
|
|
207
|
+
const factory = createBrowserWebSocketFactory();
|
|
208
|
+
const ws = factory.create("wss://api.example.com/ws");
|
|
209
|
+
|
|
210
|
+
const onclose = vi.fn();
|
|
211
|
+
ws.onclose = onclose;
|
|
212
|
+
|
|
213
|
+
// Wait for connection to open
|
|
214
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
215
|
+
|
|
216
|
+
ws.close();
|
|
217
|
+
|
|
218
|
+
// Wait for close event
|
|
219
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
220
|
+
|
|
221
|
+
expect(ws.readyState).toBe(3); // CLOSED
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it("should close with custom code and reason", async () => {
|
|
225
|
+
const factory = createBrowserWebSocketFactory();
|
|
226
|
+
const ws = factory.create("wss://api.example.com/ws");
|
|
227
|
+
|
|
228
|
+
const onclose = vi.fn();
|
|
229
|
+
ws.onclose = onclose;
|
|
230
|
+
|
|
231
|
+
// Wait for connection to open
|
|
232
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
233
|
+
|
|
234
|
+
ws.close(1001, "Going away");
|
|
235
|
+
|
|
236
|
+
// Wait for close event
|
|
237
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
238
|
+
|
|
239
|
+
expect(onclose).toHaveBeenCalledWith({
|
|
240
|
+
code: 1001,
|
|
241
|
+
reason: "Going away",
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { computeblobSha256 } from "./hash-util";
|
|
3
|
+
|
|
4
|
+
describe("computeblobSha256", () => {
|
|
5
|
+
it("should compute SHA-256 hash of a blob", async () => {
|
|
6
|
+
const blob = new Blob(["Hello, World!"], { type: "text/plain" });
|
|
7
|
+
const hash = await computeblobSha256(blob);
|
|
8
|
+
|
|
9
|
+
// Hash should be a 64-character hex string
|
|
10
|
+
expect(hash).toHaveLength(64);
|
|
11
|
+
expect(hash).toMatch(/^[a-f0-9]{64}$/);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("should compute SHA-256 hash of a File", async () => {
|
|
15
|
+
const file = new File(["Test content"], "test.txt", { type: "text/plain" });
|
|
16
|
+
const hash = await computeblobSha256(file);
|
|
17
|
+
|
|
18
|
+
expect(hash).toHaveLength(64);
|
|
19
|
+
expect(hash).toMatch(/^[a-f0-9]{64}$/);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("should return same hash for identical content", async () => {
|
|
23
|
+
const content = "Same content for both";
|
|
24
|
+
const blob1 = new Blob([content], { type: "text/plain" });
|
|
25
|
+
const blob2 = new Blob([content], { type: "text/plain" });
|
|
26
|
+
|
|
27
|
+
const hash1 = await computeblobSha256(blob1);
|
|
28
|
+
const hash2 = await computeblobSha256(blob2);
|
|
29
|
+
|
|
30
|
+
expect(hash1).toBe(hash2);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("should return different hash for different content", async () => {
|
|
34
|
+
const blob1 = new Blob(["Content A"], { type: "text/plain" });
|
|
35
|
+
const blob2 = new Blob(["Content B"], { type: "text/plain" });
|
|
36
|
+
|
|
37
|
+
const hash1 = await computeblobSha256(blob1);
|
|
38
|
+
const hash2 = await computeblobSha256(blob2);
|
|
39
|
+
|
|
40
|
+
expect(hash1).not.toBe(hash2);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("should handle empty blob", async () => {
|
|
44
|
+
const blob = new Blob([], { type: "text/plain" });
|
|
45
|
+
const hash = await computeblobSha256(blob);
|
|
46
|
+
|
|
47
|
+
expect(hash).toHaveLength(64);
|
|
48
|
+
expect(hash).toMatch(/^[a-f0-9]{64}$/);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("should handle binary content", async () => {
|
|
52
|
+
const binaryData = new Uint8Array([0, 1, 2, 255, 254, 253]);
|
|
53
|
+
const blob = new Blob([binaryData], { type: "application/octet-stream" });
|
|
54
|
+
const hash = await computeblobSha256(blob);
|
|
55
|
+
|
|
56
|
+
expect(hash).toHaveLength(64);
|
|
57
|
+
expect(hash).toMatch(/^[a-f0-9]{64}$/);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("should throw error when crypto API fails", async () => {
|
|
61
|
+
// Save original
|
|
62
|
+
const originalSubtle = crypto.subtle;
|
|
63
|
+
|
|
64
|
+
// Mock crypto.subtle.digest to throw
|
|
65
|
+
Object.defineProperty(crypto, "subtle", {
|
|
66
|
+
value: {
|
|
67
|
+
digest: vi.fn().mockRejectedValue(new Error("Crypto error")),
|
|
68
|
+
},
|
|
69
|
+
writable: true,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const blob = new Blob(["test"], { type: "text/plain" });
|
|
73
|
+
|
|
74
|
+
await expect(computeblobSha256(blob)).rejects.toThrow(
|
|
75
|
+
"Failed to compute file checksum"
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
// Restore original
|
|
79
|
+
Object.defineProperty(crypto, "subtle", {
|
|
80
|
+
value: originalSubtle,
|
|
81
|
+
writable: true,
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
});
|
package/vitest.config.ts
CHANGED
|
@@ -3,9 +3,10 @@ import { defineConfig } from "vitest/config";
|
|
|
3
3
|
export default defineConfig({
|
|
4
4
|
test: {
|
|
5
5
|
globals: true,
|
|
6
|
-
environment: "
|
|
6
|
+
environment: "happy-dom",
|
|
7
7
|
include: ["src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"],
|
|
8
8
|
exclude: ["node_modules", "dist"],
|
|
9
|
+
setupFiles: ["./src/__tests__/setup.ts"],
|
|
9
10
|
coverage: {
|
|
10
11
|
provider: "v8",
|
|
11
12
|
reporter: ["text", "json", "html"],
|
|
@@ -15,6 +16,7 @@ export default defineConfig({
|
|
|
15
16
|
"**/*.d.ts",
|
|
16
17
|
"**/*.test.ts",
|
|
17
18
|
"**/*.spec.ts",
|
|
19
|
+
"**/__tests__/**",
|
|
18
20
|
],
|
|
19
21
|
},
|
|
20
22
|
},
|