@polymorphism-tech/morph-spec 8.38.0 → 8.40.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 +19 -8
- package/README.md +2 -2
- package/bin/morph-spec.js +60 -43
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +3 -2
- package/framework/MORPH.md +1 -1
- package/framework/agents.json +36 -25
- package/framework/docs/CLI.md +2 -2
- package/framework/evals/rubrics/code-quality.md +4 -4
- package/framework/evals/rubrics/condensed.md +2 -2
- package/framework/mcp/registry.json +33 -2
- package/framework/rules/infrastructure-standards.md +0 -1
- package/framework/rules/morph-workflow.md +9 -4
- package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
- package/framework/skills/core/morph-apply/SKILL.md +5 -1
- package/framework/skills/core/morph-brainstorming/SKILL.md +1 -1
- package/framework/skills/core/morph-eval/SKILL.md +1 -1
- package/framework/skills/core/morph-hotfix/SKILL.md +1 -1
- package/framework/skills/core/morph-implement/SKILL.md +6 -1
- package/framework/skills/core/morph-media/SKILL.md +1 -1
- package/framework/skills/core/morph-plan/SKILL.md +4 -3
- package/framework/skills/core/morph-proposal/SKILL.md +1 -1
- package/framework/skills/core/morph-review/SKILL.md +1 -1
- package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
- package/framework/skills/core/morph-standards/SKILL.md +1 -1
- package/framework/skills/core/morph-uiux/SKILL.md +6 -5
- package/framework/standards/STANDARDS.json +3 -3
- package/framework/standards/ai-agents/context-providers.md +16 -26
- package/framework/standards/ai-agents/durable-workflows-hitl.md +15 -21
- package/framework/standards/ai-agents/modalities-image-gen.md +21 -25
- package/framework/standards/ai-agents/modalities-speech-to-text.md +4 -15
- package/framework/standards/ai-agents/modalities-text-to-speech.md +2 -11
- package/framework/standards/ai-agents/modalities-vision.md +1 -18
- package/framework/standards/ai-agents/prompt-sources.md +0 -4
- package/framework/standards/architecture/code-comments.md +52 -16
- package/framework/standards/backend/integrations/neon-auth/neon-auth.md +63 -50
- package/framework/standards/frontend/design-system/ai-image-generation.md +5 -5
- package/framework/standards/integration/mcp/mcp-tools.md +48 -117
- package/framework/templates/code/dotnet/abstractions/result.cs +0 -2
- package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +26 -39
- package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +28 -68
- package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +99 -117
- package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +2 -44
- package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +40 -61
- package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +54 -107
- package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +53 -58
- package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +192 -281
- package/framework/templates/code/dotnet/database/migration.cs +49 -83
- package/framework/templates/code/dotnet/database/vector-entity.cs +2 -46
- package/framework/templates/code/dotnet/extensions/result-extensions.cs +0 -2
- package/framework/templates/code/dotnet/feature/validator.cs.hbs +11 -15
- package/framework/templates/code/dotnet/jobs/agent.cs +3 -51
- package/framework/templates/code/dotnet/jobs/job.cs +2 -97
- package/framework/templates/code/dotnet/test.cs +2 -87
- package/framework/templates/code/typescript/contracts.ts +140 -167
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentApi.cs +1 -48
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactory.cs +3 -79
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactoryOptions.cs +1 -70
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpec.cs +1 -47
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpecOptions.cs +2 -99
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/AiKitAssemblyMarker.cs +0 -6
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/ConversationStore.cs +3 -88
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/IConversationStore.cs +1 -41
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/InMemoryConversationStore.cs +0 -21
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/CountingChatClient.cs +1 -25
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalAssertion.cs +2 -63
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheMissException.cs +0 -30
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheOptions.cs +1 -52
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheStamp.cs +3 -78
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalFixture.cs +4 -109
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalMode.cs +0 -22
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalQualityEvaluators.cs +1 -36
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalScenario.cs +2 -57
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalSummary.cs +0 -75
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/OfflineChatClient.cs +2 -59
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/ShortPathResponseCache.cs +4 -111
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaGenerator.cs +0 -34
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaProvider.cs +1 -57
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaArtifact.cs +0 -19
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaDisclosure.cs +2 -74
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaGenerator.cs +2 -33
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaIdempotencyKey.cs +1 -43
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJob.cs +1 -96
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobId.cs +0 -13
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobState.cs +0 -27
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaPricing.cs +0 -33
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaProvenance.cs +0 -31
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaRequest.cs +3 -75
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaResult.cs +0 -42
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaReview.cs +0 -39
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaSeams.cs +1 -55
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaStore.cs +3 -59
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MorphMedia.cs +0 -46
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/GoogleImageProvider.cs +3 -28
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/OpenAIImageProvider.cs +7 -61
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/VeoVideoProvider.cs +2 -37
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/IUsageCostSink.cs +1 -23
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostMiddleware.cs +2 -82
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostRecord.cs +1 -68
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphOpenTelemetryExtensions.cs +1 -62
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphTelemetryOptions.cs +2 -62
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/IProviderClientFactory.cs +1 -25
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelAlias.cs +0 -124
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelRegistry.cs +1 -73
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaIngest.cs +1 -66
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaPlan.cs +0 -41
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentFactoryTests.cs +2 -112
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentSpecTests.cs +2 -43
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ConversationStoreTests.cs +5 -110
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/DeltaIngestTests.cs +7 -103
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalAssertionTests.cs +1 -42
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheMissTests.cs +1 -30
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheStampTests.cs +1 -19
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFailuresTests.cs +0 -48
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFixtureTests.cs +1 -29
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalRefreshTests.cs +2 -38
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalWorkspace.cs +1 -14
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/KitEvals.cs +4 -69
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/OfflineChatClientTests.cs +1 -10
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClient.cs +1 -57
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClientTests.cs +0 -26
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeClock.cs +1 -19
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeMediaProvider.cs +0 -32
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeProviderClientFactory.cs +0 -14
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeUsageCostSink.cs +0 -24
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/InMemoryMediaStore.cs +0 -32
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAccessibilityTests.cs +0 -37
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAliasIsNotAnAgentTests.cs +0 -24
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaDisclosureTests.cs +0 -78
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaGeneratorTests.cs +0 -53
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaIdempotencyKeyTests.cs +0 -44
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaJobStateTests.cs +0 -44
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaReviewTests.cs +0 -19
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaStoreTests.cs +0 -24
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ModelRegistryTests.cs +6 -48
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/SkeletonSmokeTests.cs +1 -13
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/TelemetryTests.cs +2 -71
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/UsageCostMiddlewareTests.cs +5 -100
- package/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +39 -43
- package/framework/templates/frontend/nextjs/env.mjs.hbs +29 -32
- package/framework/templates/frontend/nextjs/page.tsx.hbs +21 -22
- package/framework/templates/frontend/nextjs/use-feature.ts.hbs +42 -54
- package/framework/templates/integrations/asaas-client.cs +332 -387
- package/framework/templates/integrations/asaas-webhook.cs +290 -351
- package/framework/templates/integrations/neon-auth-config.cs +9 -105
- package/framework/templates/project/validate.js +97 -120
- package/package.json +2 -2
- package/src/commands/dashboard/dashboard.js +80 -53
- package/src/commands/mcp/mcp-setup.js +37 -9
- package/src/commands/project/doctor.js +3 -5
- package/src/commands/project/init.js +6 -3
- package/src/commands/project/install-plugin-cmd.js +1 -1
- package/src/commands/project/update.js +5 -0
- package/src/dashboard/app/App.jsx +80 -16
- package/src/dashboard/app/components/ActivityFeed.jsx +172 -0
- package/src/dashboard/app/components/CommandPalette.jsx +155 -0
- package/src/dashboard/app/components/DetailPanel.jsx +47 -11
- package/src/dashboard/app/components/EntityLink.jsx +55 -0
- package/src/dashboard/app/components/ErrorBoundary.jsx +54 -0
- package/src/dashboard/app/components/FlowGraph.jsx +19 -17
- package/src/dashboard/app/components/KnowledgeMarkdown.jsx +53 -0
- package/src/dashboard/app/components/Markdown.jsx +47 -0
- package/src/dashboard/app/components/PhaseRail.jsx +111 -0
- package/src/dashboard/app/components/SearchFilter.jsx +40 -22
- package/src/dashboard/app/components/Sidebar.jsx +146 -37
- package/src/dashboard/app/hooks/useMorphQuery.js +54 -0
- package/src/dashboard/app/hooks/usePref.js +38 -0
- package/src/dashboard/app/lib/format.js +69 -0
- package/src/dashboard/app/lib/phases.js +56 -0
- package/src/dashboard/app/lib/project.js +50 -0
- package/src/dashboard/app/pages/Activity.jsx +24 -0
- package/src/dashboard/app/pages/AgentMap.jsx +371 -370
- package/src/dashboard/app/pages/FeatureDetail.jsx +604 -0
- package/src/dashboard/app/pages/Features.jsx +400 -0
- package/src/dashboard/app/pages/Guide.jsx +146 -0
- package/src/dashboard/app/pages/HookInspector.jsx +357 -322
- package/src/dashboard/app/pages/PhasePipeline.jsx +277 -408
- package/src/dashboard/app/pages/Portfolio.jsx +156 -0
- package/src/dashboard/app/pages/Rubrics.jsx +101 -0
- package/src/dashboard/app/pages/RulesMap.jsx +300 -273
- package/src/dashboard/app/pages/SkillsLibrary.jsx +483 -366
- package/src/dashboard/app/pages/Standards.jsx +407 -332
- package/src/dashboard/app/styles/index.css +187 -47
- package/src/dashboard/index.html +14 -12
- package/src/dashboard/package.json +32 -24
- package/src/dashboard/scanner/activity-scanner.js +351 -0
- package/src/dashboard/scanner/agents-scanner.js +59 -1
- package/src/dashboard/scanner/docs-scanner.js +59 -0
- package/src/dashboard/scanner/feature-status.js +10 -0
- package/src/dashboard/scanner/features-scanner.js +360 -0
- package/src/dashboard/scanner/index.js +14 -7
- package/src/dashboard/scanner/phases-scanner.js +14 -15
- package/src/dashboard/scanner/portfolio.js +153 -0
- package/src/dashboard/scanner/project-api.js +195 -0
- package/src/dashboard/scanner/rules-scanner.js +42 -37
- package/src/dashboard/scanner/skills-scanner.js +105 -43
- package/src/dashboard/scanner/standards-scanner.js +2 -0
- package/src/dashboard/vite-plugin-morph-data.js +134 -17
- package/src/lib/detectors/claude-config-detector.js +2 -7
- package/src/lib/detectors/index.js +5 -1
- package/src/lib/evaluator-report.js +22 -0
- package/src/lib/generators/settings-generator.js +208 -209
- package/src/lib/installers/mcp-installer.js +724 -719
- package/src/lib/installers/memory-installer.js +1 -2
- package/src/lib/projects/registry.js +154 -0
- package/src/lib/validators/code-budget/code-budget-validator.js +32 -4
- package/src/scripts/install-plugin.js +1 -1
- package/src/scripts/setup-infra.js +2 -4
- package/src/utils/agents-installer.js +57 -12
- package/src/utils/banner.js +1 -1
- package/src/commands/index.js +0 -13
- package/src/commands/project/checkpoint.js +0 -209
- package/src/core/state/phase-state-machine.js +0 -197
- package/src/dashboard/app/hooks/useDetailPanel.js +0 -15
- package/src/dashboard/app/pages/LiveState.jsx +0 -206
- package/src/dashboard/scanner/squads-scanner.js +0 -13
- package/src/dashboard/scanner/state-scanner.js +0 -85
- package/src/lib/generators/index.js +0 -10
- package/src/lib/hooks/hook-executor.js +0 -167
- package/src/lib/validators/checkpoint-persistence.js +0 -39
- package/src/lib/validators/shared/emit-validator-dispatch.js +0 -14
- package/src/lib/validators/shared/index.js +0 -12
- package/src/lib/validators/shared/issue-counter.js +0 -14
- package/src/lib/validators/shared/result-formatter.js +0 -124
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
// ============================================================
|
|
2
|
-
// PGVECTOR ENTITY + CONFIGURATION TEMPLATE
|
|
3
|
-
// Generated by MORPH Framework
|
|
4
|
-
// ============================================================
|
|
5
|
-
|
|
6
1
|
using System.ComponentModel.DataAnnotations.Schema;
|
|
7
2
|
using Microsoft.EntityFrameworkCore;
|
|
8
3
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
|
@@ -12,10 +7,6 @@ using Pgvector;
|
|
|
12
7
|
|
|
13
8
|
namespace {{NAMESPACE}}.Domain.Entities;
|
|
14
9
|
|
|
15
|
-
/// <summary>
|
|
16
|
-
/// Entity with vector embedding for similarity search via pgvector.
|
|
17
|
-
/// Dimensions: 1536 (text-embedding-3-small). Adjust if using a different model.
|
|
18
|
-
/// </summary>
|
|
19
10
|
public class {{pascalCase FEATURE_NAME}}
|
|
20
11
|
{
|
|
21
12
|
public Guid Id { get; set; }
|
|
@@ -23,6 +14,7 @@ public class {{pascalCase FEATURE_NAME}}
|
|
|
23
14
|
public string Content { get; set; } = string.Empty;
|
|
24
15
|
public string? Metadata { get; set; }
|
|
25
16
|
|
|
17
|
+
// 1536 é a dimensão do text-embedding-3-small; troca junto com o modelo, aqui e na configuração
|
|
26
18
|
[Column(TypeName = "vector(1536)")]
|
|
27
19
|
public Vector? Embedding { get; set; }
|
|
28
20
|
|
|
@@ -30,12 +22,9 @@ public class {{pascalCase FEATURE_NAME}}
|
|
|
30
22
|
public DateTimeOffset? UpdatedAt { get; set; }
|
|
31
23
|
}
|
|
32
24
|
|
|
33
|
-
// ============================================================
|
|
34
|
-
// ENTITY TYPE CONFIGURATION
|
|
35
|
-
// ============================================================
|
|
36
|
-
|
|
37
25
|
namespace {{NAMESPACE}}.Infrastructure.Data.Configurations;
|
|
38
26
|
|
|
27
|
+
// Exige UseVector() no AddNpgsql e HasPostgresExtension("vector") no OnModelCreating
|
|
39
28
|
public class {{pascalCase FEATURE_NAME}}Configuration : IEntityTypeConfiguration<{{pascalCase FEATURE_NAME}}>
|
|
40
29
|
{
|
|
41
30
|
public void Configure(EntityTypeBuilder<{{pascalCase FEATURE_NAME}}> builder)
|
|
@@ -49,7 +38,6 @@ public class {{pascalCase FEATURE_NAME}}Configuration : IEntityTypeConfiguration
|
|
|
49
38
|
builder.Property(e => e.Embedding).HasColumnType("vector(1536)");
|
|
50
39
|
builder.Property(e => e.CreatedAt).HasDefaultValueSql("now()");
|
|
51
40
|
|
|
52
|
-
// HNSW index for cosine similarity search
|
|
53
41
|
builder.HasIndex(e => e.Embedding)
|
|
54
42
|
.HasMethod("hnsw")
|
|
55
43
|
.HasOperators("vector_cosine_ops")
|
|
@@ -59,35 +47,3 @@ public class {{pascalCase FEATURE_NAME}}Configuration : IEntityTypeConfiguration
|
|
|
59
47
|
builder.HasIndex(e => e.Title);
|
|
60
48
|
}
|
|
61
49
|
}
|
|
62
|
-
|
|
63
|
-
// ============================================================
|
|
64
|
-
// SETUP INSTRUCTIONS
|
|
65
|
-
// ============================================================
|
|
66
|
-
//
|
|
67
|
-
// 1. Install NuGet packages:
|
|
68
|
-
//
|
|
69
|
-
// dotnet add package Pgvector.EntityFrameworkCore
|
|
70
|
-
// dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL
|
|
71
|
-
//
|
|
72
|
-
// 2. Configure NpgsqlDataSource with vector support in Program.cs:
|
|
73
|
-
//
|
|
74
|
-
// builder.Services.AddNpgsql<AppDbContext>(
|
|
75
|
-
// connectionString,
|
|
76
|
-
// npgsqlOptions => npgsqlOptions.UseVector());
|
|
77
|
-
//
|
|
78
|
-
// 3. Register in DbContext:
|
|
79
|
-
//
|
|
80
|
-
// public DbSet<{{pascalCase FEATURE_NAME}}> {{pascalCase FEATURE_NAME}}s => Set<{{pascalCase FEATURE_NAME}}>();
|
|
81
|
-
//
|
|
82
|
-
// protected override void OnModelCreating(ModelBuilder modelBuilder)
|
|
83
|
-
// {
|
|
84
|
-
// modelBuilder.HasPostgresExtension("vector");
|
|
85
|
-
// modelBuilder.ApplyConfiguration(new {{pascalCase FEATURE_NAME}}Configuration());
|
|
86
|
-
// }
|
|
87
|
-
//
|
|
88
|
-
// 4. Create and apply migration:
|
|
89
|
-
//
|
|
90
|
-
// dotnet ef migrations add Add{{pascalCase FEATURE_NAME}}WithVector -p src/{{NAMESPACE}}.Infrastructure -s src/{{NAMESPACE}}.Web
|
|
91
|
-
// dotnet ef database update -p src/{{NAMESPACE}}.Infrastructure -s src/{{NAMESPACE}}.Web
|
|
92
|
-
//
|
|
93
|
-
// ============================================================
|
|
@@ -5,7 +5,6 @@ namespace {{pascalCase PROJECT_NAME}}.Extensions;
|
|
|
5
5
|
|
|
6
6
|
public static class ResultExtensions
|
|
7
7
|
{
|
|
8
|
-
// For non-generic Result
|
|
9
8
|
public static TOut Match<TOut>(
|
|
10
9
|
this Result result,
|
|
11
10
|
Func<TOut> onSuccess,
|
|
@@ -16,7 +15,6 @@ public static class ResultExtensions
|
|
|
16
15
|
: onFailure(result.Error);
|
|
17
16
|
}
|
|
18
17
|
|
|
19
|
-
// 🔥 THIS is what your endpoint needs
|
|
20
18
|
public static TOut Match<TIn, TOut>(
|
|
21
19
|
this Result<TIn> result,
|
|
22
20
|
Func<TIn, TOut> onSuccess,
|
|
@@ -1,15 +1,11 @@
|
|
|
1
|
-
using FluentValidation;
|
|
2
|
-
|
|
3
|
-
namespace {{pascalCase PROJECT_NAME}}.Features.{{pascalCase FEATURE_NAME}}Feature.{{pascalCase SLICE_NAME}};
|
|
4
|
-
|
|
5
|
-
public class {{pascalCase SLICE_NAME}}Validator : AbstractValidator<{{pascalCase SLICE_NAME}}Request>
|
|
6
|
-
{
|
|
7
|
-
public {{pascalCase SLICE_NAME}}Validator()
|
|
8
|
-
{
|
|
9
|
-
// TODO: Add validation rules for {{pascalCase SLICE_NAME}}Request properties
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
// .NotEmpty().WithMessage("Name is required")
|
|
13
|
-
// .MaximumLength(200).WithMessage("Name must not exceed 200 characters");
|
|
14
|
-
}
|
|
15
|
-
}
|
|
1
|
+
using FluentValidation;
|
|
2
|
+
|
|
3
|
+
namespace {{pascalCase PROJECT_NAME}}.Features.{{pascalCase FEATURE_NAME}}Feature.{{pascalCase SLICE_NAME}};
|
|
4
|
+
|
|
5
|
+
public class {{pascalCase SLICE_NAME}}Validator : AbstractValidator<{{pascalCase SLICE_NAME}}Request>
|
|
6
|
+
{
|
|
7
|
+
public {{pascalCase SLICE_NAME}}Validator()
|
|
8
|
+
{
|
|
9
|
+
// TODO: Add validation rules for {{pascalCase SLICE_NAME}}Request properties
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
// ============================================================
|
|
2
|
-
// MICROSOFT AGENT FRAMEWORK TEMPLATE
|
|
3
|
-
// Generated by MORPH Framework
|
|
4
|
-
// ============================================================
|
|
5
|
-
|
|
6
1
|
using System.ComponentModel;
|
|
7
2
|
using System.Text.Json;
|
|
8
3
|
using Microsoft.Extensions.AI;
|
|
@@ -10,15 +5,10 @@ using Microsoft.Extensions.Logging;
|
|
|
10
5
|
|
|
11
6
|
namespace {{NAMESPACE}}.Agents.{{pascalCase FEATURE_NAME}};
|
|
12
7
|
|
|
13
|
-
/// <summary>
|
|
14
|
-
/// AI Agent for analyzing {{pascalCase FEATURE_NAME}} data.
|
|
15
|
-
/// Uses Microsoft Agent Framework with IChatClient (gpt-4o-mini by default).
|
|
16
|
-
/// </summary>
|
|
17
8
|
public class {{pascalCase FEATURE_NAME}}AnalyzerAgent(
|
|
18
9
|
IChatClient chatClient,
|
|
19
10
|
ILogger<{{pascalCase FEATURE_NAME}}AnalyzerAgent> logger) : I{{pascalCase FEATURE_NAME}}AnalyzerAgent
|
|
20
11
|
{
|
|
21
|
-
/// <inheritdoc />
|
|
22
12
|
public async Task<{{pascalCase FEATURE_NAME}}AnalysisResult> AnalyzeAsync(
|
|
23
13
|
{{pascalCase FEATURE_NAME}}Data data,
|
|
24
14
|
CancellationToken cancellationToken = default)
|
|
@@ -65,14 +55,12 @@ public class {{pascalCase FEATURE_NAME}}AnalyzerAgent(
|
|
|
65
55
|
return result;
|
|
66
56
|
}
|
|
67
57
|
|
|
68
|
-
#region Tools
|
|
69
|
-
|
|
70
58
|
[Description("Gets additional context for the analysis")]
|
|
71
59
|
private async Task<string> GetContextAsync(
|
|
72
60
|
[Description("What context is needed")] string query,
|
|
73
61
|
CancellationToken ct = default)
|
|
74
62
|
{
|
|
75
|
-
// TODO:
|
|
63
|
+
// TODO: buscar o contexto (banco ou busca)
|
|
76
64
|
await Task.CompletedTask;
|
|
77
65
|
return $"Context for: {query}";
|
|
78
66
|
}
|
|
@@ -82,19 +70,16 @@ public class {{pascalCase FEATURE_NAME}}AnalyzerAgent(
|
|
|
82
70
|
[Description("Metric type to retrieve")] string metricType,
|
|
83
71
|
CancellationToken ct = default)
|
|
84
72
|
{
|
|
85
|
-
// TODO:
|
|
73
|
+
// TODO: buscar as métricas
|
|
86
74
|
await Task.CompletedTask;
|
|
87
75
|
return $"Metrics for: {metricType}";
|
|
88
76
|
}
|
|
89
77
|
|
|
90
|
-
#endregion
|
|
91
|
-
|
|
92
|
-
#region Response Parsing
|
|
93
|
-
|
|
94
78
|
private {{pascalCase FEATURE_NAME}}AnalysisResult ParseResponse(string response)
|
|
95
79
|
{
|
|
96
80
|
try
|
|
97
81
|
{
|
|
82
|
+
// o modelo às vezes cerca o JSON com ```json mesmo instruído a não fazer
|
|
98
83
|
var json = response
|
|
99
84
|
.Replace("```json", "")
|
|
100
85
|
.Replace("```", "")
|
|
@@ -126,37 +111,4 @@ public class {{pascalCase FEATURE_NAME}}AnalyzerAgent(
|
|
|
126
111
|
public List<string>? Recommendations { get; set; }
|
|
127
112
|
public double ConfidenceScore { get; set; }
|
|
128
113
|
}
|
|
129
|
-
|
|
130
|
-
#endregion
|
|
131
114
|
}
|
|
132
|
-
|
|
133
|
-
// ============================================================
|
|
134
|
-
// DEPENDENCY INJECTION SETUP
|
|
135
|
-
// ============================================================
|
|
136
|
-
//
|
|
137
|
-
// In Program.cs:
|
|
138
|
-
//
|
|
139
|
-
// // Register IChatClient (Azure OpenAI with Managed Identity)
|
|
140
|
-
// builder.Services.AddSingleton<IChatClient>(sp =>
|
|
141
|
-
// {
|
|
142
|
-
// var cfg = sp.GetRequiredService<IConfiguration>();
|
|
143
|
-
// return new AzureOpenAIClient(
|
|
144
|
-
// new Uri(cfg["AzureOpenAI:Endpoint"]!),
|
|
145
|
-
// new DefaultAzureCredential())
|
|
146
|
-
// .GetChatClient(cfg["AzureOpenAI:DeploymentName"] ?? "gpt-4o-mini")
|
|
147
|
-
// .AsIChatClient();
|
|
148
|
-
// });
|
|
149
|
-
//
|
|
150
|
-
// // Option A: Register as scoped service
|
|
151
|
-
// builder.Services.AddScoped<I{{pascalCase FEATURE_NAME}}AnalyzerAgent, {{pascalCase FEATURE_NAME}}AnalyzerAgent>();
|
|
152
|
-
//
|
|
153
|
-
// // Option B: Register as keyed agent (recommended for multiple agents)
|
|
154
|
-
// builder.AddAIAgent("{{pascalCase FEATURE_NAME}}Analyzer", (sp, key) =>
|
|
155
|
-
// {
|
|
156
|
-
// var chatClient = sp.GetRequiredService<IChatClient>();
|
|
157
|
-
// return new ChatClientAgent(chatClient,
|
|
158
|
-
// name: key,
|
|
159
|
-
// instructions: "You are an expert {{pascalCase FEATURE_NAME}} analyst.");
|
|
160
|
-
// });
|
|
161
|
-
//
|
|
162
|
-
// ============================================================
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
// ============================================================
|
|
2
|
-
// HANGFIRE JOB TEMPLATE (VSA Pattern — ApplicationDbContext direto)
|
|
3
|
-
// Generated by MORPH Framework
|
|
4
|
-
// ============================================================
|
|
5
|
-
|
|
6
1
|
using Hangfire;
|
|
7
2
|
using Microsoft.EntityFrameworkCore;
|
|
8
3
|
using Microsoft.Extensions.Logging;
|
|
@@ -10,21 +5,11 @@ using {{pascalCase PROJECT_NAME}}.Database;
|
|
|
10
5
|
|
|
11
6
|
namespace {{pascalCase PROJECT_NAME}}.Features.{{pascalCase FEATURE_NAME}}.Jobs;
|
|
12
7
|
|
|
13
|
-
/// <summary>
|
|
14
|
-
/// Background job for processing {{pascalCase FEATURE_NAME}}.
|
|
15
|
-
/// Uses Hangfire for scheduling and retry handling.
|
|
16
|
-
/// Injects ApplicationDbContext directly (no service layer, no generic repository).
|
|
17
|
-
/// </summary>
|
|
18
8
|
public class {{pascalCase FEATURE_NAME}}ProcessorJob(
|
|
19
9
|
ApplicationDbContext db,
|
|
20
10
|
I{{pascalCase FEATURE_NAME}}AnalyzerAgent analyzer,
|
|
21
11
|
ILogger<{{pascalCase FEATURE_NAME}}ProcessorJob> logger) : I{{pascalCase FEATURE_NAME}}ProcessorJob
|
|
22
12
|
{
|
|
23
|
-
/// <summary>
|
|
24
|
-
/// Executes the {{pascalCase FEATURE_NAME}} processing job.
|
|
25
|
-
/// </summary>
|
|
26
|
-
/// <param name="id">The {{pascalCase FEATURE_NAME}} ID to process</param>
|
|
27
|
-
/// <param name="cancellationToken">Cancellation token</param>
|
|
28
13
|
[AutomaticRetry(Attempts = 3, DelaysInSeconds = new[] { 60, 300, 900 })]
|
|
29
14
|
[Queue("default")]
|
|
30
15
|
[JobDisplayName("{{pascalCase FEATURE_NAME}} Processing - ID: {0}")]
|
|
@@ -34,7 +19,6 @@ public class {{pascalCase FEATURE_NAME}}ProcessorJob(
|
|
|
34
19
|
|
|
35
20
|
try
|
|
36
21
|
{
|
|
37
|
-
// Get the entity directly from the DbSet
|
|
38
22
|
var entity = await db.Set<{{pascalCase FEATURE_NAME}}>().FindAsync(new object[] { id }, cancellationToken);
|
|
39
23
|
if (entity is null)
|
|
40
24
|
{
|
|
@@ -42,14 +26,12 @@ public class {{pascalCase FEATURE_NAME}}ProcessorJob(
|
|
|
42
26
|
return;
|
|
43
27
|
}
|
|
44
28
|
|
|
45
|
-
// Check if already processed
|
|
46
29
|
if (entity.Status == {{pascalCase FEATURE_NAME}}Status.Completed)
|
|
47
30
|
{
|
|
48
31
|
logger.LogInformation("{{pascalCase FEATURE_NAME}} {Id} already completed, skipping", id);
|
|
49
32
|
return;
|
|
50
33
|
}
|
|
51
34
|
|
|
52
|
-
// Perform MAF agent analysis (if applicable)
|
|
53
35
|
var analysisData = new {{pascalCase FEATURE_NAME}}Data(entity.Name);
|
|
54
36
|
var analysis = await analyzer.AnalyzeAsync(analysisData, cancellationToken);
|
|
55
37
|
|
|
@@ -57,7 +39,6 @@ public class {{pascalCase FEATURE_NAME}}ProcessorJob(
|
|
|
57
39
|
"{{pascalCase FEATURE_NAME}} {Id} analyzed. Confidence: {Confidence:P0}",
|
|
58
40
|
id, analysis.ConfidenceScore);
|
|
59
41
|
|
|
60
|
-
// Update the tracked entity and persist
|
|
61
42
|
entity.MarkAsCompleted(analysis);
|
|
62
43
|
await db.SaveChangesAsync(cancellationToken);
|
|
63
44
|
|
|
@@ -66,54 +47,17 @@ public class {{pascalCase FEATURE_NAME}}ProcessorJob(
|
|
|
66
47
|
catch (Exception ex)
|
|
67
48
|
{
|
|
68
49
|
logger.LogError(ex, "Failed to process {{pascalCase FEATURE_NAME}} {Id}", id);
|
|
69
|
-
throw; //
|
|
50
|
+
throw; // sem a exceção o Hangfire não aplica o AutomaticRetry
|
|
70
51
|
}
|
|
71
52
|
}
|
|
72
53
|
}
|
|
73
54
|
|
|
74
|
-
// ============================================================
|
|
75
|
-
// RECURRING JOB CONFIGURATION
|
|
76
|
-
// ============================================================
|
|
77
|
-
//
|
|
78
|
-
// For scheduled/recurring jobs, configure in Program.cs:
|
|
79
|
-
//
|
|
80
|
-
// // Run every hour
|
|
81
|
-
// RecurringJob.AddOrUpdate<I{{pascalCase FEATURE_NAME}}ProcessorJob>(
|
|
82
|
-
// "{{kebabCase FEATURE_NAME}}-processor",
|
|
83
|
-
// job => job.ExecuteAsync(0, CancellationToken.None),
|
|
84
|
-
// Cron.Hourly);
|
|
85
|
-
//
|
|
86
|
-
// // Run daily at midnight
|
|
87
|
-
// RecurringJob.AddOrUpdate<I{{pascalCase FEATURE_NAME}}ProcessorJob>(
|
|
88
|
-
// "{{kebabCase FEATURE_NAME}}-daily-processor",
|
|
89
|
-
// job => job.ExecuteAsync(0, CancellationToken.None),
|
|
90
|
-
// Cron.Daily);
|
|
91
|
-
//
|
|
92
|
-
// // Custom cron expression (every 15 minutes)
|
|
93
|
-
// RecurringJob.AddOrUpdate<I{{pascalCase FEATURE_NAME}}ProcessorJob>(
|
|
94
|
-
// "{{kebabCase FEATURE_NAME}}-frequent-processor",
|
|
95
|
-
// job => job.ExecuteAsync(0, CancellationToken.None),
|
|
96
|
-
// "*/15 * * * *");
|
|
97
|
-
//
|
|
98
|
-
// ============================================================
|
|
99
|
-
|
|
100
|
-
// ============================================================
|
|
101
|
-
// BATCH JOB TEMPLATE
|
|
102
|
-
// ============================================================
|
|
103
|
-
|
|
104
|
-
/// <summary>
|
|
105
|
-
/// Batch job for processing multiple {{pascalCase FEATURE_NAME}}s.
|
|
106
|
-
/// Queries pending items via LINQ on the DbSet and enqueues individual jobs.
|
|
107
|
-
/// </summary>
|
|
108
55
|
public class {{pascalCase FEATURE_NAME}}BatchProcessorJob(
|
|
109
56
|
ApplicationDbContext db,
|
|
110
57
|
ILogger<{{pascalCase FEATURE_NAME}}BatchProcessorJob> logger)
|
|
111
58
|
{
|
|
112
|
-
/// <summary>
|
|
113
|
-
/// Processes all pending {{pascalCase FEATURE_NAME}}s.
|
|
114
|
-
/// </summary>
|
|
115
59
|
[AutomaticRetry(Attempts = 1)]
|
|
116
|
-
[Queue("batch")]
|
|
60
|
+
[Queue("batch")] // a fila precisa constar em AddHangfireServer(o => o.Queues), senão ninguém a consome
|
|
117
61
|
[JobDisplayName("{{pascalCase FEATURE_NAME}} Batch Processing")]
|
|
118
62
|
public async Task ProcessAllPendingAsync(CancellationToken cancellationToken)
|
|
119
63
|
{
|
|
@@ -127,7 +71,6 @@ public class {{pascalCase FEATURE_NAME}}BatchProcessorJob(
|
|
|
127
71
|
|
|
128
72
|
foreach (var item in pending)
|
|
129
73
|
{
|
|
130
|
-
// Enqueue individual processing jobs
|
|
131
74
|
BackgroundJob.Enqueue<I{{pascalCase FEATURE_NAME}}ProcessorJob>(
|
|
132
75
|
job => job.ExecuteAsync(item.Id, CancellationToken.None));
|
|
133
76
|
}
|
|
@@ -135,41 +78,3 @@ public class {{pascalCase FEATURE_NAME}}BatchProcessorJob(
|
|
|
135
78
|
logger.LogInformation("Enqueued {Count} processing jobs", pending.Count);
|
|
136
79
|
}
|
|
137
80
|
}
|
|
138
|
-
|
|
139
|
-
// ============================================================
|
|
140
|
-
// HANGFIRE CONFIGURATION
|
|
141
|
-
// ============================================================
|
|
142
|
-
//
|
|
143
|
-
// In Program.cs:
|
|
144
|
-
//
|
|
145
|
-
// // Add Hangfire services
|
|
146
|
-
// builder.Services.AddHangfire(config => config
|
|
147
|
-
// .SetDataCompatibilityLevel(CompatibilityLevel.Version_180)
|
|
148
|
-
// .UseSimpleAssemblyNameTypeSerializer()
|
|
149
|
-
// .UseRecommendedSerializerSettings()
|
|
150
|
-
// .UseSqlServerStorage(connectionString, new SqlServerStorageOptions
|
|
151
|
-
// {
|
|
152
|
-
// CommandBatchMaxTimeout = TimeSpan.FromMinutes(5),
|
|
153
|
-
// SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5),
|
|
154
|
-
// QueuePollInterval = TimeSpan.Zero,
|
|
155
|
-
// UseRecommendedIsolationLevel = true,
|
|
156
|
-
// DisableGlobalLocks = true
|
|
157
|
-
// }));
|
|
158
|
-
//
|
|
159
|
-
// builder.Services.AddHangfireServer(options =>
|
|
160
|
-
// {
|
|
161
|
-
// options.Queues = new[] { "default", "batch" };
|
|
162
|
-
// options.WorkerCount = Environment.ProcessorCount * 2;
|
|
163
|
-
// });
|
|
164
|
-
//
|
|
165
|
-
// // Register jobs (ApplicationDbContext already registered via AddDbContext)
|
|
166
|
-
// builder.Services.AddScoped<I{{pascalCase FEATURE_NAME}}ProcessorJob, {{pascalCase FEATURE_NAME}}ProcessorJob>();
|
|
167
|
-
// builder.Services.AddScoped<{{pascalCase FEATURE_NAME}}BatchProcessorJob>();
|
|
168
|
-
//
|
|
169
|
-
// // In app pipeline
|
|
170
|
-
// app.UseHangfireDashboard("/hangfire", new DashboardOptions
|
|
171
|
-
// {
|
|
172
|
-
// Authorization = new[] { new HangfireAuthorizationFilter() }
|
|
173
|
-
// });
|
|
174
|
-
//
|
|
175
|
-
// ============================================================
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
// ============================================================
|
|
2
|
-
// TEST TEMPLATE (VSA Pattern — Handler + Validator)
|
|
3
|
-
// Generated by MORPH Framework
|
|
4
|
-
// ============================================================
|
|
5
|
-
|
|
6
1
|
using FluentAssertions;
|
|
7
2
|
using FluentValidation;
|
|
8
3
|
using FluentValidation.Results;
|
|
@@ -14,15 +9,6 @@ using Xunit;
|
|
|
14
9
|
|
|
15
10
|
namespace {{pascalCase PROJECT_NAME}}.Tests.Unit.Features.{{pascalCase FEATURE_NAME}};
|
|
16
11
|
|
|
17
|
-
// ============================================================
|
|
18
|
-
// Handler Tests
|
|
19
|
-
// ============================================================
|
|
20
|
-
|
|
21
|
-
/// <summary>
|
|
22
|
-
/// Unit tests for {{pascalCase FEATURE_NAME}} handlers.
|
|
23
|
-
/// Tests handlers directly against a real ApplicationDbContext backed by
|
|
24
|
-
/// in-memory SQLite (no mocks — DbSet is the repository, DbContext is the unit of work).
|
|
25
|
-
/// </summary>
|
|
26
12
|
public class {{pascalCase FEATURE_NAME}}HandlerTests : IDisposable
|
|
27
13
|
{
|
|
28
14
|
private readonly SqliteConnection _connection;
|
|
@@ -30,6 +16,7 @@ public class {{pascalCase FEATURE_NAME}}HandlerTests : IDisposable
|
|
|
30
16
|
|
|
31
17
|
public {{pascalCase FEATURE_NAME}}HandlerTests()
|
|
32
18
|
{
|
|
19
|
+
// o banco :memory: some quando a conexão fecha, então ela vive o teste inteiro
|
|
33
20
|
_connection = new SqliteConnection("DataSource=:memory:");
|
|
34
21
|
_connection.Open();
|
|
35
22
|
|
|
@@ -47,19 +34,14 @@ public class {{pascalCase FEATURE_NAME}}HandlerTests : IDisposable
|
|
|
47
34
|
_connection.Dispose();
|
|
48
35
|
}
|
|
49
36
|
|
|
50
|
-
#region Create Handler Tests
|
|
51
|
-
|
|
52
37
|
[Fact]
|
|
53
38
|
public async Task Create_HandleAsync_WithValidCommand_ReturnsSuccessResult()
|
|
54
39
|
{
|
|
55
|
-
// Arrange
|
|
56
40
|
var command = new Create{{pascalCase FEATURE_NAME}}Request("Test {{pascalCase FEATURE_NAME}}");
|
|
57
41
|
var handler = new Create{{pascalCase FEATURE_NAME}}Handler(_db);
|
|
58
42
|
|
|
59
|
-
// Act
|
|
60
43
|
var result = await handler.HandleAsync(command, CancellationToken.None);
|
|
61
44
|
|
|
62
|
-
// Assert
|
|
63
45
|
result.IsSuccess.Should().BeTrue();
|
|
64
46
|
result.Value.Should().NotBeNull();
|
|
65
47
|
|
|
@@ -67,14 +49,9 @@ public class {{pascalCase FEATURE_NAME}}HandlerTests : IDisposable
|
|
|
67
49
|
persisted.Name.Should().Be("Test {{pascalCase FEATURE_NAME}}");
|
|
68
50
|
}
|
|
69
51
|
|
|
70
|
-
#endregion
|
|
71
|
-
|
|
72
|
-
#region GetById Handler Tests
|
|
73
|
-
|
|
74
52
|
[Fact]
|
|
75
53
|
public async Task GetById_HandleAsync_WithValidId_ReturnsSuccessResult()
|
|
76
54
|
{
|
|
77
|
-
// Arrange
|
|
78
55
|
var entity = CreateTestEntity("Test {{pascalCase FEATURE_NAME}}");
|
|
79
56
|
_db.Set<{{pascalCase FEATURE_NAME}}>().Add(entity);
|
|
80
57
|
await _db.SaveChangesAsync();
|
|
@@ -82,10 +59,8 @@ public class {{pascalCase FEATURE_NAME}}HandlerTests : IDisposable
|
|
|
82
59
|
var query = new Get{{pascalCase FEATURE_NAME}}ByIdRequest(entity.Id);
|
|
83
60
|
var handler = new Get{{pascalCase FEATURE_NAME}}ByIdHandler(_db);
|
|
84
61
|
|
|
85
|
-
// Act
|
|
86
62
|
var result = await handler.HandleAsync(query, CancellationToken.None);
|
|
87
63
|
|
|
88
|
-
// Assert
|
|
89
64
|
result.IsSuccess.Should().BeTrue();
|
|
90
65
|
result.Value.Should().NotBeNull();
|
|
91
66
|
result.Value!.Name.Should().Be("Test {{pascalCase FEATURE_NAME}}");
|
|
@@ -94,25 +69,17 @@ public class {{pascalCase FEATURE_NAME}}HandlerTests : IDisposable
|
|
|
94
69
|
[Fact]
|
|
95
70
|
public async Task GetById_HandleAsync_WithInvalidId_ReturnsFailureResult()
|
|
96
71
|
{
|
|
97
|
-
// Arrange
|
|
98
72
|
var query = new Get{{pascalCase FEATURE_NAME}}ByIdRequest(Guid.CreateVersion7());
|
|
99
73
|
var handler = new Get{{pascalCase FEATURE_NAME}}ByIdHandler(_db);
|
|
100
74
|
|
|
101
|
-
// Act
|
|
102
75
|
var result = await handler.HandleAsync(query, CancellationToken.None);
|
|
103
76
|
|
|
104
|
-
// Assert
|
|
105
77
|
result.IsSuccess.Should().BeFalse();
|
|
106
78
|
}
|
|
107
79
|
|
|
108
|
-
#endregion
|
|
109
|
-
|
|
110
|
-
#region Update Handler Tests
|
|
111
|
-
|
|
112
80
|
[Fact]
|
|
113
81
|
public async Task Update_HandleAsync_WithValidCommand_ReturnsSuccessResult()
|
|
114
82
|
{
|
|
115
|
-
// Arrange
|
|
116
83
|
var entity = CreateTestEntity("Original Name");
|
|
117
84
|
_db.Set<{{pascalCase FEATURE_NAME}}>().Add(entity);
|
|
118
85
|
await _db.SaveChangesAsync();
|
|
@@ -120,10 +87,8 @@ public class {{pascalCase FEATURE_NAME}}HandlerTests : IDisposable
|
|
|
120
87
|
var command = new Update{{pascalCase FEATURE_NAME}}Request(entity.Id, "Updated Name");
|
|
121
88
|
var handler = new Update{{pascalCase FEATURE_NAME}}Handler(_db);
|
|
122
89
|
|
|
123
|
-
// Act
|
|
124
90
|
var result = await handler.HandleAsync(command, CancellationToken.None);
|
|
125
91
|
|
|
126
|
-
// Assert
|
|
127
92
|
result.IsSuccess.Should().BeTrue();
|
|
128
93
|
|
|
129
94
|
var persisted = await _db.Set<{{pascalCase FEATURE_NAME}}>().SingleAsync(x => x.Id == entity.Id);
|
|
@@ -133,25 +98,17 @@ public class {{pascalCase FEATURE_NAME}}HandlerTests : IDisposable
|
|
|
133
98
|
[Fact]
|
|
134
99
|
public async Task Update_HandleAsync_WithNonExistentId_ReturnsFailureResult()
|
|
135
100
|
{
|
|
136
|
-
// Arrange
|
|
137
101
|
var command = new Update{{pascalCase FEATURE_NAME}}Request(Guid.CreateVersion7(), "Updated");
|
|
138
102
|
var handler = new Update{{pascalCase FEATURE_NAME}}Handler(_db);
|
|
139
103
|
|
|
140
|
-
// Act
|
|
141
104
|
var result = await handler.HandleAsync(command, CancellationToken.None);
|
|
142
105
|
|
|
143
|
-
// Assert
|
|
144
106
|
result.IsSuccess.Should().BeFalse();
|
|
145
107
|
}
|
|
146
108
|
|
|
147
|
-
#endregion
|
|
148
|
-
|
|
149
|
-
#region Delete Handler Tests
|
|
150
|
-
|
|
151
109
|
[Fact]
|
|
152
110
|
public async Task Delete_HandleAsync_WithValidId_ReturnsSuccessResult()
|
|
153
111
|
{
|
|
154
|
-
// Arrange
|
|
155
112
|
var entity = CreateTestEntity("To Delete");
|
|
156
113
|
_db.Set<{{pascalCase FEATURE_NAME}}>().Add(entity);
|
|
157
114
|
await _db.SaveChangesAsync();
|
|
@@ -159,10 +116,8 @@ public class {{pascalCase FEATURE_NAME}}HandlerTests : IDisposable
|
|
|
159
116
|
var command = new Delete{{pascalCase FEATURE_NAME}}Request(entity.Id);
|
|
160
117
|
var handler = new Delete{{pascalCase FEATURE_NAME}}Handler(_db);
|
|
161
118
|
|
|
162
|
-
// Act
|
|
163
119
|
var result = await handler.HandleAsync(command, CancellationToken.None);
|
|
164
120
|
|
|
165
|
-
// Assert
|
|
166
121
|
result.IsSuccess.Should().BeTrue();
|
|
167
122
|
|
|
168
123
|
(await _db.Set<{{pascalCase FEATURE_NAME}}>().AnyAsync(x => x.Id == entity.Id)).Should().BeFalse();
|
|
@@ -171,57 +126,35 @@ public class {{pascalCase FEATURE_NAME}}HandlerTests : IDisposable
|
|
|
171
126
|
[Fact]
|
|
172
127
|
public async Task Delete_HandleAsync_WithNonExistentId_ReturnsFailureResult()
|
|
173
128
|
{
|
|
174
|
-
// Arrange
|
|
175
129
|
var command = new Delete{{pascalCase FEATURE_NAME}}Request(Guid.CreateVersion7());
|
|
176
130
|
var handler = new Delete{{pascalCase FEATURE_NAME}}Handler(_db);
|
|
177
131
|
|
|
178
|
-
// Act
|
|
179
132
|
var result = await handler.HandleAsync(command, CancellationToken.None);
|
|
180
133
|
|
|
181
|
-
// Assert
|
|
182
134
|
result.IsSuccess.Should().BeFalse();
|
|
183
135
|
}
|
|
184
136
|
|
|
185
|
-
#endregion
|
|
186
|
-
|
|
187
|
-
#region Helper Methods
|
|
188
|
-
|
|
189
137
|
private static {{pascalCase FEATURE_NAME}} CreateTestEntity(string name)
|
|
190
138
|
{
|
|
191
139
|
return new {{pascalCase FEATURE_NAME}}
|
|
192
140
|
{
|
|
193
141
|
Id = Guid.CreateVersion7(),
|
|
194
142
|
Name = name,
|
|
195
|
-
// TODO:
|
|
143
|
+
// TODO: demais campos obrigatórios da entidade
|
|
196
144
|
};
|
|
197
145
|
}
|
|
198
|
-
|
|
199
|
-
#endregion
|
|
200
146
|
}
|
|
201
147
|
|
|
202
|
-
// ============================================================
|
|
203
|
-
// Validator Tests
|
|
204
|
-
// ============================================================
|
|
205
|
-
|
|
206
|
-
/// <summary>
|
|
207
|
-
/// Unit tests for {{pascalCase FEATURE_NAME}} validators.
|
|
208
|
-
/// Tests FluentValidation rules using ValidateAsync.
|
|
209
|
-
/// </summary>
|
|
210
148
|
public class {{pascalCase FEATURE_NAME}}ValidatorTests
|
|
211
149
|
{
|
|
212
|
-
#region Create Validator Tests
|
|
213
|
-
|
|
214
150
|
[Fact]
|
|
215
151
|
public async Task CreateValidator_WithValidCommand_PassesValidation()
|
|
216
152
|
{
|
|
217
|
-
// Arrange
|
|
218
153
|
var validator = new Create{{pascalCase FEATURE_NAME}}Validator();
|
|
219
154
|
var command = new Create{{pascalCase FEATURE_NAME}}Request("Valid Name");
|
|
220
155
|
|
|
221
|
-
// Act
|
|
222
156
|
ValidationResult result = await validator.ValidateAsync(command);
|
|
223
157
|
|
|
224
|
-
// Assert
|
|
225
158
|
result.IsValid.Should().BeTrue();
|
|
226
159
|
result.Errors.Should().BeEmpty();
|
|
227
160
|
}
|
|
@@ -232,14 +165,11 @@ public class {{pascalCase FEATURE_NAME}}ValidatorTests
|
|
|
232
165
|
[InlineData(null)]
|
|
233
166
|
public async Task CreateValidator_WithInvalidName_FailsValidation(string? name)
|
|
234
167
|
{
|
|
235
|
-
// Arrange
|
|
236
168
|
var validator = new Create{{pascalCase FEATURE_NAME}}Validator();
|
|
237
169
|
var command = new Create{{pascalCase FEATURE_NAME}}Request(name!);
|
|
238
170
|
|
|
239
|
-
// Act
|
|
240
171
|
ValidationResult result = await validator.ValidateAsync(command);
|
|
241
172
|
|
|
242
|
-
// Assert
|
|
243
173
|
result.IsValid.Should().BeFalse();
|
|
244
174
|
result.Errors.Should().Contain(e => e.PropertyName == nameof(Create{{pascalCase FEATURE_NAME}}Request.Name));
|
|
245
175
|
}
|
|
@@ -247,50 +177,35 @@ public class {{pascalCase FEATURE_NAME}}ValidatorTests
|
|
|
247
177
|
[Fact]
|
|
248
178
|
public async Task CreateValidator_WithNameExceedingMaxLength_FailsValidation()
|
|
249
179
|
{
|
|
250
|
-
// Arrange
|
|
251
180
|
var validator = new Create{{pascalCase FEATURE_NAME}}Validator();
|
|
252
181
|
var command = new Create{{pascalCase FEATURE_NAME}}Request(new string('A', 201));
|
|
253
182
|
|
|
254
|
-
// Act
|
|
255
183
|
ValidationResult result = await validator.ValidateAsync(command);
|
|
256
184
|
|
|
257
|
-
// Assert
|
|
258
185
|
result.IsValid.Should().BeFalse();
|
|
259
186
|
result.Errors.Should().Contain(e => e.PropertyName == nameof(Create{{pascalCase FEATURE_NAME}}Request.Name));
|
|
260
187
|
}
|
|
261
188
|
|
|
262
|
-
#endregion
|
|
263
|
-
|
|
264
|
-
#region Update Validator Tests
|
|
265
|
-
|
|
266
189
|
[Fact]
|
|
267
190
|
public async Task UpdateValidator_WithValidCommand_PassesValidation()
|
|
268
191
|
{
|
|
269
|
-
// Arrange
|
|
270
192
|
var validator = new Update{{pascalCase FEATURE_NAME}}Validator();
|
|
271
193
|
var command = new Update{{pascalCase FEATURE_NAME}}Request(Guid.CreateVersion7(), "Updated Name");
|
|
272
194
|
|
|
273
|
-
// Act
|
|
274
195
|
ValidationResult result = await validator.ValidateAsync(command);
|
|
275
196
|
|
|
276
|
-
// Assert
|
|
277
197
|
result.IsValid.Should().BeTrue();
|
|
278
198
|
}
|
|
279
199
|
|
|
280
200
|
[Fact]
|
|
281
201
|
public async Task UpdateValidator_WithInvalidId_FailsValidation()
|
|
282
202
|
{
|
|
283
|
-
// Arrange
|
|
284
203
|
var validator = new Update{{pascalCase FEATURE_NAME}}Validator();
|
|
285
204
|
var command = new Update{{pascalCase FEATURE_NAME}}Request(Guid.Empty, "Updated Name");
|
|
286
205
|
|
|
287
|
-
// Act
|
|
288
206
|
ValidationResult result = await validator.ValidateAsync(command);
|
|
289
207
|
|
|
290
|
-
// Assert
|
|
291
208
|
result.IsValid.Should().BeFalse();
|
|
292
209
|
result.Errors.Should().Contain(e => e.PropertyName == nameof(Update{{pascalCase FEATURE_NAME}}Request.Id));
|
|
293
210
|
}
|
|
294
|
-
|
|
295
|
-
#endregion
|
|
296
211
|
}
|