create-kuckit-app 2.0.2 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +24 -14
  2. package/package.json +1 -1
  3. package/templates/base/.claude/skills/beads/CLAUDE.md +87 -0
  4. package/templates/base/.claude/skills/beads/README.md +123 -0
  5. package/templates/base/.claude/skills/beads/SKILL.md +77 -715
  6. package/templates/base/.claude/skills/beads/adr/0001-bd-prime-as-source-of-truth.md +61 -0
  7. package/templates/base/.claude/skills/beads/resources/AGENTS.md +62 -0
  8. package/templates/base/.claude/skills/beads/resources/ASYNC_GATES.md +175 -0
  9. package/templates/base/.claude/skills/beads/resources/BOUNDARIES.md +520 -0
  10. package/templates/base/.claude/skills/beads/resources/CHEMISTRY_PATTERNS.md +197 -0
  11. package/templates/base/.claude/skills/beads/resources/CLI_REFERENCE.md +561 -0
  12. package/templates/base/.claude/skills/beads/resources/DEPENDENCIES.md +754 -0
  13. package/templates/base/.claude/skills/beads/resources/INTEGRATION_PATTERNS.md +438 -0
  14. package/templates/base/.claude/skills/beads/resources/ISSUE_CREATION.md +150 -0
  15. package/templates/base/.claude/skills/beads/resources/MOLECULES.md +370 -0
  16. package/templates/base/.claude/skills/beads/resources/PATTERNS.md +363 -0
  17. package/templates/base/.claude/skills/beads/resources/RESUMABILITY.md +239 -0
  18. package/templates/base/.claude/skills/beads/resources/STATIC_DATA.md +61 -0
  19. package/templates/base/.claude/skills/beads/resources/TROUBLESHOOTING.md +537 -0
  20. package/templates/base/.claude/skills/beads/resources/WORKFLOWS.md +638 -0
  21. package/templates/base/.claude/skills/beads/resources/WORKTREES.md +95 -0
  22. package/templates/base/.claude/skills/browser-skill/SKILL.md +72 -0
  23. package/templates/base/.claude/skills/knowledge/SKILL.md +155 -205
  24. package/templates/base/.claude/skills/knowledge/reference/doc-mapping.md +49 -0
  25. package/templates/base/.claude/skills/knowledge/reference/extraction-prompts.md +102 -0
  26. package/templates/base/.claude/skills/kuckit/SKILL.md +15 -9
  27. package/templates/base/.claude/skills/kuckit/references/MODULE-DEVELOPMENT.md +142 -0
  28. package/templates/base/.claude/skills/kuckit/references/PACKAGES.md +22 -17
  29. package/templates/base/.claude/skills/kuckit/references/PUBLISHING.md +92 -0
  30. package/templates/base/.claude/skills/module-testing/SKILL.md +1 -1
  31. package/templates/base/.claude/skills/planning/SKILL.md +26 -1
  32. package/templates/base/.env.example +1 -1
  33. package/templates/base/AGENTS.md +155 -418
  34. package/templates/base/apps/server/src/modules.ts +14 -1
  35. package/templates/base/apps/web/.env.example +1 -1
  36. package/templates/base/apps/web/src/routes/$.tsx +0 -1
  37. package/templates/base/apps/web/src/routes/dashboard.tsx +3 -1
  38. package/templates/base/docs/ARCHITECTURE.md +689 -0
  39. package/templates/base/docs/DEPENDENCY-INJECTION.md +871 -0
  40. package/templates/base/docs/DEPLOYMENT.md +573 -0
  41. package/templates/base/docs/INDEX.md +135 -0
  42. package/templates/base/docs/MIGRATION.md +989 -0
  43. package/templates/base/docs/MODULE_CSS.md +343 -0
  44. package/templates/base/docs/MODULE_TESTING.md +368 -0
  45. package/templates/base/docs/MULTI_AGENT_WORKFLOW.md +909 -0
  46. package/templates/base/docs/TESTING.md +579 -0
  47. package/templates/base/docs/TROUBLESHOOTING.md +360 -0
  48. package/templates/base/package.json +2 -0
  49. package/templates/base/packages/items-module/AGENTS.md +3 -1
  50. package/templates/base/packages/items-module/src/server/adapters/{item.drizzle.ts → item.repository.ts} +1 -13
  51. package/templates/base/packages/items-module/src/server/module.ts +2 -1
  52. package/templates/base/packages/items-module/src/server/schema/item.ts +13 -0
