Skip to content

Commit

Permalink
Handle freight calculation errors and improve test coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
osantana committed Apr 25, 2017
1 parent d51d6b2 commit 0ec90de
Show file tree
Hide file tree
Showing 10 changed files with 210 additions and 39 deletions.
43 changes: 29 additions & 14 deletions correios/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@
from correios import xml_utils, DATADIR
from correios.exceptions import PostingListSerializerError, TrackingCodesLimitExceededError
from correios.models.data import EXTRA_SERVICE_MP, EXTRA_SERVICE_AR
from correios.utils import to_decimal
from correios.utils import to_decimal, to_integer
from .models.address import ZipAddress, ZipCode
from .models.posting import (NotFoundTrackingEvent, TrackingCode, PostingList, ShippingLabel,
TrackingEvent, EventStatus, Package, Freight)
TrackingEvent, EventStatus, Package, Freight, FreightError)
from .models.user import User, FederalTaxNumber, StateTaxNumber, Contract, PostingCard, Service, ExtraService
from .soap import SoapClient

Expand Down Expand Up @@ -157,18 +157,33 @@ def load_tracking_events(self, tracking_codes: Dict[str, TrackingCode], response

def build_freights_list(self, response):
result = []
for service in response.Servicos.cServico:
value = to_decimal(service.Valor)
delivery_time = int(service.PrazoEntrega)
declared_value = to_decimal(service.ValorValorDeclarado)
freight = Freight(
service=service.Codigo,
total=value,
delivery_time=delivery_time,
declared_value=declared_value,
saturday=service.EntregaSabado.upper() == "S",
home=service.EntregaDomiciliar.upper() == "S",
)
for service_data in response.Servicos.cServico:
service = Service.get(service_data.Codigo)
error_code = to_integer(service_data.Erro)
if error_code:
freight = FreightError(
service=service,
error_code=error_code,
error_message=service_data.MsgErro,
)
else:
delivery_time = int(service_data.PrazoEntrega)
value = to_decimal(service_data.ValorSemAdicionais)
declared_value = to_decimal(service_data.ValorValorDeclarado)
ar_value = to_decimal(service_data.ValorAvisoRecebimento)
mp_value = to_decimal(service_data.ValorMaoPropria)
saturday = service_data.EntregaSabado or ""
home = service_data.EntregaDomiciliar or ""
freight = Freight(
service=service,
delivery_time=delivery_time,
value=value,
declared_value=declared_value,
ar_value=ar_value,
mp_value=mp_value,
saturday=saturday.upper() == "S",
home=home.upper() == "S",
)
result.append(freight)
return result

Expand Down
7 changes: 0 additions & 7 deletions correios/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,10 +131,3 @@ class MaximumDeclaredValueError(InvalidDeclaredValueError):

class MinimumDeclaredValueError(InvalidDeclaredValueError):
pass


class FreightCalculationError(ClientError):
def __init__(self, message: str, code: int, *args) -> None:
super().__init__(*args)
self.code = code
self.message = message
47 changes: 39 additions & 8 deletions correios/models/posting.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,17 @@
# limitations under the License.


import os
import math
from datetime import datetime, timedelta
from decimal import Decimal
from typing import Optional, Sequence, Tuple, Union, List, Dict # noqa: F401
from typing import Optional, Tuple, Union, List, Dict # noqa: F401

import os
from PIL import Image

from correios import DATADIR
from correios import exceptions
from correios.utils import to_decimal
from .address import Address, ZipCode
from .data import SERVICE_PAC, TRACKING_EVENT_TYPES, TRACKING_STATUS
from .user import Contract # noqa: F401
Expand Down Expand Up @@ -649,25 +650,55 @@ def closed(self):
class Freight:
def __init__(self,
service: Union[Service, int],
total: Union[Decimal, float, int, str],
delivery_time: Union[int, timedelta],
value: Union[Decimal, float, int, str],
declared_value: Union[Decimal, float, int, str] = 0.00,
mp_value: Union[Decimal, float, int, str] = 0.00,
ar_value: Union[Decimal, float, int, str] = 0.00,
saturday: bool = False,
home: bool = False) -> None:

self.service = Service.get(service)

if not isinstance(total, Decimal):
total = Decimal(total).quantize(MONEY_QUANTIZATION)
self.total = total

if not isinstance(delivery_time, timedelta):
delivery_time = timedelta(days=delivery_time)
self.delivery_time = delivery_time

if not isinstance(value, Decimal):
value = to_decimal(value)
self.value = value

if not isinstance(declared_value, Decimal):
declared_value = Decimal(declared_value).quantize(MONEY_QUANTIZATION)
declared_value = to_decimal(declared_value)
self.declared_value = declared_value

if not isinstance(mp_value, Decimal):
mp_value = to_decimal(mp_value)
self.mp_value = mp_value

if not isinstance(ar_value, Decimal):
ar_value = to_decimal(ar_value)
self.ar_value = ar_value

self.saturday = saturday
self.home = home
self.error_code = 0
self.error_message = ""

@property
def total(self):
return self.value + self.declared_value + self.ar_value + self.mp_value


class FreightError(Freight):
def __init__(self,
service: Union[Service, int],
error_code: Union[str, int],
error_message: str) -> None:
super().__init__(
service=service,
value=Decimal("0.00"),
delivery_time=timedelta(days=0),
)
self.error_code = int(error_code)
self.error_message = error_message
1 change: 1 addition & 0 deletions correios/models/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ def __repr__(self):
return "<Service code={!r}, name={!r}>".format(self.code, self.display_name)

def __eq__(self, other):
other = Service.get(other)
return (self.id, self.code) == (other.id, other.code)

def validate_declared_value(self, value: Union[Decimal, float]) -> bool:
Expand Down
10 changes: 4 additions & 6 deletions correios/utils.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import re
from decimal import Decimal
from datetime import datetime
from decimal import Decimal
from itertools import chain
from typing import Container, Iterable, Sized, Union

import re


def capitalize_phrase(phrase: str) -> str:
return ' '.join(word.capitalize() for word in phrase.split(' '))
Expand Down Expand Up @@ -57,10 +58,7 @@ def __len__(self):


def to_integer(number: Union[int, str]) -> int:
try:
return int(str(number).strip())
except AttributeError:
return int(number)
return int(str(number).strip())


def to_datetime(date: Union[datetime, str], fmt="%Y-%m-%d %H:%M:%S%z") -> datetime:
Expand Down
33 changes: 33 additions & 0 deletions tests/fixtures/cassettes/test_calculate_freight_with_error.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
interactions:
- request:
body: <?xml version="1.0" encoding="UTF-8"?><SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ns0="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ns1="http://tempuri.org/"><SOAP-ENV:Header/><ns0:Body><ns1:CalcPrecoPrazo><ns1:nCdEmpresa>08082650</ns1:nCdEmpresa><ns1:sDsSenha>n5f9t8</ns1:sDsSenha><ns1:nCdServico>40096</ns1:nCdServico><ns1:sCepOrigem>99999000</ns1:sCepOrigem><ns1:sCepDestino>99999999</ns1:sCepDestino><ns1:nVlPeso>80.0</ns1:nVlPeso><ns1:nCdFormato>2</ns1:nCdFormato><ns1:nVlComprimento>30</ns1:nVlComprimento><ns1:nVlAltura>28</ns1:nVlAltura><ns1:nVlLargura>20</ns1:nVlLargura><ns1:nVlDiametro>16</ns1:nVlDiametro><ns1:sCdMaoPropria>N</ns1:sCdMaoPropria><ns1:nVlValorDeclarado>0.0</ns1:nVlValorDeclarado><ns1:sCdAvisoRecebimento>N</ns1:sCdAvisoRecebimento></ns1:CalcPrecoPrazo></ns0:Body></SOAP-ENV:Envelope>
headers:
Accept: ['*/*']
Accept-Encoding: ['gzip, deflate']
Connection: [keep-alive]
Content-Length: ['909']
Content-Type: [text/xml;charset=UTF-8]
SOAPAction:
- !!binary |
Imh0dHA6Ly90ZW1wdXJpLm9yZy9DYWxjUHJlY29QcmF6byI=
User-Agent: [python-requests/2.13.0]
method: POST
uri: http://ws.correios.com.br/calculador/CalcPrecoPrazo.asmx
response:
body: {string: '<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><CalcPrecoPrazoResponse
xmlns="http://tempuri.org/"><CalcPrecoPrazoResult><Servicos><cServico><Codigo>40096</Codigo><Valor>0,00</Valor><PrazoEntrega>0</PrazoEntrega><ValorMaoPropria>0,00</ValorMaoPropria><ValorAvisoRecebimento>0,00</ValorAvisoRecebimento><ValorValorDeclarado>0,00</ValorValorDeclarado><EntregaDomiciliar
/><EntregaSabado /><Erro>-4</Erro><MsgErro>Peso excedido.</MsgErro><ValorSemAdicionais>0,00</ValorSemAdicionais><obsFim
/></cServico></Servicos></CalcPrecoPrazoResult></CalcPrecoPrazoResponse></soap:Body></soap:Envelope>'}
headers:
Cache-Control: ['private, max-age=0']
Content-Length: ['759']
Content-Type: [text/xml; charset=utf-8]
Date: ['Tue, 25 Apr 2017 18:02:42 GMT']
Server: [Microsoft-IIS/7.5]
X-AspNet-Version: [4.0.30319]
X-Powered-By: [ASP.NET]
status: {code: 200, message: OK}
version: 1
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
interactions:
- request:
body: <?xml version="1.0" encoding="UTF-8"?><SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ns0="http://tempuri.org/"
xmlns:ns1="http://schemas.xmlsoap.org/soap/envelope/"><SOAP-ENV:Header/><ns1:Body><ns0:CalcPrecoPrazo><ns0:nCdEmpresa>08082650</ns0:nCdEmpresa><ns0:sDsSenha>n5f9t8</ns0:sDsSenha><ns0:nCdServico>40096</ns0:nCdServico><ns0:sCepOrigem>07192100</ns0:sCepOrigem><ns0:sCepDestino>80030001</ns0:sCepDestino><ns0:nVlPeso>0.0</ns0:nVlPeso><ns0:nCdFormato>2</ns0:nCdFormato><ns0:nVlComprimento>18</ns0:nVlComprimento><ns0:nVlAltura>23</ns0:nVlAltura><ns0:nVlLargura>19</ns0:nVlLargura><ns0:nVlDiametro>16</ns0:nVlDiametro><ns0:sCdMaoPropria>S</ns0:sCdMaoPropria><ns0:nVlValorDeclarado>9000.00</ns0:nVlValorDeclarado><ns0:sCdAvisoRecebimento>S</ns0:sCdAvisoRecebimento></ns0:CalcPrecoPrazo></ns1:Body></SOAP-ENV:Envelope>
headers:
Accept: ['*/*']
Accept-Encoding: ['gzip, deflate']
Connection: [keep-alive]
Content-Length: ['912']
Content-Type: [text/xml;charset=UTF-8]
SOAPAction:
- !!binary |
Imh0dHA6Ly90ZW1wdXJpLm9yZy9DYWxjUHJlY29QcmF6byI=
User-Agent: [python-requests/2.13.0]
method: POST
uri: http://ws.correios.com.br/calculador/CalcPrecoPrazo.asmx
response:
body: {string: '<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><CalcPrecoPrazoResponse
xmlns="http://tempuri.org/"><CalcPrecoPrazoResult><Servicos><cServico><Codigo>40096</Codigo><Valor>96,03</Valor><PrazoEntrega>1</PrazoEntrega><ValorMaoPropria>5,50</ValorMaoPropria><ValorAvisoRecebimento>4,30</ValorAvisoRecebimento><ValorValorDeclarado>62,48</ValorValorDeclarado><EntregaDomiciliar>S</EntregaDomiciliar><EntregaSabado>S</EntregaSabado><Erro>0</Erro><MsgErro
/><ValorSemAdicionais>23,75</ValorSemAdicionais><obsFim /></cServico></Servicos></CalcPrecoPrazoResult></CalcPrecoPrazoResponse></soap:Body></soap:Envelope>'}
headers:
Cache-Control: ['private, max-age=0']
Content-Length: ['773']
Content-Type: [text/xml; charset=utf-8]
Date: ['Tue, 25 Apr 2017 19:17:27 GMT']
Server: [Microsoft-IIS/7.5]
X-AspNet-Version: [4.0.30319]
X-Powered-By: [ASP.NET]
status: {code: 200, message: OK}
version: 1
57 changes: 55 additions & 2 deletions tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@


import pytest
from decimal import Decimal

from correios.exceptions import PostingListSerializerError, TrackingCodesLimitExceededError
from correios.models.address import ZipCode
from correios.models.data import SERVICE_SEDEX10, SERVICE_SEDEX, EXTRA_SERVICE_VD, SERVICE_PAC
from correios.models.data import SERVICE_SEDEX10, SERVICE_SEDEX, EXTRA_SERVICE_VD, SERVICE_PAC, EXTRA_SERVICE_AR, \
EXTRA_SERVICE_MP
from correios.models.posting import (NotFoundTrackingEvent, PostingList, ShippingLabel,
TrackingCode)
TrackingCode, Package)
from correios.models.user import PostingCard, Service, ExtraService
from .vcr import vcr

Expand Down Expand Up @@ -249,3 +251,54 @@ def test_limit_size_city_name(posting_list, shipping_label):
def test_calculate_freights(client, posting_card, package):
freights = client.calculate_freights(posting_card, [SERVICE_SEDEX, SERVICE_PAC], "07192100", "80030001", package)
assert len(freights) == 2

freight = freights[0]
assert freight.error_code == 0
assert freight.error_message == ""
assert freight.service == SERVICE_SEDEX
assert freight.delivery_time.days == 1
assert freight.total == Decimal("23.75")
assert freight.saturday is True
assert freight.home is True

freight = freights[1]
assert freight.error_code == 0
assert freight.error_message == ""
assert freight.service == SERVICE_PAC
assert freight.delivery_time.days == 6
assert freight.total == Decimal("14.10")
assert freight.saturday is False
assert freight.home is True


@pytest.mark.skipif(not correios, reason="API Client support disabled")
@vcr.use_cassette
def test_calculate_freights_with_extra_services(client, posting_card, package):
freights = client.calculate_freights(
posting_card=posting_card,
services=[SERVICE_SEDEX],
from_zip="07192100",
to_zip="80030001",
package=package,
value="9000.00",
extra_services=[EXTRA_SERVICE_AR, EXTRA_SERVICE_MP]
)
assert len(freights) == 1

freight = freights[0]
assert freight.service == SERVICE_SEDEX
assert freight.total == Decimal("96.03")
assert freight.value == Decimal("23.75")
assert freight.declared_value == Decimal("62.48")
assert freight.mp_value == Decimal("5.50")
assert freight.ar_value == Decimal("4.30")


@pytest.mark.skipif(not correios, reason="API Client support disabled")
@vcr.use_cassette
def test_calculate_freight_with_error(client, posting_card, package: Package):
package.real_weight = 80000 # invalid weight (80kg)
freights = client.calculate_freights(posting_card, [SERVICE_SEDEX], "99999000", "99999999", package)
assert len(freights) == 1
assert freights[0].error_code == -4
assert freights[0].error_message == "Peso excedido."
8 changes: 7 additions & 1 deletion tests/test_posting_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -521,9 +521,15 @@ def test_invalid_event_status(event_type):


def test_basic_freight():
freight = posting.Freight(SERVICE_SEDEX, Decimal("10.00"), timedelta(days=5))
freight = posting.Freight(SERVICE_SEDEX, timedelta(days=5), Decimal("10.00"))
assert freight.total == Decimal("10.00")
assert freight.delivery_time == timedelta(days=5)
assert freight.declared_value == Decimal("0.00")
assert freight.saturday is False
assert freight.home is False


def test_basic_freight_conversion():
freight = posting.Freight(SERVICE_SEDEX, 5, 10.00)
assert freight.delivery_time == timedelta(days=5)
assert freight.total == Decimal("10.00")
11 changes: 10 additions & 1 deletion tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import pytest

from correios.utils import capitalize_phrase, RangeSet, rreplace, to_decimal
from correios.utils import capitalize_phrase, RangeSet, rreplace, to_decimal, to_integer

phrase = 'FOo bAr BAZ qux'

Expand Down Expand Up @@ -87,3 +87,12 @@ def test_to_decimal(s, d):
))
def test_to_decimal_precision(v, p, r):
assert to_decimal(v, p) == r


@pytest.mark.parametrize('v, r', (
(3, 3),
("3", 3),
(" \t3 \n", 3),
))
def test_to_integer(v, r):
assert to_integer(v) == r

0 comments on commit 0ec90de

Please sign in to comment.