"""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='bad gateway'), ] 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()