Files
smoothschedule/frontend/src/api/__tests__/platform.test.ts
poduck 8dc2248f1f feat: Add comprehensive test suite and misc improvements
- Add frontend unit tests with Vitest for components, hooks, pages, and utilities
- Add backend tests for webhooks, notifications, middleware, and edge cases
- Add ForgotPassword, NotFound, and ResetPassword pages
- Add migration for orphaned staff resources conversion
- Add coverage directory to gitignore (generated reports)
- Various bug fixes and improvements from previous work

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-08 02:36:46 -05:00

990 lines
30 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from 'vitest';
// Mock apiClient
vi.mock('../client', () => ({
default: {
get: vi.fn(),
post: vi.fn(),
patch: vi.fn(),
delete: vi.fn(),
},
}));
import {
getBusinesses,
updateBusiness,
createBusiness,
deleteBusiness,
getUsers,
getBusinessUsers,
verifyUserEmail,
getTenantInvitations,
createTenantInvitation,
resendTenantInvitation,
cancelTenantInvitation,
getInvitationByToken,
acceptInvitation,
type PlatformBusiness,
type PlatformBusinessUpdate,
type PlatformBusinessCreate,
type PlatformUser,
type TenantInvitation,
type TenantInvitationCreate,
type TenantInvitationDetail,
type TenantInvitationAccept,
} from '../platform';
import apiClient from '../client';
describe('platform API', () => {
beforeEach(() => {
vi.clearAllMocks();
});
// ============================================================================
// Business Management
// ============================================================================
describe('getBusinesses', () => {
it('fetches all businesses from API', async () => {
const mockBusinesses: PlatformBusiness[] = [
{
id: 1,
name: 'Acme Corp',
subdomain: 'acme',
tier: 'PROFESSIONAL',
is_active: true,
created_on: '2025-01-01T00:00:00Z',
user_count: 5,
owner: {
id: 10,
username: 'john',
full_name: 'John Doe',
email: 'john@acme.com',
role: 'owner',
email_verified: true,
},
max_users: 20,
max_resources: 50,
contact_email: 'contact@acme.com',
phone: '555-1234',
can_manage_oauth_credentials: true,
can_accept_payments: true,
can_use_custom_domain: false,
can_white_label: false,
can_api_access: true,
},
{
id: 2,
name: 'Beta LLC',
subdomain: 'beta',
tier: 'STARTER',
is_active: true,
created_on: '2025-01-02T00:00:00Z',
user_count: 2,
owner: null,
max_users: 5,
max_resources: 10,
can_manage_oauth_credentials: false,
can_accept_payments: false,
can_use_custom_domain: false,
can_white_label: false,
can_api_access: false,
},
];
vi.mocked(apiClient.get).mockResolvedValue({ data: mockBusinesses });
const result = await getBusinesses();
expect(apiClient.get).toHaveBeenCalledWith('/platform/businesses/');
expect(result).toEqual(mockBusinesses);
expect(result).toHaveLength(2);
expect(result[0].name).toBe('Acme Corp');
expect(result[1].owner).toBeNull();
});
it('returns empty array when no businesses exist', async () => {
vi.mocked(apiClient.get).mockResolvedValue({ data: [] });
const result = await getBusinesses();
expect(result).toEqual([]);
});
});
describe('updateBusiness', () => {
it('updates a business with full data', async () => {
const businessId = 1;
const updateData: PlatformBusinessUpdate = {
name: 'Updated Name',
is_active: false,
subscription_tier: 'ENTERPRISE',
max_users: 100,
max_resources: 500,
can_manage_oauth_credentials: true,
can_accept_payments: true,
can_use_custom_domain: true,
can_white_label: true,
can_api_access: true,
};
const mockResponse: PlatformBusiness = {
id: 1,
name: 'Updated Name',
subdomain: 'acme',
tier: 'ENTERPRISE',
is_active: false,
created_on: '2025-01-01T00:00:00Z',
user_count: 5,
owner: null,
max_users: 100,
max_resources: 500,
can_manage_oauth_credentials: true,
can_accept_payments: true,
can_use_custom_domain: true,
can_white_label: true,
can_api_access: true,
};
vi.mocked(apiClient.patch).mockResolvedValue({ data: mockResponse });
const result = await updateBusiness(businessId, updateData);
expect(apiClient.patch).toHaveBeenCalledWith(
'/platform/businesses/1/',
updateData
);
expect(result).toEqual(mockResponse);
expect(result.name).toBe('Updated Name');
expect(result.is_active).toBe(false);
});
it('updates a business with partial data', async () => {
const businessId = 2;
const updateData: PlatformBusinessUpdate = {
is_active: true,
};
const mockResponse: PlatformBusiness = {
id: 2,
name: 'Beta LLC',
subdomain: 'beta',
tier: 'STARTER',
is_active: true,
created_on: '2025-01-02T00:00:00Z',
user_count: 2,
owner: null,
max_users: 5,
max_resources: 10,
can_manage_oauth_credentials: false,
can_accept_payments: false,
can_use_custom_domain: false,
can_white_label: false,
can_api_access: false,
};
vi.mocked(apiClient.patch).mockResolvedValue({ data: mockResponse });
const result = await updateBusiness(businessId, updateData);
expect(apiClient.patch).toHaveBeenCalledWith(
'/platform/businesses/2/',
updateData
);
expect(result.is_active).toBe(true);
});
it('updates only specific permissions', async () => {
const businessId = 3;
const updateData: PlatformBusinessUpdate = {
can_accept_payments: true,
can_api_access: true,
};
const mockResponse: PlatformBusiness = {
id: 3,
name: 'Gamma Inc',
subdomain: 'gamma',
tier: 'PROFESSIONAL',
is_active: true,
created_on: '2025-01-03T00:00:00Z',
user_count: 10,
owner: null,
max_users: 20,
max_resources: 50,
can_manage_oauth_credentials: false,
can_accept_payments: true,
can_use_custom_domain: false,
can_white_label: false,
can_api_access: true,
};
vi.mocked(apiClient.patch).mockResolvedValue({ data: mockResponse });
await updateBusiness(businessId, updateData);
expect(apiClient.patch).toHaveBeenCalledWith(
'/platform/businesses/3/',
updateData
);
});
});
describe('createBusiness', () => {
it('creates a business with minimal data', async () => {
const createData: PlatformBusinessCreate = {
name: 'New Business',
subdomain: 'newbiz',
};
const mockResponse: PlatformBusiness = {
id: 10,
name: 'New Business',
subdomain: 'newbiz',
tier: 'FREE',
is_active: true,
created_on: '2025-01-15T00:00:00Z',
user_count: 0,
owner: null,
max_users: 3,
max_resources: 5,
can_manage_oauth_credentials: false,
can_accept_payments: false,
can_use_custom_domain: false,
can_white_label: false,
can_api_access: false,
};
vi.mocked(apiClient.post).mockResolvedValue({ data: mockResponse });
const result = await createBusiness(createData);
expect(apiClient.post).toHaveBeenCalledWith(
'/platform/businesses/',
createData
);
expect(result).toEqual(mockResponse);
expect(result.id).toBe(10);
expect(result.subdomain).toBe('newbiz');
});
it('creates a business with full data including owner', async () => {
const createData: PlatformBusinessCreate = {
name: 'Premium Business',
subdomain: 'premium',
subscription_tier: 'ENTERPRISE',
is_active: true,
max_users: 100,
max_resources: 500,
contact_email: 'contact@premium.com',
phone: '555-9999',
can_manage_oauth_credentials: true,
owner_email: 'owner@premium.com',
owner_name: 'Jane Smith',
owner_password: 'secure-password',
};
const mockResponse: PlatformBusiness = {
id: 11,
name: 'Premium Business',
subdomain: 'premium',
tier: 'ENTERPRISE',
is_active: true,
created_on: '2025-01-15T10:00:00Z',
user_count: 1,
owner: {
id: 20,
username: 'owner@premium.com',
full_name: 'Jane Smith',
email: 'owner@premium.com',
role: 'owner',
email_verified: false,
},
max_users: 100,
max_resources: 500,
contact_email: 'contact@premium.com',
phone: '555-9999',
can_manage_oauth_credentials: true,
can_accept_payments: true,
can_use_custom_domain: true,
can_white_label: true,
can_api_access: true,
};
vi.mocked(apiClient.post).mockResolvedValue({ data: mockResponse });
const result = await createBusiness(createData);
expect(apiClient.post).toHaveBeenCalledWith(
'/platform/businesses/',
createData
);
expect(result.owner).not.toBeNull();
expect(result.owner?.email).toBe('owner@premium.com');
});
it('creates a business with custom tier and limits', async () => {
const createData: PlatformBusinessCreate = {
name: 'Custom Business',
subdomain: 'custom',
subscription_tier: 'PROFESSIONAL',
max_users: 50,
max_resources: 100,
};
const mockResponse: PlatformBusiness = {
id: 12,
name: 'Custom Business',
subdomain: 'custom',
tier: 'PROFESSIONAL',
is_active: true,
created_on: '2025-01-15T12:00:00Z',
user_count: 0,
owner: null,
max_users: 50,
max_resources: 100,
can_manage_oauth_credentials: true,
can_accept_payments: true,
can_use_custom_domain: false,
can_white_label: false,
can_api_access: true,
};
vi.mocked(apiClient.post).mockResolvedValue({ data: mockResponse });
const result = await createBusiness(createData);
expect(result.max_users).toBe(50);
expect(result.max_resources).toBe(100);
});
});
describe('deleteBusiness', () => {
it('deletes a business by ID', async () => {
const businessId = 5;
vi.mocked(apiClient.delete).mockResolvedValue({});
await deleteBusiness(businessId);
expect(apiClient.delete).toHaveBeenCalledWith('/platform/businesses/5/');
});
it('handles deletion with no response data', async () => {
const businessId = 10;
vi.mocked(apiClient.delete).mockResolvedValue({ data: undefined });
const result = await deleteBusiness(businessId);
expect(apiClient.delete).toHaveBeenCalledWith('/platform/businesses/10/');
expect(result).toBeUndefined();
});
});
// ============================================================================
// User Management
// ============================================================================
describe('getUsers', () => {
it('fetches all users from API', async () => {
const mockUsers: PlatformUser[] = [
{
id: 1,
email: 'admin@platform.com',
username: 'admin',
name: 'Platform Admin',
role: 'superuser',
is_active: true,
is_staff: true,
is_superuser: true,
email_verified: true,
business: null,
date_joined: '2024-01-01T00:00:00Z',
last_login: '2025-01-15T10:00:00Z',
},
{
id: 2,
email: 'user@acme.com',
username: 'user1',
name: 'Acme User',
role: 'staff',
is_active: true,
is_staff: false,
is_superuser: false,
email_verified: true,
business: 1,
business_name: 'Acme Corp',
business_subdomain: 'acme',
date_joined: '2024-06-01T00:00:00Z',
last_login: '2025-01-14T15:30:00Z',
},
{
id: 3,
email: 'inactive@example.com',
username: 'inactive',
is_active: false,
is_staff: false,
is_superuser: false,
email_verified: false,
business: null,
date_joined: '2024-03-15T00:00:00Z',
},
];
vi.mocked(apiClient.get).mockResolvedValue({ data: mockUsers });
const result = await getUsers();
expect(apiClient.get).toHaveBeenCalledWith('/platform/users/');
expect(result).toEqual(mockUsers);
expect(result).toHaveLength(3);
expect(result[0].is_superuser).toBe(true);
expect(result[1].business_name).toBe('Acme Corp');
expect(result[2].is_active).toBe(false);
});
it('returns empty array when no users exist', async () => {
vi.mocked(apiClient.get).mockResolvedValue({ data: [] });
const result = await getUsers();
expect(result).toEqual([]);
});
});
describe('getBusinessUsers', () => {
it('fetches users for a specific business', async () => {
const businessId = 1;
const mockUsers: PlatformUser[] = [
{
id: 10,
email: 'owner@acme.com',
username: 'owner',
name: 'John Doe',
role: 'owner',
is_active: true,
is_staff: false,
is_superuser: false,
email_verified: true,
business: 1,
business_name: 'Acme Corp',
business_subdomain: 'acme',
date_joined: '2024-01-01T00:00:00Z',
last_login: '2025-01-15T09:00:00Z',
},
{
id: 11,
email: 'staff@acme.com',
username: 'staff1',
name: 'Jane Smith',
role: 'staff',
is_active: true,
is_staff: false,
is_superuser: false,
email_verified: true,
business: 1,
business_name: 'Acme Corp',
business_subdomain: 'acme',
date_joined: '2024-03-01T00:00:00Z',
last_login: '2025-01-14T16:00:00Z',
},
];
vi.mocked(apiClient.get).mockResolvedValue({ data: mockUsers });
const result = await getBusinessUsers(businessId);
expect(apiClient.get).toHaveBeenCalledWith('/platform/users/?business=1');
expect(result).toEqual(mockUsers);
expect(result).toHaveLength(2);
expect(result.every(u => u.business === 1)).toBe(true);
});
it('returns empty array when business has no users', async () => {
const businessId = 99;
vi.mocked(apiClient.get).mockResolvedValue({ data: [] });
const result = await getBusinessUsers(businessId);
expect(apiClient.get).toHaveBeenCalledWith('/platform/users/?business=99');
expect(result).toEqual([]);
});
it('handles different business IDs correctly', async () => {
const businessId = 5;
vi.mocked(apiClient.get).mockResolvedValue({ data: [] });
await getBusinessUsers(businessId);
expect(apiClient.get).toHaveBeenCalledWith('/platform/users/?business=5');
});
});
describe('verifyUserEmail', () => {
it('verifies a user email by ID', async () => {
const userId = 10;
vi.mocked(apiClient.post).mockResolvedValue({});
await verifyUserEmail(userId);
expect(apiClient.post).toHaveBeenCalledWith('/platform/users/10/verify_email/');
});
it('handles verification with no response data', async () => {
const userId = 25;
vi.mocked(apiClient.post).mockResolvedValue({ data: undefined });
const result = await verifyUserEmail(userId);
expect(apiClient.post).toHaveBeenCalledWith('/platform/users/25/verify_email/');
expect(result).toBeUndefined();
});
});
// ============================================================================
// Tenant Invitations
// ============================================================================
describe('getTenantInvitations', () => {
it('fetches all tenant invitations from API', async () => {
const mockInvitations: TenantInvitation[] = [
{
id: 1,
email: 'newclient@example.com',
token: 'abc123token',
status: 'PENDING',
suggested_business_name: 'New Client Corp',
subscription_tier: 'PROFESSIONAL',
custom_max_users: 50,
custom_max_resources: 100,
permissions: {
can_manage_oauth_credentials: true,
can_accept_payments: true,
can_use_custom_domain: false,
can_white_label: false,
can_api_access: true,
},
personal_message: 'Welcome to our platform!',
invited_by: 1,
invited_by_email: 'admin@platform.com',
created_at: '2025-01-10T10:00:00Z',
expires_at: '2025-01-24T10:00:00Z',
accepted_at: null,
created_tenant: null,
created_tenant_name: null,
created_user: null,
created_user_email: null,
},
{
id: 2,
email: 'accepted@example.com',
token: 'xyz789token',
status: 'ACCEPTED',
suggested_business_name: 'Accepted Business',
subscription_tier: 'STARTER',
custom_max_users: null,
custom_max_resources: null,
permissions: {},
personal_message: '',
invited_by: 1,
invited_by_email: 'admin@platform.com',
created_at: '2025-01-05T08:00:00Z',
expires_at: '2025-01-19T08:00:00Z',
accepted_at: '2025-01-06T12:00:00Z',
created_tenant: 5,
created_tenant_name: 'Accepted Business',
created_user: 15,
created_user_email: 'accepted@example.com',
},
];
vi.mocked(apiClient.get).mockResolvedValue({ data: mockInvitations });
const result = await getTenantInvitations();
expect(apiClient.get).toHaveBeenCalledWith('/platform/tenant-invitations/');
expect(result).toEqual(mockInvitations);
expect(result).toHaveLength(2);
expect(result[0].status).toBe('PENDING');
expect(result[1].status).toBe('ACCEPTED');
});
it('returns empty array when no invitations exist', async () => {
vi.mocked(apiClient.get).mockResolvedValue({ data: [] });
const result = await getTenantInvitations();
expect(result).toEqual([]);
});
});
describe('createTenantInvitation', () => {
it('creates a tenant invitation with minimal data', async () => {
const createData: TenantInvitationCreate = {
email: 'client@example.com',
subscription_tier: 'STARTER',
};
const mockResponse: TenantInvitation = {
id: 10,
email: 'client@example.com',
token: 'generated-token-123',
status: 'PENDING',
suggested_business_name: '',
subscription_tier: 'STARTER',
custom_max_users: null,
custom_max_resources: null,
permissions: {},
personal_message: '',
invited_by: 1,
invited_by_email: 'admin@platform.com',
created_at: '2025-01-15T14:00:00Z',
expires_at: '2025-01-29T14:00:00Z',
accepted_at: null,
created_tenant: null,
created_tenant_name: null,
created_user: null,
created_user_email: null,
};
vi.mocked(apiClient.post).mockResolvedValue({ data: mockResponse });
const result = await createTenantInvitation(createData);
expect(apiClient.post).toHaveBeenCalledWith(
'/platform/tenant-invitations/',
createData
);
expect(result).toEqual(mockResponse);
expect(result.email).toBe('client@example.com');
expect(result.status).toBe('PENDING');
});
it('creates a tenant invitation with full data', async () => {
const createData: TenantInvitationCreate = {
email: 'vip@example.com',
suggested_business_name: 'VIP Corp',
subscription_tier: 'ENTERPRISE',
custom_max_users: 500,
custom_max_resources: 1000,
permissions: {
can_manage_oauth_credentials: true,
can_accept_payments: true,
can_use_custom_domain: true,
can_white_label: true,
can_api_access: true,
},
personal_message: 'Welcome to our premium tier!',
};
const mockResponse: TenantInvitation = {
id: 11,
email: 'vip@example.com',
token: 'vip-token-456',
status: 'PENDING',
suggested_business_name: 'VIP Corp',
subscription_tier: 'ENTERPRISE',
custom_max_users: 500,
custom_max_resources: 1000,
permissions: {
can_manage_oauth_credentials: true,
can_accept_payments: true,
can_use_custom_domain: true,
can_white_label: true,
can_api_access: true,
},
personal_message: 'Welcome to our premium tier!',
invited_by: 1,
invited_by_email: 'admin@platform.com',
created_at: '2025-01-15T15:00:00Z',
expires_at: '2025-01-29T15:00:00Z',
accepted_at: null,
created_tenant: null,
created_tenant_name: null,
created_user: null,
created_user_email: null,
};
vi.mocked(apiClient.post).mockResolvedValue({ data: mockResponse });
const result = await createTenantInvitation(createData);
expect(apiClient.post).toHaveBeenCalledWith(
'/platform/tenant-invitations/',
createData
);
expect(result.suggested_business_name).toBe('VIP Corp');
expect(result.custom_max_users).toBe(500);
expect(result.permissions.can_white_label).toBe(true);
});
it('creates invitation with partial permissions', async () => {
const createData: TenantInvitationCreate = {
email: 'partial@example.com',
subscription_tier: 'PROFESSIONAL',
permissions: {
can_accept_payments: true,
},
};
const mockResponse: TenantInvitation = {
id: 12,
email: 'partial@example.com',
token: 'partial-token',
status: 'PENDING',
suggested_business_name: '',
subscription_tier: 'PROFESSIONAL',
custom_max_users: null,
custom_max_resources: null,
permissions: {
can_accept_payments: true,
},
personal_message: '',
invited_by: 1,
invited_by_email: 'admin@platform.com',
created_at: '2025-01-15T16:00:00Z',
expires_at: '2025-01-29T16:00:00Z',
accepted_at: null,
created_tenant: null,
created_tenant_name: null,
created_user: null,
created_user_email: null,
};
vi.mocked(apiClient.post).mockResolvedValue({ data: mockResponse });
const result = await createTenantInvitation(createData);
expect(result.permissions.can_accept_payments).toBe(true);
});
});
describe('resendTenantInvitation', () => {
it('resends a tenant invitation by ID', async () => {
const invitationId = 5;
vi.mocked(apiClient.post).mockResolvedValue({});
await resendTenantInvitation(invitationId);
expect(apiClient.post).toHaveBeenCalledWith(
'/platform/tenant-invitations/5/resend/'
);
});
it('handles resend with no response data', async () => {
const invitationId = 10;
vi.mocked(apiClient.post).mockResolvedValue({ data: undefined });
const result = await resendTenantInvitation(invitationId);
expect(apiClient.post).toHaveBeenCalledWith(
'/platform/tenant-invitations/10/resend/'
);
expect(result).toBeUndefined();
});
});
describe('cancelTenantInvitation', () => {
it('cancels a tenant invitation by ID', async () => {
const invitationId = 7;
vi.mocked(apiClient.post).mockResolvedValue({});
await cancelTenantInvitation(invitationId);
expect(apiClient.post).toHaveBeenCalledWith(
'/platform/tenant-invitations/7/cancel/'
);
});
it('handles cancellation with no response data', async () => {
const invitationId = 15;
vi.mocked(apiClient.post).mockResolvedValue({ data: undefined });
const result = await cancelTenantInvitation(invitationId);
expect(apiClient.post).toHaveBeenCalledWith(
'/platform/tenant-invitations/15/cancel/'
);
expect(result).toBeUndefined();
});
});
describe('getInvitationByToken', () => {
it('fetches invitation details by token', async () => {
const token = 'abc123token';
const mockInvitation: TenantInvitationDetail = {
email: 'invited@example.com',
suggested_business_name: 'Invited Corp',
subscription_tier: 'PROFESSIONAL',
effective_max_users: 20,
effective_max_resources: 50,
permissions: {
can_manage_oauth_credentials: true,
can_accept_payments: true,
can_use_custom_domain: false,
can_white_label: false,
can_api_access: true,
},
expires_at: '2025-01-30T12:00:00Z',
};
vi.mocked(apiClient.get).mockResolvedValue({ data: mockInvitation });
const result = await getInvitationByToken(token);
expect(apiClient.get).toHaveBeenCalledWith(
'/platform/tenant-invitations/token/abc123token/'
);
expect(result).toEqual(mockInvitation);
expect(result.email).toBe('invited@example.com');
expect(result.effective_max_users).toBe(20);
});
it('handles tokens with special characters', async () => {
const token = 'token-with-dashes_and_underscores';
const mockInvitation: TenantInvitationDetail = {
email: 'test@example.com',
suggested_business_name: 'Test',
subscription_tier: 'FREE',
effective_max_users: 3,
effective_max_resources: 5,
permissions: {},
expires_at: '2025-02-01T00:00:00Z',
};
vi.mocked(apiClient.get).mockResolvedValue({ data: mockInvitation });
await getInvitationByToken(token);
expect(apiClient.get).toHaveBeenCalledWith(
'/platform/tenant-invitations/token/token-with-dashes_and_underscores/'
);
});
it('fetches invitation with custom limits', async () => {
const token = 'custom-limits-token';
const mockInvitation: TenantInvitationDetail = {
email: 'custom@example.com',
suggested_business_name: 'Custom Business',
subscription_tier: 'ENTERPRISE',
effective_max_users: 1000,
effective_max_resources: 5000,
permissions: {
can_manage_oauth_credentials: true,
can_accept_payments: true,
can_use_custom_domain: true,
can_white_label: true,
can_api_access: true,
},
expires_at: '2025-03-01T12:00:00Z',
};
vi.mocked(apiClient.get).mockResolvedValue({ data: mockInvitation });
const result = await getInvitationByToken(token);
expect(result.effective_max_users).toBe(1000);
expect(result.effective_max_resources).toBe(5000);
});
});
describe('acceptInvitation', () => {
it('accepts an invitation with full data', async () => {
const token = 'accept-token-123';
const acceptData: TenantInvitationAccept = {
email: 'newowner@example.com',
password: 'secure-password',
first_name: 'John',
last_name: 'Doe',
business_name: 'New Business LLC',
subdomain: 'newbiz',
contact_email: 'contact@newbiz.com',
phone: '555-1234',
};
const mockResponse = {
detail: 'Invitation accepted successfully. Your account has been created.',
};
vi.mocked(apiClient.post).mockResolvedValue({ data: mockResponse });
const result = await acceptInvitation(token, acceptData);
expect(apiClient.post).toHaveBeenCalledWith(
'/platform/tenant-invitations/token/accept-token-123/accept/',
acceptData
);
expect(result).toEqual(mockResponse);
expect(result.detail).toContain('successfully');
});
it('accepts an invitation with minimal data', async () => {
const token = 'minimal-token';
const acceptData: TenantInvitationAccept = {
email: 'minimal@example.com',
password: 'password123',
first_name: 'Jane',
last_name: 'Smith',
business_name: 'Minimal Business',
subdomain: 'minimal',
};
const mockResponse = {
detail: 'Account created successfully.',
};
vi.mocked(apiClient.post).mockResolvedValue({ data: mockResponse });
const result = await acceptInvitation(token, acceptData);
expect(apiClient.post).toHaveBeenCalledWith(
'/platform/tenant-invitations/token/minimal-token/accept/',
acceptData
);
expect(result.detail).toBe('Account created successfully.');
});
it('handles acceptance with optional contact fields', async () => {
const token = 'optional-fields-token';
const acceptData: TenantInvitationAccept = {
email: 'test@example.com',
password: 'testpass',
first_name: 'Test',
last_name: 'User',
business_name: 'Test Business',
subdomain: 'testbiz',
contact_email: 'info@testbiz.com',
};
const mockResponse = {
detail: 'Welcome to the platform!',
};
vi.mocked(apiClient.post).mockResolvedValue({ data: mockResponse });
await acceptInvitation(token, acceptData);
expect(apiClient.post).toHaveBeenCalledWith(
'/platform/tenant-invitations/token/optional-fields-token/accept/',
expect.objectContaining({
contact_email: 'info@testbiz.com',
})
);
});
it('preserves all required fields in request', async () => {
const token = 'complete-token';
const acceptData: TenantInvitationAccept = {
email: 'complete@example.com',
password: 'strong-password-123',
first_name: 'Complete',
last_name: 'User',
business_name: 'Complete Business Corp',
subdomain: 'complete',
contact_email: 'support@complete.com',
phone: '555-9876',
};
vi.mocked(apiClient.post).mockResolvedValue({
data: { detail: 'Success' },
});
await acceptInvitation(token, acceptData);
expect(apiClient.post).toHaveBeenCalledWith(
'/platform/tenant-invitations/token/complete-token/accept/',
expect.objectContaining({
email: 'complete@example.com',
password: 'strong-password-123',
first_name: 'Complete',
last_name: 'User',
business_name: 'Complete Business Corp',
subdomain: 'complete',
contact_email: 'support@complete.com',
phone: '555-9876',
})
);
});
});
});