diff options
Diffstat (limited to 't/09-faixa.t')
| -rw-r--r-- | t/09-faixa.t | 178 |
1 files changed, 178 insertions, 0 deletions
diff --git a/t/09-faixa.t b/t/09-faixa.t new file mode 100644 index 0000000..ea3f76a --- /dev/null +++ b/t/09-faixa.t @@ -0,0 +1,178 @@ +use strict; +use warnings; +use utf8; + +use FindBin (); +use lib "$FindBin::Bin/lib"; + +use Test::More; +use TestServer qw(json new_client); + +use Cepis; + +sub areal { + return { + localidade => 'Areal', + uf => 'RJ', + estado => 'Rio de Janeiro', + regiao => 'Sudeste', + ibge => '3300225', + cep_ini => '25845001', + cep_fim => '25849899', + ceps => 118, + }; +} + +sub not_found { + my (%extra) = @_; + return json( + { erro => 'cep_nao_encontrado', + mensagem => 'CEP não existe na base do DNE', + %extra, + } + ); +} + +# The 404 that knows where the CEP would have been. +{ + my $server = TestServer->new( + routes => { '/25845000' => [ 404, not_found( faixa => areal() ), {} ] } ); + my $client = new_client($server); + + my $ok = eval { $client->lookup('25845000'); 1 }; + my $err = $@; + + ok( !$ok, 'a CEP with a faixa still throws' ); + isa_ok( $err, 'Cepis::Error::CEPNotFound' ); + + my $faixa = $err->faixa; + isa_ok( $faixa, 'Cepis::Faixa' ); + is( $faixa->localidade, 'Areal', 'localidade' ); + is( $faixa->uf, 'RJ', 'uf' ); + is( $faixa->estado, 'Rio de Janeiro', 'estado' ); + is( $faixa->regiao, 'Sudeste', 'regiao' ); + is( $faixa->ibge, '3300225', 'ibge' ); + is( $faixa->cep_ini, '25845001', 'cep_ini' ); + is( $faixa->cep_fim, '25849899', 'cep_fim' ); + is( $faixa->ceps, 118, 'ceps counts what actually exists' ); + is( $faixa->label, 'Areal/RJ', 'label is ready to print' ); + + like( "$err", qr/Areal/, 'the message names the municipality' ); + like( "$err", qr/25845000/, 'and still names the CEP' ); + is( $server->count('/25845000'), 1, 'a 404 with a faixa is not retried' ); + + is_deeply( $faixa->to_hash, areal(), 'to_hash gives the eight fields back' ); + + # The DNE's own start for Areal is 25845000 — the CEP that just failed. What + # comes back is the smallest CEP that exists, which is a different number. + isnt( $faixa->cep_ini, '25845000', + 'cep_ini is not the published start of the band' ); + + # 118 CEPs across ~4,900 numbers: the range is sparse, and ceps is what says + # so. Nothing here may be read as a list of valid CEPs. + cmp_ok( $faixa->ceps, '<', $faixa->cep_fim - $faixa->cep_ini, + 'the range is far sparser than its span' ); + + $server->stop; +} + +# The 404 that does not: a band no municipality owns. The API omits the field. +{ + my $server + = TestServer->new( routes => { '/99999999' => [ 404, not_found(), {} ] } ); + my $client = new_client($server); + + my $ok = eval { $client->lookup('99999999'); 1 }; + my $err = $@; + + ok( !$ok, 'a CEP with no faixa throws the same error' ); + isa_ok( $err, 'Cepis::Error::CEPNotFound' ); + is( $err->faixa, undef, 'and faixa is undef, because the API omitted it' ); + like( "$err", qr/99999999/, 'the message names the CEP' ); + unlike( "$err", qr/would fall in/, 'and promises nothing about where it was' ); + + $server->stop; +} + +# A faixa that cannot be trusted must never cost the caller its clean 404. +{ + my @broken = ( + [ 'null' => undef ], + [ 'an empty hash' => {} ], + [ 'a scalar' => 'Areal' ], + [ 'an array' => [ 'Areal', 'RJ' ] ], + [ 'no localidade' => { %{ areal() }, localidade => '' } ], + [ 'no ceps' => { map { $_ => areal()->{$_} } qw(localidade uf cep_ini cep_fim) } ], + [ 'a non-numeric ceps' => { %{ areal() }, ceps => 'many' } ], + [ 'a malformed cep_ini' => { %{ areal() }, cep_ini => '2584500' } ], + [ 'a masked cep_fim' => { %{ areal() }, cep_fim => '25849-899' } ], + [ 'a nested localidade' => { %{ areal() }, localidade => { name => 'Areal' } } ], + ); + + for my $case (@broken) { + my ( $label, $faixa ) = @$case; + + my $server = TestServer->new( + routes => { '/25845000' => [ 404, not_found( faixa => $faixa ), {} ] } ); + my $client = new_client($server); + + my $ok = eval { $client->lookup('25845000'); 1 }; + my $err = $@; + + ok( !$ok, "a faixa that is $label still throws" ); + isa_ok( $err, 'Cepis::Error::CEPNotFound' ); + is( $err->faixa, undef, "a faixa that is $label is dropped" ); + + $server->stop; + } +} + +# A 404 body that is not JSON at all — a proxy, an HTML page — is still a 404. +{ + my $server = TestServer->new( + routes => { + '/25845000' => [ 404, '<html>404 Not Found</html>', {} ], + '/25845002' => [ 404, '', {} ], + '/25845003' => [ 404, '[1,2,3]', {} ], + } + ); + my $client = new_client($server); + + for my $cep (qw(25845000 25845002 25845003)) { + my $ok = eval { $client->lookup($cep); 1 }; + ok( !$ok, "an unparseable 404 body still throws for $cep" ); + isa_ok( $@, 'Cepis::Error::CEPNotFound' ); + is( $@->faixa, undef, 'with no faixa' ); + } + + $server->stop; +} + +# from_json on its own, without a request in the way. +{ + isa_ok( Cepis::Faixa->from_json( areal() ), 'Cepis::Faixa' ); + is( Cepis::Faixa->from_json(undef), undef, 'from_json(undef) is undef' ); + is( Cepis::Faixa->from_json( [] ), undef, 'from_json on an array is undef' ); + is( Cepis::Faixa->from_json('Areal'), undef, 'from_json on a string is undef' ); + + my $partial = Cepis::Faixa->from_json( + { localidade => 'Areal', cep_ini => '25845001', cep_fim => '25849899', ceps => 118 } ); + isa_ok( $partial, 'Cepis::Faixa' ); + is( $partial->uf, '', 'a missing uf is an empty string, as elsewhere' ); + is( $partial->estado, '', 'and so is a missing estado' ); + is( $partial->label, 'Areal', 'label falls back to the municipality alone' ); + + my $numeric = Cepis::Faixa->from_json( { %{ areal() }, ceps => '118' } ); + is( $numeric->ceps, 118, 'ceps comes back as a number' ); +} + +# An unknown CEP with a faixa is still undef in a batch: the CSV path is +# untouched by any of this. +{ + my $address = Cepis::Address->from_json( { %{ areal() }, cep => '25845000' } ); + is( $address->localidade, 'Areal', 'Cepis::Address is unaffected' ); + is( $address->geo, undef, 'and grows no faixa of its own' ); + is( $address->field('cep_ini'), '25845001', 'stray fields land in raw as ever' ); +} + +done_testing(); |