Ir al contenido

API de comentarios en casos de prueba

Los comentarios de casos de prueba se adjuntan a un caso específico mediante un almacén genérico de comentarios (entity_type='test_case', entity_id), compartido con los comentarios de defectos. Ambas superficies son estructuralmente idénticas y solo difieren en la ruta de la entidad. Los comentarios se limitan a la organización mediante el ULID del caso de prueba.

Los comentarios admiten un nivel de respuestas en hilo, fijado y resolución de menciones @. El cuerpo es texto enriquecido: envíalo como Markdown en body, o como documento en bodyDocument. Las imágenes van insertadas en el cuerpo, preparadas con POST /api/v1/images:stage; un comentario no tiene lista de adjuntos.

MétodoRutaDescripción
GET/api/v1/test-cases/{caseUlid}/commentsListar comentarios de primer nivel (paginación por cursor)
POST/api/v1/test-cases/{caseUlid}/commentsAgregar un comentario, o una respuesta usando parentUlid
PATCH/api/v1/test-cases/{caseUlid}/comments/{commentUlid}Editar un comentario o respuesta
DELETE/api/v1/test-cases/{caseUlid}/comments/{commentUlid}Eliminar un comentario (elimina en cascada sus respuestas)
POST/api/v1/test-cases/{caseUlid}/comments/{commentUlid}/pinFijar un comentario de primer nivel
DELETE/api/v1/test-cases/{caseUlid}/comments/{commentUlid}/pinDesfijar un comentario de primer nivel

{caseUlid} debe pertenecer a la organización activa. El acceso cruzado entre organizaciones devuelve 404 not_found.

Todos los endpoints requieren una sesión autenticada o un token Bearer con al menos acceso viewer.

OperaciónRol mínimo
GETVisualizador
POST (comentario o respuesta)Miembro
PATCHMiembro (propio comentario únicamente)
DELETEMiembro (propio) o Administrador/Propietario (cualquiera)
POST / DELETE .../pinMiembro (cualquier comentario de primer nivel)
GET/api/v1/test-cases/{caseUlid}/comments

Devuelve los comentarios de primer nivel (parentUlid: null) con paginación por cursor.

ParámetroTipoDescripción
cursorstringCursor ULID de la página anterior (nextCursor).
limitintegerTamaño de página. Predeterminado 50, máximo 200.
sortoldest | newestOrden de los comentarios de primer nivel. Predeterminado oldest (sin cambios respecto al comportamiento público previo). Las respuestas siempre se mantienen en orden cronológico bajo su padre, sin importar este valor.
{
"items": [
{
"ulid": "01J…",
"body": "Reproducido en staging con una sesión nueva del navegador.",
"bodyDocument": {
"type": "doc",
"attrs": { "version": 1 },
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Reproducido en staging con una sesión nueva del navegador."
}
]
}
]
},
"author": {
"ulid": "01J…",
"displayName": "Ada Lovelace",
"avatarKey": null,
"avatarVersion": 0
},
"createdAt": 1700000000000,
"editedAt": null,
"attachments": [],
"parentUlid": null,
"pinnedAt": null,
"pinnedBy": null,
"mentions": [],
"replyCount": 1,
"replies": [
{
"ulid": "01J…RPLY",
"body": "Confirmado — mismo resultado de mi lado. @[01J…GRACE]",
"bodyDocument": {
"type": "doc",
"attrs": { "version": 1 },
"content": [
{
"type": "paragraph",
"content": [
{ "type": "text", "text": "Confirmado — mismo resultado de mi lado. " },
{ "type": "mention", "attrs": { "id": "01J…GRACE" } }
]
}
]
},
"author": {
"ulid": "01J…",
"displayName": "Grace Hopper",
"avatarKey": null,
"avatarVersion": 0
},
"createdAt": 1700000100000,
"editedAt": null,
"attachments": [],
"parentUlid": "01J…",
"pinnedAt": null,
"pinnedBy": null,
"mentions": [{ "ulid": "01J…GRACE", "displayName": "Grace Hopper" }]
}
]
}
],
"nextCursor": "01J…",
"pinned": []
}
  • body (Markdown) y bodyDocument (el documento) contienen el mismo texto. Al editar, devuelve uno de los dos, nunca ambos.
  • attachments siempre es []. Las imágenes de un comentario van insertadas en su cuerpo.
  • author es null cuando el miembro fue removido de la organización.
  • editedAt es null cuando el comentario nunca se ha editado.
  • nextCursor es null en la última página.
  • pinned es el conjunto completo de comentarios de primer nivel fijados para este caso de prueba, ordenado por pinnedAt ascendente, independiente del cursor paginado de items — muéstralo encima del feed. Es idéntico sin importar sort.
  • Un objeto de respuesta nunca lleva replyCount ni replies — la profundidad del hilo está limitada a un nivel.
  • El arreglo replies de un elemento de primer nivel está en orden cronológico ascendente y limitado a 100 entradas inline.

