Skip to content

Commit a21c180

Browse files
authored
Merge pull request #3935 from dpalou/MOBILE-4510
MOBILE-4510 h5p: Fix deleting cached assets
2 parents 09a2bf3 + c9ccf05 commit a21c180

File tree

3 files changed

+141
-5
lines changed

3 files changed

+141
-5
lines changed

src/core/features/h5p/classes/framework.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -747,7 +747,7 @@ export class CoreH5PFramework {
747747

748748
await Promise.all(Object.keys(dependencies).map(async (key) => {
749749
await this.librariesCachedAssetsTables[targetSiteId].insert({
750-
hash: key,
750+
hash,
751751
libraryid: dependencies[key].libraryId,
752752
foldername: folderName,
753753
});
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
// (C) Copyright 2015 Moodle Pty Ltd.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import { mock } from '@/testing/utils';
16+
import { CoreH5PFramework } from '../classes/framework';
17+
import { CoreH5PLibraryCachedAssetsDBRecord } from '../services/database/h5p';
18+
19+
describe('CoreH5PFramework', () => {
20+
21+
const LIBRARIES = {
22+
'H5P.DragQuestion': {
23+
libraryId: 1,
24+
dependencyType: 'preloaded',
25+
machineName: 'H5P.DragQuestion',
26+
majorVersion: 1,
27+
minorVersion: 2,
28+
patchVersion: 3,
29+
},
30+
'H5P.Image': {
31+
libraryId: 2,
32+
dependencyType: 'preloaded',
33+
machineName: 'H5P.Image',
34+
majorVersion: 1,
35+
minorVersion: 2,
36+
patchVersion: 3,
37+
},
38+
'H5P.QuestionSet': {
39+
libraryId: 3,
40+
dependencyType: 'preloaded',
41+
machineName: 'H5P.QuestionSet',
42+
majorVersion: 1,
43+
minorVersion: 2,
44+
patchVersion: 3,
45+
},
46+
'H5P.MultiChoice': {
47+
libraryId: 4,
48+
dependencyType: 'preloaded',
49+
machineName: 'H5P.MultiChoice',
50+
majorVersion: 1,
51+
minorVersion: 2,
52+
patchVersion: 3,
53+
},
54+
};
55+
const SITE_ID = 'Site-12345';
56+
57+
let cachedAssetsLastId = 0;
58+
const cachedAssetsRecords: Record<number, CoreH5PLibraryCachedAssetsDBRecord> = {};
59+
60+
let framework: CoreH5PFramework;
61+
beforeEach(() => {
62+
framework = mock(new CoreH5PFramework(), {
63+
librariesCachedAssetsTables: {
64+
[SITE_ID]: {
65+
getMany: (conditions) =>
66+
Object.values(cachedAssetsRecords).filter((record) => record.libraryid === conditions.libraryid),
67+
insert: (record) => {
68+
cachedAssetsRecords[cachedAssetsLastId++] = record;
69+
},
70+
deleteWhere: (conditions) => {
71+
Object.entries(cachedAssetsRecords).forEach(([primaryKey, record]) => {
72+
if (!conditions.js(record)) {
73+
return;
74+
}
75+
76+
delete cachedAssetsRecords[primaryKey];
77+
});
78+
},
79+
},
80+
},
81+
});
82+
});
83+
84+
it('correctly saves and deletes cached assets in DB', async () => {
85+
const saveCachedAssets = async (hash, folderName, librariesNames) => {
86+
const dependencies = {};
87+
librariesNames.forEach((libraryName) => {
88+
dependencies[libraryName] = LIBRARIES[libraryName];
89+
});
90+
91+
await framework.saveCachedAssets(hash, dependencies, folderName, SITE_ID);
92+
};
93+
94+
const firstContentHash = 'abcdef123456';
95+
const firstContentFolderName = 'question-set-1-1_abcde';
96+
const firstContentDependencies = ['H5P.DragQuestion', 'H5P.Image', 'H5P.QuestionSet'];
97+
const secondContentHash = 'ghijkl789012';
98+
const secondContentFolderName = 'question-set-2-2_qwerty';
99+
const secondContentDependencies = ['H5P.QuestionSet'];
100+
const thirdContentHash = 'mnopqr345678';
101+
const thirdContentFolderName = 'multichoice-1-yuiop';
102+
const thirdContentDependencies = ['H5P.MultiChoice'];
103+
104+
// Check adding cached assets works fine.
105+
await saveCachedAssets(firstContentHash, firstContentFolderName, firstContentDependencies);
106+
107+
let entries = Object.values(cachedAssetsRecords);
108+
expect(entries.length).toEqual(3);
109+
firstContentDependencies.forEach(machineName => {
110+
const dependency = LIBRARIES[machineName];
111+
const entry = entries.find((entry) => dependency.libraryId === entry.libraryid);
112+
expect(entry).not.toBeUndefined();
113+
expect(entry?.foldername).toEqual(firstContentFolderName);
114+
expect(entry?.hash).toEqual(firstContentHash);
115+
});
116+
117+
// Save different cached assets.
118+
await saveCachedAssets(secondContentHash, secondContentFolderName, secondContentDependencies);
119+
await saveCachedAssets(thirdContentHash, thirdContentFolderName, thirdContentDependencies);
120+
121+
entries = Object.values(cachedAssetsRecords);
122+
expect(entries.length).toEqual(5);
123+
124+
// Check that deleting cached assets for a library also deletes them for all libraries sharing any of the cached assets.
125+
// Only the MultiChoice library should remain because it doesn't share any cached assets with QuestionSet.
126+
await framework.deleteCachedAssets(LIBRARIES['H5P.QuestionSet'].libraryId, SITE_ID);
127+
128+
entries = Object.values(cachedAssetsRecords);
129+
expect(entries.length).toEqual(1);
130+
expect(entries[0].libraryid).toEqual(LIBRARIES['H5P.MultiChoice'].libraryId);
131+
});
132+
133+
});

src/testing/setup.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,14 @@ console.debug = () => {
2323
// Silence.
2424
};
2525

26-
// eslint-disable-next-line no-console, jest/no-jasmine-globals, @typescript-eslint/no-explicit-any
27-
console.error = (...args: any[]) => fail(args.map(a => String(a)).join(''));
26+
// eslint-disable-next-line no-console, @typescript-eslint/no-explicit-any
27+
console.error = (...args: any[]) => {
28+
throw new Error(args.map(a => String(a)).join(''));
29+
};
2830

29-
// eslint-disable-next-line jest/no-jasmine-globals
30-
process.on('unhandledRejection', error => fail(error));
31+
process.on('unhandledRejection', error => {
32+
throw new Error(error as string);
33+
});
3134

3235
// Override the method to create singleton method proxies in order to facilitate setting up
3336
// test expectations about method calls.

0 commit comments

Comments
 (0)