Popular MySQL com dados brasileiros falsos no staging
Popular MySQL com dados brasileiros falsos no staging
Usar dados reais de clientes no banco de staging é uma prática comum — e um passivo jurídico real. Este guia mostra como popular um banco MySQL de staging com CPF, CNPJ, endereços e chaves PIX estruturalmente válidos, sem tocar em nenhum dado de produção, usando a FakeForge API.
Dados reais em staging violam a LGPD
O que diz o Art. 7º, IX da LGPD sobre bases de teste
A Lei 13.709/2018 (LGPD) exige base legal para qualquer tratamento de dado pessoal. O Art. 7º, IX autoriza tratamento para "proteção do crédito" e finalidades legítimas — não para popular bancos de teste. Ao copiar um dump de produção para staging, você está tratando CPF, e-mail e endereço sem base legal, em ambiente sem os controles de segurança do prod.
Riscos concretos: vazamento de PII em ambientes não-produtivos
Ambientes de staging têm superfície de ataque maior: credenciais menos protegidas, acesso liberado para terceiros (QA externo, parceiros, devs freelancers) e logs menos monitorados. Uma credencial de banco de staging comprometida expõe dados reais de clientes com as mesmas consequências legais de um vazamento em produção. A ANPD pode aplicar multas de até 2% do faturamento bruto do grupo, limitado a R$ 50 milhões por infração (LGPD Art. 52).
Por que anonimização parcial não basta
Mascarar apenas CPF e e-mail enquanto mantém nome completo + endereço + histórico de compras cria um conjunto re-identificável. Estudos de re-identificação mostram que três atributos quase-identificadores (CEP de 8 dígitos, data de nascimento, sexo) são suficientes para identificar mais de 87% dos registros em bases populacionais. Anonimização eficaz exige geração sintética desde a origem — não truncamento de dados reais.
O que são dados falsos estruturalmente válidos
Diferença entre dado aleatório e dado válido (dígitos verificadores)
Um CPF gerado como sequência aleatória de 11 dígitos falha em qualquer validador. Sistemas de cadastro, gateways de pagamento e ERPs rejeitam na hora. Dado falso útil para teste é aquele que passa pelas mesmas validações que o dado real — dígitos verificadores corretos, DDD existente, CEP com prefixo válido para o estado informado.
CPF com módulo 11, CNPJ com dois dígitos verificadores
O CPF usa dois dígitos verificadores calculados por módulo 11 sobre os nove primeiros dígitos. O CNPJ aplica o mesmo algoritmo duas vezes com pesos diferentes. Gerar esses números na mão é viável, mas manter a lógica atualizada (a Receita Federal já alterou regras de validação) e garantir ausência de colisões com CPFs reais exige cuidado extra — daí o valor de uma API dedicada.
Endereço correlacionado: CEP, município, UF e DDD coerentes
Um endereço com CEP 01310-100 (Av. Paulista, SP) e DDD 71 (Brasília) é tecnicamente inválido para testes de sistemas que validam coerência geográfica. A FakeForge gera endereços onde CEP, bairro, município, UF e DDD estão correlacionados — útil para testar sistemas de frete, cálculo de ICMS interestadual e cobertura de entrega.
Arquitetura da solução: API + seed script + MySQL
Visão geral do fluxo (FakeForge API → Node.js → MySQL)
FakeForge API (/api/generate)
│
▼
Node.js seed script (TypeScript)
├── fetchFakeData() ← chama a API em lotes
├── mapToRow() ← transforma resposta em objeto plano
└── insertBatch() ← INSERT parametrizado via mysql2
│
▼
MySQL staging (usuarios, empresas, transacoes)Dependências necessárias: mysql2, node-fetch (ou fetch nativo Node 18+)
npm install mysql2
npm install --save-dev tsx dotenvNode 18+ inclui fetch nativo. Se usar Node 16, adicione node-fetch@3. O tsx permite rodar TypeScript diretamente sem compilar para CI.
Estrutura de pastas do projeto de seed
seed/
├── .env # DATABASE_URL e FAKEFORGE_API_KEY
├── schema.sql # CREATE TABLE dos três domínios
├── seed.ts # script principal
└── types.ts # interfaces TypeScript dos payloads da APIConfigurar a FakeForge API para geração em lote
Endpoint /api/generate — parâmetros type, quantity e format
GET https://fakeforge.com.br/api/generate?type=pessoa&quantity=500&format=jsonOs tipos relevantes para este seed: pessoa, cep, empresa, pix, cartao. A documentação completa de parâmetros está em /docs.
Autenticação com API key (header x-api-key)
const response = await fetch(
`https://fakeforge.com.br/api/generate?type=${type}&quantity=${quantity}&format=json`,
{
headers: {
"x-api-key": process.env.FAKEFORGE_API_KEY ?? "",
},
}
);Limites do plano gratuito (100 chamadas/dia) e quando usar o plano Dev
| Plano | Chamadas/dia | Batch máximo | Preço |
|---|---|---|---|
| Grátis | 100 | 100 itens | R$ 0 |
| Dev | ilimitado | 10.000 itens | R$ 29/mês |
| Team | ilimitado | 10.000 itens | R$ 79/mês |
Para um seed de 500 registros por tabela, são ao menos 5 chamadas por tipo. Se o seed rodar diariamente no CI, o plano gratuito esgota rapidamente. O plano Dev cobre a maioria dos pipelines de staging individuais.
Criar o schema MySQL compatível com os campos gerados
Tabela usuarios, empresas e transacoes
CREATE TABLE IF NOT EXISTS usuarios (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
cpf CHAR(11) NOT NULL UNIQUE, -- sem pontuação
nome VARCHAR(120) NOT NULL,
email VARCHAR(200) NOT NULL,
telefone VARCHAR(20),
cep CHAR(8) NOT NULL,
logradouro VARCHAR(200),
bairro VARCHAR(100),
municipio VARCHAR(100),
uf CHAR(2)
);
CREATE TABLE IF NOT EXISTS empresas (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
cnpj CHAR(14) NOT NULL UNIQUE, -- sem pontuação
razao_social VARCHAR(200) NOT NULL,
cep CHAR(8),
municipio VARCHAR(100),
uf CHAR(2),
banco_codigo CHAR(3),
agencia VARCHAR(10),
conta VARCHAR(20)
);
CREATE TABLE IF NOT EXISTS transacoes (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
usuario_id INT UNSIGNED NOT NULL,
chave_pix VARCHAR(100),
valor DECIMAL(12, 2) NOT NULL,
banco_nome VARCHAR(80),
cartao_numero CHAR(16),
cartao_bandeira VARCHAR(20),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (usuario_id) REFERENCES usuarios(id)
);Tipos de coluna adequados
CHAR(11) para CPF e CHAR(14) para CNPJ armazenam sem pontuação — a pontuação é responsabilidade da camada de apresentação. DECIMAL(12,2) evita arredondamentos em valores monetários. Nunca use FLOAT para dinheiro.
Script de seed completo em TypeScript
Tipos e função de fetch
// types.ts
export interface PessoaGerada {
nome: string;
cpf: string; // "12345678901" — sem máscara
email: string;
telefone: string;
}
export interface CepGerado {
cep: string; // "01310100"
logradouro: string;
bairro: string;
municipio: string;
uf: string;
}// seed.ts — fetchFakeData
import "dotenv/config";
async function fetchFakeData<T>(type: string, quantity: number): Promise<T[]> {
const url = new URL("https://fakeforge.com.br/api/generate");
url.searchParams.set("type", type);
url.searchParams.set("quantity", String(quantity));
url.searchParams.set("format", "json");
const res = await fetch(url.toString(), {
headers: { "x-api-key": process.env.FAKEFORGE_API_KEY ?? "" },
});
if (!res.ok) {
throw new Error(`FakeForge API error: ${res.status} ${await res.text()}`);
}
const json = await res.json();
return json.data as T[];
}Script de seed completo com mysql2
import mysql from "mysql2/promise";
import type { PessoaGerada, CepGerado } from "./types";
const BATCH_SIZE = 100;
const TOTAL = 500;
async function seedUsuarios(conn: mysql.Connection) {
for (let offset = 0; offset < TOTAL; offset += BATCH_SIZE) {
const qty = Math.min(BATCH_SIZE, TOTAL - offset);
const [pessoas, enderecos] = await Promise.all([
fetchFakeData<PessoaGerada>("pessoa", qty),
fetchFakeData<CepGerado>("cep", qty),
]);
const rows = pessoas.map((p, i) => [
p.cpf,
p.nome,
p.email,
p.telefone,
enderecos[i]?.cep ?? null,
enderecos[i]?.logradouro ?? null,
enderecos[i]?.bairro ?? null,
enderecos[i]?.municipio ?? null,
enderecos[i]?.uf ?? null,
]);
const results = await Promise.allSettled(
rows.map((row) =>
conn.execute(
`INSERT IGNORE INTO usuarios
(cpf, nome, email, telefone, cep, logradouro, bairro, municipio, uf)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
row
)
)
);
const failed = results.filter((r) => r.status === "rejected").length;
if (failed > 0) console.warn(`Lote ${offset}: ${failed} falhas ignoradas`);
console.log(`Inseridos ${offset + qty} / ${TOTAL} usuários`);
// backoff linear simples entre lotes
await new Promise((r) => setTimeout(r, 200));
}
}
async function main() {
const conn = await mysql.createConnection(process.env.DATABASE_URL ?? "");
try {
await seedUsuarios(conn);
} finally {
await conn.end();
}
}
main().catch((err) => {
console.error(err);
process.exit(1);
});DICA:INSERT IGNOREdescarta silenciosamente duplicatas de CPF, tornando o script idempotente sem precisar de lógica extra. Se quiser atualizar registros existentes em vez de ignorar, troque porON DUPLICATE KEY UPDATE nome = VALUES(nome), email = VALUES(email).
Exportar direto em SQL com CREATE TABLE pronto
Usar format=sql na API
Para volumes pequenos ou onboarding rápido, a FakeForge gera um arquivo .sql completo com CREATE TABLE e todos os INSERTs:
curl -s \
-H "x-api-key: $FAKEFORGE_API_KEY" \
"https://fakeforge.com.br/api/generate?type=pessoa&quantity=200&format=sql" \
-o seed_pessoas.sql
mysql -h "$DB_HOST" -u "$DB_USER" -p"$DB_PASS" "$DB_NAME" < seed_pessoas.sqlQuando preferir SQL direto vs script Node.js
Use format=sql para seeds pontuais, demos ou quando o ambiente não tem Node.js disponível. Use o script TypeScript quando precisar de lotes acima de 10 mil registros, correlação entre tabelas (foreign keys), ou transformações customizadas no dado antes de inserir.
Automatizar o seed no pipeline de CI/CD
GitHub Actions: job seed-staging
name: Seed Staging
on:
push:
branches: [main]
jobs:
seed-staging:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
- name: Instalar dependências
run: npm ci --prefix seed
- name: Truncar tabelas e rodar seed
env:
DATABASE_URL: ${{ secrets.STAGING_DATABASE_URL }}
FAKEFORGE_API_KEY: ${{ secrets.FAKEFORGE_API_KEY }}
run: npx tsx seed/seed.ts
- name: Rodar testes e2e
run: npm run test:e2eVariáveis de ambiente como secrets
STAGING_DATABASE_URL e FAKEFORGE_API_KEY vivem em Settings → Secrets and variables → Actions no GitHub. Nunca coloque valores reais no yaml ou no repositório.
Idempotência
Adicione um step antes do seed para garantir tabelas limpas:
await conn.execute("SET FOREIGN_KEY_CHECKS = 0");
await conn.execute("TRUNCATE TABLE transacoes");
await conn.execute("TRUNCATE TABLE usuarios");
await conn.execute("TRUNCATE TABLE empresas");
await conn.execute("SET FOREIGN_KEY_CHECKS = 1");Validar a integridade dos dados inseridos
Query de sanidade pós-seed
-- Contagem por tabela
SELECT 'usuarios' AS tabela, COUNT(*) AS total FROM usuarios
UNION ALL
SELECT 'empresas', COUNT(*) FROM empresas
UNION ALL
SELECT 'transacoes', COUNT(*) FROM transacoes;
-- Campos obrigatórios nulos
SELECT COUNT(*) AS cpf_nulo FROM usuarios WHERE cpf IS NULL OR cpf = '';
SELECT COUNT(*) AS cnpj_nulo FROM empresas WHERE cnpj IS NULL OR cnpj = '';
SELECT COUNT(*) AS valor_zero FROM transacoes WHERE valor <= 0;
-- Faixa de valores monetários
SELECT MIN(valor), MAX(valor), AVG(valor) FROM transacoes;Checar CPF com a API
Para validar uma amostra dos CPFs inseridos, use o endpoint /validar-cpf:
curl "https://fakeforge.com.br/api/generate?type=validar-cpf&cpf=12345678901"O mesmo vale para CNPJ via /validar-cnpj. Em produção, faça essa validação no seed script antes do INSERT — não depois.
Boas práticas de segurança no ambiente de staging
Nunca commitar API key
# .gitignore
.env
.env.local
*.envAVISO: Uma API key de FakeForge exposta no repositório pode ser usada por terceiros para esgotar sua cota ou gerar cobranças no seu plano. Ferramentas comogit-secretsougitleaksdetectam esse tipo de vazamento antes do push.
Restringir acesso ao banco de staging
Configure o security group (AWS) ou firewall do MySQL para aceitar conexões apenas do range de IP do CI (GitHub Actions publica a lista de IPs em https://api.github.com/meta) e de VPN corporativa. Evite 0.0.0.0/0 mesmo em staging.
Rotacionar dados falsos a cada ciclo de release
Dados sintéticos envelhecem: testes ficam acoplados a IDs fixos, seeds tornam-se fixtures implícitas. A cada release, execute TRUNCATE + seed para garantir que os testes e2e cubram combinações novas de dados.
Resumo
- Schema primeiro: defina
CHAR(11)para CPF,CHAR(14)para CNPJ eDECIMAL(12,2)para valores antes de rodar qualquer seed — tipos errados geram truncamentos silenciosos. - Use
INSERT IGNOREouON DUPLICATE KEY UPDATEpara tornar o script idempotente e seguro para re-execução no CI. - Separe segredos do código:
DATABASE_URLeFAKEFORGE_API_KEYcomo GitHub Secrets, nunca hardcoded ou commitados. - Valide após inserir: execute a query de sanidade e cheque CPFs com /validar-cpf para detectar falhas de mapeamento antes dos testes e2e rodarem.
- Para volumes pequenos,
format=sqlbasta: baixe o arquivo e importe commysql <; para seeds com correlação entre tabelas, use o script TypeScript commysql2. - Próximos geradores úteis: cartão de crédito com Luhn válido, PIS/PASEP para sistemas de RH e Título de Eleitor cobrem os tipos restantes em sistemas financeiros e de RH.
Perguntas frequentes
Posso reutilizar o script de seed se meu database já tem dados?+
Sim, com INSERT IGNORE ou ON DUPLICATE KEY UPDATE. Exemplo: `INSERT IGNORE INTO usuarios (cpf, nome, email...) VALUES (?, ?, ?)` descarta silenciosamente CPFs duplicados. Se quiser atualizar campos existentes, use `ON DUPLICATE KEY UPDATE nome = VALUES(nome)`. Backups automáticos recomendados.
O que fazer se a API FakeForge retornar erro 429 (rate limit)?+
Implemente backoff exponencial no script. Exemplo: comece com 1s, dobre a cada tentativa (1s, 2s, 4s, 8s). A biblioteca `p-retry` (npm) automação isso. Para plano gratuito, reduza batch de 100 para 50 ou migre para plano Dev se seed roda diariamente no CI.
Como garantir que telefone e CEP estão correlacionados corretamente?+
A API FakeForge retorna DDD válido baseado no CEP — não precisa validação extra. Mas se misturar `pessoa` + `cep` de lotes separados (como no artigo), ordem fica aleatória. Use um seed com `empresa` (que retorna pessoa + cep correlacionados) ou indexe por nome para casar manualmente.
É seguro rodar seed em paralelo com testes e2e contra o mesmo DB?+
Não. Use TRUNCATE antes do seed atomicamente. Melhor: rode seed em banco limpo antes dos testes (GitHub Actions: `seed-staging` → `test-e2e`). Se precisa testar dados dinâmicos, crie um usuário fixture por teste (INSERT com dados únicos do teste, limpe com `teardown`).
Quanto custa gerar 10 mil registros por mês no plano Dev?+
Plano Dev é R$ 29/mês com limite de até 10 mil itens por request (ilimitado número de requests). 10 mil registros = 1 chamada. Custo fixo R$ 29/mês independente do volume. Economicamente viável para staging contínuo. Team é R$ 79/mês com features extras (suporte, SLA).