@su-record/vibe 2.3.0 → 2.3.2

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 (98) hide show
  1. package/.claude/settings.json +35 -35
  2. package/.claude/settings.local.json +24 -25
  3. package/.claude/vibe/constitution.md +184 -184
  4. package/.claude/vibe/rules/core/communication-guide.md +104 -104
  5. package/.claude/vibe/rules/core/development-philosophy.md +52 -52
  6. package/.claude/vibe/rules/core/quick-start.md +120 -120
  7. package/.claude/vibe/rules/languages/dart-flutter.md +509 -509
  8. package/.claude/vibe/rules/languages/go.md +396 -396
  9. package/.claude/vibe/rules/languages/java-spring.md +586 -586
  10. package/.claude/vibe/rules/languages/kotlin-android.md +491 -491
  11. package/.claude/vibe/rules/languages/python-django.md +371 -371
  12. package/.claude/vibe/rules/languages/python-fastapi.md +386 -386
  13. package/.claude/vibe/rules/languages/rust.md +425 -425
  14. package/.claude/vibe/rules/languages/swift-ios.md +516 -516
  15. package/.claude/vibe/rules/languages/typescript-nextjs.md +441 -441
  16. package/.claude/vibe/rules/languages/typescript-node.md +375 -375
  17. package/.claude/vibe/rules/languages/typescript-nuxt.md +521 -521
  18. package/.claude/vibe/rules/languages/typescript-react-native.md +446 -446
  19. package/.claude/vibe/rules/languages/typescript-react.md +525 -525
  20. package/.claude/vibe/rules/languages/typescript-vue.md +353 -353
  21. package/.claude/vibe/rules/quality/bdd-contract-testing.md +388 -388
  22. package/.claude/vibe/rules/quality/checklist.md +276 -276
  23. package/.claude/vibe/rules/quality/testing-strategy.md +437 -437
  24. package/.claude/vibe/rules/standards/anti-patterns.md +369 -369
  25. package/.claude/vibe/rules/standards/code-structure.md +291 -291
  26. package/.claude/vibe/rules/standards/complexity-metrics.md +312 -312
  27. package/.claude/vibe/rules/standards/naming-conventions.md +198 -198
  28. package/.claude/vibe/setup.sh +31 -31
  29. package/.claude/vibe/templates/constitution-template.md +184 -184
  30. package/.claude/vibe/templates/contract-backend-template.md +517 -517
  31. package/.claude/vibe/templates/contract-frontend-template.md +594 -594
  32. package/.claude/vibe/templates/feature-template.md +96 -96
  33. package/.claude/vibe/templates/spec-template.md +199 -199
  34. package/CLAUDE.md +345 -323
  35. package/LICENSE +21 -21
  36. package/README.md +744 -724
  37. package/agents/compounder.md +261 -261
  38. package/agents/diagrammer.md +178 -178
  39. package/agents/e2e-tester.md +266 -266
  40. package/agents/explorer.md +48 -48
  41. package/agents/implementer.md +53 -53
  42. package/agents/research/best-practices-agent.md +139 -139
  43. package/agents/research/codebase-patterns-agent.md +147 -147
  44. package/agents/research/framework-docs-agent.md +181 -181
  45. package/agents/research/security-advisory-agent.md +167 -167
  46. package/agents/review/architecture-reviewer.md +107 -107
  47. package/agents/review/complexity-reviewer.md +116 -116
  48. package/agents/review/data-integrity-reviewer.md +88 -88
  49. package/agents/review/git-history-reviewer.md +103 -103
  50. package/agents/review/performance-reviewer.md +86 -86
  51. package/agents/review/python-reviewer.md +152 -152
  52. package/agents/review/rails-reviewer.md +139 -139
  53. package/agents/review/react-reviewer.md +144 -144
  54. package/agents/review/security-reviewer.md +80 -80
  55. package/agents/review/simplicity-reviewer.md +140 -140
  56. package/agents/review/test-coverage-reviewer.md +116 -116
  57. package/agents/review/typescript-reviewer.md +127 -127
  58. package/agents/searcher.md +54 -54
  59. package/agents/simplifier.md +119 -119
  60. package/agents/tester.md +49 -49
  61. package/agents/ui-previewer.md +137 -137
  62. package/commands/vibe.analyze.md +245 -180
  63. package/commands/vibe.reason.md +223 -183
  64. package/commands/vibe.review.md +200 -136
  65. package/commands/vibe.run.md +838 -836
  66. package/commands/vibe.spec.md +419 -383
  67. package/commands/vibe.utils.md +101 -101
  68. package/commands/vibe.verify.md +282 -241
  69. package/dist/cli/index.js +385 -385
  70. package/dist/lib/MemoryManager.d.ts.map +1 -1
  71. package/dist/lib/MemoryManager.js +119 -114
  72. package/dist/lib/MemoryManager.js.map +1 -1
  73. package/dist/lib/PythonParser.js +108 -108
  74. package/dist/lib/gemini-mcp.js +15 -15
  75. package/dist/lib/gemini-oauth.js +35 -35
  76. package/dist/lib/gpt-mcp.js +17 -17
  77. package/dist/lib/gpt-oauth.js +44 -44
  78. package/dist/tools/analytics/getUsageAnalytics.js +12 -12
  79. package/dist/tools/index.d.ts +50 -0
  80. package/dist/tools/index.d.ts.map +1 -0
  81. package/dist/tools/index.js +61 -0
  82. package/dist/tools/index.js.map +1 -0
  83. package/dist/tools/memory/createMemoryTimeline.js +10 -10
  84. package/dist/tools/memory/getMemoryGraph.js +12 -12
  85. package/dist/tools/memory/getSessionContext.js +9 -9
  86. package/dist/tools/memory/linkMemories.js +14 -14
  87. package/dist/tools/memory/listMemories.js +4 -4
  88. package/dist/tools/memory/recallMemory.js +4 -4
  89. package/dist/tools/memory/saveMemory.js +4 -4
  90. package/dist/tools/memory/searchMemoriesAdvanced.js +22 -22
  91. package/dist/tools/planning/generatePrd.js +46 -46
  92. package/dist/tools/prompt/enhancePromptGemini.js +160 -160
  93. package/dist/tools/reasoning/applyReasoningFramework.js +56 -56
  94. package/dist/tools/semantic/analyzeDependencyGraph.js +12 -12
  95. package/hooks/hooks.json +121 -103
  96. package/package.json +73 -69
  97. package/skills/git-worktree.md +178 -178
  98. package/skills/priority-todos.md +236 -236