Importante: las respuestas no aparecen en la enumeración plana de items[] — solo son accesibles a través de replies[] de su comentario padre. Este es un cambio aditivo pero no compatible byte a byte con los clientes que asumían que items[] enumeraba todos los comentarios: recorre replies de cada elemento si necesitas el contenido de las respuestas, y usa replyCount si solo necesitas el conteo.

POST/api/v1/test-cases/{caseUlid}/comments
CampoRequeridoNotas
bodyuno de los dosMarkdown. Sin espacios sobrantes, máximo 5000 caracteres, y debe contener contenido: los valores que solo contienen espacios son rechazados. Puede contener tokens de mención @[ulid] (ver Menciones).
bodyDocumentuno de los dosEl documento versionado del cuerpo (ver Campos de texto enriquecido). Envía body o bodyDocument, nunca ambos.
parentUlidnoULID de un comentario de primer nivel existente en el mismo caso de prueba. Crea una respuesta de profundidad 1. Omítelo para crear un comentario de primer nivel (comportamiento sin cambios).

Errores:

  • 422 validation_failed — se envían body y bodyDocument a la vez, ninguno contiene contenido, body supera los 5000 caracteres, la solicitud incluye una clave que este endpoint no acepta (como attachments), o parentUlid apunta a un comentario que es en sí mismo una respuesta (responder a una respuesta está rechazado). El mensaje describe el problema; details contiene el campo que falló.
  • 422 validation_failed con details.field igual a body — un rechazo de texto enriquecido. details.code indica la regla que rompe el cuerpo, por ejemplo too_large para un documento que supera el límite de 256 KiB (ver Campos de texto enriquecido).
  • 422 validation_failed con details.code igual a unknown_image — el documento menciona una imagen que nunca se preparó o que expiró. details.ulids enumera esas imágenes.
  • 404 not_found — parentUlid no corresponde a un comentario de primer nivel de este caso de prueba (entidad u organización incorrecta).
  • 403 — rol visualizador.
{
"ulid": "01J…",
"body": "También se ve bien en móvil.",
"bodyDocument": {
"type": "doc",
"attrs": { "version": 1 },
"content": [
{
"type": "paragraph",
"content": [{ "type": "text", "text": "También se ve bien en móvil." }]
}
]
},
"author": { "ulid": "01J…", "displayName": "Ada", "avatarKey": null, "avatarVersion": 0 },
"createdAt": 1700000000000,
"editedAt": null,
"attachments": [],
"parentUlid": null,
"pinnedAt": null,
"pinnedBy": null,
"mentions": [],
"replyCount": 0,
"replies": []
}

Auditoría: emite test_case.commented con metadata.excerpt (como máximo 140 caracteres del texto plano del cuerpo).

PATCH/api/v1/test-cases/{caseUlid}/comments/{commentUlid}

Solo el autor original del comentario puede editarlo (exclusivo del autor, ya sea de primer nivel o respuesta). Los administradores y propietarios no pueden editar el comentario de otro miembro. Las menciones se vuelven a resolver a partir del nuevo cuerpo en cada edición.

CampoRequeridoNotas
bodyuno de los dosCuerpo Markdown de reemplazo, con la misma validación que POST.
bodyDocumentuno de los dosDocumento de reemplazo, con la misma validación que POST. Envía body o bodyDocument, nunca ambos.

Comentario actualizado con editedAt establecido en el momento actual y mentions[] reflejando el nuevo cuerpo.

Errores: 403 si no eres el autor. 404 si el comentario no existe en este caso (caseUlid o organización incorrectos). 422 validation_failed para los mismos rechazos que POST.

Auditoría: emite test_case.comment_updated con metadata.excerpt.

DELETE/api/v1/test-cases/{caseUlid}/comments/{commentUlid}

Los autores pueden eliminar su propio comentario. Los administradores y propietarios pueden eliminar cualquier comentario. Eliminar un comentario de primer nivel elimina en cascada todas sus respuestas. Cada comentario eliminado libera las imágenes insertadas que contiene su cuerpo, de modo que una imagen que ningún otro texto ni revisión almacenada usa se elimina junto con él.

