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.
Files changed (39) hide show
  1. package/.circleci/config.yml +1 -1
  2. package/dist/node/scratch-storage.js +45 -36
  3. package/dist/node/scratch-storage.js.map +1 -1
  4. package/dist/web/scratch-storage.js +45 -36
  5. package/dist/web/scratch-storage.js.map +1 -1
  6. package/dist/web/scratch-storage.min.js +45 -36
  7. package/dist/web/scratch-storage.min.js.map +1 -1
  8. package/jest.config.js +5 -0
  9. package/package.json +11 -9
  10. package/src/BuiltinHelper.js +3 -3
  11. package/src/scratchFetch.js +13 -0
  12. package/test/.eslintrc.js +5 -1
  13. package/test/{mocks/mock-fetch.js → __mocks__/cross-fetch.js} +25 -16
  14. package/test/fixtures/.gitattributes +3 -0
  15. package/test/fixtures/assets/117504922.json +196 -0
  16. package/test/fixtures/assets/66895930177178ea01d9e610917f8acf.png +0 -0
  17. package/test/fixtures/assets/6e8bd9ae68fdb02b7e1e3df656a75635.svg +37 -0
  18. package/test/fixtures/assets/7e24c99c1b853e52f8e7f9004416fa34.png +0 -0
  19. package/test/fixtures/assets/83c36d806dc92327b9e7049a565c6bff.wav +0 -0
  20. package/test/fixtures/assets/f88bf1935daea28f8ca098462a31dbb0.svg +35 -0
  21. package/test/fixtures/assets/fe5e3566965f9de793beeffce377d054.jpg +0 -0
  22. package/test/fixtures/known-assets.js +60 -0
  23. package/test/integration/download-known-assets.test.js +141 -0
  24. package/test/transformers/.eslintrc.js +8 -0
  25. package/test/transformers/arraybuffer-loader.js +11 -0
  26. package/test/unit/{add-helper.js → add-helper.test.js} +16 -20
  27. package/test/unit/fetch-tool.test.js +56 -0
  28. package/test/unit/load-default-assets.test.js +67 -0
  29. package/test/unit/metadata.test.js +129 -0
  30. package/webpack.config.js +4 -0
  31. package/.nyc_output/f1105466-7461-4f73-8b18-ee824eb652b1.json +0 -1
  32. package/.nyc_output/fd8f05df-43a0-4695-b45d-f4877b37f387.json +0 -1
  33. package/.nyc_output/processinfo/f1105466-7461-4f73-8b18-ee824eb652b1.json +0 -1
  34. package/.nyc_output/processinfo/fd8f05df-43a0-4695-b45d-f4877b37f387.json +0 -1
  35. package/.nyc_output/processinfo/index.json +0 -1
  36. package/test/integration/download-known-assets.js +0 -111
  37. package/test/unit/fetch-tool.js +0 -57
  38. package/test/unit/load-default-assets.js +0 -48
  39. 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
+ });
@@ -0,0 +1,8 @@
1
+ module.exports = {
2
+ extends: ['scratch/es6', 'plugin:jest/recommended'],
3
+ env: {
4
+ jest: true,
5
+ node: true
6
+ },
7
+ plugins: ['jest']
8
+ };
@@ -0,0 +1,11 @@
1
+ const fs = require('fs');
2
+
3
+ module.exports = {
4
+ process (_sourceText, sourcePath) {
5
+ const buffer = fs.readFileSync(sourcePath);
6
+ const array = buffer.toJSON().data;
7
+ return {
8
+ code: `module.exports = Buffer.from([${array}]);`
9
+ };
10
+ }
11
+ };
@@ -1,6 +1,4 @@
1
- const test = require('tap').test;
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', t => {
37
+ test('ScratchStorage constructor', () => {
40
38
  const storage = new ScratchStorage();
41
- t.type(storage, ScratchStorage);
42
- t.end();
39
+ expect(storage).toBeInstanceOf(ScratchStorage);
43
40
  });
44
41
 
45
- test('LoggingHelper constructor', t => {
42
+ test('LoggingHelper constructor', () => {
46
43
  const storage = new ScratchStorage();
47
44
  const loggingHelper = new LoggingHelper(storage, 'constructor test', true, []);
48
- t.type(loggingHelper, LoggingHelper);
49
- t.end();
45
+ expect(loggingHelper).toBeInstanceOf(LoggingHelper);
50
46
  });
51
47
 
52
- test('addHelper', t => {
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
- t.equal(storage._helpers.length, initialHelperCount + loggingHelpers.length);
68
+ expect(storage._helpers.length).toBe(initialHelperCount + loggingHelpers.length);
73
69
 
74
70
  // We shouldn't have any log entries yet
75
- t.deepEqual(logContainer, []);
71
+ expect(logContainer).toStrictEqual([]);
72
+
73
+ await storage.load(storage.AssetType.Project, '0');
76
74
 
77
- return storage.load(storage.AssetType.Project, '0').then(() => {
78
- // Verify that all helpers were consulted, and in the correct order
79
- t.deepEqual(logContainer, [
80
- 'first',
81
- 'second',
82
- 'third'
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
@@ -24,6 +24,10 @@ const base = {
24
24
  // @babel/plugin-transform-runtime with CommonJS files.
25
25
  sourceType: 'unambiguous'
26
26
  }
27
+ },
28
+ {
29
+ test: /\.(png|svg|wav)$/,
30
+ loader: 'arraybuffer-loader'
27
31
  }
28
32
  ]
29
33
  },
@@ -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
- });
@@ -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
- });