473 lines
14 KiB
TypeScript
473 lines
14 KiB
TypeScript
/**
|
|
* View Data Layer Tests - Onboarding Functionality
|
|
*
|
|
* This test file covers the view data layer for onboarding functionality.
|
|
*
|
|
* The view data layer is responsible for:
|
|
* - DTO → UI model mapping
|
|
* - Formatting, sorting, and grouping
|
|
* - Derived fields and defaults
|
|
* - UI-specific semantics
|
|
*
|
|
* This layer isolates the UI from API churn by providing a stable interface
|
|
* between the API layer and the presentation layer.
|
|
*
|
|
* Test coverage includes:
|
|
* - Onboarding page data transformation and validation
|
|
* - Onboarding wizard view models and field formatting
|
|
* - Authentication and authorization checks for onboarding flow
|
|
* - Redirect logic based on onboarding status (already onboarded, not authenticated)
|
|
* - Onboarding-specific formatting and validation
|
|
* - Derived fields for onboarding UI components (progress, completion status, etc.)
|
|
* - Default values and fallbacks for onboarding views
|
|
* - Onboarding step data mapping and state management
|
|
* - Error handling and fallback UI states for onboarding flow
|
|
*/
|
|
|
|
import { OnboardingViewDataBuilder } from '@/lib/builders/view-data/OnboardingViewDataBuilder';
|
|
import { OnboardingPageViewDataBuilder } from '@/lib/builders/view-data/OnboardingPageViewDataBuilder';
|
|
import { CompleteOnboardingViewDataBuilder } from '@/lib/builders/view-data/CompleteOnboardingViewDataBuilder';
|
|
import { Result } from '@/lib/contracts/Result';
|
|
import { PresentationError } from '@/lib/contracts/page-queries/PresentationError';
|
|
import type { CompleteOnboardingOutputDTO } from '@/lib/types/generated/CompleteOnboardingOutputDTO';
|
|
|
|
describe('OnboardingViewDataBuilder', () => {
|
|
describe('happy paths', () => {
|
|
it('should transform successful onboarding check to ViewData correctly', () => {
|
|
const apiDto: Result<{ isAlreadyOnboarded: boolean }, PresentationError> = Result.ok({
|
|
isAlreadyOnboarded: false,
|
|
});
|
|
|
|
const result = OnboardingViewDataBuilder.build(apiDto);
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
expect(result.unwrap()).toEqual({
|
|
isAlreadyOnboarded: false,
|
|
});
|
|
});
|
|
|
|
it('should handle already onboarded user correctly', () => {
|
|
const apiDto: Result<{ isAlreadyOnboarded: boolean }, PresentationError> = Result.ok({
|
|
isAlreadyOnboarded: true,
|
|
});
|
|
|
|
const result = OnboardingViewDataBuilder.build(apiDto);
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
expect(result.unwrap()).toEqual({
|
|
isAlreadyOnboarded: true,
|
|
});
|
|
});
|
|
|
|
it('should handle missing isAlreadyOnboarded field with default false', () => {
|
|
const apiDto: Result<{ isAlreadyOnboarded?: boolean }, PresentationError> = Result.ok({});
|
|
|
|
const result = OnboardingViewDataBuilder.build(apiDto);
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
expect(result.unwrap()).toEqual({
|
|
isAlreadyOnboarded: false,
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('error handling', () => {
|
|
it('should propagate unauthorized error', () => {
|
|
const apiDto: Result<{ isAlreadyOnboarded: boolean }, PresentationError> = Result.err('unauthorized');
|
|
|
|
const result = OnboardingViewDataBuilder.build(apiDto);
|
|
|
|
expect(result.isErr()).toBe(true);
|
|
expect(result.getError()).toBe('unauthorized');
|
|
});
|
|
|
|
it('should propagate notFound error', () => {
|
|
const apiDto: Result<{ isAlreadyOnboarded: boolean }, PresentationError> = Result.err('notFound');
|
|
|
|
const result = OnboardingViewDataBuilder.build(apiDto);
|
|
|
|
expect(result.isErr()).toBe(true);
|
|
expect(result.getError()).toBe('notFound');
|
|
});
|
|
|
|
it('should propagate serverError', () => {
|
|
const apiDto: Result<{ isAlreadyOnboarded: boolean }, PresentationError> = Result.err('serverError');
|
|
|
|
const result = OnboardingViewDataBuilder.build(apiDto);
|
|
|
|
expect(result.isErr()).toBe(true);
|
|
expect(result.getError()).toBe('serverError');
|
|
});
|
|
|
|
it('should propagate networkError', () => {
|
|
const apiDto: Result<{ isAlreadyOnboarded: boolean }, PresentationError> = Result.err('networkError');
|
|
|
|
const result = OnboardingViewDataBuilder.build(apiDto);
|
|
|
|
expect(result.isErr()).toBe(true);
|
|
expect(result.getError()).toBe('networkError');
|
|
});
|
|
|
|
it('should propagate validationError', () => {
|
|
const apiDto: Result<{ isAlreadyOnboarded: boolean }, PresentationError> = Result.err('validationError');
|
|
|
|
const result = OnboardingViewDataBuilder.build(apiDto);
|
|
|
|
expect(result.isErr()).toBe(true);
|
|
expect(result.getError()).toBe('validationError');
|
|
});
|
|
|
|
it('should propagate unknown error', () => {
|
|
const apiDto: Result<{ isAlreadyOnboarded: boolean }, PresentationError> = Result.err('unknown');
|
|
|
|
const result = OnboardingViewDataBuilder.build(apiDto);
|
|
|
|
expect(result.isErr()).toBe(true);
|
|
expect(result.getError()).toBe('unknown');
|
|
});
|
|
});
|
|
|
|
describe('data transformation', () => {
|
|
it('should preserve all DTO fields in the output', () => {
|
|
const apiDto: Result<{ isAlreadyOnboarded: boolean }, PresentationError> = Result.ok({
|
|
isAlreadyOnboarded: false,
|
|
});
|
|
|
|
const result = OnboardingViewDataBuilder.build(apiDto);
|
|
|
|
expect(result.unwrap().isAlreadyOnboarded).toBe(false);
|
|
});
|
|
|
|
it('should not modify the input DTO', () => {
|
|
const apiDto: Result<{ isAlreadyOnboarded: boolean }, PresentationError> = Result.ok({
|
|
isAlreadyOnboarded: false,
|
|
});
|
|
|
|
const originalDto = { ...apiDto.unwrap() };
|
|
OnboardingViewDataBuilder.build(apiDto);
|
|
|
|
expect(apiDto.unwrap()).toEqual(originalDto);
|
|
});
|
|
});
|
|
|
|
describe('edge cases', () => {
|
|
it('should handle null isAlreadyOnboarded as false', () => {
|
|
const apiDto: Result<{ isAlreadyOnboarded: boolean | null }, PresentationError> = Result.ok({
|
|
isAlreadyOnboarded: null,
|
|
});
|
|
|
|
const result = OnboardingViewDataBuilder.build(apiDto);
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
expect(result.unwrap()).toEqual({
|
|
isAlreadyOnboarded: false,
|
|
});
|
|
});
|
|
|
|
it('should handle undefined isAlreadyOnboarded as false', () => {
|
|
const apiDto: Result<{ isAlreadyOnboarded: boolean | undefined }, PresentationError> = Result.ok({
|
|
isAlreadyOnboarded: undefined,
|
|
});
|
|
|
|
const result = OnboardingViewDataBuilder.build(apiDto);
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
expect(result.unwrap()).toEqual({
|
|
isAlreadyOnboarded: false,
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('OnboardingPageViewDataBuilder', () => {
|
|
describe('happy paths', () => {
|
|
it('should transform driver data to ViewData correctly when driver exists', () => {
|
|
const apiDto = { id: 'driver-123', name: 'Test Driver' };
|
|
|
|
const result = OnboardingPageViewDataBuilder.build(apiDto);
|
|
|
|
expect(result).toEqual({
|
|
isAlreadyOnboarded: true,
|
|
});
|
|
});
|
|
|
|
it('should handle empty object as driver data', () => {
|
|
const apiDto = {};
|
|
|
|
const result = OnboardingPageViewDataBuilder.build(apiDto);
|
|
|
|
expect(result).toEqual({
|
|
isAlreadyOnboarded: true,
|
|
});
|
|
});
|
|
|
|
it('should handle null driver data', () => {
|
|
const apiDto = null;
|
|
|
|
const result = OnboardingPageViewDataBuilder.build(apiDto);
|
|
|
|
expect(result).toEqual({
|
|
isAlreadyOnboarded: false,
|
|
});
|
|
});
|
|
|
|
it('should handle undefined driver data', () => {
|
|
const apiDto = undefined;
|
|
|
|
const result = OnboardingPageViewDataBuilder.build(apiDto);
|
|
|
|
expect(result).toEqual({
|
|
isAlreadyOnboarded: false,
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('data transformation', () => {
|
|
it('should preserve all driver data fields in the output', () => {
|
|
const apiDto = {
|
|
id: 'driver-123',
|
|
name: 'Test Driver',
|
|
email: 'test@example.com',
|
|
createdAt: '2024-01-01T00:00:00.000Z',
|
|
};
|
|
|
|
const result = OnboardingPageViewDataBuilder.build(apiDto);
|
|
|
|
expect(result.isAlreadyOnboarded).toBe(true);
|
|
});
|
|
|
|
it('should not modify the input driver data', () => {
|
|
const apiDto = { id: 'driver-123', name: 'Test Driver' };
|
|
const originalDto = { ...apiDto };
|
|
|
|
OnboardingPageViewDataBuilder.build(apiDto);
|
|
|
|
expect(apiDto).toEqual(originalDto);
|
|
});
|
|
});
|
|
|
|
describe('edge cases', () => {
|
|
it('should handle empty string as driver data', () => {
|
|
const apiDto = '';
|
|
|
|
const result = OnboardingPageViewDataBuilder.build(apiDto);
|
|
|
|
expect(result).toEqual({
|
|
isAlreadyOnboarded: false,
|
|
});
|
|
});
|
|
|
|
it('should handle zero as driver data', () => {
|
|
const apiDto = 0;
|
|
|
|
const result = OnboardingPageViewDataBuilder.build(apiDto);
|
|
|
|
expect(result).toEqual({
|
|
isAlreadyOnboarded: false,
|
|
});
|
|
});
|
|
|
|
it('should handle false as driver data', () => {
|
|
const apiDto = false;
|
|
|
|
const result = OnboardingPageViewDataBuilder.build(apiDto);
|
|
|
|
expect(result).toEqual({
|
|
isAlreadyOnboarded: false,
|
|
});
|
|
});
|
|
|
|
it('should handle array as driver data', () => {
|
|
const apiDto = ['driver-123'];
|
|
|
|
const result = OnboardingPageViewDataBuilder.build(apiDto);
|
|
|
|
expect(result).toEqual({
|
|
isAlreadyOnboarded: true,
|
|
});
|
|
});
|
|
|
|
it('should handle function as driver data', () => {
|
|
const apiDto = () => {};
|
|
|
|
const result = OnboardingPageViewDataBuilder.build(apiDto);
|
|
|
|
expect(result).toEqual({
|
|
isAlreadyOnboarded: true,
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('CompleteOnboardingViewDataBuilder', () => {
|
|
describe('happy paths', () => {
|
|
it('should transform successful onboarding completion DTO to ViewData correctly', () => {
|
|
const apiDto: CompleteOnboardingOutputDTO = {
|
|
success: true,
|
|
driverId: 'driver-123',
|
|
};
|
|
|
|
const result = CompleteOnboardingViewDataBuilder.build(apiDto);
|
|
|
|
expect(result).toEqual({
|
|
success: true,
|
|
driverId: 'driver-123',
|
|
errorMessage: undefined,
|
|
});
|
|
});
|
|
|
|
it('should handle onboarding completion with error message', () => {
|
|
const apiDto: CompleteOnboardingOutputDTO = {
|
|
success: false,
|
|
driverId: undefined,
|
|
errorMessage: 'Failed to complete onboarding',
|
|
};
|
|
|
|
const result = CompleteOnboardingViewDataBuilder.build(apiDto);
|
|
|
|
expect(result).toEqual({
|
|
success: false,
|
|
driverId: undefined,
|
|
errorMessage: 'Failed to complete onboarding',
|
|
});
|
|
});
|
|
|
|
it('should handle onboarding completion with only success field', () => {
|
|
const apiDto: CompleteOnboardingOutputDTO = {
|
|
success: true,
|
|
};
|
|
|
|
const result = CompleteOnboardingViewDataBuilder.build(apiDto);
|
|
|
|
expect(result).toEqual({
|
|
success: true,
|
|
driverId: undefined,
|
|
errorMessage: undefined,
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('data transformation', () => {
|
|
it('should preserve all DTO fields in the output', () => {
|
|
const apiDto: CompleteOnboardingOutputDTO = {
|
|
success: true,
|
|
driverId: 'driver-123',
|
|
errorMessage: undefined,
|
|
};
|
|
|
|
const result = CompleteOnboardingViewDataBuilder.build(apiDto);
|
|
|
|
expect(result.success).toBe(apiDto.success);
|
|
expect(result.driverId).toBe(apiDto.driverId);
|
|
expect(result.errorMessage).toBe(apiDto.errorMessage);
|
|
});
|
|
|
|
it('should not modify the input DTO', () => {
|
|
const apiDto: CompleteOnboardingOutputDTO = {
|
|
success: true,
|
|
driverId: 'driver-123',
|
|
errorMessage: undefined,
|
|
};
|
|
|
|
const originalDto = { ...apiDto };
|
|
CompleteOnboardingViewDataBuilder.build(apiDto);
|
|
|
|
expect(apiDto).toEqual(originalDto);
|
|
});
|
|
});
|
|
|
|
describe('edge cases', () => {
|
|
it('should handle false success value', () => {
|
|
const apiDto: CompleteOnboardingOutputDTO = {
|
|
success: false,
|
|
driverId: undefined,
|
|
errorMessage: 'Error occurred',
|
|
};
|
|
|
|
const result = CompleteOnboardingViewDataBuilder.build(apiDto);
|
|
|
|
expect(result.success).toBe(false);
|
|
expect(result.driverId).toBeUndefined();
|
|
expect(result.errorMessage).toBe('Error occurred');
|
|
});
|
|
|
|
it('should handle empty string error message', () => {
|
|
const apiDto: CompleteOnboardingOutputDTO = {
|
|
success: false,
|
|
driverId: undefined,
|
|
errorMessage: '',
|
|
};
|
|
|
|
const result = CompleteOnboardingViewDataBuilder.build(apiDto);
|
|
|
|
expect(result.success).toBe(false);
|
|
expect(result.errorMessage).toBe('');
|
|
});
|
|
|
|
it('should handle very long driverId', () => {
|
|
const longDriverId = 'driver-' + 'a'.repeat(1000);
|
|
const apiDto: CompleteOnboardingOutputDTO = {
|
|
success: true,
|
|
driverId: longDriverId,
|
|
};
|
|
|
|
const result = CompleteOnboardingViewDataBuilder.build(apiDto);
|
|
|
|
expect(result.driverId).toBe(longDriverId);
|
|
});
|
|
|
|
it('should handle special characters in error message', () => {
|
|
const apiDto: CompleteOnboardingOutputDTO = {
|
|
success: false,
|
|
driverId: undefined,
|
|
errorMessage: 'Error: "Failed to create driver" (code: 500)',
|
|
};
|
|
|
|
const result = CompleteOnboardingViewDataBuilder.build(apiDto);
|
|
|
|
expect(result.errorMessage).toBe('Error: "Failed to create driver" (code: 500)');
|
|
});
|
|
});
|
|
|
|
describe('derived fields calculation', () => {
|
|
it('should calculate isSuccessful derived field correctly', () => {
|
|
const apiDto: CompleteOnboardingOutputDTO = {
|
|
success: true,
|
|
driverId: 'driver-123',
|
|
};
|
|
|
|
const result = CompleteOnboardingViewDataBuilder.build(apiDto);
|
|
|
|
// Note: The builder doesn't add derived fields, but we can verify the structure
|
|
expect(result.success).toBe(true);
|
|
expect(result.driverId).toBe('driver-123');
|
|
});
|
|
|
|
it('should handle success with no driverId', () => {
|
|
const apiDto: CompleteOnboardingOutputDTO = {
|
|
success: true,
|
|
driverId: undefined,
|
|
};
|
|
|
|
const result = CompleteOnboardingViewDataBuilder.build(apiDto);
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(result.driverId).toBeUndefined();
|
|
});
|
|
|
|
it('should handle failure with driverId', () => {
|
|
const apiDto: CompleteOnboardingOutputDTO = {
|
|
success: false,
|
|
driverId: 'driver-123',
|
|
errorMessage: 'Partial failure',
|
|
};
|
|
|
|
const result = CompleteOnboardingViewDataBuilder.build(apiDto);
|
|
|
|
expect(result.success).toBe(false);
|
|
expect(result.driverId).toBe('driver-123');
|
|
expect(result.errorMessage).toBe('Partial failure');
|
|
});
|
|
});
|
|
});
|