@xiboplayer/cache 0.1.0
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/docs/CACHE_PROXY_ARCHITECTURE.md +439 -0
- package/docs/README.md +118 -0
- package/package.json +41 -0
- package/src/cache-proxy.js +493 -0
- package/src/cache-proxy.test.js +391 -0
- package/src/cache.js +739 -0
- package/src/cache.test.js +760 -0
- package/src/download-manager.js +434 -0
- package/src/download-manager.test.js +726 -0
- package/src/index.js +4 -0
- package/src/test-utils.js +133 -0
package/src/index.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test Utilities for Xibo Player Cache package
|
|
3
|
+
* Re-exports common test helpers.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { vi } from 'vitest';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Mock fetch with controllable responses
|
|
10
|
+
*/
|
|
11
|
+
export function mockFetch(responses = {}) {
|
|
12
|
+
global.fetch = vi.fn((url, options) => {
|
|
13
|
+
const method = options?.method || 'GET';
|
|
14
|
+
const key = `${method} ${url}`;
|
|
15
|
+
const response = responses[key] || responses[url];
|
|
16
|
+
|
|
17
|
+
if (!response) {
|
|
18
|
+
return Promise.resolve({
|
|
19
|
+
ok: false,
|
|
20
|
+
status: 404,
|
|
21
|
+
statusText: 'Not Found',
|
|
22
|
+
headers: {
|
|
23
|
+
get: () => null
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return Promise.resolve({
|
|
29
|
+
ok: response.ok !== false,
|
|
30
|
+
status: response.status || 200,
|
|
31
|
+
statusText: response.statusText || 'OK',
|
|
32
|
+
headers: {
|
|
33
|
+
get: (name) => response.headers?.[name] || null
|
|
34
|
+
},
|
|
35
|
+
blob: () => Promise.resolve(response.blob || new Blob()),
|
|
36
|
+
text: () => Promise.resolve(response.text || ''),
|
|
37
|
+
json: () => Promise.resolve(response.json || {}),
|
|
38
|
+
arrayBuffer: () => Promise.resolve(response.arrayBuffer || new ArrayBuffer(0))
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
return global.fetch;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Mock fetch that supports Range request handling (for chunk download tests)
|
|
47
|
+
* Returns the correct portion of a source blob based on the Range header.
|
|
48
|
+
*/
|
|
49
|
+
export function mockChunkedFetch(sourceBlob) {
|
|
50
|
+
global.fetch = vi.fn(async (url, options) => {
|
|
51
|
+
const method = options?.method || 'GET';
|
|
52
|
+
|
|
53
|
+
if (method === 'HEAD') {
|
|
54
|
+
return {
|
|
55
|
+
ok: true,
|
|
56
|
+
status: 200,
|
|
57
|
+
headers: {
|
|
58
|
+
get: (name) => {
|
|
59
|
+
if (name === 'Content-Length') return String(sourceBlob.size);
|
|
60
|
+
if (name === 'Content-Type') return sourceBlob.type || 'application/octet-stream';
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Handle Range requests
|
|
68
|
+
const rangeHeader = options?.headers?.Range;
|
|
69
|
+
if (rangeHeader) {
|
|
70
|
+
const match = rangeHeader.match(/bytes=(\d+)-(\d+)/);
|
|
71
|
+
if (match) {
|
|
72
|
+
const start = parseInt(match[1]);
|
|
73
|
+
const end = parseInt(match[2]);
|
|
74
|
+
const chunk = sourceBlob.slice(start, end + 1);
|
|
75
|
+
return {
|
|
76
|
+
ok: true,
|
|
77
|
+
status: 206,
|
|
78
|
+
headers: {
|
|
79
|
+
get: (name) => {
|
|
80
|
+
if (name === 'Content-Length') return String(chunk.size);
|
|
81
|
+
if (name === 'Content-Range') return `bytes ${start}-${end}/${sourceBlob.size}`;
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
blob: () => Promise.resolve(chunk)
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Full file download
|
|
91
|
+
return {
|
|
92
|
+
ok: true,
|
|
93
|
+
status: 200,
|
|
94
|
+
headers: { get: () => null },
|
|
95
|
+
blob: () => Promise.resolve(sourceBlob)
|
|
96
|
+
};
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
return global.fetch;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Create test blob of specified size
|
|
104
|
+
*/
|
|
105
|
+
export function createTestBlob(size = 1024, type = 'application/octet-stream') {
|
|
106
|
+
const buffer = new ArrayBuffer(size);
|
|
107
|
+
// Fill with non-zero data so chunks are distinguishable
|
|
108
|
+
const view = new Uint8Array(buffer);
|
|
109
|
+
for (let i = 0; i < size; i++) {
|
|
110
|
+
view[i] = i % 256;
|
|
111
|
+
}
|
|
112
|
+
return new Blob([buffer], { type });
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Wait for condition to be true
|
|
117
|
+
*/
|
|
118
|
+
export async function waitFor(condition, timeout = 5000) {
|
|
119
|
+
const start = Date.now();
|
|
120
|
+
while (!condition()) {
|
|
121
|
+
if (Date.now() - start > timeout) {
|
|
122
|
+
throw new Error('waitFor timeout');
|
|
123
|
+
}
|
|
124
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Create a spy
|
|
130
|
+
*/
|
|
131
|
+
export function createSpy() {
|
|
132
|
+
return vi.fn();
|
|
133
|
+
}
|