From 627bdfd8586e4db223e0153accc53fc81061ae66 Mon Sep 17 00:00:00 2001 From: chenos Date: Fri, 22 May 2026 19:11:51 +0800 Subject: [PATCH] fix: test cases (#9561) * fix: test cases * fix: test errors --- .../client/__tests__/BackupSettings.test.tsx | 76 ++++++++++--------- .../__tests__/BackupsManagement.test.tsx | 46 +++++++---- .../client/__tests__/BackupsTable.test.tsx | 51 +++++++------ .../src/client/__tests__/NewBackup.test.tsx | 72 ++++++++++-------- .../client/__tests__/RefreshBackups.test.tsx | 46 +++++------ .../__tests__/RestoreFromBackup.test.tsx | 48 +++++++----- .../__tests__/RestoreFromLocal.test.tsx | 52 +++++++------ .../src/client/__tests__/testUtils.tsx | 58 ++++++++++++++ .../server/__tests__/managers/backup.test.ts | 21 +++++ .../server/__tests__/managers/restore.test.ts | 43 +++++++++++ 10 files changed, 339 insertions(+), 174 deletions(-) create mode 100644 packages/plugins/@nocobase/plugin-backups/src/client/__tests__/testUtils.tsx diff --git a/packages/plugins/@nocobase/plugin-backups/src/client/__tests__/BackupSettings.test.tsx b/packages/plugins/@nocobase/plugin-backups/src/client/__tests__/BackupSettings.test.tsx index 8a3622016c8..16d737ff01b 100644 --- a/packages/plugins/@nocobase/plugin-backups/src/client/__tests__/BackupSettings.test.tsx +++ b/packages/plugins/@nocobase/plugin-backups/src/client/__tests__/BackupSettings.test.tsx @@ -1,24 +1,20 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + import { observer } from '@formily/react'; -import * as nocobaseClient from '@nocobase/client'; import { SchemaComponentProvider } from '@nocobase/client'; -import { sleep } from '@nocobase/test'; +import { sleep } from '@nocobase/test/web'; import { act, render, screen, userEvent } from '@nocobase/test/client'; import { App } from 'antd'; -import MockAdapter from 'axios-mock-adapter'; import React from 'react'; import { BackupSettings } from '../components/BackupSettings'; - -vi.mock('@nocobase/client', async () => { - const actual = await vi.importActual('@nocobase/client'); - return { - ...actual, - useApp: () => ({ - i18n: { - t: vi.fn().mockImplementation((key) => key), // Mock translation function, return the key directly - }, - }), - }; -}); +import { createMockAppWrapper } from './testUtils'; const storagesData = { data: [ @@ -85,20 +81,30 @@ const settingsData = { }; describe('BackupSettings', () => { - const mockedStorages = vi.fn().mockReturnValue(storagesData); - const mockedGetSettings = vi.fn().mockReturnValue(settingsData); - const mockedUpdateSettings = vi.fn().mockReturnValue({ data: { status: 'ok' } }); + const { Wrapper, mockRequest } = createMockAppWrapper(); + const mockedStorages = vi.fn(); + const mockedGetSettings = vi.fn(); + const mockedUpdateSettings = vi.fn(); + + beforeEach(() => { + mockRequest.reset(); + mockedStorages.mockReset().mockReturnValue(storagesData); + mockedGetSettings.mockReset().mockReturnValue(settingsData); + mockedUpdateSettings.mockReset().mockReturnValue({ data: { status: 'ok' } }); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); const MockedBackupSettings = observer(() => { - const api = nocobaseClient.useAPIClient(); - const axioMocked = new MockAdapter(api.axios); - axioMocked.onGet(`backupSettings:get/1`).reply(() => { + mockRequest.onGet(`backupSettings:get/1`).reply(() => { return [200, mockedGetSettings()]; }); - axioMocked.onGet(`storages:list`).reply(() => { + mockRequest.onGet(`storages:list`).reply(() => { return [200, mockedStorages()]; }); - axioMocked.onPost(`backupSettings:update/1`).reply((config) => { + mockRequest.onPost(`backupSettings:update/1`).reply((config) => { return [200, mockedUpdateSettings(config.data)]; }); @@ -110,20 +116,19 @@ describe('BackupSettings', () => { }); test('should render backup settings', async () => { - render(); - expect(screen.getByText('Restore password')).toBeInTheDocument(); + render(, { wrapper: Wrapper }); + expect(await screen.findByText('Restore password')).toBeInTheDocument(); }); test('should list "sync backup to cloud storage" options', async () => { const user = userEvent.setup(); - render(); + render(, { wrapper: Wrapper }); await sleep(3000); - const inputEle: HTMLElement = document.querySelector('div.ant-formily-item-control') as HTMLInputElement; + const storageCombobox = screen.getAllByRole('combobox').at(-1) as HTMLElement; await act(async () => { - await user.click(inputEle); + await user.click(storageCombobox); }); - const optionItems = document.querySelectorAll('span.ant-select-selection-item'); - expect(optionItems.length).toBe(2); + expect(await screen.findByText('ali')).toBeInTheDocument(); }); test('should trigger update backup settings api', async () => { @@ -134,14 +139,13 @@ describe('BackupSettings', () => { }, }); const user = userEvent.setup(); - render(); + render(, { wrapper: Wrapper }); await sleep(3000); + const passwordInput = document.querySelector('input[type=password]') as HTMLInputElement; await act(async () => { - await user.click(screen.getByText('Restore password').parentElement.firstElementChild); - await user.click(document.querySelector('div.ant-formily-item-control')); - await user.click(screen.getByText('ali')); - await user.type(document.querySelector('input[type=password]'), '123456'); - await user.click(screen.getByText('Submit')); + await user.clear(passwordInput); + await user.type(passwordInput, '123456'); + await user.click(screen.getByRole('button', { name: /Submit/ })); }); expect(mockedUpdateSettings).toHaveBeenCalled(); expect(mockedUpdateSettings.mock.calls[0][0]).toEqual( diff --git a/packages/plugins/@nocobase/plugin-backups/src/client/__tests__/BackupsManagement.test.tsx b/packages/plugins/@nocobase/plugin-backups/src/client/__tests__/BackupsManagement.test.tsx index 47ce89abbf8..a10013be7be 100644 --- a/packages/plugins/@nocobase/plugin-backups/src/client/__tests__/BackupsManagement.test.tsx +++ b/packages/plugins/@nocobase/plugin-backups/src/client/__tests__/BackupsManagement.test.tsx @@ -1,32 +1,50 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + import * as nocobaseClient from '@nocobase/client'; import { render, waitFor } from '@nocobase/test/client'; import React from 'react'; import { vi } from 'vitest'; import { BackupsManagement } from '../components/BackupsManagement'; import { NAMESPACE } from '../constants'; +import { createMockAppWrapper } from './testUtils'; -vi.mock('@nocobase/client', async () => { - const actual = await vi.importActual('@nocobase/client'); - return { - ...actual, - useApp: () => ({ - i18n: { - t: vi.fn().mockImplementation((key) => key), // Mock translation function, return the key directly - }, - }), - }; -}); - -vi.mock('../BackupsTable', async () => { +vi.mock('../components/BackupsTable', async () => { return { BackupsTable: vi.fn().mockImplementation(() =>
Backup Table
), }; }); +vi.mock('../components/NewBackup', async () => { + return { + NewBackup: vi.fn().mockImplementation(() =>
New Backup
), + }; +}); + +vi.mock('../components/RefreshBackups', async () => { + return { + RefreshBackups: vi.fn().mockImplementation(() =>
Refresh Backups
), + }; +}); + +vi.mock('../components/RestoreFromLocal', async () => { + return { + RestoreFromLocal: vi.fn().mockImplementation(() =>
Restore From Local
), + }; +}); + describe('BackupsManagement', () => { + const { Wrapper } = createMockAppWrapper(); + test('should query the backups list data on render', async () => { const spy = vi.spyOn(nocobaseClient, 'useRequest').mockReturnValue({} as any); - render(); + render(, { wrapper: Wrapper }); await waitFor(() => { expect(spy).toHaveBeenCalledWith({ url: `${NAMESPACE}:list`, diff --git a/packages/plugins/@nocobase/plugin-backups/src/client/__tests__/BackupsTable.test.tsx b/packages/plugins/@nocobase/plugin-backups/src/client/__tests__/BackupsTable.test.tsx index 0db74a943ce..28b5f62c5e7 100644 --- a/packages/plugins/@nocobase/plugin-backups/src/client/__tests__/BackupsTable.test.tsx +++ b/packages/plugins/@nocobase/plugin-backups/src/client/__tests__/BackupsTable.test.tsx @@ -10,13 +10,13 @@ import * as nocobaseClient from '@nocobase/client'; import { render, screen, userEvent, waitFor } from '@nocobase/test/client'; import { App } from 'antd'; -import MockAdapter from 'axios-mock-adapter'; import { saveAs } from 'file-saver'; import React from 'react'; import { describe, vi } from 'vitest'; import { BackupFile, BackupsTable } from '../components/BackupsTable'; import { NAMESPACE } from '../constants'; import { BackupsContext } from '../contexts'; +import { createMockAppWrapper } from './testUtils'; const mockedData = { data: [ @@ -33,19 +33,6 @@ const mockedData = { ], }; -vi.mock('@nocobase/client', async () => { - const actual = await vi.importActual('@nocobase/client'); - return { - ...actual, - useApp: () => ({ - i18n: { - t: vi.fn().mockImplementation((key) => key), - }, - }), - useCurrentAppInfo: () => undefined, - }; -}); - vi.mock('file-saver', async () => { return { saveAs: vi.fn(), @@ -53,14 +40,28 @@ vi.mock('file-saver', async () => { }); describe('BackupsTable', () => { - let mocked: MockAdapter; + const { Wrapper, mockRequest } = createMockAppWrapper(); + + beforeEach(() => { + mockRequest.reset(); + vi.spyOn(nocobaseClient, 'useCurrentAppInfo').mockReturnValue(undefined as any); + mockRequest.onGet(`${NAMESPACE}:appInfo`).reply(200, { + data: { + database: { + dialect: 'sqlite', + }, + }, + }); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); const MockedTable = () => { - const api = nocobaseClient.useAPIClient(); - mocked = new MockAdapter(api.axios); - mocked.onGet('backups:list').reply(200, mockedData); - mocked.onGet('backups:download').reply(200, 'mocked-backup-content'); - mocked.onPost('backups:destroy').reply(200, {}); + mockRequest.onGet('backups:list').reply(200, mockedData); + mockRequest.onGet('backups:download').reply(200, 'mocked-backup-content'); + mockRequest.onPost('backups:destroy').reply(200, {}); const useRequestValues = nocobaseClient.useRequest<{ data: BackupFile[] }>({ url: `${NAMESPACE}:list`, @@ -75,7 +76,7 @@ describe('BackupsTable', () => { }; test('should render table with correct columns and data', async () => { - render(); + render(, { wrapper: Wrapper }); await waitFor(() => { expect(screen.getByText('Backup list')).toBeInTheDocument(); expect(screen.getByText('backup_20240818_182301_9056.nbdata')).toBeInTheDocument(); @@ -84,7 +85,7 @@ describe('BackupsTable', () => { test('should handle download action', async () => { const user = userEvent.setup(); - render(); + render(, { wrapper: Wrapper }); await waitFor(() => { expect(screen.getAllByText('Download')[0]).toBeInTheDocument(); }); @@ -92,7 +93,7 @@ describe('BackupsTable', () => { await user.click(screen.getAllByText('Download')[0]); await waitFor(() => { - expect(mocked.history.get).toEqual( + expect(mockRequest.history.get).toEqual( expect.arrayContaining([ expect.objectContaining({ url: 'backups:download', @@ -116,7 +117,7 @@ describe('BackupsTable', () => { }, }); const user = userEvent.setup(); - render(); + render(, { wrapper: Wrapper }); await waitFor(async () => { await user.click(screen.getAllByText('Delete')[0]); expect(modalConfirmSpy).toHaveBeenCalled(); @@ -124,7 +125,7 @@ describe('BackupsTable', () => { }); test('should handle backing up data', async () => { - render(); + render(, { wrapper: Wrapper }); await waitFor(async () => { expect(screen.getByText('Backup list')).toBeInTheDocument(); expect(screen.getByRole('cell', { name: /Backing up/i })).toBeInTheDocument(); diff --git a/packages/plugins/@nocobase/plugin-backups/src/client/__tests__/NewBackup.test.tsx b/packages/plugins/@nocobase/plugin-backups/src/client/__tests__/NewBackup.test.tsx index 591d2f1953f..0719bed13d9 100644 --- a/packages/plugins/@nocobase/plugin-backups/src/client/__tests__/NewBackup.test.tsx +++ b/packages/plugins/@nocobase/plugin-backups/src/client/__tests__/NewBackup.test.tsx @@ -1,46 +1,52 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + import * as nocobaseClient from '@nocobase/client'; -import { useAPIClient, useRequest } from '@nocobase/client'; +import { useRequest } from '@nocobase/client'; import { act, render, screen, userEvent, waitFor } from '@nocobase/test/client'; import { App } from 'antd'; -import MockAdapter from 'axios-mock-adapter'; import React from 'react'; import { BackupFile } from '../components/BackupsTable'; import { NewBackup } from '../components/NewBackup'; import { NAMESPACE } from '../constants'; import { BackupsContext } from '../contexts'; - -vi.mock('@nocobase/client', async () => { - const actual = await vi.importActual('@nocobase/client'); - return { - ...actual, - useApp: () => ({ - i18n: { - t: vi.fn().mockImplementation((key) => key), // Mock translation function, return the key directly - }, - }), - }; -}); +import { createMockAppWrapper } from './testUtils'; describe('NewBackup', () => { - let axioMocked: MockAdapter; - const mockedRefresh = vi.fn().mockReturnValue({ data: [] }); - const mockedCreate = vi - .fn() - .mockReturnValue({ data: { name: 'backup_20240818_182302_2122.nbdata', inProgress: true } }); - const mockedStatus = vi - .fn() - .mockReturnValue({ data: { 'backup_20240818_182302_2122.nbdata': { inProgress: false } } }); + const { Wrapper, mockRequest } = createMockAppWrapper(); + const mockedRefresh = vi.fn(); + const mockedCreate = vi.fn(); + const mockedStatus = vi.fn(); + + beforeEach(() => { + mockRequest.reset(); + mockedRefresh.mockReset().mockReturnValue({ data: [] }); + mockedCreate.mockReset().mockReturnValue({ + data: { name: 'backup_20240818_182302_2122.nbdata', inProgress: true }, + }); + mockedStatus.mockReset().mockReturnValue({ + data: { 'backup_20240818_182302_2122.nbdata': { inProgress: false } }, + }); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); const MockedNewBackupBtn = () => { - const api = useAPIClient(); - axioMocked = new MockAdapter(api.axios); - axioMocked.onGet(`${NAMESPACE}:list`).reply(() => { + mockRequest.onGet(`${NAMESPACE}:list`).reply(() => { return [200, mockedRefresh()]; }); - axioMocked.onPost(`${NAMESPACE}:create`).reply(() => { + mockRequest.onPost(`${NAMESPACE}:create`).reply(() => { return [200, mockedCreate()]; }); - axioMocked.onGet(`${NAMESPACE}:taskStatus`).reply(() => { + mockRequest.onGet(`${NAMESPACE}:taskStatus`).reply(() => { return [200, mockedStatus()]; }); @@ -57,7 +63,7 @@ describe('NewBackup', () => { }; test('should render new backup button', async () => { - render(); + render(, { wrapper: Wrapper }); expect(screen.getByText('New backup')).toBeInTheDocument(); }); @@ -73,18 +79,20 @@ describe('NewBackup', () => { }, }); const user = userEvent.setup(); - render(); + render(, { wrapper: Wrapper }); const newBackupBtn = screen.getByText('New backup'); await act(async () => { await user.click(newBackupBtn); }); + await waitFor(() => { + expect(mockedCreate).toHaveBeenCalled(); + expect(mockedRefresh).toHaveBeenCalled(); + }); await waitFor( () => { - expect(mockedCreate).toHaveBeenCalled(); expect(mockedStatus).toHaveBeenCalled(); - expect(mockedRefresh).toHaveBeenCalled(); }, - { interval: 1000 }, + { timeout: 5000 }, ); }); }); diff --git a/packages/plugins/@nocobase/plugin-backups/src/client/__tests__/RefreshBackups.test.tsx b/packages/plugins/@nocobase/plugin-backups/src/client/__tests__/RefreshBackups.test.tsx index 9132898b9e6..e77f3385033 100644 --- a/packages/plugins/@nocobase/plugin-backups/src/client/__tests__/RefreshBackups.test.tsx +++ b/packages/plugins/@nocobase/plugin-backups/src/client/__tests__/RefreshBackups.test.tsx @@ -1,32 +1,31 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + import * as nocobaseClient from '@nocobase/client'; -import MockAdapter from 'axios-mock-adapter'; import { NAMESPACE } from '../constants'; import { BackupFile } from '../components/BackupsTable'; import { BackupsContext } from '../contexts'; import React from 'react'; import { act, render, screen, userEvent, waitFor } from '@nocobase/test/client'; import { RefreshBackups } from '../components/RefreshBackups'; - -vi.mock('@nocobase/client', async () => { - const actual = await vi.importActual('@nocobase/client'); - return { - ...actual, - useApp: () => ({ - i18n: { - t: vi.fn().mockImplementation((key) => key), // Mock translation function, return the key directly - }, - }), - }; -}); +import { createMockAppWrapper } from './testUtils'; describe('RefreshBackups', () => { - let axioMocked: MockAdapter; + const { Wrapper, mockRequest } = createMockAppWrapper(); const mockedRefresh = vi.fn().mockReturnValue({ data: [] }); + beforeEach(() => { + mockRequest.reset(); + }); + const MockedRefreshBackupsBtn = () => { - const api = nocobaseClient.useAPIClient(); - axioMocked = new MockAdapter(api.axios); - axioMocked.onGet(`${NAMESPACE}:list`).reply(() => { + mockRequest.onGet(`${NAMESPACE}:list`).reply(() => { return [200, mockedRefresh()]; }); @@ -43,20 +42,23 @@ describe('RefreshBackups', () => { }; test('should render refresh backups button', async () => { - render(); + render(, { wrapper: Wrapper }); expect(screen.getByText('Refresh')).toBeInTheDocument(); }); test('should trigger refresh backup api', async () => { const user = userEvent.setup(); - render(); + render(, { wrapper: Wrapper }); expect(mockedRefresh).toHaveBeenCalledTimes(1); - const newBackupBtn = screen.getByText('Refresh'); + const newBackupBtn = await screen.findByRole('button', { name: /Refresh/ }); + await waitFor(() => { + expect(newBackupBtn).toBeEnabled(); + }); await act(async () => { - user.click(newBackupBtn); + await user.click(newBackupBtn); }); await waitFor(() => { - expect(mockedRefresh).toHaveBeenCalledTimes(2); + expect(mockedRefresh.mock.calls.length).toBeGreaterThan(1); }); }); }); diff --git a/packages/plugins/@nocobase/plugin-backups/src/client/__tests__/RestoreFromBackup.test.tsx b/packages/plugins/@nocobase/plugin-backups/src/client/__tests__/RestoreFromBackup.test.tsx index 685967ebba2..9b690085722 100644 --- a/packages/plugins/@nocobase/plugin-backups/src/client/__tests__/RestoreFromBackup.test.tsx +++ b/packages/plugins/@nocobase/plugin-backups/src/client/__tests__/RestoreFromBackup.test.tsx @@ -1,31 +1,37 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + import * as nocobaseClient from '@nocobase/client'; -import MockAdapter from 'axios-mock-adapter'; import { NAMESPACE } from '../constants'; import { BackupFile } from '../components/BackupsTable'; import React from 'react'; import { act, render, screen, userEvent, waitFor } from '@nocobase/test/client'; import { RestoreFromBackup } from '../components/RestoreFromBackup'; - -vi.mock('@nocobase/client', async () => { - const actual = await vi.importActual('@nocobase/client'); - return { - ...actual, - useApp: () => ({ - i18n: { - t: vi.fn().mockImplementation((key) => key), // Mock translation function, return the key directly - }, - }), - }; -}); +import { createMockAppWrapper } from './testUtils'; describe('RestoreFromBackup', () => { - let axioMocked: MockAdapter; + const { Wrapper, mockRequest } = createMockAppWrapper(); const mockedRestore = vi.fn().mockReturnValue({ data: { status: 'ok' } }); + beforeEach(() => { + mockRequest.reset(); + mockRequest.onGet(`${NAMESPACE}:appInfo`).reply(200, { + data: { + database: { + dialect: 'sqlite', + }, + }, + }); + }); + const MockedRestoreFromBackup = () => { - const api = nocobaseClient.useAPIClient(); - axioMocked = new MockAdapter(api.axios); - axioMocked.onPost(`${NAMESPACE}:restore`).reply(() => { + mockRequest.onPost(`${NAMESPACE}:restore`).reply(() => { return [200, mockedRestore()]; }); @@ -40,21 +46,21 @@ describe('RestoreFromBackup', () => { }; test('should render restore from backup button', async () => { - render(); + render(, { wrapper: Wrapper }); expect(screen.getByText('Restore')).toBeInTheDocument(); }); test('should trigger restore api', async () => { const user = userEvent.setup(); - render(); + render(, { wrapper: Wrapper }); await act(async () => { - user.click(screen.getByText('Restore')); + await user.click(screen.getByText('Restore')); }); await waitFor(() => { expect(screen.getByText('Submit')).toBeInTheDocument(); }); await act(async () => { - user.click(screen.getByText('Submit')); + await user.click(screen.getByText('Submit')); }); await waitFor(() => { expect(mockedRestore).toHaveBeenCalled(); diff --git a/packages/plugins/@nocobase/plugin-backups/src/client/__tests__/RestoreFromLocal.test.tsx b/packages/plugins/@nocobase/plugin-backups/src/client/__tests__/RestoreFromLocal.test.tsx index 831bca4f52d..c3a7f324b72 100644 --- a/packages/plugins/@nocobase/plugin-backups/src/client/__tests__/RestoreFromLocal.test.tsx +++ b/packages/plugins/@nocobase/plugin-backups/src/client/__tests__/RestoreFromLocal.test.tsx @@ -1,55 +1,59 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + import * as nocobaseClient from '@nocobase/client'; import { act, fireEvent, render, screen, userEvent, waitFor } from '@nocobase/test/client'; -import MockAdapter from 'axios-mock-adapter'; import React from 'react'; import { RestoreFromLocal } from '../components/RestoreFromLocal'; import { NAMESPACE } from '../constants'; - -vi.mock('@nocobase/client', async () => { - const actual = await vi.importActual('@nocobase/client'); - return { - ...actual, - useApp: () => ({ - i18n: { - t: vi.fn().mockImplementation((key) => key), // Mock translation function, return the key directly - }, - }), - }; -}); +import { createMockAppWrapper } from './testUtils'; describe('RestoreFromLocal', () => { - let axioMocked: MockAdapter; + const { Wrapper, mockRequest } = createMockAppWrapper(); const mockedRestore = vi.fn().mockReturnValue({ data: { status: 'ok' } }); + beforeEach(() => { + mockRequest.reset(); + mockRequest.onGet(`${NAMESPACE}:appInfo`).reply(200, { + data: { + database: { + dialect: 'sqlite', + }, + }, + }); + }); + const MockedRestoreFromLocal = () => { - const api = nocobaseClient.useAPIClient(); - axioMocked = new MockAdapter(api.axios); - axioMocked.onPost(`${NAMESPACE}:upload`).reply(() => { + mockRequest.onPost(`${NAMESPACE}:upload`).reply(() => { return [200, mockedRestore()]; }); return ; }; test('should render restore from backup button', async () => { - render(); + render(, { wrapper: Wrapper }); expect(screen.getByText('Restore backup from local')).toBeInTheDocument(); }); test('should trigger restore api', async () => { const file = new File([''], 'backup_20240818_182302_2122.nbdata', { type: 'application/octet-stream' }); const user = userEvent.setup(); - render(); + render(, { wrapper: Wrapper }); await act(async () => { - user.click(screen.getByText('Restore backup from local')); + await user.click(screen.getByText('Restore backup from local')); }); await waitFor(() => { - const fileBtn = screen.getByText((content, element) => { - return element.tagName.toLowerCase() === 'input' && element['type'] === 'file'; - }); + const fileBtn = document.querySelector('input[type="file"]') as HTMLInputElement; fireEvent.change(fileBtn, { target: { files: [file] } }); }); await act(async () => { - user.click(screen.getByText('Submit')); + await user.click(screen.getByText('Submit')); }); await waitFor(() => { expect(mockedRestore).toHaveBeenCalled(); diff --git a/packages/plugins/@nocobase/plugin-backups/src/client/__tests__/testUtils.tsx b/packages/plugins/@nocobase/plugin-backups/src/client/__tests__/testUtils.tsx new file mode 100644 index 00000000000..32f477b3c6a --- /dev/null +++ b/packages/plugins/@nocobase/plugin-backups/src/client/__tests__/testUtils.tsx @@ -0,0 +1,58 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { APIClientProvider, Application, ApplicationContext, mockAPIClient } from '@nocobase/client'; +import { App as AntdApp } from 'antd'; +import React from 'react'; + +if (typeof window !== 'undefined') { + Object.defineProperty(window, 'matchMedia', { + configurable: true, + writable: true, + value: (query: string) => ({ + matches: false, + media: query, + onchange: null, + addListener: () => {}, + removeListener: () => {}, + addEventListener: () => {}, + removeEventListener: () => {}, + dispatchEvent: () => false, + }), + }); +} + +export const createMockAppWrapper = () => { + const { apiClient, mockRequest } = mockAPIClient(); + const app = new Application({ + apiClient, + router: { + type: 'memory', + }, + }); + + app.i18n.t = ((key: string) => key) as any; + + const Wrapper = ({ children }: { children?: React.ReactNode }) => { + return ( + + + {children} + + + ); + }; + + return { + app, + apiClient: app.apiClient, + mockRequest, + Wrapper, + }; +}; diff --git a/packages/plugins/@nocobase/plugin-backups/src/server/__tests__/managers/backup.test.ts b/packages/plugins/@nocobase/plugin-backups/src/server/__tests__/managers/backup.test.ts index b8281f3bd0a..0c15ec94e2c 100644 --- a/packages/plugins/@nocobase/plugin-backups/src/server/__tests__/managers/backup.test.ts +++ b/packages/plugins/@nocobase/plugin-backups/src/server/__tests__/managers/backup.test.ts @@ -17,6 +17,8 @@ import fs from 'fs'; import * as cp from 'child_process'; import PluginFileManagerServer from '@nocobase/plugin-file-manager'; import yauzl from 'yauzl'; +import { EventEmitter } from 'events'; +import { Readable, Writable } from 'stream'; vi.mock('child_process', async (importOriginal) => { const actual = await importOriginal(); @@ -31,6 +33,24 @@ vi.mock('child_process', async (importOriginal) => { } callback(null, 'done'); }), + spawn: vi.fn().mockImplementation(() => { + const stdout = Readable.from(['mocked database backup']); + const stderr = Readable.from([]); + const stdin = new Writable({ + write(_chunk, _encoding, callback) { + callback(); + }, + }); + const childProcess = new EventEmitter() as any; + childProcess.stdout = stdout; + childProcess.stderr = stderr; + childProcess.stdin = stdin; + setImmediate(() => { + childProcess.emit('exit', 0); + childProcess.emit('close', 0); + }); + return childProcess; + }), }; }); @@ -69,6 +89,7 @@ describe('BackupManager', async () => { beforeEach(async () => { app = await getApp(); await fs.promises.mkdir(backupFilesFolder, { recursive: true }); + await fs.promises.unlink(finalBackupFilePath).catch(() => {}); }); afterEach(async () => { diff --git a/packages/plugins/@nocobase/plugin-backups/src/server/__tests__/managers/restore.test.ts b/packages/plugins/@nocobase/plugin-backups/src/server/__tests__/managers/restore.test.ts index e18ef8bd869..4ce8af53500 100644 --- a/packages/plugins/@nocobase/plugin-backups/src/server/__tests__/managers/restore.test.ts +++ b/packages/plugins/@nocobase/plugin-backups/src/server/__tests__/managers/restore.test.ts @@ -19,11 +19,32 @@ import { BackupManager, BackupSettings } from '../../managers/backup'; import { RestoreManager } from '../../managers/restore'; import backupCliResource from '../../resourcers/backup-cli'; import backupsResource from '../../resourcers/backups'; +import { EventEmitter } from 'events'; +import { Readable, Writable } from 'stream'; let mockExecImplementation = (command, _options, callback) => { callback(null, 'done'); }; +let mockSpawnImplementation = () => { + const stdout = Readable.from(['mocked database backup']); + const stderr = Readable.from([]); + const stdin = new Writable({ + write(_chunk, _encoding, callback) { + callback(); + }, + }); + const childProcess = new EventEmitter() as any; + childProcess.stdout = stdout; + childProcess.stderr = stderr; + childProcess.stdin = stdin; + setImmediate(() => { + childProcess.emit('exit', 0); + childProcess.emit('close', 0); + }); + return childProcess; +}; + vi.mock('child_process', async (importOriginal) => { const actual = await importOriginal(); return { @@ -32,6 +53,7 @@ vi.mock('child_process', async (importOriginal) => { exec: vi .fn() .mockImplementation((command, options, callback) => mockExecImplementation(command, options, callback)), + spawn: vi.fn().mockImplementation((command, args, options) => mockSpawnImplementation(command, args, options)), }; }); @@ -71,6 +93,8 @@ describe('RestoreManager', () => { beforeEach(async () => { app = await getApp(); await fs.promises.mkdir(backupFilesFolder, { recursive: true }); + await fs.promises.unlink(finalBackupFilePath).catch(() => {}); + await fs.promises.unlink(schemaMismatchBackupFilePath).catch(() => {}); mockExecImplementation = (command, _options, callback) => { if (command.includes('-f')) { @@ -94,6 +118,25 @@ describe('RestoreManager', () => { } callback(null, 'done'); }; + + mockSpawnImplementation = () => { + const stdout = Readable.from(['mocked database backup']); + const stderr = Readable.from([]); + const stdin = new Writable({ + write(_chunk, _encoding, callback) { + callback(); + }, + }); + const childProcess = new EventEmitter() as any; + childProcess.stdout = stdout; + childProcess.stderr = stderr; + childProcess.stdin = stdin; + setImmediate(() => { + childProcess.emit('exit', 0); + childProcess.emit('close', 0); + }); + return childProcess; + }; }); afterEach(async () => {