@polymorphism-tech/morph-spec 2.2.0 → 2.4.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/CLAUDE.md +314 -1673
- package/LICENSE +72 -72
- package/README.md +515 -516
- package/bin/detect-agents.js +225 -225
- package/bin/morph-spec.js +358 -173
- package/bin/render-template.js +302 -302
- package/bin/semantic-detect-agents.js +246 -246
- package/bin/task-manager.js +429 -0
- package/bin/validate-agents-skills.js +251 -251
- package/bin/validate-agents.js +69 -69
- package/bin/validate-phase.js +263 -263
- package/bin/validate.js +369 -0
- package/content/.azure/README.md +293 -293
- package/content/.azure/docs/azure-devops-setup.md +454 -454
- package/content/.azure/docs/branch-strategy.md +398 -398
- package/content/.azure/docs/local-development.md +515 -515
- package/content/.azure/pipelines/pipeline-variables.yml +34 -34
- package/content/.azure/pipelines/prod-pipeline.yml +319 -319
- package/content/.azure/pipelines/staging-pipeline.yml +234 -234
- package/content/.azure/pipelines/templates/build-dotnet.yml +75 -75
- package/content/.azure/pipelines/templates/deploy-app-service.yml +94 -94
- package/content/.azure/pipelines/templates/deploy-container-app.yml +120 -120
- package/content/.azure/pipelines/templates/infra-deploy.yml +90 -90
- package/content/.claude/commands/morph-apply.md +221 -158
- package/content/.claude/commands/morph-archive.md +79 -79
- package/content/.claude/commands/morph-infra.md +209 -209
- package/content/.claude/commands/morph-preflight.md +227 -0
- package/content/.claude/commands/morph-proposal.md +122 -101
- package/content/.claude/commands/morph-status.md +86 -86
- package/content/.claude/commands/morph-troubleshoot.md +122 -0
- package/content/.claude/settings.local.json +15 -15
- package/content/.claude/skills/checklists/code-review.md +226 -0
- package/content/.claude/skills/checklists/morph-checklist.md +117 -0
- package/content/.claude/skills/checklists/simulation-checklist.md +77 -0
- package/content/.claude/skills/infra/bicep-architect.md +126 -419
- package/content/.claude/skills/infra/container-specialist.md +131 -437
- package/content/.claude/skills/infra/devops-engineer.md +119 -405
- package/content/.claude/skills/integrations/asaas-financial.md +130 -333
- package/content/.claude/skills/integrations/azure-identity.md +142 -309
- package/content/.claude/skills/integrations/clerk-auth.md +108 -290
- package/content/.claude/skills/integrations/resend-email.md +119 -0
- package/content/.claude/skills/specialists/ai-system-architect.md +192 -604
- package/content/.claude/skills/specialists/azure-architect.md +142 -142
- package/content/.claude/skills/specialists/code-analyzer.md +235 -0
- package/content/.claude/skills/specialists/dotnet-senior.md +287 -0
- package/content/.claude/skills/specialists/ef-modeler.md +113 -200
- package/content/.claude/skills/specialists/hangfire-orchestrator.md +126 -245
- package/content/.claude/skills/specialists/ms-agent-expert.md +109 -263
- package/content/.claude/skills/specialists/po-pm-advisor.md +197 -197
- package/content/.claude/skills/specialists/standards-architect.md +156 -78
- package/content/.claude/skills/specialists/testing-specialist.md +126 -0
- package/content/.claude/skills/specialists/ui-ux-designer.md +191 -1060
- package/content/.claude/skills/stacks/dotnet-blazor.md +210 -588
- package/content/.claude/skills/stacks/dotnet-nextjs.md +154 -402
- package/content/.claude/skills/workflows/morph-replicate.md +213 -0
- package/content/.claude/{commands/morph-clarify.md → skills/workflows/phase-clarify.md} +5 -58
- package/content/.claude/{commands/morph-design.md → skills/workflows/phase-design.md} +16 -86
- package/content/.claude/{commands/morph-setup.md → skills/workflows/phase-setup.md} +9 -17
- package/content/.claude/skills/workflows/phase-tasks.md +164 -0
- package/content/.claude/{commands/morph-uiux.md → skills/workflows/phase-uiux.md} +15 -88
- package/content/.morph/.morphversion +5 -5
- package/content/.morph/archive/.gitkeep +25 -25
- package/content/.morph/config/agents.json +378 -242
- package/content/.morph/config/config.template.json +89 -108
- package/content/.morph/docs/STORY-DRIVEN-DEVELOPMENT.md +392 -392
- package/content/.morph/docs/workflows/design-impl.md +37 -0
- package/content/.morph/docs/workflows/fast-track.md +29 -0
- package/content/.morph/docs/workflows/full-morph.md +76 -0
- package/content/.morph/docs/workflows/standard.md +44 -0
- package/content/.morph/docs/workflows/ui-refresh.md +39 -0
- package/content/.morph/examples/api-nextjs/README.md +241 -241
- package/content/.morph/examples/api-nextjs/contracts.ts +307 -307
- package/content/.morph/examples/api-nextjs/spec.md +399 -399
- package/content/.morph/examples/api-nextjs/tasks.md +168 -168
- package/content/.morph/examples/micro-saas/README.md +125 -125
- package/content/.morph/examples/micro-saas/contracts.cs +358 -358
- package/content/.morph/examples/micro-saas/decisions.md +246 -246
- package/content/.morph/examples/micro-saas/spec.md +236 -236
- package/content/.morph/examples/micro-saas/tasks.md +150 -150
- package/content/.morph/examples/multi-agent/README.md +309 -309
- package/content/.morph/examples/multi-agent/contracts.cs +433 -433
- package/content/.morph/examples/multi-agent/spec.md +479 -479
- package/content/.morph/examples/multi-agent/tasks.md +185 -185
- package/content/.morph/examples/scheduled-reports/decisions.md +158 -0
- package/content/.morph/examples/scheduled-reports/proposal.md +95 -0
- package/content/.morph/examples/scheduled-reports/spec.md +267 -0
- package/content/.morph/examples/state-v3.json +188 -0
- package/content/.morph/features/.gitkeep +25 -25
- package/content/.morph/hooks/README.md +190 -239
- package/content/.morph/hooks/pre-commit-agents.sh +24 -24
- package/content/.morph/hooks/pre-commit-all.sh +48 -48
- package/content/.morph/hooks/pre-commit-specs.sh +49 -49
- package/content/.morph/hooks/pre-commit-tests.sh +60 -60
- package/content/.morph/project.md +160 -160
- package/content/.morph/schemas/agent.schema.json +296 -296
- package/content/.morph/schemas/tasks.schema.json +220 -0
- package/content/.morph/specs/.gitkeep +20 -20
- package/content/.morph/standards/agent-framework-blazor-ui.md +359 -0
- package/content/.morph/standards/agent-framework-production.md +410 -0
- package/content/.morph/standards/agent-framework-setup.md +413 -453
- package/content/.morph/standards/agent-framework-workflows.md +349 -0
- package/content/.morph/standards/architecture.md +325 -325
- package/content/.morph/standards/azure.md +605 -379
- package/content/.morph/standards/coding.md +377 -377
- package/content/.morph/standards/dotnet10-migration.md +520 -494
- package/content/.morph/standards/fluent-ui-setup.md +590 -590
- package/content/.morph/standards/migration-guide.md +514 -514
- package/content/.morph/standards/passkeys-auth.md +423 -423
- package/content/.morph/standards/vector-search-rag.md +536 -536
- package/content/.morph/state.json +17 -17
- package/content/.morph/templates/FluentDesignTheme.cs +149 -149
- package/content/.morph/templates/MudTheme.cs +281 -281
- package/content/.morph/templates/agent.cs +163 -172
- package/content/.morph/templates/clarify-questions.md +159 -0
- package/content/.morph/templates/component.razor +239 -239
- package/content/.morph/templates/contracts/Commands.cs +74 -0
- package/content/.morph/templates/contracts/Entities.cs +25 -0
- package/content/.morph/templates/contracts/Queries.cs +74 -0
- package/content/.morph/templates/contracts/README.md +74 -0
- package/content/.morph/templates/contracts.cs +217 -217
- package/content/.morph/templates/decisions.md +123 -106
- package/content/.morph/templates/design-system.css +226 -226
- package/content/.morph/templates/infra/.dockerignore.example +89 -89
- package/content/.morph/templates/infra/Dockerfile.example +82 -82
- package/content/.morph/templates/infra/README.md +286 -286
- package/content/.morph/templates/infra/app-insights.bicep +63 -63
- package/content/.morph/templates/infra/app-service.bicep +164 -164
- package/content/.morph/templates/infra/container-app-env.bicep +49 -49
- package/content/.morph/templates/infra/container-app.bicep +156 -156
- package/content/.morph/templates/infra/deploy-checklist.md +426 -0
- package/content/.morph/templates/infra/deploy.ps1 +229 -229
- package/content/.morph/templates/infra/deploy.sh +208 -208
- package/content/.morph/templates/infra/key-vault.bicep +91 -91
- package/content/.morph/templates/infra/main.bicep +189 -189
- package/content/.morph/templates/infra/parameters.dev.json +29 -29
- package/content/.morph/templates/infra/parameters.prod.json +29 -29
- package/content/.morph/templates/infra/parameters.staging.json +29 -29
- package/content/.morph/templates/infra/sql-database.bicep +103 -103
- package/content/.morph/templates/infra/storage.bicep +106 -106
- package/content/.morph/templates/integrations/asaas-client.cs +387 -387
- package/content/.morph/templates/integrations/asaas-webhook.cs +351 -351
- package/content/.morph/templates/integrations/azure-identity-config.cs +288 -288
- package/content/.morph/templates/integrations/clerk-config.cs +258 -258
- package/content/.morph/templates/job.cs +171 -171
- package/content/.morph/templates/migration.cs +83 -83
- package/content/.morph/templates/proposal.md +141 -155
- package/content/.morph/templates/recap.md +94 -105
- package/content/.morph/templates/repository.cs +141 -141
- package/content/.morph/templates/saas/subscription.cs +347 -347
- package/content/.morph/templates/saas/tenant.cs +338 -338
- package/content/.morph/templates/service.cs +139 -139
- package/content/.morph/templates/simulation.md +353 -0
- package/content/.morph/templates/spec.md +149 -148
- package/content/.morph/templates/sprint-status.yaml +68 -68
- package/content/.morph/templates/state.template.json +222 -222
- package/content/.morph/templates/story.md +143 -143
- package/content/.morph/templates/tasks.md +257 -235
- package/content/.morph/templates/test.cs +239 -239
- package/content/.morph/templates/ui-components.md +362 -276
- package/content/.morph/templates/ui-design-system.md +286 -286
- package/content/.morph/templates/ui-flows.md +336 -336
- package/content/.morph/templates/ui-mockups.md +133 -133
- package/content/.morph/test-infra/example.bicep +59 -59
- package/content/CLAUDE.md +150 -442
- package/content/README.md +79 -79
- package/detectors/config-detector.js +223 -223
- package/detectors/conversation-analyzer.js +163 -163
- package/detectors/index.js +84 -84
- package/detectors/standards-generator.js +275 -275
- package/detectors/structure-detector.js +245 -250
- package/docs/README.md +144 -149
- package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg +977 -977
- package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg +1048 -1048
- package/docs/api/scripts/collapse.js +38 -38
- package/docs/api/scripts/commonNav.js +28 -28
- package/docs/api/scripts/linenumber.js +25 -25
- package/docs/api/scripts/nav.js +12 -12
- package/docs/api/scripts/polyfill.js +3 -3
- package/docs/api/scripts/prettify/Apache-License-2.0.txt +202 -202
- package/docs/api/scripts/prettify/lang-css.js +2 -2
- package/docs/api/scripts/prettify/prettify.js +28 -28
- package/docs/api/scripts/search.js +98 -98
- package/docs/api/styles/jsdoc.css +776 -776
- package/docs/api/styles/prettify.css +80 -80
- package/docs/examples.md +328 -328
- package/docs/getting-started.md +301 -302
- package/docs/installation.md +361 -361
- package/docs/templates.md +418 -418
- package/docs/validation-checklist.md +265 -266
- package/package.json +80 -80
- package/scripts/postinstall.js +132 -132
- package/src/commands/advance-phase.js +183 -0
- package/src/commands/analyze-blazor-concurrency.js +193 -0
- package/src/commands/create-story.js +351 -351
- package/src/commands/detect-agents.js +139 -0
- package/src/commands/detect.js +104 -104
- package/src/commands/doctor.js +356 -280
- package/src/commands/generate.js +149 -149
- package/src/commands/init.js +258 -245
- package/src/commands/lint-fluent.js +352 -0
- package/src/commands/rollback-phase.js +185 -0
- package/src/commands/session-summary.js +291 -0
- package/src/commands/shard-spec.js +224 -224
- package/src/commands/sprint-status.js +250 -250
- package/src/commands/state.js +333 -333
- package/src/commands/sync.js +167 -167
- package/src/commands/task.js +78 -0
- package/src/commands/troubleshoot.js +222 -0
- package/src/commands/update.js +192 -159
- package/src/commands/validate-blazor-state.js +210 -0
- package/src/commands/validate-blazor.js +156 -0
- package/src/commands/validate-css.js +84 -0
- package/src/commands/validate-phase.js +221 -0
- package/src/lib/blazor-concurrency-analyzer.js +288 -0
- package/src/lib/blazor-state-validator.js +291 -0
- package/src/lib/blazor-validator.js +374 -0
- package/src/lib/complexity-analyzer.js +441 -292
- package/src/lib/continuous-validator.js +421 -0
- package/src/lib/css-validator.js +352 -0
- package/src/lib/decision-constraint-loader.js +109 -0
- package/src/lib/design-system-generator.js +298 -298
- package/src/lib/learning-system.js +520 -0
- package/src/lib/mockup-generator.js +366 -0
- package/src/lib/recap-generator.js +205 -0
- package/src/lib/state-manager.js +397 -340
- package/src/lib/troubleshoot-grep.js +194 -0
- package/src/lib/troubleshoot-index.js +144 -0
- package/src/lib/ui-detector.js +350 -0
- package/src/lib/validation-runner.js +231 -0
- package/src/lib/validators/architecture-validator.js +387 -0
- package/src/lib/validators/contract-compliance-validator.js +273 -0
- package/src/lib/validators/package-validator.js +360 -0
- package/src/lib/validators/ui-contrast-validator.js +422 -0
- package/src/utils/file-copier.js +179 -139
- package/src/utils/logger.js +32 -32
- package/src/utils/version-checker.js +175 -175
- package/content/.claude/commands/morph-costs.md +0 -206
- package/content/.claude/commands/morph-tasks.md +0 -319
- package/content/.claude/skills/specialists/cost-guardian.md +0 -110
- package/content/.claude/skills/stacks/shopify.md +0 -445
- package/content/.morph/config/azure-pricing.json +0 -70
- package/content/.morph/config/azure-pricing.schema.json +0 -50
- package/content/.morph/hooks/pre-commit-costs.sh +0 -91
- package/docs/api/cost-calculator.js.html +0 -513
- package/docs/api/design-system-generator.js.html +0 -382
- package/docs/api/global.html +0 -5263
- package/docs/api/index.html +0 -96
- package/docs/api/state-manager.js.html +0 -423
- package/src/commands/cost.js +0 -181
- package/src/commands/update-pricing.js +0 -206
- package/src/lib/cost-calculator.js +0 -429
|
@@ -1,377 +1,377 @@
|
|
|
1
|
-
# Padrões de Código - MORPH Framework
|
|
2
|
-
|
|
3
|
-
## 📝 Nomenclatura
|
|
4
|
-
|
|
5
|
-
### Classes e Interfaces
|
|
6
|
-
```csharp
|
|
7
|
-
// Interfaces: sempre com prefixo I
|
|
8
|
-
public interface IReportService { }
|
|
9
|
-
public interface IReportRepository { }
|
|
10
|
-
|
|
11
|
-
// Classes: PascalCase, sufixo indica tipo
|
|
12
|
-
public class ReportService : IReportService { }
|
|
13
|
-
public class ReportRepository : IReportRepository { }
|
|
14
|
-
public class ReportController : ControllerBase { }
|
|
15
|
-
public class ReportGeneratorJob { } // Hangfire job
|
|
16
|
-
public class ReportAnalyzerAgent { } // AI Agent
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
### Métodos
|
|
20
|
-
```csharp
|
|
21
|
-
// Async sempre com sufixo Async
|
|
22
|
-
public async Task<Report> GetByIdAsync(int id) { }
|
|
23
|
-
public async Task CreateAsync(Report report) { }
|
|
24
|
-
|
|
25
|
-
// Métodos síncronos sem sufixo
|
|
26
|
-
public Report GetById(int id) { }
|
|
27
|
-
public void Validate(Report report) { }
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
### Variáveis e Parâmetros
|
|
31
|
-
```csharp
|
|
32
|
-
// camelCase para variáveis locais e parâmetros
|
|
33
|
-
public async Task ProcessAsync(string reportId, CancellationToken cancellationToken)
|
|
34
|
-
{
|
|
35
|
-
var report = await _repository.GetByIdAsync(reportId);
|
|
36
|
-
var analysisResult = await _analyzer.AnalyzeAsync(report);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// _camelCase para campos privados
|
|
40
|
-
private readonly IReportRepository _repository;
|
|
41
|
-
private readonly ILogger<ReportService> _logger;
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
---
|
|
45
|
-
|
|
46
|
-
## 📁 Estrutura de Projeto
|
|
47
|
-
|
|
48
|
-
```
|
|
49
|
-
src/
|
|
50
|
-
├── MyProject.Web/ # Blazor Server
|
|
51
|
-
│ ├── Components/
|
|
52
|
-
│ │ ├── Pages/ # Páginas (rotas)
|
|
53
|
-
│ │ ├── Shared/ # Componentes compartilhados
|
|
54
|
-
│ │ └── Layout/ # Layouts
|
|
55
|
-
│ └── Program.cs
|
|
56
|
-
│
|
|
57
|
-
├── MyProject.Application/ # Casos de uso
|
|
58
|
-
│ ├── Features/
|
|
59
|
-
│ │ └── Reports/
|
|
60
|
-
│ │ ├── Commands/
|
|
61
|
-
│ │ ├── Queries/
|
|
62
|
-
│ │ └── Services/
|
|
63
|
-
│ └── Common/
|
|
64
|
-
│
|
|
65
|
-
├── MyProject.Domain/ # Entidades e regras
|
|
66
|
-
│ ├── Entities/
|
|
67
|
-
│ ├── ValueObjects/
|
|
68
|
-
│ └── Enums/
|
|
69
|
-
│
|
|
70
|
-
├── MyProject.Infrastructure/ # Implementações
|
|
71
|
-
│ ├── Data/
|
|
72
|
-
│ │ ├── Configurations/
|
|
73
|
-
│ │ ├── Migrations/
|
|
74
|
-
│ │ └── AppDbContext.cs
|
|
75
|
-
│ └── Services/
|
|
76
|
-
│
|
|
77
|
-
└── MyProject.Agents/ # MS Agent Framework
|
|
78
|
-
└── ReportAnalyzer/
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
---
|
|
82
|
-
|
|
83
|
-
## 💉 Dependency Injection
|
|
84
|
-
|
|
85
|
-
```csharp
|
|
86
|
-
// Usar primary constructor (C# 12+)
|
|
87
|
-
public class ReportService(
|
|
88
|
-
IReportRepository repository,
|
|
89
|
-
ILogger<ReportService> logger) : IReportService
|
|
90
|
-
{
|
|
91
|
-
public async Task<Report> GetByIdAsync(int id)
|
|
92
|
-
{
|
|
93
|
-
logger.LogInformation("Getting report {ReportId}", id);
|
|
94
|
-
return await repository.GetByIdAsync(id);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
---
|
|
100
|
-
|
|
101
|
-
## 🗄️ Entity Framework Core
|
|
102
|
-
|
|
103
|
-
### Entidades
|
|
104
|
-
```csharp
|
|
105
|
-
public class ReportSchedule
|
|
106
|
-
{
|
|
107
|
-
public int Id { get; private set; }
|
|
108
|
-
public string Name { get; private set; } = string.Empty;
|
|
109
|
-
public ReportType ReportType { get; private set; }
|
|
110
|
-
public string CronExpression { get; private set; } = string.Empty;
|
|
111
|
-
public List<string> Recipients { get; private set; } = [];
|
|
112
|
-
public bool IsActive { get; private set; }
|
|
113
|
-
public DateTime CreatedAt { get; private set; }
|
|
114
|
-
|
|
115
|
-
// Factory method para criação
|
|
116
|
-
public static ReportSchedule Create(string name, ReportType reportType,
|
|
117
|
-
string cronExpression, List<string> recipients)
|
|
118
|
-
{
|
|
119
|
-
return new ReportSchedule
|
|
120
|
-
{
|
|
121
|
-
Name = name,
|
|
122
|
-
ReportType = reportType,
|
|
123
|
-
CronExpression = cronExpression,
|
|
124
|
-
Recipients = recipients,
|
|
125
|
-
IsActive = true,
|
|
126
|
-
CreatedAt = DateTime.UtcNow
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Métodos de domínio
|
|
131
|
-
public void Activate() => IsActive = true;
|
|
132
|
-
public void Deactivate() => IsActive = false;
|
|
133
|
-
}
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
### Configuração Fluent API
|
|
137
|
-
```csharp
|
|
138
|
-
public class ReportScheduleConfiguration : IEntityTypeConfiguration<ReportSchedule>
|
|
139
|
-
{
|
|
140
|
-
public void Configure(EntityTypeBuilder<ReportSchedule> builder)
|
|
141
|
-
{
|
|
142
|
-
builder.ToTable("ReportSchedules");
|
|
143
|
-
builder.HasKey(x => x.Id);
|
|
144
|
-
|
|
145
|
-
builder.Property(x => x.Name)
|
|
146
|
-
.IsRequired()
|
|
147
|
-
.HasMaxLength(200);
|
|
148
|
-
|
|
149
|
-
// JSON column para lista
|
|
150
|
-
builder.Property(x => x.Recipients)
|
|
151
|
-
.HasConversion(
|
|
152
|
-
v => JsonSerializer.Serialize(v, (JsonSerializerOptions?)null),
|
|
153
|
-
v => JsonSerializer.Deserialize<List<string>>(v, (JsonSerializerOptions?)null) ?? [])
|
|
154
|
-
.HasColumnType("nvarchar(max)");
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
---
|
|
160
|
-
|
|
161
|
-
## 🔥 Componentes Blazor
|
|
162
|
-
|
|
163
|
-
```razor
|
|
164
|
-
@page "/reports/schedules"
|
|
165
|
-
@attribute [Authorize(Policy = "CanManageReports")]
|
|
166
|
-
@inject IReportScheduleService ReportService
|
|
167
|
-
@inject ILogger<ReportScheduleList> Logger
|
|
168
|
-
|
|
169
|
-
<PageTitle>Report Schedules</PageTitle>
|
|
170
|
-
|
|
171
|
-
@if (_isLoading)
|
|
172
|
-
{
|
|
173
|
-
<LoadingSpinner />
|
|
174
|
-
}
|
|
175
|
-
else if (_schedules is null || !_schedules.Any())
|
|
176
|
-
{
|
|
177
|
-
<EmptyState Message="No schedules configured" />
|
|
178
|
-
}
|
|
179
|
-
else
|
|
180
|
-
{
|
|
181
|
-
<div class="schedule-list">
|
|
182
|
-
@foreach (var schedule in _schedules)
|
|
183
|
-
{
|
|
184
|
-
<ScheduleCard Schedule="schedule" OnToggle="HandleToggleAsync" />
|
|
185
|
-
}
|
|
186
|
-
</div>
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
@code {
|
|
190
|
-
private List<ReportScheduleDto>? _schedules;
|
|
191
|
-
private bool _isLoading = true;
|
|
192
|
-
|
|
193
|
-
protected override async Task OnInitializedAsync()
|
|
194
|
-
{
|
|
195
|
-
try
|
|
196
|
-
{
|
|
197
|
-
_schedules = await ReportService.GetAllAsync();
|
|
198
|
-
}
|
|
199
|
-
catch (Exception ex)
|
|
200
|
-
{
|
|
201
|
-
Logger.LogError(ex, "Failed to load schedules");
|
|
202
|
-
}
|
|
203
|
-
finally
|
|
204
|
-
{
|
|
205
|
-
_isLoading = false;
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
### Padrões Obrigatórios
|
|
212
|
-
- ✅ Loading state em toda página
|
|
213
|
-
- ✅ Empty state quando lista vazia
|
|
214
|
-
- ✅ Error handling com try/catch
|
|
215
|
-
- ✅ `@inject` para DI (não construtor)
|
|
216
|
-
- ✅ `@attribute [Authorize]` quando necessário
|
|
217
|
-
|
|
218
|
-
---
|
|
219
|
-
|
|
220
|
-
## 🤖 AI Agents - Padrão Obrigatório (.NET 10)
|
|
221
|
-
|
|
222
|
-
> **CRÍTICO:** Use **exclusivamente** Microsoft Agent Framework.
|
|
223
|
-
> Semantic Kernel foi descontinuado no MORPH-SPEC.
|
|
224
|
-
|
|
225
|
-
### Pattern Obrigatório
|
|
226
|
-
|
|
227
|
-
```csharp
|
|
228
|
-
using Microsoft.Agents.AI;
|
|
229
|
-
using Microsoft.Extensions.AI;
|
|
230
|
-
|
|
231
|
-
public class ReportAnalyzerAgent(
|
|
232
|
-
IChatClient chatClient,
|
|
233
|
-
ILogger<ReportAnalyzerAgent> logger) : IReportAnalyzerAgent
|
|
234
|
-
{
|
|
235
|
-
public async Task<AnalysisResult> AnalyzeAsync(
|
|
236
|
-
ReportData reportData,
|
|
237
|
-
CancellationToken cancellationToken = default)
|
|
238
|
-
{
|
|
239
|
-
var agent = chatClient.CreateAgent(
|
|
240
|
-
instructions: """
|
|
241
|
-
Você é um especialista em análise de relatórios.
|
|
242
|
-
Analise os dados fornecidos e retorne em JSON:
|
|
243
|
-
{
|
|
244
|
-
"summary": "resumo",
|
|
245
|
-
"insights": ["top 3 insights"],
|
|
246
|
-
"recommendations": ["recomendações"]
|
|
247
|
-
}
|
|
248
|
-
""",
|
|
249
|
-
name: "ReportAnalyzer"
|
|
250
|
-
);
|
|
251
|
-
|
|
252
|
-
var prompt = $"Analise este relatório:\n{reportData.ToJson()}";
|
|
253
|
-
|
|
254
|
-
try
|
|
255
|
-
{
|
|
256
|
-
var response = await agent.RunAsync(prompt, cancellationToken: cancellationToken);
|
|
257
|
-
return ParseResponse(response.Content);
|
|
258
|
-
}
|
|
259
|
-
catch (Exception ex)
|
|
260
|
-
{
|
|
261
|
-
logger.LogError(ex, "Analysis failed for report {ReportId}", reportData.Id);
|
|
262
|
-
throw new ReportAnalysisException("Failed to analyze", ex);
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
```
|
|
267
|
-
|
|
268
|
-
### Configuração (Program.cs)
|
|
269
|
-
|
|
270
|
-
```csharp
|
|
271
|
-
// Registrar ChatClient
|
|
272
|
-
builder.Services.AddSingleton<IChatClient>(sp =>
|
|
273
|
-
{
|
|
274
|
-
var config = sp.GetRequiredService<IConfiguration>();
|
|
275
|
-
return new ChatClient(
|
|
276
|
-
model: "gpt-4o-mini",
|
|
277
|
-
credential: new ApiKeyCredential(config["AzureOpenAI:ApiKey"]!),
|
|
278
|
-
endpoint: new Uri(config["AzureOpenAI:Endpoint"]!)
|
|
279
|
-
);
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
// Registrar agent
|
|
283
|
-
builder.Services.AddScoped<IReportAnalyzerAgent, ReportAnalyzerAgent>();
|
|
284
|
-
```
|
|
285
|
-
|
|
286
|
-
**Referência:** [Agent Framework Setup](./agent-framework-setup.md)
|
|
287
|
-
|
|
288
|
-
---
|
|
289
|
-
|
|
290
|
-
## ⏰ Hangfire Jobs
|
|
291
|
-
|
|
292
|
-
```csharp
|
|
293
|
-
public class ReportGeneratorJob(
|
|
294
|
-
IReportScheduleService scheduleService,
|
|
295
|
-
IReportAnalyzerAgent analyzer,
|
|
296
|
-
ILogger<ReportGeneratorJob> logger) : IReportGeneratorJob
|
|
297
|
-
{
|
|
298
|
-
[AutomaticRetry(Attempts = 3, DelaysInSeconds = new[] { 60, 300, 900 })]
|
|
299
|
-
[Queue("reports")]
|
|
300
|
-
public async Task ExecuteAsync(int scheduleId, CancellationToken cancellationToken)
|
|
301
|
-
{
|
|
302
|
-
logger.LogInformation("Starting report generation for {ScheduleId}", scheduleId);
|
|
303
|
-
|
|
304
|
-
var schedule = await scheduleService.GetByIdAsync(scheduleId);
|
|
305
|
-
if (schedule is null || !schedule.IsActive)
|
|
306
|
-
{
|
|
307
|
-
logger.LogWarning("Schedule {ScheduleId} not found or inactive", scheduleId);
|
|
308
|
-
return;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
// ... implementação
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
```
|
|
315
|
-
|
|
316
|
-
---
|
|
317
|
-
|
|
318
|
-
## 🧪 Testes
|
|
319
|
-
|
|
320
|
-
### Nomenclatura
|
|
321
|
-
```csharp
|
|
322
|
-
// Classe: {ClasseTestada}Tests
|
|
323
|
-
public class ReportServiceTests
|
|
324
|
-
{
|
|
325
|
-
// Método: {Método}_{Cenário}_{ResultadoEsperado}
|
|
326
|
-
[Fact]
|
|
327
|
-
public async Task GetByIdAsync_WithValidId_ReturnsReport() { }
|
|
328
|
-
|
|
329
|
-
[Fact]
|
|
330
|
-
public async Task GetByIdAsync_WithInvalidId_ThrowsNotFoundException() { }
|
|
331
|
-
}
|
|
332
|
-
```
|
|
333
|
-
|
|
334
|
-
### Estrutura AAA
|
|
335
|
-
```csharp
|
|
336
|
-
[Fact]
|
|
337
|
-
public async Task CreateAsync_WithValidData_CreatesSchedule()
|
|
338
|
-
{
|
|
339
|
-
// Arrange
|
|
340
|
-
var request = new CreateReportScheduleRequest("Daily Sales", ReportType.Sales);
|
|
341
|
-
|
|
342
|
-
// Act
|
|
343
|
-
var result = await _service.CreateAsync(request);
|
|
344
|
-
|
|
345
|
-
// Assert
|
|
346
|
-
Assert.NotNull(result);
|
|
347
|
-
Assert.Equal(request.Name, result.Name);
|
|
348
|
-
}
|
|
349
|
-
```
|
|
350
|
-
|
|
351
|
-
---
|
|
352
|
-
|
|
353
|
-
## 🚫 Anti-Patterns a Evitar
|
|
354
|
-
|
|
355
|
-
```csharp
|
|
356
|
-
// ❌ Service Locator
|
|
357
|
-
var service = serviceProvider.GetService<IReportService>();
|
|
358
|
-
|
|
359
|
-
// ✅ Constructor Injection
|
|
360
|
-
public class MyClass(IReportService reportService) { }
|
|
361
|
-
|
|
362
|
-
// ❌ Async void
|
|
363
|
-
public async void ProcessReport() { }
|
|
364
|
-
|
|
365
|
-
// ✅ Async Task
|
|
366
|
-
public async Task ProcessReportAsync() { }
|
|
367
|
-
|
|
368
|
-
// ❌ Catching generic Exception sem log
|
|
369
|
-
catch (Exception) { }
|
|
370
|
-
|
|
371
|
-
// ✅ Log and handle
|
|
372
|
-
catch (Exception ex)
|
|
373
|
-
{
|
|
374
|
-
_logger.LogError(ex, "Operation failed");
|
|
375
|
-
throw;
|
|
376
|
-
}
|
|
377
|
-
```
|
|
1
|
+
# Padrões de Código - MORPH Framework
|
|
2
|
+
|
|
3
|
+
## 📝 Nomenclatura
|
|
4
|
+
|
|
5
|
+
### Classes e Interfaces
|
|
6
|
+
```csharp
|
|
7
|
+
// Interfaces: sempre com prefixo I
|
|
8
|
+
public interface IReportService { }
|
|
9
|
+
public interface IReportRepository { }
|
|
10
|
+
|
|
11
|
+
// Classes: PascalCase, sufixo indica tipo
|
|
12
|
+
public class ReportService : IReportService { }
|
|
13
|
+
public class ReportRepository : IReportRepository { }
|
|
14
|
+
public class ReportController : ControllerBase { }
|
|
15
|
+
public class ReportGeneratorJob { } // Hangfire job
|
|
16
|
+
public class ReportAnalyzerAgent { } // AI Agent
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### Métodos
|
|
20
|
+
```csharp
|
|
21
|
+
// Async sempre com sufixo Async
|
|
22
|
+
public async Task<Report> GetByIdAsync(int id) { }
|
|
23
|
+
public async Task CreateAsync(Report report) { }
|
|
24
|
+
|
|
25
|
+
// Métodos síncronos sem sufixo
|
|
26
|
+
public Report GetById(int id) { }
|
|
27
|
+
public void Validate(Report report) { }
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Variáveis e Parâmetros
|
|
31
|
+
```csharp
|
|
32
|
+
// camelCase para variáveis locais e parâmetros
|
|
33
|
+
public async Task ProcessAsync(string reportId, CancellationToken cancellationToken)
|
|
34
|
+
{
|
|
35
|
+
var report = await _repository.GetByIdAsync(reportId);
|
|
36
|
+
var analysisResult = await _analyzer.AnalyzeAsync(report);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// _camelCase para campos privados
|
|
40
|
+
private readonly IReportRepository _repository;
|
|
41
|
+
private readonly ILogger<ReportService> _logger;
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## 📁 Estrutura de Projeto
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
src/
|
|
50
|
+
├── MyProject.Web/ # Blazor Server
|
|
51
|
+
│ ├── Components/
|
|
52
|
+
│ │ ├── Pages/ # Páginas (rotas)
|
|
53
|
+
│ │ ├── Shared/ # Componentes compartilhados
|
|
54
|
+
│ │ └── Layout/ # Layouts
|
|
55
|
+
│ └── Program.cs
|
|
56
|
+
│
|
|
57
|
+
├── MyProject.Application/ # Casos de uso
|
|
58
|
+
│ ├── Features/
|
|
59
|
+
│ │ └── Reports/
|
|
60
|
+
│ │ ├── Commands/
|
|
61
|
+
│ │ ├── Queries/
|
|
62
|
+
│ │ └── Services/
|
|
63
|
+
│ └── Common/
|
|
64
|
+
│
|
|
65
|
+
├── MyProject.Domain/ # Entidades e regras
|
|
66
|
+
│ ├── Entities/
|
|
67
|
+
│ ├── ValueObjects/
|
|
68
|
+
│ └── Enums/
|
|
69
|
+
│
|
|
70
|
+
├── MyProject.Infrastructure/ # Implementações
|
|
71
|
+
│ ├── Data/
|
|
72
|
+
│ │ ├── Configurations/
|
|
73
|
+
│ │ ├── Migrations/
|
|
74
|
+
│ │ └── AppDbContext.cs
|
|
75
|
+
│ └── Services/
|
|
76
|
+
│
|
|
77
|
+
└── MyProject.Agents/ # MS Agent Framework
|
|
78
|
+
└── ReportAnalyzer/
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## 💉 Dependency Injection
|
|
84
|
+
|
|
85
|
+
```csharp
|
|
86
|
+
// Usar primary constructor (C# 12+)
|
|
87
|
+
public class ReportService(
|
|
88
|
+
IReportRepository repository,
|
|
89
|
+
ILogger<ReportService> logger) : IReportService
|
|
90
|
+
{
|
|
91
|
+
public async Task<Report> GetByIdAsync(int id)
|
|
92
|
+
{
|
|
93
|
+
logger.LogInformation("Getting report {ReportId}", id);
|
|
94
|
+
return await repository.GetByIdAsync(id);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## 🗄️ Entity Framework Core
|
|
102
|
+
|
|
103
|
+
### Entidades
|
|
104
|
+
```csharp
|
|
105
|
+
public class ReportSchedule
|
|
106
|
+
{
|
|
107
|
+
public int Id { get; private set; }
|
|
108
|
+
public string Name { get; private set; } = string.Empty;
|
|
109
|
+
public ReportType ReportType { get; private set; }
|
|
110
|
+
public string CronExpression { get; private set; } = string.Empty;
|
|
111
|
+
public List<string> Recipients { get; private set; } = [];
|
|
112
|
+
public bool IsActive { get; private set; }
|
|
113
|
+
public DateTime CreatedAt { get; private set; }
|
|
114
|
+
|
|
115
|
+
// Factory method para criação
|
|
116
|
+
public static ReportSchedule Create(string name, ReportType reportType,
|
|
117
|
+
string cronExpression, List<string> recipients)
|
|
118
|
+
{
|
|
119
|
+
return new ReportSchedule
|
|
120
|
+
{
|
|
121
|
+
Name = name,
|
|
122
|
+
ReportType = reportType,
|
|
123
|
+
CronExpression = cronExpression,
|
|
124
|
+
Recipients = recipients,
|
|
125
|
+
IsActive = true,
|
|
126
|
+
CreatedAt = DateTime.UtcNow
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Métodos de domínio
|
|
131
|
+
public void Activate() => IsActive = true;
|
|
132
|
+
public void Deactivate() => IsActive = false;
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Configuração Fluent API
|
|
137
|
+
```csharp
|
|
138
|
+
public class ReportScheduleConfiguration : IEntityTypeConfiguration<ReportSchedule>
|
|
139
|
+
{
|
|
140
|
+
public void Configure(EntityTypeBuilder<ReportSchedule> builder)
|
|
141
|
+
{
|
|
142
|
+
builder.ToTable("ReportSchedules");
|
|
143
|
+
builder.HasKey(x => x.Id);
|
|
144
|
+
|
|
145
|
+
builder.Property(x => x.Name)
|
|
146
|
+
.IsRequired()
|
|
147
|
+
.HasMaxLength(200);
|
|
148
|
+
|
|
149
|
+
// JSON column para lista
|
|
150
|
+
builder.Property(x => x.Recipients)
|
|
151
|
+
.HasConversion(
|
|
152
|
+
v => JsonSerializer.Serialize(v, (JsonSerializerOptions?)null),
|
|
153
|
+
v => JsonSerializer.Deserialize<List<string>>(v, (JsonSerializerOptions?)null) ?? [])
|
|
154
|
+
.HasColumnType("nvarchar(max)");
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## 🔥 Componentes Blazor
|
|
162
|
+
|
|
163
|
+
```razor
|
|
164
|
+
@page "/reports/schedules"
|
|
165
|
+
@attribute [Authorize(Policy = "CanManageReports")]
|
|
166
|
+
@inject IReportScheduleService ReportService
|
|
167
|
+
@inject ILogger<ReportScheduleList> Logger
|
|
168
|
+
|
|
169
|
+
<PageTitle>Report Schedules</PageTitle>
|
|
170
|
+
|
|
171
|
+
@if (_isLoading)
|
|
172
|
+
{
|
|
173
|
+
<LoadingSpinner />
|
|
174
|
+
}
|
|
175
|
+
else if (_schedules is null || !_schedules.Any())
|
|
176
|
+
{
|
|
177
|
+
<EmptyState Message="No schedules configured" />
|
|
178
|
+
}
|
|
179
|
+
else
|
|
180
|
+
{
|
|
181
|
+
<div class="schedule-list">
|
|
182
|
+
@foreach (var schedule in _schedules)
|
|
183
|
+
{
|
|
184
|
+
<ScheduleCard Schedule="schedule" OnToggle="HandleToggleAsync" />
|
|
185
|
+
}
|
|
186
|
+
</div>
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
@code {
|
|
190
|
+
private List<ReportScheduleDto>? _schedules;
|
|
191
|
+
private bool _isLoading = true;
|
|
192
|
+
|
|
193
|
+
protected override async Task OnInitializedAsync()
|
|
194
|
+
{
|
|
195
|
+
try
|
|
196
|
+
{
|
|
197
|
+
_schedules = await ReportService.GetAllAsync();
|
|
198
|
+
}
|
|
199
|
+
catch (Exception ex)
|
|
200
|
+
{
|
|
201
|
+
Logger.LogError(ex, "Failed to load schedules");
|
|
202
|
+
}
|
|
203
|
+
finally
|
|
204
|
+
{
|
|
205
|
+
_isLoading = false;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### Padrões Obrigatórios
|
|
212
|
+
- ✅ Loading state em toda página
|
|
213
|
+
- ✅ Empty state quando lista vazia
|
|
214
|
+
- ✅ Error handling com try/catch
|
|
215
|
+
- ✅ `@inject` para DI (não construtor)
|
|
216
|
+
- ✅ `@attribute [Authorize]` quando necessário
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## 🤖 AI Agents - Padrão Obrigatório (.NET 10)
|
|
221
|
+
|
|
222
|
+
> **CRÍTICO:** Use **exclusivamente** Microsoft Agent Framework.
|
|
223
|
+
> Semantic Kernel foi descontinuado no MORPH-SPEC.
|
|
224
|
+
|
|
225
|
+
### Pattern Obrigatório
|
|
226
|
+
|
|
227
|
+
```csharp
|
|
228
|
+
using Microsoft.Agents.AI;
|
|
229
|
+
using Microsoft.Extensions.AI;
|
|
230
|
+
|
|
231
|
+
public class ReportAnalyzerAgent(
|
|
232
|
+
IChatClient chatClient,
|
|
233
|
+
ILogger<ReportAnalyzerAgent> logger) : IReportAnalyzerAgent
|
|
234
|
+
{
|
|
235
|
+
public async Task<AnalysisResult> AnalyzeAsync(
|
|
236
|
+
ReportData reportData,
|
|
237
|
+
CancellationToken cancellationToken = default)
|
|
238
|
+
{
|
|
239
|
+
var agent = chatClient.CreateAgent(
|
|
240
|
+
instructions: """
|
|
241
|
+
Você é um especialista em análise de relatórios.
|
|
242
|
+
Analise os dados fornecidos e retorne em JSON:
|
|
243
|
+
{
|
|
244
|
+
"summary": "resumo",
|
|
245
|
+
"insights": ["top 3 insights"],
|
|
246
|
+
"recommendations": ["recomendações"]
|
|
247
|
+
}
|
|
248
|
+
""",
|
|
249
|
+
name: "ReportAnalyzer"
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
var prompt = $"Analise este relatório:\n{reportData.ToJson()}";
|
|
253
|
+
|
|
254
|
+
try
|
|
255
|
+
{
|
|
256
|
+
var response = await agent.RunAsync(prompt, cancellationToken: cancellationToken);
|
|
257
|
+
return ParseResponse(response.Content);
|
|
258
|
+
}
|
|
259
|
+
catch (Exception ex)
|
|
260
|
+
{
|
|
261
|
+
logger.LogError(ex, "Analysis failed for report {ReportId}", reportData.Id);
|
|
262
|
+
throw new ReportAnalysisException("Failed to analyze", ex);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### Configuração (Program.cs)
|
|
269
|
+
|
|
270
|
+
```csharp
|
|
271
|
+
// Registrar ChatClient
|
|
272
|
+
builder.Services.AddSingleton<IChatClient>(sp =>
|
|
273
|
+
{
|
|
274
|
+
var config = sp.GetRequiredService<IConfiguration>();
|
|
275
|
+
return new ChatClient(
|
|
276
|
+
model: "gpt-4o-mini",
|
|
277
|
+
credential: new ApiKeyCredential(config["AzureOpenAI:ApiKey"]!),
|
|
278
|
+
endpoint: new Uri(config["AzureOpenAI:Endpoint"]!)
|
|
279
|
+
);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
// Registrar agent
|
|
283
|
+
builder.Services.AddScoped<IReportAnalyzerAgent, ReportAnalyzerAgent>();
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
**Referência:** [Agent Framework Setup](./agent-framework-setup.md)
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## ⏰ Hangfire Jobs
|
|
291
|
+
|
|
292
|
+
```csharp
|
|
293
|
+
public class ReportGeneratorJob(
|
|
294
|
+
IReportScheduleService scheduleService,
|
|
295
|
+
IReportAnalyzerAgent analyzer,
|
|
296
|
+
ILogger<ReportGeneratorJob> logger) : IReportGeneratorJob
|
|
297
|
+
{
|
|
298
|
+
[AutomaticRetry(Attempts = 3, DelaysInSeconds = new[] { 60, 300, 900 })]
|
|
299
|
+
[Queue("reports")]
|
|
300
|
+
public async Task ExecuteAsync(int scheduleId, CancellationToken cancellationToken)
|
|
301
|
+
{
|
|
302
|
+
logger.LogInformation("Starting report generation for {ScheduleId}", scheduleId);
|
|
303
|
+
|
|
304
|
+
var schedule = await scheduleService.GetByIdAsync(scheduleId);
|
|
305
|
+
if (schedule is null || !schedule.IsActive)
|
|
306
|
+
{
|
|
307
|
+
logger.LogWarning("Schedule {ScheduleId} not found or inactive", scheduleId);
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// ... implementação
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
## 🧪 Testes
|
|
319
|
+
|
|
320
|
+
### Nomenclatura
|
|
321
|
+
```csharp
|
|
322
|
+
// Classe: {ClasseTestada}Tests
|
|
323
|
+
public class ReportServiceTests
|
|
324
|
+
{
|
|
325
|
+
// Método: {Método}_{Cenário}_{ResultadoEsperado}
|
|
326
|
+
[Fact]
|
|
327
|
+
public async Task GetByIdAsync_WithValidId_ReturnsReport() { }
|
|
328
|
+
|
|
329
|
+
[Fact]
|
|
330
|
+
public async Task GetByIdAsync_WithInvalidId_ThrowsNotFoundException() { }
|
|
331
|
+
}
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### Estrutura AAA
|
|
335
|
+
```csharp
|
|
336
|
+
[Fact]
|
|
337
|
+
public async Task CreateAsync_WithValidData_CreatesSchedule()
|
|
338
|
+
{
|
|
339
|
+
// Arrange
|
|
340
|
+
var request = new CreateReportScheduleRequest("Daily Sales", ReportType.Sales);
|
|
341
|
+
|
|
342
|
+
// Act
|
|
343
|
+
var result = await _service.CreateAsync(request);
|
|
344
|
+
|
|
345
|
+
// Assert
|
|
346
|
+
Assert.NotNull(result);
|
|
347
|
+
Assert.Equal(request.Name, result.Name);
|
|
348
|
+
}
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
---
|
|
352
|
+
|
|
353
|
+
## 🚫 Anti-Patterns a Evitar
|
|
354
|
+
|
|
355
|
+
```csharp
|
|
356
|
+
// ❌ Service Locator
|
|
357
|
+
var service = serviceProvider.GetService<IReportService>();
|
|
358
|
+
|
|
359
|
+
// ✅ Constructor Injection
|
|
360
|
+
public class MyClass(IReportService reportService) { }
|
|
361
|
+
|
|
362
|
+
// ❌ Async void
|
|
363
|
+
public async void ProcessReport() { }
|
|
364
|
+
|
|
365
|
+
// ✅ Async Task
|
|
366
|
+
public async Task ProcessReportAsync() { }
|
|
367
|
+
|
|
368
|
+
// ❌ Catching generic Exception sem log
|
|
369
|
+
catch (Exception) { }
|
|
370
|
+
|
|
371
|
+
// ✅ Log and handle
|
|
372
|
+
catch (Exception ex)
|
|
373
|
+
{
|
|
374
|
+
_logger.LogError(ex, "Operation failed");
|
|
375
|
+
throw;
|
|
376
|
+
}
|
|
377
|
+
```
|