fix(frontend): keep entity and message deletes in sync

This commit is contained in:
2026-03-27 03:38:41 -03:00
parent 726c8f3afd
commit 433874d11e
7 changed files with 112 additions and 4 deletions

View File

@@ -21,7 +21,7 @@ class EntityService(
/** Returns all virtual entities. */
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. */
fun findById(id: UUID): VirtualEntityResponseDto? =

View File

@@ -138,4 +138,32 @@ class TaskGeneratedMessageControllerTest {
.andExpect(jsonPath("$").isArray)
.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"))
}
}

View File

@@ -62,6 +62,17 @@ class VirtualEntityControllerTest {
.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
fun should_return200AndEntity_when_getById() {
val entity = virtualEntityRepository.save(VirtualEntity(name = "Test Entity", email = "entity@condado.com", jobTitle = "Test Job"))

View File

@@ -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 () => {
persistedHistory = [
{

View File

@@ -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 () => {
vi.mocked(entitiesApi.getEntities).mockResolvedValue([mockEntity])
render(<EntitiesPage />, { wrapper })

View File

@@ -255,8 +255,15 @@ export default function EditTaskPage() {
const deleteGeneratedMessageMutation = useMutation({
mutationFn: (messageId: string) => deleteTaskGeneratedMessage(taskId, messageId),
onSuccess: async () => {
await queryClient.invalidateQueries({ queryKey: ['task-generated-messages', taskId] })
onSuccess: ( _data, messageId) => {
queryClient.setQueryData(
['task-generated-messages', taskId],
(
currentMessages:
| Awaited<ReturnType<typeof getTaskGeneratedMessages>>
| undefined
) => currentMessages?.filter((message) => message.id !== messageId) ?? []
)
},
})

View File

@@ -37,7 +37,13 @@ export default function EntitiesPage() {
const deleteMutation = useMutation({
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 (