621 lines
19 KiB
TypeScript
621 lines
19 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
import { AuthPageService } from '@/lib/services/auth/AuthPageService';
|
|
import { AuthPageParams } from '@/lib/services/auth/AuthPageParams';
|
|
import { LoginPageDTO } from '@/lib/services/auth/types/LoginPageDTO';
|
|
import { ForgotPasswordPageDTO } from '@/lib/services/auth/types/ForgotPasswordPageDTO';
|
|
import { ResetPasswordPageDTO } from '@/lib/services/auth/types/ResetPasswordPageDTO';
|
|
import { SignupPageDTO } from '@/lib/services/auth/types/SignupPageDTO';
|
|
|
|
describe('AuthPageService', () => {
|
|
let service: AuthPageService;
|
|
|
|
beforeEach(() => {
|
|
service = new AuthPageService();
|
|
});
|
|
|
|
describe('processLoginParams', () => {
|
|
describe('happy paths', () => {
|
|
it('should process login params with returnTo', async () => {
|
|
const params: AuthPageParams = {
|
|
returnTo: '/dashboard',
|
|
};
|
|
|
|
const result = await service.processLoginParams(params);
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
const dto = result.unwrap();
|
|
expect(dto.returnTo).toBe('/dashboard');
|
|
expect(dto.hasInsufficientPermissions).toBe(true);
|
|
});
|
|
|
|
it('should process login params with null returnTo', async () => {
|
|
const params: AuthPageParams = {
|
|
returnTo: null,
|
|
};
|
|
|
|
const result = await service.processLoginParams(params);
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
const dto = result.unwrap();
|
|
expect(dto.returnTo).toBe('/dashboard');
|
|
expect(dto.hasInsufficientPermissions).toBe(false);
|
|
});
|
|
|
|
it('should process login params with undefined returnTo', async () => {
|
|
const params: AuthPageParams = {};
|
|
|
|
const result = await service.processLoginParams(params);
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
const dto = result.unwrap();
|
|
expect(dto.returnTo).toBe('/dashboard');
|
|
expect(dto.hasInsufficientPermissions).toBe(false);
|
|
});
|
|
|
|
it('should process login params with empty string returnTo', async () => {
|
|
const params: AuthPageParams = {
|
|
returnTo: '',
|
|
};
|
|
|
|
const result = await service.processLoginParams(params);
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
const dto = result.unwrap();
|
|
expect(dto.returnTo).toBe('');
|
|
expect(dto.hasInsufficientPermissions).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('decision branches', () => {
|
|
it('should handle different returnTo paths', async () => {
|
|
const paths = [
|
|
'/dashboard',
|
|
'/settings',
|
|
'/profile',
|
|
'/admin',
|
|
'/projects/123',
|
|
'/projects/123/tasks',
|
|
];
|
|
|
|
for (const path of paths) {
|
|
const params: AuthPageParams = {
|
|
returnTo: path,
|
|
};
|
|
|
|
const result = await service.processLoginParams(params);
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
const dto = result.unwrap();
|
|
expect(dto.returnTo).toBe(path);
|
|
expect(dto.hasInsufficientPermissions).toBe(true);
|
|
}
|
|
});
|
|
|
|
it('should handle special characters in returnTo path', async () => {
|
|
const paths = [
|
|
'/dashboard?param=value',
|
|
'/dashboard#section',
|
|
'/dashboard/with/slashes',
|
|
'/dashboard/with-dashes',
|
|
'/dashboard/with_underscores',
|
|
];
|
|
|
|
for (const path of paths) {
|
|
const params: AuthPageParams = {
|
|
returnTo: path,
|
|
};
|
|
|
|
const result = await service.processLoginParams(params);
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
const dto = result.unwrap();
|
|
expect(dto.returnTo).toBe(path);
|
|
expect(dto.hasInsufficientPermissions).toBe(true);
|
|
}
|
|
});
|
|
|
|
it('should handle different returnTo values and hasInsufficientPermissions', async () => {
|
|
const testCases = [
|
|
{ returnTo: '/dashboard', expectedHasInsufficientPermissions: true },
|
|
{ returnTo: null, expectedHasInsufficientPermissions: false },
|
|
{ returnTo: undefined, expectedHasInsufficientPermissions: false },
|
|
{ returnTo: '', expectedHasInsufficientPermissions: true },
|
|
];
|
|
|
|
for (const testCase of testCases) {
|
|
const params: AuthPageParams = {
|
|
returnTo: testCase.returnTo as string | null | undefined,
|
|
};
|
|
|
|
const result = await service.processLoginParams(params);
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
const dto = result.unwrap();
|
|
expect(dto.hasInsufficientPermissions).toBe(testCase.expectedHasInsufficientPermissions);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('aggregation logic', () => {
|
|
it('should aggregate login params into DTO correctly', async () => {
|
|
const params: AuthPageParams = {
|
|
returnTo: '/dashboard',
|
|
};
|
|
|
|
const result = await service.processLoginParams(params);
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
const dto = result.unwrap();
|
|
|
|
// Verify all fields are correctly aggregated
|
|
expect(dto.returnTo).toBe('/dashboard');
|
|
expect(dto.hasInsufficientPermissions).toBe(true);
|
|
expect(typeof dto.returnTo).toBe('string');
|
|
expect(typeof dto.hasInsufficientPermissions).toBe('boolean');
|
|
});
|
|
|
|
it('should handle empty params object', async () => {
|
|
const params: AuthPageParams = {};
|
|
|
|
const result = await service.processLoginParams(params);
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
const dto = result.unwrap();
|
|
|
|
// Verify default values are used
|
|
expect(dto.returnTo).toBe('/dashboard');
|
|
expect(dto.hasInsufficientPermissions).toBe(false);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('processForgotPasswordParams', () => {
|
|
describe('happy paths', () => {
|
|
it('should process forgot password params with returnTo', async () => {
|
|
const params: AuthPageParams = {
|
|
returnTo: '/auth/login',
|
|
};
|
|
|
|
const result = await service.processForgotPasswordParams(params);
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
const dto = result.unwrap();
|
|
expect(dto.returnTo).toBe('/auth/login');
|
|
});
|
|
|
|
it('should process forgot password params with null returnTo', async () => {
|
|
const params: AuthPageParams = {
|
|
returnTo: null,
|
|
};
|
|
|
|
const result = await service.processForgotPasswordParams(params);
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
const dto = result.unwrap();
|
|
expect(dto.returnTo).toBe('/auth/login');
|
|
});
|
|
|
|
it('should process forgot password params with undefined returnTo', async () => {
|
|
const params: AuthPageParams = {};
|
|
|
|
const result = await service.processForgotPasswordParams(params);
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
const dto = result.unwrap();
|
|
expect(dto.returnTo).toBe('/auth/login');
|
|
});
|
|
});
|
|
|
|
describe('decision branches', () => {
|
|
it('should handle different returnTo paths', async () => {
|
|
const paths = [
|
|
'/auth/login',
|
|
'/auth/signup',
|
|
'/dashboard',
|
|
'/settings',
|
|
];
|
|
|
|
for (const path of paths) {
|
|
const params: AuthPageParams = {
|
|
returnTo: path,
|
|
};
|
|
|
|
const result = await service.processForgotPasswordParams(params);
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
const dto = result.unwrap();
|
|
expect(dto.returnTo).toBe(path);
|
|
}
|
|
});
|
|
|
|
it('should handle special characters in returnTo path', async () => {
|
|
const paths = [
|
|
'/auth/login?param=value',
|
|
'/auth/login#section',
|
|
'/auth/login/with/slashes',
|
|
];
|
|
|
|
for (const path of paths) {
|
|
const params: AuthPageParams = {
|
|
returnTo: path,
|
|
};
|
|
|
|
const result = await service.processForgotPasswordParams(params);
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
const dto = result.unwrap();
|
|
expect(dto.returnTo).toBe(path);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('aggregation logic', () => {
|
|
it('should aggregate forgot password params into DTO correctly', async () => {
|
|
const params: AuthPageParams = {
|
|
returnTo: '/auth/login',
|
|
};
|
|
|
|
const result = await service.processForgotPasswordParams(params);
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
const dto = result.unwrap();
|
|
|
|
// Verify all fields are correctly aggregated
|
|
expect(dto.returnTo).toBe('/auth/login');
|
|
expect(typeof dto.returnTo).toBe('string');
|
|
});
|
|
|
|
it('should handle empty params object', async () => {
|
|
const params: AuthPageParams = {};
|
|
|
|
const result = await service.processForgotPasswordParams(params);
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
const dto = result.unwrap();
|
|
|
|
// Verify default values are used
|
|
expect(dto.returnTo).toBe('/auth/login');
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('processResetPasswordParams', () => {
|
|
describe('happy paths', () => {
|
|
it('should process reset password params with token and returnTo', async () => {
|
|
const params: AuthPageParams = {
|
|
token: 'reset-token-123',
|
|
returnTo: '/auth/login',
|
|
};
|
|
|
|
const result = await service.processResetPasswordParams(params);
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
const dto = result.unwrap();
|
|
expect(dto.token).toBe('reset-token-123');
|
|
expect(dto.returnTo).toBe('/auth/login');
|
|
});
|
|
|
|
it('should process reset password params with token and null returnTo', async () => {
|
|
const params: AuthPageParams = {
|
|
token: 'reset-token-123',
|
|
returnTo: null,
|
|
};
|
|
|
|
const result = await service.processResetPasswordParams(params);
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
const dto = result.unwrap();
|
|
expect(dto.token).toBe('reset-token-123');
|
|
expect(dto.returnTo).toBe('/auth/login');
|
|
});
|
|
|
|
it('should process reset password params with token and undefined returnTo', async () => {
|
|
const params: AuthPageParams = {
|
|
token: 'reset-token-123',
|
|
};
|
|
|
|
const result = await service.processResetPasswordParams(params);
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
const dto = result.unwrap();
|
|
expect(dto.token).toBe('reset-token-123');
|
|
expect(dto.returnTo).toBe('/auth/login');
|
|
});
|
|
});
|
|
|
|
describe('failure modes', () => {
|
|
it('should return error when token is missing', async () => {
|
|
const params: AuthPageParams = {
|
|
returnTo: '/auth/login',
|
|
};
|
|
|
|
const result = await service.processResetPasswordParams(params);
|
|
|
|
expect(result.isErr()).toBe(true);
|
|
expect(result.getError().type).toBe('validation');
|
|
expect(result.getError().message).toBe('Missing reset token');
|
|
});
|
|
|
|
it('should return error when token is null', async () => {
|
|
const params: AuthPageParams = {
|
|
token: null,
|
|
returnTo: '/auth/login',
|
|
};
|
|
|
|
const result = await service.processResetPasswordParams(params);
|
|
|
|
expect(result.isErr()).toBe(true);
|
|
expect(result.getError().type).toBe('validation');
|
|
expect(result.getError().message).toBe('Missing reset token');
|
|
});
|
|
|
|
it('should return error when token is empty string', async () => {
|
|
const params: AuthPageParams = {
|
|
token: '',
|
|
returnTo: '/auth/login',
|
|
};
|
|
|
|
const result = await service.processResetPasswordParams(params);
|
|
|
|
expect(result.isErr()).toBe(true);
|
|
expect(result.getError().type).toBe('validation');
|
|
expect(result.getError().message).toBe('Missing reset token');
|
|
});
|
|
});
|
|
|
|
describe('decision branches', () => {
|
|
it('should handle different token formats', async () => {
|
|
const tokens = [
|
|
'reset-token-123',
|
|
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c',
|
|
'token-with-special-chars-!@#$%^&*()',
|
|
];
|
|
|
|
for (const token of tokens) {
|
|
const params: AuthPageParams = {
|
|
token,
|
|
returnTo: '/auth/login',
|
|
};
|
|
|
|
const result = await service.processResetPasswordParams(params);
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
const dto = result.unwrap();
|
|
expect(dto.token).toBe(token);
|
|
}
|
|
});
|
|
|
|
it('should handle different returnTo paths', async () => {
|
|
const paths = [
|
|
'/auth/login',
|
|
'/auth/signup',
|
|
'/dashboard',
|
|
'/settings',
|
|
];
|
|
|
|
for (const path of paths) {
|
|
const params: AuthPageParams = {
|
|
token: 'reset-token-123',
|
|
returnTo: path,
|
|
};
|
|
|
|
const result = await service.processResetPasswordParams(params);
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
const dto = result.unwrap();
|
|
expect(dto.returnTo).toBe(path);
|
|
}
|
|
});
|
|
|
|
it('should handle special characters in returnTo path', async () => {
|
|
const paths = [
|
|
'/auth/login?param=value',
|
|
'/auth/login#section',
|
|
'/auth/login/with/slashes',
|
|
];
|
|
|
|
for (const path of paths) {
|
|
const params: AuthPageParams = {
|
|
token: 'reset-token-123',
|
|
returnTo: path,
|
|
};
|
|
|
|
const result = await service.processResetPasswordParams(params);
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
const dto = result.unwrap();
|
|
expect(dto.returnTo).toBe(path);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('aggregation logic', () => {
|
|
it('should aggregate reset password params into DTO correctly', async () => {
|
|
const params: AuthPageParams = {
|
|
token: 'reset-token-123',
|
|
returnTo: '/auth/login',
|
|
};
|
|
|
|
const result = await service.processResetPasswordParams(params);
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
const dto = result.unwrap();
|
|
|
|
// Verify all fields are correctly aggregated
|
|
expect(dto.token).toBe('reset-token-123');
|
|
expect(dto.returnTo).toBe('/auth/login');
|
|
expect(typeof dto.token).toBe('string');
|
|
expect(typeof dto.returnTo).toBe('string');
|
|
});
|
|
|
|
it('should handle params with only token', async () => {
|
|
const params: AuthPageParams = {
|
|
token: 'reset-token-123',
|
|
};
|
|
|
|
const result = await service.processResetPasswordParams(params);
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
const dto = result.unwrap();
|
|
|
|
// Verify default returnTo is used
|
|
expect(dto.token).toBe('reset-token-123');
|
|
expect(dto.returnTo).toBe('/auth/login');
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('processSignupParams', () => {
|
|
describe('happy paths', () => {
|
|
it('should process signup params with returnTo', async () => {
|
|
const params: AuthPageParams = {
|
|
returnTo: '/onboarding',
|
|
};
|
|
|
|
const result = await service.processSignupParams(params);
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
const dto = result.unwrap();
|
|
expect(dto.returnTo).toBe('/onboarding');
|
|
});
|
|
|
|
it('should process signup params with null returnTo', async () => {
|
|
const params: AuthPageParams = {
|
|
returnTo: null,
|
|
};
|
|
|
|
const result = await service.processSignupParams(params);
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
const dto = result.unwrap();
|
|
expect(dto.returnTo).toBe('/onboarding');
|
|
});
|
|
|
|
it('should process signup params with undefined returnTo', async () => {
|
|
const params: AuthPageParams = {};
|
|
|
|
const result = await service.processSignupParams(params);
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
const dto = result.unwrap();
|
|
expect(dto.returnTo).toBe('/onboarding');
|
|
});
|
|
});
|
|
|
|
describe('decision branches', () => {
|
|
it('should handle different returnTo paths', async () => {
|
|
const paths = [
|
|
'/onboarding',
|
|
'/dashboard',
|
|
'/settings',
|
|
'/projects',
|
|
];
|
|
|
|
for (const path of paths) {
|
|
const params: AuthPageParams = {
|
|
returnTo: path,
|
|
};
|
|
|
|
const result = await service.processSignupParams(params);
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
const dto = result.unwrap();
|
|
expect(dto.returnTo).toBe(path);
|
|
}
|
|
});
|
|
|
|
it('should handle special characters in returnTo path', async () => {
|
|
const paths = [
|
|
'/onboarding?param=value',
|
|
'/onboarding#section',
|
|
'/onboarding/with/slashes',
|
|
];
|
|
|
|
for (const path of paths) {
|
|
const params: AuthPageParams = {
|
|
returnTo: path,
|
|
};
|
|
|
|
const result = await service.processSignupParams(params);
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
const dto = result.unwrap();
|
|
expect(dto.returnTo).toBe(path);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('aggregation logic', () => {
|
|
it('should aggregate signup params into DTO correctly', async () => {
|
|
const params: AuthPageParams = {
|
|
returnTo: '/onboarding',
|
|
};
|
|
|
|
const result = await service.processSignupParams(params);
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
const dto = result.unwrap();
|
|
|
|
// Verify all fields are correctly aggregated
|
|
expect(dto.returnTo).toBe('/onboarding');
|
|
expect(typeof dto.returnTo).toBe('string');
|
|
});
|
|
|
|
it('should handle empty params object', async () => {
|
|
const params: AuthPageParams = {};
|
|
|
|
const result = await service.processSignupParams(params);
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
const dto = result.unwrap();
|
|
|
|
// Verify default values are used
|
|
expect(dto.returnTo).toBe('/onboarding');
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('error handling', () => {
|
|
it('should handle unexpected error types in processLoginParams', async () => {
|
|
const params: AuthPageParams = {
|
|
returnTo: '/dashboard',
|
|
};
|
|
|
|
// This should not throw an error
|
|
const result = await service.processLoginParams(params);
|
|
expect(result.isOk()).toBe(true);
|
|
});
|
|
|
|
it('should handle unexpected error types in processForgotPasswordParams', async () => {
|
|
const params: AuthPageParams = {
|
|
returnTo: '/auth/login',
|
|
};
|
|
|
|
// This should not throw an error
|
|
const result = await service.processForgotPasswordParams(params);
|
|
expect(result.isOk()).toBe(true);
|
|
});
|
|
|
|
it('should handle unexpected error types in processResetPasswordParams', async () => {
|
|
const params: AuthPageParams = {
|
|
token: 'reset-token-123',
|
|
returnTo: '/auth/login',
|
|
};
|
|
|
|
// This should not throw an error
|
|
const result = await service.processResetPasswordParams(params);
|
|
expect(result.isOk()).toBe(true);
|
|
});
|
|
|
|
it('should handle unexpected error types in processSignupParams', async () => {
|
|
const params: AuthPageParams = {
|
|
returnTo: '/onboarding',
|
|
};
|
|
|
|
// This should not throw an error
|
|
const result = await service.processSignupParams(params);
|
|
expect(result.isOk()).toBe(true);
|
|
});
|
|
});
|
|
});
|