@@ -1,517 +1,517 @@
1
- # Backend Contract Tests: {기능명}
2
-
3
- **Generated from**: `specs/{기능명}.md` (Section 6: API 계약)
4
- **Framework**: {FastAPI | Django | Express | NestJS}
5
- **Language**: {Python | TypeScript | JavaScript}
6
- **Priority**: {HIGH | MEDIUM | LOW}
7
-
8
- ---
9
-
10
- ## Overview
11
-
12
- Contract Testing은 **API 계약(스키마)을 검증**합니다:
13
- - ✅ Request/Response 스키마 준수
14
- - ✅ 상태 코드 일치
15
- - ✅ 헤더 검증
16
- - ✅ 데이터 타입 및 필수 필드 확인
17
-
18
- **Consumer → Provider 계약 보장** (Pact 패턴)
19
-
20
- ---
21
-
22
- ## API Contracts
23
-
24
- ### Contract 1: {엔드포인트 이름}
25
-
26
- **Endpoint**: `POST /api/v1/{resource}`
27
- **Mapped to**: REQ-001 in SPEC
28
-
29
- #### Request Contract
30
-
31
- ```json
32
- {
33
- "method": "POST",
34
- "path": "/api/v1/{resource}",
35
- "headers": {
36
- "Content-Type": "application/json",
37
- "Authorization": "Bearer {token}"
38
- },
39
- "body": {
40
- "field1": "string (required)",
41
- "field2": "integer (required)",
42
- "field3": "boolean (optional)"
43
- }
44
- }
45
- ```
46
-
47
- **JSON Schema**:
48
- ```json
49
- {
50
- "$schema": "http://json-schema.org/draft-07/schema#",
51
- "type": "object",
52
- "required": ["field1", "field2"],
53
- "properties": {
54
- "field1": {
55
- "type": "string",
56
- "minLength": 1,
57
- "maxLength": 100
58
- },
59
- "field2": {
60
- "type": "integer",
61
- "minimum": 0
62
- },
63
- "field3": {
64
- "type": "boolean",
65
- "default": false
66
- }
67
- },
68
- "additionalProperties": false
69
- }
70
- ```
71
-
72
- #### Response Contract (Success)
73
-
74
- ```json
75
- {
76
- "status": 201,
77
- "headers": {
78
- "Content-Type": "application/json"
79
- },
80
- "body": {
81
- "id": "uuid",
82
- "field1": "string",
83
- "field2": "integer",
84
- "field3": "boolean",
85
- "created_at": "datetime (ISO 8601)"
86
- }
87
- }
88
- ```
89
-
90
- **JSON Schema**:
91
- ```json
92
- {
93
- "$schema": "http://json-schema.org/draft-07/schema#",
94
- "type": "object",
95
- "required": ["id", "field1", "field2", "created_at"],
96
- "properties": {
97
- "id": {
98
- "type": "string",
99
- "format": "uuid"
100
- },
101
- "field1": {
102
- "type": "string"
103
- },
104
- "field2": {
105
- "type": "integer"
106
- },
107
- "field3": {
108
- "type": "boolean"
109
- },
110
- "created_at": {
111
- "type": "string",
112
- "format": "date-time"
113
- }
114
- }
115
- }
116
- ```
117
-
118
- #### Response Contract (Error)
119
-
120
- ```json
121
- {
122
- "status": 400,
123
- "body": {
124
- "error": "string",
125
- "message": "string",
126
- "details": ["array of strings (optional)"]
127
- }
128
- }
129
- ```
130
-
131
- ---
132
-
133
- ## Implementation
134
-
135
- ### Python (FastAPI + Pydantic)
136
-
137
- **File**: `tests/contract/test_{기능명}_contract.py`
138
-
139
- ```python
140
- import pytest
141
- from fastapi.testclient import TestClient
142
- from jsonschema import validate, ValidationError
143
- from app.main import app
144
-
145
- client = TestClient(app)
146
-
147
- # JSON Schema definitions
148
- REQUEST_SCHEMA = {
149
- "$schema": "http://json-schema.org/draft-07/schema#",
150
- "type": "object",
151
- "required": ["field1", "field2"],
152
- "properties": {
153
- "field1": {"type": "string", "minLength": 1, "maxLength": 100},
154
- "field2": {"type": "integer", "minimum": 0},
155
- "field3": {"type": "boolean", "default": False}
156
- },
157
- "additionalProperties": False
158
- }
159
-
160
- RESPONSE_SCHEMA = {
161
- "$schema": "http://json-schema.org/draft-07/schema#",
162
- "type": "object",
163
- "required": ["id", "field1", "field2", "created_at"],
164
- "properties": {
165
- "id": {"type": "string", "format": "uuid"},
166
- "field1": {"type": "string"},
167
- "field2": {"type": "integer"},
168
- "field3": {"type": "boolean"},
169
- "created_at": {"type": "string", "format": "date-time"}
170
- }
171
- }
172
-
173
- class TestCreateResourceContract:
174
- """Contract tests for POST /api/v1/resource"""
175
-
176
- def test_request_schema_valid(self):
177
- """Request body matches contract schema"""
178
- payload = {
179
- "field1": "test value",
180
- "field2": 42,
181
- "field3": True
182
- }
183
- # Should not raise ValidationError
184
- validate(instance=payload, schema=REQUEST_SCHEMA)
185
-
186
- def test_request_schema_invalid_missing_required(self):
187
- """Request with missing required field is rejected"""
188
- payload = {
189
- "field1": "test value"
190
- # Missing field2
191
- }
192
- with pytest.raises(ValidationError):
193
- validate(instance=payload, schema=REQUEST_SCHEMA)
194
-
195
- def test_response_schema_success(self):
196
- """Response body matches contract schema (201 Created)"""
197
- payload = {
198
- "field1": "test value",
199
- "field2": 42,
200
- "field3": True
201
- }
202
- response = client.post(
203
- "/api/v1/resource",
204
- json=payload,
205
- headers={"Authorization": "Bearer test-token"}
206
- )
207
-
208
- # Status code contract
209
- assert response.status_code == 201
210
-
211
- # Response schema contract
212
- response_data = response.json()
213
- validate(instance=response_data, schema=RESPONSE_SCHEMA)
214
-
215
- # Data contract
216
- assert response_data["field1"] == payload["field1"]
217
- assert response_data["field2"] == payload["field2"]
218
- assert response_data["field3"] == payload["field3"]
219
-
220
- def test_response_schema_error(self):
221
- """Error response matches contract schema (400 Bad Request)"""
222
- payload = {
223
- "field1": "", # Invalid: empty string
224
- "field2": -1 # Invalid: negative
225
- }
226
- response = client.post(
227
- "/api/v1/resource",
228
- json=payload,
229
- headers={"Authorization": "Bearer test-token"}
230
- )
231
-
232
- # Status code contract
233
- assert response.status_code == 400
234
-
235
- # Error schema contract
236
- error_data = response.json()
237
- assert "error" in error_data
238
- assert "message" in error_data
239
- assert isinstance(error_data["message"], str)
240
-
241
- def test_headers_contract(self):
242
- """Response headers match contract"""
243
- payload = {
244
- "field1": "test value",
245
- "field2": 42
246
- }
247
- response = client.post(
248
- "/api/v1/resource",
249
- json=payload,
250
- headers={"Authorization": "Bearer test-token"}
251
- )
252
-
253
- assert response.headers["Content-Type"] == "application/json"
254
-
255
- @pytest.mark.parametrize("invalid_payload,expected_error", [
256
- ({"field1": "x" * 101, "field2": 42}, "field1 too long"),
257
- ({"field1": "test", "field2": -1}, "field2 must be positive"),
258
- ({"field2": 42}, "field1 is required"),
259
- ])
260
- def test_validation_errors(self, invalid_payload, expected_error):
261
- """Contract validation errors are properly handled"""
262
- response = client.post(
263
- "/api/v1/resource",
264
- json=invalid_payload,
265
- headers={"Authorization": "Bearer test-token"}
266
- )
267
- assert response.status_code == 400
268
- ```
269
-
270
- **Run**:
271
- ```bash
272
- pytest tests/contract/test_{기능명}_contract.py -v --tb=short
273
- ```
274
-
275
- ---
276
-
277
- ### Python (Pact - Consumer-Driven Contracts)
278
-
279
- **File**: `tests/pact/consumer_test_{기능명}.py`
280
-
281
- ```python
282
- import pytest
283
- from pact import Consumer, Provider, Like, EachLike, Format
284
-
285
- pact = Consumer('FrontendApp').has_pact_with(Provider('BackendAPI'))
286
-
287
- @pytest.fixture(scope='module')
288
- def setup_pact():
289
- pact.start_service()
290
- yield
291
- pact.stop_service()
292
-
293
- def test_create_resource_contract(setup_pact):
294
- """Consumer expects provider to create resource"""
295
- expected = {
296
- 'id': Format().uuid,
297
- 'field1': Like('test value'),
298
- 'field2': Like(42),
299
- 'field3': Like(True),
300
- 'created_at': Format().iso_8601_datetime
301
- }
302
-
303
- (pact
304
- .given('user is authenticated')
305
- .upon_receiving('a request to create resource')
306
- .with_request('POST', '/api/v1/resource',
307
- headers={'Authorization': Like('Bearer token')},
308
- body={
309
- 'field1': 'test value',
310
- 'field2': 42,
311
- 'field3': True
312
- })
313
- .will_respond_with(201, body=expected))
314
-
315
- with pact:
316
- # Test consumer code
317
- result = api_client.create_resource(field1='test value', field2=42)
318
- assert result['id'] is not None
319
- assert result['field1'] == 'test value'
320
- ```
321
-
322
- **Generate Pact file**:
323
- ```bash
324
- pytest tests/pact/ --pact-broker-url=https://your-pact-broker.com
325
- ```
326
-
327
- ---
328
-
329
- ### TypeScript (NestJS + Jest)
330
-
331
- **File**: `test/contract/{기능명}.contract.spec.ts`
332
-
333
- ```typescript
334
- import { Test } from '@nestjs/testing';
335
- import { INestApplication, ValidationPipe } from '@nestjs/common';
336
- import * as request from 'supertest';
337
- import { AppModule } from '../src/app.module';
338
- import Ajv from 'ajv';
339
- import addFormats from 'ajv-formats';
340
-
341
- describe('Create Resource Contract (e2e)', () => {
342
- let app: INestApplication;
343
- const ajv = new Ajv();
344
- addFormats(ajv);
345
-
346
- const requestSchema = {
347
- type: 'object',
348
- required: ['field1', 'field2'],
349
- properties: {
350
- field1: { type: 'string', minLength: 1, maxLength: 100 },
351
- field2: { type: 'integer', minimum: 0 },
352
- field3: { type: 'boolean' }
353
- },
354
- additionalProperties: false
355
- };
356
-
357
- const responseSchema = {
358
- type: 'object',
359
- required: ['id', 'field1', 'field2', 'createdAt'],
360
- properties: {
361
- id: { type: 'string', format: 'uuid' },
362
- field1: { type: 'string' },
363
- field2: { type: 'integer' },
364
- field3: { type: 'boolean' },
365
- createdAt: { type: 'string', format: 'date-time' }
366
- }
367
- };
368
-
369
- beforeAll(async () => {
370
- const moduleFixture = await Test.createTestingModule({
371
- imports: [AppModule],
372
- }).compile();
373
-
374
- app = moduleFixture.createNestApplication();
375
- app.useGlobalPipes(new ValidationPipe());
376
- await app.init();
377
- });
378
-
379
- afterAll(async () => {
380
- await app.close();
381
- });
382
-
383
- it('POST /api/v1/resource - validates request schema', () => {
384
- const payload = {
385
- field1: 'test value',
386
- field2: 42,
387
- field3: true
388
- };
389
-
390
- const validate = ajv.compile(requestSchema);
391
- expect(validate(payload)).toBe(true);
392
- });
393
-
394
- it('POST /api/v1/resource - validates response schema (201)', async () => {
395
- const response = await request(app.getHttpServer())
396
- .post('/api/v1/resource')
397
- .set('Authorization', 'Bearer test-token')
398
- .send({
399
- field1: 'test value',
400
- field2: 42,
401
- field3: true
402
- })
403
- .expect(201)
404
- .expect('Content-Type', /json/);
405
-
406
- const validate = ajv.compile(responseSchema);
407
- expect(validate(response.body)).toBe(true);
408
- });
409
-
410
- it('POST /api/v1/resource - returns 400 for invalid request', async () => {
411
- await request(app.getHttpServer())
412
- .post('/api/v1/resource')
413
- .set('Authorization', 'Bearer test-token')
414
- .send({
415
- field1: '', // Invalid
416
- field2: -1 // Invalid
417
- })
418
- .expect(400);
419
- });
420
- });
421
- ```
422
-
423
- **Run**:
424
- ```bash
425
- npm test -- test/contract/{기능명}.contract.spec.ts
426
- ```
427
-
428
- ---
429
-
430
- ## Contract Testing Strategy
431
-
432
- ### 1. Provider Tests (Backend)
433
- ```bash
434
- # Run all contract tests
435
- pytest tests/contract/ -v
436
-
437
- # Run specific contract
438
- pytest tests/contract/test_{기능명}_contract.py
439
-
440
- # Generate Pact file for consumer
441
- pytest tests/pact/ --pact-broker-url=...
442
- ```
443
-
444
- ### 2. Consumer Tests (Frontend)
445
- ```bash
446
- # Verify against provider contract
447
- npm run test:contract -- --pact-broker-url=...
448
- ```
449
-
450
- ### 3. CI/CD Integration
451
- ```yaml
452
- # .github/workflows/contract-tests.yml
453
- name: Contract Tests
454
-
455
- on: [pull_request]
456
-
457
- jobs:
458
- contract-tests:
459
- runs-on: ubuntu-latest
460
- steps:
461
- - uses: actions/checkout@v2
462
- - name: Run provider contract tests
463
- run: pytest tests/contract/ -v
464
- - name: Publish Pact
465
- run: pytest tests/pact/ --pact-broker-url=${{ secrets.PACT_BROKER_URL }}
466
- ```
467
-
468
- ---
469
-
470
- ## Coverage Mapping
471
-
472
- | Contract | SPEC REQ | Endpoints | Status |
473
- |----------|----------|-----------|--------|
474
- | Create Resource | REQ-001 | POST /api/v1/resource | ⬜ |
475
- | Get Resource | REQ-002 | GET /api/v1/resource/:id | ⬜ |
476
- | Update Resource | REQ-003 | PATCH /api/v1/resource/:id | ⬜ |
477
-
478
- **Coverage**: 0 / {총 계약 수} (0%)
479
-
480
- ---
481
-
482
- ## Best Practices
483
-
484
- 1. **Test Contract, Not Implementation**
485
- - ✅ 스키마 준수 확인
486
- - ❌ 비즈니스 로직 테스트 금지
487
-
488
- 2. **Provider-First vs Consumer-First**
489
- - Provider-First: API 먼저 정의 → Contract 테스트 작성
490
- - Consumer-First: Frontend 요구사항 → Pact 작성 → Provider 구현
491
-
492
- 3. **Version Control**
493
- - API 버전별 Contract 파일 관리
494
- - Breaking Changes 감지
495
-
496
- 4. **Pact Broker 활용**
497
- - Contract 중앙 관리
498
- - Consumer-Provider 매칭
499
- - CI/CD 자동화
500
-
501
- ---
502
-
503
- ## Next Steps
504
-
505
- ```bash
506
- # 1. Contract 테스트 작성
507
- vibe contract "{기능명}"
508
-
509
- # 2. Provider 구현
510
- vibe run "Task 1-1"
511
-
512
- # 3. Contract 검증
513
- vibe test "{기능명}" --contract
514
-
515
- # 4. Pact 발행 (선택)
516
- pytest tests/pact/ --pact-broker-url=...
517
- ```
1
+ # Backend Contract Tests: {기능명}
2
+
3
+ **Generated from**: `specs/{기능명}.md` (Section 6: API 계약)
4
+ **Framework**: {FastAPI | Django | Express | NestJS}
5
+ **Language**: {Python | TypeScript | JavaScript}
6
+ **Priority**: {HIGH | MEDIUM | LOW}
7
+
8
+ ---
9
+
10
+ ## Overview
11
+
12
+ Contract Testing은 **API 계약(스키마)을 검증**합니다:
13
+ - ✅ Request/Response 스키마 준수
14
+ - ✅ 상태 코드 일치
15
+ - ✅ 헤더 검증
16
+ - ✅ 데이터 타입 및 필수 필드 확인
17
+
18
+ **Consumer → Provider 계약 보장** (Pact 패턴)
19
+
20
+ ---
21
+
22
+ ## API Contracts
23
+
24
+ ### Contract 1: {엔드포인트 이름}
25
+
26
+ **Endpoint**: `POST /api/v1/{resource}`
27
+ **Mapped to**: REQ-001 in SPEC
28
+
29
+ #### Request Contract
30
+
31
+ ```json
32
+ {
33
+ "method": "POST",
34
+ "path": "/api/v1/{resource}",
35
+ "headers": {
36
+ "Content-Type": "application/json",
37
+ "Authorization": "Bearer {token}"
38
+ },
39
+ "body": {
40
+ "field1": "string (required)",
41
+ "field2": "integer (required)",
42
+ "field3": "boolean (optional)"
43
+ }
44
+ }
45
+ ```
46
+
47
+ **JSON Schema**:
48
+ ```json
49
+ {
50
+ "$schema": "http://json-schema.org/draft-07/schema#",
51
+ "type": "object",
52
+ "required": ["field1", "field2"],
53
+ "properties": {
54
+ "field1": {
55
+ "type": "string",
56
+ "minLength": 1,
57
+ "maxLength": 100
58
+ },
59
+ "field2": {
60
+ "type": "integer",
61
+ "minimum": 0
62
+ },
63
+ "field3": {
64
+ "type": "boolean",
65
+ "default": false
66
+ }
67
+ },
68
+ "additionalProperties": false
69
+ }
70
+ ```
71
+
72
+ #### Response Contract (Success)
73
+
74
+ ```json
75
+ {
76
+ "status": 201,
77
+ "headers": {
78
+ "Content-Type": "application/json"
79
+ },
80
+ "body": {
81
+ "id": "uuid",
82
+ "field1": "string",
83
+ "field2": "integer",
84
+ "field3": "boolean",
85
+ "created_at": "datetime (ISO 8601)"
86
+ }
87
+ }
88
+ ```
89
+
90
+ **JSON Schema**:
91
+ ```json
92
+ {
93
+ "$schema": "http://json-schema.org/draft-07/schema#",
94
+ "type": "object",
95
+ "required": ["id", "field1", "field2", "created_at"],
96
+ "properties": {
97
+ "id": {
98
+ "type": "string",
99
+ "format": "uuid"
100
+ },
101
+ "field1": {
102
+ "type": "string"
103
+ },
104
+ "field2": {
105
+ "type": "integer"
106
+ },
107
+ "field3": {
108
+ "type": "boolean"
109
+ },
110
+ "created_at": {
111
+ "type": "string",
112
+ "format": "date-time"
113
+ }
114
+ }
115
+ }
116
+ ```
117
+
118
+ #### Response Contract (Error)
119
+
120
+ ```json
121
+ {
122
+ "status": 400,
123
+ "body": {
124
+ "error": "string",
125
+ "message": "string",
126
+ "details": ["array of strings (optional)"]
127
+ }
128
+ }
129
+ ```
130
+
131
+ ---
132
+
133
+ ## Implementation
134
+
135
+ ### Python (FastAPI + Pydantic)
136
+
137
+ **File**: `tests/contract/test_{기능명}_contract.py`
138
+
139
+ ```python
140
+ import pytest
141
+ from fastapi.testclient import TestClient
142
+ from jsonschema import validate, ValidationError
143
+ from app.main import app
144
+
145
+ client = TestClient(app)
146
+
147
+ # JSON Schema definitions
148
+ REQUEST_SCHEMA = {
149
+ "$schema": "http://json-schema.org/draft-07/schema#",
150
+ "type": "object",
151
+ "required": ["field1", "field2"],
152
+ "properties": {
153
+ "field1": {"type": "string", "minLength": 1, "maxLength": 100},
154
+ "field2": {"type": "integer", "minimum": 0},
155
+ "field3": {"type": "boolean", "default": False}
156
+ },
157
+ "additionalProperties": False
158
+ }
159
+
160
+ RESPONSE_SCHEMA = {
161
+ "$schema": "http://json-schema.org/draft-07/schema#",
162
+ "type": "object",
163
+ "required": ["id", "field1", "field2", "created_at"],
164
+ "properties": {
165
+ "id": {"type": "string", "format": "uuid"},
166
+ "field1": {"type": "string"},
167
+ "field2": {"type": "integer"},
168
+ "field3": {"type": "boolean"},
169
+ "created_at": {"type": "string", "format": "date-time"}
170
+ }
171
+ }
172
+
173
+ class TestCreateResourceContract:
174
+ """Contract tests for POST /api/v1/resource"""
175
+
176
+ def test_request_schema_valid(self):
177
+ """Request body matches contract schema"""
178
+ payload = {
179
+ "field1": "test value",
180
+ "field2": 42,
181
+ "field3": True
182
+ }
183
+ # Should not raise ValidationError
184
+ validate(instance=payload, schema=REQUEST_SCHEMA)
185
+
186
+ def test_request_schema_invalid_missing_required(self):
187
+ """Request with missing required field is rejected"""
188
+ payload = {
189
+ "field1": "test value"
190
+ # Missing field2
191
+ }
192
+ with pytest.raises(ValidationError):
193
+ validate(instance=payload, schema=REQUEST_SCHEMA)
194
+
195
+ def test_response_schema_success(self):
196
+ """Response body matches contract schema (201 Created)"""
197
+ payload = {
198
+ "field1": "test value",
199
+ "field2": 42,
200
+ "field3": True
201
+ }
202
+ response = client.post(
203
+ "/api/v1/resource",
204
+ json=payload,
205
+ headers={"Authorization": "Bearer test-token"}
206
+ )
207
+
208
+ # Status code contract
209
+ assert response.status_code == 201
210
+
211
+ # Response schema contract
212
+ response_data = response.json()
213
+ validate(instance=response_data, schema=RESPONSE_SCHEMA)
214
+
215
+ # Data contract
216
+ assert response_data["field1"] == payload["field1"]
217
+ assert response_data["field2"] == payload["field2"]
218
+ assert response_data["field3"] == payload["field3"]
219
+
220
+ def test_response_schema_error(self):
221
+ """Error response matches contract schema (400 Bad Request)"""
222
+ payload = {
223
+ "field1": "", # Invalid: empty string
224
+ "field2": -1 # Invalid: negative
225
+ }
226
+ response = client.post(
227
+ "/api/v1/resource",
228
+ json=payload,
229
+ headers={"Authorization": "Bearer test-token"}
230
+ )
231
+
232
+ # Status code contract
233
+ assert response.status_code == 400
234
+
235
+ # Error schema contract
236
+ error_data = response.json()
237
+ assert "error" in error_data
238
+ assert "message" in error_data
239
+ assert isinstance(error_data["message"], str)
240
+
241
+ def test_headers_contract(self):
242
+ """Response headers match contract"""
243
+ payload = {
244
+ "field1": "test value",
245
+ "field2": 42
246
+ }
247
+ response = client.post(
248
+ "/api/v1/resource",
249
+ json=payload,
250
+ headers={"Authorization": "Bearer test-token"}
251
+ )
252
+
253
+ assert response.headers["Content-Type"] == "application/json"
254
+
255
+ @pytest.mark.parametrize("invalid_payload,expected_error", [
256
+ ({"field1": "x" * 101, "field2": 42}, "field1 too long"),
257
+ ({"field1": "test", "field2": -1}, "field2 must be positive"),
258
+ ({"field2": 42}, "field1 is required"),
259
+ ])
260
+ def test_validation_errors(self, invalid_payload, expected_error):
261
+ """Contract validation errors are properly handled"""
262
+ response = client.post(
263
+ "/api/v1/resource",
264
+ json=invalid_payload,
265
+ headers={"Authorization": "Bearer test-token"}
266
+ )
267
+ assert response.status_code == 400
268
+ ```
269
+
270
+ **Run**:
271
+ ```bash
272
+ pytest tests/contract/test_{기능명}_contract.py -v --tb=short
273
+ ```
274
+
275
+ ---
276
+
277
+ ### Python (Pact - Consumer-Driven Contracts)
278
+
279
+ **File**: `tests/pact/consumer_test_{기능명}.py`
280
+
281
+ ```python
282
+ import pytest
283
+ from pact import Consumer, Provider, Like, EachLike, Format
284
+
285
+ pact = Consumer('FrontendApp').has_pact_with(Provider('BackendAPI'))
286
+
287
+ @pytest.fixture(scope='module')
288
+ def setup_pact():
289
+ pact.start_service()
290
+ yield
291
+ pact.stop_service()
292
+
293
+ def test_create_resource_contract(setup_pact):
294
+ """Consumer expects provider to create resource"""
295
+ expected = {
296
+ 'id': Format().uuid,
297
+ 'field1': Like('test value'),
298
+ 'field2': Like(42),
299
+ 'field3': Like(True),
300
+ 'created_at': Format().iso_8601_datetime
301
+ }
302
+
303
+ (pact
304
+ .given('user is authenticated')
305
+ .upon_receiving('a request to create resource')
306
+ .with_request('POST', '/api/v1/resource',
307
+ headers={'Authorization': Like('Bearer token')},
308
+ body={
309
+ 'field1': 'test value',
310
+ 'field2': 42,
311
+ 'field3': True
312
+ })
313
+ .will_respond_with(201, body=expected))
314
+
315
+ with pact:
316
+ # Test consumer code
317
+ result = api_client.create_resource(field1='test value', field2=42)
318
+ assert result['id'] is not None
319
+ assert result['field1'] == 'test value'
320
+ ```
321
+
322
+ **Generate Pact file**:
323
+ ```bash
324
+ pytest tests/pact/ --pact-broker-url=https://your-pact-broker.com
325
+ ```
326
+
327
+ ---
328
+
329
+ ### TypeScript (NestJS + Jest)
330
+
331
+ **File**: `test/contract/{기능명}.contract.spec.ts`
332
+
333
+ ```typescript
334
+ import { Test } from '@nestjs/testing';
335
+ import { INestApplication, ValidationPipe } from '@nestjs/common';
336
+ import * as request from 'supertest';
337
+ import { AppModule } from '../src/app.module';
338
+ import Ajv from 'ajv';
339
+ import addFormats from 'ajv-formats';
340
+
341
+ describe('Create Resource Contract (e2e)', () => {
342
+ let app: INestApplication;
343
+ const ajv = new Ajv();
344
+ addFormats(ajv);
345
+
346
+ const requestSchema = {
347
+ type: 'object',
348
+ required: ['field1', 'field2'],
349
+ properties: {
350
+ field1: { type: 'string', minLength: 1, maxLength: 100 },
351
+ field2: { type: 'integer', minimum: 0 },
352
+ field3: { type: 'boolean' }
353
+ },
354
+ additionalProperties: false
355
+ };
356
+
357
+ const responseSchema = {
358
+ type: 'object',
359
+ required: ['id', 'field1', 'field2', 'createdAt'],
360
+ properties: {
361
+ id: { type: 'string', format: 'uuid' },
362
+ field1: { type: 'string' },
363
+ field2: { type: 'integer' },
364
+ field3: { type: 'boolean' },
365
+ createdAt: { type: 'string', format: 'date-time' }
366
+ }
367
+ };
368
+
369
+ beforeAll(async () => {
370
+ const moduleFixture = await Test.createTestingModule({
371
+ imports: [AppModule],
372
+ }).compile();
373
+
374
+ app = moduleFixture.createNestApplication();
375
+ app.useGlobalPipes(new ValidationPipe());
376
+ await app.init();
377
+ });
378
+
379
+ afterAll(async () => {
380
+ await app.close();
381
+ });
382
+
383
+ it('POST /api/v1/resource - validates request schema', () => {
384
+ const payload = {
385
+ field1: 'test value',
386
+ field2: 42,
387
+ field3: true
388
+ };
389
+
390
+ const validate = ajv.compile(requestSchema);
391
+ expect(validate(payload)).toBe(true);
392
+ });
393
+
394
+ it('POST /api/v1/resource - validates response schema (201)', async () => {
395
+ const response = await request(app.getHttpServer())
396
+ .post('/api/v1/resource')
397
+ .set('Authorization', 'Bearer test-token')
398
+ .send({
399
+ field1: 'test value',
400
+ field2: 42,
401
+ field3: true
402
+ })
403
+ .expect(201)
404
+ .expect('Content-Type', /json/);
405
+
406
+ const validate = ajv.compile(responseSchema);
407
+ expect(validate(response.body)).toBe(true);
408
+ });
409
+
410
+ it('POST /api/v1/resource - returns 400 for invalid request', async () => {
411
+ await request(app.getHttpServer())
412
+ .post('/api/v1/resource')
413
+ .set('Authorization', 'Bearer test-token')
414
+ .send({
415
+ field1: '', // Invalid
416
+ field2: -1 // Invalid
417
+ })
418
+ .expect(400);
419
+ });
420
+ });
421
+ ```
422
+
423
+ **Run**:
424
+ ```bash
425
+ npm test -- test/contract/{기능명}.contract.spec.ts
426
+ ```
427
+
428
+ ---
429
+
430
+ ## Contract Testing Strategy
431
+
432
+ ### 1. Provider Tests (Backend)
433
+ ```bash
434
+ # Run all contract tests
435
+ pytest tests/contract/ -v
436
+
437
+ # Run specific contract
438
+ pytest tests/contract/test_{기능명}_contract.py
439
+
440
+ # Generate Pact file for consumer
441
+ pytest tests/pact/ --pact-broker-url=...
442
+ ```
443
+
444
+ ### 2. Consumer Tests (Frontend)
445
+ ```bash
446
+ # Verify against provider contract
447
+ npm run test:contract -- --pact-broker-url=...
448
+ ```
449
+
450
+ ### 3. CI/CD Integration
451
+ ```yaml
452
+ # .github/workflows/contract-tests.yml
453
+ name: Contract Tests
454
+
455
+ on: [pull_request]
456
+
457
+ jobs:
458
+ contract-tests:
459
+ runs-on: ubuntu-latest
460
+ steps:
461
+ - uses: actions/checkout@v2
462
+ - name: Run provider contract tests
463
+ run: pytest tests/contract/ -v
464
+ - name: Publish Pact
465
+ run: pytest tests/pact/ --pact-broker-url=${{ secrets.PACT_BROKER_URL }}
466
+ ```
467
+
468
+ ---
469
+
470
+ ## Coverage Mapping
471
+
472
+ | Contract | SPEC REQ | Endpoints | Status |
473
+ |----------|----------|-----------|--------|
474
+ | Create Resource | REQ-001 | POST /api/v1/resource | ⬜ |
475
+ | Get Resource | REQ-002 | GET /api/v1/resource/:id | ⬜ |
476
+ | Update Resource | REQ-003 | PATCH /api/v1/resource/:id | ⬜ |
477
+
478
+ **Coverage**: 0 / {총 계약 수} (0%)
479
+
480
+ ---
481
+
482
+ ## Best Practices
483
+
484
+ 1. **Test Contract, Not Implementation**
485
+ - ✅ 스키마 준수 확인
486
+ - ❌ 비즈니스 로직 테스트 금지
487
+
488
+ 2. **Provider-First vs Consumer-First**
489
+ - Provider-First: API 먼저 정의 → Contract 테스트 작성
490
+ - Consumer-First: Frontend 요구사항 → Pact 작성 → Provider 구현
491
+
492
+ 3. **Version Control**
493
+ - API 버전별 Contract 파일 관리
494
+ - Breaking Changes 감지
495
+
496
+ 4. **Pact Broker 활용**
497
+ - Contract 중앙 관리
498
+ - Consumer-Provider 매칭
499
+ - CI/CD 자동화
500
+
501
+ ---
502
+
503
+ ## Next Steps
504
+
505
+ ```bash
506
+ # 1. Contract 테스트 작성
507
+ vibe contract "{기능명}"
508
+
509
+ # 2. Provider 구현
510
+ vibe run "Task 1-1"
511
+
512
+ # 3. Contract 검증
513
+ vibe test "{기능명}" --contract
514
+
515
+ # 4. Pact 발행 (선택)
516
+ pytest tests/pact/ --pact-broker-url=...
517
+ ```