Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
fabf003
refactor: adjustments to translations
wellfernandes Mar 25, 2026
142eb1f
test: added translations unit tests
wellfernandes Mar 25, 2026
a96d202
refactor: adjusted default random source and fixed comments for docs
wellfernandes Mar 25, 2026
3210fba
refactor: added sentinel errors and adjusted comments for docs
wellfernandes Mar 25, 2026
eb0888a
test: added brazilian vote registration unit tests
wellfernandes Mar 25, 2026
91aab8b
refactor: adjusted generator for vote registration and comments for docs
wellfernandes Mar 25, 2026
56b9a12
refactor: added sentinel errors and adjusted comments for docs
wellfernandes Mar 25, 2026
08822c5
refactor: adjusted person generator and comments for docs
wellfernandes Mar 25, 2026
5271c30
test: added phone unit tests
wellfernandes Mar 25, 2026
16f572d
test: added person generator unit tests
wellfernandes Mar 25, 2026
e1f0a5a
refactor: adjusted brazilian national id and comments for docs
wellfernandes Mar 25, 2026
be5fa77
test: added brazilian national id unit tests
wellfernandes Mar 25, 2026
77e2a11
refactor: adjusted national id generator and comments for docs
wellfernandes Mar 25, 2026
b0312bc
refactor: adjusted gender and comments for docs
wellfernandes Mar 25, 2026
4e47588
test: added gender unit tests
wellfernandes Mar 25, 2026
c0c8d64
refactor: cpf adjusted, sentinel errors added and comments for docs
wellfernandes Mar 25, 2026
d91eaed
test: added cpf generator unit tests
wellfernandes Mar 25, 2026
0eb7d86
feat: added func for formatting cnpj
wellfernandes Mar 25, 2026
9eaa1d1
refactor: added sentinel errors and adjusted comments for docs
wellfernandes Mar 25, 2026
c3ea7f7
refactor: adjusted params, rand, and comments for docs
wellfernandes Mar 25, 2026
af49045
refactor: added sentinel errors and other adjustments
wellfernandes Mar 25, 2026
249ae8c
test: added cnpj generator unit tests
wellfernandes Mar 25, 2026
182ddc2
test: removed unnecessary comment
wellfernandes Mar 25, 2026
3ccd1c1
test: added brazilian certificates unit tests
wellfernandes Mar 25, 2026
d6f5d0e
refactor: adjusted entity address and fixed comments for docs
wellfernandes Mar 25, 2026
7171964
test: added address generator unit tests
wellfernandes Mar 25, 2026
309dbc7
feat: added provider to enable integration
wellfernandes Mar 25, 2026
0a0f5b0
feat: added options to make the mocaí more flexible
wellfernandes Mar 25, 2026
afba396
refactor: mocker generation adjusted and godoc comments added
wellfernandes Mar 25, 2026
1b00511
test: added mocker unit tests
wellfernandes Mar 25, 2026
fe63265
docs(example): update mocaí usage examples
wellfernandes Mar 25, 2026
bfc1a8e
refactor(ptbr): update math/rand to v2
wellfernandes Mar 26, 2026
e691a0f
refactor(ptbr): remove dead code
wellfernandes Mar 26, 2026
2cccf5f
fix(enus): fixed error tag
wellfernandes Mar 26, 2026
3f37b99
docs(readme-pt): updated readme
wellfernandes Mar 26, 2026
3e56b39
docs(readme): updated readme
wellfernandes Mar 26, 2026
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
54 changes: 45 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ The name Mocaí is a tribute to the Brazilian initiative behind the library. It

## 📦 Supported Entities
- Person (with gender, age, CPF)
- Gender (standalone generation)
- Address (street, number, city, state, UF, ZIP)
- Phone (area code, number)
- Company (name, CNPJ)
- CPF (Brazilian individual taxpayer registry)
- CNPJ (Brazilian company registry)
- Certificates (Birth, Marriage, Death)
- National ID (RG)
- Voter Registration (Título de Eleitor)
Comment on lines 22 to 31
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