@@ -0,0 +1,579 @@
1
+ # Testing Guide
2
+
3
+ This guide covers testing strategies for Kuckit applications, from unit tests to integration testing.
4
+
5
+ > **Module Testing**: For testing standalone npm modules before publishing, see [Module Testing Guide](./MODULE_TESTING.md).
6
+
7
+ ## Quick Reference
8
+
9
+ ```bash
10
+ # Run all tests
11
+ bun test
12
+
13
+ # Run tests in a specific package
14
+ bun test --cwd packages/my-module
15
+
16
+ # Run with coverage
17
+ bun test --coverage
18
+
19
+ # Watch mode
20
+ bun test --watch
21
+ ```
22
+
23
+ ---
24
+
25
+ ## Testing Philosophy
26
+
27
+ Kuckit follows Clean Architecture, which naturally creates testable boundaries:
28
+
29
+ ```
30
+ ┌─────────────────────────────────────────────────────────────┐
31
+ │ API Layer │ ← Integration tests
32
+ │ (oRPC routers, Express handlers) │
33
+ ├─────────────────────────────────────────────────────────────┤
34
+ │ Application Layer │ ← Unit tests (use cases)
35
+ │ (Use cases, application services) │
36
+ ├─────────────────────────────────────────────────────────────┤
37
+ │ Domain Layer │ ← Unit tests (pure logic)
38
+ │ (Entities, value objects, domain services) │
39
+ ├─────────────────────────────────────────────────────────────┤
40
+ │ Infrastructure Layer │ ← Integration tests
41
+ │ (Repositories, external services) │
42
+ └─────────────────────────────────────────────────────────────┘
43
+ ```
44
+
45
+ ### Test Pyramid
46
+
47
+ | Level | Focus | Speed | Coverage |
48
+ | ----------- | ------------------------- | ------ | -------- |
49
+ | Unit | Domain logic, use cases | Fast | High |
50
+ | Integration | Repository, API endpoints | Medium | Medium |
51
+ | E2E | Full user flows | Slow | Low |
52
+
53
+ ---
54
+
55
+ ## Unit Testing
56
+
57
+ ### Domain Layer Tests
58
+
59
+ Domain entities contain pure business logic and are the easiest to test:
60
+
61
+ ```typescript
62
+ // packages/my-module/src/server/domain/entities/invoice.test.ts
63
+ import { describe, expect, it } from 'bun:test'
64
+ import { Invoice } from './invoice'
65
+
66
+ describe('Invoice', () => {
67
+ it('calculates total from line items', () => {
68
+ const invoice = Invoice.create({
69
+ lineItems: [
70
+ { description: 'Item 1', amount: 100 },
71
+ { description: 'Item 2', amount: 50 },
72
+ ],
73
+ })
74
+
75
+ expect(invoice.total).toBe(150)
76
+ })
77
+
78
+ it('applies discount correctly', () => {
79
+ const invoice = Invoice.create({
80
+ lineItems: [{ description: 'Item', amount: 100 }],
81
+ discountPercent: 10,
82
+ })
83
+
84
+ expect(invoice.total).toBe(90)
85
+ })
86
+
87
+ it('throws on negative amounts', () => {
88
+ expect(() =>
89
+ Invoice.create({
90
+ lineItems: [{ description: 'Item', amount: -50 }],
91
+ })
92
+ ).toThrow('Amount must be positive')
93
+ })
94
+ })
95
+ ```
96
+
97
+ ### Use Case Tests
98
+
99
+ Use cases orchestrate domain logic and interact with ports. Mock the ports:
100
+
101
+ ```typescript
102
+ // packages/my-module/src/server/usecases/create-invoice.test.ts
103
+ import { describe, expect, it, mock } from 'bun:test'
104
+ import { CreateInvoiceUseCase } from './create-invoice'
105
+ import type { InvoiceRepository } from '../ports/invoice-repository'
106
+ import type { Logger } from '@kuckit/domain'
107
+
108
+ describe('CreateInvoiceUseCase', () => {
109
+ it('creates invoice and persists it', async () => {
110
+ const mockRepo: InvoiceRepository = {
111
+ save: mock(() => Promise.resolve()),
112
+ findById: mock(() => Promise.resolve(null)),
113
+ findAll: mock(() => Promise.resolve([])),
114
+ }
115
+
116
+ const mockLogger: Logger = {
117
+ info: mock(() => {}),
118
+ warn: mock(() => {}),
119
+ error: mock(() => {}),
120
+ debug: mock(() => {}),
121
+ }
122
+
123
+ const useCase = new CreateInvoiceUseCase(mockRepo, mockLogger)
124
+
125
+ const result = await useCase.execute({
126
+ customerId: 'cust-123',
127
+ lineItems: [{ description: 'Service', amount: 500 }],
128
+ })
129
+
130
+ expect(result.total).toBe(500)
131
+ expect(mockRepo.save).toHaveBeenCalledTimes(1)
132
+ expect(mockLogger.info).toHaveBeenCalled()
133
+ })
134
+
135
+ it('throws when customer not found', async () => {
136
+ const mockRepo: InvoiceRepository = {
137
+ save: mock(() => Promise.reject(new Error('Customer not found'))),
138
+ findById: mock(() => Promise.resolve(null)),
139
+ findAll: mock(() => Promise.resolve([])),
140
+ }
141
+
142
+ const useCase = new CreateInvoiceUseCase(mockRepo, {
143
+ info: () => {},
144
+ warn: () => {},
145
+ error: () => {},
146
+ debug: () => {},
147
+ })
148
+
149
+ await expect(useCase.execute({ customerId: 'invalid', lineItems: [] })).rejects.toThrow(
150
+ 'Customer not found'
151
+ )
152
+ })
153
+ })
154
+ ```
155
+
156
+ ### Testing with DI Container
157
+
158
+ When you need the full container for tests:
159
+
160
+ ```typescript
161
+ // packages/my-module/src/server/__tests__/integration.test.ts
162
+ import { describe, expect, it, beforeAll, afterAll } from 'bun:test'
163
+ import { createContainer, asFunction, asValue } from 'awilix'
164
+ import { createTestDatabase } from '../test-utils/database'
165
+
166
+ describe('Module Integration', () => {
167
+ let container: ReturnType<typeof createContainer>
168
+ let testDb: Awaited<ReturnType<typeof createTestDatabase>>
169
+
170
+ beforeAll(async () => {
171
+ testDb = await createTestDatabase()
172
+
173
+ container = createContainer()
174
+ container.register({
175
+ db: asValue(testDb.db),
176
+ logger: asValue({ info: () => {}, warn: () => {}, error: () => {}, debug: () => {} }),
177
+ invoiceRepository: asFunction(({ db }) => new DrizzleInvoiceRepository(db)).scoped(),
178
+ createInvoiceUseCase: asFunction(
179
+ ({ invoiceRepository, logger }) => new CreateInvoiceUseCase(invoiceRepository, logger)
180
+ ).scoped(),
181
+ })
182
+ })
183
+
184
+ afterAll(async () => {
185
+ await testDb.cleanup()
186
+ })
187
+
188
+ it('creates invoice through use case', async () => {
189
+ const useCase = container.resolve('createInvoiceUseCase')
190
+ const result = await useCase.execute({
191
+ customerId: 'test-customer',
192
+ lineItems: [{ description: 'Test', amount: 100 }],
193
+ })
194
+
195
+ expect(result.id).toBeDefined()
196
+ })
197
+ })
198
+ ```
199
+
200
+ ---
201
+
202
+ ## Integration Testing
203
+
204
+ ### Repository Tests
205
+
206
+ Test repositories against a real database:
207
+
208
+ ```typescript
209
+ // packages/my-module/src/server/adapters/__tests__/invoice-repository.test.ts
210
+ import { describe, expect, it, beforeAll, afterAll, beforeEach } from 'bun:test'
211
+ import { DrizzleInvoiceRepository } from '../invoice-repository'
212
+ import { createTestDatabase, cleanupTestData } from '../../test-utils/database'
213
+
214
+ describe('DrizzleInvoiceRepository', () => {
215
+ let repository: DrizzleInvoiceRepository
216
+ let testDb: Awaited<ReturnType<typeof createTestDatabase>>
217
+
218
+ beforeAll(async () => {
219
+ testDb = await createTestDatabase()
220
+ repository = new DrizzleInvoiceRepository(testDb.db)
221
+ })
222
+
223
+ afterAll(async () => {
224
+ await testDb.cleanup()
225
+ })
226
+
227
+ beforeEach(async () => {
228
+ await cleanupTestData(testDb.db)
229
+ })
230
+
231
+ it('saves and retrieves invoice', async () => {
232
+ const invoice = Invoice.create({
233
+ customerId: 'cust-123',
234
+ lineItems: [{ description: 'Test', amount: 100 }],
235
+ })
236
+
237
+ await repository.save(invoice)
238
+
239
+ const retrieved = await repository.findById(invoice.id)
240
+ expect(retrieved).toBeDefined()
241
+ expect(retrieved?.total).toBe(100)
242
+ })
243
+
244
+ it('returns null for non-existent invoice', async () => {
245
+ const result = await repository.findById('non-existent-id')
246
+ expect(result).toBeNull()
247
+ })
248
+ })
249
+ ```
250
+
251
+ ### Test Database Utilities
252
+
253
+ Create a reusable test database helper:
254
+
255
+ ```typescript
256
+ // packages/my-module/src/server/test-utils/database.ts
257
+ import { drizzle } from 'drizzle-orm/node-postgres'
258
+ import { Pool } from 'pg'
259
+ import * as schema from '../adapters/schema'
260
+
261
+ export async function createTestDatabase() {
262
+ const testDbUrl =
263
+ process.env.TEST_DATABASE_URL ?? 'postgresql://postgres:postgres@localhost:5432/kuckit_test'
264
+
265
+ const pool = new Pool({ connectionString: testDbUrl })
266
+ const db = drizzle(pool, { schema })
267
+
268
+ return {
269
+ db,
270
+ pool,
271
+ cleanup: async () => {
272
+ await pool.end()
273
+ },
274
+ }
275
+ }
276
+
277
+ export async function cleanupTestData(db: ReturnType<typeof drizzle>) {
278
+ await db.delete(schema.invoices)
279
+ await db.delete(schema.lineItems)
280
+ }
281
+ ```
282
+
283
+ ### API Endpoint Tests
284
+
285
+ Test oRPC routers with a test client:
286
+
287
+ ```typescript
288
+ // packages/my-module/src/server/api/__tests__/invoices-router.test.ts
289
+ import { describe, expect, it, beforeAll, afterAll } from 'bun:test'
290
+ import { createORPCFetchClient } from '@orpc/client'
291
+ import type { InvoicesRouter } from '../invoices-router'
292
+
293
+ describe('Invoices API', () => {
294
+ let client: ReturnType<typeof createORPCFetchClient<InvoicesRouter>>
295
+ let server: any
296
+
297
+ beforeAll(async () => {
298
+ // Start test server
299
+ server = await startTestServer()
300
+ client = createORPCFetchClient<InvoicesRouter>({
301
+ baseURL: `http://localhost:${server.port}/rpc/invoices`,
302
+ })
303
+ })
304
+
305
+ afterAll(async () => {
306
+ await server.close()
307
+ })
308
+
309
+ it('creates invoice via API', async () => {
310
+ const result = await client.create({
311
+ customerId: 'cust-123',
312
+ lineItems: [{ description: 'API Test', amount: 250 }],
313
+ })
314
+
315
+ expect(result.id).toBeDefined()
316
+ expect(result.total).toBe(250)
317
+ })
318
+
319
+ it('returns 404 for non-existent invoice', async () => {
320
+ await expect(client.getById({ id: 'non-existent' })).rejects.toThrow(/not found/i)
321
+ })
322
+ })
323
+ ```
324
+
325
+ ---
326
+
327
+ ## Test Configuration
328
+
329
+ ### Bun Test Setup
330
+
331
+ Create `bunfig.toml` in your project root:
332
+
333
+ ```toml
334
+ [test]
335
+ preload = ["./test-setup.ts"]
336
+ ```
337
+
338
+ Create `test-setup.ts` for global setup:
339
+
340
+ ```typescript
341
+ // test-setup.ts
342
+ import { beforeAll, afterAll } from 'bun:test'
343
+
344
+ beforeAll(async () => {
345
+ // Global setup: start containers, seed data, etc.
346
+ console.log('🧪 Starting test suite...')
347
+ })
348
+
349
+ afterAll(async () => {
350
+ // Global cleanup
351
+ console.log('✅ Test suite complete')
352
+ })
353
+ ```
354
+
355
+ ### Environment Variables for Tests
356
+
357
+ Create `.env.test`:
358
+
359
+ ```bash
360
+ # Test database (separate from dev)
361
+ DATABASE_URL=postgresql://postgres:postgres@localhost:5432/kuckit_test
362
+ TEST_DATABASE_URL=postgresql://postgres:postgres@localhost:5432/kuckit_test
363
+
364
+ # Auth (test values)
365
+ BETTER_AUTH_SECRET=test-secret-for-testing-only
366
+ BETTER_AUTH_URL=http://localhost:3000
367
+
368
+ # Disable external services in tests
369
+ DISABLE_EXTERNAL_APIS=true
370
+ ```
371
+
372
+ Load in tests:
373
+
374
+ ```typescript
375
+ import { beforeAll } from 'bun:test'
376
+
377
+ beforeAll(() => {
378
+ // Load test environment
379
+ process.loadEnvFile('.env.test')
380
+ })
381
+ ```
382
+
383
+ ---
384
+
385
+ ## Test Patterns
386
+
387
+ ### Factories for Test Data
388
+
389
+ Create factories to generate test data:
390
+
391
+ ```typescript
392
+ // packages/my-module/src/server/test-utils/factories.ts
393
+ import { Invoice } from '../domain/entities/invoice'
394
+
395
+ let invoiceCounter = 0
396
+
397
+ export function createTestInvoice(overrides: Partial<InvoiceProps> = {}): Invoice {
398
+ invoiceCounter++
399
+ return Invoice.create({
400
+ id: `test-invoice-${invoiceCounter}`,
401
+ customerId: `test-customer-${invoiceCounter}`,
402
+ lineItems: [{ description: 'Test Item', amount: 100 }],
403
+ ...overrides,
404
+ })
405
+ }
406
+
407
+ export function createTestLineItem(overrides: Partial<LineItemProps> = {}) {
408
+ return {
409
+ description: 'Test Line Item',
410
+ amount: 100,
411
+ quantity: 1,
412
+ ...overrides,
413
+ }
414
+ }
415
+ ```
416
+
417
+ ### Snapshot Testing
418
+
419
+ Use snapshots for complex outputs:
420
+
421
+ ```typescript
422
+ import { describe, expect, it } from 'bun:test'
423
+
424
+ describe('Invoice formatting', () => {
425
+ it('formats invoice as JSON', () => {
426
+ const invoice = createTestInvoice({
427
+ lineItems: [
428
+ { description: 'Service A', amount: 100 },
429
+ { description: 'Service B', amount: 200 },
430
+ ],
431
+ })
432
+
433
+ expect(invoice.toJSON()).toMatchSnapshot()
434
+ })
435
+ })
436
+ ```
437
+
438
+ ### Testing Async Operations
439
+
440
+ ```typescript
441
+ import { describe, expect, it } from 'bun:test'
442
+
443
+ describe('Async operations', () => {
444
+ it('handles timeout correctly', async () => {
445
+ const slowOperation = new Promise((resolve) => setTimeout(() => resolve('done'), 100))
446
+
447
+ const result = await slowOperation
448
+ expect(result).toBe('done')
449
+ })
450
+
451
+ it('rejects on error', async () => {
452
+ const failingOperation = Promise.reject(new Error('Failed'))
453
+
454
+ await expect(failingOperation).rejects.toThrow('Failed')
455
+ })
456
+ })
457
+ ```
458
+
459
+ ---
460
+
461
+ ## CI/CD Integration
462
+
463
+ ### GitHub Actions Workflow
464
+
465
+ ```yaml
466
+ # .github/workflows/test.yml
467
+ name: Tests
468
+
469
+ on:
470
+ push:
471
+ branches: [main]
472
+ pull_request:
473
+ branches: [main]
474
+
475
+ jobs:
476
+ test:
477
+ runs-on: ubuntu-latest
478
+
479
+ services:
480
+ postgres:
481
+ image: postgres:15
482
+ env:
483
+ POSTGRES_PASSWORD: postgres
484
+ POSTGRES_DB: kuckit_test
485
+ ports:
486
+ - 5432:5432
487
+ options: >-
488
+ --health-cmd pg_isready
489
+ --health-interval 10s
490
+ --health-timeout 5s
491
+ --health-retries 5
492
+
493
+ steps:
494
+ - uses: actions/checkout@v4
495
+
496
+ - uses: oven-sh/setup-bun@v2
497
+ with:
498
+ bun-version: latest
499
+
500
+ - name: Install dependencies
501
+ run: bun install
502
+
503
+ - name: Run type checks
504
+ run: bun run check-types
505
+
506
+ - name: Run tests
507
+ run: bun test --coverage
508
+ env:
509
+ DATABASE_URL: postgresql://postgres:postgres@localhost:5432/kuckit_test
510
+ BETTER_AUTH_SECRET: test-secret
511
+ BETTER_AUTH_URL: http://localhost:3000
512
+
513
+ - name: Upload coverage
514
+ uses: codecov/codecov-action@v4
515
+ with:
516
+ files: ./coverage/coverage.json
517
+ ```
518
+
519
+ ### Pre-commit Hooks
520
+
521
+ Add to `.husky/pre-commit`:
522
+
523
+ ```bash
524
+ #!/usr/bin/env sh
525
+ . "$(dirname -- "$0")/_/husky.sh"
526
+
527
+ bun run check-types
528
+ bun test --bail
529
+ ```
530
+
531
+ ---
532
+
533
+ ## Debugging Tests
534
+
535
+ ### Verbose Output
536
+
537
+ ```bash
538
+ # Run with verbose logging
539
+ bun test --verbose
540
+
541
+ # Run single test file
542
+ bun test packages/my-module/src/server/usecases/create-invoice.test.ts
543
+ ```
544
+
545
+ ### Debug Mode
546
+
547
+ ```typescript
548
+ import { describe, it } from 'bun:test'
549
+
550
+ describe('Debugging', () => {
551
+ it('logs intermediate values', async () => {
552
+ const result = await someOperation()
553
+
554
+ // Debug output
555
+ console.log('Result:', JSON.stringify(result, null, 2))
556
+
557
+ expect(result).toBeDefined()
558
+ })
559
+ })
560
+ ```
561
+
562
+ ### Inspecting Test Database
563
+
564
+ ```bash
565
+ # Connect to test database
566
+ psql postgresql://postgres:postgres@localhost:5432/kuckit_test
567
+
568
+ # Check table contents
569
+ SELECT * FROM invoices LIMIT 10;
570
+ ```
571
+
572
+ ---
573
+
574
+ ## Related Documentation
575
+
576
+ - [Module Testing Guide](./MODULE_TESTING.md) - Testing modules before npm publish
577
+ - [Module Development](../packages/sdk/docs/MODULE_DEVELOPMENT.md) - Building modules
578
+ - [Architecture Overview](./ARCHITECTURE.md) - Clean Architecture layers
579
+ - [Troubleshooting](./TROUBLESHOOTING.md) - Common issues and fixes