Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: rota /api/cpf #544

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions pages/api/cpf/v1/[cpf].js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import app from '@/app';

import BaseError from '@/errors/BaseError';
import InternalError from '@/errors/InternalError';
import BadRequestError from '@/errors/BadRequestError';

import { cpfIsValid, getCpfRegionAndUfs, formatCpf } from '@/services/cpf/cpf';

async function regionOfCpf(request, response, next) {
try {
const requestedCpf = request.query.cpf.replace(/[-.]/g, '');
const formatedCpf = formatCpf(requestedCpf);

if (!/^[0-9]+$/.test(requestedCpf)) {
throw new BadRequestError({
message: `CPF ${formatedCpf} inválido.`,
type: 'BadRequestError',
name: 'bad_request',
});
}

if (requestedCpf.length !== 11) {
throw new BadRequestError({
message: `CPF ${formatedCpf} inválido.`,
type: 'bad_request',
name: 'BadRequestError',
});
}

if (!cpfIsValid(requestedCpf)) {
response.status(200);
return response.json({
isValid: false,
});
}

const cpfRegionAndUfs = getCpfRegionAndUfs(requestedCpf);

response.status(200);
return response.json({
cpf: formatedCpf,
isValid: true,
rf: cpfRegionAndUfs.regionNumber,
ufs: cpfRegionAndUfs.ufs,
});
} catch (error) {
if (error instanceof BaseError) {
console.log(error);
return next(error);
}

console.log(error);
throw new InternalError({
message: 'Todos os serviços de CPF retornaram erro.',
type: 'service_error',
});
}
}

export default app().get(regionOfCpf);
86 changes: 86 additions & 0 deletions pages/docs/doc/cpf.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
{
"tags": [
{
"name": "CPF",
"description": "Informações referentes a CPFs"
}
],

"paths": {
"/cpf/v1/{cpf}": {
"get": {
"tags": ["CPF"],
"summary": "Verifica o CPF e caso válido retorna sua região",
"description": "",
"parameters": [
{
"name": "CPF",
"description": "O CPF (Cadastro de Pessoas Físicas) é um sistema de códigos de registro de contribuintes mantido pela Receita Federal que representa unicamente uma pessoa física no Brasil.\n",
"in": "path",
"required": false,
"schema": {
"type": "string",
"format": "string"
}
}
],
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CPFInfo"
}
}
}
},
"400": {
"description": "CPF em um padrão inválido",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorMessage"
},
"example": {
"name": "BadRequestError",
"message": "CPF 10723555072 inválido.",
"type": "bad_request"
}
}
}
}
}
}
}
},
"components": {
"schemas": {
"CPFInfo": {
"title": "CPF",
"required": ["cpf", "isValid", "rf", "ufs"],
"type": "object",
"properties": {
"cpf": {
"type": "string"
},
"isValid": {
"type": "boolean"
},
"rf": {
"type": "integer"
},
"ufs": {
"type": "object"
}
},
"example": {
"cpf": "10723555079",
"isValid": true,
"rf": 0,
"ufs": ["RS"]
}
}
}
}
}
1 change: 1 addition & 0 deletions pages/sitemap.xml/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const getDocs = () => {
'ISBN',
'CPTEC',
'PIX',
'CPF',
];
};

Expand Down
69 changes: 69 additions & 0 deletions services/cpf/cpf.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
export const formatCpf = (cpf) => {
return cpf.replace(/(\d{3})(\d{3})(\d{3})(\d{2})/, '$1.$2.$3-$4');
};

export const cpfIsValid = (cpf) => {
const arr = cpf.split('');
if (arr.every((digit) => digit === arr[0])) return false;
let add = 0;
let rev = 0;
for (let i = 0; i < 9; i += 1) add += parseInt(cpf.charAt(i), 10) * (10 - i);
rev = 11 - (add % 11);
if (rev === 10 || rev === 11) rev = 0;
if (rev !== parseInt(cpf.charAt(9), 10)) return false;

add = 0;
for (let i = 0; i < 10; i += 1) add += parseInt(cpf.charAt(i), 10) * (11 - i);
rev = 11 - (add % 11);
if (rev === 10 || rev === 11) rev = 0;
if (rev !== parseInt(cpf.charAt(10), 10)) return false;
return true;
};

export const getCpfRegionAndUfs = (requestedCpf) => {
const cpfDigit = Number(requestedCpf[8]);

const regionsAndufs = [
{
regionNumber: 0,
ufs: ['RS'],
},
{
regionNumber: 1,
ufs: ['DF', 'GO', 'MT', 'MS', 'TO'],
},
{
regionNumber: 2,
ufs: ['AC', 'AP', 'AM', 'PA', 'RO', 'RR'],
},
{
regionNumber: 3,
ufs: ['CE', 'MA', 'PI'],
},
{
regionNumber: 4,
ufs: ['AL', 'PB', 'PE', 'RN'],
},
{
regionNumber: 5,
ufs: ['BA', 'SE'],
},
{
regionNumber: 6,
ufs: ['MG'],
},
{
regionNumber: 7,
ufs: ['ES', 'RJ'],
},
{
regionNumber: 8,
ufs: ['SP'],
},
{
regionNumber: 9,
ufs: ['PR', 'SC'],
},
];
return regionsAndufs[cpfDigit];
};
63 changes: 63 additions & 0 deletions tests/cpf-v1.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
const axios = require('axios');

const requestUrl = `${global.SERVER_URL}/api/cpf/v1`;

describe('api/cpf/v1 (E2E)', () => {
test('Utilizando um CPF válido: 54083180013', async () => {
const response = await axios.get(`${requestUrl}/54083180013`);
const { data, status } = response;

expect(status).toEqual(200);
expect(data.cpf).toEqual('540.831.800-13');
expect(data.rf).toEqual(0);
expect(data.ufs).toEqual(['RS']);
expect(data.isValid).toEqual(true);
});

test('Utilizando um CPF válido com formatação: 540.831.800-13', async () => {
try {
await axios.get(`${requestUrl}/540.831.800-13`);
} catch (error) {
const { response } = error;
const { data, status } = response;

expect(status).toEqual(200);
expect(data.cpf).toEqual('540.831.800-13');
expect(data.rf).toEqual(0);
expect(data.ufs).toEqual(['RS']);
expect(data.isValid).toEqual(true);
}
});

test('Utilizando um CPF inválido (valor): 00000000000', async () => {
try {
await axios.get(`${requestUrl}/00000000000`);
} catch (error) {
const { response } = error;
const { data, status } = response;

expect(status).toEqual(400);
expect(data).toEqual({
message: 'CPF 000.000.000-00 inválido.',
name: 'BadRequestError',
type: 'bad_request',
});
}
});

test('Utilizando um CPF inválido (tamanho): 123', async () => {
try {
await axios.get(`${requestUrl}/123`);
} catch (error) {
const { response } = error;
const { data, status } = response;

expect(status).toEqual(400);
expect(data).toEqual({
message: 'CPF 123 inválido.',
name: 'BadRequestError',
type: 'bad_request',
});
}
});
});
Loading