Updated tests after sync

This commit is contained in:
Nick Sandstrom 2026-01-27 16:09:01 -08:00
parent 216e582a91
commit 04e2886ba5
4 changed files with 487 additions and 9 deletions

View file

@ -40,9 +40,6 @@ const localStorageMock = (() => {
global.localStorage = localStorageMock;
// Mock console methods
global.console.error = vi.fn();
// Helper to create a mock JWT token
const createMockToken = (expiresInSeconds = 3600) => {
const now = Math.floor(Date.now() / 1000);
@ -161,7 +158,9 @@ describe('useAuthStore', () => {
await result.current.login({ username: 'testuser', password: 'wrong' });
});
expect(console.error).toHaveBeenCalledWith('Login failed:', expect.any(Error));
expect(API.login).toHaveBeenCalledWith('testuser', 'wrong');
expect(result.current.isAuthenticated).toBe(false);
expect(localStorageMock.setItem).not.toHaveBeenCalled();
});
});
@ -183,7 +182,6 @@ describe('useAuthStore', () => {
expect(API.refreshToken).toHaveBeenCalledWith('old-refresh-token');
expect(newToken).toBe(mockNewAccessToken);
expect(result.current.isAuthenticated).toBe(true);
expect(localStorageMock.setItem).toHaveBeenCalledWith('accessToken', mockNewAccessToken);
});
@ -212,7 +210,6 @@ describe('useAuthStore', () => {
await result.current.getRefreshToken();
});
expect(console.error).toHaveBeenCalledWith('Token refresh failed:', expect.any(Error));
expect(result.current.isAuthenticated).toBe(false);
expect(localStorageMock.removeItem).toHaveBeenCalledWith('accessToken');
expect(localStorageMock.removeItem).toHaveBeenCalledWith('refreshToken');
@ -292,7 +289,6 @@ describe('useAuthStore', () => {
await result.current.logout();
});
expect(console.error).toHaveBeenCalledWith('Logout API call failed:', expect.any(Error));
expect(result.current.isAuthenticated).toBe(false);
expect(localStorageMock.removeItem).toHaveBeenCalled();
});
@ -435,8 +431,6 @@ describe('useAuthStore', () => {
await act(async () => {
await result.current.initData();
});
expect(console.error).toHaveBeenCalledWith('Error initializing data:', expect.any(Error));
});
});
@ -476,4 +470,162 @@ describe('useAuthStore', () => {
expect(result.current.superuserExists).toBe(false);
});
});
describe('getRefreshToken edge cases', () => {
it('should return false if API response has no access token', async () => {
localStorageMock.getItem.mockReturnValue('refresh-token');
API.refreshToken.mockResolvedValue({});
const { result } = renderHook(() => useAuthStore());
let response;
await act(async () => {
response = await result.current.getRefreshToken();
});
expect(response).toBe(false);
});
});
describe('login edge cases', () => {
it('should not update state if response has no access token', async () => {
API.login.mockResolvedValue({});
const { result } = renderHook(() => useAuthStore());
await act(async () => {
await result.current.login({ username: 'testuser', password: 'password' });
});
expect(result.current.accessToken).toBeNull();
expect(localStorageMock.setItem).not.toHaveBeenCalled();
});
});
describe('logout edge cases', () => {
it('should reset isInitializing flag on logout', async () => {
useAuthStore.setState({ isInitializing: true });
API.logout.mockResolvedValue();
const { result } = renderHook(() => useAuthStore());
await act(async () => {
await result.current.logout();
});
expect(result.current.isInitializing).toBe(false);
});
});
describe('initializeAuth edge cases', () => {
it('should return false if refresh token API call fails', async () => {
localStorageMock.getItem.mockReturnValue('refresh-token');
API.refreshToken.mockRejectedValue(new Error('Token expired'));
API.logout.mockResolvedValue();
const { result } = renderHook(() => useAuthStore());
let initialized;
await act(async () => {
initialized = await result.current.initializeAuth();
});
expect(initialized).toBe(false);
});
it('should return false if refresh returns no access token', async () => {
localStorageMock.getItem.mockReturnValue('refresh-token');
API.refreshToken.mockResolvedValue({});
const { result } = renderHook(() => useAuthStore());
let initialized;
await act(async () => {
initialized = await result.current.initializeAuth();
});
expect(initialized).toBe(false);
});
});
describe('initData edge cases', () => {
it('should skip initialization if already initialized', async () => {
useAuthStore.setState({ isInitialized: true });
const { result } = renderHook(() => useAuthStore());
await act(async () => {
await result.current.initData();
});
expect(API.me).not.toHaveBeenCalled();
});
it('should skip initialization if already initializing', async () => {
useAuthStore.setState({ isInitializing: true });
const { result } = renderHook(() => useAuthStore());
await act(async () => {
await result.current.initData();
});
expect(API.me).not.toHaveBeenCalled();
});
it('should set isInitializing to false on error', async () => {
// Reset state before the test
useAuthStore.setState({
isInitializing: false,
isInitialized: false
});
API.me.mockRejectedValue(new Error('API error'));
const { result } = renderHook(() => useAuthStore());
await act(async () => {
try {
await result.current.initData();
} catch {
// Expected error
}
});
expect(result.current.isInitializing).toBe(false);
expect(result.current.isInitialized).toBe(false);
});
it('should call fetchChannels in background after initialization', async () => {
const mockUser = {
username: 'admin',
email: 'admin@test.com',
user_level: USER_LEVELS.ADMIN,
};
const fetchChannels = vi.fn().mockResolvedValue();
useChannelsStore.getState = () => ({
fetchChannels,
fetchChannelGroups: vi.fn().mockResolvedValue(),
fetchChannelProfiles: vi.fn().mockResolvedValue(),
});
API.me.mockResolvedValue(mockUser);
const { result } = renderHook(() => useAuthStore());
await act(async () => {
await result.current.initData();
});
// Wait for the background call to complete
await act(async () => {
await new Promise(resolve => setTimeout(resolve, 10));
});
// The background fetchChannels is called synchronously without await
// so we just need to verify it was called
expect(fetchChannels).toHaveBeenCalled();
});
});
});

