Files
dtf-system/tests/test_jadlog.py
Cauê Faleiros 26c46ccc1d
All checks were successful
Build and deploy / Validate source (push) Successful in 9s
Build and deploy / Integration suite on a real stack (push) Successful in 3m5s
Build and deploy / Secret scan and release gate (push) Successful in 6s
Build and deploy / Publish images (push) Successful in 1m1s
feat: Jadlog quotes by the larger of the real and the cubed weight
The client's box is 56 x 14 x 14 cm and weighs 0.30 kg; the film is 0.075 kg
per metre. Jadlog charges the larger of the real and the cubed weight, and this
box is large for its weight, so quoting the real weight alone would price
below what Jadlog charges. JADLOG_CAIXA_CM and JADLOG_CUBAGEM_KG_M3 (both or
neither) give the cubed weight; the quote sends the larger. Until the
contract's factor is set, the Kanban's Frete card says so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 16:20:44 -03:00

175 lines
8.2 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
from decimal import Decimal
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_box_cubed_weight_wins_when_larger(self):
seen = []
def handler(request):
seen.append(json.loads(request.content)['frete'][0]['peso'])
return httpx.Response(200, json={'frete': [{'vltotal': 30, 'prazo': 3}]})
# 56 x 14 x 14 cm is 0.010976 m³; at 300 kg/m³ that is 3.293 kg.
freight = self.freight(handler, JADLOG_PESO_BASE_KG='0.3', JADLOG_PESO_POR_METRO_KG='0.075',
JADLOG_CAIXA_CM='56 x 14 x 14', JADLOG_CUBAGEM_KG_M3='300')
self.assertEqual(freight.cubed_kg(), Decimal('3.293'))
quote = freight.quote('jadlog', '01310100', '1', 1490)
self.assertEqual((seen[0], quote['weight_kg'], quote['real_kg']), (3.293, '3.293', '0.375'))
# Past the box's cubed weight, the film's real weight is what counts.
freight.quote('jadlog', '01310100', '60', 1490)
self.assertEqual(seen[1], 4.8)
def test_a_box_needs_its_factor(self):
for env in ({'JADLOG_CAIXA_CM': '56x14x14'}, {'JADLOG_CUBAGEM_KG_M3': '300'},
{'JADLOG_CAIXA_CM': '56x14', 'JADLOG_CUBAGEM_KG_M3': '300'},
{'JADLOG_CAIXA_CM': '56x14x14', 'JADLOG_CUBAGEM_KG_M3': '0'}):
with self.subTest(env=env), self.assertRaises(RuntimeError):
self.freight(lambda request: httpx.Response(200), **env)
# Neither: the real weight only, as before the box was known.
self.assertIsNone(self.freight(lambda request: httpx.Response(200)).cubed_kg())
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()