Errores: 403 si el miembro intenta eliminar el comentario de otro miembro.

Auditoría: emite test_case.comment_deleted por el comentario eliminado, más un test_case.comment_deleted adicional por cada respuesta eliminada en cascada.

POST/api/v1/test-cases/{caseUlid}/comments/{commentUlid}/pin

Fija un comentario de primer nivel para que siempre aparezca en el arreglo pinned[] de la respuesta de listado, mostrado antes del feed paginado sin importar sort. Cualquier miembro o rol superior puede fijar cualquier comentario de primer nivel (no solo los propios). No hay límite en la cantidad de comentarios fijados por caso de prueba.

Errores: 422 validation_failed si commentUlid corresponde a una respuesta — solo los comentarios de primer nivel pueden fijarse. 403 para el rol visualizador. 404 si el comentario no existe en este caso.

Comentario actualizado con pinnedAt (epoch ms) y pinnedBy (resumen del actor que lo fijó) establecidos.

Auditoría: emite test_case.comment_pinned.

DELETE/api/v1/test-cases/{caseUlid}/comments/{commentUlid}/pin

Limpia pinnedAt/pinnedBy. Cualquier miembro o rol superior puede desfijar.

Comentario actualizado con pinnedAt: null, pinnedBy: null.

Errores: 403 para el rol visualizador. 404 si el comentario no existe en este caso.

Auditoría: emite test_case.comment_unpinned.

El cuerpo de un comentario puede incluir tokens @[ulid] que referencian a un miembro de la organización por su ULID (la forma ULID, no un nombre visible, para que los cambios de nombre nunca rompan una mención). En bodyDocument, la misma mención es un nodo mention cuyo attrs.id es ese ULID. Al crear o editar, el servidor:

  1. Extrae cada mención del cuerpo: el token @[ulid] en Markdown o el nodo mention en el documento.
  2. Resuelve cada mención solo contra los miembros de la organización activa — un ULID de otra organización, o uno que no corresponde a un miembro, se deja como texto plano sin formato y no crea ninguna fila de mención.
  3. Persiste una fila por cada mención válida distinta, y reconcilia el conjunto en cada edición (las menciones eliminadas se borran y se agregan las nuevas).

El arreglo mentions[] de la respuesta siempre refleja el nombre visible actual del miembro mencionado — renombrar a un miembro actualiza cada mención pasada que lo referencia. Las menciones nunca generan una notificación ni un correo.

Cuando se elimina un caso de prueba (DELETE /api/v1/test-cases/{caseUlid}), todos sus comentarios y respuestas se eliminan junto con las imágenes insertadas que contienen, en la misma transacción que el caso. No se requiere ninguna llamada adicional a la API; si la eliminación se rechaza, los comentarios se conservan.

Ventana de terminal
# Listar la página de comentarios más recientes primero
curl -sS \
-H "Authorization: Bearer $PROBARA_API_TOKEN" \
"https://app.probara.net/api/v1/test-cases/01J...CASE/comments?sort=newest"
# Agregar un comentario de primer nivel
curl -sS -X POST \
-H "Authorization: Bearer $PROBARA_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"body":"Verificado en Safari 17 — pasa."}' \
"https://app.probara.net/api/v1/test-cases/01J...CASE/comments"
# Responder a un comentario de primer nivel, mencionando a un compañero
curl -sS -X POST \
-H "Authorization: Bearer $PROBARA_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"body":"Gracias @[01J...GRACE] — confirmado también aquí.","parentUlid":"01J...CMT"}' \
"https://app.probara.net/api/v1/test-cases/01J...CASE/comments"
# Fijar un comentario
curl -sS -X POST \
-H "Authorization: Bearer $PROBARA_API_TOKEN" \
"https://app.probara.net/api/v1/test-cases/01J...CASE/comments/01J...CMT/pin"
# Desfijar un comentario
curl -sS -X DELETE \
-H "Authorization: Bearer $PROBARA_API_TOKEN" \
"https://app.probara.net/api/v1/test-cases/01J...CASE/comments/01J...CMT/pin"
# Editar un comentario
curl -sS -X PATCH \
-H "Authorization: Bearer $PROBARA_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"body":"Nota actualizada tras volver a probar."}' \
"https://app.probara.net/api/v1/test-cases/01J...CASE/comments/01J...CMT"
# Eliminar un comentario
curl -sS -X DELETE \
-H "Authorization: Bearer $PROBARA_API_TOKEN" \
"https://app.probara.net/api/v1/test-cases/01J...CASE/comments/01J...CMT"