creation for course creator access When a course creator creates a new library, the cached JWT token for Meilisearch needs to be refreshed to include the new library's access_id. Without this, newly added components won't appear in search results until the page is refreshed. This works in conjunction with the backend fix that creates SearchAccess records immediately on library creation. The fix invalidates the content_search query, triggering React Query to refetch the token with updated access permissions.
251 lines
8.3 KiB
TypeScript
251 lines
8.3 KiB
TypeScript
import React from 'react';
|
|
|
|
import { QueryClientProvider } from '@tanstack/react-query';
|
|
import { renderHook, waitFor } from '@testing-library/react';
|
|
|
|
import { mockContentLibrary } from '@src/library-authoring/data/api.mocks';
|
|
import { initializeMocks } from '@src/testUtils';
|
|
import { libraryAuthoringQueryKeys } from '../../data/apiHooks';
|
|
import {
|
|
useCreateLibraryRestore,
|
|
useCreateLibraryV2,
|
|
useGetLibraryRestoreStatus,
|
|
} from './apiHooks';
|
|
import { LibraryRestoreStatus } from './restoreConstants';
|
|
|
|
mockContentLibrary.applyMock();
|
|
const { axiosMock, queryClient } = initializeMocks();
|
|
|
|
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
<QueryClientProvider client={queryClient}>
|
|
{children}
|
|
</QueryClientProvider>
|
|
);
|
|
|
|
describe('create library apiHooks', () => {
|
|
beforeEach(() => {
|
|
queryClient.clear();
|
|
axiosMock.reset();
|
|
});
|
|
|
|
describe('useCreateLibraryV2', () => {
|
|
it('should create library and invalidate queries', async () => {
|
|
const libraryData = {
|
|
title: 'Test Library',
|
|
org: 'test-org',
|
|
slug: 'test-library',
|
|
learning_package: 1,
|
|
};
|
|
const expectedResult = {
|
|
id: 'lib:test-org:test-library',
|
|
title: 'Test Library',
|
|
org: 'test-org',
|
|
slug: 'test-library',
|
|
};
|
|
|
|
// Mock the API call
|
|
axiosMock.onPost('http://localhost:18010/api/libraries/v2/').reply(200, expectedResult);
|
|
|
|
// Spy on query invalidation
|
|
const invalidateQueriesSpy = jest.spyOn(queryClient, 'invalidateQueries');
|
|
|
|
const { result } = renderHook(() => useCreateLibraryV2(), { wrapper });
|
|
|
|
await result.current.mutateAsync(libraryData);
|
|
|
|
expect(axiosMock.history.post[0].url).toEqual('http://localhost:18010/api/libraries/v2/');
|
|
expect(JSON.parse(axiosMock.history.post[0].data)).toEqual({
|
|
description: '',
|
|
...libraryData,
|
|
});
|
|
|
|
// Check that queries are invalidated on success
|
|
expect(invalidateQueriesSpy).toHaveBeenCalledWith({
|
|
queryKey: libraryAuthoringQueryKeys.contentLibraryList(),
|
|
});
|
|
expect(invalidateQueriesSpy).toHaveBeenCalledWith({
|
|
queryKey: ['content_search'],
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('useCreateLibraryRestore', () => {
|
|
it('should restore library from file', async () => {
|
|
const file = new File(['test content'], 'test.tar.gz', { type: 'application/gzip' });
|
|
const expectedResult = { taskId: 'test-task-id' };
|
|
|
|
axiosMock.onPost('http://localhost:18010/api/libraries/v2/restore/').reply(200, expectedResult);
|
|
|
|
const { result } = renderHook(() => useCreateLibraryRestore(), { wrapper });
|
|
|
|
const response = await result.current.mutateAsync(file);
|
|
|
|
expect(axiosMock.history.post[0].url).toEqual('http://localhost:18010/api/libraries/v2/restore/');
|
|
expect(axiosMock.history.post[0].data).toBeInstanceOf(FormData);
|
|
expect(response).toEqual(expectedResult);
|
|
});
|
|
|
|
it('should handle restore error', async () => {
|
|
const file = new File(['test content'], 'test.tar.gz', { type: 'application/gzip' });
|
|
|
|
axiosMock.onPost('http://localhost:18010/api/libraries/v2/restore/').reply(400, 'Bad Request');
|
|
|
|
const { result } = renderHook(() => useCreateLibraryRestore(), { wrapper });
|
|
|
|
await expect(result.current.mutateAsync(file)).rejects.toThrow();
|
|
});
|
|
});
|
|
|
|
describe('useGetLibraryRestoreStatus', () => {
|
|
it('should get restore status when taskId is provided', async () => {
|
|
const taskId = 'test-task-id';
|
|
const expectedResult = {
|
|
state: LibraryRestoreStatus.Succeeded,
|
|
result: {
|
|
learningPackageId: 123,
|
|
title: 'Test Library',
|
|
org: 'test-org',
|
|
slug: 'test-library',
|
|
key: 'lib:test-org:test-library',
|
|
archiveKey: 'archive-key',
|
|
containers: 1,
|
|
components: 5,
|
|
collections: 2,
|
|
sections: 1,
|
|
subsections: 1,
|
|
units: 1,
|
|
createdOnServer: '2024-01-01T00:00:00Z',
|
|
createdAt: '2024-01-01T00:00:00Z',
|
|
createdBy: {
|
|
username: 'testuser',
|
|
email: 'test@example.com',
|
|
},
|
|
},
|
|
error: null,
|
|
errorLog: null,
|
|
};
|
|
|
|
axiosMock.onGet(`http://localhost:18010/api/libraries/v2/restore/?task_id=${taskId}`).reply(200, expectedResult);
|
|
|
|
const { result } = renderHook(() => useGetLibraryRestoreStatus(taskId), { wrapper });
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.isLoading).toBeFalsy();
|
|
});
|
|
|
|
expect(result.current.data).toEqual(expectedResult);
|
|
expect(axiosMock.history.get[0].url).toEqual(`http://localhost:18010/api/libraries/v2/restore/?task_id=${taskId}`);
|
|
});
|
|
|
|
it('should not make request when taskId is empty', async () => {
|
|
const { result } = renderHook(() => useGetLibraryRestoreStatus(''), { wrapper });
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.isLoading).toBeFalsy();
|
|
});
|
|
|
|
expect(result.current.data).toBeUndefined();
|
|
expect(axiosMock.history.get).toHaveLength(0);
|
|
});
|
|
|
|
it('should handle pending status with refetch interval', async () => {
|
|
const taskId = 'pending-task-id';
|
|
const pendingResult = {
|
|
state: LibraryRestoreStatus.Pending,
|
|
result: null,
|
|
error: null,
|
|
error_log: null,
|
|
};
|
|
|
|
const expectedResult = {
|
|
state: LibraryRestoreStatus.Pending,
|
|
result: null,
|
|
error: null,
|
|
errorLog: null,
|
|
};
|
|
|
|
axiosMock.onGet(`http://localhost:18010/api/libraries/v2/restore/?task_id=${taskId}`).reply(200, pendingResult);
|
|
|
|
const { result } = renderHook(() => useGetLibraryRestoreStatus(taskId), { wrapper });
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.isLoading).toBeFalsy();
|
|
});
|
|
|
|
expect(result.current.data).toEqual(expectedResult);
|
|
expect(axiosMock.history.get[0].url).toEqual(`http://localhost:18010/api/libraries/v2/restore/?task_id=${taskId}`);
|
|
});
|
|
|
|
it('should handle in-progress status with refetch interval', async () => {
|
|
const taskId = 'in-progress-task-id';
|
|
const inProgressResult = {
|
|
state: LibraryRestoreStatus.InProgress,
|
|
result: null,
|
|
error: null,
|
|
error_log: null,
|
|
};
|
|
|
|
const expectedResult = {
|
|
state: LibraryRestoreStatus.InProgress,
|
|
result: null,
|
|
error: null,
|
|
errorLog: null,
|
|
};
|
|
|
|
axiosMock.onGet(`http://localhost:18010/api/libraries/v2/restore/?task_id=${taskId}`).reply(200, inProgressResult);
|
|
|
|
const { result } = renderHook(() => useGetLibraryRestoreStatus(taskId), { wrapper });
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.isLoading).toBeFalsy();
|
|
});
|
|
|
|
expect(result.current.data).toEqual(expectedResult);
|
|
expect(axiosMock.history.get[0].url).toEqual(`http://localhost:18010/api/libraries/v2/restore/?task_id=${taskId}`);
|
|
});
|
|
|
|
it('should handle failed status', async () => {
|
|
const taskId = 'failed-task-id';
|
|
const failedResult = {
|
|
state: LibraryRestoreStatus.Failed,
|
|
result: null,
|
|
error: 'Restore failed',
|
|
error_log: 'Error details here',
|
|
};
|
|
|
|
const expectedResult = {
|
|
state: LibraryRestoreStatus.Failed,
|
|
result: null,
|
|
error: 'Restore failed',
|
|
errorLog: 'Error details here',
|
|
};
|
|
|
|
axiosMock.onGet(`http://localhost:18010/api/libraries/v2/restore/?task_id=${taskId}`).reply(200, failedResult);
|
|
|
|
const { result } = renderHook(() => useGetLibraryRestoreStatus(taskId), { wrapper });
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.isLoading).toBeFalsy();
|
|
});
|
|
|
|
expect(result.current.data).toEqual(expectedResult);
|
|
expect(axiosMock.history.get[0].url).toEqual(`http://localhost:18010/api/libraries/v2/restore/?task_id=${taskId}`);
|
|
});
|
|
|
|
it('should handle API error', async () => {
|
|
const taskId = 'error-task-id';
|
|
|
|
axiosMock.onGet(`http://localhost:18010/api/libraries/v2/restore/?task_id=${taskId}`).reply(404, 'Not Found');
|
|
|
|
const { result } = renderHook(() => useGetLibraryRestoreStatus(taskId), { wrapper });
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.isError).toBeTruthy();
|
|
});
|
|
|
|
expect(result.current.data).toBeUndefined();
|
|
expect(axiosMock.history.get[0].url).toEqual(`http://localhost:18010/api/libraries/v2/restore/?task_id=${taskId}`);
|
|
});
|
|
});
|
|
});
|