Pruebe una integración de Carrier Lookup sin conexión con fixtures de respuesta
Pruebe campos de operador planos, resultados vacíos, respuestas múltiples ordenadas y ramas de error sin datos de contacto reales ni llamadas a la API de pago.

Inyecte el transporte y pruebe fixtures de respuesta sintéticos explícitos antes de permitir que un consumidor de CarrierLookup utilice la red.
Las pruebas mock de la API de Carrier Lookup deben reemplazar el transporte HTTP, denegar el acceso accidental a la red y verificar cómo su aplicación maneja cada resultado documentado. Los fixtures sintéticos permiten probar la lógica del consumidor sin enviar números de teléfono ni gastar saldo.
Coloque el punto de prueba antes de la solicitud HTTP
Un importador de CRM puede superar su prueba de camino feliz y aun así etiquetar un número Indeterminado como inválido. También puede superar una prueba de navegador simulada mientras su backend realiza una solicitud real de pago. Coloque su punto de prueba en la función del backend que realiza el intercambio HTTP saliente.
El consumidor debe aceptar una dependencia de transporte en lugar de descubrir un cliente real internamente. El transporte de prueba inyectado recibe la ruta y el cuerpo de la solicitud previstos, los verifica y devuelve un sobre sintético. No hay ninguna clave de API en la prueba. Mantenga las credenciales activas fuera del trabajo de CI también.
Utilice una restricción de red separada como medida de seguridad. Un mock que devuelve datos convenientes no es prueba de que cada llamada saliente fue interceptada. Deniegue el tráfico saliente en el contenedor de CI o en el límite del ejecutor, y haga que el trabajo falle si se intenta acceder a la red. Un protector a nivel de lenguaje ayuda a detectar errores comunes, pero no puede cubrir subprocesos ni todas las bibliotecas de red.
Defina los resultados antes de escribir los fixtures
Nuestro servicio de operador síncrono utiliza service_type=carrier. Las respuestas individuales tienen campos data planos, incluyendo identifier, carrier, underlying_carrier, number_type, country_code, region y city. Leer data.extra.carrier prueba una forma inventada.
| Fixture | Aserción del consumidor |
|---|---|
| Código 0 con campos de asignación | Mantener los valores de asignación original |
| Código 0 con todos los campos de asignación vacíos | Consulta completada con valores no disponibles |
| Código 42200 | Indeterminado; no crear un éxito vacío |
| Elemento múltiple con exists=false | Sin resultado normal; no marcar el número como desconectado |
| Código 42901 | Rama ocupada; sin actualización exitosa del CRM |
| Código 50400 | Rama de tiempo de espera; sin actualización exitosa del CRM |
Los valores del operador describen la asignación original. No deben completar un campo del CRM llamado red actual. Del mismo modo, ni un resultado de asignación vacío ni exists=false establecen la validez de un teléfono. Utilice etiquetas de resultado de aplicación separadas para que una consulta de informe posterior no pueda fusionar estos estados accidentalmente.
Ejecute una pequeña suite de fixtures sintéticos
Guarde lo siguiente como carrier_consumer_test.py y ejecute python3 carrier_consumer_test.py. Utiliza la biblioteca estándar de Python. El ejemplo es un punto de conexión mínimo del consumidor, con identificadores sintéticos y valores elegidos deliberadamente; nunca contacta a CarrierLookup. Reemplace el client y el clasificador de demostración con su propio adaptador mientras mantiene las aserciones de los fixtures.
import socket
import unittest
from unittest.mock import patch
FIELDS = ("carrier", "underlying_carrier", "number_type",
"country_code", "region", "city")
A, B = "+12025550101", "+12025550102" # Solo datos de prueba sintéticos.
def classify(envelope):
code = envelope["code"]
if code != 0:
return {42200: "undetermined", 42901: "busy",
50400: "timeout"}.get(code, "error")
data = envelope["data"]
if data["service_type"] != "carrier":
raise ValueError("unexpected service")
return "completed" if data["carrier"] else "completed_empty"
def client(transport, identifiers):
multi = len(identifiers) > 1
path = "/api/v1/batch-check" if multi else "/api/v1/check"
body = {"service_type": "carrier"}
body["identifiers" if multi else "identifier"] = (
identifiers if multi else identifiers[0])
return transport(path, body)
def completed(identifier, carrier=""):
return {"code": 0, "msg": "synthetic fixture", "data": {
"service_type": "carrier", "identifier": identifier,
**dict.fromkeys(FIELDS, ""), "carrier": carrier}}
class ConsumerTests(unittest.TestCase):
def setUp(self):
self.guard = patch.object(
socket.socket, "connect",
side_effect=AssertionError("network forbidden"))
self.guard.start()
self.addCleanup(self.guard.stop)
def test_single_flat_data_and_outcomes(self):
for value, expected in [("Example Allocation", "completed"),
("", "completed_empty")]:
def fake(path, body):
self.assertEqual(path, "/api/v1/check")
self.assertEqual(body, {"service_type": "carrier",
"identifier": A})
return completed(A, value)
result = client(fake, [A])
self.assertNotIn("extra", result["data"])
self.assertNotIn("registered", result["data"])
self.assertEqual(classify(result), expected)
def test_error_branches(self):
for code, expected in [(42200, "undetermined"),
(42901, "busy"), (50400, "timeout")]:
with self.subTest(code=code):
result = client(lambda path, body: {
"code": code, "msg": "synthetic error",
"data": None}, [A])
self.assertEqual(classify(result), expected)
def test_multi_order_and_no_result(self):
def fake(path, body):
self.assertEqual(path, "/api/v1/batch-check")
self.assertEqual(body, {"service_type": "carrier",
"identifiers": [B, A]})
return {"code": 0, "msg": "synthetic fixture", "data": {
"service_type": "carrier", "total": 2,
"succeeded": 1, "failed": 1, "results": [
{"identifier": B, "exists": True,
**dict.fromkeys(FIELDS, "")},
{"identifier": A, "exists": False}]}}
data = client(fake, [B, A])["data"]
rows = data["results"]
self.assertEqual(data["service_type"], "carrier")
self.assertEqual([r["identifier"] for r in rows], [B, A])
self.assertEqual(len(rows), data["total"])
outcomes = ["completed" if r["exists"] else "no_result"
for r in rows]
self.assertEqual(outcomes, ["completed", "no_result"])
self.assertNotIn("carrier", rows[1])
def test_guard_rejects_an_escape(self):
with socket.socket() as sock:
with self.assertRaisesRegex(AssertionError, "network forbidden"):
sock.connect(("127.0.0.1", 9))
if __name__ == "__main__":
unittest.main()
El ejemplo devuelve sobres ya decodificados desde su transporte. Prueba la construcción de la solicitud y el manejo de resultados, en lugar del análisis HTTP. Mantenga los fixtures para el estado HTTP, los encabezados y el JSON mal formado también en su límite de transporte real. En particular, una respuesta 42901 incluye Retry-After; un fixture de encabezado de respuesta debe verificar que su transporte lo preserve para el llamador. No codifique un nuevo intervalo de reintento en el clasificador de resultados.
El protector de socket prueba intencionalmente su propia ruta de falla. Bloquea el método de conexión de socket de este proceso de Python; la restricción de salida de CI separada sigue siendo necesaria para otras rutas. Una llamada de pago accidental debería hacer fallar el trabajo, no convertirse en un fixture adicional inadvertido.
Haga que las aserciones de resultados múltiples sean sensibles a errores de orden
El fixture múltiple envía B antes que A. Eso importa: una prueba que envía identificadores ordenados alfabéticamente y recibe el mismo orden ordenado puede pasar por alto un adaptador que ordena las respuestas. Haga coincidir cada identificador devuelto con su posición de entrada y verifique la longitud total.
El primer elemento múltiple produce un resultado completado con valores de asignación vacíos. El segundo tiene exists=false y no tiene campos de operador. Preserve esa diferencia al mapear a los registros de la aplicación. Si su adaptador de producción descarta filas fallidas, el fixture debería fallar porque, de lo contrario, las filas posteriores podrían desplazarse a los contactos incorrectos.
Agregue una aserción en la función de mapeo del CRM real: una consulta indeterminada o fallida debe dejar intacta la observación de asignación anterior o marcarla explícitamente como obsoleta según su política. No debe sobrescribir el registro con "número inválido". La misma prueba debería rechazar derivar la ubicación actual de la región o ciudad de asignación.
Un tiempo de espera múltiple síncrono hace fallar toda la solicitud sin resultados parciales. Pruebe la rama de tiempo de espera de toda la solicitud por separado de un sobre exitoso que contenga un elemento fallido. Esas son entradas diferentes para el consumidor.
Mantenga la confianza offline separada de la verificación en vivo
Estas pruebas establecen cómo responde su código al contrato representado por los fixtures. No demuestran la disponibilidad actual del proveedor, la aceptación del país, los permisos de cuenta o la facturación. Un fixture fabricado tampoco puede probar que un proveedor en vivo devolverá un campo de asignación particular para un número particular.
Mantenga la procedencia del fixture junto a la prueba: la CarrierLookup API reference, la fecha de revisión y los campos que consume su adaptador. Actualice los fixtures cuando cambie un contrato documentado, luego examine si los datos antiguos de la aplicación necesitan una ruta de lectura compatible.
Ejecute una prueba de humo en vivo autorizada por separado cuando la verificación de integración lo requiera, utilizando una entrada aprobada y su propio presupuesto de saldo. No haga que la CI ordinaria dependa de esa llamada. Comience reemplazando una importación de cliente en vivo en su suite de pruebas con un transporte inyectado y confirmando que un intento de escape de red falle inmediatamente.
Aprenda más
Elija la información del producto que se ajuste al siguiente paso en su flujo de trabajo.