A lista de entidades suportadas ficou incompleta.

O CNPJ standalone saiu daqui, mas a própria PR ainda refatora/adiciona cobertura para esse gerador. Isso passa a impressão de que a geração direta de CNPJ não faz mais parte da API pública.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` around lines 22 - 31, A seção "## 📦 Supported Entities" removeu a
menção ao gerador de CNPJ standalone, gerando ambiguidade; atualize essa seção
para incluir explicitamente "CNPJ (standalone)" ou ajustar a linha "Company
(name, CNPJ)" para deixar claro que existe também um gerador direto de CNPJ na
API pública; revise o bloco de entidades (o título "Supported Entities" e as
linhas "Company" e "CNPJ") e reintroduza ou clarifique a entrada para CNPJ para
refletir a implementação atual.

Expand Down Expand Up @@ -64,54 +64,90 @@ import (

func main() {
// Create a Mocker instance for Brazilian Portuguese
mocker := mocai.NewMocker("ptbr", true, nil) // isFormatted: true for formatted docs (e.g., CPF/CNPJ)
mocker := mocai.NewMocker(
mocai.WithLanguage("ptbr"),
mocai.WithFormatted(true),
)

// Generate a mock address
address, err := mocker.NewAddress()
if err != nil {
// Error messages are localized where translation keys are available
log.Fatal(err)
}
fmt.Printf("Address: %s, %d - %s, %s (%s) - %s\n", address.Street, address.Number, address.City, address.State, address.UF, address.ZIP)

// Generate a mock person
person, err := mocker.NewPerson()
if err != nil {
// Error messages are localized where translation keys are available
log.Fatal(err)
}
fmt.Printf("Person: %s %s, Gender: %s, Age: %d, CPF: %s\n", person.FirstNameMale, person.LastName, person.Gender.Identity, person.Age, person.CPF.Number)

// Generate a mock company
company, err := mocker.NewCompany()
if err != nil {
// Error messages are localized where translation keys are available
log.Fatal(err)
}
fmt.Printf("Company: %s, CNPJ: %s\n", company.BrazilianCompany.Name, company.BrazilianCompany.CNPJ)
}

```

### Error Messages & Localization

Error messages are localized where translation keys are available. When an error occurs (e.g., invalid data, unsupported language, or generation failure), the error message will be presented in the language configured for the `Mocker` instance, if a translation exists. In some cases, fallback or hardcoded errors may occur if translation coverage is incomplete.

You do not need to perform any extra steps for error localization — Mocai handles this automatically for all supported languages where translation keys are present.
```

> **Note:** Each call to a method like `NewPerson()` or `NewAddress()` generates a new mock with random data. The `Mocker` instance is immutable regarding its configuration (language, formatting, random source).

#### About Languages
Currently, only "ptbr" is implemented. To support other languages, contribute with new translation and mock data files.

#### About Formatting
The `isFormatted` parameter controls whether documents like CPF/CNPJ are returned formatted (e.g., `123.456.789-00`) or as plain numbers (`12345678900`).
The `WithFormatted` option controls whether documents like CPF/CNPJ are returned formatted (e.g., `123.456.789-00`) or as plain numbers (`12345678900`).

### Advanced Usage

#### MockGenerator Interface
The `MockGenerator` interface defines the contract for generating mock data. Use it for dependency injection in your tests:

```go
func CreateUser(generator mocai.MockGenerator) (*User, error) {
person, err := generator.NewPerson()
if err != nil {
return nil, err
}
return &User{Name: person.FirstNameMale + " " + person.LastName}, nil
}
```

#### Custom Providers
You can inject custom providers for Address, Person, and Company to integrate with external APIs or databases:

```go
mocker := mocai.NewMocker(
mocai.WithLanguage("ptbr"),
mocai.WithAddressProvider(myCustomAddressProvider),
mocai.WithPersonProvider(myCustomPersonProvider),
mocai.WithCompanyProvider(myCustomCompanyProvider),
)
```

#### Deterministic Generation
Use `WithRandSource` to provide a custom random source for reproducible test data:

```go
rnd := translations.NewSafeRandSource(myFixedRand)
mocker := mocai.NewMocker(
mocai.WithLanguage("ptbr"),
mocai.WithRandSource(rnd),
)
```

### Examples
The ***examples*** folder contains usage samples:

- `mocker/`: Example using the main entry point `mocai.NewMocker(lang string, isFormatted bool, rnd RandSource)` to generate mocks in a fluent and simplified way.
- `mocker/`: Example using the main entry point `mocai.NewMocker(opts ...Option)` with functional options to generate mocks in a fluent and simplified way.

To run an example:
```sh
Expand Down
56 changes: 47 additions & 9 deletions docs/localization/pt/README-PT.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

