400 lines
14 KiB
TypeScript
400 lines
14 KiB
TypeScript
import { describe, it, expect, beforeEach, vi, Mock } from 'vitest';
|
|
import { CheckAuthenticationUseCase } from '../../../../packages/application/use-cases/CheckAuthenticationUseCase';
|
|
import { AuthenticationState } from '../../../../packages/domain/value-objects/AuthenticationState';
|
|
import { BrowserAuthenticationState } from '../../../../packages/domain/value-objects/BrowserAuthenticationState';
|
|
import { Result } from '../../../../packages/shared/result/Result';
|
|
import type { IAuthenticationService } from '../../../../packages/application/ports/IAuthenticationService';
|
|
|
|
interface ISessionValidator {
|
|
validateSession(): Promise<Result<boolean>>;
|
|
}
|
|
|
|
describe('CheckAuthenticationUseCase', () => {
|
|
let mockAuthService: {
|
|
checkSession: Mock;
|
|
initiateLogin: Mock;
|
|
clearSession: Mock;
|
|
getState: Mock;
|
|
validateServerSide: Mock;
|
|
refreshSession: Mock;
|
|
getSessionExpiry: Mock;
|
|
verifyPageAuthentication: Mock;
|
|
};
|
|
let mockSessionValidator: {
|
|
validateSession: Mock;
|
|
};
|
|
|
|
beforeEach(() => {
|
|
mockAuthService = {
|
|
checkSession: vi.fn(),
|
|
initiateLogin: vi.fn(),
|
|
clearSession: vi.fn(),
|
|
getState: vi.fn(),
|
|
validateServerSide: vi.fn(),
|
|
refreshSession: vi.fn(),
|
|
getSessionExpiry: vi.fn(),
|
|
verifyPageAuthentication: vi.fn(),
|
|
};
|
|
|
|
mockSessionValidator = {
|
|
validateSession: vi.fn(),
|
|
};
|
|
});
|
|
|
|
describe('File-based validation only', () => {
|
|
it('should return AUTHENTICATED when cookies are valid', async () => {
|
|
const useCase = new CheckAuthenticationUseCase(
|
|
mockAuthService as unknown as IAuthenticationService
|
|
);
|
|
|
|
mockAuthService.checkSession.mockResolvedValue(
|
|
Result.ok(AuthenticationState.AUTHENTICATED)
|
|
);
|
|
mockAuthService.getSessionExpiry.mockResolvedValue(
|
|
Result.ok(new Date(Date.now() + 3600000))
|
|
);
|
|
|
|
const result = await useCase.execute();
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
expect(result.unwrap()).toBe(AuthenticationState.AUTHENTICATED);
|
|
expect(mockAuthService.checkSession).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('should return EXPIRED when cookies are expired', async () => {
|
|
const useCase = new CheckAuthenticationUseCase(
|
|
mockAuthService as unknown as IAuthenticationService
|
|
);
|
|
|
|
mockAuthService.checkSession.mockResolvedValue(
|
|
Result.ok(AuthenticationState.EXPIRED)
|
|
);
|
|
mockAuthService.getSessionExpiry.mockResolvedValue(
|
|
Result.ok(new Date(Date.now() - 3600000))
|
|
);
|
|
|
|
const result = await useCase.execute();
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
expect(result.unwrap()).toBe(AuthenticationState.EXPIRED);
|
|
});
|
|
|
|
it('should return UNKNOWN when no session exists', async () => {
|
|
const useCase = new CheckAuthenticationUseCase(
|
|
mockAuthService as unknown as IAuthenticationService
|
|
);
|
|
|
|
mockAuthService.checkSession.mockResolvedValue(
|
|
Result.ok(AuthenticationState.UNKNOWN)
|
|
);
|
|
mockAuthService.getSessionExpiry.mockResolvedValue(
|
|
Result.ok(null)
|
|
);
|
|
|
|
const result = await useCase.execute();
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
expect(result.unwrap()).toBe(AuthenticationState.UNKNOWN);
|
|
});
|
|
});
|
|
|
|
describe('Server-side validation enabled', () => {
|
|
it('should confirm AUTHENTICATED when file and server both validate', async () => {
|
|
const useCase = new CheckAuthenticationUseCase(
|
|
mockAuthService as unknown as IAuthenticationService,
|
|
mockSessionValidator as unknown as ISessionValidator
|
|
);
|
|
|
|
mockAuthService.checkSession.mockResolvedValue(
|
|
Result.ok(AuthenticationState.AUTHENTICATED)
|
|
);
|
|
mockAuthService.getSessionExpiry.mockResolvedValue(
|
|
Result.ok(new Date(Date.now() + 3600000))
|
|
);
|
|
mockSessionValidator.validateSession.mockResolvedValue(
|
|
Result.ok(true)
|
|
);
|
|
|
|
const result = await useCase.execute();
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
expect(result.unwrap()).toBe(AuthenticationState.AUTHENTICATED);
|
|
expect(mockSessionValidator.validateSession).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('should return EXPIRED when file says valid but server rejects', async () => {
|
|
const useCase = new CheckAuthenticationUseCase(
|
|
mockAuthService as unknown as IAuthenticationService,
|
|
mockSessionValidator as unknown as ISessionValidator
|
|
);
|
|
|
|
mockAuthService.checkSession.mockResolvedValue(
|
|
Result.ok(AuthenticationState.AUTHENTICATED)
|
|
);
|
|
mockAuthService.getSessionExpiry.mockResolvedValue(
|
|
Result.ok(new Date(Date.now() + 3600000))
|
|
);
|
|
mockSessionValidator.validateSession.mockResolvedValue(
|
|
Result.ok(false)
|
|
);
|
|
|
|
const result = await useCase.execute();
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
expect(result.unwrap()).toBe(AuthenticationState.EXPIRED);
|
|
});
|
|
|
|
it('should work without ISessionValidator injected (optional dependency)', async () => {
|
|
const useCase = new CheckAuthenticationUseCase(
|
|
mockAuthService as unknown as IAuthenticationService
|
|
);
|
|
|
|
mockAuthService.checkSession.mockResolvedValue(
|
|
Result.ok(AuthenticationState.AUTHENTICATED)
|
|
);
|
|
mockAuthService.getSessionExpiry.mockResolvedValue(
|
|
Result.ok(new Date(Date.now() + 3600000))
|
|
);
|
|
|
|
const result = await useCase.execute();
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
expect(result.unwrap()).toBe(AuthenticationState.AUTHENTICATED);
|
|
});
|
|
});
|
|
|
|
describe('Error handling', () => {
|
|
it('should not block file-based result if server validation fails', async () => {
|
|
const useCase = new CheckAuthenticationUseCase(
|
|
mockAuthService as unknown as IAuthenticationService,
|
|
mockSessionValidator as unknown as ISessionValidator
|
|
);
|
|
|
|
mockAuthService.checkSession.mockResolvedValue(
|
|
Result.ok(AuthenticationState.AUTHENTICATED)
|
|
);
|
|
mockAuthService.getSessionExpiry.mockResolvedValue(
|
|
Result.ok(new Date(Date.now() + 3600000))
|
|
);
|
|
mockSessionValidator.validateSession.mockResolvedValue(
|
|
Result.err('Network error')
|
|
);
|
|
|
|
const result = await useCase.execute();
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
expect(result.unwrap()).toBe(AuthenticationState.AUTHENTICATED);
|
|
});
|
|
|
|
it('should handle authentication service errors gracefully', async () => {
|
|
const useCase = new CheckAuthenticationUseCase(
|
|
mockAuthService as unknown as IAuthenticationService
|
|
);
|
|
|
|
mockAuthService.checkSession.mockResolvedValue(
|
|
Result.err('File read error')
|
|
);
|
|
|
|
const result = await useCase.execute();
|
|
|
|
expect(result.isErr()).toBe(true);
|
|
expect(result.unwrapErr()).toContain('File read error');
|
|
});
|
|
|
|
it('should handle session expiry check errors gracefully', async () => {
|
|
const useCase = new CheckAuthenticationUseCase(
|
|
mockAuthService as unknown as IAuthenticationService
|
|
);
|
|
|
|
mockAuthService.checkSession.mockResolvedValue(
|
|
Result.ok(AuthenticationState.AUTHENTICATED)
|
|
);
|
|
mockAuthService.getSessionExpiry.mockResolvedValue(
|
|
Result.err('Invalid session format')
|
|
);
|
|
|
|
const result = await useCase.execute();
|
|
|
|
// Should not block on expiry check errors, return file-based state
|
|
expect(result.isOk()).toBe(true);
|
|
expect(result.unwrap()).toBe(AuthenticationState.AUTHENTICATED);
|
|
});
|
|
});
|
|
|
|
describe('Page content verification', () => {
|
|
it('should call verifyPageAuthentication when verifyPageContent is true', async () => {
|
|
const useCase = new CheckAuthenticationUseCase(
|
|
mockAuthService as unknown as IAuthenticationService
|
|
);
|
|
|
|
mockAuthService.checkSession.mockResolvedValue(
|
|
Result.ok(AuthenticationState.AUTHENTICATED)
|
|
);
|
|
mockAuthService.getSessionExpiry.mockResolvedValue(
|
|
Result.ok(new Date(Date.now() + 3600000))
|
|
);
|
|
(mockAuthService as any).verifyPageAuthentication = vi.fn().mockResolvedValue(
|
|
Result.ok(new BrowserAuthenticationState(true, true))
|
|
);
|
|
|
|
await useCase.execute({ verifyPageContent: true });
|
|
|
|
expect((mockAuthService as any).verifyPageAuthentication).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('should return EXPIRED when cookies valid but page shows login UI', async () => {
|
|
const useCase = new CheckAuthenticationUseCase(
|
|
mockAuthService as unknown as IAuthenticationService
|
|
);
|
|
|
|
mockAuthService.checkSession.mockResolvedValue(
|
|
Result.ok(AuthenticationState.AUTHENTICATED)
|
|
);
|
|
mockAuthService.getSessionExpiry.mockResolvedValue(
|
|
Result.ok(new Date(Date.now() + 3600000))
|
|
);
|
|
(mockAuthService as any).verifyPageAuthentication = vi.fn().mockResolvedValue(
|
|
Result.ok(new BrowserAuthenticationState(true, false))
|
|
);
|
|
|
|
const result = await useCase.execute({ verifyPageContent: true });
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
expect(result.unwrap()).toBe(AuthenticationState.EXPIRED);
|
|
});
|
|
|
|
it('should return AUTHENTICATED when both cookies AND page authenticated', async () => {
|
|
const useCase = new CheckAuthenticationUseCase(
|
|
mockAuthService as unknown as IAuthenticationService
|
|
);
|
|
|
|
mockAuthService.checkSession.mockResolvedValue(
|
|
Result.ok(AuthenticationState.AUTHENTICATED)
|
|
);
|
|
mockAuthService.getSessionExpiry.mockResolvedValue(
|
|
Result.ok(new Date(Date.now() + 3600000))
|
|
);
|
|
(mockAuthService as any).verifyPageAuthentication = vi.fn().mockResolvedValue(
|
|
Result.ok(new BrowserAuthenticationState(true, true))
|
|
);
|
|
|
|
const result = await useCase.execute({ verifyPageContent: true });
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
expect(result.unwrap()).toBe(AuthenticationState.AUTHENTICATED);
|
|
});
|
|
|
|
it('should default verifyPageContent to false (backward compatible)', async () => {
|
|
const useCase = new CheckAuthenticationUseCase(
|
|
mockAuthService as unknown as IAuthenticationService
|
|
);
|
|
|
|
mockAuthService.checkSession.mockResolvedValue(
|
|
Result.ok(AuthenticationState.AUTHENTICATED)
|
|
);
|
|
mockAuthService.getSessionExpiry.mockResolvedValue(
|
|
Result.ok(new Date(Date.now() + 3600000))
|
|
);
|
|
(mockAuthService as any).verifyPageAuthentication = vi.fn();
|
|
|
|
await useCase.execute();
|
|
|
|
expect((mockAuthService as any).verifyPageAuthentication).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should handle verifyPageAuthentication errors gracefully', async () => {
|
|
const useCase = new CheckAuthenticationUseCase(
|
|
mockAuthService as unknown as IAuthenticationService
|
|
);
|
|
|
|
mockAuthService.checkSession.mockResolvedValue(
|
|
Result.ok(AuthenticationState.AUTHENTICATED)
|
|
);
|
|
mockAuthService.getSessionExpiry.mockResolvedValue(
|
|
Result.ok(new Date(Date.now() + 3600000))
|
|
);
|
|
(mockAuthService as any).verifyPageAuthentication = vi.fn().mockResolvedValue(
|
|
Result.err('Page navigation failed')
|
|
);
|
|
|
|
const result = await useCase.execute({ verifyPageContent: true });
|
|
|
|
// Should not block on page verification errors, return cookie-based state
|
|
expect(result.isOk()).toBe(true);
|
|
expect(result.unwrap()).toBe(AuthenticationState.AUTHENTICATED);
|
|
});
|
|
});
|
|
|
|
describe('BDD Scenarios', () => {
|
|
it('Given valid session cookies, When checking auth, Then return AUTHENTICATED', async () => {
|
|
const useCase = new CheckAuthenticationUseCase(
|
|
mockAuthService as unknown as IAuthenticationService
|
|
);
|
|
|
|
mockAuthService.checkSession.mockResolvedValue(
|
|
Result.ok(AuthenticationState.AUTHENTICATED)
|
|
);
|
|
mockAuthService.getSessionExpiry.mockResolvedValue(
|
|
Result.ok(new Date(Date.now() + 7200000))
|
|
);
|
|
|
|
const result = await useCase.execute();
|
|
|
|
expect(result.unwrap()).toBe(AuthenticationState.AUTHENTICATED);
|
|
});
|
|
|
|
it('Given expired session cookies, When checking auth, Then return EXPIRED', async () => {
|
|
const useCase = new CheckAuthenticationUseCase(
|
|
mockAuthService as unknown as IAuthenticationService
|
|
);
|
|
|
|
mockAuthService.checkSession.mockResolvedValue(
|
|
Result.ok(AuthenticationState.EXPIRED)
|
|
);
|
|
mockAuthService.getSessionExpiry.mockResolvedValue(
|
|
Result.ok(new Date(Date.now() - 1000))
|
|
);
|
|
|
|
const result = await useCase.execute();
|
|
|
|
expect(result.unwrap()).toBe(AuthenticationState.EXPIRED);
|
|
});
|
|
|
|
it('Given no session file, When checking auth, Then return UNKNOWN', async () => {
|
|
const useCase = new CheckAuthenticationUseCase(
|
|
mockAuthService as unknown as IAuthenticationService
|
|
);
|
|
|
|
mockAuthService.checkSession.mockResolvedValue(
|
|
Result.ok(AuthenticationState.UNKNOWN)
|
|
);
|
|
mockAuthService.getSessionExpiry.mockResolvedValue(
|
|
Result.ok(null)
|
|
);
|
|
|
|
const result = await useCase.execute();
|
|
|
|
expect(result.unwrap()).toBe(AuthenticationState.UNKNOWN);
|
|
});
|
|
|
|
it('Given valid cookies but page shows login, When verifying page content, Then return EXPIRED', async () => {
|
|
const useCase = new CheckAuthenticationUseCase(
|
|
mockAuthService as unknown as IAuthenticationService
|
|
);
|
|
|
|
mockAuthService.checkSession.mockResolvedValue(
|
|
Result.ok(AuthenticationState.AUTHENTICATED)
|
|
);
|
|
mockAuthService.getSessionExpiry.mockResolvedValue(
|
|
Result.ok(new Date(Date.now() + 3600000))
|
|
);
|
|
(mockAuthService as any).verifyPageAuthentication = vi.fn().mockResolvedValue(
|
|
Result.ok(new BrowserAuthenticationState(true, false))
|
|
);
|
|
|
|
const result = await useCase.execute({ verifyPageContent: true });
|
|
|
|
expect(result.unwrap()).toBe(AuthenticationState.EXPIRED);
|
|
});
|
|
});
|
|
}); |