Skip to content

Commit a5f491b

Browse files
committed
Calculo de progreso en curso
1 parent b69590a commit a5f491b

File tree

2 files changed

+112
-109
lines changed

2 files changed

+112
-109
lines changed
Lines changed: 108 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -1,119 +1,122 @@
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';
24

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
105
vi.mock('@/.config/kysely.config', () => ({
116
newKyselyPostgresql: vi.fn(),
12-
}))
13-
14-
// Cast the mock to the correct type for TypeScript
15-
const mockedNewKyselyPostgresql = newKyselyPostgresql as vi.Mock
7+
}));
168

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;
1913

2014
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+
};
2324

24-
// Default mock setup for a successful but empty query chain
2525
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,
5764
percentagePaid: 0,
58-
}));
65+
},
66+
];
67+
mockCourseQueryChain.execute.mockResolvedValue(mockCoursesWithProgress);
5968

69+
const request = new Request('http://localhost/api/courses-with-progress?lang=en&walletAddress=0x123');
6070

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();
6573

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+
});
6981

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);
7684

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+
});

apps/nextjs/app/api/courses-with-progress/route.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,15 +67,15 @@ export async function GET(request: Request) {
6767
'p.creditosMd',
6868
sql<number>`
6969
COALESCE(
70-
(SUM(CASE WHEN gu.points > 0 THEN 1 ELSE 0 END) * 100.0) /
71-
NULLIF(COUNT(a.id), 0),
70+
(COUNT(DISTINCT CASE WHEN gu.points > 0 THEN a.id ELSE NULL END) * 100.0) /
71+
NULLIF(COUNT(DISTINCT a.id), 0),
7272
0
7373
)
7474
`.as('percentageCompleted'),
7575
sql<number>`
7676
COALESCE(
77-
(SUM(CASE WHEN gu.amountpaid > 0 THEN 1 ELSE 0 END) * 100.0) /
78-
NULLIF(COUNT(a.id), 0),
77+
(COUNT(DISTINCT CASE WHEN gu.amountpaid > 0 THEN a.id ELSE NULL END) * 100.0) /
78+
NULLIF(COUNT(DISTINCT a.id), 0),
7979
0
8080
)
8181
`.as('percentagePaid')

0 commit comments

Comments
 (0)