View file

@ -292,4 +292,111 @@ describe('useChannelsTableStore', () => {
expect(result.current.sorting).toEqual([]);
});
});
describe('isUnlocked', () => {
it('should initialize with default false value', () => {
const { result } = renderHook(() => useChannelsTableStore());
expect(result.current.isUnlocked).toBe(false);
});
});
describe('setIsUnlocked', () => {
it('should update isUnlocked to true', () => {
const { result } = renderHook(() => useChannelsTableStore());
act(() => {
result.current.setIsUnlocked(true);
});
expect(result.current.isUnlocked).toBe(true);
});
it('should update isUnlocked to false', () => {
const { result } = renderHook(() => useChannelsTableStore());
act(() => {
result.current.setIsUnlocked(true);
});
expect(result.current.isUnlocked).toBe(true);
act(() => {
result.current.setIsUnlocked(false);
});
expect(result.current.isUnlocked).toBe(false);
});
});
describe('updateChannel', () => {
it('should update an existing channel', () => {
const { result } = renderHook(() => useChannelsTableStore());
const mockChannels = [
{ id: 1, name: 'Channel 1', channel_number: 1 },
{ id: 2, name: 'Channel 2', channel_number: 2 },
{ id: 3, name: 'Channel 3', channel_number: 3 },
];
act(() => {
useChannelsTableStore.setState({ channels: mockChannels });
});
const updatedChannel = { id: 2, name: 'Updated Channel 2', channel_number: 22 };
act(() => {
result.current.updateChannel(updatedChannel);
});
expect(result.current.channels).toEqual([
{ id: 1, name: 'Channel 1', channel_number: 1 },
{ id: 2, name: 'Updated Channel 2', channel_number: 22 },
{ id: 3, name: 'Channel 3', channel_number: 3 },
]);
});
it('should not modify channels when updating non-existent channel', () => {
const { result } = renderHook(() => useChannelsTableStore());
const mockChannels = [
{ id: 1, name: 'Channel 1', channel_number: 1 },
{ id: 2, name: 'Channel 2', channel_number: 2 },
];
act(() => {
useChannelsTableStore.setState({ channels: mockChannels });
});
const updatedChannel = { id: 999, name: 'Non-existent', channel_number: 999 };
act(() => {
result.current.updateChannel(updatedChannel);
});
expect(result.current.channels).toEqual(mockChannels);
});
it('should preserve other channels when updating one channel', () => {
const { result } = renderHook(() => useChannelsTableStore());
const mockChannels = [
{ id: 1, name: 'Channel 1', channel_number: 1, streams: ['stream1'] },
{ id: 2, name: 'Channel 2', channel_number: 2, streams: ['stream2'] },
];
act(() => {
useChannelsTableStore.setState({ channels: mockChannels });
});
const updatedChannel = { id: 1, name: 'Updated Channel 1', channel_number: 10 };
act(() => {
result.current.updateChannel(updatedChannel);
});
expect(result.current.channels[0]).toEqual(updatedChannel);
expect(result.current.channels[1]).toEqual(mockChannels[1]);
});
});
});

View file

