fix(frontend): keep entity and message deletes in sync
This commit is contained in:
@@ -21,7 +21,7 @@ class EntityService(
|
|||||||
|
|
||||||
/** Returns all virtual entities. */
|
/** Returns all virtual entities. */
|
||||||
fun findAll(): List<VirtualEntityResponseDto> =
|
fun findAll(): List<VirtualEntityResponseDto> =
|
||||||
virtualEntityRepository.findAll().map { VirtualEntityResponseDto.from(it) }
|
virtualEntityRepository.findAllByActiveTrue().map { VirtualEntityResponseDto.from(it) }
|
||||||
|
|
||||||
/** Returns one entity by ID, or null if not found. */
|
/** Returns one entity by ID, or null if not found. */
|
||||||
fun findById(id: UUID): VirtualEntityResponseDto? =
|
fun findById(id: UUID): VirtualEntityResponseDto? =
|
||||||
|
|||||||
@@ -138,4 +138,32 @@ class TaskGeneratedMessageControllerTest {
|
|||||||
.andExpect(jsonPath("$").isArray)
|
.andExpect(jsonPath("$").isArray)
|
||||||
.andExpect(jsonPath("$.length()").value(0))
|
.andExpect(jsonPath("$.length()").value(0))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun should_deleteOnlySelectedHistoryItem_when_multipleMessagesExist() {
|
||||||
|
val task = createTask()
|
||||||
|
val firstMessage = generatedMessageHistoryRepository.save(
|
||||||
|
GeneratedMessageHistory(
|
||||||
|
task = task,
|
||||||
|
label = "Message #1",
|
||||||
|
content = "SUBJECT: First\nBODY:\nHello"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val secondMessage = generatedMessageHistoryRepository.save(
|
||||||
|
GeneratedMessageHistory(
|
||||||
|
task = task,
|
||||||
|
label = "Message #2",
|
||||||
|
content = "SUBJECT: Second\nBODY:\nHi"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
mockMvc.perform(delete("/api/v1/tasks/${task.id}/generated-messages/${firstMessage.id}").cookie(authCookie()))
|
||||||
|
.andExpect(status().isNoContent)
|
||||||
|
|
||||||
|
mockMvc.perform(get("/api/v1/tasks/${task.id}/generated-messages").cookie(authCookie()))
|
||||||
|
.andExpect(status().isOk)
|
||||||
|
.andExpect(jsonPath("$.length()").value(1))
|
||||||
|
.andExpect(jsonPath("$[0].id").value(secondMessage.id.toString()))
|
||||||
|
.andExpect(jsonPath("$[0].label").value("Message #2"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,6 +62,17 @@ class VirtualEntityControllerTest {
|
|||||||
.andExpect(status().isOk).andExpect(jsonPath("$").isArray).andExpect(jsonPath("$[0].name").value("Test Entity"))
|
.andExpect(status().isOk).andExpect(jsonPath("$").isArray).andExpect(jsonPath("$[0].name").value("Test Entity"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun should_returnOnlyActiveEntities_when_getAllEntities() {
|
||||||
|
virtualEntityRepository.save(VirtualEntity(name = "Active Entity", email = "active@condado.com", jobTitle = "Tester", active = true))
|
||||||
|
virtualEntityRepository.save(VirtualEntity(name = "Inactive Entity", email = "inactive@condado.com", jobTitle = "Tester", active = false))
|
||||||
|
|
||||||
|
mockMvc.perform(get("/api/v1/virtual-entities").cookie(authCookie()))
|
||||||
|
.andExpect(status().isOk)
|
||||||
|
.andExpect(jsonPath("$.length()").value(1))
|
||||||
|
.andExpect(jsonPath("$[0].name").value("Active Entity"))
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun should_return200AndEntity_when_getById() {
|
fun should_return200AndEntity_when_getById() {
|
||||||
val entity = virtualEntityRepository.save(VirtualEntity(name = "Test Entity", email = "entity@condado.com", jobTitle = "Test Job"))
|
val entity = virtualEntityRepository.save(VirtualEntity(name = "Test Entity", email = "entity@condado.com", jobTitle = "Test Job"))
|
||||||
|
|||||||
@@ -294,6 +294,45 @@ describe('EditTaskPage', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should_removeOnlyDeletedGeneratedMessage_when_deleteSucceedsWithoutRefetch', async () => {
|
||||||
|
persistedHistory = [
|
||||||
|
{
|
||||||
|
id: 'message-2',
|
||||||
|
taskId: 'task-1',
|
||||||
|
label: 'Message #2',
|
||||||
|
content: 'SUBJECT: Second\nBODY:\nSecond output',
|
||||||
|
createdAt: '2026-03-27T12:10:00Z',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'message-1',
|
||||||
|
taskId: 'task-1',
|
||||||
|
label: 'Message #1',
|
||||||
|
content: 'SUBJECT: First\nBODY:\nFirst output',
|
||||||
|
createdAt: '2026-03-27T12:00:00Z',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
vi.mocked(tasksApi.getTaskGeneratedMessages).mockResolvedValue(persistedHistory)
|
||||||
|
vi.mocked(tasksApi.deleteTaskGeneratedMessage).mockResolvedValue(undefined)
|
||||||
|
|
||||||
|
renderPage()
|
||||||
|
|
||||||
|
const secondMessageHistoryItem = await screen.findByRole('button', { name: /^message #2$/i })
|
||||||
|
expect(await screen.findByRole('button', { name: /^message #1$/i })).toBeInTheDocument()
|
||||||
|
|
||||||
|
fireEvent.click(
|
||||||
|
screen.getByRole('button', {
|
||||||
|
name: /delete message #1/i,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(tasksApi.deleteTaskGeneratedMessage).toHaveBeenCalledWith('task-1', 'message-1')
|
||||||
|
expect(screen.queryByRole('button', { name: /^message #1$/i })).not.toBeInTheDocument()
|
||||||
|
expect(secondMessageHistoryItem).toBeInTheDocument()
|
||||||
|
expect(screen.getByText(/Second output/i)).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
it('should_loadPersistedGeneratedMessageHistory_when_pageLoads', async () => {
|
it('should_loadPersistedGeneratedMessageHistory_when_pageLoads', async () => {
|
||||||
persistedHistory = [
|
persistedHistory = [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -89,6 +89,23 @@ describe('EntitiesPage', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should_removeDeletedEntityFromList_when_deleteSucceeds', async () => {
|
||||||
|
vi.mocked(entitiesApi.getEntities).mockResolvedValue([mockEntity])
|
||||||
|
vi.mocked(entitiesApi.deleteEntity).mockResolvedValue(undefined)
|
||||||
|
|
||||||
|
render(<EntitiesPage />, { wrapper })
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByText('Test Entity')).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: /delete|deactivate/i }))
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.queryByText('Test Entity')).not.toBeInTheDocument()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
it('should_renderDetailLink_when_entitiesLoaded', async () => {
|
it('should_renderDetailLink_when_entitiesLoaded', async () => {
|
||||||
vi.mocked(entitiesApi.getEntities).mockResolvedValue([mockEntity])
|
vi.mocked(entitiesApi.getEntities).mockResolvedValue([mockEntity])
|
||||||
render(<EntitiesPage />, { wrapper })
|
render(<EntitiesPage />, { wrapper })
|
||||||
|
|||||||
@@ -255,8 +255,15 @@ export default function EditTaskPage() {
|
|||||||
|
|
||||||
const deleteGeneratedMessageMutation = useMutation({
|
const deleteGeneratedMessageMutation = useMutation({
|
||||||
mutationFn: (messageId: string) => deleteTaskGeneratedMessage(taskId, messageId),
|
mutationFn: (messageId: string) => deleteTaskGeneratedMessage(taskId, messageId),
|
||||||
onSuccess: async () => {
|
onSuccess: ( _data, messageId) => {
|
||||||
await queryClient.invalidateQueries({ queryKey: ['task-generated-messages', taskId] })
|
queryClient.setQueryData(
|
||||||
|
['task-generated-messages', taskId],
|
||||||
|
(
|
||||||
|
currentMessages:
|
||||||
|
| Awaited<ReturnType<typeof getTaskGeneratedMessages>>
|
||||||
|
| undefined
|
||||||
|
) => currentMessages?.filter((message) => message.id !== messageId) ?? []
|
||||||
|
)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,13 @@ export default function EntitiesPage() {
|
|||||||
|
|
||||||
const deleteMutation = useMutation({
|
const deleteMutation = useMutation({
|
||||||
mutationFn: (id: string) => deleteEntity(id),
|
mutationFn: (id: string) => deleteEntity(id),
|
||||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['entities'] }),
|
onSuccess: (_data, id) => {
|
||||||
|
queryClient.setQueryData(
|
||||||
|
['entities'],
|
||||||
|
(currentEntities: Awaited<ReturnType<typeof getEntities>> | undefined) =>
|
||||||
|
currentEntities?.filter((entity) => entity.id !== id) ?? []
|
||||||
|
)
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
Reference in New Issue
Block a user