Claude Code Plugins

Community-maintained marketplace

Feedback

FakeBuilder Generator

@edneyreis999/zord-project
0
0

Gera FakeBuilders para agregados DDD usando Chance.js seguindo padrão do projeto com PropOrFactory, type augmentation e dados realistas para testes.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

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

import { Chance } from 'chance';
import { [Agregado], Create[Agregado]Props } from './[agregado].aggregate';

type PropOrFactory<T> = T | ((index: number) => T);

export class [Agregado]FakeBuilder<TBuild = any> {
  private chance: Chance.Chance;
  private countObjs: number;
  private baseIndex: number;
  private static globalIndex = 0;

  // Propriedades com valores padrão usando factories
  private _empresaId: PropOrFactory<string> = () => this.chance.guid();
  private _propriedade1: PropOrFactory<tipo> = (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<tipo>) {
    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<any>, 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:

private _codigo: PropOrFactory<string> = (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:

private _codigo: PropOrFactory<string> = (index: number) =>
  `CC${(this.baseIndex + index + 1).toString().padStart(3, '0')}`;

3. Códigos Hierárquicos

Para plano de contas:

private _codigo: PropOrFactory<string> = (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:

private _documento: PropOrFactory<string> = (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:

private _nome: PropOrFactory<string> = (index: number) =>
  this.chance.name();

// Para empresas
private _razaoSocial: PropOrFactory<string> = (index: number) =>
  `${this.chance.company()} ${this.chance.pickone(['Ltda', 'S.A.', 'ME', 'EIRELI'])}`;

6. Emails

private _email: PropOrFactory<string | null> = (index: number) =>
  this.chance.email();

// Ou null por padrão se opcional
private _email: PropOrFactory<string | null> = () => null;

7. Telefones

private _telefone: PropOrFactory<string | null> = (index: number) =>
  `11${this.chance.integer({ min: 900000000, max: 999999999 })}`;

8. Enums

private _tipo: PropOrFactory<TipoPlanoContas> = (index: number) =>
  this.chance.pickone(['RECEITA', 'DESPESA']);

private _status: PropOrFactory<Status> = () => 'ATIVO';

9. Campos Opcionais

private _descricao: PropOrFactory<string | null> = () => null;

// Ou com valor gerado
private _descricao: PropOrFactory<string | null> = (index: number) =>
  this.chance.sentence({ words: 10 });

10. Relacionamentos (IDs)

private _parentId: PropOrFactory<string | null> = () => null;

// Para forçar relacionamento
withParentId(valueOrFactory: PropOrFactory<string>) {
  this._parentId = valueOrFactory;
  return this;
}

Chance.js - Métodos Úteis

// 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)

import { Chance } from 'chance';
import { Banco, CreateBancoProps } from './banco.aggregate';

type PropOrFactory<T> = T | ((index: number) => T);

export class BancoFakeBuilder<TBuild = any> {
  private chance: Chance.Chance;
  private countObjs: number;
  private baseIndex: number;
  private static globalIndex = 0;

  private _empresaId: PropOrFactory<string> = () => this.chance.guid();

  private _codigo: PropOrFactory<string> = (index: number) => {
    const value = (this.baseIndex + index) % 900;
    return (value + 100).toString().padStart(3, '0');
  };

  private _nome: PropOrFactory<string> = (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<Banco>(1);
  }

  static theEntities(countObjs: number) {
    return new BancoFakeBuilder<Banco[]>(countObjs);
  }

  withEmpresaId(valueOrFactory: PropOrFactory<string>) {
    this._empresaId = valueOrFactory;
    return this;
  }

  withCodigo(valueOrFactory: PropOrFactory<string>) {
    this._codigo = valueOrFactory;
    return this;
  }

  withNome(valueOrFactory: PropOrFactory<string>) {
    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<any>, 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

const banco = Banco.fake()
  .anEntity()
  .build();

Multiple Entities

const bancos = Banco.fake()
  .theEntities(5)
  .build();

Custom Properties

const banco = Banco.fake()
  .anEntity()
  .withCodigo('001')
  .withNome('Banco do Brasil')
  .build();

Factory Functions

const bancos = Banco.fake()
  .theEntities(10)
  .withCodigo((index) => (index + 1).toString().padStart(3, '0'))
  .withEmpresaId(() => 'same-empresa-id')
  .build();

Test Data Setup

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<T>
  • 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