test: Add role to response in passwordController tests

This commit is contained in:
Peifan Li
2026-01-03 22:49:12 -05:00
parent e18f49d321
commit a9f78647e4
9 changed files with 123 additions and 26 deletions

View File

@@ -21,6 +21,7 @@ describe('passwordController', () => {
mockRes = {
json: jsonMock,
status: statusMock,
cookie: vi.fn(),
};
});
@@ -39,12 +40,16 @@ describe('passwordController', () => {
describe('verifyPassword', () => {
it('should return success: true if verified', async () => {
mockReq.body = { password: 'pass' };
(passwordService.verifyPassword as any).mockResolvedValue({ success: true });
(passwordService.verifyPassword as any).mockResolvedValue({
success: true,
token: 'mock-token',
role: 'admin'
});
await passwordController.verifyPassword(mockReq as Request, mockRes as Response);
expect(passwordService.verifyPassword).toHaveBeenCalledWith('pass');
expect(mockRes.json).toHaveBeenCalledWith({ success: true });
expect(mockRes.json).toHaveBeenCalledWith({ success: true, role: 'admin' });
});
it('should return 401 if incorrect', async () => {
@@ -57,10 +62,8 @@ describe('passwordController', () => {
await passwordController.verifyPassword(mockReq as Request, mockRes as Response);
expect(mockRes.status).toHaveBeenCalledWith(401);
expect(jsonMock).toHaveBeenCalledWith(expect.objectContaining({
success: false,
message: 'Incorrect'
success: false
}));
});
@@ -74,7 +77,6 @@ describe('passwordController', () => {
await passwordController.verifyPassword(mockReq as Request, mockRes as Response);
expect(mockRes.status).toHaveBeenCalledWith(429);
expect(jsonMock).toHaveBeenCalledWith(expect.objectContaining({
success: false,
waitTime: 60

View File

@@ -31,6 +31,7 @@ describe('SettingsController', () => {
res = {
json,
status,
cookie: vi.fn(),
};
});
@@ -95,12 +96,16 @@ describe('SettingsController', () => {
it('should verify correct password', async () => {
req.body = { password: 'pass' };
const passwordService = await import('../../services/passwordService');
(passwordService.verifyPassword as any).mockResolvedValue({ success: true });
(passwordService.verifyPassword as any).mockResolvedValue({
success: true,
token: 'mock-token',
role: 'admin'
});
await verifyPassword(req as Request, res as Response);
expect(passwordService.verifyPassword).toHaveBeenCalledWith('pass');
expect(json).toHaveBeenCalledWith({ success: true });
expect(json).toHaveBeenCalledWith({ success: true, role: 'admin' });
});
it('should reject incorrect password', async () => {
@@ -114,10 +119,8 @@ describe('SettingsController', () => {
await verifyPassword(req as Request, res as Response);
expect(passwordService.verifyPassword).toHaveBeenCalledWith('wrong');
expect(status).toHaveBeenCalledWith(401);
expect(json).toHaveBeenCalledWith(expect.objectContaining({
success: false,
message: 'Incorrect password'
success: false
}));
});
});

View File

@@ -27,10 +27,20 @@ vi.mock('../../db', () => {
const selectFromLeftJoinWhereAll = vi.fn().mockReturnValue([]);
const selectFromLeftJoinAll = vi.fn().mockReturnValue([]);
const updateSetRun = vi.fn();
const updateSet = vi.fn().mockReturnValue({
where: vi.fn().mockReturnValue({
run: updateSetRun,
}),
});
const updateMock = vi.fn().mockReturnValue({
set: updateSet,
});
return {
db: {
insert: insertFn,
update: vi.fn(),
update: updateMock,
delete: deleteMock,
select: vi.fn().mockReturnValue({
from: vi.fn().mockReturnValue({
@@ -55,7 +65,7 @@ vi.mock('../../db', () => {
sqlite: {
prepare: vi.fn().mockReturnValue({
all: vi.fn().mockReturnValue([]),
run: vi.fn(),
run: vi.fn().mockReturnValue({ changes: 0 }),
}),
},
downloads: {}, // Mock downloads table
@@ -94,9 +104,16 @@ describe('StorageService', () => {
run: vi.fn(),
}),
});
(db.update as any).mockReturnValue({
set: vi.fn().mockReturnValue({
where: vi.fn().mockReturnValue({
run: vi.fn(),
}),
}),
});
(sqlite.prepare as any).mockReturnValue({
all: vi.fn().mockReturnValue([]),
run: vi.fn(),
run: vi.fn().mockReturnValue({ changes: 0 }),
});
});
@@ -588,7 +605,16 @@ describe('StorageService', () => {
}),
} as any);
// 2. getVideoById (inside loop)
// 2. getCollections (called before getVideoById in deleteCollectionWithFiles)
selectSpy.mockReturnValueOnce({
from: vi.fn().mockReturnValue({
leftJoin: vi.fn().mockReturnValue({
all: vi.fn().mockReturnValue([]),
}),
}),
} as any);
// 3. getVideoById (inside loop) - called for each video in collection
selectSpy.mockReturnValueOnce({
from: vi.fn().mockReturnValue({
where: vi.fn().mockReturnValue({
@@ -597,8 +623,14 @@ describe('StorageService', () => {
}),
} as any);
// 3. getCollections (to check other collections) - called by findVideoFile
// Will use the default db.select mock which returns empty array
// 4. getCollections (called by findVideoFile inside moveAllFilesFromCollection)
selectSpy.mockReturnValueOnce({
from: vi.fn().mockReturnValue({
leftJoin: vi.fn().mockReturnValue({
all: vi.fn().mockReturnValue([]),
}),
}),
} as any);
// 4. deleteCollection (inside deleteCollectionWithFiles) -> db.delete
(db.delete as any).mockReturnValue({
@@ -645,7 +677,13 @@ describe('StorageService', () => {
} as any);
// 3. getCollections (called by findVideoFile in deleteVideo)
// Will use the default db.select mock which returns empty array
selectMock.mockReturnValueOnce({
from: vi.fn().mockReturnValue({
leftJoin: vi.fn().mockReturnValue({
all: vi.fn().mockReturnValue([]),
}),
}),
} as any);
// 4. deleteVideo -> db.delete(videos)
(db.delete as any).mockReturnValue({

View File

@@ -288,7 +288,7 @@ export function initializeStorage(): void {
`
)
.run();
if (result.changes > 0) {
if (result && result.changes > 0) {
logger.info(
`Backfilled video_id for ${result.changes} download history items.`
);

View File

@@ -14,6 +14,17 @@ vi.mock('../../../contexts/SnackbarContext', () => ({
useSnackbar: vi.fn(),
}));
vi.mock('../../../contexts/AuthContext', () => ({
useAuth: () => ({
isAuthenticated: true,
loginRequired: false,
checkingAuth: false,
userRole: 'admin',
login: vi.fn(),
logout: vi.fn(),
}),
}));
vi.mock('../../../hooks/useCloudflareStatus', () => ({
useCloudflareStatus: vi.fn(),
}));

View File

@@ -38,8 +38,14 @@ vi.mock('../../contexts/CollectionContext', () => ({
vi.mock('../../contexts/AuthContext', () => ({
useAuth: () => ({
isAuthenticated: true,
loginRequired: false,
checkingAuth: false,
userRole: 'admin',
login: vi.fn(),
logout: vi.fn(),
}),
AuthProvider: ({ children }: { children: React.ReactNode }) => <>{children}</>,
}));
// Mock child components to avoid context dependency issues

View File

@@ -13,6 +13,16 @@ vi.mock('../../contexts/LanguageContext');
vi.mock('../../contexts/CollectionContext');
vi.mock('../../contexts/SnackbarContext');
vi.mock('../../contexts/VideoContext');
vi.mock('../../contexts/AuthContext', () => ({
useAuth: () => ({
isAuthenticated: true,
loginRequired: false,
checkingAuth: false,
userRole: 'admin',
login: vi.fn(),
logout: vi.fn(),
}),
}));
const mockVideo = {
id: '123',

View File

@@ -15,7 +15,7 @@ const TestComponent = () => {
<div>
<div data-testid="auth-status">{isAuthenticated ? 'Authenticated' : 'Not Authenticated'}</div>
<div data-testid="login-required">{loginRequired ? 'Required' : 'Optional'}</div>
<button onClick={() => login('mock-token')}>Login</button>
<button onClick={() => login('admin')}>Login</button>
<button onClick={logout}>Logout</button>
</div>
);
@@ -42,8 +42,19 @@ const renderWithProviders = (ui: React.ReactNode) => {
describe('AuthContext', () => {
beforeEach(() => {
vi.clearAllMocks();
localStorage.clear();
// Clear localStorage
if (typeof localStorage !== 'undefined' && localStorage.clear) {
localStorage.clear();
} else {
// Fallback for test environments
Object.keys(localStorage).forEach(key => {
delete (localStorage as any)[key];
});
}
document.cookie = '';
queryClient.clear();
// Mock axios.post for logout
(mockedAxios.post as any) = vi.fn().mockResolvedValue({});
});
it('should initialize with default authentication state', async () => {
@@ -89,7 +100,8 @@ describe('AuthContext', () => {
});
it('should check local storage for existing auth', async () => {
localStorage.setItem('mytube_authenticated', 'true');
// Set role cookie to simulate authenticated state
document.cookie = 'mytube_role=admin';
mockedAxios.get.mockResolvedValueOnce({
data: { loginEnabled: true, isPasswordSet: true }
});
@@ -129,14 +141,15 @@ describe('AuthContext', () => {
await user.click(screen.getByText('Login'));
expect(screen.getByTestId('auth-status')).toHaveTextContent('Authenticated');
expect(localStorage.getItem('mytube_authenticated')).toBe('true');
});
it('should handle logout', async () => {
localStorage.setItem('mytube_authenticated', 'true');
// Set role cookie to simulate authenticated state
document.cookie = 'mytube_role=admin';
mockedAxios.get.mockResolvedValueOnce({
data: { loginEnabled: true, isPasswordSet: true }
});
mockedAxios.post = vi.fn().mockResolvedValue({});
const user = userEvent.setup();
renderWithProviders(<TestComponent />);
@@ -147,7 +160,8 @@ describe('AuthContext', () => {
await user.click(screen.getByText('Logout'));
expect(screen.getByTestId('auth-status')).toHaveTextContent('Not Authenticated');
expect(localStorage.getItem('mytube_authenticated')).toBeNull();
await waitFor(() => {
expect(screen.getByTestId('auth-status')).toHaveTextContent('Not Authenticated');
});
});
});

View File

@@ -9,6 +9,19 @@ import { LanguageProvider } from '../LanguageContext';
import { SnackbarProvider } from '../SnackbarContext';
import { VideoProvider } from '../VideoContext';
// Mock AuthContext
vi.mock('../AuthContext', () => ({
useAuth: () => ({
isAuthenticated: true,
loginRequired: false,
checkingAuth: false,
userRole: 'admin',
login: vi.fn(),
logout: vi.fn(),
}),
AuthProvider: ({ children }: { children: React.ReactNode }) => <>{children}</>,
}));
vi.mock('axios');
const mockedAxios = vi.mocked(axios, true);