@uploadista/client-browser 0.0.20 → 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.
@@ -0,0 +1,248 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { createBrowserServices, type BrowserServiceOptions } from "./create-browser-services";
3
+
4
+ describe("createBrowserServices", () => {
5
+ it("should create a service container with default options", () => {
6
+ const services = createBrowserServices();
7
+
8
+ expect(services).toBeDefined();
9
+ expect(services.platform).toBeDefined();
10
+ expect(services.storage).toBeDefined();
11
+ expect(services.idGeneration).toBeDefined();
12
+ expect(services.httpClient).toBeDefined();
13
+ expect(services.fileReader).toBeDefined();
14
+ expect(services.websocket).toBeDefined();
15
+ expect(services.abortController).toBeDefined();
16
+ expect(services.checksumService).toBeDefined();
17
+ expect(services.fingerprintService).toBeDefined();
18
+ });
19
+
20
+ it("should create a service container with custom options", () => {
21
+ const options: BrowserServiceOptions = {
22
+ useLocalStorage: true,
23
+ connectionPooling: {
24
+ maxConnectionsPerHost: 10,
25
+ connectionTimeout: 60000,
26
+ keepAliveTimeout: 120000,
27
+ enableHttp2: true,
28
+ retryOnConnectionError: true,
29
+ },
30
+ };
31
+
32
+ const services = createBrowserServices(options);
33
+
34
+ expect(services).toBeDefined();
35
+ expect(services.platform).toBeDefined();
36
+ expect(services.storage).toBeDefined();
37
+ });
38
+
39
+ it("should use localStorage when useLocalStorage is true", () => {
40
+ const services = createBrowserServices({ useLocalStorage: true });
41
+
42
+ // Verify storage service is created
43
+ expect(services.storage).toBeDefined();
44
+ expect(services.storage.getItem).toBeDefined();
45
+ expect(services.storage.setItem).toBeDefined();
46
+ expect(services.storage.removeItem).toBeDefined();
47
+ expect(services.storage.find).toBeDefined();
48
+ expect(services.storage.findAll).toBeDefined();
49
+ });
50
+
51
+ it("should use localStorage as default when useLocalStorage is false", () => {
52
+ // Currently the implementation uses localStorage as fallback
53
+ const services = createBrowserServices({ useLocalStorage: false });
54
+
55
+ expect(services.storage).toBeDefined();
56
+ expect(services.storage.getItem).toBeDefined();
57
+ });
58
+
59
+ describe("platform service", () => {
60
+ it("should have all platform service methods", () => {
61
+ const services = createBrowserServices();
62
+ const platform = services.platform;
63
+
64
+ expect(platform.setTimeout).toBeDefined();
65
+ expect(platform.clearTimeout).toBeDefined();
66
+ expect(platform.isBrowser).toBeDefined();
67
+ expect(platform.isOnline).toBeDefined();
68
+ expect(platform.isFileLike).toBeDefined();
69
+ expect(platform.getFileName).toBeDefined();
70
+ expect(platform.getFileType).toBeDefined();
71
+ expect(platform.getFileSize).toBeDefined();
72
+ expect(platform.getFileLastModified).toBeDefined();
73
+ });
74
+ });
75
+
76
+ describe("storage service", () => {
77
+ it("should have all storage service methods", () => {
78
+ const services = createBrowserServices();
79
+ const storage = services.storage;
80
+
81
+ expect(typeof storage.getItem).toBe("function");
82
+ expect(typeof storage.setItem).toBe("function");
83
+ expect(typeof storage.removeItem).toBe("function");
84
+ expect(typeof storage.find).toBe("function");
85
+ expect(typeof storage.findAll).toBe("function");
86
+ });
87
+ });
88
+
89
+ describe("idGeneration service", () => {
90
+ it("should generate unique IDs", () => {
91
+ const services = createBrowserServices();
92
+ const id1 = services.idGeneration.generate();
93
+ const id2 = services.idGeneration.generate();
94
+
95
+ expect(id1).toBeDefined();
96
+ expect(id2).toBeDefined();
97
+ expect(id1).not.toBe(id2);
98
+ });
99
+ });
100
+
101
+ describe("httpClient", () => {
102
+ it("should have all HTTP client methods", () => {
103
+ const services = createBrowserServices();
104
+ const httpClient = services.httpClient;
105
+
106
+ expect(httpClient.request).toBeDefined();
107
+ expect(httpClient.getMetrics).toBeDefined();
108
+ expect(httpClient.getDetailedMetrics).toBeDefined();
109
+ expect(httpClient.warmupConnections).toBeDefined();
110
+ expect(httpClient.reset).toBeDefined();
111
+ expect(httpClient.close).toBeDefined();
112
+ });
113
+ });
114
+
115
+ describe("fileReader service", () => {
116
+ it("should have openFile method", () => {
117
+ const services = createBrowserServices();
118
+ const fileReader = services.fileReader;
119
+
120
+ expect(fileReader.openFile).toBeDefined();
121
+ expect(typeof fileReader.openFile).toBe("function");
122
+ });
123
+
124
+ it("should open a file", async () => {
125
+ const services = createBrowserServices();
126
+ const file = new File(["test content"], "test.txt", {
127
+ type: "text/plain",
128
+ });
129
+
130
+ const source = await services.fileReader.openFile(file, 1024);
131
+
132
+ expect(source).toBeDefined();
133
+ expect(source.size).toBe(file.size);
134
+ expect(source.name).toBe("test.txt");
135
+ });
136
+ });
137
+
138
+ describe("websocket factory", () => {
139
+ it("should have create method", () => {
140
+ const services = createBrowserServices();
141
+ const websocket = services.websocket;
142
+
143
+ expect(websocket.create).toBeDefined();
144
+ expect(typeof websocket.create).toBe("function");
145
+ });
146
+ });
147
+
148
+ describe("abortController factory", () => {
149
+ it("should have create method", () => {
150
+ const services = createBrowserServices();
151
+ const abortController = services.abortController;
152
+
153
+ expect(abortController.create).toBeDefined();
154
+ expect(typeof abortController.create).toBe("function");
155
+ });
156
+
157
+ it("should create working abort controllers", () => {
158
+ const services = createBrowserServices();
159
+ const controller = services.abortController.create();
160
+
161
+ expect(controller.signal.aborted).toBe(false);
162
+ controller.abort();
163
+ expect(controller.signal.aborted).toBe(true);
164
+ });
165
+ });
166
+
167
+ describe("checksumService", () => {
168
+ it("should compute checksums", async () => {
169
+ const services = createBrowserServices();
170
+ const data = new Uint8Array([1, 2, 3, 4, 5]);
171
+
172
+ const checksum = await services.checksumService.computeChecksum(data);
173
+
174
+ expect(checksum).toBeDefined();
175
+ expect(checksum).toHaveLength(64);
176
+ expect(checksum).toMatch(/^[a-f0-9]{64}$/);
177
+ });
178
+ });
179
+
180
+ describe("fingerprintService", () => {
181
+ it("should compute fingerprints", async () => {
182
+ const services = createBrowserServices();
183
+ const file = new File(["test content"], "test.txt", {
184
+ type: "text/plain",
185
+ });
186
+
187
+ const fingerprint = await services.fingerprintService.computeFingerprint(
188
+ file,
189
+ "https://api.example.com"
190
+ );
191
+
192
+ expect(fingerprint).toBeDefined();
193
+ expect(fingerprint).toHaveLength(64);
194
+ expect(fingerprint).toMatch(/^[a-f0-9]{64}$/);
195
+ });
196
+ });
197
+
198
+ describe("integration", () => {
199
+ it("should work together for a typical upload workflow", async () => {
200
+ const services = createBrowserServices();
201
+
202
+ // 1. Check if we're in browser
203
+ expect(services.platform.isBrowser()).toBe(true);
204
+
205
+ // 2. Generate an upload ID
206
+ const uploadId = services.idGeneration.generate();
207
+ expect(uploadId).toMatch(/^[0-9a-f-]{36}$/i);
208
+
209
+ // 3. Create a file and open it
210
+ const file = new File(["test file content"], "upload.txt", {
211
+ type: "text/plain",
212
+ });
213
+ const source = await services.fileReader.openFile(file, 1024);
214
+ expect(source.size).toBe(file.size);
215
+
216
+ // 4. Compute fingerprint for deduplication
217
+ const fingerprint = await services.fingerprintService.computeFingerprint(
218
+ file,
219
+ "https://api.example.com"
220
+ );
221
+ expect(fingerprint).toHaveLength(64);
222
+
223
+ // 5. Create an abort controller for cancellation
224
+ const controller = services.abortController.create();
225
+ expect(controller.signal.aborted).toBe(false);
226
+
227
+ // 6. Store upload state
228
+ await services.storage.setItem(
229
+ `upload:${uploadId}`,
230
+ JSON.stringify({
231
+ id: uploadId,
232
+ fingerprint,
233
+ status: "pending",
234
+ })
235
+ );
236
+
237
+ // 7. Retrieve stored state
238
+ const storedState = await services.storage.getItem(`upload:${uploadId}`);
239
+ expect(storedState).toBeDefined();
240
+ expect(JSON.parse(storedState!).id).toBe(uploadId);
241
+
242
+ // 8. Clean up
243
+ await services.storage.removeItem(`upload:${uploadId}`);
244
+ const removed = await services.storage.getItem(`upload:${uploadId}`);
245
+ expect(removed).toBeNull();
246
+ });
247
+ });
248
+ });
@@ -0,0 +1,171 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { createBrowserFileReaderService } from "./file-reader";
3
+
4
+ describe("createBrowserFileReaderService", () => {
5
+ it("should create a file reader service", () => {
6
+ const service = createBrowserFileReaderService();
7
+ expect(service).toBeDefined();
8
+ expect(service.openFile).toBeDefined();
9
+ expect(typeof service.openFile).toBe("function");
10
+ });
11
+
12
+ describe("openFile", () => {
13
+ it("should open a File object", async () => {
14
+ const service = createBrowserFileReaderService();
15
+ const file = new File(["Hello, World!"], "test.txt", {
16
+ type: "text/plain",
17
+ });
18
+
19
+ const source = await service.openFile(file, 1024);
20
+
21
+ expect(source).toBeDefined();
22
+ expect(source.input).toBe(file);
23
+ expect(source.size).toBe(file.size);
24
+ expect(source.name).toBe("test.txt");
25
+ expect(source.type).toBe("text/plain");
26
+ expect(source.lastModified).toBeDefined();
27
+ });
28
+
29
+ it("should open a Blob object", async () => {
30
+ const service = createBrowserFileReaderService();
31
+ const blob = new Blob(["Hello, World!"], { type: "text/plain" });
32
+
33
+ const source = await service.openFile(blob, 1024);
34
+
35
+ expect(source).toBeDefined();
36
+ expect(source.input).toBe(blob);
37
+ expect(source.size).toBe(blob.size);
38
+ expect(source.name).toBeNull();
39
+ expect(source.type).toBeNull();
40
+ expect(source.lastModified).toBeNull();
41
+ });
42
+
43
+ it("should throw for invalid input", async () => {
44
+ const service = createBrowserFileReaderService();
45
+
46
+ await expect(
47
+ service.openFile("invalid" as unknown as Blob, 1024)
48
+ ).rejects.toThrow("source object may only be an instance of File, Blob");
49
+ });
50
+ });
51
+
52
+ describe("FileSource", () => {
53
+ describe("slice", () => {
54
+ it("should read a slice of data", async () => {
55
+ const service = createBrowserFileReaderService();
56
+ const content = "Hello, World!";
57
+ const file = new File([content], "test.txt", { type: "text/plain" });
58
+
59
+ const source = await service.openFile(file, 1024);
60
+ const result = await source.slice(0, 5);
61
+
62
+ expect(result.value).toBeInstanceOf(Uint8Array);
63
+ expect(result.size).toBe(5);
64
+ expect(result.done).toBe(false);
65
+
66
+ // Verify content
67
+ const text = new TextDecoder().decode(result.value);
68
+ expect(text).toBe("Hello");
69
+ });
70
+
71
+ it("should read the entire file", async () => {
72
+ const service = createBrowserFileReaderService();
73
+ const content = "Hello";
74
+ const file = new File([content], "test.txt", { type: "text/plain" });
75
+
76
+ const source = await service.openFile(file, 1024);
77
+ const result = await source.slice(0, file.size);
78
+
79
+ expect(result.value).toBeInstanceOf(Uint8Array);
80
+ expect(result.size).toBe(5);
81
+ expect(result.done).toBe(true);
82
+
83
+ const text = new TextDecoder().decode(result.value);
84
+ expect(text).toBe("Hello");
85
+ });
86
+
87
+ it("should read from middle of file", async () => {
88
+ const service = createBrowserFileReaderService();
89
+ const content = "Hello, World!";
90
+ const file = new File([content], "test.txt", { type: "text/plain" });
91
+
92
+ const source = await service.openFile(file, 1024);
93
+ const result = await source.slice(7, 12);
94
+
95
+ const text = new TextDecoder().decode(result.value);
96
+ expect(text).toBe("World");
97
+ });
98
+
99
+ it("should handle empty slice", async () => {
100
+ const service = createBrowserFileReaderService();
101
+ const content = "Hello";
102
+ const file = new File([content], "test.txt", { type: "text/plain" });
103
+
104
+ const source = await service.openFile(file, 1024);
105
+ const result = await source.slice(0, 0);
106
+
107
+ expect(result.size).toBe(0);
108
+ });
109
+
110
+ it("should handle binary data", async () => {
111
+ const service = createBrowserFileReaderService();
112
+ const binaryData = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
113
+ const blob = new Blob([binaryData], { type: "application/octet-stream" });
114
+
115
+ const source = await service.openFile(blob, 1024);
116
+ const result = await source.slice(0, 5);
117
+
118
+ expect(result.value).toEqual(new Uint8Array([0, 1, 2, 3, 4]));
119
+ });
120
+ });
121
+
122
+ describe("close", () => {
123
+ it("should not throw when closing", async () => {
124
+ const service = createBrowserFileReaderService();
125
+ const file = new File(["Hello"], "test.txt", { type: "text/plain" });
126
+
127
+ const source = await service.openFile(file, 1024);
128
+
129
+ expect(() => source.close()).not.toThrow();
130
+ });
131
+ });
132
+ });
133
+
134
+ describe("chunked reading", () => {
135
+ it("should read file in chunks", async () => {
136
+ const service = createBrowserFileReaderService();
137
+ const content = "ABCDEFGHIJ"; // 10 bytes
138
+ const file = new File([content], "test.txt", { type: "text/plain" });
139
+ const chunkSize = 3;
140
+
141
+ const source = await service.openFile(file, chunkSize);
142
+ const chunks: Uint8Array[] = [];
143
+ let offset = 0;
144
+
145
+ while (offset < file.size) {
146
+ const end = Math.min(offset + chunkSize, file.size);
147
+ const result = await source.slice(offset, end);
148
+ chunks.push(result.value);
149
+ offset = end;
150
+
151
+ if (result.done) break;
152
+ }
153
+
154
+ // Should have 4 chunks: ABC, DEF, GHI, J
155
+ expect(chunks).toHaveLength(4);
156
+
157
+ // Verify combined content
158
+ const combined = new Uint8Array(
159
+ chunks.reduce((acc, chunk) => acc + chunk.length, 0)
160
+ );
161
+ let pos = 0;
162
+ for (const chunk of chunks) {
163
+ combined.set(chunk, pos);
164
+ pos += chunk.length;
165
+ }
166
+
167
+ const text = new TextDecoder().decode(combined);
168
+ expect(text).toBe(content);
169
+ });
170
+ });
171
+ });
@@ -0,0 +1,123 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { createFingerprintService } from "./fingerprint-service";
3
+
4
+ describe("createFingerprintService", () => {
5
+ it("should create a fingerprint service", () => {
6
+ const service = createFingerprintService();
7
+ expect(service).toBeDefined();
8
+ expect(service.computeFingerprint).toBeDefined();
9
+ expect(typeof service.computeFingerprint).toBe("function");
10
+ });
11
+
12
+ describe("computeFingerprint", () => {
13
+ it("should compute fingerprint for a File", async () => {
14
+ const service = createFingerprintService();
15
+ const file = new File(["Hello, World!"], "test.txt", {
16
+ type: "text/plain",
17
+ });
18
+
19
+ const fingerprint = await service.computeFingerprint(
20
+ file,
21
+ "https://api.example.com"
22
+ );
23
+
24
+ // Should return a 64-character hex string (SHA-256)
25
+ expect(fingerprint).toHaveLength(64);
26
+ expect(fingerprint).toMatch(/^[a-f0-9]{64}$/);
27
+ });
28
+
29
+ it("should compute fingerprint for a Blob", async () => {
30
+ const service = createFingerprintService();
31
+ const blob = new Blob(["Test content"], { type: "text/plain" });
32
+
33
+ const fingerprint = await service.computeFingerprint(
34
+ blob,
35
+ "https://api.example.com"
36
+ );
37
+
38
+ expect(fingerprint).toHaveLength(64);
39
+ expect(fingerprint).toMatch(/^[a-f0-9]{64}$/);
40
+ });
41
+
42
+ it("should return same fingerprint for identical files", async () => {
43
+ const service = createFingerprintService();
44
+ const content = "Same content";
45
+ const file1 = new File([content], "file1.txt", { type: "text/plain" });
46
+ const file2 = new File([content], "file2.txt", { type: "text/plain" });
47
+
48
+ const fingerprint1 = await service.computeFingerprint(
49
+ file1,
50
+ "https://api.example.com"
51
+ );
52
+ const fingerprint2 = await service.computeFingerprint(
53
+ file2,
54
+ "https://api.example.com"
55
+ );
56
+
57
+ expect(fingerprint1).toBe(fingerprint2);
58
+ });
59
+
60
+ it("should return different fingerprint for different files", async () => {
61
+ const service = createFingerprintService();
62
+ const file1 = new File(["Content A"], "file1.txt", { type: "text/plain" });
63
+ const file2 = new File(["Content B"], "file2.txt", { type: "text/plain" });
64
+
65
+ const fingerprint1 = await service.computeFingerprint(
66
+ file1,
67
+ "https://api.example.com"
68
+ );
69
+ const fingerprint2 = await service.computeFingerprint(
70
+ file2,
71
+ "https://api.example.com"
72
+ );
73
+
74
+ expect(fingerprint1).not.toBe(fingerprint2);
75
+ });
76
+
77
+ it("should return same fingerprint regardless of endpoint", async () => {
78
+ const service = createFingerprintService();
79
+ const file = new File(["Test content"], "test.txt", {
80
+ type: "text/plain",
81
+ });
82
+
83
+ const fingerprint1 = await service.computeFingerprint(
84
+ file,
85
+ "https://api1.example.com"
86
+ );
87
+ const fingerprint2 = await service.computeFingerprint(
88
+ file,
89
+ "https://api2.example.com"
90
+ );
91
+
92
+ // The current implementation ignores the endpoint
93
+ expect(fingerprint1).toBe(fingerprint2);
94
+ });
95
+
96
+ it("should handle empty file", async () => {
97
+ const service = createFingerprintService();
98
+ const file = new File([], "empty.txt", { type: "text/plain" });
99
+
100
+ const fingerprint = await service.computeFingerprint(
101
+ file,
102
+ "https://api.example.com"
103
+ );
104
+
105
+ expect(fingerprint).toHaveLength(64);
106
+ expect(fingerprint).toMatch(/^[a-f0-9]{64}$/);
107
+ });
108
+
109
+ it("should handle binary content", async () => {
110
+ const service = createFingerprintService();
111
+ const binaryData = new Uint8Array([0, 1, 2, 255, 254, 253]);
112
+ const blob = new Blob([binaryData], { type: "application/octet-stream" });
113
+
114
+ const fingerprint = await service.computeFingerprint(
115
+ blob,
116
+ "https://api.example.com"
117
+ );
118
+
119
+ expect(fingerprint).toHaveLength(64);
120
+ expect(fingerprint).toMatch(/^[a-f0-9]{64}$/);
121
+ });
122
+ });
123
+ });
@@ -0,0 +1,54 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { createBrowserIdGenerationService } from "./id-generation";
3
+
4
+ describe("createBrowserIdGenerationService", () => {
5
+ it("should create an ID generation service", () => {
6
+ const service = createBrowserIdGenerationService();
7
+ expect(service).toBeDefined();
8
+ expect(service.generate).toBeDefined();
9
+ expect(typeof service.generate).toBe("function");
10
+ });
11
+
12
+ describe("generate", () => {
13
+ it("should generate a UUID v4 string", () => {
14
+ const service = createBrowserIdGenerationService();
15
+ const id = service.generate();
16
+
17
+ // UUID v4 format: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
18
+ expect(id).toMatch(
19
+ /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
20
+ );
21
+ });
22
+
23
+ it("should generate unique IDs", () => {
24
+ const service = createBrowserIdGenerationService();
25
+ const ids = new Set<string>();
26
+
27
+ for (let i = 0; i < 100; i++) {
28
+ ids.add(service.generate());
29
+ }
30
+
31
+ // All 100 IDs should be unique
32
+ expect(ids.size).toBe(100);
33
+ });
34
+
35
+ it("should have correct UUID v4 version byte", () => {
36
+ const service = createBrowserIdGenerationService();
37
+ const id = service.generate();
38
+
39
+ // The 13th character (index 14 after hyphens) should be '4' for v4
40
+ const parts = id.split("-");
41
+ expect(parts[2][0]).toBe("4");
42
+ });
43
+
44
+ it("should have correct UUID v4 variant byte", () => {
45
+ const service = createBrowserIdGenerationService();
46
+ const id = service.generate();
47
+
48
+ // The variant byte should be 8, 9, a, or b
49
+ const parts = id.split("-");
50
+ const variantChar = parts[3][0].toLowerCase();
51
+ expect(["8", "9", "a", "b"]).toContain(variantChar);
52
+ });
53
+ });
54
+ });