maestro-bundle 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +91 -0
- package/package.json +25 -0
- package/src/cli.mjs +212 -0
- package/templates/bundle-ai-agents/.spec/constitution.md +33 -0
- package/templates/bundle-ai-agents/AGENTS.md +140 -0
- package/templates/bundle-ai-agents/skills/agent-orchestration/SKILL.md +132 -0
- package/templates/bundle-ai-agents/skills/api-design/SKILL.md +100 -0
- package/templates/bundle-ai-agents/skills/clean-architecture/SKILL.md +99 -0
- package/templates/bundle-ai-agents/skills/context-engineering/SKILL.md +98 -0
- package/templates/bundle-ai-agents/skills/database-modeling/SKILL.md +59 -0
- package/templates/bundle-ai-agents/skills/docker-containerization/SKILL.md +114 -0
- package/templates/bundle-ai-agents/skills/eval-testing/SKILL.md +115 -0
- package/templates/bundle-ai-agents/skills/memory-management/SKILL.md +106 -0
- package/templates/bundle-ai-agents/skills/prompt-engineering/SKILL.md +66 -0
- package/templates/bundle-ai-agents/skills/rag-pipeline/SKILL.md +128 -0
- package/templates/bundle-ai-agents/skills/testing-strategy/SKILL.md +95 -0
- package/templates/bundle-base/AGENTS.md +118 -0
- package/templates/bundle-base/skills/branch-strategy/SKILL.md +42 -0
- package/templates/bundle-base/skills/code-review/SKILL.md +54 -0
- package/templates/bundle-base/skills/commit-pattern/SKILL.md +58 -0
- package/templates/bundle-data-pipeline/.spec/constitution.md +32 -0
- package/templates/bundle-data-pipeline/AGENTS.md +115 -0
- package/templates/bundle-data-pipeline/skills/data-preprocessing/SKILL.md +75 -0
- package/templates/bundle-data-pipeline/skills/docker-containerization/SKILL.md +114 -0
- package/templates/bundle-data-pipeline/skills/feature-engineering/SKILL.md +76 -0
- package/templates/bundle-data-pipeline/skills/mlops-pipeline/SKILL.md +77 -0
- package/templates/bundle-data-pipeline/skills/model-training/SKILL.md +68 -0
- package/templates/bundle-data-pipeline/skills/rag-pipeline/SKILL.md +128 -0
- package/templates/bundle-frontend-spa/.spec/constitution.md +32 -0
- package/templates/bundle-frontend-spa/AGENTS.md +107 -0
- package/templates/bundle-frontend-spa/skills/authentication/SKILL.md +90 -0
- package/templates/bundle-frontend-spa/skills/component-design/SKILL.md +115 -0
- package/templates/bundle-frontend-spa/skills/e2e-testing/SKILL.md +101 -0
- package/templates/bundle-frontend-spa/skills/integration-api/SKILL.md +95 -0
- package/templates/bundle-frontend-spa/skills/react-patterns/SKILL.md +130 -0
- package/templates/bundle-frontend-spa/skills/responsive-layout/SKILL.md +65 -0
- package/templates/bundle-frontend-spa/skills/state-management/SKILL.md +86 -0
- package/templates/bundle-jhipster-microservices/.spec/constitution.md +37 -0
- package/templates/bundle-jhipster-microservices/AGENTS.md +307 -0
- package/templates/bundle-jhipster-microservices/skills/ci-cd-pipeline/SKILL.md +112 -0
- package/templates/bundle-jhipster-microservices/skills/clean-architecture/SKILL.md +99 -0
- package/templates/bundle-jhipster-microservices/skills/ddd-tactical/SKILL.md +138 -0
- package/templates/bundle-jhipster-microservices/skills/jhipster-angular/SKILL.md +97 -0
- package/templates/bundle-jhipster-microservices/skills/jhipster-docker-k8s/SKILL.md +183 -0
- package/templates/bundle-jhipster-microservices/skills/jhipster-entities/SKILL.md +87 -0
- package/templates/bundle-jhipster-microservices/skills/jhipster-gateway/SKILL.md +96 -0
- package/templates/bundle-jhipster-microservices/skills/jhipster-kafka/SKILL.md +145 -0
- package/templates/bundle-jhipster-microservices/skills/jhipster-registry/SKILL.md +83 -0
- package/templates/bundle-jhipster-microservices/skills/jhipster-service/SKILL.md +131 -0
- package/templates/bundle-jhipster-microservices/skills/testing-strategy/SKILL.md +95 -0
- package/templates/bundle-jhipster-monorepo/.spec/constitution.md +32 -0
- package/templates/bundle-jhipster-monorepo/AGENTS.md +227 -0
- package/templates/bundle-jhipster-monorepo/skills/clean-architecture/SKILL.md +99 -0
- package/templates/bundle-jhipster-monorepo/skills/ddd-tactical/SKILL.md +138 -0
- package/templates/bundle-jhipster-monorepo/skills/jhipster-angular/SKILL.md +166 -0
- package/templates/bundle-jhipster-monorepo/skills/jhipster-entities/SKILL.md +141 -0
- package/templates/bundle-jhipster-monorepo/skills/jhipster-liquibase/SKILL.md +95 -0
- package/templates/bundle-jhipster-monorepo/skills/jhipster-security/SKILL.md +89 -0
- package/templates/bundle-jhipster-monorepo/skills/jhipster-spring/SKILL.md +155 -0
- package/templates/bundle-jhipster-monorepo/skills/testing-strategy/SKILL.md +95 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: jhipster-entities
|
|
3
|
+
description: Criar e gerenciar entidades JHipster com JDL, relationships, enums, validações e geração de código. Use quando precisar criar entidades, definir modelo de dados, ou gerar código com JHipster.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# JHipster Entities
|
|
7
|
+
|
|
8
|
+
## Fluxo
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
1. Definir entidade no JDL
|
|
12
|
+
2. Rodar jhipster import-jdl
|
|
13
|
+
3. Enriquecer entidade gerada com comportamento DDD
|
|
14
|
+
4. Ajustar Liquibase changeset se necessário
|
|
15
|
+
5. Criar/ajustar testes
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## JDL — Sintaxe
|
|
19
|
+
|
|
20
|
+
```jdl
|
|
21
|
+
entity NomeDaEntidade {
|
|
22
|
+
campo TipoDoCampo validacao
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Tipos disponíveis
|
|
27
|
+
| Tipo JDL | Java | DB |
|
|
28
|
+
|---|---|---|
|
|
29
|
+
| String | String | varchar(255) |
|
|
30
|
+
| Integer | Integer | integer |
|
|
31
|
+
| Long | Long | bigint |
|
|
32
|
+
| Float | Float | float |
|
|
33
|
+
| Double | Double | double |
|
|
34
|
+
| BigDecimal | BigDecimal | decimal |
|
|
35
|
+
| Boolean | Boolean | boolean |
|
|
36
|
+
| LocalDate | LocalDate | date |
|
|
37
|
+
| Instant | Instant | timestamp |
|
|
38
|
+
| ZonedDateTime | ZonedDateTime | timestamp |
|
|
39
|
+
| Duration | Duration | bigint |
|
|
40
|
+
| UUID | UUID | uuid |
|
|
41
|
+
| Blob | byte[] | blob |
|
|
42
|
+
| AnyBlob | byte[] | blob |
|
|
43
|
+
| ImageBlob | byte[] | blob |
|
|
44
|
+
| TextBlob | String | clob |
|
|
45
|
+
|
|
46
|
+
### Validações
|
|
47
|
+
```jdl
|
|
48
|
+
entity Product {
|
|
49
|
+
name String required minlength(3) maxlength(100)
|
|
50
|
+
price BigDecimal required min(0)
|
|
51
|
+
description TextBlob
|
|
52
|
+
active Boolean required
|
|
53
|
+
sku String required unique pattern(/^[A-Z]{3}-[0-9]{4}$/)
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Relationships
|
|
58
|
+
```jdl
|
|
59
|
+
relationship OneToMany {
|
|
60
|
+
Department{employees} to Employee{department(name) required}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
relationship ManyToMany {
|
|
64
|
+
Project{members(login)} to User with builtInEntity
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
relationship OneToOne {
|
|
68
|
+
Employee{address} to Address with jpaDerivedIdentifier
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Enums
|
|
73
|
+
```jdl
|
|
74
|
+
enum Status {
|
|
75
|
+
ACTIVE("Ativo"),
|
|
76
|
+
INACTIVE("Inativo"),
|
|
77
|
+
SUSPENDED("Suspenso")
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Gerar código
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
# Gerar a partir do JDL
|
|
85
|
+
jhipster import-jdl jhipster-jdl.jdl
|
|
86
|
+
|
|
87
|
+
# Gerar entidade individual
|
|
88
|
+
jhipster entity Demand
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Enriquecer com DDD
|
|
92
|
+
|
|
93
|
+
Após gerar, adicionar comportamento à entidade:
|
|
94
|
+
|
|
95
|
+
```java
|
|
96
|
+
@Entity
|
|
97
|
+
@Table(name = "demand")
|
|
98
|
+
public class Demand implements Serializable {
|
|
99
|
+
|
|
100
|
+
// Campos gerados pelo JHipster (manter)
|
|
101
|
+
@Id
|
|
102
|
+
@GeneratedValue(strategy = GenerationType.SEQUENCE)
|
|
103
|
+
private Long id;
|
|
104
|
+
|
|
105
|
+
@NotNull
|
|
106
|
+
@Lob
|
|
107
|
+
private String description;
|
|
108
|
+
|
|
109
|
+
@NotNull
|
|
110
|
+
@Enumerated(EnumType.STRING)
|
|
111
|
+
private DemandStatus status;
|
|
112
|
+
|
|
113
|
+
@OneToMany(mappedBy = "demand", cascade = CascadeType.ALL, orphanRemoval = true)
|
|
114
|
+
private List<Task> tasks = new ArrayList<>();
|
|
115
|
+
|
|
116
|
+
// ADICIONAR — comportamento rico
|
|
117
|
+
public void addTask(Task task) {
|
|
118
|
+
if (this.tasks.size() >= 20) {
|
|
119
|
+
throw new BusinessException("Máximo 20 tasks por demanda");
|
|
120
|
+
}
|
|
121
|
+
this.tasks.add(task);
|
|
122
|
+
task.setDemand(this);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
public void removeTask(Task task) {
|
|
126
|
+
this.tasks.remove(task);
|
|
127
|
+
task.setDemand(null);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
public boolean isCompletable() {
|
|
131
|
+
return this.tasks.stream().allMatch(t -> t.getStatus() == TaskStatus.COMPLETED);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
public void complete() {
|
|
135
|
+
if (!isCompletable()) {
|
|
136
|
+
throw new BusinessException("Nem todas as tasks foram concluídas");
|
|
137
|
+
}
|
|
138
|
+
this.status = DemandStatus.COMPLETED;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
```
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: jhipster-liquibase
|
|
3
|
+
description: Gerenciar migrations de banco com Liquibase no JHipster. Use quando precisar criar tabelas, alterar schema, adicionar colunas, ou criar índices.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Liquibase no JHipster
|
|
7
|
+
|
|
8
|
+
## Estrutura
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
src/main/resources/config/liquibase/
|
|
12
|
+
├── master.xml # Arquivo principal (lista todos os changelogs)
|
|
13
|
+
├── changelog/
|
|
14
|
+
│ ├── 00000000000000_initial_schema.xml
|
|
15
|
+
│ ├── 20260327100000_added_entity_Demand.xml
|
|
16
|
+
│ ├── 20260327100100_added_entity_Task.xml
|
|
17
|
+
│ └── 20260327120000_added_column_priority.xml
|
|
18
|
+
└── data/
|
|
19
|
+
├── authority.csv # Dados iniciais
|
|
20
|
+
└── user.csv
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Criar changeset
|
|
24
|
+
|
|
25
|
+
### Nova tabela
|
|
26
|
+
```xml
|
|
27
|
+
<changeSet id="20260327-1" author="dev">
|
|
28
|
+
<createTable tableName="demand">
|
|
29
|
+
<column name="id" type="bigint" autoIncrement="true">
|
|
30
|
+
<constraints primaryKey="true" nullable="false"/>
|
|
31
|
+
</column>
|
|
32
|
+
<column name="description" type="clob">
|
|
33
|
+
<constraints nullable="false"/>
|
|
34
|
+
</column>
|
|
35
|
+
<column name="status" type="varchar(20)">
|
|
36
|
+
<constraints nullable="false"/>
|
|
37
|
+
</column>
|
|
38
|
+
<column name="priority" type="varchar(20)">
|
|
39
|
+
<constraints nullable="false"/>
|
|
40
|
+
</column>
|
|
41
|
+
<column name="created_at" type="timestamp">
|
|
42
|
+
<constraints nullable="false"/>
|
|
43
|
+
</column>
|
|
44
|
+
<column name="completed_at" type="timestamp"/>
|
|
45
|
+
</createTable>
|
|
46
|
+
|
|
47
|
+
<createIndex indexName="idx_demand_status" tableName="demand">
|
|
48
|
+
<column name="status"/>
|
|
49
|
+
</createIndex>
|
|
50
|
+
</changeSet>
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Adicionar coluna
|
|
54
|
+
```xml
|
|
55
|
+
<changeSet id="20260327-2" author="dev">
|
|
56
|
+
<addColumn tableName="demand">
|
|
57
|
+
<column name="requester" type="varchar(100)">
|
|
58
|
+
<constraints nullable="false"/>
|
|
59
|
+
</column>
|
|
60
|
+
</addColumn>
|
|
61
|
+
</changeSet>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Foreign key
|
|
65
|
+
```xml
|
|
66
|
+
<changeSet id="20260327-3" author="dev">
|
|
67
|
+
<addForeignKeyConstraint
|
|
68
|
+
baseTableName="task"
|
|
69
|
+
baseColumnNames="demand_id"
|
|
70
|
+
constraintName="fk_task_demand"
|
|
71
|
+
referencedTableName="demand"
|
|
72
|
+
referencedColumnNames="id"/>
|
|
73
|
+
</changeSet>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Regras
|
|
77
|
+
|
|
78
|
+
- IDs de changeset: `YYYYMMDD-N` (data + sequencial)
|
|
79
|
+
- Nunca modificar changeset já executado em produção
|
|
80
|
+
- Sempre adicionar rollback:
|
|
81
|
+
```xml
|
|
82
|
+
<changeSet id="20260327-1" author="dev">
|
|
83
|
+
<addColumn tableName="demand">
|
|
84
|
+
<column name="priority" type="varchar(20)"/>
|
|
85
|
+
</addColumn>
|
|
86
|
+
<rollback>
|
|
87
|
+
<dropColumn tableName="demand" columnName="priority"/>
|
|
88
|
+
</rollback>
|
|
89
|
+
</changeSet>
|
|
90
|
+
```
|
|
91
|
+
- Testar migration com `./mvnw liquibase:updateSQL` antes de rodar
|
|
92
|
+
- Registrar no `master.xml`:
|
|
93
|
+
```xml
|
|
94
|
+
<include file="config/liquibase/changelog/20260327120000_added_column_priority.xml" relativeToChangelogFile="false"/>
|
|
95
|
+
```
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: jhipster-security
|
|
3
|
+
description: Configurar segurança no JHipster com JWT, OAuth2, roles e permissões. Use quando precisar implementar autenticação, autorização, roles, ou proteger endpoints.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# JHipster Security
|
|
7
|
+
|
|
8
|
+
## JWT (padrão monorepo)
|
|
9
|
+
|
|
10
|
+
O JHipster gera JWT automaticamente. Customizar:
|
|
11
|
+
|
|
12
|
+
### Proteger endpoints por role
|
|
13
|
+
|
|
14
|
+
```java
|
|
15
|
+
@Configuration
|
|
16
|
+
public class SecurityConfiguration {
|
|
17
|
+
|
|
18
|
+
@Bean
|
|
19
|
+
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
|
20
|
+
http
|
|
21
|
+
.authorizeHttpRequests(auth -> auth
|
|
22
|
+
.requestMatchers("/api/public/**").permitAll()
|
|
23
|
+
.requestMatchers("/api/admin/**").hasAuthority(AuthoritiesConstants.ADMIN)
|
|
24
|
+
.requestMatchers("/api/demands/**").hasAnyAuthority(
|
|
25
|
+
AuthoritiesConstants.USER, AuthoritiesConstants.ADMIN)
|
|
26
|
+
.requestMatchers("/api/agents/**").hasAuthority("ROLE_AGENT")
|
|
27
|
+
.requestMatchers("/api/**").authenticated()
|
|
28
|
+
);
|
|
29
|
+
return http.build();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Criar roles customizadas
|
|
35
|
+
|
|
36
|
+
```java
|
|
37
|
+
public final class AuthoritiesConstants {
|
|
38
|
+
public static final String ADMIN = "ROLE_ADMIN";
|
|
39
|
+
public static final String USER = "ROLE_USER";
|
|
40
|
+
public static final String AGENT = "ROLE_AGENT"; // Agentes AI
|
|
41
|
+
public static final String TECH_LEAD = "ROLE_TECH_LEAD"; // Aprovador de merge
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Segurança no método
|
|
46
|
+
|
|
47
|
+
```java
|
|
48
|
+
@Service
|
|
49
|
+
public class DemandServiceImpl implements DemandService {
|
|
50
|
+
|
|
51
|
+
@PreAuthorize("hasAuthority('ROLE_TECH_LEAD')")
|
|
52
|
+
public void approveMerge(Long demandId) {
|
|
53
|
+
// Somente tech lead pode aprovar merge
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@PreAuthorize("#login == authentication.name or hasAuthority('ROLE_ADMIN')")
|
|
57
|
+
public DemandDTO findByUser(String login) {
|
|
58
|
+
// Usuário vê só suas demands, admin vê todas
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## OAuth2 + Keycloak (recomendado para microservices)
|
|
64
|
+
|
|
65
|
+
```yaml
|
|
66
|
+
# application.yml
|
|
67
|
+
spring:
|
|
68
|
+
security:
|
|
69
|
+
oauth2:
|
|
70
|
+
client:
|
|
71
|
+
provider:
|
|
72
|
+
oidc:
|
|
73
|
+
issuer-uri: http://keycloak:9080/realms/jhipster
|
|
74
|
+
registration:
|
|
75
|
+
oidc:
|
|
76
|
+
client-id: web_app
|
|
77
|
+
client-secret: web_app
|
|
78
|
+
scope: openid,profile,email
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Checklist de segurança
|
|
82
|
+
|
|
83
|
+
- [ ] Rate limiting nos endpoints públicos
|
|
84
|
+
- [ ] CORS configurado corretamente (não `*` em prod)
|
|
85
|
+
- [ ] CSRF habilitado para browser, desabilitado para API
|
|
86
|
+
- [ ] Secrets em variáveis de ambiente, nunca no código
|
|
87
|
+
- [ ] Senhas com BCrypt (padrão JHipster)
|
|
88
|
+
- [ ] Audit trail para operações críticas
|
|
89
|
+
- [ ] HTTPS em produção
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: jhipster-spring
|
|
3
|
+
description: Desenvolver backend Spring Boot no JHipster com services, repositories, DTOs, MapStruct e Spring Security. Use quando precisar criar endpoints, services, ou customizar o backend JHipster.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# JHipster Spring Boot
|
|
7
|
+
|
|
8
|
+
## Camadas no JHipster
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
Controller (web/rest/) → Service (service/) → Repository (repository/) → Entity (domain/)
|
|
12
|
+
↕
|
|
13
|
+
DTO + Mapper (service/dto/ + service/mapper/)
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Controller — REST API
|
|
17
|
+
|
|
18
|
+
```java
|
|
19
|
+
@RestController
|
|
20
|
+
@RequestMapping("/api/v1/demands")
|
|
21
|
+
public class DemandResource {
|
|
22
|
+
|
|
23
|
+
private final DemandService demandService;
|
|
24
|
+
|
|
25
|
+
public DemandResource(DemandService demandService) {
|
|
26
|
+
this.demandService = demandService;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
@GetMapping
|
|
30
|
+
public ResponseEntity<List<DemandDTO>> getAllDemands(
|
|
31
|
+
@RequestParam(required = false) DemandStatus status,
|
|
32
|
+
@org.springdoc.core.annotations.ParameterObject Pageable pageable) {
|
|
33
|
+
Page<DemandDTO> page = demandService.findAll(status, pageable);
|
|
34
|
+
return ResponseEntity.ok()
|
|
35
|
+
.headers(PaginationUtil.generatePaginationHttpHeaders(
|
|
36
|
+
ServletUriComponentsBuilder.fromCurrentRequest(), page))
|
|
37
|
+
.body(page.getContent());
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@PostMapping
|
|
41
|
+
@ResponseStatus(HttpStatus.CREATED)
|
|
42
|
+
public DemandDTO createDemand(@Valid @RequestBody CreateDemandDTO dto) {
|
|
43
|
+
return demandService.create(dto);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
@GetMapping("/{id}")
|
|
47
|
+
public ResponseEntity<DemandDTO> getDemand(@PathVariable Long id) {
|
|
48
|
+
return ResponseUtil.wrapOrNotFound(demandService.findOne(id));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
@PutMapping("/{id}")
|
|
52
|
+
public DemandDTO updateDemand(@PathVariable Long id, @Valid @RequestBody UpdateDemandDTO dto) {
|
|
53
|
+
return demandService.update(id, dto);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@DeleteMapping("/{id}")
|
|
57
|
+
@ResponseStatus(HttpStatus.NO_CONTENT)
|
|
58
|
+
public void deleteDemand(@PathVariable Long id) {
|
|
59
|
+
demandService.delete(id);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Service — Lógica de aplicação
|
|
65
|
+
|
|
66
|
+
```java
|
|
67
|
+
@Service
|
|
68
|
+
@Transactional
|
|
69
|
+
public class DemandServiceImpl implements DemandService {
|
|
70
|
+
|
|
71
|
+
private final DemandRepository demandRepository;
|
|
72
|
+
private final DemandMapper demandMapper;
|
|
73
|
+
|
|
74
|
+
public DemandServiceImpl(DemandRepository demandRepository, DemandMapper demandMapper) {
|
|
75
|
+
this.demandRepository = demandRepository;
|
|
76
|
+
this.demandMapper = demandMapper;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
@Override
|
|
80
|
+
public DemandDTO create(CreateDemandDTO dto) {
|
|
81
|
+
Demand demand = new Demand();
|
|
82
|
+
demand.setDescription(dto.description());
|
|
83
|
+
demand.setStatus(DemandStatus.CREATED);
|
|
84
|
+
demand.setPriority(dto.priority());
|
|
85
|
+
demand.setCreatedAt(Instant.now());
|
|
86
|
+
demand = demandRepository.save(demand);
|
|
87
|
+
return demandMapper.toDto(demand);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
@Override
|
|
91
|
+
@Transactional(readOnly = true)
|
|
92
|
+
public Page<DemandDTO> findAll(DemandStatus status, Pageable pageable) {
|
|
93
|
+
if (status != null) {
|
|
94
|
+
return demandRepository.findByStatus(status, pageable).map(demandMapper::toDto);
|
|
95
|
+
}
|
|
96
|
+
return demandRepository.findAll(pageable).map(demandMapper::toDto);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## DTO — Records (Java 21)
|
|
102
|
+
|
|
103
|
+
```java
|
|
104
|
+
public record CreateDemandDTO(
|
|
105
|
+
@NotBlank @Size(min = 10) String description,
|
|
106
|
+
@NotNull Priority priority
|
|
107
|
+
) {}
|
|
108
|
+
|
|
109
|
+
public record DemandDTO(
|
|
110
|
+
Long id,
|
|
111
|
+
String description,
|
|
112
|
+
DemandStatus status,
|
|
113
|
+
Priority priority,
|
|
114
|
+
Instant createdAt,
|
|
115
|
+
List<TaskDTO> tasks
|
|
116
|
+
) {}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## MapStruct Mapper
|
|
120
|
+
|
|
121
|
+
```java
|
|
122
|
+
@Mapper(componentModel = "spring", uses = {TaskMapper.class})
|
|
123
|
+
public interface DemandMapper extends EntityMapper<DemandDTO, Demand> {
|
|
124
|
+
|
|
125
|
+
@Mapping(target = "tasks", source = "tasks")
|
|
126
|
+
DemandDTO toDto(Demand demand);
|
|
127
|
+
|
|
128
|
+
@Mapping(target = "id", ignore = true)
|
|
129
|
+
@Mapping(target = "tasks", ignore = true)
|
|
130
|
+
Demand toEntity(CreateDemandDTO dto);
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Repository — Spring Data JPA
|
|
135
|
+
|
|
136
|
+
```java
|
|
137
|
+
@Repository
|
|
138
|
+
public interface DemandRepository extends JpaRepository<Demand, Long>, JpaSpecificationExecutor<Demand> {
|
|
139
|
+
|
|
140
|
+
Page<Demand> findByStatus(DemandStatus status, Pageable pageable);
|
|
141
|
+
|
|
142
|
+
@Query("SELECT d FROM Demand d LEFT JOIN FETCH d.tasks WHERE d.id = :id")
|
|
143
|
+
Optional<Demand> findByIdWithTasks(@Param("id") Long id);
|
|
144
|
+
|
|
145
|
+
long countByStatus(DemandStatus status);
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Regras
|
|
150
|
+
|
|
151
|
+
- Constructor injection sempre (nunca `@Autowired` em campo)
|
|
152
|
+
- `@Transactional(readOnly = true)` em queries
|
|
153
|
+
- `@Transactional` apenas no Service, nunca no Controller
|
|
154
|
+
- DTOs na API, Entities no domínio — nunca misturar
|
|
155
|
+
- Validação com Bean Validation (`@Valid`) no Controller
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: testing-strategy
|
|
3
|
+
description: Implementar estratégia de testes com unitários, integração e e2e usando Pytest ou JUnit. Use quando for escrever testes, definir estratégia de testes, ou melhorar cobertura.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Estratégia de Testes
|
|
7
|
+
|
|
8
|
+
## Pirâmide
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
/ E2E \ Poucos, lentos, caros
|
|
12
|
+
/ Integr. \ Moderados
|
|
13
|
+
/ Unitários \ Muitos, rápidos, baratos
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Testes Unitários — Domínio
|
|
17
|
+
|
|
18
|
+
Testar regras de negócio sem infraestrutura.
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
# tests/domain/test_demand.py
|
|
22
|
+
class TestDemand:
|
|
23
|
+
def test_should_decompose_new_demand(self):
|
|
24
|
+
demand = Demand(id=DemandId.generate(), description="Criar CRUD")
|
|
25
|
+
planner = FakePlanner(tasks=[Task(...), Task(...)])
|
|
26
|
+
|
|
27
|
+
tasks = demand.decompose(planner)
|
|
28
|
+
|
|
29
|
+
assert len(tasks) == 2
|
|
30
|
+
assert demand.status == DemandStatus.PLANNED
|
|
31
|
+
|
|
32
|
+
def test_should_reject_decompose_if_already_planned(self):
|
|
33
|
+
demand = Demand(id=DemandId.generate(), description="Criar CRUD")
|
|
34
|
+
demand.decompose(FakePlanner(tasks=[Task(...)]))
|
|
35
|
+
|
|
36
|
+
with pytest.raises(DemandAlreadyDecomposedException):
|
|
37
|
+
demand.decompose(FakePlanner(tasks=[]))
|
|
38
|
+
|
|
39
|
+
def test_should_not_allow_more_than_20_tasks(self):
|
|
40
|
+
demand = Demand(id=DemandId.generate(), description="Mega projeto")
|
|
41
|
+
for i in range(20):
|
|
42
|
+
demand.add_task(Task(...))
|
|
43
|
+
|
|
44
|
+
with pytest.raises(TooManyTasksException):
|
|
45
|
+
demand.add_task(Task(...))
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Testes Unitários — Value Objects
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
class TestComplianceScore:
|
|
52
|
+
def test_passing_score(self):
|
|
53
|
+
score = ComplianceScore(85.0)
|
|
54
|
+
assert score.is_passing() is True
|
|
55
|
+
|
|
56
|
+
def test_failing_score(self):
|
|
57
|
+
score = ComplianceScore(60.0)
|
|
58
|
+
assert score.is_passing() is False
|
|
59
|
+
|
|
60
|
+
def test_invalid_score_raises(self):
|
|
61
|
+
with pytest.raises(ValueError):
|
|
62
|
+
ComplianceScore(150.0)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Testes de Integração — Repositórios
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
# tests/infrastructure/test_pg_demand_repository.py
|
|
69
|
+
@pytest.fixture
|
|
70
|
+
def db_session():
|
|
71
|
+
engine = create_engine(TEST_DATABASE_URL)
|
|
72
|
+
with Session(engine) as session:
|
|
73
|
+
yield session
|
|
74
|
+
session.rollback()
|
|
75
|
+
|
|
76
|
+
class TestPgDemandRepository:
|
|
77
|
+
def test_should_save_and_find_demand(self, db_session):
|
|
78
|
+
repo = PgDemandRepository(db_session)
|
|
79
|
+
demand = Demand(id=DemandId.generate(), description="Test")
|
|
80
|
+
|
|
81
|
+
repo.save(demand)
|
|
82
|
+
found = repo.find_by_id(demand.id)
|
|
83
|
+
|
|
84
|
+
assert found.description == "Test"
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Naming
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
test_should_<resultado>_when_<condição>
|
|
91
|
+
|
|
92
|
+
test_should_return_error_when_email_is_invalid
|
|
93
|
+
test_should_decompose_demand_when_status_is_created
|
|
94
|
+
test_should_reject_merge_when_conflicts_exist
|
|
95
|
+
```
|