All checks were successful
Build and deploy / Validate source (push) Successful in 5s
Build and deploy / Integration suite on a real stack (push) Successful in 2m19s
Build and deploy / Secret scan and release gate (push) Successful in 7s
Build and deploy / Publish images (push) Successful in 1m33s
Jadlog issues the account as 000000-0 and documents a six-character field; stripping the dash sent seven digits. The value now goes as typed, so the accepted form can be found on the account without a new release. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
148 lines
6.7 KiB
Python
148 lines
6.7 KiB
Python
"""The Jadlog quote client against a fake HTTP transport: payload and errors.
|
|
|
|
This proves the manual's contract only; app.jadlog_probe must still confirm
|
|
it on the client's account. Runs where httpx is installed.
|
|
"""
|
|
import json
|
|
import os
|
|
import unittest
|
|
from unittest import mock
|
|
|
|
import httpx
|
|
|
|
from app.jadlog import QUOTE_URL, JadlogError, JadlogFreight, JadlogQuotes
|
|
from app.jadlog_probe import run
|
|
|
|
CNPJ = '11.222.333/0001-81'
|
|
|
|
|
|
def client(handler, **settings):
|
|
return JadlogQuotes(token=settings.pop('token', 'tok-1'), cnpj=CNPJ, conta='123456',
|
|
transport=httpx.MockTransport(handler), **settings)
|
|
|
|
|
|
class JadlogQuoteTest(unittest.TestCase):
|
|
def test_payload_follows_the_manual_and_price_becomes_centavos(self):
|
|
seen = []
|
|
|
|
def handler(request):
|
|
seen.append(request)
|
|
item = json.loads(request.content)['frete'][0]
|
|
return httpx.Response(200, json={'frete': [{**item, 'vltotal': 23.455, 'prazo': 4}]})
|
|
|
|
result = client(handler).quote('01310-100', 1.5, 12990)
|
|
request = seen[0]
|
|
self.assertEqual(str(request.url), QUOTE_URL)
|
|
self.assertEqual(request.headers['authorization'], 'tok-1')
|
|
item = json.loads(request.content)['frete'][0]
|
|
self.assertEqual(item, {'cepori': '14402310', 'cepdes': '01310100', 'frap': 'N', 'peso': 1.5,
|
|
'cnpj': '11222333000181', 'conta': '123456', 'contrato': None,
|
|
'modalidade': 3, 'tpentrega': 'D', 'tpseguro': 'N',
|
|
'vldeclarado': 129.9, 'vlcoleta': 0})
|
|
self.assertEqual((result['total_cents'], result['days']), (2346, 4))
|
|
|
|
def test_contract_is_sent_only_when_configured(self):
|
|
def handler(request):
|
|
item = json.loads(request.content)['frete'][0]
|
|
self.assertEqual(item['contrato'], '042')
|
|
return httpx.Response(200, json={'frete': [{'vltotal': 10, 'prazo': 2}]})
|
|
|
|
client(handler, contrato='042').quote('01310100', 1, 100)
|
|
|
|
def test_account_is_sent_as_configured(self):
|
|
def handler(request):
|
|
self.assertEqual(json.loads(request.content)['frete'][0]['conta'], '123456-7')
|
|
return httpx.Response(200, json={'frete': [{'vltotal': 10, 'prazo': 2}]})
|
|
|
|
JadlogQuotes(token='tok', cnpj=CNPJ, conta=' 123456-7 ', transport=httpx.MockTransport(handler)).quote('01310100', 1, 100)
|
|
|
|
def test_account_and_item_errors_are_raised_with_jadlog_text(self):
|
|
replies = [
|
|
httpx.Response(200, json={'frete': [{}], 'error': {'id': -1, 'descricao': 'frete[0].contrato Numero de contrato invalido'}}),
|
|
httpx.Response(200, json={'frete': [{'erro': {'id': 2, 'descricao': 'CEP destino invalido'}}]}),
|
|
httpx.Response(200, json={'frete': [{'prazo': 3}]}),
|
|
httpx.Response(502, text='<html>bad gateway</html>'),
|
|
]
|
|
messages = ['contrato invalido', 'CEP destino invalido', 'no freight value', 'not JSON']
|
|
for reply, message in zip(replies, messages):
|
|
with self.subTest(message=message), self.assertRaisesRegex(JadlogError, message):
|
|
client(lambda request, reply=reply: reply).quote('01310100', 1, 100)
|
|
|
|
def test_missing_credentials_refuse_to_start(self):
|
|
with self.assertRaises(RuntimeError):
|
|
JadlogQuotes(token='', cnpj=CNPJ)
|
|
with self.assertRaises(RuntimeError):
|
|
JadlogQuotes(token='tok', cnpj='123')
|
|
|
|
|
|
class JadlogFreightTest(unittest.TestCase):
|
|
WEIGHTS = {'JADLOG_PESO_BASE_KG': '0.2', 'JADLOG_PESO_POR_METRO_KG': '0.15', 'FREIGHT_PRODUCTION_DAYS': '2'}
|
|
|
|
def freight(self, handler, **env):
|
|
with mock.patch.dict(os.environ, {**self.WEIGHTS, **env}):
|
|
return JadlogFreight(client(handler))
|
|
|
|
def test_package_weight_value_and_days(self):
|
|
seen = []
|
|
|
|
def handler(request):
|
|
seen.append(json.loads(request.content)['frete'][0])
|
|
return httpx.Response(200, json={'frete': [{'vltotal': 18.4, 'prazo': 3}]})
|
|
|
|
quote = self.freight(handler).quote('jadlog', '01310100', '2.8', 6972)
|
|
# 0.2 kg of packaging plus 0.15 kg for each of the 2.8 billed metres.
|
|
self.assertEqual((seen[0]['peso'], seen[0]['vldeclarado'], seen[0]['cepdes']), (0.62, 69.72, '01310100'))
|
|
self.assertEqual((quote['total_cents'], quote['days'], quote['service']), (1840, 5, 'jadlog'))
|
|
|
|
def test_pickup_is_free_and_bad_requests_are_refused(self):
|
|
freight = self.freight(lambda request: httpx.Response(500))
|
|
self.assertEqual(freight.quote('pickup', '')['total_cents'], 0)
|
|
for args in (('mock-standard', '01310100', '1'), ('jadlog', '0131', '1'), ('jadlog', '01310100', None)):
|
|
with self.subTest(args=args), self.assertRaises(ValueError):
|
|
freight.quote(*args)
|
|
|
|
def test_a_jadlog_failure_is_a_clear_refusal(self):
|
|
freight = self.freight(lambda request: httpx.Response(200, json={'frete': [{}], 'error': {'id': -1, 'descricao': 'CEP destino invalido'}}))
|
|
with self.assertRaisesRegex(ValueError, 'CEP destino invalido'):
|
|
freight.quote('jadlog', '01310100', '1', 100)
|
|
|
|
def test_the_weight_has_no_default(self):
|
|
with mock.patch.dict(os.environ, {}, clear=True), self.assertRaises(KeyError):
|
|
JadlogFreight(client(lambda request: httpx.Response(200)))
|
|
with self.assertRaises(RuntimeError):
|
|
self.freight(lambda request: httpx.Response(200), JADLOG_PESO_POR_METRO_KG='0')
|
|
|
|
|
|
class JadlogProbeTest(unittest.TestCase):
|
|
def test_bearer_is_tried_once_after_a_401_and_kept(self):
|
|
headers = []
|
|
|
|
def handler(request):
|
|
headers.append(request.headers['authorization'])
|
|
if not request.headers['authorization'].startswith('Bearer '):
|
|
return httpx.Response(401, json={'descricao': 'TOKEN INVALIDO', 'id': 1})
|
|
return httpx.Response(200, json={'frete': [{'vltotal': 12.5, 'prazo': 3}]})
|
|
|
|
lines = []
|
|
status = run(client(handler), ['01310100', '20040002'], ['1'], 10000, lines.append)
|
|
self.assertEqual(status, 0)
|
|
self.assertEqual(headers, ['tok-1', 'Bearer tok-1', 'Bearer tok-1'])
|
|
self.assertIn('Bearer', lines[0])
|
|
self.assertIn('R$ 12,50', lines[1])
|
|
|
|
def test_first_failure_stops_the_run(self):
|
|
calls = []
|
|
|
|
def handler(request):
|
|
calls.append(request)
|
|
return httpx.Response(200, json={'frete': [{}], 'error': {'id': -1, 'descricao': 'CNPJ invalido'}})
|
|
|
|
lines = []
|
|
self.assertEqual(run(client(handler), ['01310100', '20040002'], ['1', '2'], 10000, lines.append), 1)
|
|
self.assertEqual(len(calls), 1)
|
|
self.assertIn('CNPJ invalido', lines[0])
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|