mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-07-12 22:42:13 +02:00
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.
247 lines
9 KiB
TypeScript
247 lines
9 KiB
TypeScript
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' }),
|
|
})
|
|
);
|
|
});
|
|
});
|
|
});
|