view data tests
This commit is contained in:
@@ -1,791 +0,0 @@
|
||||
/**
|
||||
* View Data Layer Tests - Admin Functionality
|
||||
*
|
||||
* This test file covers the view data layer for admin 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:
|
||||
* - Admin dashboard data transformation
|
||||
* - User management view models
|
||||
* - Admin-specific formatting and validation
|
||||
* - Derived fields for admin UI components
|
||||
* - Default values and fallbacks for admin views
|
||||
*/
|
||||
|
||||
import { AdminDashboardViewDataBuilder } from '@/lib/builders/view-data/AdminDashboardViewDataBuilder';
|
||||
import { AdminUsersViewDataBuilder } from '@/lib/builders/view-data/AdminUsersViewDataBuilder';
|
||||
import type { DashboardStats } from '@/lib/types/admin';
|
||||
import type { UserListResponse } from '@/lib/types/admin';
|
||||
|
||||
describe('AdminDashboardViewDataBuilder', () => {
|
||||
describe('happy paths', () => {
|
||||
it('should transform DashboardStats DTO to AdminDashboardViewData correctly', () => {
|
||||
const dashboardStats: DashboardStats = {
|
||||
totalUsers: 1000,
|
||||
activeUsers: 800,
|
||||
suspendedUsers: 50,
|
||||
deletedUsers: 150,
|
||||
systemAdmins: 5,
|
||||
recentLogins: 120,
|
||||
newUsersToday: 15,
|
||||
};
|
||||
|
||||
const result = AdminDashboardViewDataBuilder.build(dashboardStats);
|
||||
|
||||
expect(result).toEqual({
|
||||
stats: {
|
||||
totalUsers: 1000,
|
||||
activeUsers: 800,
|
||||
suspendedUsers: 50,
|
||||
deletedUsers: 150,
|
||||
systemAdmins: 5,
|
||||
recentLogins: 120,
|
||||
newUsersToday: 15,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle zero values correctly', () => {
|
||||
const dashboardStats: DashboardStats = {
|
||||
totalUsers: 0,
|
||||
activeUsers: 0,
|
||||
suspendedUsers: 0,
|
||||
deletedUsers: 0,
|
||||
systemAdmins: 0,
|
||||
recentLogins: 0,
|
||||
newUsersToday: 0,
|
||||
};
|
||||
|
||||
const result = AdminDashboardViewDataBuilder.build(dashboardStats);
|
||||
|
||||
expect(result).toEqual({
|
||||
stats: {
|
||||
totalUsers: 0,
|
||||
activeUsers: 0,
|
||||
suspendedUsers: 0,
|
||||
deletedUsers: 0,
|
||||
systemAdmins: 0,
|
||||
recentLogins: 0,
|
||||
newUsersToday: 0,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle large numbers correctly', () => {
|
||||
const dashboardStats: DashboardStats = {
|
||||
totalUsers: 1000000,
|
||||
activeUsers: 750000,
|
||||
suspendedUsers: 25000,
|
||||
deletedUsers: 225000,
|
||||
systemAdmins: 50,
|
||||
recentLogins: 50000,
|
||||
newUsersToday: 1000,
|
||||
};
|
||||
|
||||
const result = AdminDashboardViewDataBuilder.build(dashboardStats);
|
||||
|
||||
expect(result.stats.totalUsers).toBe(1000000);
|
||||
expect(result.stats.activeUsers).toBe(750000);
|
||||
expect(result.stats.systemAdmins).toBe(50);
|
||||
});
|
||||
});
|
||||
|
||||
describe('data transformation', () => {
|
||||
it('should preserve all DTO fields in the output', () => {
|
||||
const dashboardStats: DashboardStats = {
|
||||
totalUsers: 500,
|
||||
activeUsers: 400,
|
||||
suspendedUsers: 25,
|
||||
deletedUsers: 75,
|
||||
systemAdmins: 3,
|
||||
recentLogins: 80,
|
||||
newUsersToday: 10,
|
||||
};
|
||||
|
||||
const result = AdminDashboardViewDataBuilder.build(dashboardStats);
|
||||
|
||||
expect(result.stats.totalUsers).toBe(dashboardStats.totalUsers);
|
||||
expect(result.stats.activeUsers).toBe(dashboardStats.activeUsers);
|
||||
expect(result.stats.suspendedUsers).toBe(dashboardStats.suspendedUsers);
|
||||
expect(result.stats.deletedUsers).toBe(dashboardStats.deletedUsers);
|
||||
expect(result.stats.systemAdmins).toBe(dashboardStats.systemAdmins);
|
||||
expect(result.stats.recentLogins).toBe(dashboardStats.recentLogins);
|
||||
expect(result.stats.newUsersToday).toBe(dashboardStats.newUsersToday);
|
||||
});
|
||||
|
||||
it('should not modify the input DTO', () => {
|
||||
const dashboardStats: DashboardStats = {
|
||||
totalUsers: 100,
|
||||
activeUsers: 80,
|
||||
suspendedUsers: 5,
|
||||
deletedUsers: 15,
|
||||
systemAdmins: 2,
|
||||
recentLogins: 20,
|
||||
newUsersToday: 5,
|
||||
};
|
||||
|
||||
const originalStats = { ...dashboardStats };
|
||||
AdminDashboardViewDataBuilder.build(dashboardStats);
|
||||
|
||||
expect(dashboardStats).toEqual(originalStats);
|
||||
});
|
||||
});
|
||||
|
||||
describe('edge cases', () => {
|
||||
it('should handle negative values (if API returns them)', () => {
|
||||
const dashboardStats: DashboardStats = {
|
||||
totalUsers: -1,
|
||||
activeUsers: -1,
|
||||
suspendedUsers: -1,
|
||||
deletedUsers: -1,
|
||||
systemAdmins: -1,
|
||||
recentLogins: -1,
|
||||
newUsersToday: -1,
|
||||
};
|
||||
|
||||
const result = AdminDashboardViewDataBuilder.build(dashboardStats);
|
||||
|
||||
expect(result.stats.totalUsers).toBe(-1);
|
||||
expect(result.stats.activeUsers).toBe(-1);
|
||||
});
|
||||
|
||||
it('should handle very large numbers', () => {
|
||||
const dashboardStats: DashboardStats = {
|
||||
totalUsers: Number.MAX_SAFE_INTEGER,
|
||||
activeUsers: Number.MAX_SAFE_INTEGER - 1000,
|
||||
suspendedUsers: 100,
|
||||
deletedUsers: 100,
|
||||
systemAdmins: 10,
|
||||
recentLogins: 1000,
|
||||
newUsersToday: 100,
|
||||
};
|
||||
|
||||
const result = AdminDashboardViewDataBuilder.build(dashboardStats);
|
||||
|
||||
expect(result.stats.totalUsers).toBe(Number.MAX_SAFE_INTEGER);
|
||||
expect(result.stats.activeUsers).toBe(Number.MAX_SAFE_INTEGER - 1000);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('AdminUsersViewDataBuilder', () => {
|
||||
describe('happy paths', () => {
|
||||
it('should transform UserListResponse DTO to AdminUsersViewData correctly', () => {
|
||||
const userListResponse: UserListResponse = {
|
||||
users: [
|
||||
{
|
||||
id: 'user-1',
|
||||
email: 'admin@example.com',
|
||||
displayName: 'Admin User',
|
||||
roles: ['admin', 'owner'],
|
||||
status: 'active',
|
||||
isSystemAdmin: true,
|
||||
createdAt: '2024-01-01T00:00:00.000Z',
|
||||
updatedAt: '2024-01-15T12:00:00.000Z',
|
||||
lastLoginAt: '2024-01-20T10:00:00.000Z',
|
||||
primaryDriverId: 'driver-123',
|
||||
},
|
||||
{
|
||||
id: 'user-2',
|
||||
email: 'user@example.com',
|
||||
displayName: 'Regular User',
|
||||
roles: ['member'],
|
||||
status: 'active',
|
||||
isSystemAdmin: false,
|
||||
createdAt: '2024-01-05T00:00:00.000Z',
|
||||
updatedAt: '2024-01-10T08:00:00.000Z',
|
||||
lastLoginAt: '2024-01-18T14:00:00.000Z',
|
||||
primaryDriverId: 'driver-456',
|
||||
},
|
||||
],
|
||||
total: 2,
|
||||
page: 1,
|
||||
limit: 10,
|
||||
totalPages: 1,
|
||||
};
|
||||
|
||||
const result = AdminUsersViewDataBuilder.build(userListResponse);
|
||||
|
||||
expect(result.users).toHaveLength(2);
|
||||
expect(result.users[0]).toEqual({
|
||||
id: 'user-1',
|
||||
email: 'admin@example.com',
|
||||
displayName: 'Admin User',
|
||||
roles: ['admin', 'owner'],
|
||||
status: 'active',
|
||||
isSystemAdmin: true,
|
||||
createdAt: '2024-01-01T00:00:00.000Z',
|
||||
updatedAt: '2024-01-15T12:00:00.000Z',
|
||||
lastLoginAt: '2024-01-20T10:00:00.000Z',
|
||||
primaryDriverId: 'driver-123',
|
||||
});
|
||||
expect(result.users[1]).toEqual({
|
||||
id: 'user-2',
|
||||
email: 'user@example.com',
|
||||
displayName: 'Regular User',
|
||||
roles: ['member'],
|
||||
status: 'active',
|
||||
isSystemAdmin: false,
|
||||
createdAt: '2024-01-05T00:00:00.000Z',
|
||||
updatedAt: '2024-01-10T08:00:00.000Z',
|
||||
lastLoginAt: '2024-01-18T14:00:00.000Z',
|
||||
primaryDriverId: 'driver-456',
|
||||
});
|
||||
expect(result.total).toBe(2);
|
||||
expect(result.page).toBe(1);
|
||||
expect(result.limit).toBe(10);
|
||||
expect(result.totalPages).toBe(1);
|
||||
});
|
||||
|
||||
it('should calculate derived fields correctly', () => {
|
||||
const userListResponse: UserListResponse = {
|
||||
users: [
|
||||
{
|
||||
id: 'user-1',
|
||||
email: 'user1@example.com',
|
||||
displayName: 'User 1',
|
||||
roles: ['member'],
|
||||
status: 'active',
|
||||
isSystemAdmin: false,
|
||||
createdAt: '2024-01-01T00:00:00.000Z',
|
||||
updatedAt: '2024-01-15T12:00:00.000Z',
|
||||
},
|
||||
{
|
||||
id: 'user-2',
|
||||
email: 'user2@example.com',
|
||||
displayName: 'User 2',
|
||||
roles: ['member'],
|
||||
status: 'active',
|
||||
isSystemAdmin: false,
|
||||
createdAt: '2024-01-02T00:00:00.000Z',
|
||||
updatedAt: '2024-01-16T12:00:00.000Z',
|
||||
},
|
||||
{
|
||||
id: 'user-3',
|
||||
email: 'user3@example.com',
|
||||
displayName: 'User 3',
|
||||
roles: ['admin'],
|
||||
status: 'suspended',
|
||||
isSystemAdmin: true,
|
||||
createdAt: '2024-01-03T00:00:00.000Z',
|
||||
updatedAt: '2024-01-17T12:00:00.000Z',
|
||||
},
|
||||
{
|
||||
id: 'user-4',
|
||||
email: 'user4@example.com',
|
||||
displayName: 'User 4',
|
||||
roles: ['member'],
|
||||
status: 'deleted',
|
||||
isSystemAdmin: false,
|
||||
createdAt: '2024-01-04T00:00:00.000Z',
|
||||
updatedAt: '2024-01-18T12:00:00.000Z',
|
||||
},
|
||||
],
|
||||
total: 4,
|
||||
page: 1,
|
||||
limit: 10,
|
||||
totalPages: 1,
|
||||
};
|
||||
|
||||
const result = AdminUsersViewDataBuilder.build(userListResponse);
|
||||
|
||||
// activeUserCount should count only users with status 'active'
|
||||
expect(result.activeUserCount).toBe(2);
|
||||
// adminCount should count only system admins
|
||||
expect(result.adminCount).toBe(1);
|
||||
});
|
||||
|
||||
it('should handle empty users list', () => {
|
||||
const userListResponse: UserListResponse = {
|
||||
users: [],
|
||||
total: 0,
|
||||
page: 1,
|
||||
limit: 10,
|
||||
totalPages: 0,
|
||||
};
|
||||
|
||||
const result = AdminUsersViewDataBuilder.build(userListResponse);
|
||||
|
||||
expect(result.users).toHaveLength(0);
|
||||
expect(result.total).toBe(0);
|
||||
expect(result.activeUserCount).toBe(0);
|
||||
expect(result.adminCount).toBe(0);
|
||||
});
|
||||
|
||||
it('should handle users without optional fields', () => {
|
||||
const userListResponse: UserListResponse = {
|
||||
users: [
|
||||
{
|
||||
id: 'user-1',
|
||||
email: 'user1@example.com',
|
||||
displayName: 'User 1',
|
||||
roles: ['member'],
|
||||
status: 'active',
|
||||
isSystemAdmin: false,
|
||||
createdAt: '2024-01-01T00:00:00.000Z',
|
||||
updatedAt: '2024-01-15T12:00:00.000Z',
|
||||
// lastLoginAt and primaryDriverId are optional
|
||||
},
|
||||
],
|
||||
total: 1,
|
||||
page: 1,
|
||||
limit: 10,
|
||||
totalPages: 1,
|
||||
};
|
||||
|
||||
const result = AdminUsersViewDataBuilder.build(userListResponse);
|
||||
|
||||
expect(result.users[0].lastLoginAt).toBeUndefined();
|
||||
expect(result.users[0].primaryDriverId).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('date formatting', () => {
|
||||
it('should handle ISO date strings correctly', () => {
|
||||
const userListResponse: UserListResponse = {
|
||||
users: [
|
||||
{
|
||||
id: 'user-1',
|
||||
email: 'user1@example.com',
|
||||
displayName: 'User 1',
|
||||
roles: ['member'],
|
||||
status: 'active',
|
||||
isSystemAdmin: false,
|
||||
createdAt: '2024-01-01T00:00:00.000Z',
|
||||
updatedAt: '2024-01-15T12:00:00.000Z',
|
||||
lastLoginAt: '2024-01-20T10:00:00.000Z',
|
||||
},
|
||||
],
|
||||
total: 1,
|
||||
page: 1,
|
||||
limit: 10,
|
||||
totalPages: 1,
|
||||
};
|
||||
|
||||
const result = AdminUsersViewDataBuilder.build(userListResponse);
|
||||
|
||||
expect(result.users[0].createdAt).toBe('2024-01-01T00:00:00.000Z');
|
||||
expect(result.users[0].updatedAt).toBe('2024-01-15T12:00:00.000Z');
|
||||
expect(result.users[0].lastLoginAt).toBe('2024-01-20T10:00:00.000Z');
|
||||
});
|
||||
|
||||
it('should handle Date objects and convert to ISO strings', () => {
|
||||
const userListResponse: UserListResponse = {
|
||||
users: [
|
||||
{
|
||||
id: 'user-1',
|
||||
email: 'user1@example.com',
|
||||
displayName: 'User 1',
|
||||
roles: ['member'],
|
||||
status: 'active',
|
||||
isSystemAdmin: false,
|
||||
createdAt: new Date('2024-01-01T00:00:00.000Z'),
|
||||
updatedAt: new Date('2024-01-15T12:00:00.000Z'),
|
||||
lastLoginAt: new Date('2024-01-20T10:00:00.000Z'),
|
||||
},
|
||||
],
|
||||
total: 1,
|
||||
page: 1,
|
||||
limit: 10,
|
||||
totalPages: 1,
|
||||
};
|
||||
|
||||
const result = AdminUsersViewDataBuilder.build(userListResponse);
|
||||
|
||||
expect(result.users[0].createdAt).toBe('2024-01-01T00:00:00.000Z');
|
||||
expect(result.users[0].updatedAt).toBe('2024-01-15T12:00:00.000Z');
|
||||
expect(result.users[0].lastLoginAt).toBe('2024-01-20T10:00:00.000Z');
|
||||
});
|
||||
|
||||
it('should handle Date objects for lastLoginAt when present', () => {
|
||||
const userListResponse: UserListResponse = {
|
||||
users: [
|
||||
{
|
||||
id: 'user-1',
|
||||
email: 'user1@example.com',
|
||||
displayName: 'User 1',
|
||||
roles: ['member'],
|
||||
status: 'active',
|
||||
isSystemAdmin: false,
|
||||
createdAt: '2024-01-01T00:00:00.000Z',
|
||||
updatedAt: '2024-01-15T12:00:00.000Z',
|
||||
lastLoginAt: new Date('2024-01-20T10:00:00.000Z'),
|
||||
},
|
||||
],
|
||||
total: 1,
|
||||
page: 1,
|
||||
limit: 10,
|
||||
totalPages: 1,
|
||||
};
|
||||
|
||||
const result = AdminUsersViewDataBuilder.build(userListResponse);
|
||||
|
||||
expect(result.users[0].lastLoginAt).toBe('2024-01-20T10:00:00.000Z');
|
||||
});
|
||||
});
|
||||
|
||||
describe('data transformation', () => {
|
||||
it('should preserve all DTO fields in the output', () => {
|
||||
const userListResponse: UserListResponse = {
|
||||
users: [
|
||||
{
|
||||
id: 'user-1',
|
||||
email: 'user1@example.com',
|
||||
displayName: 'User 1',
|
||||
roles: ['admin', 'owner'],
|
||||
status: 'active',
|
||||
isSystemAdmin: true,
|
||||
createdAt: '2024-01-01T00:00:00.000Z',
|
||||
updatedAt: '2024-01-15T12:00:00.000Z',
|
||||
lastLoginAt: '2024-01-20T10:00:00.000Z',
|
||||
primaryDriverId: 'driver-123',
|
||||
},
|
||||
],
|
||||
total: 1,
|
||||
page: 1,
|
||||
limit: 10,
|
||||
totalPages: 1,
|
||||
};
|
||||
|
||||
const result = AdminUsersViewDataBuilder.build(userListResponse);
|
||||
|
||||
expect(result.users[0].id).toBe(userListResponse.users[0].id);
|
||||
expect(result.users[0].email).toBe(userListResponse.users[0].email);
|
||||
expect(result.users[0].displayName).toBe(userListResponse.users[0].displayName);
|
||||
expect(result.users[0].roles).toEqual(userListResponse.users[0].roles);
|
||||
expect(result.users[0].status).toBe(userListResponse.users[0].status);
|
||||
expect(result.users[0].isSystemAdmin).toBe(userListResponse.users[0].isSystemAdmin);
|
||||
expect(result.users[0].createdAt).toBe(userListResponse.users[0].createdAt);
|
||||
expect(result.users[0].updatedAt).toBe(userListResponse.users[0].updatedAt);
|
||||
expect(result.users[0].lastLoginAt).toBe(userListResponse.users[0].lastLoginAt);
|
||||
expect(result.users[0].primaryDriverId).toBe(userListResponse.users[0].primaryDriverId);
|
||||
expect(result.total).toBe(userListResponse.total);
|
||||
expect(result.page).toBe(userListResponse.page);
|
||||
expect(result.limit).toBe(userListResponse.limit);
|
||||
expect(result.totalPages).toBe(userListResponse.totalPages);
|
||||
});
|
||||
|
||||
it('should not modify the input DTO', () => {
|
||||
const userListResponse: UserListResponse = {
|
||||
users: [
|
||||
{
|
||||
id: 'user-1',
|
||||
email: 'user1@example.com',
|
||||
displayName: 'User 1',
|
||||
roles: ['member'],
|
||||
status: 'active',
|
||||
isSystemAdmin: false,
|
||||
createdAt: '2024-01-01T00:00:00.000Z',
|
||||
updatedAt: '2024-01-15T12:00:00.000Z',
|
||||
},
|
||||
],
|
||||
total: 1,
|
||||
page: 1,
|
||||
limit: 10,
|
||||
totalPages: 1,
|
||||
};
|
||||
|
||||
const originalResponse = { ...userListResponse };
|
||||
AdminUsersViewDataBuilder.build(userListResponse);
|
||||
|
||||
expect(userListResponse).toEqual(originalResponse);
|
||||
});
|
||||
});
|
||||
|
||||
describe('edge cases', () => {
|
||||
it('should handle users with multiple roles', () => {
|
||||
const userListResponse: UserListResponse = {
|
||||
users: [
|
||||
{
|
||||
id: 'user-1',
|
||||
email: 'user1@example.com',
|
||||
displayName: 'User 1',
|
||||
roles: ['admin', 'owner', 'steward', 'member'],
|
||||
status: 'active',
|
||||
isSystemAdmin: true,
|
||||
createdAt: '2024-01-01T00:00:00.000Z',
|
||||
updatedAt: '2024-01-15T12:00:00.000Z',
|
||||
},
|
||||
],
|
||||
total: 1,
|
||||
page: 1,
|
||||
limit: 10,
|
||||
totalPages: 1,
|
||||
};
|
||||
|
||||
const result = AdminUsersViewDataBuilder.build(userListResponse);
|
||||
|
||||
expect(result.users[0].roles).toEqual(['admin', 'owner', 'steward', 'member']);
|
||||
});
|
||||
|
||||
it('should handle users with different statuses', () => {
|
||||
const userListResponse: UserListResponse = {
|
||||
users: [
|
||||
{
|
||||
id: 'user-1',
|
||||
email: 'user1@example.com',
|
||||
displayName: 'User 1',
|
||||
roles: ['member'],
|
||||
status: 'active',
|
||||
isSystemAdmin: false,
|
||||
createdAt: '2024-01-01T00:00:00.000Z',
|
||||
updatedAt: '2024-01-15T12:00:00.000Z',
|
||||
},
|
||||
{
|
||||
id: 'user-2',
|
||||
email: 'user2@example.com',
|
||||
displayName: 'User 2',
|
||||
roles: ['member'],
|
||||
status: 'suspended',
|
||||
isSystemAdmin: false,
|
||||
createdAt: '2024-01-02T00:00:00.000Z',
|
||||
updatedAt: '2024-01-16T12:00:00.000Z',
|
||||
},
|
||||
{
|
||||
id: 'user-3',
|
||||
email: 'user3@example.com',
|
||||
displayName: 'User 3',
|
||||
roles: ['member'],
|
||||
status: 'deleted',
|
||||
isSystemAdmin: false,
|
||||
createdAt: '2024-01-03T00:00:00.000Z',
|
||||
updatedAt: '2024-01-17T12:00:00.000Z',
|
||||
},
|
||||
],
|
||||
total: 3,
|
||||
page: 1,
|
||||
limit: 10,
|
||||
totalPages: 1,
|
||||
};
|
||||
|
||||
const result = AdminUsersViewDataBuilder.build(userListResponse);
|
||||
|
||||
expect(result.users[0].status).toBe('active');
|
||||
expect(result.users[1].status).toBe('suspended');
|
||||
expect(result.users[2].status).toBe('deleted');
|
||||
expect(result.activeUserCount).toBe(1);
|
||||
});
|
||||
|
||||
it('should handle pagination metadata correctly', () => {
|
||||
const userListResponse: UserListResponse = {
|
||||
users: [
|
||||
{
|
||||
id: 'user-1',
|
||||
email: 'user1@example.com',
|
||||
displayName: 'User 1',
|
||||
roles: ['member'],
|
||||
status: 'active',
|
||||
isSystemAdmin: false,
|
||||
createdAt: '2024-01-01T00:00:00.000Z',
|
||||
updatedAt: '2024-01-15T12:00:00.000Z',
|
||||
},
|
||||
],
|
||||
total: 100,
|
||||
page: 5,
|
||||
limit: 20,
|
||||
totalPages: 5,
|
||||
};
|
||||
|
||||
const result = AdminUsersViewDataBuilder.build(userListResponse);
|
||||
|
||||
expect(result.total).toBe(100);
|
||||
expect(result.page).toBe(5);
|
||||
expect(result.limit).toBe(20);
|
||||
expect(result.totalPages).toBe(5);
|
||||
});
|
||||
|
||||
it('should handle users with empty roles array', () => {
|
||||
const userListResponse: UserListResponse = {
|
||||
users: [
|
||||
{
|
||||
id: 'user-1',
|
||||
email: 'user1@example.com',
|
||||
displayName: 'User 1',
|
||||
roles: [],
|
||||
status: 'active',
|
||||
isSystemAdmin: false,
|
||||
createdAt: '2024-01-01T00:00:00.000Z',
|
||||
updatedAt: '2024-01-15T12:00:00.000Z',
|
||||
},
|
||||
],
|
||||
total: 1,
|
||||
page: 1,
|
||||
limit: 10,
|
||||
totalPages: 1,
|
||||
};
|
||||
|
||||
const result = AdminUsersViewDataBuilder.build(userListResponse);
|
||||
|
||||
expect(result.users[0].roles).toEqual([]);
|
||||
});
|
||||
|
||||
it('should handle users with special characters in display name', () => {
|
||||
const userListResponse: UserListResponse = {
|
||||
users: [
|
||||
{
|
||||
id: 'user-1',
|
||||
email: 'user1@example.com',
|
||||
displayName: 'User 1 & 2 (Admin)',
|
||||
roles: ['admin'],
|
||||
status: 'active',
|
||||
isSystemAdmin: true,
|
||||
createdAt: '2024-01-01T00:00:00.000Z',
|
||||
updatedAt: '2024-01-15T12:00:00.000Z',
|
||||
},
|
||||
],
|
||||
total: 1,
|
||||
page: 1,
|
||||
limit: 10,
|
||||
totalPages: 1,
|
||||
};
|
||||
|
||||
const result = AdminUsersViewDataBuilder.build(userListResponse);
|
||||
|
||||
expect(result.users[0].displayName).toBe('User 1 & 2 (Admin)');
|
||||
});
|
||||
|
||||
it('should handle users with very long email addresses', () => {
|
||||
const longEmail = 'verylongemailaddresswithmanycharacters@example.com';
|
||||
const userListResponse: UserListResponse = {
|
||||
users: [
|
||||
{
|
||||
id: 'user-1',
|
||||
email: longEmail,
|
||||
displayName: 'User 1',
|
||||
roles: ['member'],
|
||||
status: 'active',
|
||||
isSystemAdmin: false,
|
||||
createdAt: '2024-01-01T00:00:00.000Z',
|
||||
updatedAt: '2024-01-15T12:00:00.000Z',
|
||||
},
|
||||
],
|
||||
total: 1,
|
||||
page: 1,
|
||||
limit: 10,
|
||||
totalPages: 1,
|
||||
};
|
||||
|
||||
const result = AdminUsersViewDataBuilder.build(userListResponse);
|
||||
|
||||
expect(result.users[0].email).toBe(longEmail);
|
||||
});
|
||||
});
|
||||
|
||||
describe('derived fields calculation', () => {
|
||||
it('should calculate activeUserCount correctly with mixed statuses', () => {
|
||||
const userListResponse: UserListResponse = {
|
||||
users: [
|
||||
{ id: '1', email: '1@e.com', displayName: '1', roles: ['member'], status: 'active', isSystemAdmin: false, createdAt: '2024-01-01T00:00:00.000Z', updatedAt: '2024-01-15T12:00:00.000Z' },
|
||||
{ id: '2', email: '2@e.com', displayName: '2', roles: ['member'], status: 'active', isSystemAdmin: false, createdAt: '2024-01-01T00:00:00.000Z', updatedAt: '2024-01-15T12:00:00.000Z' },
|
||||
{ id: '3', email: '3@e.com', displayName: '3', roles: ['member'], status: 'suspended', isSystemAdmin: false, createdAt: '2024-01-01T00:00:00.000Z', updatedAt: '2024-01-15T12:00:00.000Z' },
|
||||
{ id: '4', email: '4@e.com', displayName: '4', roles: ['member'], status: 'deleted', isSystemAdmin: false, createdAt: '2024-01-01T00:00:00.000Z', updatedAt: '2024-01-15T12:00:00.000Z' },
|
||||
],
|
||||
total: 4,
|
||||
page: 1,
|
||||
limit: 10,
|
||||
totalPages: 1,
|
||||
};
|
||||
|
||||
const result = AdminUsersViewDataBuilder.build(userListResponse);
|
||||
|
||||
expect(result.activeUserCount).toBe(2);
|
||||
});
|
||||
|
||||
it('should calculate adminCount correctly with mixed roles', () => {
|
||||
const userListResponse: UserListResponse = {
|
||||
users: [
|
||||
{ id: '1', email: '1@e.com', displayName: '1', roles: ['admin'], status: 'active', isSystemAdmin: true, createdAt: '2024-01-01T00:00:00.000Z', updatedAt: '2024-01-15T12:00:00.000Z' },
|
||||
{ id: '2', email: '2@e.com', displayName: '2', roles: ['admin', 'owner'], status: 'active', isSystemAdmin: true, createdAt: '2024-01-01T00:00:00.000Z', updatedAt: '2024-01-15T12:00:00.000Z' },
|
||||
{ id: '3', email: '3@e.com', displayName: '3', roles: ['member'], status: 'active', isSystemAdmin: false, createdAt: '2024-01-01T00:00:00.000Z', updatedAt: '2024-01-15T12:00:00.000Z' },
|
||||
{ id: '4', email: '4@e.com', displayName: '4', roles: ['owner'], status: 'active', isSystemAdmin: false, createdAt: '2024-01-01T00:00:00.000Z', updatedAt: '2024-01-15T12:00:00.000Z' },
|
||||
],
|
||||
total: 4,
|
||||
page: 1,
|
||||
limit: 10,
|
||||
totalPages: 1,
|
||||
};
|
||||
|
||||
const result = AdminUsersViewDataBuilder.build(userListResponse);
|
||||
|
||||
expect(result.adminCount).toBe(2);
|
||||
});
|
||||
|
||||
it('should handle all active users', () => {
|
||||
const userListResponse: UserListResponse = {
|
||||
users: [
|
||||
{ id: '1', email: '1@e.com', displayName: '1', roles: ['member'], status: 'active', isSystemAdmin: false, createdAt: '2024-01-01T00:00:00.000Z', updatedAt: '2024-01-15T12:00:00.000Z' },
|
||||
{ id: '2', email: '2@e.com', displayName: '2', roles: ['member'], status: 'active', isSystemAdmin: false, createdAt: '2024-01-01T00:00:00.000Z', updatedAt: '2024-01-15T12:00:00.000Z' },
|
||||
{ id: '3', email: '3@e.com', displayName: '3', roles: ['member'], status: 'active', isSystemAdmin: false, createdAt: '2024-01-01T00:00:00.000Z', updatedAt: '2024-01-15T12:00:00.000Z' },
|
||||
],
|
||||
total: 3,
|
||||
page: 1,
|
||||
limit: 10,
|
||||
totalPages: 1,
|
||||
};
|
||||
|
||||
const result = AdminUsersViewDataBuilder.build(userListResponse);
|
||||
|
||||
expect(result.activeUserCount).toBe(3);
|
||||
});
|
||||
|
||||
it('should handle no active users', () => {
|
||||
const userListResponse: UserListResponse = {
|
||||
users: [
|
||||
{ id: '1', email: '1@e.com', displayName: '1', roles: ['member'], status: 'suspended', isSystemAdmin: false, createdAt: '2024-01-01T00:00:00.000Z', updatedAt: '2024-01-15T12:00:00.000Z' },
|
||||
{ id: '2', email: '2@e.com', displayName: '2', roles: ['member'], status: 'deleted', isSystemAdmin: false, createdAt: '2024-01-01T00:00:00.000Z', updatedAt: '2024-01-15T12:00:00.000Z' },
|
||||
],
|
||||
total: 2,
|
||||
page: 1,
|
||||
limit: 10,
|
||||
totalPages: 1,
|
||||
};
|
||||
|
||||
const result = AdminUsersViewDataBuilder.build(userListResponse);
|
||||
|
||||
expect(result.activeUserCount).toBe(0);
|
||||
});
|
||||
|
||||
it('should handle all system admins', () => {
|
||||
const userListResponse: UserListResponse = {
|
||||
users: [
|
||||
{ id: '1', email: '1@e.com', displayName: '1', roles: ['admin'], status: 'active', isSystemAdmin: true, createdAt: '2024-01-01T00:00:00.000Z', updatedAt: '2024-01-15T12:00:00.000Z' },
|
||||
{ id: '2', email: '2@e.com', displayName: '2', roles: ['admin'], status: 'active', isSystemAdmin: true, createdAt: '2024-01-01T00:00:00.000Z', updatedAt: '2024-01-15T12:00:00.000Z' },
|
||||
{ id: '3', email: '3@e.com', displayName: '3', roles: ['admin'], status: 'active', isSystemAdmin: true, createdAt: '2024-01-01T00:00:00.000Z', updatedAt: '2024-01-15T12:00:00.000Z' },
|
||||
],
|
||||
total: 3,
|
||||
page: 1,
|
||||
limit: 10,
|
||||
totalPages: 1,
|
||||
};
|
||||
|
||||
const result = AdminUsersViewDataBuilder.build(userListResponse);
|
||||
|
||||
expect(result.adminCount).toBe(3);
|
||||
});
|
||||
|
||||
it('should handle no system admins', () => {
|
||||
const userListResponse: UserListResponse = {
|
||||
users: [
|
||||
{ id: '1', email: '1@e.com', displayName: '1', roles: ['member'], status: 'active', isSystemAdmin: false, createdAt: '2024-01-01T00:00:00.000Z', updatedAt: '2024-01-15T12:00:00.000Z' },
|
||||
{ id: '2', email: '2@e.com', displayName: '2', roles: ['owner'], status: 'active', isSystemAdmin: false, createdAt: '2024-01-01T00:00:00.000Z', updatedAt: '2024-01-15T12:00:00.000Z' },
|
||||
],
|
||||
total: 2,
|
||||
page: 1,
|
||||
limit: 10,
|
||||
totalPages: 1,
|
||||
};
|
||||
|
||||
const result = AdminUsersViewDataBuilder.build(userListResponse);
|
||||
|
||||
expect(result.adminCount).toBe(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,472 +0,0 @@
|
||||
/**
|
||||
* 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');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,26 +0,0 @@
|
||||
/**
|
||||
* View Data Layer Tests - Profile Functionality
|
||||
*
|
||||
* This test file will cover the view data layer for profile 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 will include:
|
||||
* - Driver profile data transformation and formatting
|
||||
* - Profile statistics (rating, rank, race counts, finishes, consistency, etc.)
|
||||
* - Team membership data mapping and role labeling
|
||||
* - Extended profile data (timezone, racing style, favorite track/car, etc.)
|
||||
* - Social handles formatting and URL generation
|
||||
* - Achievement data transformation and icon mapping
|
||||
* - Friends list data mapping and display formatting
|
||||
* - Derived fields (percentile, consistency, looking for team, open to requests)
|
||||
* - Default values and fallbacks for profile views
|
||||
* - Profile-specific formatting (country flags, date labels, etc.)
|
||||
*/
|
||||
@@ -1,29 +0,0 @@
|
||||
/**
|
||||
* View Data Layer Tests - Races Functionality
|
||||
*
|
||||
* This test file will cover the view data layer for races 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 will include:
|
||||
* - Race list data transformation and sorting
|
||||
* - Individual race page view models (race details, schedule, participants)
|
||||
* - Race results data formatting and ranking calculations
|
||||
* - Stewarding data transformation (protests, penalties, incidents)
|
||||
* - All races page data aggregation and filtering
|
||||
* - Derived race fields (status, eligibility, availability, etc.)
|
||||
* - Default values and fallbacks for race views
|
||||
* - Race-specific formatting (lap times, gaps, points, positions, etc.)
|
||||
* - Data grouping and categorization for race components (by series, date, type)
|
||||
* - Race search and filtering view models
|
||||
* - Real-time race updates and state management
|
||||
* - Historical race data transformation
|
||||
* - Race registration and withdrawal data handling
|
||||
*/
|
||||
@@ -1,29 +0,0 @@
|
||||
/**
|
||||
* View Data Layer Tests - Sponsor Functionality
|
||||
*
|
||||
* This test file will cover the view data layer for sponsor 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 will include:
|
||||
* - Sponsor dashboard data transformation and metrics
|
||||
* - Sponsor billing and payment view models
|
||||
* - Campaign management data formatting and status tracking
|
||||
* - League sponsorship data aggregation and tier calculations
|
||||
* - Sponsor settings and configuration view models
|
||||
* - Sponsor signup and onboarding data handling
|
||||
* - Derived sponsor fields (engagement metrics, ROI calculations, etc.)
|
||||
* - Default values and fallbacks for sponsor views
|
||||
* - Sponsor-specific formatting (budgets, impressions, clicks, conversions)
|
||||
* - Data grouping and categorization for sponsor components (by campaign, league, status)
|
||||
* - Sponsor search and filtering view models
|
||||
* - Real-time sponsor metrics and state management
|
||||
* - Historical sponsor performance data transformation
|
||||
*/
|
||||
@@ -1,28 +0,0 @@
|
||||
/**
|
||||
* View Data Layer Tests - Teams Functionality
|
||||
*
|
||||
* This test file will cover the view data layer for teams 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 will include:
|
||||
* - Team list data transformation and sorting
|
||||
* - Individual team profile view models
|
||||
* - Team creation form data handling
|
||||
* - Team leaderboard data transformation
|
||||
* - Team statistics and metrics formatting
|
||||
* - Derived team fields (performance ratings, rankings, etc.)
|
||||
* - Default values and fallbacks for team views
|
||||
* - Team-specific formatting (points, positions, member counts, etc.)
|
||||
* - Data grouping and categorization for team components
|
||||
* - Team search and filtering view models
|
||||
* - Team member data transformation
|
||||
* - Team comparison data transformation
|
||||
*/
|
||||
Reference in New Issue
Block a user