![mocai](../../.././img/mocai.svg)

#### [README (English)](/README.md)

Comment on lines +5 to +6
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Corrija o nível do heading para manter a hierarquia correta.

O static analysis (markdownlint) indica que o heading na linha 5 usa #### (h4), mas deveria seguir a hierarquia incrementando de h1 para h2. Isso melhora a acessibilidade e a estrutura do documento.

📝 Correção sugerida
-#### [README (English)](/README.md)
+## [README (English)](/README.md)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#### [README (English)](/README.md)
## [README (English)](/README.md)
🧰 Tools
🪛 markdownlint-cli2 (0.22.0)

[warning] 5-5: Heading levels should only increment by one level at a time
Expected: h2; Actual: h4

(MD001, heading-increment)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/localization/pt/README-PT.md` around lines 5 - 6, The heading "####
[README (English)](/README.md)" is using h4 but should be h2 to preserve
hierarchical order; update that Markdown heading from "#### [README
(English)](/README.md)" to "## [README (English)](/README.md)" so it increments
correctly from the document h1 to h2 and satisfies markdownlint/accessibility
rules.

Uma biblioteca Go para geração de dados de teste, permitindo criar mocks de entidades de forma simples e eficiente.

## 📖 Descrição
Expand All @@ -19,11 +21,11 @@ O nome Mocai é uma homenagem à iniciativa brasileira por trás da biblioteca.

## 📦 Entidades Suportadas
- Pessoa (com gênero, idade, CPF)
- Gênero (geração independente)
- Endereço (rua, número, cidade, estado, UF, CEP)
- Telefone (DDD, número)
- Empresa (nome, CNPJ)
- CPF (Cadastro de Pessoa Física)
- CNPJ (Cadastro Nacional de Pessoa Jurídica)
- Certidões (Nascimento, Casamento, Óbito)
- RG (Identidade)
- Título de Eleitor
Expand Down Expand Up @@ -62,54 +64,90 @@ import (

func main() {
// Cria uma instância do Mocker para português do Brasil
mocker := mocai.NewMocker("ptbr", true, nil) // isFormatted: true para documentos formatados (ex: CPF/CNPJ)
mocker := mocai.NewMocker(
mocai.WithLanguage("ptbr"),
mocai.WithFormatted(true),
)

// Gera um endereço fictício
address, err := mocker.NewAddress()
if err != nil {
// As mensagens de erro são localizadas quando há chave de tradução disponível
log.Fatal(err)
}
fmt.Printf("Endereço: %s, %d - %s, %s (%s) - %s\n", address.Street, address.Number, address.City, address.State, address.UF, address.ZIP)

// Gera uma pessoa fictícia
person, err := mocker.NewPerson()
if err != nil {
// As mensagens de erro são localizadas quando há chave de tradução disponível
log.Fatal(err)
}
fmt.Printf("Pessoa: %s %s, Gênero: %s, Idade: %d, CPF: %s\n", person.FirstNameMale, person.LastName, person.Gender.Identity, person.Age, person.CPF.Number)

// Gera uma empresa fictícia
company, err := mocker.NewCompany()
if err != nil {
// As mensagens de erro são localizadas quando há chave de tradução disponível
log.Fatal(err)
}
fmt.Printf("Empresa: %s, CNPJ: %s\n", company.BrazilianCompany.Name, company.BrazilianCompany.CNPJ)
}

```

