UNPKG

@memberjunction/actions-bizapps-lms

Version:

LMS system integration actions for MemberJunction

284 lines (238 loc) 10.4 kB
import { describe, it, expect, vi, beforeEach } from 'vitest'; // Mock dependencies (same pattern as lms.test.ts) vi.mock('@memberjunction/actions', () => ({ BaseAction: class BaseAction { protected async InternalRunAction(): Promise<unknown> { return {}; } }, })); vi.mock('@memberjunction/global', () => ({ RegisterClass: () => (target: unknown) => target, })); vi.mock('@memberjunction/core', () => ({ UserInfo: class UserInfo {}, Metadata: vi.fn(), RunView: vi.fn().mockImplementation(() => ({ RunView: vi.fn().mockResolvedValue({ Success: true, Results: [] }), })), })); vi.mock('@memberjunction/core-entities', () => ({ MJCompanyIntegrationEntity: class MJCompanyIntegrationEntity { CompanyID: string = ''; APIKey: string | null = null; AccessToken: string | null = null; ExternalSystemID: string | null = null; CustomAttribute1: string | null = null; }, })); vi.mock('@memberjunction/actions-base', () => ({ ActionParam: class ActionParam { Name: string = ''; Value: unknown = null; Type: string = 'Input'; }, ActionResultSimple: class ActionResultSimple { Success: boolean = false; ResultCode: string = ''; Message: string = ''; Params?: Array<{ Name: string; Value: unknown; Type: string }>; }, RunActionParams: class RunActionParams { Params: Array<{ Name: string; Value: unknown; Type: string }> = []; ContextUser: unknown = {}; Action: unknown = {}; Filters: unknown[] = []; }, })); import { EnrollUserAction } from '../providers/learnworlds/actions/enroll-user.action'; import { UserInfo } from '@memberjunction/core'; import { EnrollUserParams } from '../providers/learnworlds/interfaces'; /** Helper to build a mock UserInfo */ function mockContextUser(): UserInfo { return { ID: 'user-1', Name: 'Test User', Email: 'test@example.com' } as unknown as UserInfo; } /** Helper to build a mock LW enrollment response */ function mockLWEnrollmentResponse(overrides: Record<string, unknown> = {}): Record<string, unknown> { return { success: true, data: { id: 'enrollment-abc', user_id: 'lw-user-1', course_id: 'course-1', enrolled_at: '2024-06-15T10:00:00Z', status: 'active', price: 0, progress_percentage: 0, completed_units: 0, total_units: 10, ...((overrides.data as Record<string, unknown>) || {}), }, ...(overrides.success !== undefined ? { success: overrides.success } : {}), }; } /** Helper to build a mock LW course response */ function mockLWCourseResponse(title: string = 'Intro to Testing'): Record<string, unknown> { return { success: true, data: { title }, }; } /** Helper to build a mock LW user lookup response */ function mockLWUserResponse(email: string = 'student@example.com'): Record<string, unknown> { return { success: true, data: { email, username: 'student1' }, }; } describe('EnrollUserAction', () => { let action: EnrollUserAction; const contextUser = mockContextUser(); beforeEach(() => { action = new EnrollUserAction(); vi.restoreAllMocks(); }); describe('EnrollUser() typed method', () => { it('should enroll user in course', async () => { const requestSpy = vi.spyOn(action as unknown as { makeLearnWorldsRequest: (...args: unknown[]) => Promise<unknown> }, 'makeLearnWorldsRequest'); requestSpy .mockResolvedValueOnce(mockLWEnrollmentResponse()) // enrollment .mockResolvedValueOnce(mockLWCourseResponse()) // course title lookup .mockResolvedValueOnce(mockLWUserResponse()); // user name lookup const params: EnrollUserParams = { CompanyID: 'comp-1', UserID: 'lw-user-1', CourseID: 'course-1', }; const result = await action.EnrollUser(params, contextUser); expect(result.EnrollmentDetails.id).toBe('enrollment-abc'); expect(result.EnrollmentDetails.userId).toBe('lw-user-1'); expect(result.EnrollmentDetails.courseId).toBe('course-1'); expect(result.EnrollmentDetails.status).toBe('active'); expect(result.Summary.courseTitle).toBe('Intro to Testing'); expect(result.Summary.userName).toBe('student@example.com'); expect(result.Summary.notificationSent).toBe(true); // Verify first call used the unified enrollment endpoint expect(requestSpy.mock.calls[0][0]).toBe('users/lw-user-1/enrollment'); expect(requestSpy.mock.calls[0][1]).toBe('POST'); }); it('should enroll user in bundle (ProductType: bundle)', async () => { const requestSpy = vi.spyOn(action as unknown as { makeLearnWorldsRequest: (...args: unknown[]) => Promise<unknown> }, 'makeLearnWorldsRequest'); requestSpy .mockResolvedValueOnce(mockLWEnrollmentResponse()) .mockResolvedValueOnce(mockLWCourseResponse('Bundle Course')) .mockResolvedValueOnce(mockLWUserResponse()); const params: EnrollUserParams = { CompanyID: 'comp-1', UserID: 'lw-user-1', CourseID: 'bundle-1', ProductType: 'bundle', Price: 49.99, }; const result = await action.EnrollUser(params, contextUser); expect(result.EnrollmentDetails.id).toBe('enrollment-abc'); // Verify the unified enrollment endpoint was used expect(requestSpy.mock.calls[0][0]).toBe('users/lw-user-1/enrollment'); const body = requestSpy.mock.calls[0][2] as Record<string, unknown>; expect(body.productType).toBe('bundle'); expect(body.productId).toBe('bundle-1'); expect(body.price).toBe(49.99); }); it('should throw when UserID missing', async () => { const params: EnrollUserParams = { CompanyID: 'comp-1', UserID: '', CourseID: 'course-1', }; await expect(action.EnrollUser(params, contextUser)).rejects.toThrow('UserID is required'); }); it('should throw when CourseID missing', async () => { const params: EnrollUserParams = { CompanyID: 'comp-1', UserID: 'lw-user-1', CourseID: '', }; await expect(action.EnrollUser(params, contextUser)).rejects.toThrow('CourseID is required'); }); it('should throw when enrollment API returns success: false', async () => { vi.spyOn(action as unknown as { makeLearnWorldsRequest: (...args: unknown[]) => Promise<unknown> }, 'makeLearnWorldsRequest').mockResolvedValueOnce({ success: false, message: 'User already enrolled', }); const params: EnrollUserParams = { CompanyID: 'comp-1', UserID: 'lw-user-1', CourseID: 'course-1', }; await expect(action.EnrollUser(params, contextUser)).rejects.toThrow('User already enrolled'); }); it('should use defaults for optional fields', async () => { const requestSpy = vi.spyOn(action as unknown as { makeLearnWorldsRequest: (...args: unknown[]) => Promise<unknown> }, 'makeLearnWorldsRequest'); requestSpy.mockResolvedValueOnce(mockLWEnrollmentResponse()).mockResolvedValueOnce(mockLWCourseResponse()).mockResolvedValueOnce(mockLWUserResponse()); const params: EnrollUserParams = { CompanyID: 'comp-1', UserID: 'lw-user-1', CourseID: 'course-1', }; await action.EnrollUser(params, contextUser); const body = requestSpy.mock.calls[0][2] as Record<string, unknown>; expect(body.price).toBe(0); expect(body.justification).toBe('API Enrollment'); expect(body.send_enrollment_email).toBe(true); }); it('should fall back to defaults when course/user lookups fail', async () => { const requestSpy = vi.spyOn(action as unknown as { makeLearnWorldsRequest: (...args: unknown[]) => Promise<unknown> }, 'makeLearnWorldsRequest'); requestSpy .mockResolvedValueOnce(mockLWEnrollmentResponse()) // enrollment succeeds .mockRejectedValueOnce(new Error('Course not found')) // course lookup fails .mockRejectedValueOnce(new Error('User not found')); // user lookup fails const params: EnrollUserParams = { CompanyID: 'comp-1', UserID: 'lw-user-1', CourseID: 'course-1', }; const result = await action.EnrollUser(params, contextUser); expect(result.Summary.courseTitle).toBe('Unknown Course'); expect(result.Summary.userName).toBe('Unknown User'); }); }); describe('InternalRunAction()', () => { it('should return success', async () => { const requestSpy = vi.spyOn(action as unknown as { makeLearnWorldsRequest: (...args: unknown[]) => Promise<unknown> }, 'makeLearnWorldsRequest'); requestSpy.mockResolvedValueOnce(mockLWEnrollmentResponse()).mockResolvedValueOnce(mockLWCourseResponse()).mockResolvedValueOnce(mockLWUserResponse()); const runParams = { Params: [ { Name: 'CompanyID', Value: 'comp-1', Type: 'Input' as const }, { Name: 'UserID', Value: 'lw-user-1', Type: 'Input' as const }, { Name: 'CourseID', Value: 'course-1', Type: 'Input' as const }, ], ContextUser: contextUser, Action: {}, Filters: [], }; const result = await action['InternalRunAction'](runParams as unknown as Parameters<(typeof action)['InternalRunAction']>[0]); expect(result.Success).toBe(true); expect(result.ResultCode).toBe('SUCCESS'); expect(result.Message).toContain('Successfully enrolled user'); }); it('should return error on failure', async () => { vi.spyOn(action as unknown as { makeLearnWorldsRequest: (...args: unknown[]) => Promise<unknown> }, 'makeLearnWorldsRequest').mockRejectedValue( new Error('API timeout'), ); const runParams = { Params: [ { Name: 'CompanyID', Value: 'comp-1', Type: 'Input' as const }, { Name: 'UserID', Value: 'lw-user-1', Type: 'Input' as const }, { Name: 'CourseID', Value: 'course-1', Type: 'Input' as const }, ], ContextUser: contextUser, Action: {}, Filters: [], }; const result = await action['InternalRunAction'](runParams as unknown as Parameters<(typeof action)['InternalRunAction']>[0]); expect(result.Success).toBe(false); expect(result.ResultCode).toBe('ERROR'); expect(result.Message).toContain('API timeout'); }); }); });