From f7fab214327580a167e200d5c6e3cfdb271bf1b3 Mon Sep 17 00:00:00 2001 From: axel7083 <42176370+axel7083@users.noreply.github.com> Date: Tue, 4 Feb 2025 19:33:15 +0100 Subject: [PATCH] tests: ensuring everything is properly tested Signed-off-by: axel7083 <42176370+axel7083@users.noreply.github.com> --- .../backend/src/apis/quadlet-api-impl.spec.ts | 76 ++++++++++ .../utils/parsers/quadlet-kube-parser.spec.ts | 37 +++++ .../utils/parsers/quadlet-type-parser.spec.ts | 26 ++++ .../lib/monaco-editor/KubeYamlEditor.spec.ts | 139 ++++++++++++++++++ .../lib/monaco-editor/KubeYamlEditor.svelte | 2 +- .../frontend/src/pages/QuadletDetails.spec.ts | 109 ++++++++++++++ packages/frontend/src/utils/quadlet.spec.ts | 56 +++++++ 7 files changed, 444 insertions(+), 1 deletion(-) create mode 100644 packages/backend/src/apis/quadlet-api-impl.spec.ts create mode 100644 packages/backend/src/utils/parsers/quadlet-kube-parser.spec.ts create mode 100644 packages/backend/src/utils/parsers/quadlet-type-parser.spec.ts create mode 100644 packages/frontend/src/lib/monaco-editor/KubeYamlEditor.spec.ts create mode 100644 packages/frontend/src/pages/QuadletDetails.spec.ts create mode 100644 packages/frontend/src/utils/quadlet.spec.ts diff --git a/packages/backend/src/apis/quadlet-api-impl.spec.ts b/packages/backend/src/apis/quadlet-api-impl.spec.ts new file mode 100644 index 0000000..fed748c --- /dev/null +++ b/packages/backend/src/apis/quadlet-api-impl.spec.ts @@ -0,0 +1,76 @@ +/********************************************************************** + * Copyright (C) 2025 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 { QuadletApiImpl } from './quadlet-api-impl'; +import type { QuadletService } from '../services/quadlet-service'; +import type { SystemdService } from '../services/systemd-service'; +import type { PodmanService } from '../services/podman-service'; +import type { ProviderService } from '../services/provider-service'; +import type { LoggerService } from '../services/logger-service'; +import type { ProviderContainerConnection } from '@podman-desktop/api'; +import type { ProviderContainerConnectionIdentifierInfo } from '/@shared/src/models/provider-container-connection-identifier-info'; + +const QUADLET_SERVICE: QuadletService = { + getKubeYAML: vi.fn(), +} as unknown as QuadletService; +const SYSTEMD_SERVICE: SystemdService = {} as unknown as SystemdService; +const PODMAN_SERVICE: PodmanService = {} as unknown as PodmanService; +const PROVIDER_SERVICE: ProviderService = { + getProviderContainerConnection: vi.fn(), +} as unknown as ProviderService; +const LOGGER_SERVICE: LoggerService = {} as unknown as LoggerService; + +const WSL_PROVIDER_CONNECTION_MOCK: ProviderContainerConnection = { + connection: { + type: 'podman', + vmType: 'WSL', + name: 'podman-machine-default', + }, + providerId: 'podman', +} as ProviderContainerConnection; + +const WSL_PROVIDER_IDENTIFIER: ProviderContainerConnectionIdentifierInfo = { + name: WSL_PROVIDER_CONNECTION_MOCK.connection.name, + providerId: WSL_PROVIDER_CONNECTION_MOCK.providerId, +}; + +beforeEach(() => { + vi.resetAllMocks(); + + vi.mocked(PROVIDER_SERVICE.getProviderContainerConnection).mockReturnValue(WSL_PROVIDER_CONNECTION_MOCK); +}); + +function getQuadletApiImpl(): QuadletApiImpl { + return new QuadletApiImpl({ + quadlet: QUADLET_SERVICE, + systemd: SYSTEMD_SERVICE, + podman: PODMAN_SERVICE, + providers: PROVIDER_SERVICE, + loggerService: LOGGER_SERVICE, + }); +} + +test('QuadletApiImpl#getKubeYAML should propagate result from QuadletService#getKubeYAML', async () => { + vi.mocked(QUADLET_SERVICE.getKubeYAML).mockResolvedValue('dummy-yaml-content'); + + const api = getQuadletApiImpl(); + + const result = await api.getKubeYAML(WSL_PROVIDER_IDENTIFIER, 'dummy-quadlet-id'); + expect(result).toStrictEqual('dummy-yaml-content'); + expect(PROVIDER_SERVICE.getProviderContainerConnection).toHaveBeenCalledWith(WSL_PROVIDER_IDENTIFIER); +}); diff --git a/packages/backend/src/utils/parsers/quadlet-kube-parser.spec.ts b/packages/backend/src/utils/parsers/quadlet-kube-parser.spec.ts new file mode 100644 index 0000000..3302c02 --- /dev/null +++ b/packages/backend/src/utils/parsers/quadlet-kube-parser.spec.ts @@ -0,0 +1,37 @@ +/********************************************************************** + * Copyright (C) 2025 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 { test, expect } from 'vitest'; +import { QuadletKubeParser } from './quadlet-kube-parser'; + +const KUBE_QUADLET_EXAMPLE = ` +# example.kube +[X-Kube] +Yaml=/mnt/foo/bar.yaml + +[Unit] +Wants=network-online.target +After=network-online.target +SourcePath=/home/user/.config/containers/systemd/nginx2.container +RequiresMountsFor=%t/containers +`; + +test('expect parser to properly extract Yaml path', () => { + const xkube = new QuadletKubeParser(KUBE_QUADLET_EXAMPLE).parse(); + expect(xkube.yaml).toStrictEqual('/mnt/foo/bar.yaml'); +}); diff --git a/packages/backend/src/utils/parsers/quadlet-type-parser.spec.ts b/packages/backend/src/utils/parsers/quadlet-type-parser.spec.ts new file mode 100644 index 0000000..a7a6251 --- /dev/null +++ b/packages/backend/src/utils/parsers/quadlet-type-parser.spec.ts @@ -0,0 +1,26 @@ +/********************************************************************** + * Copyright (C) 2025 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 { test, expect } from 'vitest'; +import { QuadletType } from '/@shared/src/utils/quadlet-type'; +import { QuadletTypeParser } from './quadlet-type-parser'; + +test.each(Object.values(QuadletType))('parsing quadlet %s', (type: QuadletType) => { + const result = new QuadletTypeParser(`[${type}]\nfoo=bar`).parse(); + expect(result).toStrictEqual(type); +}); diff --git a/packages/frontend/src/lib/monaco-editor/KubeYamlEditor.spec.ts b/packages/frontend/src/lib/monaco-editor/KubeYamlEditor.spec.ts new file mode 100644 index 0000000..ed837aa --- /dev/null +++ b/packages/frontend/src/lib/monaco-editor/KubeYamlEditor.spec.ts @@ -0,0 +1,139 @@ +/********************************************************************** + * Copyright (C) 2025 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 '@testing-library/jest-dom/vitest'; + +import { render, fireEvent } from '@testing-library/svelte'; +import { beforeEach, test, vi, expect } from 'vitest'; +import KubeYamlEditor from '/@/lib/monaco-editor/KubeYamlEditor.svelte'; +import type { QuadletInfo } from '/@shared/src/models/quadlet-info'; +import { QuadletType } from '/@shared/src/utils/quadlet-type'; +import type { ProviderContainerConnectionIdentifierInfo } from '/@shared/src/models/provider-container-connection-identifier-info'; +import { quadletAPI } from '/@/api/client'; +import MonacoEditor from '/@/lib/monaco-editor/MonacoEditor.svelte'; + +// mock monaco editor +vi.mock('/@/lib/monaco-editor/MonacoEditor.svelte'); +// mock clients +vi.mock('/@/api/client', () => ({ + quadletAPI: { + getKubeYAML: vi.fn(), + }, +})); + +const MOCK_YAML = ` +foo=bar +`; + +beforeEach(() => { + vi.resetAllMocks(); + + vi.mocked(quadletAPI.getKubeYAML).mockResolvedValue(MOCK_YAML); +}); + +const PODMAN_MACHINE_DEFAULT: ProviderContainerConnectionIdentifierInfo = { + name: 'podman-machine-default', + providerId: 'podman', +}; + +const KUBE_QUADLET: QuadletInfo & { type: QuadletType.KUBE } = { + type: QuadletType.KUBE, + id: `foo.bar`, + path: `/mnt/foo/bar.kube`, + content: 'dummy-content', + state: 'active', + connection: PODMAN_MACHINE_DEFAULT, +}; + +test('ensure reload button is visible', async () => { + const { getByTitle } = render(KubeYamlEditor, { + quadlet: KUBE_QUADLET, + loading: false, + }); + + const reloadBtn = getByTitle('Reload file'); + expect(reloadBtn).toBeInTheDocument(); + // onmount pull the yaml so need to wait for completion + await vi.waitFor(() => { + expect(reloadBtn).toBeEnabled(); + }); +}); + +test('ensure reload button is disabled when loading true', async () => { + const { getByTitle } = render(KubeYamlEditor, { + quadlet: KUBE_QUADLET, + loading: true, + }); + + const reloadBtn = getByTitle('Reload file'); + expect(reloadBtn).toBeInTheDocument(); + expect(reloadBtn).toBeDisabled(); +}); + +test('expect reload button to call quadletAPI#getKubeYAML', async () => { + const { getByTitle } = render(KubeYamlEditor, { + quadlet: KUBE_QUADLET, + loading: false, + }); + + const reloadBtn = getByTitle('Reload file'); + vi.mocked(quadletAPI.getKubeYAML).mockReset(); + await fireEvent.click(reloadBtn); + + await vi.waitFor(() => { + expect(quadletAPI.getKubeYAML).toHaveBeenCalledOnce(); + }); +}); + +test('expect result from quadletAPI#getKubeYAML to be displayed in monaco editor', async () => { + render(KubeYamlEditor, { + quadlet: KUBE_QUADLET, + loading: false, + }); + + await vi.waitFor(() => { + expect(quadletAPI.getKubeYAML).toHaveBeenCalled(); + }); + + await vi.waitFor(() => { + expect(MonacoEditor).toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ + content: MOCK_YAML, + }), + ); + }); +}); + +test('expect error from quadletAPI#getKubeYAML to be displayed', async () => { + vi.mocked(quadletAPI.getKubeYAML).mockRejectedValue(new Error('Dummy foo error')); + + const { getByRole } = render(KubeYamlEditor, { + quadlet: KUBE_QUADLET, + loading: false, + }); + + await vi.waitFor(() => { + expect(quadletAPI.getKubeYAML).toHaveBeenCalled(); + }); + + await vi.waitFor(() => { + const alert = getByRole('alert'); + expect(alert).toHaveTextContent('Something went wrong: Error: Dummy foo error'); + }); +}); diff --git a/packages/frontend/src/lib/monaco-editor/KubeYamlEditor.svelte b/packages/frontend/src/lib/monaco-editor/KubeYamlEditor.svelte index 6acb84b..ff0a63c 100644 --- a/packages/frontend/src/lib/monaco-editor/KubeYamlEditor.svelte +++ b/packages/frontend/src/lib/monaco-editor/KubeYamlEditor.svelte @@ -37,7 +37,7 @@ onMount(() => {
- +
{#if error} diff --git a/packages/frontend/src/pages/QuadletDetails.spec.ts b/packages/frontend/src/pages/QuadletDetails.spec.ts new file mode 100644 index 0000000..e3f87f0 --- /dev/null +++ b/packages/frontend/src/pages/QuadletDetails.spec.ts @@ -0,0 +1,109 @@ +/********************************************************************** + * Copyright (C) 2025 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 '@testing-library/jest-dom/vitest'; + +import { render } from '@testing-library/svelte'; +import * as quadletStore from '/@store/quadlets'; +import { beforeEach, expect, test, vi } from 'vitest'; +import type { QuadletInfo } from '/@shared/src/models/quadlet-info'; +import { QuadletType } from '/@shared/src/utils/quadlet-type'; +import { readable } from 'svelte/store'; +import * as connectionStore from '/@store/connections'; +import type { ProviderContainerConnectionDetailedInfo } from '/@shared/src/models/provider-container-connection-detailed-info'; +import QuadletDetails from '/@/pages/QuadletDetails.svelte'; + +// mock clients +vi.mock('/@/api/client', () => ({ + providerAPI: {}, + quadletAPI: {}, +})); +// mock stores +vi.mock('/@store/connections'); +vi.mock('/@store/quadlets'); +// mock component +vi.mock('/@/lib/monaco-editor/MonacoEditor.svelte'); + +// ui object +const WSL_PROVIDER_DETAILED_INFO: ProviderContainerConnectionDetailedInfo = { + providerId: 'podman', + name: 'podman-machine', + vmType: 'WSL', + status: 'started', +}; + +const CONTAINER_QUADLET_MOCK: QuadletInfo = { + connection: WSL_PROVIDER_DETAILED_INFO, + id: `foo.container`, + content: 'dummy-content', + state: 'active', + path: `bar/foo.container`, + type: QuadletType.CONTAINER, +}; + +const IMAGE_QUADLET_MOCK: QuadletInfo = { + // either WSL either QEMU + connection: WSL_PROVIDER_DETAILED_INFO, + id: `foo.image`, + content: 'dummy-content', + state: 'active', + path: `bar/foo.image`, + type: QuadletType.IMAGE, +}; + +const KUBE_QUADLET_MOCK: QuadletInfo = { + // either WSL either QEMU + connection: WSL_PROVIDER_DETAILED_INFO, + id: `foo.kube`, + content: 'dummy-content', + state: 'active', + path: `bar/foo.kube`, + type: QuadletType.KUBE, +}; + +beforeEach(() => { + vi.resetAllMocks(); + vi.mocked(quadletStore).quadletsInfo = readable([CONTAINER_QUADLET_MOCK, IMAGE_QUADLET_MOCK, KUBE_QUADLET_MOCK]); + vi.mocked(connectionStore).providerConnectionsInfo = readable([WSL_PROVIDER_DETAILED_INFO]); +}); + +test('container quadlet should have generated and source tab', async () => { + const { getByText, queryByText } = render(QuadletDetails, { + connection: WSL_PROVIDER_DETAILED_INFO.name, + providerId: WSL_PROVIDER_DETAILED_INFO.providerId, + id: CONTAINER_QUADLET_MOCK.id, + }); + + const generateTab = getByText('Generated'); + expect(generateTab).toBeInTheDocument(); + const sourceTab = getByText('Source'); + expect(sourceTab).toBeInTheDocument(); + const kubeTab = queryByText('kube yaml'); + expect(kubeTab).toBeNull(); +}); + +test('kube quadlet should have kube yaml tab', async () => { + const { getByText } = render(QuadletDetails, { + connection: WSL_PROVIDER_DETAILED_INFO.name, + providerId: WSL_PROVIDER_DETAILED_INFO.providerId, + id: KUBE_QUADLET_MOCK.id, + }); + + const kubeTab = getByText('kube yaml'); + expect(kubeTab).toBeInTheDocument(); +}); diff --git a/packages/frontend/src/utils/quadlet.spec.ts b/packages/frontend/src/utils/quadlet.spec.ts new file mode 100644 index 0000000..75844e5 --- /dev/null +++ b/packages/frontend/src/utils/quadlet.spec.ts @@ -0,0 +1,56 @@ +/********************************************************************** + * Copyright (C) 2025 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 { test, expect } from 'vitest'; +import { QuadletType } from '/@shared/src/utils/quadlet-type'; +import { isKubeQuadlet } from '/@/utils/quadlet'; +import type { ProviderContainerConnectionIdentifierInfo } from '/@shared/src/models/provider-container-connection-identifier-info'; + +const PODMAN_MACHINE_DEFAULT: ProviderContainerConnectionIdentifierInfo = { + name: 'podman-machine-default', + providerId: 'podman', +}; + +test.each(Object.values(QuadletType).filter(type => type !== QuadletType.KUBE))( + 'expect %s not to be recognised as kube', + type => { + expect( + isKubeQuadlet({ + type: type, + id: `foo.bar`, + path: `/mnt/foo/bar.${type.toLowerCase()}`, + content: 'dummy-content', + state: 'active', + connection: PODMAN_MACHINE_DEFAULT, + }), + ).toBeFalsy(); + }, +); + +test(`expect ${QuadletType.KUBE} to be recognised`, () => { + expect( + isKubeQuadlet({ + type: QuadletType.KUBE, + id: `foo.bar`, + path: `/mnt/foo/bar.kube`, + content: 'dummy-content', + state: 'active', + connection: PODMAN_MACHINE_DEFAULT, + }), + ).toBeTruthy(); +});