### Mensagens de Erro e Localização

As mensagens de erro são localizadas quando há chave de tradução disponível. Quando ocorre um erro (ex: dados inválidos, idioma não suportado ou falha na geração), a mensagem será apresentada no idioma configurado para a instância do `Mocker`, se houver tradução. Em alguns casos, mensagens fixas podem aparecer caso a cobertura de traduções não seja completa.

Não é necessário realizar nenhuma etapa extra para a localização das mensagens de erro — o Mocai faz isso automaticamente para todos os idiomas suportados onde há chave de tradução.
```

> **Nota:** Cada chamada de método como `NewPerson()` ou `NewAddress()` gera um novo mock com dados aleatórios. A instância do `Mocker` é imutável quanto à configuração (idioma, formatação, fonte de aleatoriedade).

#### Sobre Idiomas
Atualmente, apenas "ptbr" está implementado. Para suportar outros idiomas, contribua com novos arquivos de tradução e mocks.

#### Sobre Formatação
O parâmetro `isFormatted` controla se documentos como CPF/CNPJ são retornados formatados (ex: `123.456.789-00`) ou apenas números (`12345678900`).
A opção `WithFormatted` controla se documentos como CPF/CNPJ são retornados formatados (ex: `123.456.789-00`) ou apenas números (`12345678900`).

### Uso Avançado

#### Interface MockGenerator
A interface `MockGenerator` define o contrato para geração de dados fictícios. Use-a para injeção de dependência nos seus testes:

```go
func CreateUser(generator mocai.MockGenerator) (*User, error) {
person, err := generator.NewPerson()
if err != nil {
return nil, err
}
return &User{Name: person.FirstNameMale + " " + person.LastName}, nil
}
```

#### Providers Customizados
Você pode injetar providers customizados para Address, Person e Company, integrando com APIs externas ou bancos de dados:

```go
mocker := mocai.NewMocker(
mocai.WithLanguage("ptbr"),
mocai.WithAddressProvider(meuProviderDeEndereco),
mocai.WithPersonProvider(meuProviderDePessoa),
mocai.WithCompanyProvider(meuProviderDeEmpresa),
)
```

#### Geração Determinística
Use `WithRandSource` para fornecer uma fonte de aleatoriedade customizada para dados reproduzíveis em testes:

```go
rnd := translations.NewSafeRandSource(meuRandFixo)
mocker := mocai.NewMocker(
mocai.WithLanguage("ptbr"),
mocai.WithRandSource(rnd),
)
```

### Exemplos
O diretório ***examples*** contém exemplos de uso:

- `mocker/`: Exemplo usando o ponto de entrada principal `mocai.NewMocker(lang string, isFormatted bool, rnd RandSource)` para gerar mocks de maneira fluida e simplificada.
- `mocker/`: Exemplo usando o ponto de entrada principal `mocai.NewMocker(opts ...Option)` com functional options para gerar mocks de maneira fluida e simplificada.

Para executar um exemplo:
```sh
Expand Down
77 changes: 68 additions & 9 deletions examples/mocker/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,87 @@ package main

import (
"fmt"
"log"

"github.com/brazzcore/mocai/internal/cli"
"github.com/brazzcore/mocai/pkg/mocai"
)

func main() {

fmt.Println(cli.HeaderMain)
fmt.Println(cli.SubHeader)

m := mocai.NewMocker("ptbr", true, nil)
// Create a Mocker using functional options
m := mocai.NewMocker(
mocai.WithLanguage("ptbr"),
mocai.WithFormatted(true),
)

// Generate a mock person
p, err := m.NewPerson()
if err != nil {
log.Fatal(err)
}
fmt.Printf("Person: %s %s, Gender: %s, Age: %d, CPF: %s\n",
p.FirstNameMale, p.LastName, p.Gender.Identity, p.Age, p.CPF.Number)

// Generate a mock address
addr, err := m.NewAddress()
if err != nil {
log.Fatal(err)
}
fmt.Printf("Address: %s, %d - %s, %s (%s) - %s\n",
addr.Street, addr.Number, addr.City, addr.State, addr.UF, addr.ZIP)

// Generate a mock company
comp, err := m.NewCompany()
if err != nil {
log.Fatal(err)
}
fmt.Printf("Company: %s, CNPJ: %s\n", comp.BrazilianCompany.Name, comp.BrazilianCompany.CNPJ)

// Generate a mock CPF
cpfVal, err := m.NewCPF()
if err != nil {
log.Fatal(err)
}
fmt.Printf("CPF: %s\n", cpfVal.Number)

// Generate a mock certificate
cert, err := m.NewCertificate()
if err != nil {
log.Fatal(err)
}
fmt.Printf("Birth Certificate: %s\n", cert.Brazil.BirthCertificate.Number)

// Generate a mock national ID (RG)
nid, err := m.NewNationalID()
if err != nil {
log.Fatal(err)
}
fmt.Printf("RG: %s - %s/%s\n", nid.BrazilianRG.Number, nid.BrazilianRG.IssuingBody, nid.BrazilianRG.State)

// Generate a mock voter registration
vr, err := m.NewVoteRegistration()
if err != nil {
log.Fatal(err)
}
fmt.Printf("Voter Registration: %s (Section: %s, Zone: %s)\n",
vr.BrazilianVoteRegistration.Number, vr.BrazilianVoteRegistration.Section, vr.BrazilianVoteRegistration.Zone)

address, err := m.NewAddress()
// Generate a mock phone
ph, err := m.NewPhone()
if err != nil {
fmt.Println(err)
} else {
fmt.Printf("Address: %s, %d - %s, %s (%s) - %s\n",
address.Street, address.Number, address.City, address.State, address.UF, address.ZIP)
log.Fatal(err)
}
fmt.Printf("Phone: (%s) %s\n", ph.AreaCode, ph.Number)

certificate, _ := m.NewCertificate()
fmt.Println("Brazilian birth certificate:", certificate.Brazil.BirthCertificate.Number)
// You can also use the MockGenerator interface for dependency injection:
printLanguage(m)

fmt.Println(cli.Footer)
}

func printLanguage(m mocai.MockGenerator) {
fmt.Printf("\nLanguage: %s\n", m.Language())
}
11 changes: 6 additions & 5 deletions pkg/mocai/entities/address/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,13 @@ type Address struct {
ZIP string
}

type SlicesToCheck struct {
// slicesToCheck is an internal helper for validating required data slices.
type slicesToCheck struct {
data []string
err error
}

// NewAddress generates a mock address using a custom language and a random source
// NewAddress generates a mock address using a custom language and a random source.
func NewAddress(lang string, rnd translations.RandSource) (*Address, error) {
addr, err := generateAddress(lang, rnd)
if err != nil {
Expand All @@ -37,22 +38,22 @@ func generateAddress(lang string, rnd translations.RandSource) (*Address, error)
supportedLang = "ptbr"
}
if rnd == nil {
rnd = translations.NewSafeRandSource(translations.DefaultRandSource())
rnd = translations.DefaultRandSource()
}

streets := translations.GetList(supportedLang, "address_street")
cities := translations.GetList(supportedLang, "address_city")
states := translations.GetList(supportedLang, "address_state")
zips := translations.GetList(supportedLang, "address_zip")

slicesToCheck := []SlicesToCheck{
checks := []slicesToCheck{
{streets, ErrNoStreets},
{cities, ErrNoCities},
{states, ErrNoStates},
{zips, ErrNoZips},
}

for _, s := range slicesToCheck {
for _, s := range checks {
if len(s.data) == 0 {
return nil, s.err
}
Expand Down
Loading
Loading