feat(hq): server assembly, build script and e2e smoke (HQ-1 task 14)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>feat/client-detail-redesign
parent
4a025786aa
commit
3671c9c40f
@ -0,0 +1,16 @@
|
||||
import { build } from 'esbuild'
|
||||
|
||||
await build({
|
||||
entryPoints: ['src/server.ts'],
|
||||
outfile: 'dist/server.cjs',
|
||||
bundle: true,
|
||||
platform: 'node',
|
||||
format: 'cjs',
|
||||
// Native module (better-sqlite3) and puppeteer (locates its browser relative
|
||||
// to its own package dir) must stay outside the bundle.
|
||||
external: ['better-sqlite3', 'puppeteer'],
|
||||
// server.ts falls back to import.meta.url only when __dirname is missing (ESM
|
||||
// under vitest/tsx); in this CJS bundle that branch is dead, so the warning is noise.
|
||||
logOverride: { 'empty-import-meta': 'silent' },
|
||||
})
|
||||
console.log('hq built')
|
||||
@ -0,0 +1,41 @@
|
||||
// apps/hq/test/e2e.test.ts
|
||||
import { describe, it, expect, afterAll } from 'vitest'
|
||||
import express from 'express'
|
||||
import { openDb } from '../src/db'
|
||||
import { seedIfEmpty } from '../src/seed'
|
||||
import { createStaff } from '../src/auth'
|
||||
import { apiRouter } from '../src/api'
|
||||
|
||||
const db = openDb(':memory:')
|
||||
seedIfEmpty(db)
|
||||
createStaff(db, { email: 'e2e@test.in', displayName: 'E2E', role: 'owner', password: 'e2e-password' })
|
||||
const app = express(); app.use(express.json()); app.locals['db'] = db; app.use('/api', apiRouter(db))
|
||||
const server = app.listen(0)
|
||||
const base = `http://localhost:${(server.address() as { port: number }).port}/api`
|
||||
let token = ''
|
||||
const call = async (method: string, path: string, body?: unknown) => {
|
||||
const res = await fetch(base + path, { method,
|
||||
headers: { 'content-type': 'application/json', authorization: `Bearer ${token}` },
|
||||
...(body ? { body: JSON.stringify(body) } : {}) })
|
||||
return { status: res.status, json: await res.json() as any }
|
||||
}
|
||||
|
||||
describe('hq end to end', () => {
|
||||
afterAll(() => server.close())
|
||||
it('walks quotation → invoice → payment → paid', async () => {
|
||||
token = (await call('POST', '/auth/login', { email: 'e2e@test.in', password: 'e2e-password' })).json.token
|
||||
const client = (await call('POST', '/clients', { name: 'Flow Mart', stateCode: '32' })).json.client
|
||||
const mod = (await call('POST', '/modules', { code: 'POS', name: 'POS Billing' })).json.module
|
||||
await call('POST', `/modules/${mod.id}/prices`, { kind: 'yearly', pricePaise: 10_000_00, effectiveFrom: '2026-04-01' })
|
||||
const qt = (await call('POST', '/documents', { docType: 'QUOTATION', clientId: client.id,
|
||||
lines: [{ moduleId: mod.id, qty: 1, kind: 'yearly' }] })).json.document
|
||||
await call('POST', `/documents/${qt.id}/issue`)
|
||||
const inv = (await call('POST', `/documents/${qt.id}/convert`, { to: 'INVOICE' })).json.document
|
||||
await call('POST', `/documents/${inv.id}/issue`)
|
||||
await call('POST', '/payments', { clientId: client.id, receivedOn: '2026-07-10', mode: 'bank', amountPaise: 11_800_00 })
|
||||
const after = (await call('GET', `/documents/${inv.id}`)).json.document
|
||||
expect(after.status).toBe('paid')
|
||||
const ledger = (await call('GET', `/clients/${client.id}/ledger`)).json
|
||||
expect(ledger.advancePaise).toBe(0)
|
||||
})
|
||||
})
|
||||
Loading…
Reference in New Issue