mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-07-12 22:42:13 +02:00
I've added comprehensive unit and integration tests for the recently implemented Slack connector features. This includes tests for backend logic (SlackHistory, indexing tasks, API routes) and frontend components and hooks.
Backend Tests (`surfsense_backend`):
- `test_slack_history.py`:
- Tests for `SlackHistory` class methods including `get_all_channels`,
`get_conversation_history`, timestamp conversions, and message
formatting. Covers various API response scenarios, pagination,
and error handling.
- `test_connectors_indexing_tasks.py`:
- Unit tests for `index_slack_messages` task.
- Scenarios include initial indexing, periodic indexing, on-demand
re-indexing (targeted channels, force re-index, custom dates),
channel filtering, and error handling.
- Mocks `SlackHistory` and database interactions.
- `test_search_source_connectors_routes.py`:
- Tests for `/slack/{connector_id}/discover-channels` endpoint.
- Tests for `/slack/{connector_id}/reindex-channels` endpoint,
verifying parameter passing to the background task.
- Tests for the main `/search-source-connectors/{connector_id}/index`
endpoint for Slack connectors, ensuring the `force_full_reindex`
flag is handled.
Frontend Tests (`surfsense_web`):
- `components/editConnector/EditSlackConnectorConfigForm.test.tsx`:
- Tests rendering of all Slack configuration fields.
- Verifies `onConfigChange` callback functionality.
- Tests conditional rendering of periodic indexing settings.
- Checks disabled state of the form.
- `app/dashboard/[search_space_id]/connectors/[connector_id]/edit/page.test.tsx`:
- Tests for the "Channel Management" tab specific to Slack connectors.
- Verifies UI for discovering channels (mocking API calls).
- Tests selection of channels and updating the connector configuration.
- Tests UI for triggering on-demand re-indexing with various options.
- `hooks/useSearchSourceConnectors.test.ts`:
- Unit tests for the `discoverSlackChannels` function, mocking `fetch`.
- Unit tests for the `reindexSlackChannels` function, mocking `fetch`
and verifying correct request body construction.
- Includes testing of the `fetchWithAuth` helper function.
This commit is contained in:
parent
d1f11d4fbb
commit
4bf09b8bde
6 changed files with 1804 additions and 387 deletions
247
surfsense_web/hooks/useSearchSourceConnectors.test.ts
Normal file
247
surfsense_web/hooks/useSearchSourceConnectors.test.ts
Normal file
|
|
@ -0,0 +1,247 @@
|
|||
import { renderHook, act, waitFor } from '@testing-library/react';
|
||||
import { useSearchSourceConnectors, SlackChannelInfo } from './useSearchSourceConnectors'; // Adjust path as needed
|
||||
|
||||
// Helper to create a mock response for fetch
|
||||
const mockFetchResponse = (data: any, ok: boolean = true, status: number = 200) => {
|
||||
return Promise.resolve({
|
||||
ok,
|
||||
status,
|
||||
json: () => Promise.resolve(data),
|
||||
text: () => Promise.resolve(JSON.stringify(data)), // For error messages
|
||||
} as Response);
|
||||
};
|
||||
|
||||
const mockFetchResponseNoContent = (ok: boolean = true, status: number = 202) => {
|
||||
return Promise.resolve({
|
||||
ok,
|
||||
status,
|
||||
json: () => Promise.reject(new Error("No JSON content")), // Should not be called for 202/204
|
||||
text: () => Promise.resolve(""),
|
||||
} as Response);
|
||||
};
|
||||
|
||||
describe('useSearchSourceConnectors Hook - Slack Operations', () => {
|
||||
let mockFetch: jest.SpyInstance;
|
||||
let mockLocalStorageGetItem: jest.SpyInstance;
|
||||
|
||||
const mockApiUrl = 'http://localhost:8000/api/v1'; // Example, ensure it matches env
|
||||
process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL = mockApiUrl;
|
||||
|
||||
|
||||
beforeEach(() => {
|
||||
// Spy on global fetch
|
||||
mockFetch = jest.spyOn(window, 'fetch');
|
||||
// Mock localStorage
|
||||
mockLocalStorageGetItem = jest.spyOn(Storage.prototype, 'getItem');
|
||||
mockLocalStorageGetItem.mockReturnValue('test-token'); // Default to having a token
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mockFetch.mockRestore();
|
||||
mockLocalStorageGetItem.mockRestore();
|
||||
});
|
||||
|
||||
describe('discoverSlackChannels', () => {
|
||||
test('successful API call returns channels and calls fetch correctly', async () => {
|
||||
const mockChannelsData: { channels: SlackChannelInfo[] } = {
|
||||
channels: [
|
||||
{ id: 'C1', name: 'General', is_private: false, is_member: true },
|
||||
{ id: 'C2', name: 'Random', is_private: true, is_member: true },
|
||||
],
|
||||
};
|
||||
mockFetch.mockReturnValueOnce(mockFetchResponse(mockChannelsData));
|
||||
|
||||
const { result } = renderHook(() => useSearchSourceConnectors());
|
||||
// Wait for initial connectors fetch to complete if any (though not directly tested here)
|
||||
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
||||
|
||||
|
||||
let discoveredChannels: SlackChannelInfo[] = [];
|
||||
await act(async () => {
|
||||
discoveredChannels = await result.current.discoverSlackChannels(1);
|
||||
});
|
||||
|
||||
expect(discoveredChannels).toEqual(mockChannelsData.channels);
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
`${mockApiUrl}/slack/1/discover-channels`,
|
||||
expect.objectContaining({
|
||||
method: 'GET',
|
||||
headers: expect.objectContaining({
|
||||
'Authorization': 'Bearer test-token',
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test('API error throws an error', async () => {
|
||||
mockFetch.mockReturnValueOnce(mockFetchResponse({ detail: 'API Error' }, false, 500));
|
||||
|
||||
const { result } = renderHook(() => useSearchSourceConnectors());
|
||||
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
||||
|
||||
await expect(result.current.discoverSlackChannels(1)).rejects.toThrow(
|
||||
/API request failed: Internal Server Error - {"detail":"API Error"}/
|
||||
);
|
||||
});
|
||||
|
||||
test('no token throws an error', async () => {
|
||||
mockLocalStorageGetItem.mockReturnValueOnce(null); // No token
|
||||
|
||||
const { result } = renderHook(() => useSearchSourceConnectors());
|
||||
// Initial fetch will fail here, which is fine for this test's focus
|
||||
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
||||
|
||||
|
||||
await expect(result.current.discoverSlackChannels(1)).rejects.toThrow(
|
||||
'No authentication token found'
|
||||
);
|
||||
expect(mockFetch).not.toHaveBeenCalled(); // fetchWithAuth should prevent call
|
||||
});
|
||||
});
|
||||
|
||||
describe('reindexSlackChannels', () => {
|
||||
test('successful API call (basic) calls fetch correctly', async () => {
|
||||
mockFetch.mockReturnValueOnce(mockFetchResponseNoContent(true, 202)); // 202 Accepted
|
||||
|
||||
const { result } = renderHook(() => useSearchSourceConnectors());
|
||||
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
||||
|
||||
await act(async () => {
|
||||
await result.current.reindexSlackChannels(1, ['C1', 'C2']);
|
||||
});
|
||||
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
`${mockApiUrl}/slack/1/reindex-channels`,
|
||||
expect.objectContaining({
|
||||
method: 'POST',
|
||||
headers: expect.objectContaining({
|
||||
'Authorization': 'Bearer test-token',
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: JSON.stringify({
|
||||
channel_ids: ['C1', 'C2'],
|
||||
force_reindex_all_messages: undefined, // Explicitly undefined if not passed
|
||||
reindex_start_date: null, // Defaulted to null if not passed
|
||||
reindex_latest_date: null, // Defaulted to null if not passed
|
||||
}),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test('successful API call with all optional parameters', async () => {
|
||||
mockFetch.mockReturnValueOnce(mockFetchResponseNoContent(true, 202));
|
||||
|
||||
const { result } = renderHook(() => useSearchSourceConnectors());
|
||||
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
||||
|
||||
await act(async () => {
|
||||
await result.current.reindexSlackChannels(
|
||||
1,
|
||||
['C1'],
|
||||
true,
|
||||
'2023-01-01',
|
||||
'2023-01-31'
|
||||
);
|
||||
});
|
||||
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
`${mockApiUrl}/slack/1/reindex-channels`,
|
||||
expect.objectContaining({
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
channel_ids: ['C1'],
|
||||
force_reindex_all_messages: true,
|
||||
reindex_start_date: '2023-01-01',
|
||||
reindex_latest_date: '2023-01-31',
|
||||
}),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test('handles empty date strings by passing null', async () => {
|
||||
mockFetch.mockReturnValueOnce(mockFetchResponseNoContent(true, 202));
|
||||
|
||||
const { result } = renderHook(() => useSearchSourceConnectors());
|
||||
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
||||
|
||||
await act(async () => {
|
||||
await result.current.reindexSlackChannels(
|
||||
1,
|
||||
['C1'],
|
||||
true,
|
||||
'', // Empty start date
|
||||
'' // Empty latest date
|
||||
);
|
||||
});
|
||||
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
`${mockApiUrl}/slack/1/reindex-channels`,
|
||||
expect.objectContaining({
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
channel_ids: ['C1'],
|
||||
force_reindex_all_messages: true,
|
||||
reindex_start_date: null, // Should be null
|
||||
reindex_latest_date: null, // Should be null
|
||||
}),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test('API error throws an error during reindex', async () => {
|
||||
mockFetch.mockReturnValueOnce(mockFetchResponse({ detail: 'Reindex Failed' }, false, 400));
|
||||
|
||||
const { result } = renderHook(() => useSearchSourceConnectors());
|
||||
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
||||
|
||||
await expect(result.current.reindexSlackChannels(1, ['C1'])).rejects.toThrow(
|
||||
/API request failed: Bad Request - {"detail":"Reindex Failed"}/
|
||||
);
|
||||
});
|
||||
|
||||
test('no token throws an error during reindex', async () => {
|
||||
mockLocalStorageGetItem.mockReturnValueOnce(null); // No token
|
||||
|
||||
const { result } = renderHook(() => useSearchSourceConnectors());
|
||||
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
||||
|
||||
await expect(result.current.reindexSlackChannels(1, ['C1'])).rejects.toThrow(
|
||||
'No authentication token found'
|
||||
);
|
||||
expect(mockFetch).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
// Basic test for fetchWithAuth behavior (implicitly tested by others, but can be explicit)
|
||||
describe('fetchWithAuth internal behavior', () => {
|
||||
test('fetchWithAuth throws if no token', async () => {
|
||||
mockLocalStorageGetItem.mockReturnValueOnce(null);
|
||||
const { result } = renderHook(() => useSearchSourceConnectors());
|
||||
// The hook itself might make initial calls, let them pass/fail
|
||||
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
||||
|
||||
// Attempting any operation that uses fetchWithAuth internally should fail early
|
||||
// For example, discoverSlackChannels
|
||||
await expect(result.current.discoverSlackChannels(1)).rejects.toThrow('No authentication token found');
|
||||
expect(mockFetch).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('fetchWithAuth includes Authorization header', async () => {
|
||||
mockFetch.mockReturnValueOnce(mockFetchResponse({ channels: [] })); // For discoverSlackChannels
|
||||
const { result } = renderHook(() => useSearchSourceConnectors());
|
||||
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
||||
|
||||
await act(async () => {
|
||||
await result.current.discoverSlackChannels(1);
|
||||
});
|
||||
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
expect.any(String),
|
||||
expect.objectContaining({
|
||||
headers: expect.objectContaining({ Authorization: 'Bearer test-token' }),
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue