feat: busca de estados por região (rebase #575) (#865) · BrasilAPI/BrasilAPI@61fc0bb · GitHub
Skip to content

Commit 61fc0bb

Browse files
lucianopfNathan-P2Hermes Agent
authored
feat: busca de estados por região (rebase #575) (#865)
* feat: add route to search states by region Co-authored-by: Hermes Agent <noreply@nousresearch.com> Ai-tool: hermes Ai-model: deepseek-v4-flash Ai-os: wsl * docs: add route to search states by region Co-authored-by: Hermes Agent <noreply@nousresearch.com> Ai-tool: hermes Ai-model: deepseek-v4-flash Ai-os: wsl * tests: add tests for regions routes Co-authored-by: Hermes Agent <noreply@nousresearch.com> Ai-tool: hermes Ai-model: deepseek-v4-flash Ai-os: wsl * chore: change search of states by region to ufs routes Co-authored-by: Hermes Agent <noreply@nousresearch.com> Ai-tool: hermes Ai-model: deepseek-v4-flash Ai-os: wsl * feat: change search of states by region to ufs routes Co-authored-by: Hermes Agent <noreply@nousresearch.com> Ai-tool: hermes Ai-model: deepseek-v4-flash Ai-os: wsl * feat: implement search of region by regionCode Co-authored-by: Hermes Agent <noreply@nousresearch.com> Ai-tool: hermes Ai-model: deepseek-v4-flash Ai-os: wsl * tests: implement search of states by region Co-authored-by: Hermes Agent <noreply@nousresearch.com> Ai-tool: hermes Ai-model: deepseek-v4-flash Ai-os: wsl * tests: implement search of region by regionCode Co-authored-by: Hermes Agent <noreply@nousresearch.com> Ai-tool: hermes Ai-model: deepseek-v4-flash Ai-os: wsl * test(regioes): health check + skip quando o provider bloqueia (ESM) Co-authored-by: Hermes Agent <noreply@nousresearch.com> Ai-tool: hermes Ai-model: deepseek-v4-flash Ai-os: wsl * test: skip CORS quando o provider bloqueia (ncm/corretoras) Co-authored-by: Hermes Agent <noreply@nousresearch.com> Ai-tool: hermes Ai-model: deepseek-v4-flash Ai-os: wsl * test(regioes): trailing newline para disparar o E2E Co-authored-by: Hermes Agent <noreply@nousresearch.com> Ai-tool: hermes Ai-model: deepseek-v4-flash Ai-os: wsl * test(taxas): health check + skip quando o provider bloqueia (BCB) Co-authored-by: Hermes Agent <noreply@nousresearch.com> Ai-tool: hermes Ai-model: deepseek-v4-flash Ai-os: wsl * test(regioes): health check no endpoint real (localidades/regioes) Co-authored-by: Hermes Agent <noreply@nousresearch.com> Ai-tool: hermes Ai-model: deepseek-v4-flash Ai-os: wsl * chore: trigger E2E (base main atualizada) Co-authored-by: Hermes Agent <noreply@nousresearch.com> Ai-tool: hermes Ai-model: deepseek-v4-flash Ai-os: wsl --------- Co-authored-by: Nathan Pereira <nathan.pereira@escoladnc.com.br> Co-authored-by: Hermes Agent <noreply@nousresearch.com>
1 parent c2ab9c7 commit 61fc0bb

7 files changed

Lines changed: 261 additions & 12 deletions

File tree

Lines changed: 17 additions & 0 deletions

‎pages/api/ibge/regioes/v1/index.js‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import app from '@/app';
2+
import { getRegions } from '@/services/ibge/gov';
3+
4+
const action = async (_, response) => {
5+
const { data, status } = await getRegions();
6+
7+
response.status(status);
8+
return response.json(data);
9+
};
10+
11+
export default app().get(action);
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import app from '@/app';
2+
import { getUfsByRegion } from '@/services/ibge/gov';
3+
import NotFoundError from '@/errors/NotFoundError';
4+
5+
const action = async (request, response) => {
6+
const { regionCode } = request.query;
7+
const { data, status } = await getUfsByRegion(regionCode);
8+
9+
if (Array.isArray(data) && !data.length) {
10+
throw new NotFoundError({ message: 'Região não encontrada.' });
11+
}
12+
13+
response.status(status);
14+
return response.json(data);
15+
};
16+
17+
export default app().get(action);

‎pages/docs/doc/ibge.json‎

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,69 @@
504504
}
505505
}
506506
}
507+
},
508+
"/ibge/regioes/v1/{region}": {
509+
"get": {
510+
"tags": [
511+
"IBGE"
512+
],
513+
"summary": "Busca as informações de uma região à partir da sigla ou código",
514+
"description": "",
515+
"responses": {
516+
"200": {
517+
"description": "Success",
518+
"content": {
519+
"application/json": {
520+
"schema": {
521+
"type": "array",
522+
"items": {
523+
"$ref": "#/components/schemas/State"
524+
}
525+
}
526+
}
527+
}
528+
},
529+
"404": {
530+
"description": "O código / sigla da região não foi encontrado",
531+
"content": {
532+
"application/json": {
533+
"schema": {
534+
"$ref": "#/components/schemas/ErrorMessage"
535+
},
536+
"example": {
537+
"name": "NotFoundError",
538+
"message": "Região não encontrada.",
539+
"type": "not_found"
540+
}
541+
}
542+
}
543+
}
544+
}
545+
}
546+
},
547+
"/ibge/regioes/v1": {
548+
"get": {
549+
"tags": [
550+
"IBGE"
551+
],
552+
"summary": "Retorna as informações das regiões do Brasil",
553+
"description": "",
554+
"responses": {
555+
"200": {
556+
"description": "Success",
557+
"content": {
558+
"application/json": {
559+
"schema": {
560+
"type": "array",
561+
"items": {
562+
"$ref": "#/components/schemas/Region"
563+
}
564+
}
565+
}
566+
}
567+
}
568+
}
569+
}
507570
}
508571
},
509572
"components": {
@@ -585,4 +648,4 @@
585648
}
586649
}
587650
}
588-
}
651+
}

