@@ -6,16 +6,17 @@ import io.github.jpicklyk.mcptask.domain.model.EntityType
66import io.github.jpicklyk.mcptask.domain.model.Feature
77import io.github.jpicklyk.mcptask.domain.model.FeatureStatus
88import io.github.jpicklyk.mcptask.domain.model.Priority
9+ import io.github.jpicklyk.mcptask.domain.model.Project
910import io.github.jpicklyk.mcptask.domain.repository.FeatureRepository
11+ import io.github.jpicklyk.mcptask.domain.repository.ProjectRepository
1012import io.github.jpicklyk.mcptask.domain.repository.RepositoryError
1113import io.github.jpicklyk.mcptask.domain.repository.Result
1214import io.github.jpicklyk.mcptask.infrastructure.repository.RepositoryProvider
1315import io.mockk.coEvery
1416import io.mockk.every
1517import io.mockk.mockk
1618import kotlinx.coroutines.runBlocking
17- import kotlinx.serialization.json.JsonObject
18- import kotlinx.serialization.json.JsonPrimitive
19+ import kotlinx.serialization.json.*
1920import org.junit.jupiter.api.Assertions.*
2021import org.junit.jupiter.api.BeforeEach
2122import org.junit.jupiter.api.Test
@@ -26,16 +27,19 @@ class UpdateFeatureToolTest {
2627 private lateinit var tool: UpdateFeatureTool
2728 private lateinit var context: ToolExecutionContext
2829 private lateinit var mockFeatureRepository: FeatureRepository
30+ private lateinit var mockProjectRepository: ProjectRepository
2931 private val testFeatureId = UUID .randomUUID()
3032
3133 @BeforeEach
3234 fun setup () {
3335 // Create mock repositories
3436 mockFeatureRepository = mockk<FeatureRepository >()
37+ mockProjectRepository = mockk<ProjectRepository >()
3538 val mockRepositoryProvider = mockk<RepositoryProvider >()
3639
3740 // Configure the repository provider to return the mock repositories
3841 every { mockRepositoryProvider.featureRepository() } returns mockFeatureRepository
42+ every { mockRepositoryProvider.projectRepository() } returns mockProjectRepository
3943
4044 // Create an original feature
4145 val originalFeature = Feature (
@@ -208,4 +212,129 @@ class UpdateFeatureToolTest {
208212 assertEquals(false , (resultObj[" success" ] as JsonPrimitive ).content.toBoolean())
209213 assertTrue((resultObj[" message" ] as JsonPrimitive ).content.contains(" Feature not found" ))
210214 }
215+
216+ @Test
217+ fun `test invalid projectId foreign key validation` () = runBlocking {
218+ val invalidProjectId = UUID .randomUUID().toString()
219+
220+ // Mock project repository to return not found for invalid project ID
221+ coEvery {
222+ mockProjectRepository.getById(UUID .fromString(invalidProjectId))
223+ } returns Result .Error (
224+ RepositoryError .NotFound (
225+ UUID .fromString(invalidProjectId),
226+ EntityType .PROJECT ,
227+ " Project not found"
228+ )
229+ )
230+
231+ val params = JsonObject (
232+ mapOf (
233+ " id" to JsonPrimitive (testFeatureId.toString()),
234+ " projectId" to JsonPrimitive (invalidProjectId)
235+ )
236+ )
237+
238+ val result = tool.execute(params, context)
239+ assertTrue(result is JsonObject , " Response should be a JsonObject" )
240+
241+ val responseObj = result as JsonObject
242+ val success = responseObj[" success" ]?.jsonPrimitive?.boolean ? : true
243+ assertFalse(success, " Success should be false for invalid projectId" )
244+
245+ val message = responseObj[" message" ]?.jsonPrimitive?.content
246+ assertEquals(" Project not found" , message, " Should return proper project not found message" )
247+
248+ val error = responseObj[" error" ]?.jsonObject
249+ assertNotNull(error, " Error object should not be null" )
250+ assertEquals(" RESOURCE_NOT_FOUND" , error!! [" code" ]?.jsonPrimitive?.content)
251+
252+ val details = error[" details" ]?.jsonPrimitive?.content
253+ assertTrue(
254+ details?.contains(" No project exists with ID $invalidProjectId " ) ? : false ,
255+ " Error details should mention the specific project ID"
256+ )
257+ }
258+
259+ @Test
260+ fun `test valid projectId foreign key validation` () = runBlocking {
261+ val validProjectId = UUID .randomUUID().toString()
262+ val mockProject = mockk<Project >()
263+
264+ // Mock project repository to return success for valid project ID
265+ coEvery {
266+ mockProjectRepository.getById(UUID .fromString(validProjectId))
267+ } returns Result .Success (mockProject)
268+
269+ val params = JsonObject (
270+ mapOf (
271+ " id" to JsonPrimitive (testFeatureId.toString()),
272+ " projectId" to JsonPrimitive (validProjectId)
273+ )
274+ )
275+
276+ val result = tool.execute(params, context)
277+ assertTrue(result is JsonObject , " Response should be a JsonObject" )
278+
279+ val responseObj = result as JsonObject
280+ val success = responseObj[" success" ]?.jsonPrimitive?.boolean ? : false
281+ assertTrue(success, " Success should be true for valid projectId" )
282+
283+ val message = responseObj[" message" ]?.jsonPrimitive?.content
284+ assertTrue(
285+ message?.contains(" updated successfully" ) ? : false ,
286+ " Message should contain 'updated successfully'"
287+ )
288+ }
289+
290+ @Test
291+ fun `test projectId validation only triggered when changing project` () = runBlocking {
292+ // Feature already has a project ID - updating without changing it should not trigger validation
293+ val existingProjectId = UUID .randomUUID()
294+ val featureWithProject = Feature (
295+ id = testFeatureId,
296+ projectId = existingProjectId,
297+ name = " Original Feature" ,
298+ summary = " Original description" ,
299+ status = FeatureStatus .PLANNING ,
300+ priority = Priority .MEDIUM ,
301+ createdAt = Instant .now(),
302+ modifiedAt = Instant .now(),
303+ tags = emptyList()
304+ )
305+
306+ coEvery {
307+ mockFeatureRepository.getById(testFeatureId)
308+ } returns Result .Success (featureWithProject)
309+
310+ val params = JsonObject (
311+ mapOf (
312+ " id" to JsonPrimitive (testFeatureId.toString()),
313+ " name" to JsonPrimitive (" Updated Name" ) // Only updating name, not projectId
314+ )
315+ )
316+
317+ val result = tool.execute(params, context)
318+ assertTrue(result is JsonObject , " Response should be a JsonObject" )
319+
320+ val responseObj = result as JsonObject
321+ val success = responseObj[" success" ]?.jsonPrimitive?.boolean ? : false
322+ assertTrue(success, " Success should be true when not changing projectId" )
323+ }
324+
325+ @Test
326+ fun `test projectId parameter validation` () {
327+ val params = JsonObject (
328+ mapOf (
329+ " id" to JsonPrimitive (testFeatureId.toString()),
330+ " projectId" to JsonPrimitive (" not-a-uuid" )
331+ )
332+ )
333+
334+ // Should throw an exception for invalid projectId format
335+ val exception = assertThrows(ToolValidationException ::class .java) {
336+ tool.validateParams(params)
337+ }
338+ assertTrue(exception.message!! .contains(" Invalid project ID format" ))
339+ }
211340}
0 commit comments