Skip to content

Commit 1729873

Browse files
authored
feat: read catalog synchronously during init (#3094)
* feat: read catalog synchronously Signed-off-by: Philippe Martin <[email protected]> * feat: wrap JsonWatcher into Promise Signed-off-by: Philippe Martin <[email protected]> * test: mock catalogManager Signed-off-by: Philippe Martin <[email protected]>
1 parent ac72327 commit 1729873

File tree

4 files changed

+47
-22
lines changed

4 files changed

+47
-22
lines changed

packages/backend/src/managers/catalogManager.spec.ts

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ beforeEach(async () => {
9696
describe('invalid user catalog', () => {
9797
beforeEach(async () => {
9898
vi.mocked(promises.readFile).mockResolvedValue('invalid json');
99-
catalogManager.init();
99+
await catalogManager.init();
100100
});
101101

102102
test('expect correct model is returned with valid id', () => {
@@ -116,7 +116,7 @@ describe('invalid user catalog', () => {
116116

117117
test('expect correct model is returned from default catalog with valid id when no user catalog exists', async () => {
118118
vi.mocked(existsSync).mockReturnValue(false);
119-
catalogManager.init();
119+
await catalogManager.init();
120120
await vi.waitUntil(() => catalogManager.getRecipes().length > 0);
121121

122122
const model = catalogManager.getModelById('llama-2-7b-chat.Q5_K_S');
@@ -132,7 +132,7 @@ test('expect correct model is returned with valid id when the user catalog is va
132132
vi.mocked(existsSync).mockReturnValue(true);
133133
vi.mocked(promises.readFile).mockResolvedValue(JSON.stringify(userContent));
134134

135-
catalogManager.init();
135+
await catalogManager.init();
136136
await vi.waitUntil(() => catalogManager.getModels().some(model => model.id === 'model1'));
137137

138138
const model = catalogManager.getModelById('model1');
@@ -146,7 +146,7 @@ test('expect to call writeFile in addLocalModelsToCatalog with catalog updated',
146146
vi.mocked(existsSync).mockReturnValue(true);
147147
vi.mocked(promises.readFile).mockResolvedValue(JSON.stringify(userContent));
148148

149-
catalogManager.init();
149+
await catalogManager.init();
150150
await vi.waitUntil(() => catalogManager.getRecipes().length > 0);
151151

152152
const mtimeDate = new Date('2024-04-03T09:51:15.766Z');
@@ -174,7 +174,7 @@ test('expect to call writeFile in removeLocalModelFromCatalog with catalog updat
174174
vi.mocked(promises.readFile).mockResolvedValue(JSON.stringify(userContent));
175175
vi.mocked(path.resolve).mockReturnValue('path');
176176

177-
catalogManager.init();
177+
await catalogManager.init();
178178
await vi.waitUntil(() => catalogManager.getRecipes().length > 0);
179179

180180
vi.mocked(promises.writeFile).mockResolvedValue();
@@ -196,7 +196,7 @@ test('catalog should be the combination of user catalog and default catalog', as
196196
vi.mocked(promises.readFile).mockResolvedValue(JSON.stringify(userContent));
197197
vi.mocked(path.resolve).mockReturnValue('path');
198198

199-
catalogManager.init();
199+
await catalogManager.init();
200200
await vi.waitUntil(() => catalogManager.getModels().length > userContent.models.length);
201201

202202
const mtimeDate = new Date('2024-04-03T09:51:15.766Z');
@@ -238,7 +238,7 @@ test('catalog should use user items in favour of default', async () => {
238238

239239
vi.mocked(promises.readFile).mockResolvedValue(JSON.stringify(overwriteFullCatalog));
240240

241-
catalogManager.init();
241+
await catalogManager.init();
242242
await vi.waitUntil(() => catalogManager.getModels().length > 0);
243243

244244
const mtimeDate = new Date('2024-04-03T09:51:15.766Z');
@@ -330,7 +330,7 @@ test('filter recipes by language', async () => {
330330
vi.mocked(existsSync).mockReturnValue(true);
331331
vi.mocked(promises.readFile).mockResolvedValue(JSON.stringify(userContent));
332332

333-
catalogManager.init();
333+
await catalogManager.init();
334334
await vi.waitUntil(() => catalogManager.getModels().some(model => model.id === 'model1'));
335335
const result1 = catalogManager.filterRecipes({
336336
languages: ['lang1'],
@@ -375,7 +375,7 @@ test('filter recipes by tool', async () => {
375375
vi.mocked(existsSync).mockReturnValue(true);
376376
vi.mocked(promises.readFile).mockResolvedValue(JSON.stringify(userContent));
377377

378-
catalogManager.init();
378+
await catalogManager.init();
379379
await vi.waitUntil(() => catalogManager.getModels().some(model => model.id === 'model1'));
380380

381381
const result1 = catalogManager.filterRecipes({
@@ -445,7 +445,7 @@ test('filter recipes by framework', async () => {
445445
vi.mocked(existsSync).mockReturnValue(true);
446446
vi.mocked(promises.readFile).mockResolvedValue(JSON.stringify(userContent));
447447

448-
catalogManager.init();
448+
await catalogManager.init();
449449
await vi.waitUntil(() => catalogManager.getModels().some(model => model.id === 'model1'));
450450

451451
const result1 = catalogManager.filterRecipes({
@@ -519,7 +519,7 @@ test('filter recipes by language and framework', async () => {
519519
vi.mocked(existsSync).mockReturnValue(true);
520520
vi.mocked(promises.readFile).mockResolvedValue(JSON.stringify(userContent));
521521

522-
catalogManager.init();
522+
await catalogManager.init();
523523
await vi.waitUntil(() => catalogManager.getModels().some(model => model.id === 'model1'));
524524

525525
const result1 = catalogManager.filterRecipes({
@@ -546,7 +546,7 @@ test('filter recipes by language, tool and framework', async () => {
546546
vi.mocked(existsSync).mockReturnValue(true);
547547
vi.mocked(promises.readFile).mockResolvedValue(JSON.stringify(userContent));
548548

549-
catalogManager.init();
549+
await catalogManager.init();
550550
await vi.waitUntil(() => catalogManager.getModels().some(model => model.id === 'model1'));
551551

552552
const result1 = catalogManager.filterRecipes({
@@ -567,3 +567,15 @@ test('filter recipes by language, tool and framework', async () => {
567567
tools: [{ name: 'tool1', count: 1 }],
568568
});
569569
});
570+
571+
test('models are loaded as soon as init is finished when no user catalog', async () => {
572+
await catalogManager.init();
573+
expect(catalogManager.getModels()).toHaveLength(3);
574+
});
575+
576+
test('models are loaded as soon as init is finished when user catalog exists', async () => {
577+
vi.mocked(promises.readFile).mockResolvedValue(JSON.stringify(userContent));
578+
vi.mocked(existsSync).mockReturnValue(true);
579+
await catalogManager.init();
580+
expect(catalogManager.getModels()).toHaveLength(5);
581+
});

packages/backend/src/managers/catalogManager.ts

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -60,16 +60,21 @@ export class CatalogManager extends Publisher<ApplicationCatalog> implements Dis
6060
/**
6161
* The init method will start a watcher on the user catalog.json
6262
*/
63-
init(): void {
64-
// Creating a json watcher
65-
this.#jsonWatcher = new JsonWatcher(this.getUserCatalogPath(), {
66-
version: CatalogFormat.CURRENT,
67-
recipes: [],
68-
models: [],
69-
categories: [],
63+
async init(): Promise<void> {
64+
return new Promise<void>(resolve => {
65+
// Creating a json watcher
66+
this.#jsonWatcher = new JsonWatcher(this.getUserCatalogPath(), {
67+
version: CatalogFormat.CURRENT,
68+
recipes: [],
69+
models: [],
70+
categories: [],
71+
});
72+
this.#jsonWatcher.onContentUpdated(content => {
73+
this.onUserCatalogUpdate(content);
74+
resolve();
75+
});
76+
this.#jsonWatcher.init();
7077
});
71-
this.#jsonWatcher.onContentUpdated(content => this.onUserCatalogUpdate(content));
72-
this.#jsonWatcher.init();
7378
}
7479

7580
private loadDefaultCatalog(): void {

packages/backend/src/studio.spec.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,12 @@
2121
import { afterEach, beforeEach, expect, test, vi, describe, type MockInstance } from 'vitest';
2222
import { Studio } from './studio';
2323
import { type ExtensionContext, EventEmitter, version } from '@podman-desktop/api';
24+
import { CatalogManager } from './managers/catalogManager';
2425

2526
import * as fs from 'node:fs';
2627

2728
vi.mock('./managers/modelsManager');
29+
vi.mock('./managers/catalogManager');
2830

2931
const mockedExtensionContext = {
3032
subscriptions: [],
@@ -124,6 +126,12 @@ beforeEach(() => {
124126
} as unknown as EventEmitter<unknown>);
125127

126128
mocks.postMessage.mockResolvedValue(undefined);
129+
130+
vi.mocked(CatalogManager).mockReturnValue({
131+
onUpdate: vi.fn(),
132+
init: vi.fn(),
133+
getRecipes: vi.fn().mockReturnValue([]),
134+
} as unknown as CatalogManager);
127135
});
128136

129137
afterEach(() => {

packages/backend/src/studio.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ export class Studio {
212212
* Create catalog manager, responsible for loading the catalog files and watching for changes
213213
*/
214214
this.#catalogManager = new CatalogManager(this.#rpcExtension, appUserDirectory);
215-
this.#catalogManager.init();
215+
await this.#catalogManager.init();
216216

217217
/**
218218
* The builder manager is handling the building tasks, create corresponding tasks

0 commit comments

Comments
 (0)