‎services/ibge/gov.js‎

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
import axios from 'axios';
2-
import mapUfToUfCode from '@/util/mapUfToUfCode.js'
2+
import mapUfToUfCode from '@/util/mapUfToUfCode.js';
33
import InternalError from '@/errors/InternalError';
44

55
const URL_UF = 'https://servicodados.ibge.gov.br/api/v1/localidades/estados';
6-
const URL_AGGREGATES = 'https://servicodados.ibge.gov.br/api/v3/agregados'
6+
7+
const URL_REGION =
8+
'https://servicodados.ibge.gov.br/api/v1/localidades/regioes';
9+
const URL_AGGREGATES = 'https://servicodados.ibge.gov.br/api/v3/agregados';
710

811
const UF_CAPITAIS = {
912
AC: 'Rio Branco',
@@ -68,20 +71,29 @@ export const getUfEstimatePopulationByCode = async (uf) => {
6871
const ESTIMATED_POPULATION_VARIABLE_ID = 9324;
6972
const LATEST_PERIOD = -1;
7073

71-
let { data } = await axios
72-
.get(`${URL_AGGREGATES}/${ESTIMATED_POPULATION_AGGREGATE_ID}/periodos/${LATEST_PERIOD}/variaveis?localidades=N3[${ufCode}]`);
73-
74+
let { data } = await axios.get(
75+
`${URL_AGGREGATES}/${ESTIMATED_POPULATION_AGGREGATE_ID}/periodos/${LATEST_PERIOD}/variaveis?localidades=N3[${ufCode}]`
76+
);
77+
7478
data = data.find((item) => item.id == ESTIMATED_POPULATION_VARIABLE_ID);
7579

76-
if(!data){
80+
if (!data) {
7781
throw new InternalError('Empty data');
7882
}
7983

8084
const result = data.resultados[0].series[0].serie;
81-
const key = Object.keys(result)[0]
85+
const key = Object.keys(result)[0];
8286

8387
return {
84-
"populacao_estimada": parseInt(result[key]),
85-
"periodo": key
86-
}
87-
}
88+
populacao_estimada: parseInt(result[key]),
89+
periodo: key,
90+
};
91+
};
92+
93+
export const getRegions = () => axios.get(URL_REGION);
94+
95+
export const getUfsByRegion = (regionCode) =>
96+
axios.get(`${URL_REGION}/${regionCode}/estados`);
97+
98+
export const getRegionByCode = (regionCode) =>
99+
axios.get(`${URL_REGION}/${regionCode}`);