@ -16,6 +16,10 @@ describe('useSettingsStore', () => {
country_name: '',
env_mode: 'prod',
},
version: {
version: '',
timestamp: null,
},
isLoading: false,
error: null,
});
@ -201,4 +205,149 @@ describe('useSettingsStore', () => {
expect(result.current.settings).toEqual({});
});
it('should initialize version with default state', () => {
const { result } = renderHook(() => useSettingsStore());
expect(result.current.version).toEqual({
version: '',
timestamp: null,
});
});
it('should fetch version successfully', async () => {
const mockVersion = {
version: '1.2.3',
timestamp: '2024-01-01T00:00:00Z',
};
api.getVersion.mockResolvedValue(mockVersion);
const { result } = renderHook(() => useSettingsStore());
let versionResult;
await act(async () => {
versionResult = await result.current.fetchVersion();
});
expect(api.getVersion).toHaveBeenCalled();
expect(result.current.version).toEqual({
version: '1.2.3',
timestamp: '2024-01-01T00:00:00Z',
});
expect(versionResult).toEqual({
version: '1.2.3',
timestamp: '2024-01-01T00:00:00Z',
});
});
it('should skip fetching version if already loaded', async () => {
useSettingsStore.setState({
version: {
version: '1.0.0',
timestamp: '2023-01-01T00:00:00Z',
},
});
api.getVersion.mockResolvedValue({ version: '2.0.0', timestamp: '2024-01-01T00:00:00Z' });
const { result } = renderHook(() => useSettingsStore());
let versionResult;
await act(async () => {
versionResult = await result.current.fetchVersion();
});
expect(api.getVersion).not.toHaveBeenCalled();
expect(result.current.version).toEqual({
version: '1.0.0',
timestamp: '2023-01-01T00:00:00Z',
});
expect(versionResult).toEqual({
version: '1.0.0',
timestamp: '2023-01-01T00:00:00Z',
});
});
it('should handle null version response', async () => {
api.getVersion.mockResolvedValue(null);
const { result } = renderHook(() => useSettingsStore());
await act(async () => {
await result.current.fetchVersion();
});
expect(result.current.version).toEqual({
version: '',
timestamp: null,
});
});
it('should handle fetch version error', async () => {
const mockError = new Error('Version fetch failed');
api.getVersion.mockRejectedValue(mockError);
const { result } = renderHook(() => useSettingsStore());
let versionResult;
await act(async () => {
versionResult = await result.current.fetchVersion();
});
expect(versionResult).toEqual({
version: '',
timestamp: null,
});
});
it('should fetch version with settings when version not loaded', async () => {
const mockSettings = [{ key: 'setting1', value: 'value1' }];
const mockEnv = { public_ip: '192.168.1.1' };
const mockVersion = { version: '1.0.0', timestamp: '2024-01-01T00:00:00Z' };
api.getSettings.mockResolvedValue(mockSettings);
api.getEnvironmentSettings.mockResolvedValue(mockEnv);
api.getVersion.mockResolvedValue(mockVersion);
const { result } = renderHook(() => useSettingsStore());
await act(async () => {
await result.current.fetchSettings();
});
expect(api.getVersion).toHaveBeenCalled();
expect(result.current.version).toEqual({
version: '1.0.0',
timestamp: '2024-01-01T00:00:00Z',
});
});
it('should skip fetching version with settings when already loaded', async () => {
useSettingsStore.setState({
version: {
version: '1.0.0',
timestamp: '2023-01-01T00:00:00Z',
},
});
const mockSettings = [{ key: 'setting1', value: 'value1' }];
const mockEnv = { public_ip: '192.168.1.1' };
api.getSettings.mockResolvedValue(mockSettings);
api.getEnvironmentSettings.mockResolvedValue(mockEnv);
api.getVersion.mockResolvedValue({ version: '2.0.0', timestamp: '2024-01-01T00:00:00Z' });
const { result } = renderHook(() => useSettingsStore());
await act(async () => {
await result.current.fetchSettings();
});
expect(api.getVersion).not.toHaveBeenCalled();
expect(result.current.version).toEqual({
version: '1.0.0',
timestamp: '2023-01-01T00:00:00Z',
});
});
});

View file

@ -477,4 +477,74 @@ describe('useVODLogosStore', () => {
expect(api.getVODLogos).toHaveBeenCalledWith({ search: 'test', page: 2 });
});
it('should get unused logos count successfully', async () => {
const mockResponse = {
results: [{ id: 1, name: 'Unused Logo' }],
count: 42,
};
api.getVODLogos.mockResolvedValue(mockResponse);
const { result } = renderHook(() => useVODLogosStore());
let unusedCount;
await act(async () => {
unusedCount = await result.current.getUnusedLogosCount();
});
expect(api.getVODLogos).toHaveBeenCalledWith({
used: 'false',
page_size: 1,
});
expect(unusedCount).toBe(42);
});
it('should return 0 when unused logos count response has no count', async () => {
api.getVODLogos.mockResolvedValue({ results: [] });
const { result } = renderHook(() => useVODLogosStore());
let unusedCount;
await act(async () => {
unusedCount = await result.current.getUnusedLogosCount();
});
expect(unusedCount).toBe(0);
});
it('should handle get unused logos count error', async () => {
const mockError = new Error('Failed to fetch count');
api.getVODLogos.mockRejectedValue(mockError);
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
const { result } = renderHook(() => useVODLogosStore());
await act(async () => {
try {
await result.current.getUnusedLogosCount();
} catch (error) {
expect(error).toBe(mockError);
}
});
expect(consoleErrorSpy).toHaveBeenCalledWith('Failed to fetch unused logos count:', mockError);
consoleErrorSpy.mockRestore();
});
it('should update currentPage and pageSize state', () => {
const { result } = renderHook(() => useVODLogosStore());
act(() => {
useVODLogosStore.setState({
currentPage: 3,
pageSize: 50,
});
});
expect(result.current.currentPage).toBe(3);
expect(result.current.pageSize).toBe(50);
});
});