scratch-storage 2.2.1 → 2.3.1
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/.circleci/config.yml +1 -1
- package/dist/node/scratch-storage.js +45 -36
- package/dist/node/scratch-storage.js.map +1 -1
- package/dist/web/scratch-storage.js +45 -36
- package/dist/web/scratch-storage.js.map +1 -1
- package/dist/web/scratch-storage.min.js +45 -36
- package/dist/web/scratch-storage.min.js.map +1 -1
- package/jest.config.js +5 -0
- package/package.json +11 -9
- package/src/BuiltinHelper.js +3 -3
- package/src/scratchFetch.js +13 -0
- package/test/.eslintrc.js +5 -1
- package/test/{mocks/mock-fetch.js → __mocks__/cross-fetch.js} +25 -16
- package/test/fixtures/.gitattributes +3 -0
- package/test/fixtures/assets/117504922.json +196 -0
- package/test/fixtures/assets/66895930177178ea01d9e610917f8acf.png +0 -0
- package/test/fixtures/assets/6e8bd9ae68fdb02b7e1e3df656a75635.svg +37 -0
- package/test/fixtures/assets/7e24c99c1b853e52f8e7f9004416fa34.png +0 -0
- package/test/fixtures/assets/83c36d806dc92327b9e7049a565c6bff.wav +0 -0
- package/test/fixtures/assets/f88bf1935daea28f8ca098462a31dbb0.svg +35 -0
- package/test/fixtures/assets/fe5e3566965f9de793beeffce377d054.jpg +0 -0
- package/test/fixtures/known-assets.js +60 -0
- package/test/integration/download-known-assets.test.js +141 -0
- package/test/transformers/.eslintrc.js +8 -0
- package/test/transformers/arraybuffer-loader.js +11 -0
- package/test/unit/{add-helper.js → add-helper.test.js} +16 -20
- package/test/unit/fetch-tool.test.js +56 -0
- package/test/unit/load-default-assets.test.js +67 -0
- package/test/unit/metadata.test.js +129 -0
- package/webpack.config.js +4 -0
- package/.nyc_output/f1105466-7461-4f73-8b18-ee824eb652b1.json +0 -1
- package/.nyc_output/fd8f05df-43a0-4695-b45d-f4877b37f387.json +0 -1
- package/.nyc_output/processinfo/f1105466-7461-4f73-8b18-ee824eb652b1.json +0 -1
- package/.nyc_output/processinfo/fd8f05df-43a0-4695-b45d-f4877b37f387.json +0 -1
- package/.nyc_output/processinfo/index.json +0 -1
- package/test/integration/download-known-assets.js +0 -111
- package/test/unit/fetch-tool.js +0 -57
- package/test/unit/load-default-assets.js +0 -48
- package/test/unit/metadata.js +0 -147
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
const md5 = require('js-md5');
|
|
2
|
+
|
|
3
|
+
const ScratchStorage = require('../../src/index.js');
|
|
4
|
+
|
|
5
|
+
test('constructor', () => {
|
|
6
|
+
const storage = new ScratchStorage();
|
|
7
|
+
expect(storage).toBeInstanceOf(ScratchStorage);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {object} AssetTestInfo
|
|
12
|
+
* @property {AssetType} type - The type of the asset.
|
|
13
|
+
* @property {string} id - The asset's unique ID.
|
|
14
|
+
* @property {string} md5 - The asset's MD5 hash.
|
|
15
|
+
* @property {DataFormat} [ext] - Optional: the asset's data format / file extension.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {ScratchStorage} storage The storage module.
|
|
20
|
+
* @returns {AssetTestInfo[]} an array of asset info objects.
|
|
21
|
+
*/
|
|
22
|
+
const getTestAssets = storage => [
|
|
23
|
+
// Project
|
|
24
|
+
{
|
|
25
|
+
type: storage.AssetType.Project,
|
|
26
|
+
id: '117504922',
|
|
27
|
+
md5: '1225460702e149727de28bff4cfd9e23'
|
|
28
|
+
},
|
|
29
|
+
// SVG without explicit extension
|
|
30
|
+
{
|
|
31
|
+
type: storage.AssetType.ImageVector,
|
|
32
|
+
id: 'f88bf1935daea28f8ca098462a31dbb0', // cat1-a
|
|
33
|
+
md5: 'f88bf1935daea28f8ca098462a31dbb0'
|
|
34
|
+
},
|
|
35
|
+
// SVG with explicit extension
|
|
36
|
+
{
|
|
37
|
+
type: storage.AssetType.ImageVector,
|
|
38
|
+
id: '6e8bd9ae68fdb02b7e1e3df656a75635', // cat1-b
|
|
39
|
+
md5: '6e8bd9ae68fdb02b7e1e3df656a75635',
|
|
40
|
+
ext: storage.DataFormat.SVG
|
|
41
|
+
},
|
|
42
|
+
// PNG without explicit extension
|
|
43
|
+
{
|
|
44
|
+
type: storage.AssetType.ImageBitmap,
|
|
45
|
+
id: '7e24c99c1b853e52f8e7f9004416fa34', // squirrel
|
|
46
|
+
md5: '7e24c99c1b853e52f8e7f9004416fa34'
|
|
47
|
+
},
|
|
48
|
+
// PNG with explicit extension
|
|
49
|
+
{
|
|
50
|
+
type: storage.AssetType.ImageBitmap,
|
|
51
|
+
id: '66895930177178ea01d9e610917f8acf', // bus
|
|
52
|
+
md5: '66895930177178ea01d9e610917f8acf',
|
|
53
|
+
ext: storage.DataFormat.PNG
|
|
54
|
+
},
|
|
55
|
+
// JPG with explicit extension
|
|
56
|
+
{
|
|
57
|
+
type: storage.AssetType.ImageBitmap,
|
|
58
|
+
id: 'fe5e3566965f9de793beeffce377d054', // building at MIT
|
|
59
|
+
md5: 'fe5e3566965f9de793beeffce377d054',
|
|
60
|
+
ext: storage.DataFormat.JPG
|
|
61
|
+
},
|
|
62
|
+
// WAV without explicit extension
|
|
63
|
+
{
|
|
64
|
+
type: storage.AssetType.Sound,
|
|
65
|
+
id: '83c36d806dc92327b9e7049a565c6bff', // meow
|
|
66
|
+
md5: '83c36d806dc92327b9e7049a565c6bff' // wat
|
|
67
|
+
}
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
const addWebStores = storage => {
|
|
71
|
+
// these `asset => ...` callbacks generate values specifically for the cross-fetch mock
|
|
72
|
+
// in the real world they would generate proper URIs
|
|
73
|
+
storage.addWebStore(
|
|
74
|
+
[storage.AssetType.Project],
|
|
75
|
+
asset => asset.assetId,
|
|
76
|
+
null, null);
|
|
77
|
+
storage.addWebStore(
|
|
78
|
+
[storage.AssetType.ImageVector, storage.AssetType.ImageBitmap, storage.AssetType.Sound],
|
|
79
|
+
asset => `${asset.assetId}.${asset.dataFormat}`,
|
|
80
|
+
null, null
|
|
81
|
+
);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
test('addWebStore', () => {
|
|
85
|
+
const storage = new ScratchStorage();
|
|
86
|
+
addWebStores(storage);
|
|
87
|
+
expect(storage.webHelper.stores.length).toBe(2);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test('load', () => {
|
|
91
|
+
const storage = new ScratchStorage();
|
|
92
|
+
addWebStores(storage);
|
|
93
|
+
const testAssets = getTestAssets(storage);
|
|
94
|
+
const assetChecks = testAssets.map(async assetInfo => {
|
|
95
|
+
const asset = await storage.load(assetInfo.type, assetInfo.id, assetInfo.ext)
|
|
96
|
+
.catch(e => {
|
|
97
|
+
if (e instanceof Array) {
|
|
98
|
+
// This is storage.load reporting one or more errors from individual tools.
|
|
99
|
+
e = e.flat();
|
|
100
|
+
|
|
101
|
+
if (e.length === 1) {
|
|
102
|
+
// If we just have one, it'll display well as-is. Don't bother wrapping it.
|
|
103
|
+
// Note that this still might be either an Error or a status code (see below).
|
|
104
|
+
e = e[0];
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (e instanceof Array) {
|
|
109
|
+
/* global AggregateError */
|
|
110
|
+
// we must have >1 error, so report it as an AggregateError (supported in Node 15+)
|
|
111
|
+
e = new AggregateError(
|
|
112
|
+
e.flat(),
|
|
113
|
+
`failed to load ${assetInfo.type.name} asset with id=${assetInfo.id}`
|
|
114
|
+
);
|
|
115
|
+
// Jest doesn't display AggregateError very well on its own
|
|
116
|
+
console.error(e);
|
|
117
|
+
} else if (!(e instanceof Error)) {
|
|
118
|
+
// storage.load can throw a status like 403 or 500 which isn't an Error.
|
|
119
|
+
// That can look confusing in test output, so wrap it in an Error that will display well.
|
|
120
|
+
e = new Error(`failed to load ${assetInfo.type.name} asset with id=${assetInfo.id} (e=${e})`);
|
|
121
|
+
}
|
|
122
|
+
// else it's an Error that's already suitable for reporting
|
|
123
|
+
|
|
124
|
+
throw e;
|
|
125
|
+
});
|
|
126
|
+
expect(asset).toBeInstanceOf(storage.Asset);
|
|
127
|
+
expect(asset.assetId).toBe(assetInfo.id);
|
|
128
|
+
expect(asset.assetType).toBe(assetInfo.type);
|
|
129
|
+
expect(asset.data.length).toBeGreaterThan(0);
|
|
130
|
+
|
|
131
|
+
// Web assets should come back as clean
|
|
132
|
+
expect(asset.clean).toBeTruthy();
|
|
133
|
+
|
|
134
|
+
if (assetInfo.md5) {
|
|
135
|
+
// eslint-disable-next-line jest/no-conditional-expect
|
|
136
|
+
expect(md5(asset.data)).toBe(assetInfo.md5);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
return Promise.all(assetChecks);
|
|
141
|
+
});
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
const
|
|
2
|
-
|
|
3
|
-
const ScratchStorage = require('../../dist/node/scratch-storage');
|
|
1
|
+
const ScratchStorage = require('../../src');
|
|
4
2
|
|
|
5
3
|
/**
|
|
6
4
|
* Simulate a storage helper, adding log messages when "load" is called rather than actually loading anything.
|
|
@@ -36,20 +34,18 @@ class LoggingHelper {
|
|
|
36
34
|
}
|
|
37
35
|
}
|
|
38
36
|
|
|
39
|
-
test('ScratchStorage constructor',
|
|
37
|
+
test('ScratchStorage constructor', () => {
|
|
40
38
|
const storage = new ScratchStorage();
|
|
41
|
-
|
|
42
|
-
t.end();
|
|
39
|
+
expect(storage).toBeInstanceOf(ScratchStorage);
|
|
43
40
|
});
|
|
44
41
|
|
|
45
|
-
test('LoggingHelper constructor',
|
|
42
|
+
test('LoggingHelper constructor', () => {
|
|
46
43
|
const storage = new ScratchStorage();
|
|
47
44
|
const loggingHelper = new LoggingHelper(storage, 'constructor test', true, []);
|
|
48
|
-
|
|
49
|
-
t.end();
|
|
45
|
+
expect(loggingHelper).toBeInstanceOf(LoggingHelper);
|
|
50
46
|
});
|
|
51
47
|
|
|
52
|
-
test('addHelper',
|
|
48
|
+
test('addHelper', async () => {
|
|
53
49
|
const logContainer = [];
|
|
54
50
|
const storage = new ScratchStorage();
|
|
55
51
|
|
|
@@ -69,17 +65,17 @@ test('addHelper', t => {
|
|
|
69
65
|
storage.addHelper(loggingHelpers[1], 0);
|
|
70
66
|
|
|
71
67
|
// Did they all get added?
|
|
72
|
-
|
|
68
|
+
expect(storage._helpers.length).toBe(initialHelperCount + loggingHelpers.length);
|
|
73
69
|
|
|
74
70
|
// We shouldn't have any log entries yet
|
|
75
|
-
|
|
71
|
+
expect(logContainer).toStrictEqual([]);
|
|
72
|
+
|
|
73
|
+
await storage.load(storage.AssetType.Project, '0');
|
|
76
74
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
]);
|
|
84
|
-
});
|
|
75
|
+
// Verify that all helpers were consulted, and in the correct order
|
|
76
|
+
expect(logContainer).toStrictEqual([
|
|
77
|
+
'first',
|
|
78
|
+
'second',
|
|
79
|
+
'third'
|
|
80
|
+
]);
|
|
85
81
|
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const TextDecoder = require('util').TextDecoder;
|
|
2
|
+
|
|
3
|
+
jest.mock('cross-fetch');
|
|
4
|
+
const mockFetch = require('cross-fetch');
|
|
5
|
+
const FetchTool = require('../../src/FetchTool.js');
|
|
6
|
+
|
|
7
|
+
test('send success returns response.text()', async () => {
|
|
8
|
+
const tool = new FetchTool();
|
|
9
|
+
|
|
10
|
+
const result = await tool.send({url: '200'});
|
|
11
|
+
expect(result).toBe(mockFetch.successText);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test('send failure returns response.status', async () => {
|
|
15
|
+
const tool = new FetchTool();
|
|
16
|
+
|
|
17
|
+
const catcher = jest.fn();
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
await tool.send({url: '500'});
|
|
21
|
+
} catch (e) {
|
|
22
|
+
catcher(e);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
expect(catcher).toHaveBeenCalledWith(500);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('get success returns Uint8Array.body(response.arrayBuffer())', async () => {
|
|
29
|
+
const encoding = 'utf-8';
|
|
30
|
+
const decoder = new TextDecoder(encoding);
|
|
31
|
+
|
|
32
|
+
const tool = new FetchTool();
|
|
33
|
+
|
|
34
|
+
const result = await tool.get({url: '200'});
|
|
35
|
+
expect(decoder.decode(result)).toBe(mockFetch.successText);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('get with 404 response returns null data', async () => {
|
|
39
|
+
const tool = new FetchTool();
|
|
40
|
+
|
|
41
|
+
const result = await tool.get({url: '404'});
|
|
42
|
+
expect(result).toBeNull();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('get failure returns response.status', async () => {
|
|
46
|
+
const tool = new FetchTool();
|
|
47
|
+
const catcher = jest.fn();
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
await tool.get({url: '500'});
|
|
51
|
+
} catch (e) {
|
|
52
|
+
catcher(e);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
expect(catcher).toHaveBeenCalledWith(500);
|
|
56
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
const md5 = require('js-md5');
|
|
2
|
+
|
|
3
|
+
const ScratchStorage = require('../../dist/node/scratch-storage');
|
|
4
|
+
|
|
5
|
+
// Hash and file size of each default asset
|
|
6
|
+
const knownSizes = {
|
|
7
|
+
'8e768a5a5a01891b05c01c9ca15eb6aa': 255,
|
|
8
|
+
'b586745b98e94d7574f7f7b48d831e20': 46,
|
|
9
|
+
'e5cb3b2aa4e1a9b4c735c3415e507e66': 925
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const getDefaultAssetTypes = storage => {
|
|
13
|
+
const defaultAssetTypes = [storage.AssetType.ImageBitmap, storage.AssetType.ImageVector, storage.AssetType.Sound];
|
|
14
|
+
return defaultAssetTypes;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const getDefaultAssetIds = (storage, defaultAssetTypes) => {
|
|
18
|
+
const defaultIds = {};
|
|
19
|
+
for (const assetType of defaultAssetTypes) {
|
|
20
|
+
const id = storage.getDefaultAssetId(assetType);
|
|
21
|
+
defaultIds[assetType.name] = id;
|
|
22
|
+
}
|
|
23
|
+
return defaultIds;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
test('constructor', () => {
|
|
27
|
+
const storage = new ScratchStorage();
|
|
28
|
+
expect(storage).toBeInstanceOf(ScratchStorage);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('getDefaultAssetId', () => {
|
|
32
|
+
const storage = new ScratchStorage();
|
|
33
|
+
const defaultAssetTypes = getDefaultAssetTypes(storage);
|
|
34
|
+
const defaultIds = getDefaultAssetIds(storage, defaultAssetTypes);
|
|
35
|
+
for (const assetType of defaultAssetTypes) {
|
|
36
|
+
const id = defaultIds[assetType.name];
|
|
37
|
+
expect(typeof id).toBe('string');
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('load', () => {
|
|
42
|
+
const storage = new ScratchStorage();
|
|
43
|
+
const defaultAssetTypes = getDefaultAssetTypes(storage);
|
|
44
|
+
const defaultIds = getDefaultAssetIds(storage, defaultAssetTypes);
|
|
45
|
+
|
|
46
|
+
const promises = [];
|
|
47
|
+
const checkAsset = (assetType, id, asset) => {
|
|
48
|
+
expect(asset).toBeInstanceOf(storage.Asset);
|
|
49
|
+
expect(asset.assetId).toStrictEqual(id);
|
|
50
|
+
expect(asset.assetType).toStrictEqual(assetType);
|
|
51
|
+
expect(asset.data.length).toBeTruthy();
|
|
52
|
+
expect(asset.data.length).toBe(knownSizes[id]);
|
|
53
|
+
expect(md5(asset.data)).toBe(id);
|
|
54
|
+
};
|
|
55
|
+
for (const assetType of defaultAssetTypes) {
|
|
56
|
+
const id = defaultIds[assetType.name];
|
|
57
|
+
|
|
58
|
+
const promise = storage.load(assetType, id);
|
|
59
|
+
expect(promise).toBeInstanceOf(Promise);
|
|
60
|
+
|
|
61
|
+
const checkedPromise = promise.then(asset => checkAsset(assetType, id, asset));
|
|
62
|
+
|
|
63
|
+
promises.push(checkedPromise);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return Promise.all(promises);
|
|
67
|
+
});
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
jest.mock('cross-fetch');
|
|
2
|
+
|
|
3
|
+
beforeEach(() => {
|
|
4
|
+
// reset the metadata container to ensure the tests don't interfere with each other
|
|
5
|
+
// but this also means we need to `require` inside the tests
|
|
6
|
+
/* eslint-disable global-require */
|
|
7
|
+
jest.resetModules();
|
|
8
|
+
|
|
9
|
+
// temporary: pretend we're running in a browser and the URL has scratchMetadata=1
|
|
10
|
+
// this is a hack to enable the metadata feature in tests
|
|
11
|
+
// see also `hasMetadata` in scratchFetch.js
|
|
12
|
+
global.self = global.self || {};
|
|
13
|
+
global.self.location = new URL('https://example.com/?scratchMetadata=1');
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test('get without metadata', async () => {
|
|
17
|
+
const FetchTool = require('../../src/FetchTool.js');
|
|
18
|
+
const tool = new FetchTool();
|
|
19
|
+
|
|
20
|
+
const mockFetchTestData = {};
|
|
21
|
+
const result = await tool.get({url: '200', mockFetchTestData});
|
|
22
|
+
|
|
23
|
+
expect(result).toBeInstanceOf(Uint8Array);
|
|
24
|
+
expect(mockFetchTestData.headers).toBeTruthy();
|
|
25
|
+
expect(mockFetchTestData.headersCount).toBe(0);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('get with metadata', async () => {
|
|
29
|
+
const FetchTool = require('../../src/FetchTool.js');
|
|
30
|
+
const ScratchFetch = require('../../src/scratchFetch');
|
|
31
|
+
const {RequestMetadata, setMetadata} = ScratchFetch;
|
|
32
|
+
|
|
33
|
+
const tool = new FetchTool();
|
|
34
|
+
|
|
35
|
+
setMetadata(RequestMetadata.ProjectId, 1234);
|
|
36
|
+
setMetadata(RequestMetadata.RunId, 5678);
|
|
37
|
+
|
|
38
|
+
const mockFetchTestData = {};
|
|
39
|
+
const result = await tool.get({url: '200', mockFetchTestData});
|
|
40
|
+
|
|
41
|
+
expect(result).toBeInstanceOf(Uint8Array);
|
|
42
|
+
expect(mockFetchTestData.headers).toBeTruthy();
|
|
43
|
+
expect(mockFetchTestData.headersCount).toBe(2);
|
|
44
|
+
expect(mockFetchTestData.headers?.get(RequestMetadata.ProjectId)).toBe('1234');
|
|
45
|
+
expect(mockFetchTestData.headers?.get(RequestMetadata.RunId)).toBe('5678');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('send without metadata', async () => {
|
|
49
|
+
const FetchTool = require('../../src/FetchTool.js');
|
|
50
|
+
const tool = new FetchTool();
|
|
51
|
+
|
|
52
|
+
const mockFetchTestData = {};
|
|
53
|
+
const result = await tool.send({url: '200', mockFetchTestData});
|
|
54
|
+
|
|
55
|
+
expect(typeof result).toBe('string');
|
|
56
|
+
expect(mockFetchTestData.headers).toBeTruthy();
|
|
57
|
+
expect(mockFetchTestData.headersCount).toBe(0);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('send with metadata', async () => {
|
|
61
|
+
const FetchTool = require('../../src/FetchTool.js');
|
|
62
|
+
const ScratchFetch = require('../../src/scratchFetch');
|
|
63
|
+
const {RequestMetadata, setMetadata} = ScratchFetch;
|
|
64
|
+
|
|
65
|
+
const tool = new FetchTool();
|
|
66
|
+
|
|
67
|
+
setMetadata(RequestMetadata.ProjectId, 4321);
|
|
68
|
+
setMetadata(RequestMetadata.RunId, 8765);
|
|
69
|
+
|
|
70
|
+
const mockFetchTestData = {};
|
|
71
|
+
const result = await tool.send({url: '200', mockFetchTestData});
|
|
72
|
+
|
|
73
|
+
expect(typeof result).toBe('string');
|
|
74
|
+
expect(mockFetchTestData.headers).toBeTruthy();
|
|
75
|
+
expect(mockFetchTestData.headersCount).toBe(2);
|
|
76
|
+
expect(mockFetchTestData.headers?.get(RequestMetadata.ProjectId)).toBe('4321');
|
|
77
|
+
expect(mockFetchTestData.headers?.get(RequestMetadata.RunId)).toBe('8765');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('selectively delete metadata', async () => {
|
|
81
|
+
const FetchTool = require('../../src/FetchTool.js');
|
|
82
|
+
const ScratchFetch = require('../../src/scratchFetch');
|
|
83
|
+
const {RequestMetadata, setMetadata, unsetMetadata} = ScratchFetch;
|
|
84
|
+
|
|
85
|
+
// verify that these special values are preserved and not interpreted as "delete"
|
|
86
|
+
setMetadata(RequestMetadata.ProjectId, null);
|
|
87
|
+
setMetadata(RequestMetadata.RunId, void 0); // void 0 = undefined
|
|
88
|
+
|
|
89
|
+
const tool = new FetchTool();
|
|
90
|
+
|
|
91
|
+
const mockFetchTestData = {};
|
|
92
|
+
|
|
93
|
+
const result1 = await tool.send({url: '200', mockFetchTestData});
|
|
94
|
+
expect(typeof result1).toBe('string');
|
|
95
|
+
expect(mockFetchTestData.headers).toBeTruthy();
|
|
96
|
+
|
|
97
|
+
expect(mockFetchTestData.headersCount).toBe(2);
|
|
98
|
+
expect(mockFetchTestData.headers?.get(RequestMetadata.ProjectId)).toBe('null'); // string "null" means it's present
|
|
99
|
+
expect(mockFetchTestData.headers?.get(RequestMetadata.RunId)).toBe('undefined');
|
|
100
|
+
|
|
101
|
+
// remove the Project ID from metadata
|
|
102
|
+
unsetMetadata(RequestMetadata.ProjectId);
|
|
103
|
+
|
|
104
|
+
const result2 = await tool.send({url: '200', mockFetchTestData});
|
|
105
|
+
expect(typeof result2).toBe('string');
|
|
106
|
+
expect(mockFetchTestData.headers).toBeTruthy();
|
|
107
|
+
|
|
108
|
+
expect(mockFetchTestData.headersCount).toBe(1);
|
|
109
|
+
expect(mockFetchTestData.headers?.get(RequestMetadata.ProjectId)).toBeNull(); // value `null` means it's present
|
|
110
|
+
expect(mockFetchTestData.headers?.get(RequestMetadata.RunId)).toBe('undefined');
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('metadata has case-insensitive keys', async () => {
|
|
114
|
+
const FetchTool = require('../../src/FetchTool.js');
|
|
115
|
+
const ScratchFetch = require('../../src/scratchFetch');
|
|
116
|
+
const {setMetadata} = ScratchFetch;
|
|
117
|
+
|
|
118
|
+
setMetadata('foo', 1);
|
|
119
|
+
setMetadata('FOO', 2);
|
|
120
|
+
|
|
121
|
+
const tool = new FetchTool();
|
|
122
|
+
|
|
123
|
+
const mockFetchTestData = {};
|
|
124
|
+
await tool.get({url: '200', mockFetchTestData});
|
|
125
|
+
|
|
126
|
+
expect(mockFetchTestData.headers).toBeTruthy();
|
|
127
|
+
expect(mockFetchTestData.headersCount).toBe(1);
|
|
128
|
+
expect(mockFetchTestData.headers?.get('foo')).toBe('2');
|
|
129
|
+
});
|
package/webpack.config.js
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"parent":null,"pid":257,"argv":["/usr/local/bin/node","/home/circleci/project/node_modules/.bin/tap","./test/integration/download-known-assets.js"],"execArgv":[],"cwd":"/home/circleci/project","time":1680278197547,"ppid":246,"coverageFilename":"/home/circleci/project/.nyc_output/f1105466-7461-4f73-8b18-ee824eb652b1.json","externalId":"","uuid":"f1105466-7461-4f73-8b18-ee824eb652b1","files":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"parent":"f1105466-7461-4f73-8b18-ee824eb652b1","pid":268,"argv":["/usr/local/bin/node","/home/circleci/project/test/integration/download-known-assets.js"],"execArgv":[],"cwd":"/home/circleci/project","time":1680278198785,"ppid":257,"coverageFilename":"/home/circleci/project/.nyc_output/fd8f05df-43a0-4695-b45d-f4877b37f387.json","externalId":"./test/integration/download-known-assets.js","uuid":"fd8f05df-43a0-4695-b45d-f4877b37f387","files":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"processes":{"f1105466-7461-4f73-8b18-ee824eb652b1":{"parent":null,"children":["fd8f05df-43a0-4695-b45d-f4877b37f387"]},"fd8f05df-43a0-4695-b45d-f4877b37f387":{"parent":"f1105466-7461-4f73-8b18-ee824eb652b1","externalId":"./test/integration/download-known-assets.js","children":[]}},"files":{},"externalIds":{"./test/integration/download-known-assets.js":{"root":"fd8f05df-43a0-4695-b45d-f4877b37f387","children":[]}}}
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
const md5 = require('js-md5');
|
|
2
|
-
const test = require('tap').test;
|
|
3
|
-
|
|
4
|
-
const ScratchStorage = require('../../dist/node/scratch-storage');
|
|
5
|
-
|
|
6
|
-
let storage;
|
|
7
|
-
test('constructor', t => {
|
|
8
|
-
storage = new ScratchStorage();
|
|
9
|
-
t.type(storage, ScratchStorage);
|
|
10
|
-
t.end();
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
*
|
|
15
|
-
* @type {AssetTestInfo[]}
|
|
16
|
-
* @typedef {object} AssetTestInfo
|
|
17
|
-
* @property {AssetType} type - The type of the asset.
|
|
18
|
-
* @property {string} id - The asset's unique ID.
|
|
19
|
-
* @property {string} md5 - The asset's MD5 hash.
|
|
20
|
-
* @property {DataFormat} [ext] - Optional: the asset's data format / file extension.
|
|
21
|
-
*/
|
|
22
|
-
const testAssets = [
|
|
23
|
-
// TODO: mock project download, since we can no longer download projects directly
|
|
24
|
-
// {
|
|
25
|
-
// type: storage.AssetType.Project,
|
|
26
|
-
// id: '117504922',
|
|
27
|
-
// md5: null // don't check MD5 for project without revision ID
|
|
28
|
-
// },
|
|
29
|
-
// {
|
|
30
|
-
// type: storage.AssetType.Project,
|
|
31
|
-
// id: '117504922.d6ae1ffb76f2bc83421cd3f40fc4fd57',
|
|
32
|
-
// md5: '1225460702e149727de28bff4cfd9e23'
|
|
33
|
-
// },
|
|
34
|
-
{
|
|
35
|
-
type: storage.AssetType.ImageVector,
|
|
36
|
-
id: 'f88bf1935daea28f8ca098462a31dbb0', // cat1-a
|
|
37
|
-
md5: 'f88bf1935daea28f8ca098462a31dbb0'
|
|
38
|
-
},
|
|
39
|
-
{
|
|
40
|
-
type: storage.AssetType.ImageVector,
|
|
41
|
-
id: '6e8bd9ae68fdb02b7e1e3df656a75635', // cat1-b
|
|
42
|
-
md5: '6e8bd9ae68fdb02b7e1e3df656a75635',
|
|
43
|
-
ext: storage.DataFormat.SVG
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
type: storage.AssetType.ImageBitmap,
|
|
47
|
-
id: '7e24c99c1b853e52f8e7f9004416fa34', // squirrel
|
|
48
|
-
md5: '7e24c99c1b853e52f8e7f9004416fa34'
|
|
49
|
-
},
|
|
50
|
-
{
|
|
51
|
-
type: storage.AssetType.ImageBitmap,
|
|
52
|
-
id: '66895930177178ea01d9e610917f8acf', // bus
|
|
53
|
-
md5: '66895930177178ea01d9e610917f8acf',
|
|
54
|
-
ext: storage.DataFormat.PNG
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
type: storage.AssetType.ImageBitmap,
|
|
58
|
-
id: 'fe5e3566965f9de793beeffce377d054', // building at MIT
|
|
59
|
-
md5: 'fe5e3566965f9de793beeffce377d054',
|
|
60
|
-
ext: storage.DataFormat.JPG
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
type: storage.AssetType.Sound,
|
|
64
|
-
id: '83c36d806dc92327b9e7049a565c6bff', // meow
|
|
65
|
-
md5: '83c36d806dc92327b9e7049a565c6bff' // wat
|
|
66
|
-
}
|
|
67
|
-
];
|
|
68
|
-
|
|
69
|
-
test('addWebStore', t => {
|
|
70
|
-
t.doesNotThrow(() => {
|
|
71
|
-
storage.addWebStore(
|
|
72
|
-
[storage.AssetType.Project],
|
|
73
|
-
asset => {
|
|
74
|
-
const idParts = asset.assetId.split('.');
|
|
75
|
-
return idParts[1] ?
|
|
76
|
-
`https://cdn.projects.scratch.mit.edu/internalapi/project/${idParts[0]}/get/${idParts[1]}` :
|
|
77
|
-
`https://cdn.projects.scratch.mit.edu/internalapi/project/${idParts[0]}/get/`;
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
|
-
t.doesNotThrow(() => {
|
|
81
|
-
storage.addWebStore(
|
|
82
|
-
[storage.AssetType.ImageVector, storage.AssetType.ImageBitmap, storage.AssetType.Sound],
|
|
83
|
-
asset => `https://cdn.assets.scratch.mit.edu/internalapi/asset/${asset.assetId}.${asset.dataFormat}/get/`
|
|
84
|
-
);
|
|
85
|
-
});
|
|
86
|
-
t.end();
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
test('load', t => {
|
|
90
|
-
const assetChecks = testAssets.map(async assetInfo => {
|
|
91
|
-
const asset = await storage.load(assetInfo.type, assetInfo.id, assetInfo.ext)
|
|
92
|
-
.catch(e => {
|
|
93
|
-
// tap's output isn't great if we just let it catch the unhandled promise rejection
|
|
94
|
-
// wrapping it like this makes a failure much easier to read in the test output
|
|
95
|
-
throw new Error(`failed to load ${assetInfo.type.name} asset with id=${assetInfo.id} (e=${e})`);
|
|
96
|
-
});
|
|
97
|
-
t.type(asset, storage.Asset);
|
|
98
|
-
t.equal(asset.assetId, assetInfo.id);
|
|
99
|
-
t.equal(asset.assetType, assetInfo.type);
|
|
100
|
-
t.ok(asset.data.length);
|
|
101
|
-
|
|
102
|
-
// Web assets should come back as clean
|
|
103
|
-
t.ok(asset.clean);
|
|
104
|
-
|
|
105
|
-
if (assetInfo.md5) {
|
|
106
|
-
t.equal(md5(asset.data), assetInfo.md5);
|
|
107
|
-
}
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
return Promise.all(assetChecks);
|
|
111
|
-
});
|
package/test/unit/fetch-tool.js
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
const tap = require('tap');
|
|
2
|
-
const TextDecoder = require('util').TextDecoder;
|
|
3
|
-
|
|
4
|
-
const mockFetch = require('../mocks/mock-fetch.js');
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* This is the real FetchTool, but the 'cross-fetch' module has been replaced with the mockFetch function.
|
|
8
|
-
* @type {typeof import('../../src/FetchTool')}
|
|
9
|
-
*/
|
|
10
|
-
const FetchTool = tap.mock('../../src/FetchTool', {
|
|
11
|
-
'cross-fetch': mockFetch
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
tap.test('send success returns response.text()', t => {
|
|
15
|
-
const tool = new FetchTool();
|
|
16
|
-
|
|
17
|
-
return t.resolves(
|
|
18
|
-
tool.send({url: '200'}).then(result => {
|
|
19
|
-
t.equal(result, mockFetch.successText);
|
|
20
|
-
})
|
|
21
|
-
);
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
tap.test('send failure returns response.status', t => {
|
|
25
|
-
const tool = new FetchTool();
|
|
26
|
-
|
|
27
|
-
return t.rejects(tool.send({url: '500'}), 500);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
tap.test('get success returns Uint8Array.body(response.arrayBuffer())', t => {
|
|
31
|
-
const encoding = 'utf-8';
|
|
32
|
-
const decoder = new TextDecoder(encoding);
|
|
33
|
-
|
|
34
|
-
const tool = new FetchTool();
|
|
35
|
-
|
|
36
|
-
return t.resolves(
|
|
37
|
-
tool.get({url: '200'}).then(result => {
|
|
38
|
-
t.equal(decoder.decode(result), mockFetch.successText);
|
|
39
|
-
})
|
|
40
|
-
);
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
tap.test('get with 404 response returns null data', t => {
|
|
44
|
-
const tool = new FetchTool();
|
|
45
|
-
|
|
46
|
-
return t.resolves(
|
|
47
|
-
tool.get({url: '404'}).then(result => {
|
|
48
|
-
t.equal(result, null);
|
|
49
|
-
})
|
|
50
|
-
);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
tap.test('get failure returns response.status', t => {
|
|
54
|
-
const tool = new FetchTool();
|
|
55
|
-
|
|
56
|
-
return t.rejects(tool.get({url: '500'}), 500);
|
|
57
|
-
});
|