From 613bd0044f9116de42419db4036f55e1634ef0e7 Mon Sep 17 00:00:00 2001 From: axel7083 <42176370+axel7083@users.noreply.github.com> Date: Thu, 8 Feb 2024 11:09:50 +0100 Subject: [PATCH] fix: avoid losing tasks when pull application throw error (#247) * fix: avoid losing tasks when pull application throw error Signed-off-by: axel7083 <42176370+axel7083@users.noreply.github.com> * fix: prettier Signed-off-by: axel7083 <42176370+axel7083@users.noreply.github.com> * fix: tests Signed-off-by: axel7083 <42176370+axel7083@users.noreply.github.com> --------- Signed-off-by: axel7083 <42176370+axel7083@users.noreply.github.com> --- .../registries/RecipeStatusRegistry.spec.ts | 113 ++++++++++++++++++ .../src/registries/RecipeStatusRegistry.ts | 11 +- packages/backend/src/studio-api-impl.ts | 2 +- 3 files changed, 124 insertions(+), 2 deletions(-) create mode 100644 packages/backend/src/registries/RecipeStatusRegistry.spec.ts diff --git a/packages/backend/src/registries/RecipeStatusRegistry.spec.ts b/packages/backend/src/registries/RecipeStatusRegistry.spec.ts new file mode 100644 index 000000000..a1b12daba --- /dev/null +++ b/packages/backend/src/registries/RecipeStatusRegistry.spec.ts @@ -0,0 +1,113 @@ +/********************************************************************** + * Copyright (C) 2024 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ***********************************************************************/ +import { expect, test, vi, beforeEach } from 'vitest'; +import type { TaskRegistry } from './TaskRegistry'; +import type { Webview } from '@podman-desktop/api'; +import { RecipeStatusRegistry } from './RecipeStatusRegistry'; +import { MSG_NEW_RECIPE_STATE } from '@shared/Messages'; + +const mocks = vi.hoisted(() => ({ + setMock: vi.fn(), + postMessageMock: vi.fn(), +})); + +const taskRegistry = { + set: mocks.setMock, +} as unknown as TaskRegistry; + +const webview = { + postMessage: mocks.postMessageMock, +} as unknown as Webview; + +beforeEach(() => { + vi.resetAllMocks(); +}); + +test('recipe status registry should start without any statuses', () => { + const recipeStatusRegistry = new RecipeStatusRegistry(taskRegistry, webview); + expect(recipeStatusRegistry.getStatuses().size).toBe(0); +}); + +test('taskRegistry should have been updated', () => { + const recipeStatusRegistry = new RecipeStatusRegistry(taskRegistry, webview); + recipeStatusRegistry.setStatus('random', { + recipeId: 'random', + state: 'none', + tasks: [ + { + id: 'task-1', + name: 'task-1', + state: 'loading', + }, + ], + }); + expect(recipeStatusRegistry.getStatuses().size).toBe(1); + expect(mocks.setMock).toHaveBeenNthCalledWith(1, { + id: 'task-1', + name: 'task-1', + state: 'loading', + }); +}); + +test('webview should have been notified', () => { + const recipeStatusRegistry = new RecipeStatusRegistry(taskRegistry, webview); + recipeStatusRegistry.setStatus('random', { + recipeId: 'random', + state: 'none', + tasks: [], + }); + expect(mocks.postMessageMock).toHaveBeenNthCalledWith(1, { + id: MSG_NEW_RECIPE_STATE, + body: new Map([ + [ + 'random', + { + recipeId: 'random', + state: 'none', + tasks: [], + }, + ], + ]), + }); +}); + +test('recipe status should have been updated', () => { + const recipeStatusRegistry = new RecipeStatusRegistry(taskRegistry, webview); + recipeStatusRegistry.setStatus('random', { + recipeId: 'random', + state: 'none', + tasks: [ + { + id: 'task-1', + name: 'task-1', + state: 'loading', + }, + ], + }); + let statuses = recipeStatusRegistry.getStatuses(); + expect(statuses.size).toBe(1); + expect(statuses.get('random').tasks.length).toBe(1); + expect(statuses.get('random').state).toBe('none'); + + // update the recipe state + recipeStatusRegistry.setRecipeState('random', 'error'); + statuses = recipeStatusRegistry.getStatuses(); + expect(statuses.size).toBe(1); + expect(statuses.get('random').tasks.length).toBe(1); + expect(statuses.get('random').state).toBe('error'); +}); diff --git a/packages/backend/src/registries/RecipeStatusRegistry.ts b/packages/backend/src/registries/RecipeStatusRegistry.ts index e614bec27..a3d10a620 100644 --- a/packages/backend/src/registries/RecipeStatusRegistry.ts +++ b/packages/backend/src/registries/RecipeStatusRegistry.ts @@ -16,7 +16,7 @@ * SPDX-License-Identifier: Apache-2.0 ***********************************************************************/ -import type { RecipeStatus } from '@shared/src/models/IRecipeStatus'; +import type { RecipeStatus, RecipeStatusState } from '@shared/src/models/IRecipeStatus'; import type { TaskRegistry } from './TaskRegistry'; import type { Webview } from '@podman-desktop/api'; import { MSG_NEW_RECIPE_STATE } from '@shared/Messages'; @@ -40,6 +40,15 @@ export class RecipeStatusRegistry { }); // we don't want to wait } + setRecipeState(recipeId: string, state: RecipeStatusState): void { + if (!this.statuses.has(recipeId)) throw new Error(`The recipe status with id ${recipeId} does not exist.`); + const recipeStatus = this.statuses.get(recipeId); + this.statuses.set(recipeId, { + ...recipeStatus, + state: state, + }); + } + getStatus(recipeId: string): RecipeStatus | undefined { return this.statuses.get(recipeId); } diff --git a/packages/backend/src/studio-api-impl.ts b/packages/backend/src/studio-api-impl.ts index d8ea3283a..efe82c19a 100644 --- a/packages/backend/src/studio-api-impl.ts +++ b/packages/backend/src/studio-api-impl.ts @@ -82,7 +82,7 @@ export class StudioApiImpl implements StudioAPI { this.applicationManager.pullApplication(recipe, model), ) .catch(() => { - this.recipeStatusRegistry.setStatus(recipeId, { recipeId: recipeId, state: 'error', tasks: [] }); + this.recipeStatusRegistry.setRecipeState(recipeId, 'error'); }); }