invoices.spec.ts (10 cas) : - Création : 201 + rubisEarned=1 (bonus saisie) + status=pending - Création client à la volée : si nom non matché + email fourni → client créé - 422 client_email_required si pas d'email pour création à la volée - Si planId fourni : RelanceTasks scheduled créées (assertion sur DB count) - Numéro unique par org : 422 sur duplicate (testant l'exception handler) - mark-paid idempotent : 2e appel ne re-bumpe pas rubisEarned ni org.rubis_count - mark-paid annule les RelanceTasks scheduled (passe à cancelled) - Cross-org : user B → 404 sur mark-paid d'une facture de A - GET /invoices : pagination + meta total/page - GET /invoices/counts : agrège par status imports.spec.ts (6 cas) : - POST /upload mock JSON : 1 batch + N drafts en pending, mock OCR rempli les fields - Refus > 20 fichiers - Validate transforme draft en Invoice + status=validated + invoiceId set - Validate sur draft déjà processed → 409 draft_already_processed - Skip → status=skipped, idempotent - DELETE batch → CASCADE supprime les drafts dashboard.spec.ts (6 cas) : - KPIs zéros sur org vierge - factureToRelance compte les invoices pending - Après mark-paid : encaisseCents et rubisCount bumpent (org.rubisCount agrégé) - Activity vide sur org sans actions - Activity loggue invoice_paid après mark-paid (label dans le feed) - Top-late liste les clients avec invoices actives en retard (dueDate < today)
157 lines
5.3 KiB
TypeScript
157 lines
5.3 KiB
TypeScript
import { test } from '@japa/runner'
|
|
import testUtils from '@adonisjs/core/services/test_utils'
|
|
import { createTestUser } from '../helpers/auth.js'
|
|
import { body, type ApiOk } from '../helpers/response.js'
|
|
|
|
type Kpis = {
|
|
rubisCount: number
|
|
rubisThisMonth: number
|
|
encaisseCents: number
|
|
factureToRelance: number
|
|
factureInRelance: number
|
|
miseEnDemeurePending: number
|
|
monthlyGoalProgress: number
|
|
}
|
|
|
|
type ActivityEvent = { id: string; kind: string; label: string }
|
|
type LatePayer = { clientId: string; name: string; lateInvoicesCount: number }
|
|
|
|
test.group('Dashboard — GET /dashboard/kpis', (group) => {
|
|
group.each.setup(() => testUtils.db().withGlobalTransaction())
|
|
|
|
test('zéros sur une org vierge', async ({ client, assert }) => {
|
|
const { bearer } = await createTestUser()
|
|
const r = await client.get('/api/v1/dashboard/kpis').headers(bearer)
|
|
r.assertStatus(200)
|
|
|
|
const k = body<ApiOk<Kpis>>(r).data
|
|
assert.equal(k.rubisCount, 0)
|
|
assert.equal(k.rubisThisMonth, 0)
|
|
assert.equal(k.encaisseCents, 0)
|
|
assert.equal(k.factureToRelance, 0)
|
|
assert.equal(k.factureInRelance, 0)
|
|
assert.equal(k.miseEnDemeurePending, 0)
|
|
})
|
|
|
|
test('factureToRelance compte les pending', async ({ client, assert }) => {
|
|
const { bearer } = await createTestUser()
|
|
const c = await client
|
|
.post('/api/v1/clients')
|
|
.headers(bearer)
|
|
.json({ name: 'Dash', email: 'dash@spec.test' })
|
|
const cid = body<ApiOk<{ id: string }>>(c).data.id
|
|
|
|
await client.post('/api/v1/invoices').headers(bearer).json({
|
|
clientId: cid,
|
|
clientName: 'Dash',
|
|
numero: 'F-DASH-01',
|
|
amountTtcCents: 10000,
|
|
issueDate: '2026-04-01T09:00:00.000Z',
|
|
dueDate: '2026-05-01T09:00:00.000Z',
|
|
})
|
|
|
|
const r = await client.get('/api/v1/dashboard/kpis').headers(bearer)
|
|
assert.equal(body<ApiOk<Kpis>>(r).data.factureToRelance, 1)
|
|
})
|
|
|
|
test('encaisseCents et rubisCount bumpent après mark-paid', async ({
|
|
client,
|
|
assert,
|
|
}) => {
|
|
const { bearer } = await createTestUser()
|
|
const c = await client
|
|
.post('/api/v1/clients')
|
|
.headers(bearer)
|
|
.json({ name: 'Paid Spec', email: 'paid@spec.test' })
|
|
const cid = body<ApiOk<{ id: string }>>(c).data.id
|
|
|
|
const inv = await client.post('/api/v1/invoices').headers(bearer).json({
|
|
clientId: cid,
|
|
clientName: 'Paid Spec',
|
|
numero: 'F-PAID-01',
|
|
amountTtcCents: 250000,
|
|
// issueDate/dueDate dans le mois courant pour rentrer dans rubisThisMonth
|
|
issueDate: new Date().toISOString(),
|
|
dueDate: new Date(Date.now() + 86_400_000).toISOString(),
|
|
})
|
|
const invId = body<ApiOk<{ id: string }>>(inv).data.id
|
|
|
|
await client.post(`/api/v1/invoices/${invId}/mark-paid`).headers(bearer)
|
|
|
|
const r = await client.get('/api/v1/dashboard/kpis').headers(bearer)
|
|
const k = body<ApiOk<Kpis>>(r).data
|
|
assert.equal(k.rubisCount, 1)
|
|
assert.equal(k.encaisseCents, 250000)
|
|
})
|
|
})
|
|
|
|
test.group('Dashboard — GET /dashboard/activity', (group) => {
|
|
group.each.setup(() => testUtils.db().withGlobalTransaction())
|
|
|
|
test('vide sur une org sans actions', async ({ client, assert }) => {
|
|
const { bearer } = await createTestUser()
|
|
const r = await client.get('/api/v1/dashboard/activity').headers(bearer)
|
|
r.assertStatus(200)
|
|
assert.deepEqual(body<ApiOk<ActivityEvent[]>>(r).data, [])
|
|
})
|
|
|
|
test('mark-paid logge un event invoice_paid', async ({ client, assert }) => {
|
|
const { bearer } = await createTestUser()
|
|
const c = await client
|
|
.post('/api/v1/clients')
|
|
.headers(bearer)
|
|
.json({ name: 'Activity', email: 'activity@spec.test' })
|
|
const cid = body<ApiOk<{ id: string }>>(c).data.id
|
|
|
|
const inv = await client.post('/api/v1/invoices').headers(bearer).json({
|
|
clientId: cid,
|
|
clientName: 'Activity',
|
|
numero: 'F-ACT-01',
|
|
amountTtcCents: 50000,
|
|
issueDate: '2026-04-01T09:00:00.000Z',
|
|
dueDate: '2026-05-01T09:00:00.000Z',
|
|
})
|
|
const invId = body<ApiOk<{ id: string }>>(inv).data.id
|
|
|
|
await client.post(`/api/v1/invoices/${invId}/mark-paid`).headers(bearer)
|
|
|
|
const r = await client.get('/api/v1/dashboard/activity').headers(bearer)
|
|
const events = body<ApiOk<ActivityEvent[]>>(r).data
|
|
assert.isAbove(events.length, 0)
|
|
assert.equal(events[0].kind, 'invoice_paid')
|
|
})
|
|
})
|
|
|
|
test.group('Dashboard — GET /dashboard/top-late', (group) => {
|
|
group.each.setup(() => testUtils.db().withGlobalTransaction())
|
|
|
|
test('liste les clients avec factures actives en retard', async ({
|
|
client,
|
|
assert,
|
|
}) => {
|
|
const { bearer } = await createTestUser()
|
|
const c = await client
|
|
.post('/api/v1/clients')
|
|
.headers(bearer)
|
|
.json({ name: 'En Retard', email: 'late@spec.test' })
|
|
const cid = body<ApiOk<{ id: string }>>(c).data.id
|
|
|
|
// dueDate dans le passé → en retard
|
|
await client.post('/api/v1/invoices').headers(bearer).json({
|
|
clientId: cid,
|
|
clientName: 'En Retard',
|
|
numero: 'F-LATE-01',
|
|
amountTtcCents: 50000,
|
|
issueDate: '2025-12-01T09:00:00.000Z',
|
|
dueDate: '2025-12-31T09:00:00.000Z',
|
|
})
|
|
|
|
const r = await client.get('/api/v1/dashboard/top-late').headers(bearer)
|
|
r.assertStatus(200)
|
|
const list = body<ApiOk<LatePayer[]>>(r).data
|
|
assert.lengthOf(list, 1)
|
|
assert.equal(list[0].name, 'En Retard')
|
|
assert.equal(list[0].lateInvoicesCount, 1)
|
|
})
|
|
})
|