muno-claude-plugin 1.2.0 → 1.3.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "muno-claude-plugin",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "Unleash Claude Code's full power - Complete development workflow with expert personas, TDD-based agents, and automated documentation",
5
5
  "main": "bin/cli.js",
6
6
  "bin": {
@@ -169,37 +169,33 @@ status: ready
169
169
 
170
170
  ## 출력: 인수 테스트 코드
171
171
 
172
- ### Integration Test 패턴
172
+ ### 프로젝트 언어 감지
173
+
174
+ **테스트 코드 생성 전 반드시 프로젝트의 기술 스택을 파악합니다:**
175
+
176
+ ```
177
+ 1. build.gradle.kts / build.gradle → Kotlin/Java + Spring
178
+ 2. pom.xml → Java + Spring
179
+ 3. package.json → TypeScript/JavaScript + Node.js
180
+ 4. requirements.txt / pyproject.toml → Python
181
+ 5. go.mod → Go
182
+ 6. 기존 테스트 코드 패턴 확인 → 프로젝트 컨벤션 따르기
183
+ ```
184
+
185
+ ---
186
+
187
+ ## 언어별 Integration Test 패턴
188
+
189
+ ### Kotlin + Spring Boot
173
190
 
174
191
  ```kotlin
175
- package com.example.integration.chat
176
-
177
- import org.junit.jupiter.api.*
178
- import org.springframework.beans.factory.annotation.Autowired
179
- import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
180
- import org.springframework.boot.test.context.SpringBootTest
181
- import org.springframework.http.MediaType
182
- import org.springframework.test.web.servlet.MockMvc
183
- import org.springframework.test.web.servlet.post
184
- import org.springframework.transaction.annotation.Transactional
185
-
186
- /**
187
- * STORY-001: 채팅방 관리
188
- *
189
- * TC 기반 인수 테스트 - Story 완료 검증
190
- * TC 생성 직후 작성 (Test First)
191
- */
192
192
  @SpringBootTest
193
193
  @AutoConfigureMockMvc
194
194
  @Transactional
195
195
  @DisplayName("STORY-001: 채팅방 관리 인수 테스트")
196
196
  class ChatRoomAcceptanceTest {
197
197
 
198
- @Autowired
199
- lateinit var mockMvc: MockMvc
200
-
201
- @Autowired
202
- lateinit var chatRoomRepository: ChatRoomRepository
198
+ @Autowired lateinit var mockMvc: MockMvc
203
199
 
204
200
  @Nested
205
201
  @DisplayName("TC-001-01: 채팅방 생성 - Happy Path")
@@ -207,50 +203,10 @@ class ChatRoomAcceptanceTest {
207
203
 
208
204
  @Test
209
205
  @DisplayName("인증된 사용자가 유효한 제목으로 채팅방 생성 시 성공")
210
- fun `POST api_chat-rooms - 201 Created with chatRoomId`() {
211
- // Arrange (사전 조건)
212
- val token = createTestUserToken(userId = 1L)
213
- val requestBody = """
214
- {
215
- "title": "새 채팅방"
216
- }
217
- """.trimIndent()
218
-
219
- // Act (실행 단계)
220
- val result = mockMvc.post("/api/chat-rooms") {
221
- contentType = MediaType.APPLICATION_JSON
222
- content = requestBody
223
- header("Authorization", "Bearer $token")
224
- }
225
-
226
- // Assert (기대 결과)
227
- result.andExpect {
228
- status { isCreated() }
229
- jsonPath("$.chatRoomId") { exists() }
230
- jsonPath("$.title") { value("새 채팅방") }
231
- }
232
-
233
- // DB 검증
234
- val chatRooms = chatRoomRepository.findAll()
235
- assertThat(chatRooms).hasSize(1)
236
- assertThat(chatRooms[0].title).isEqualTo("새 채팅방")
237
- }
238
- }
239
-
240
- @Nested
241
- @DisplayName("TC-001-02: 채팅방 생성 - 빈 제목 오류")
242
- inner class TC_001_02_EmptyTitle {
243
-
244
- @Test
245
- @DisplayName("빈 제목으로 채팅방 생성 시 400 Bad Request")
246
- fun `POST api_chat-rooms with empty title - 400 Bad Request`() {
206
+ fun `POST api_chat-rooms - 201 Created`() {
247
207
  // Arrange
248
208
  val token = createTestUserToken(userId = 1L)
249
- val requestBody = """
250
- {
251
- "title": ""
252
- }
253
- """.trimIndent()
209
+ val requestBody = """{"title": "새 채팅방"}"""
254
210
 
255
211
  // Act & Assert
256
212
  mockMvc.post("/api/chat-rooms") {
@@ -258,86 +214,135 @@ class ChatRoomAcceptanceTest {
258
214
  content = requestBody
259
215
  header("Authorization", "Bearer $token")
260
216
  }.andExpect {
261
- status { isBadRequest() }
262
- jsonPath("$.error") { exists() }
217
+ status { isCreated() }
218
+ jsonPath("$.chatRoomId") { exists() }
263
219
  }
264
220
  }
265
221
  }
222
+ }
223
+ ```
224
+
225
+ ### Java + Spring Boot
226
+
227
+ ```java
228
+ @SpringBootTest
229
+ @AutoConfigureMockMvc
230
+ @Transactional
231
+ @DisplayName("STORY-001: 채팅방 관리 인수 테스트")
232
+ class ChatRoomAcceptanceTest {
233
+
234
+ @Autowired
235
+ private MockMvc mockMvc;
266
236
 
267
237
  @Nested
268
- @DisplayName("TC-001-03: 인증 없이 채팅방 생성 시도")
269
- inner class TC_001_03_Unauthorized {
238
+ @DisplayName("TC-001-01: 채팅방 생성 - Happy Path")
239
+ class TC_001_01_CreateChatRoom {
270
240
 
271
241
  @Test
272
- @DisplayName("인증 토큰 없이 채팅방 생성 시 401 Unauthorized")
273
- fun `POST api_chat-rooms without token - 401 Unauthorized`() {
242
+ @DisplayName("인증된 사용자가 유효한 제목으로 채팅방 생성 시 성공")
243
+ void createChatRoom_withValidTitle_returns201() throws Exception {
274
244
  // Arrange
275
- val requestBody = """
276
- {
277
- "title": "새 채팅방"
278
- }
279
- """.trimIndent()
245
+ String requestBody = """{"title": "새 채팅방"}""";
280
246
 
281
247
  // Act & Assert
282
- mockMvc.post("/api/chat-rooms") {
283
- contentType = MediaType.APPLICATION_JSON
284
- content = requestBody
285
- }.andExpect {
286
- status { isUnauthorized() }
287
- }
248
+ mockMvc.perform(post("/api/chat-rooms")
249
+ .contentType(MediaType.APPLICATION_JSON)
250
+ .content(requestBody)
251
+ .header("Authorization", "Bearer " + testToken))
252
+ .andExpect(status().isCreated())
253
+ .andExpect(jsonPath("$.chatRoomId").exists());
288
254
  }
289
255
  }
290
-
291
- private fun createTestUserToken(userId: Long): String {
292
- // 테스트용 JWT 토큰 생성
293
- return "test-token-for-user-$userId"
294
- }
295
256
  }
296
257
  ```
297
258
 
298
- ---
259
+ ### TypeScript + Jest + Supertest
260
+
261
+ ```typescript
262
+ describe('STORY-001: 채팅방 관리 인수 테스트', () => {
263
+ describe('TC-001-01: 채팅방 생성 - Happy Path', () => {
264
+ it('인증된 사용자가 유효한 제목으로 채팅방 생성 시 성공', async () => {
265
+ // Arrange
266
+ const token = await createTestUserToken(1);
267
+ const requestBody = { title: '새 채팅방' };
268
+
269
+ // Act
270
+ const response = await request(app)
271
+ .post('/api/chat-rooms')
272
+ .set('Authorization', `Bearer ${token}`)
273
+ .send(requestBody);
274
+
275
+ // Assert
276
+ expect(response.status).toBe(201);
277
+ expect(response.body.chatRoomId).toBeDefined();
278
+ });
279
+ });
280
+ });
281
+ ```
299
282
 
300
- ## 테스트 유형별 패턴
283
+ ### Python + pytest + FastAPI/Django
284
+
285
+ ```python
286
+ import pytest
287
+ from httpx import AsyncClient
288
+
289
+ class TestChatRoomAcceptance:
290
+ """STORY-001: 채팅방 관리 인수 테스트"""
291
+
292
+ class TestTC_001_01_CreateChatRoom:
293
+ """TC-001-01: 채팅방 생성 - Happy Path"""
294
+
295
+ @pytest.mark.asyncio
296
+ async def test_create_chatroom_with_valid_title_returns_201(
297
+ self, client: AsyncClient, auth_token: str
298
+ ):
299
+ response = await client.post(
300
+ "/api/chat-rooms",
301
+ json={"title": "새 채팅방"},
302
+ headers={"Authorization": f"Bearer {auth_token}"}
303
+ )
304
+ assert response.status_code == 201
305
+ assert "chatRoomId" in response.json()
306
+ ```
301
307
 
302
- ### Integration Test (API 레벨)
308
+ ### Go + testing + httptest
303
309
 
304
- ```kotlin
305
- @SpringBootTest
306
- @AutoConfigureMockMvc
307
- @Transactional
308
- class XxxAcceptanceTest {
309
- @Autowired lateinit var mockMvc: MockMvc
310
+ ```go
311
+ func TestChatRoomAcceptance(t *testing.T) {
312
+ t.Run("TC-001-01: 채팅방 생성 - Happy Path", func(t *testing.T) {
313
+ // Arrange
314
+ token := createTestUserToken(1)
315
+ body := `{"title": "새 채팅방"}`
310
316
 
311
- @Nested
312
- @DisplayName("TC-XXX-XX: 시나리오명")
313
- inner class TC_XXX_XX { ... }
314
- }
315
- ```
317
+ req := httptest.NewRequest("POST", "/api/chat-rooms", strings.NewReader(body))
318
+ req.Header.Set("Authorization", "Bearer "+token)
316
319
 
317
- ### E2E Test (전체 흐름)
320
+ // Act
321
+ rec := httptest.NewRecorder()
322
+ router.ServeHTTP(rec, req)
318
323
 
319
- ```kotlin
320
- @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
321
- @TestMethodOrder(MethodOrderer.OrderAnnotation::class)
322
- class XxxE2ETest {
323
- @Autowired lateinit var restTemplate: TestRestTemplate
324
-
325
- @Test
326
- @Order(1)
327
- fun `Step 1: 사용자 생성`() { ... }
328
-
329
- @Test
330
- @Order(2)
331
- fun `Step 2: 로그인`() { ... }
332
-
333
- @Test
334
- @Order(3)
335
- fun `Step 3: 채팅방 생성`() { ... }
324
+ // Assert
325
+ assert.Equal(t, http.StatusCreated, rec.Code)
326
+ })
336
327
  }
337
328
  ```
338
329
 
339
330
  ---
340
331
 
332
+ ## 테스트 유형별 패턴 (언어 공통)
333
+
334
+ ### Integration Test (API 레벨)
335
+ - HTTP 요청/응답 검증
336
+ - DB 상태 검증
337
+ - 트랜잭션 롤백으로 격리
338
+
339
+ ### E2E Test (전체 흐름)
340
+ - 실제 서버 구동 (랜덤 포트)
341
+ - 순차적 시나리오 실행
342
+ - 외부 의존성 Mock 또는 실제 연동
343
+
344
+ ---
345
+
341
346
  ## 생성 워크플로우
342
347
 
343
348
  ### Step 1: TC 분석
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: epic-story-reviewer
3
3
  description: Epic/Story 문서를 체크리스트 기반으로 검토합니다. 통과하면 OK, 문제 있을 때만 피드백합니다.
4
- model: opus
4
+ model: sonnet
5
5
  color: blue
6
6
  ---
7
7
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: prd-reviewer
3
3
  description: PRD 문서를 체크리스트 기반으로 검토합니다. 통과하면 OK, 문제 있을 때만 피드백합니다.
4
- model: opus
4
+ model: sonnet
5
5
  color: red
6
6
  ---
7
7
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: tc-reviewer
3
3
  description: TC 문서를 체크리스트 기반으로 검토합니다. 통과하면 OK, 문제 있을 때만 피드백합니다.
4
- model: opus
4
+ model: sonnet
5
5
  color: purple
6
6
  ---
7
7
 
@@ -153,21 +153,26 @@ status: todo
153
153
 
154
154
  ## 출력: Unit Test 코드
155
155
 
156
- ### 생성 패턴
156
+ ### 프로젝트 언어 감지
157
+
158
+ **테스트 코드 생성 전 반드시 프로젝트의 기술 스택을 파악합니다:**
159
+
160
+ ```
161
+ 1. build.gradle.kts / build.gradle → Kotlin/Java + Spring
162
+ 2. pom.xml → Java + Spring
163
+ 3. package.json → TypeScript/JavaScript + Node.js
164
+ 4. requirements.txt / pyproject.toml → Python
165
+ 5. go.mod → Go
166
+ 6. 기존 테스트 코드 패턴 확인 → 프로젝트 컨벤션 따르기
167
+ ```
168
+
169
+ ---
170
+
171
+ ## 언어별 Unit Test 패턴
172
+
173
+ ### Kotlin + JUnit5
157
174
 
158
175
  ```kotlin
159
- package com.example.domain.chat.model
160
-
161
- import org.junit.jupiter.api.*
162
- import org.assertj.core.api.Assertions.assertThat
163
- import org.assertj.core.api.Assertions.assertThatThrownBy
164
-
165
- /**
166
- * TASK-001: ChatRoom Entity 생성
167
- *
168
- * TDD 기반 Unit Test - 구현 전 작성
169
- * Task 완료 조건을 검증하는 테스트
170
- */
171
176
  @DisplayName("ChatRoom Entity 테스트")
172
177
  class ChatRoomTest {
173
178
 
@@ -176,8 +181,8 @@ class ChatRoomTest {
176
181
  inner class Creation {
177
182
 
178
183
  @Test
179
- @DisplayName("유효한 값으로 Entity 생성 시 성공한다")
180
- fun `유효한 userId와 title로 ChatRoom 생성 시 externalId가 자동 생성된다`() {
184
+ @DisplayName("유효한 값으로 Entity 생성 시 성공")
185
+ fun `유효한 userId와 title로 ChatRoom 생성 시 성공`() {
181
186
  // Arrange
182
187
  val userId = 1L
183
188
  val title = "테스트 채팅방"
@@ -187,80 +192,150 @@ class ChatRoomTest {
187
192
 
188
193
  // Assert
189
194
  assertThat(chatRoom.externalId).isNotNull()
190
- assertThat(chatRoom.userId).isEqualTo(userId)
191
195
  assertThat(chatRoom.title).isEqualTo(title)
192
- assertThat(chatRoom.status).isEqualTo(ChatRoomStatus.ACTIVE)
193
- }
194
-
195
- @Test
196
- @DisplayName("externalId는 UUIDv7 형식이다")
197
- fun `externalId는 UUIDv7 형식으로 생성된다`() {
198
- // Arrange & Act
199
- val chatRoom = ChatRoom(userId = 1L, title = "테스트")
200
-
201
- // Assert
202
- assertThat(chatRoom.externalId).isNotNull()
203
- assertThat(chatRoom.externalId.toString()).matches(
204
- "^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
205
- )
206
196
  }
207
197
  }
208
198
 
209
199
  @Nested
210
- @DisplayName("Title 유효성 검증")
211
- inner class TitleValidation {
200
+ @DisplayName("유효성 검증")
201
+ inner class Validation {
212
202
 
213
203
  @Test
214
204
  @DisplayName("빈 제목으로 생성 시 예외 발생")
215
- fun `빈 title로 ChatRoom 생성 시 IllegalArgumentException 발생`() {
216
- // Arrange & Act & Assert
217
- assertThatThrownBy {
218
- ChatRoom(userId = 1L, title = "")
219
- }.isInstanceOf(IllegalArgumentException::class.java)
220
- .hasMessageContaining("title")
205
+ fun `빈 title로 ChatRoom 생성 시 예외`() {
206
+ assertThatThrownBy { ChatRoom(userId = 1L, title = "") }
207
+ .isInstanceOf(IllegalArgumentException::class.java)
221
208
  }
209
+ }
210
+ }
211
+ ```
222
212
 
223
- @Test
224
- @DisplayName("100자 초과 제목으로 생성 시 예외 발생")
225
- fun `100자 초과 title로 ChatRoom 생성 시 IllegalArgumentException 발생`() {
226
- // Arrange
227
- val longTitle = "가".repeat(101)
213
+ ### Java + JUnit5
228
214
 
229
- // Act & Assert
230
- assertThatThrownBy {
231
- ChatRoom(userId = 1L, title = longTitle)
232
- }.isInstanceOf(IllegalArgumentException::class.java)
233
- .hasMessageContaining("100")
234
- }
215
+ ```java
216
+ @DisplayName("ChatRoom Entity 테스트")
217
+ class ChatRoomTest {
218
+
219
+ @Nested
220
+ @DisplayName("Entity 생성")
221
+ class Creation {
235
222
 
236
223
  @Test
237
- @DisplayName("100자 제목은 허용")
238
- fun `100자 title로 ChatRoom 생성 시 성공`() {
224
+ @DisplayName("유효한 값으로 Entity 생성 시 성공")
225
+ void createChatRoom_withValidInput_succeeds() {
239
226
  // Arrange
240
- val maxTitle = "가".repeat(100)
227
+ Long userId = 1L;
228
+ String title = "테스트 채팅방";
241
229
 
242
230
  // Act
243
- val chatRoom = ChatRoom(userId = 1L, title = maxTitle)
231
+ ChatRoom chatRoom = new ChatRoom(userId, title);
244
232
 
245
233
  // Assert
246
- assertThat(chatRoom.title).hasSize(100)
234
+ assertThat(chatRoom.getExternalId()).isNotNull();
235
+ assertThat(chatRoom.getTitle()).isEqualTo(title);
247
236
  }
248
237
  }
249
238
 
250
239
  @Nested
251
- @DisplayName("Status 기본값")
252
- inner class DefaultStatus {
240
+ @DisplayName("유효성 검증")
241
+ class Validation {
253
242
 
254
243
  @Test
255
- @DisplayName("생성 시 기본 상태는 ACTIVE")
256
- fun `ChatRoom 생성 시 status 기본값은 ACTIVE`() {
244
+ @DisplayName("빈 제목으로 생성 시 예외 발생")
245
+ void createChatRoom_withEmptyTitle_throwsException() {
246
+ assertThatThrownBy(() -> new ChatRoom(1L, ""))
247
+ .isInstanceOf(IllegalArgumentException.class);
248
+ }
249
+ }
250
+ }
251
+ ```
252
+
253
+ ### TypeScript + Jest
254
+
255
+ ```typescript
256
+ describe('ChatRoom Entity', () => {
257
+ describe('Entity 생성', () => {
258
+ it('유효한 값으로 Entity 생성 시 성공', () => {
259
+ // Arrange
260
+ const userId = 1;
261
+ const title = '테스트 채팅방';
262
+
263
+ // Act
264
+ const chatRoom = new ChatRoom({ userId, title });
265
+
266
+ // Assert
267
+ expect(chatRoom.externalId).toBeDefined();
268
+ expect(chatRoom.title).toBe(title);
269
+ });
270
+ });
271
+
272
+ describe('유효성 검증', () => {
273
+ it('빈 제목으로 생성 시 예외 발생', () => {
274
+ expect(() => new ChatRoom({ userId: 1, title: '' }))
275
+ .toThrow('title');
276
+ });
277
+ });
278
+ });
279
+ ```
280
+
281
+ ### Python + pytest
282
+
283
+ ```python
284
+ import pytest
285
+ from domain.chat.model import ChatRoom
286
+
287
+ class TestChatRoom:
288
+ """ChatRoom Entity 테스트"""
289
+
290
+ class TestCreation:
291
+ """Entity 생성"""
292
+
293
+ def test_create_chatroom_with_valid_input_succeeds(self):
294
+ # Arrange
295
+ user_id = 1
296
+ title = "테스트 채팅방"
297
+
298
+ # Act
299
+ chat_room = ChatRoom(user_id=user_id, title=title)
300
+
301
+ # Assert
302
+ assert chat_room.external_id is not None
303
+ assert chat_room.title == title
304
+
305
+ class TestValidation:
306
+ """유효성 검증"""
307
+
308
+ def test_create_chatroom_with_empty_title_raises_error(self):
309
+ with pytest.raises(ValueError, match="title"):
310
+ ChatRoom(user_id=1, title="")
311
+ ```
312
+
313
+ ### Go + testing
314
+
315
+ ```go
316
+ func TestChatRoom(t *testing.T) {
317
+ t.Run("Entity 생성", func(t *testing.T) {
318
+ t.Run("유효한 값으로 Entity 생성 시 성공", func(t *testing.T) {
319
+ // Arrange
320
+ userID := int64(1)
321
+ title := "테스트 채팅방"
322
+
257
323
  // Act
258
- val chatRoom = ChatRoom(userId = 1L, title = "테스트")
324
+ chatRoom, err := NewChatRoom(userID, title)
259
325
 
260
326
  // Assert
261
- assertThat(chatRoom.status).isEqualTo(ChatRoomStatus.ACTIVE)
262
- }
263
- }
327
+ assert.NoError(t, err)
328
+ assert.NotEmpty(t, chatRoom.ExternalID)
329
+ assert.Equal(t, title, chatRoom.Title)
330
+ })
331
+ })
332
+
333
+ t.Run("유효성 검증", func(t *testing.T) {
334
+ t.Run("빈 제목으로 생성 시 에러", func(t *testing.T) {
335
+ _, err := NewChatRoom(1, "")
336
+ assert.Error(t, err)
337
+ })
338
+ })
264
339
  }
265
340
  ```
266
341