Aggregeert verify_result counts (ALIGNED/PARTIAL/EMPTY/DIVERGENT) en top-5 EMPTY/DIVERGENT jobs over de laatste N dagen voor de ingelogde gebruiker. Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
112 lines
3.2 KiB
TypeScript
112 lines
3.2 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
|
|
const { mockGroupBy, mockFindMany } = vi.hoisted(() => ({
|
|
mockGroupBy: vi.fn(),
|
|
mockFindMany: vi.fn(),
|
|
}))
|
|
|
|
vi.mock('@/lib/prisma', () => ({
|
|
prisma: {
|
|
claudeJob: {
|
|
groupBy: mockGroupBy,
|
|
findMany: mockFindMany,
|
|
},
|
|
},
|
|
}))
|
|
|
|
import { getVerifyResultStats } from '@/lib/insights/verify-stats'
|
|
|
|
const USER_ID = 'user-1'
|
|
|
|
const makeJob = (id: string, verifyResult: string, daysAgo: number) => {
|
|
const finishedAt = new Date()
|
|
finishedAt.setDate(finishedAt.getDate() - daysAgo)
|
|
return {
|
|
id,
|
|
finished_at: finishedAt,
|
|
task: { id: `task-${id}`, title: `Task ${id}` },
|
|
product: { id: 'prod-1', name: 'Scrum4Me' },
|
|
verify_result: verifyResult,
|
|
}
|
|
}
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
})
|
|
|
|
describe('getVerifyResultStats', () => {
|
|
it('returns counts in ALIGNED→PARTIAL→EMPTY→DIVERGENT order', async () => {
|
|
mockGroupBy.mockResolvedValue([
|
|
{ verify_result: 'DIVERGENT', _count: { _all: 2 } },
|
|
{ verify_result: 'ALIGNED', _count: { _all: 10 } },
|
|
{ verify_result: 'EMPTY', _count: { _all: 3 } },
|
|
{ verify_result: 'PARTIAL', _count: { _all: 1 } },
|
|
])
|
|
mockFindMany.mockResolvedValue([])
|
|
|
|
const stats = await getVerifyResultStats(USER_ID)
|
|
|
|
expect(stats.counts.map(c => c.result)).toEqual([
|
|
'ALIGNED', 'PARTIAL', 'EMPTY', 'DIVERGENT',
|
|
])
|
|
expect(stats.counts.map(c => c.count)).toEqual([10, 1, 3, 2])
|
|
})
|
|
|
|
it('omits results with zero count from groupBy', async () => {
|
|
mockGroupBy.mockResolvedValue([
|
|
{ verify_result: 'ALIGNED', _count: { _all: 5 } },
|
|
])
|
|
mockFindMany.mockResolvedValue([])
|
|
|
|
const stats = await getVerifyResultStats(USER_ID)
|
|
|
|
expect(stats.counts).toHaveLength(1)
|
|
expect(stats.counts[0]).toEqual({ result: 'ALIGNED', count: 5 })
|
|
})
|
|
|
|
it('maps topEmpty jobs correctly', async () => {
|
|
mockGroupBy.mockResolvedValue([])
|
|
const job = makeJob('j1', 'EMPTY', 2)
|
|
// First findMany call → topEmpty, second → topDivergent
|
|
mockFindMany
|
|
.mockResolvedValueOnce([job])
|
|
.mockResolvedValueOnce([])
|
|
|
|
const stats = await getVerifyResultStats(USER_ID)
|
|
|
|
expect(stats.topEmpty).toHaveLength(1)
|
|
expect(stats.topEmpty[0]).toMatchObject({
|
|
jobId: 'j1',
|
|
taskId: 'task-j1',
|
|
taskTitle: 'Task j1',
|
|
productId: 'prod-1',
|
|
productName: 'Scrum4Me',
|
|
})
|
|
})
|
|
|
|
it('topDivergent is ordered most-recent first (from DB order)', async () => {
|
|
mockGroupBy.mockResolvedValue([])
|
|
const jobs = [
|
|
makeJob('jOld', 'DIVERGENT', 10),
|
|
makeJob('jNew', 'DIVERGENT', 1),
|
|
]
|
|
mockFindMany
|
|
.mockResolvedValueOnce([]) // topEmpty
|
|
.mockResolvedValueOnce(jobs) // topDivergent (already sorted by Prisma orderBy)
|
|
|
|
const stats = await getVerifyResultStats(USER_ID)
|
|
|
|
expect(stats.topDivergent.map(j => j.jobId)).toEqual(['jOld', 'jNew'])
|
|
})
|
|
|
|
it('returns empty stats when no jobs found', async () => {
|
|
mockGroupBy.mockResolvedValue([])
|
|
mockFindMany.mockResolvedValue([])
|
|
|
|
const stats = await getVerifyResultStats(USER_ID)
|
|
|
|
expect(stats.counts).toEqual([])
|
|
expect(stats.topEmpty).toEqual([])
|
|
expect(stats.topDivergent).toEqual([])
|
|
})
|
|
})
|