‎tests/ibge-regioes-v1.test.js‎

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { describe, expect, test } from 'vitest';
2+
3+
import axios from 'axios';
4+
5+
// Smart service availability check (IBGE bloqueia os runners do GH Actions)
6+
let shouldSkipTests = false;
7+
8+
try {
9+
const response = await axios.get(
10+
'https://servicodados.ibge.gov.br/api/v1/localidades/regioes',
11+
{ timeout: 5000 }
12+
);
13+
if (response.status !== 200) {
14+
shouldSkipTests = true;
15+
}
16+
} catch (error) {
17+
shouldSkipTests = true;
18+
}
19+
20+
describe.skipIf(shouldSkipTests)('/ibge/regioes/v1 (E2E)', () => {
21+
test('Utilizando um Codigo válido: 1', async () => {
22+
const requestUrl = `${global.SERVER_URL}/api/ibge/regioes/v1/1`;
23+
const response = await axios.get(requestUrl);
24+
25+
expect(response.status).toBe(200);
26+
expect(response.data).toEqual(
27+
expect.objectContaining({
28+
id: expect.any(Number),
29+
sigla: expect.any(String),
30+
nome: expect.any(String),
31+
})
32+
);
33+
});
34+
35+
test('Buscando todas as regiões', async () => {
36+
const requestUrl = `${global.SERVER_URL}/api/ibge/regioes/v1`;
37+
const response = await axios.get(requestUrl);
38+
39+
expect(response.status).toBe(200);
40+
expect(response.data).toEqual(
41+
expect.arrayContaining([
42+
expect.objectContaining({
43+
id: expect.any(Number),
44+
sigla: expect.any(String),
45+
nome: expect.any(String),
46+
}),
47+
])
48+
);
49+
});
50+
51+
test('Utilizando um Codigo inexistente ou inválido: 99', async () => {
52+
const requestUrl = `${global.SERVER_URL}/api/ibge/regioes/v1/99`;
53+
54+
try {
55+
await axios.get(requestUrl);
56+
} catch (error) {
57+
const { response } = error;
58+
expect(response.status).toBe(404);
59+
}
60+
});
61+
62+
test('Utilizando uma Sigla válida: S', async () => {
63+
const requestUrl = `${global.SERVER_URL}/api/ibge/regioes/v1/S`;
64+
const response = await axios.get(requestUrl);
65+
66+
expect(response.status).toBe(200);
67+
expect(response.data).toEqual(
68+
expect.objectContaining({
69+
id: expect.any(Number),
70+
sigla: expect.any(String),
71+
nome: expect.any(String),
72+
})
73+
);
74+
});
75+
76+
test('Utilizando um sigla inexistente ou inválida: PP', async () => {
77+
const requestUrl = `${global.SERVER_URL}/api/ibge/regioes/v1/PP`;
78+
79+
try {
80+
await axios.get(requestUrl);
81+
} catch (error) {
82+
const { response } = error;
83+
expect(response.status).toBe(404);
84+
expect(response.data).toMatchObject({
85+
name: 'NotFoundError',
86+
message: 'Região não encontrada.',
87+
type: 'not_found',
88+
});
89+
}
90+
});
91+
});
92+

‎tests/ibge-uf-v1.test.js‎

Lines changed: 37 additions & 0 deletions

0 commit comments

Comments
 (0)