test(mounts): admin CRUD hurl coverage + frontend mounts-tab tests
Close the two coverage gaps in the external-mounts feature — the admin REST surface and the admin UI tab both previously had no automated tests. Backend (hurl): - tests/api/external_mounts.hurl — black-box coverage of the admin mount endpoints (list / create / delete): input validation (empty name, non-existent host path → 400), create → 201 with the full view, the mount appears in the list, delete → 204 then 404, and the /api/admin middleware denies a non-admin on every verb → 403. - server.env: enable OXICLOUD_ENABLE_EXTERNAL_MOUNTS for the test server; run.sh: register the new scenario. Frontend (vitest): - admin/page.test.ts — opening the mounts tab loads + lists mounts, the create form calls createExternalMount, and delete goes through the confirm modal to deleteExternalMount. Tooling: - devenv.nix: add b3sum (used by tests/api/run.sh's storage-integrity check).
This commit is contained in:
@@ -9,10 +9,13 @@ vi.mock('$lib/stores/session.svelte', () => ({ session }));
|
||||
vi.mock('$lib/stores/ui.svelte', () => ({ ui }));
|
||||
vi.mock('$lib/api/endpoints/admin', () => ({
|
||||
clearPluginLogs: vi.fn(),
|
||||
createExternalMount: vi.fn(),
|
||||
createUser: vi.fn(),
|
||||
deleteExternalMount: vi.fn(),
|
||||
deletePlugin: vi.fn(),
|
||||
deleteUser: vi.fn(),
|
||||
getDashboard: vi.fn(),
|
||||
listExternalMounts: vi.fn(),
|
||||
getMigration: vi.fn(),
|
||||
getOidcSettings: vi.fn(),
|
||||
getPluginLogs: vi.fn(),
|
||||
@@ -71,6 +74,17 @@ const user = {
|
||||
is_external: false
|
||||
};
|
||||
|
||||
const mount = {
|
||||
mount_folder_id: 'mnt-1',
|
||||
name: 'Media',
|
||||
kind: 'local_fs',
|
||||
owner_id: 'admin',
|
||||
read_only: true,
|
||||
drive_id: 'd1',
|
||||
mount_path: 'Personal/Media',
|
||||
config: { path: '/srv/media', read_only: true }
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
m(admin.getDashboard).mockResolvedValue(dashboard);
|
||||
@@ -104,6 +118,7 @@ beforeEach(() => {
|
||||
from: 'a@x.test',
|
||||
user_state: 'unset'
|
||||
});
|
||||
m(admin.listExternalMounts).mockResolvedValue([mount]);
|
||||
});
|
||||
|
||||
it('loads the dashboard on mount', async () => {
|
||||
@@ -160,6 +175,51 @@ it('loads plugins when the plugins tab is opened', async () => {
|
||||
await waitFor(() => expect(admin.listPlugins).toHaveBeenCalled());
|
||||
});
|
||||
|
||||
it('loads external mounts when the mounts tab is opened and lists them', async () => {
|
||||
render(AdminPage);
|
||||
await fireEvent.click(await screen.findByTestId('admin-mounts-tab'));
|
||||
await waitFor(() => expect(admin.listExternalMounts).toHaveBeenCalled());
|
||||
// The configured mount is rendered in the table.
|
||||
expect(await screen.findByText('Media')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('creates a mount from the mounts form', async () => {
|
||||
// A distinct mount (new id) so the keyed {#each} doesn't collide with
|
||||
// the one already loaded by listExternalMounts.
|
||||
m(admin.createExternalMount).mockResolvedValue({
|
||||
...mount,
|
||||
mount_folder_id: 'mnt-2',
|
||||
name: 'Photos',
|
||||
read_only: false,
|
||||
mount_path: 'Personal/Photos',
|
||||
config: { path: '/srv/photos', read_only: false }
|
||||
});
|
||||
render(AdminPage);
|
||||
await fireEvent.click(await screen.findByTestId('admin-mounts-tab'));
|
||||
await fireEvent.input(await screen.findByTestId('mount-name'), {
|
||||
target: { value: 'Photos' }
|
||||
});
|
||||
await fireEvent.input(screen.getByTestId('mount-path'), {
|
||||
target: { value: '/srv/photos' }
|
||||
});
|
||||
await fireEvent.click(screen.getByTestId('mount-create'));
|
||||
await waitFor(() =>
|
||||
expect(admin.createExternalMount).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ name: 'Photos', host_path: '/srv/photos' })
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it('deletes a mount through the confirm modal', async () => {
|
||||
m(admin.deleteExternalMount).mockResolvedValue(undefined);
|
||||
render(AdminPage);
|
||||
await fireEvent.click(await screen.findByTestId('admin-mounts-tab'));
|
||||
await fireEvent.click(await screen.findByTestId('mount-delete'));
|
||||
// deleteMount() gates on the styled confirm modal.
|
||||
await fireEvent.click(await screen.findByTestId('admin-confirm-ok-btn'));
|
||||
await waitFor(() => expect(admin.deleteExternalMount).toHaveBeenCalledWith('mnt-1'));
|
||||
});
|
||||
|
||||
it("toggles a user's role through the confirm modal", async () => {
|
||||
m(admin.setUserRole).mockResolvedValue(undefined);
|
||||
render(AdminPage);
|
||||
|
||||
Reference in New Issue
Block a user