--- name: "FakeBuilder Generator" description: "Gera FakeBuilders para agregados DDD usando Chance.js seguindo padrão do projeto com PropOrFactory, type augmentation e dados realistas para testes." --- # FakeBuilder Generator Skill ## Objetivo Esta Skill orienta Claude Code a gerar FakeBuilders completos para agregados DDD seguindo o padrão estabelecido no projeto: Chance.js para dados fake realistas, fluent API, type augmentation e suporte a factories. ## Quando usar Ative esta Skill quando: - Criar um novo agregado que precisa de fake builder para testes - Gerar dados de teste realistas para testes unitários ou integração - Implementar test fixtures com Chance.js - Criar builders com padrão fluent API - Precisar de dados fake válidos (CPF, CNPJ, emails, etc) - Configurar test data factories ## Entradas esperadas - `agregado`: Classe do agregado/entidade para o qual gerar o builder - `propriedades`: Lista de propriedades e seus tipos - `validacoes_especiais`: Regras específicas (CPF/CNPJ válido, códigos hierárquicos, etc) - `caminho_arquivo`: Onde salvar o builder (geralmente `[modulo]/domain/[nome].fake-builder.ts`) ## Saídas esperadas - Arquivo `.fake-builder.ts` completo e funcional - Type augmentation para adicionar método `.fake()` no agregado - Comentários explicativos quando necessário - Exemplos de uso no topo do arquivo - Suporte a PropOrFactory para valores fixos ou funções ## Estrutura do FakeBuilder ### Template Base ```typescript import { Chance } from 'chance'; import { [Agregado], Create[Agregado]Props } from './[agregado].aggregate'; type PropOrFactory = T | ((index: number) => T); export class [Agregado]FakeBuilder { private chance: Chance.Chance; private countObjs: number; private baseIndex: number; private static globalIndex = 0; // Propriedades com valores padrão usando factories private _empresaId: PropOrFactory = () => this.chance.guid(); private _propriedade1: PropOrFactory = (index: number) => { // Lógica de geração }; private constructor(countObjs: number = 1) { this.countObjs = countObjs; this.chance = new Chance(); this.baseIndex = [Agregado]FakeBuilder.globalIndex * 100; [Agregado]FakeBuilder.globalIndex += 1; } static anEntity() { return new [Agregado]FakeBuilder<[Agregado]>(1); } static theEntities(countObjs: number) { return new [Agregado]FakeBuilder<[Agregado][]>(countObjs); } with[Propriedade](valueOrFactory: PropOrFactory) { this._propriedade = valueOrFactory; return this; } build(): TBuild { const entities = new Array(this.countObjs) .fill(undefined) .map((_, index) => { const props: Create[Agregado]Props = { empresaId: this.callFactory(this._empresaId, index), propriedade1: this.callFactory(this._propriedade1, index), // ... outras propriedades }; const entity = [Agregado].create(props); return entity; }); return this.countObjs === 1 ? (entities[0] as any) : (entities as any); } private callFactory(factoryOrValue: PropOrFactory, index: number) { return typeof factoryOrValue === 'function' ? factoryOrValue(index) : factoryOrValue; } } // Adicionar método estático ao agregado [Agregado].fake = function () { return [Agregado]FakeBuilder; }; // Type augmentation declare module './[agregado].aggregate' { export interface [Agregado] { fake?: typeof [Agregado]FakeBuilder; } namespace [Agregado] { export let fake: () => typeof [Agregado]FakeBuilder; } } ``` ## Padrões Específicos por Tipo de Dado ### 1. Códigos Simples (3 dígitos) Para bancos: ```typescript private _codigo: PropOrFactory = (index: number) => { const value = (this.baseIndex + index) % 900; // 0-899 return (value + 100).toString().padStart(3, '0'); // 100-999 }; ``` ### 2. Códigos Sequenciais Para centro de custo: ```typescript private _codigo: PropOrFactory = (index: number) => `CC${(this.baseIndex + index + 1).toString().padStart(3, '0')}`; ``` ### 3. Códigos Hierárquicos Para plano de contas: ```typescript private _codigo: PropOrFactory = (index: number) => { const level1 = ((this.baseIndex + index) % 9) + 1; const level2 = ((this.baseIndex + index) % 99) + 1; const level3 = (index % 999) + 1; return `${level1}.${level2.toString().padStart(2, '0')}.${level3.toString().padStart(3, '0')}`; }; ``` ### 4. CPF/CNPJ Válidos Para clientes/fornecedores: ```typescript private _documento: PropOrFactory = (index: number) => { return this._tipoPessoa === 'PF' ? this.generateValidCPF() : this.generateValidCNPJ(); }; private generateValidCPF(): string { const digits = Array.from({ length: 9 }, () => this.chance.integer({ min: 0, max: 9 }) ); const d1 = this.calculateCPFDigit(digits, 10); const d2 = this.calculateCPFDigit([...digits, d1], 11); return [...digits, d1, d2].join(''); } private calculateCPFDigit(digits: number[], weight: number): number { const sum = digits.reduce((acc, digit, idx) => { return acc + digit * (weight - idx); }, 0); const remainder = sum % 11; return remainder < 2 ? 0 : 11 - remainder; } private generateValidCNPJ(): string { const digits = Array.from({ length: 12 }, () => this.chance.integer({ min: 0, max: 9 }) ); const d1 = this.calculateCNPJDigit(digits, [5,4,3,2,9,8,7,6,5,4,3,2]); const d2 = this.calculateCNPJDigit([...digits, d1], [6,5,4,3,2,9,8,7,6,5,4,3,2]); return [...digits, d1, d2].join(''); } private calculateCNPJDigit(digits: number[], weights: number[]): number { const sum = digits.reduce((acc, digit, idx) => { return acc + digit * weights[idx]; }, 0); const remainder = sum % 11; return remainder < 2 ? 0 : 11 - remainder; } ``` ### 5. Nomes Realistas Usando Chance.js: ```typescript private _nome: PropOrFactory = (index: number) => this.chance.name(); // Para empresas private _razaoSocial: PropOrFactory = (index: number) => `${this.chance.company()} ${this.chance.pickone(['Ltda', 'S.A.', 'ME', 'EIRELI'])}`; ``` ### 6. Emails ```typescript private _email: PropOrFactory = (index: number) => this.chance.email(); // Ou null por padrão se opcional private _email: PropOrFactory = () => null; ``` ### 7. Telefones ```typescript private _telefone: PropOrFactory = (index: number) => `11${this.chance.integer({ min: 900000000, max: 999999999 })}`; ``` ### 8. Enums ```typescript private _tipo: PropOrFactory = (index: number) => this.chance.pickone(['RECEITA', 'DESPESA']); private _status: PropOrFactory = () => 'ATIVO'; ``` ### 9. Campos Opcionais ```typescript private _descricao: PropOrFactory = () => null; // Ou com valor gerado private _descricao: PropOrFactory = (index: number) => this.chance.sentence({ words: 10 }); ``` ### 10. Relacionamentos (IDs) ```typescript private _parentId: PropOrFactory = () => null; // Para forçar relacionamento withParentId(valueOrFactory: PropOrFactory) { this._parentId = valueOrFactory; return this; } ``` ## Chance.js - Métodos Úteis ```typescript // Identificadores this.chance.guid() // UUID this.chance.hash({ length: 10 }) // Hash aleatório // Textos this.chance.name() // Nome de pessoa this.chance.company() // Nome de empresa this.chance.email() // Email this.chance.sentence({ words: 10 }) // Frase this.chance.paragraph() // Parágrafo // Números this.chance.integer({ min: 0, max: 999 }) this.chance.floating({ min: 0, max: 100, fixed: 2 }) // Datas this.chance.date() this.chance.timestamp() // Endereços this.chance.address() this.chance.city() this.chance.state() this.chance.zip() this.chance.country() // Telefones this.chance.phone() // Seleção this.chance.pickone(['A', 'B', 'C']) this.chance.shuffle(['A', 'B', 'C']) // Booleanos this.chance.bool() this.chance.bool({ likelihood: 70 }) // 70% true ``` ## Exemplos Completos ### Exemplo 1: Banco (Simples) ```typescript import { Chance } from 'chance'; import { Banco, CreateBancoProps } from './banco.aggregate'; type PropOrFactory = T | ((index: number) => T); export class BancoFakeBuilder { private chance: Chance.Chance; private countObjs: number; private baseIndex: number; private static globalIndex = 0; private _empresaId: PropOrFactory = () => this.chance.guid(); private _codigo: PropOrFactory = (index: number) => { const value = (this.baseIndex + index) % 900; return (value + 100).toString().padStart(3, '0'); }; private _nome: PropOrFactory = (index: number) => `Banco ${this.baseIndex + index + 1}`; private constructor(countObjs: number = 1) { this.countObjs = countObjs; this.chance = new Chance(); this.baseIndex = BancoFakeBuilder.globalIndex * 100; BancoFakeBuilder.globalIndex += 1; } static anEntity() { return new BancoFakeBuilder(1); } static theEntities(countObjs: number) { return new BancoFakeBuilder(countObjs); } withEmpresaId(valueOrFactory: PropOrFactory) { this._empresaId = valueOrFactory; return this; } withCodigo(valueOrFactory: PropOrFactory) { this._codigo = valueOrFactory; return this; } withNome(valueOrFactory: PropOrFactory) { this._nome = valueOrFactory; return this; } build(): TBuild { const entities = new Array(this.countObjs) .fill(undefined) .map((_, index) => { const props: CreateBancoProps = { empresaId: this.callFactory(this._empresaId, index), codigo: this.callFactory(this._codigo, index), nome: this.callFactory(this._nome, index), }; const banco = Banco.create(props); return banco; }); return this.countObjs === 1 ? (entities[0] as any) : (entities as any); } private callFactory(factoryOrValue: PropOrFactory, index: number) { return typeof factoryOrValue === 'function' ? factoryOrValue(index) : factoryOrValue; } } Banco.fake = function () { return BancoFakeBuilder; }; declare module './banco.aggregate' { export interface Banco { fake?: typeof BancoFakeBuilder; } namespace Banco { export let fake: () => typeof BancoFakeBuilder; } } ``` ### Exemplo 2: PlanoContas (Hierárquico) ## Uso dos FakeBuilders ### Single Entity ```typescript const banco = Banco.fake() .anEntity() .build(); ``` ### Multiple Entities ```typescript const bancos = Banco.fake() .theEntities(5) .build(); ``` ### Custom Properties ```typescript const banco = Banco.fake() .anEntity() .withCodigo('001') .withNome('Banco do Brasil') .build(); ``` ### Factory Functions ```typescript const bancos = Banco.fake() .theEntities(10) .withCodigo((index) => (index + 1).toString().padStart(3, '0')) .withEmpresaId(() => 'same-empresa-id') .build(); ``` ### Test Data Setup ```typescript describe('BancoService', () => { let empresaId: string; let bancos: Banco[]; beforeEach(() => { empresaId = 'test-empresa-id'; bancos = Banco.fake() .theEntities(5) .withEmpresaId(empresaId) .build(); }); it('should have all bancos with same empresaId', () => { expect(bancos.every(b => b.empresaId === empresaId)).toBe(true); }); }); ``` ## Checklist de Implementação Ao gerar um FakeBuilder, SEMPRE inclua: ### Estrutura Base - [ ] Import de Chance - [ ] Import do agregado e suas props - [ ] Type `PropOrFactory` - [ ] Classe com TBuild generic - [ ] Propriedades privadas com factories - [ ] Constructor privado - [ ] Métodos estáticos `anEntity()` e `theEntities()` - [ ] Métodos `withX()` para cada propriedade - [ ] Método `build()` - [ ] Método auxiliar `callFactory()` ### Type Augmentation - [ ] Adicionar método `.fake()` ao agregado - [ ] Declare module correto - [ ] Export do tipo correto ### Dados Realistas - [ ] Usar Chance.js apropriadamente - [ ] CPF/CNPJ válidos quando necessário - [ ] Códigos únicos usando baseIndex - [ ] Emails válidos - [ ] Nomes em português quando apropriado ### Índice Global - [ ] Static globalIndex para evitar colisões - [ ] baseIndex calculado no constructor - [ ] Incremento do globalIndex ## Quando NÃO usar esta Skill - Para criar in-memory repositories - Para refatorações de código existente