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, '404 Not Found', {} ], '/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();