|
1 | | -/// <reference types="vitest/globals" /> |
| 1 | +import { describe, it, expect, vi, beforeEach } from 'vitest'; |
| 2 | +import { GET } from '../route'; |
| 3 | +import { newKyselyPostgresql } from '@/.config/kysely.config'; |
2 | 4 |
|
3 | | -import { NextRequest } from 'next/server' |
4 | | -import { describe, it, expect, vi, beforeEach } from 'vitest' |
5 | | -import { GET } from '../route' |
6 | | -import { newKyselyPostgresql } from '@/.config/kysely.config' |
7 | | -import { sql } from 'kysely' |
8 | | - |
9 | | -// Mock the entire module |
10 | 5 | vi.mock('@/.config/kysely.config', () => ({ |
11 | 6 | newKyselyPostgresql: vi.fn(), |
12 | | -})) |
13 | | - |
14 | | -// Cast the mock to the correct type for TypeScript |
15 | | -const mockedNewKyselyPostgresql = newKyselyPostgresql as vi.Mock |
| 7 | +})); |
16 | 8 |
|
17 | | -describe('API /api/courses-with-progress', () => { |
18 | | - let mockDb: any |
| 9 | +describe('GET /api/courses-with-progress', () => { |
| 10 | + let mockUserQueryChain: any; |
| 11 | + let mockCourseQueryChain: any; |
| 12 | + let mockDb: any; |
19 | 13 |
|
20 | 14 | beforeEach(() => { |
21 | | - // Reset mocks before each test |
22 | | - vi.clearAllMocks() |
| 15 | + vi.resetAllMocks(); |
| 16 | + |
| 17 | + mockUserQueryChain = { |
| 18 | + executeTakeFirst: vi.fn(), |
| 19 | + }; |
| 20 | + |
| 21 | + mockCourseQueryChain = { |
| 22 | + execute: vi.fn(), |
| 23 | + }; |
23 | 24 |
|
24 | | - // Default mock setup for a successful but empty query chain |
25 | 25 | mockDb = { |
26 | | - selectFrom: vi.fn().mockReturnThis(), |
27 | | - innerJoin: vi.fn().mockReturnThis(), |
28 | | - leftJoin: vi.fn().mockReturnThis(), |
29 | | - where: vi.fn().mockReturnThis(), |
30 | | - on: vi.fn().mockReturnThis(), |
31 | | - onRef: vi.fn().mockReturnThis(), |
32 | | - groupBy: vi.fn().mockReturnThis(), |
33 | | - select: vi.fn().mockReturnThis(), |
34 | | - selectAll: vi.fn().mockReturnThis(), |
35 | | - executeTakeFirst: vi.fn().mockResolvedValue(undefined), |
36 | | - execute: vi.fn().mockResolvedValue([]), |
37 | | - } |
38 | | - mockedNewKyselyPostgresql.mockReturnValue(mockDb) |
39 | | - }) |
40 | | - |
41 | | - it('should return 400 if walletAddress is missing', async () => { |
42 | | - const req = new NextRequest('http://localhost?lang=en') |
43 | | - const response = await GET(req) |
44 | | - const json = await response.json() |
45 | | - expect(response.status).toBe(400) |
46 | | - expect(json.error).toBe('walletAddress is required') |
47 | | - }) |
48 | | - |
49 | | - it('should return courses with 0 progress if user is not found', async () => { |
50 | | - const mockCourses = [ |
51 | | - { id: 1, title: 'Course 1', /* other fields */ }, |
52 | | - { id: 2, title: 'Course 2', /* other fields */ }, |
53 | | - ] |
54 | | - const expectedCourses = mockCourses.map(c => ({ |
55 | | - ...c, |
56 | | - percentageCompleted: 0, |
| 26 | + selectFrom: vi.fn().mockImplementation((table: string) => { |
| 27 | + if (table === 'billetera_usuario') { |
| 28 | + return { |
| 29 | + innerJoin: vi.fn().mockReturnThis(), |
| 30 | + where: vi.fn().mockReturnThis(), |
| 31 | + select: vi.fn().mockReturnValue(mockUserQueryChain), |
| 32 | + }; |
| 33 | + } |
| 34 | + if (table.startsWith('cor1440_gen_proyectofinanciero')) { |
| 35 | + return { |
| 36 | + leftJoin: vi.fn().mockReturnThis(), |
| 37 | + where: vi.fn().mockReturnThis(), |
| 38 | + groupBy: vi.fn().mockReturnThis(), |
| 39 | + select: vi.fn().mockReturnValue(mockCourseQueryChain), |
| 40 | + }; |
| 41 | + } |
| 42 | + return {}; |
| 43 | + }), |
| 44 | + }; |
| 45 | + |
| 46 | + (newKyselyPostgresql as vi.Mock).mockReturnValue(mockDb); |
| 47 | + }); |
| 48 | + |
| 49 | + it('should return courses with correct progress when user exists', async () => { |
| 50 | + const mockUser = { userId: 1 }; |
| 51 | + mockUserQueryChain.executeTakeFirst.mockResolvedValue(mockUser); |
| 52 | + |
| 53 | + const mockCoursesWithProgress = [ |
| 54 | + { |
| 55 | + id: 101, |
| 56 | + titulo: 'Test Course', |
| 57 | + subtitulo: 'A course for testing', |
| 58 | + idioma: 'en', |
| 59 | + prefijoRuta: '/test-course', |
| 60 | + imagen: null, |
| 61 | + resumenMd: 'Summary', |
| 62 | + creditosMd: 'Credits', |
| 63 | + percentageCompleted: 100.0, |
57 | 64 | percentagePaid: 0, |
58 | | - })); |
| 65 | + }, |
| 66 | + ]; |
| 67 | + mockCourseQueryChain.execute.mockResolvedValue(mockCoursesWithProgress); |
59 | 68 |
|
| 69 | + const request = new Request('http://localhost/api/courses-with-progress?lang=en&walletAddress=0x123'); |
60 | 70 |
|
61 | | - // User query fails |
62 | | - mockDb.executeTakeFirst.mockResolvedValue(null) |
63 | | - // Fallback course query succeeds |
64 | | - mockDb.execute.mockResolvedValue(mockCourses) |
| 71 | + const response = await GET(request); |
| 72 | + const data = await response.json(); |
65 | 73 |
|
66 | | - const req = new NextRequest('http://localhost?lang=en&walletAddress=0xnotfound') |
67 | | - const response = await GET(req) |
68 | | - const json = await response.json() |
| 74 | + expect(response.status).toBe(200); |
| 75 | + expect(data).toEqual(mockCoursesWithProgress); |
| 76 | + expect(mockDb.selectFrom).toHaveBeenCalledWith('billetera_usuario'); |
| 77 | + expect(mockDb.selectFrom).toHaveBeenCalledWith('cor1440_gen_proyectofinanciero as p'); |
| 78 | + expect(mockUserQueryChain.executeTakeFirst).toHaveBeenCalled(); |
| 79 | + expect(mockCourseQueryChain.execute).toHaveBeenCalled(); |
| 80 | + }); |
69 | 81 |
|
70 | | - expect(response.status).toBe(200) |
71 | | - expect(json).toEqual(expectedCourses) |
72 | | - expect(mockDb.executeTakeFirst).toHaveBeenCalledOnce() |
73 | | - // Check that the second `execute` (for courses) was called |
74 | | - expect(mockDb.execute).toHaveBeenCalledOnce() |
75 | | - }) |
| 82 | + it('should return courses with zero progress when user does not exist', async () => { |
| 83 | + mockUserQueryChain.executeTakeFirst.mockResolvedValue(null); |
76 | 84 |
|
77 | | - it('should return courses with calculated progress if user is found', async () => { |
78 | | - const mockUser = { userId: 123 } |
79 | | - const mockCoursesWithProgress = [ |
80 | | - { |
81 | | - id: 1, |
82 | | - titulo: 'Course 1', |
83 | | - percentageCompleted: 50, |
84 | | - percentagePaid: 25, |
85 | | - }, |
86 | | - ] |
87 | | - |
88 | | - // User query succeeds |
89 | | - mockDb.executeTakeFirst.mockResolvedValue(mockUser) |
90 | | - // Main progress query succeeds |
91 | | - mockDb.execute.mockResolvedValue(mockCoursesWithProgress) |
92 | | - |
93 | | - const req = new NextRequest('http://localhost?lang=en&walletAddress=0xfound') |
94 | | - const response = await GET(req) |
95 | | - const json = await response.json() |
96 | | - |
97 | | - expect(response.status).toBe(200) |
98 | | - expect(json).toEqual(mockCoursesWithProgress) |
99 | | - expect(mockDb.executeTakeFirst).toHaveBeenCalledOnce() |
100 | | - expect(mockDb.execute).toHaveBeenCalledOnce() |
101 | | - // Verify it's using the more complex query path |
102 | | - expect(mockDb.leftJoin).toHaveBeenCalled() |
103 | | - expect(mockDb.groupBy).toHaveBeenCalled() |
104 | | - }) |
105 | | - |
106 | | - it('should handle database errors gracefully', async () => { |
107 | | - const dbError = new Error('DB connection failed') |
108 | | - mockedNewKyselyPostgresql.mockImplementation(() => { |
109 | | - throw dbError |
110 | | - }) |
111 | | - |
112 | | - const req = new NextRequest('http://localhost?lang=en&walletAddress=0xany') |
113 | | - const response = await GET(req) |
114 | | - const json = await response.json() |
115 | | - |
116 | | - expect(response.status).toBe(500) |
117 | | - expect(json.error).toContain('Failed to fetch data') |
118 | | - }) |
119 | | -}) |
| 85 | + const mockCourses = [ |
| 86 | + { |
| 87 | + id: 101, |
| 88 | + titulo: 'Test Course', |
| 89 | + // ... other properties from cor1440_gen_proyectofinanciero |
| 90 | + } |
| 91 | + ]; |
| 92 | + // This uses a different query path |
| 93 | + const zeroProgressQueryChain = { execute: vi.fn().mockResolvedValue(mockCourses) }; |
| 94 | + mockDb.selectFrom.mockImplementation((table: string) => { |
| 95 | + if (table === 'billetera_usuario') { |
| 96 | + return { |
| 97 | + innerJoin: vi.fn().mockReturnThis(), |
| 98 | + where: vi.fn().mockReturnThis(), |
| 99 | + select: vi.fn().mockReturnValue(mockUserQueryChain), |
| 100 | + }; |
| 101 | + } |
| 102 | + if (table === 'cor1440_gen_proyectofinanciero') { |
| 103 | + return { |
| 104 | + where: vi.fn().mockReturnThis(), |
| 105 | + selectAll: vi.fn().mockReturnValue(zeroProgressQueryChain) |
| 106 | + } |
| 107 | + } |
| 108 | + return {}; |
| 109 | + }); |
| 110 | + |
| 111 | + const request = new Request('http://localhost/api/courses-with-progress?lang=en&walletAddress=0x456'); |
| 112 | + |
| 113 | + const response = await GET(request); |
| 114 | + const data = await response.json(); |
| 115 | + |
| 116 | + expect(response.status).toBe(200); |
| 117 | + expect(data.length).toBe(1); |
| 118 | + expect(data[0].percentageCompleted).toBe(0); |
| 119 | + expect(data[0].percentagePaid).toBe(0); |
| 120 | + expect(zeroProgressQueryChain.execute).toHaveBeenCalled(); |
| 121 | + }); |
| 122 | +}); |
0 commit comments