Skip to content

fix: [TESIS-124] read the shipments listing filters as single values - #85

Merged
TomasMartin2004 merged 4 commits into
masterfrom
TESIS-124-harden-shipments-query-params
Sep 26, 2026
Merged

TomasMartin2004 merged 4 commits into
masterfrom
TESIS-124-harden-shipments-query-params

Conversation

@TomasMartin2004

Copy link
Copy Markdown
Contributor

Ticket de Jira

https://proyectofinalfrlp.atlassian.net/browse/TESIS-124


Descripción

shipments#index era el último listado de api/v1 que leía params[...] directo. Ahora sus cuatro parámetros —page, per_page, status y order_id— pasan por scalar_param, igual que órdenes y productos.

El alcance salió más chico que el de la card, y un hallazgo salió distinto.

La card preveía endurecer también products#index, pero eso ya entró con TESIS-62: lo verifiqué contra master antes de empezar y sus cinco parámetros ya usan el helper. Queda sólo envíos, que es lo que hace este PR.

Lo que sí cambió respecto de lo que la card describía. La card daba por hecho que los cuatro parámetros devolvían 500. Los reproduje uno por uno sobre master:

page      -> NoMethodError    (Array#to_i no existe)
per_page  -> NoMethodError
status    -> TypeError        (ActionController::Parameters dentro de un where)
order_id  -> 200

?order_id[]=1 no fallaba, y eso es peor que el 500. where recibía el Array y lo traducía a un IN, así que el listado filtraba por varias órdenes a la vez y contestaba 200: una capacidad que nadie declaró, que nadie documentó y que nadie prueba, escondida detrás de una respuesta exitosa. Un 500 se ve; esto no.

Lo que no cambia, porque la card pide conservarlo: un valor desconocido no es un valor mal formado. ?status=inventado sigue devolviendo lista vacía —la respuesta honesta para un filtro que no matchea nada— y sólo la forma mal armada es 400. Hay un ejemplo que lo fija, para que endurecer no se lleve puesta esa distinción.

Y se responde 400 y no «se ignora el filtro»: descartarlo en silencio devolvería el listado entero, que es una respuesta plausible y equivocada.

  • shipments#index usa scalar_param para page y per_page
  • filtered_shipments lo usa para status y order_id
  • 6 ejemplos nuevos en spec/requests/api/v1/shipments_spec.rb: uno por parámetro, uno que verifica que el mensaje nombra cuál está mal, y el del estado desconocido

No hizo falta tocar el rescue_from: MalformedParameterError ya se rescata en ApplicationController.


Evidencia visual

N/A — es el manejo de parámetros de un endpoint existente.


Cómo probarlo

Precondición: sesión iniciada con un usuario del tenant norte.

  1. GET /api/v1/shipments?page[]=1 → 400, con el mensaje nombrando page. Antes: 500.
  2. Ídem ?per_page[]=1 y ?status[foo]=bar.
  3. GET /api/v1/shipments?order_id[]=1&order_id[]=2 → 400. Antes: 200 filtrando por las dos órdenes.
  4. GET /api/v1/shipments?status=inventado → 200 con data vacío, que es lo que tiene que seguir pasando.
  5. El listado sin parámetros y con ?status=in_transit siguen igual.

Verificación: bundle exec rspec (1257 ejemplos, 0 fallas), bundle exec rubocop (247 archivos, sin ofensas) y bin/brakeman -q (0 warnings).

Los specs tienen dientes: volví scalar_param(:page) a params[:page] y falló 1 ejemplo; volví scalar_param(:order_id) a params[:order_id] y fallaron 2. Ninguno pasa por construcción.


Impacto y consideraciones

¿Introduce breaking changes?
Para un cliente que mandaba bien los parámetros, no. Sí deja de funcionar ?order_id[]=1&order_id[]=2, que filtraba por varias órdenes — pero eso nunca fue parte del contrato y el front no lo usa (fetchOrderShipment manda un order_id escalar).

¿Requiere nuevas variables de entorno?
No.

¿Afecta la arquitectura o genera un nuevo patrón?
No. Es aplicar a envíos el helper que ya usaban los otros dos índices. Con esto se cumple el criterio de la card de que los tres usen el mismo, sin copias del cálculo de página.


🤖 Generated with Claude Code

https://claude.ai/code/session_012xAtddb53LRNVLuyTXyELp

`shipments#index` was the last listing reading `params[...]` straight.
`?page[]=1` and `?per_page[]=1` answered 500, because `Array#to_i` does not
exist, and `?status[foo]=bar` did too, from putting an
`ActionController::Parameters` inside a `where`.

`?order_id[]=1` was the one worth finding: it did not fail. `where`
translated the array into an `IN`, so the listing filtered by several orders
at once and answered 200 — an undeclared capability hidden behind a
successful response. The card expected a 500 there; it was quieter than
that.

The unknown-versus-malformed distinction stays: `?status=inventado` still
answers an empty list, which is the honest answer for a filter that matches
nothing, and only a malformed shape is a 400.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012xAtddb53LRNVLuyTXyELp

@Sanntinat Sanntinat left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Revisión — TESIS-124 (PR #85) · Parámetros de query de shipments#index

Revisión contra origin/master de proyecto-api, sobre TESIS-124-harden-shipments-query-params (03dab3b), con la card TESIS-124 (sin comentarios de alcance) y la descripción del PR al lado.

Check Resultado
Suite completa (corrida local, sobre la rama) 1257 examples, 0 failures (6 nuevos)
RuboCop · Brakeman (local) 256 archivos, sin ofensas · 0 warnings
CI (GitHub Actions) scan_ruby · lint · test · validate-pr-title: success
Rama vs master Al día (0 commits atrás)
Merge con los otros PRs abiertos de la API (#86, #87, #88) Sin conflictos de texto; los cuatro juntos: 1403 examples, 0 failures

✅ Lo que hace, lo hace bien

  • Los cuatro parámetros de shipments#index pasan por scalar_param, el mismo helper de orders#index, y el rescue_from de MalformedParameterError ya estaba en ApplicationController: no hacía falta nada más.
  • Lo de ?order_id[]=1 es el mejor hallazgo del PR. Lo reproduje sobre master: responde 200 y filtra por las dos órdenes con un IN. Con el PR, 400. Que un parámetro mal formado no rompa pero cambie la semántica de la consulta es peor que el 500, y está bien explicado en el comentario.
  • Se conserva la distinción que pedía la card: ?status=inventado sigue siendo 200 con lista vacía, y hay un ejemplo que la fija.
  • Los defaults no cambian: ?per_page= vacío sigue cayendo en 1, como antes, y un per_page ausente sigue siendo 20.

✅ Los specs tienen dientes. Los rompí uno por uno

Volví cada parámetro a params[...], de a uno, y corrí shipments_spec.rb:

Rotura Resultado
page 1 failure: returns 400 for a page that is not a single value
per_page 1 failure: returns 400 for a per_page that is not a single value
status 1 failure: returns 400 for a status that is not a single value
order_id 2 failures: el del 400 y says which parameter is wrong

Cada rotura hace fallar su propio ejemplo, y ninguno pasa por construcción.

🔴 shipments#index no era el último listado que leía params[...] directo

La descripción dice que «era el último listado de api/v1 que leía params[...] directo». No es así: en master quedan dos más, y los dos tienen exactamente los dos síntomas que este PR corrige en envíos.

# failed_events_controller.rb
page = [params[:page].to_i, 1].max
per_page = params.fetch(:per_page, 20).to_i.clamp(1, 100)
events = events.where(event_type: params[:event_type]) if params[:event_type].present?

# stock_transfers_controller.rb
transfers = transfers.where(status: params[:status]) if params[:status].present?
transfers = transfers.where(product_id: params[:product_id]) if params[:product_id].present?

Lo verifiqué con una sonda de request specs sobre la rama del PR (no la dejé en el repo):

failed-events   page[]          -> NoMethodError   (500)
failed-events   per_page[]      -> NoMethodError   (500)
failed-events   event_type[]    -> 200             (IN silencioso, como order_id)
failed-events   event_type{}    -> TypeError       (500)
stock-transfers status{}        -> TypeError       (500)
stock-transfers status[]        -> 200             (IN silencioso)
stock-transfers product_id[]    -> 200             (IN silencioso)
shipments       order_id[]      -> 400             (este PR; sobre master: 200)

El primer criterio de la card es general: «Ningún listado de api/v1 responde 500 ante un parámetro de query mal formado». Con este PR sigue habiendo dos que sí. La card nombraba sólo envíos y productos porque se escribió mirando esos dos, pero el criterio es el que manda, y el propio PR se presenta como el que cierra el último.

Qué pediría: pasar por scalar_param los parámetros de failed_events#index (page, per_page, event_type; status ya se valida contra el enum y no rompe) y de stock_transfers#index (status, product_id), con un 400 por parámetro como los de envíos. Son los mismos cinco renglones que este PR ya resolvió.

Coordinación con #87 (TESIS-108): ese PR lleva el page/per_page de failed_events al concern Paginatable, que ya usa scalar_param. Si #87 entra primero, acá quedan event_type, status y product_id. Si entra este primero, el cálculo de página de failed_events se toca dos veces. Las dos son del mismo autor, así que conviene decidir el orden antes de seguir.

🟡 El segundo criterio queda a medias, y no por culpa de este PR

«Los tres índices usan el mismo helper, sin copias del cálculo de página»: el helper es el mismo, pero el cálculo de página sigue copiado en orders, products y shipments. Eso es TESIS-108. Sin embargo, #87 tampoco lo resuelve para envíos: su diff no toca shipments_controller.rb ni orders_controller.rb (lo detallo en su revisión). Así que el conflicto que anuncian los dos PRs en shipments#index no existe: simulé los merges en todos los órdenes y no hay ninguno. Esto no bloquea este PR, pero conviene saber que, con los dos mergeados, el criterio de la card sigue sin cumplirse.

Los criterios de la card

  • Ningún listado de api/v1 responde 500 ante un parámetro de query mal formado. No se cumple: faltan failed-events y stock-transfers (ver la sonda).
  • [~] Los tres índices usan el mismo helper, sin copias del cálculo de página. Se cumple lo del helper. Las copias del cálculo quedan para TESIS-108.
  • Hay un spec por parámetro que falla si alguien vuelve a leer params[...] directo. Verificado rompiendo cada uno.

Veredicto

REQUEST CHANGES.

Lo que el PR hace está bien hecho, y el hallazgo del IN silencioso vale por sí mismo. Pido cambios por un solo motivo: el criterio central de la card no se cumple, y la descripción afirma lo contrario. Extender el mismo tratamiento a failed_events#index y stock_transfers#index, con sus specs de 400, lo cierra. Antes de hacerlo, conviene acordar el orden con #87.

The review probed the listings the card did not name and found the same
two symptoms this PR fixes for shipments: `?event_type[]=x` and
`?status[]=x` answer 200 while filtering with an IN, and a hash-shaped
filter raises TypeError as a 500.

The card's criterion is general — no listing of api/v1 answers 500 to a
malformed query parameter — so `failed_events#index` and
`stock_transfers#index` now read their filters with `scalar_param`, with
a 400 spec per parameter.

The status of failed events was not crashing, since it is validated
against the enum, but a list-shaped status was silently ignored and the
whole listing came back as if no filter had been sent.

`page` and `per_page` of failed events stay untouched here: TESIS-108
moves them into the Paginatable concern, which already uses
`scalar_param`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012xAtddb53LRNVLuyTXyELp
@TomasMartin2004

Copy link
Copy Markdown
Contributor Author

Respuesta — TESIS-124 (review de Santiago, 24/sep)

Commit en TESIS-124-harden-shipments-query-params: d7c9d30.

🔴 No era el último listado que leía params[...] directo

Tenías razón, y la sonda lo deja sin discusión: failed-events y stock-transfers tenían los dos síntomas —el IN silencioso con 200 y el TypeError con 500—. El criterio de la card es general, así que lo general es lo que manda; la descripción afirmaba lo contrario y estaba mal.

Los dos listados leen ahora sus filtros con scalar_param, con un 400 por parámetro:

  • failed-events: event_type y también status. No rompía, porque lo valida el enum, pero un ?status[]=dead se ignoraba en silencio y devolvía el listado entero como si no se hubiera filtrado — la versión educada del mismo problema.
  • stock-transfers: status y product_id.

Siete ejemplos nuevos, y tienen dientes: volví los dos filtros de transferencias a params[...] y caen los cuatro de ese archivo.

page y per_page de failed-events los dejé quietos acá a propósito: los lleva #87 al concern, que ya usa scalar_param.

El reparto con #87

Tomé tu sugerencia de acordarlo antes de seguir:

PR Qué toca
#85 (este) Sólo los filtros
#87 (TESIS-108) Sólo la página de órdenes, envíos, failed-events y transferencias

Así no se pisan, salvo en las dos líneas de shipments#index que #87 ahora sí toca —ahí el conflicto pasó a existir de verdad, y el que entre segundo se queda con el concern—. Orden: #86 → #87 → #85.

El segundo criterio

Con #87 en su forma nueva, el cálculo de página queda efectivamente en un solo archivo, que era lo que faltaba para cerrarlo.

Verificación: rspec → 1265 ejemplos, 0 fallas; RuboCop limpio en 256 archivos; Brakeman 0 warnings.

Gracias por sondear los listados que la card no nombraba — el hallazgo es de la misma familia que el del order_id[], y era el que faltaba para que el criterio fuera cierto.

🤖 Generated with Claude Code

https://claude.ai/code/session_012xAtddb53LRNVLuyTXyELp

@Sanntinat Sanntinat left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-revisión — TESIS-124 (PR #85) · Parámetros de query de los listados

Segunda vuelta sobre TESIS-124-harden-shipments-query-params (d7c9d30), contra origin/master de proyecto-api. La primera revisión pidió cambios porque failed-events y stock-transfers seguían leyendo params[...] directo.

Check Resultado
CI (GitHub Actions) lint · scan_ruby · test · validate-pr-title: success
Rama vs master 2 commits atrás (TESIS-82 y TESIS-129). Mergea limpio
Merge en el orden propuesto (master → #86 → #87 → #85), resuelto a mano 1372 examples, 0 failures · RuboCop 271 archivos sin ofensas
Conflictos con otros PRs Con #87: shipments_controller.rb y stock_transfers_controller.rb (ver abajo). Con #88: failed_events_spec.rb y stock_transfers_spec.rb, de texto

🔴 → ✅ Los dos listados que faltaban

failed_events#index lee event_type y status con scalar_param, y stock_transfers#index hace lo mismo con status y product_id. Me gustó que se sumara status de failed-events: no rompía, pero ?status[]=dead se ignoraba y devolvía el listado entero. Es la versión silenciosa del mismo problema.

Tienen dientes, en los dos archivos. Tomás lo mostró para transferencias; yo lo repetí para failed-events, volviendo sus tres lecturas a params[...]:

failed_events_spec.rb -> 19 examples, 4 failures (los cuatro nuevos)

Con #87 y #85 aplicados, no queda en api/v1 ningún filtro de listado leyendo params[...] directo (busqué params[:status], [:order_id], [:product_id], [:page] y [:event_type] en los controllers).

🟡 El reparto con #87 no es tan limpio como dice la respuesta

La respuesta dice que los dos PRs «no se pisan salvo en las dos líneas de shipments#index». Lo simulé en el orden propuesto y también choca stock_transfers_controller.rb. Ese conflicto trae una trampa: #87 movió los filtros a un privado nuevo, filtered_transfers, que sigue leyendo params[:status] y params[:product_id]. Si quien resuelva «se queda con el concern», como sugiere la respuesta, pierde el endurecimiento de este PR.

Lo probé resolviendo así a propósito: 7 ejemplos en rojo, cuatro de transferencias y tres de envíos. O sea que la suite lo atrapa, y no se perdería en silencio. Pero hay que saber qué hacer. Cómo lo resolví yo, y dejó todo en verde:

  • shipments_controller.rb: el index de #87 (sin las dos líneas de página) y el filtered_shipments de este PR.
  • stock_transfers_controller.rb: el index de #87 y, adentro de filtered_transfers, las lecturas con scalar_param de este PR.

🟡 La descripción del PR sigue diciendo lo que ya no es cierto

La respuesta dice que la descripción «afirmaba lo contrario y estaba mal», pero no se editó. Todavía abre con «shipments#index era el último listado de api/v1 que leía params[...] directo», no nombra los dos listados nuevos y el conteo de verificación es el viejo (1257). Es lo que va a leer quien llegue al PR desde la card.

Los criterios de la card

  • Ningún listado de api/v1 responde 500 ante un parámetro de query mal formado. Con #87 para la página y este PR para los filtros.
  • Los tres índices usan el mismo helper, sin copias del cálculo de página. Con #87, que lleva el cálculo al concern.
  • Hay un spec por parámetro que falla si alguien vuelve a leer params[...] directo. Verificado en los tres archivos.

Veredicto

APPROVE.

El 🔴 está resuelto, y lo verifiqué rompiéndolo. Lo que queda son dos cosas de proceso, ninguna del código: actualizar la descripción y resolver el conflicto con #87 como está arriba, que es el caso en que el orden de merge importa de verdad.

TomasMartin2004 and others added 2 commits September 25, 2026 22:39
Brings in TESIS-82, TESIS-129 and TESIS-131.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012xAtddb53LRNVLuyTXyELp
TESIS-108 landed, so the page calculation of shipments and transfers
now lives in the concern and this card keeps only its half: the filters
read through `scalar_param`.

In the two request specs both sides stay — master's examples cover the
filters that work, and this card's cover the 400 for a filter that is
not a single value.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012xAtddb53LRNVLuyTXyELp
@TomasMartin2004
TomasMartin2004 merged commit ed868f3 into master Sep 26, 2026
4 checks passed
@TomasMartin2004
TomasMartin2004 deleted the TESIS-124-harden-shipments-query-params branch September 26, 2026 16:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants