summaryrefslogtreecommitdiffstats
path: root/t/09-faixa.t
blob: ea3f76a9da0a05440e426d53a6c77125c8294af6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
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();