Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions src/features/orders/hooks/useUpdateOrder.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { act, renderHook } from '@testing-library/react'
import type { ReactNode } from 'react'
import { beforeEach, describe, expect, it, vi } from 'vitest'

import * as api from '../api'
import { orderKeys, quoteKeys } from '../queryKeys'
import type { OrderDetail, UpdateOrderPayload } from '../types'

import { useUpdateOrder } from './useUpdateOrder'

const PAYLOAD = { order: { status: 'paid' } } as unknown as UpdateOrderPayload

// El detalle, la cotización de esta orden y la de otra, ya en caché.
function seededClient() {
const queryClient = new QueryClient({ defaultOptions: { mutations: { retry: false } } })
queryClient.setQueryData(orderKeys.detail(8829), {})
queryClient.setQueryData(quoteKeys.order(8829, 1), [{ shippingCost: 2500 }])
queryClient.setQueryData(quoteKeys.order(7000, 1), [{ shippingCost: 900 }])
return queryClient
}

function renderFor(queryClient: QueryClient) {
return renderHook(() => useUpdateOrder(8829, '"v1"'), {
wrapper: ({ children }: { children: ReactNode }) => (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
),
})
}

beforeEach(() => {
vi.restoreAllMocks()
})

describe('useUpdateOrder', () => {
// Hallazgo de auditoría (TESIS-89): la cotización se calculó con las líneas y
// el destino de antes, y despachar dentro del minuto de caché mandaba el
// costo viejo.
it('drops the shipping quote of the order it saved', async () => {
vi.spyOn(api, 'updateOrder').mockResolvedValue({} as OrderDetail)
const queryClient = seededClient()
const { result } = renderFor(queryClient)

await act(() => result.current.mutateAsync(PAYLOAD))

expect(queryClient.getQueryData(quoteKeys.order(8829, 1))).toBeUndefined()
})

it('keeps the quotes of other orders', async () => {
vi.spyOn(api, 'updateOrder').mockResolvedValue({} as OrderDetail)
const queryClient = seededClient()
const { result } = renderFor(queryClient)

await act(() => result.current.mutateAsync(PAYLOAD))

expect(queryClient.getQueryData(quoteKeys.order(7000, 1))).toEqual([{ shippingCost: 900 }])
})

it('still refreshes the order', async () => {
vi.spyOn(api, 'updateOrder').mockResolvedValue({} as OrderDetail)
const queryClient = seededClient()
const { result } = renderFor(queryClient)

await act(() => result.current.mutateAsync(PAYLOAD))

expect(queryClient.getQueryState(orderKeys.detail(8829))?.isInvalidated).toBe(true)
})
})
17 changes: 13 additions & 4 deletions src/features/orders/hooks/useUpdateOrder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'
import type { ApiRequestError } from 'shared/api/types'

import { updateOrder } from '../api'
import { orderKeys } from '../queryKeys'
import { orderKeys, quoteKeys } from '../queryKeys'
import type { OrderDetail, UpdateOrderPayload } from '../types'

/** La API rechaza con 412 el guardado que parte de una versión vieja. */
Expand All @@ -17,17 +17,26 @@ export const NOT_EDITABLE_STATUS = 409
* Al terminar invalida el dominio entero de órdenes —el listado muestra el total
* y el destino— y el catálogo: cambiar las líneas mueve stock, así que el
* `total_stock` del inventario y el stock por depósito también quedaron viejos.
* Y borra la cotización de la orden, que dependía de esas líneas y ese destino.
*/
export function useUpdateOrder(id: number, version: string | null) {
const queryClient = useQueryClient()

return useMutation<OrderDetail, ApiRequestError, UpdateOrderPayload>({
mutationFn: (payload) => updateOrder(id, payload, version),
onSuccess: () =>
Promise.all([
onSuccess: () => {
// La cotización de esta orden se calculó con las líneas y el destino de
// antes: dura un minuto en caché, y despachar dentro de ese minuto mandaba
// el costo viejo (hallazgo de auditoría, TESIS-89). Se borra y no sólo se
// invalida: el diálogo de despacho está cerrado, y una entrada invalidada
// se seguiría mostrando hasta que vuelva la nueva.
queryClient.removeQueries({ queryKey: [...quoteKeys.all, 'order', id] })

return Promise.all([
queryClient.invalidateQueries({ queryKey: orderKeys.all }),
// Literal y no la factory de inventario: una feature no importa otra.
queryClient.invalidateQueries({ queryKey: ['inventory'] }),
]),
])
},
})
}
Loading