@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,239 +1,239 @@
|
|
|
1
|
-
// ============================================================
|
|
2
|
-
// TEST TEMPLATE
|
|
3
|
-
// Generated by MORPH Framework
|
|
4
|
-
// ============================================================
|
|
5
|
-
|
|
6
|
-
using FluentAssertions;
|
|
7
|
-
using Microsoft.Extensions.Logging;
|
|
8
|
-
using NSubstitute;
|
|
9
|
-
using Xunit;
|
|
10
|
-
|
|
11
|
-
namespace MyProject.Tests.Unit.Features.{Feature};
|
|
12
|
-
|
|
13
|
-
/// <summary>
|
|
14
|
-
/// Unit tests for {Feature}Service.
|
|
15
|
-
/// </summary>
|
|
16
|
-
public class {Feature}ServiceTests
|
|
17
|
-
{
|
|
18
|
-
private readonly I{Feature}Repository _repository;
|
|
19
|
-
private readonly ILogger<{Feature}Service> _logger;
|
|
20
|
-
private readonly {Feature}Service _sut;
|
|
21
|
-
|
|
22
|
-
public {Feature}ServiceTests()
|
|
23
|
-
{
|
|
24
|
-
_repository = Substitute.For<I{Feature}Repository>();
|
|
25
|
-
_logger = Substitute.For<ILogger<{Feature}Service>>();
|
|
26
|
-
_sut = new {Feature}Service(_repository, _logger);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
#region GetByIdAsync Tests
|
|
30
|
-
|
|
31
|
-
[Fact]
|
|
32
|
-
public async Task GetByIdAsync_WithValidId_ReturnsDto()
|
|
33
|
-
{
|
|
34
|
-
// Arrange
|
|
35
|
-
var id = 1;
|
|
36
|
-
var entity = CreateTestEntity(id, "Test {Feature}");
|
|
37
|
-
_repository.GetByIdAsync(id, Arg.Any<CancellationToken>())
|
|
38
|
-
.Returns(entity);
|
|
39
|
-
|
|
40
|
-
// Act
|
|
41
|
-
var result = await _sut.GetByIdAsync(id);
|
|
42
|
-
|
|
43
|
-
// Assert
|
|
44
|
-
result.Should().NotBeNull();
|
|
45
|
-
result!.Id.Should().Be(id);
|
|
46
|
-
result.Name.Should().Be("Test {Feature}");
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
[Fact]
|
|
50
|
-
public async Task GetByIdAsync_WithInvalidId_ReturnsNull()
|
|
51
|
-
{
|
|
52
|
-
// Arrange
|
|
53
|
-
var id = 999;
|
|
54
|
-
_repository.GetByIdAsync(id, Arg.Any<CancellationToken>())
|
|
55
|
-
.Returns((Domain.Entities.{Feature}?)null);
|
|
56
|
-
|
|
57
|
-
// Act
|
|
58
|
-
var result = await _sut.GetByIdAsync(id);
|
|
59
|
-
|
|
60
|
-
// Assert
|
|
61
|
-
result.Should().BeNull();
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
#endregion
|
|
65
|
-
|
|
66
|
-
#region GetAllAsync Tests
|
|
67
|
-
|
|
68
|
-
[Fact]
|
|
69
|
-
public async Task GetAllAsync_ReturnsAllItems()
|
|
70
|
-
{
|
|
71
|
-
// Arrange
|
|
72
|
-
var entities = new List<Domain.Entities.{Feature}>
|
|
73
|
-
{
|
|
74
|
-
CreateTestEntity(1, "First"),
|
|
75
|
-
CreateTestEntity(2, "Second"),
|
|
76
|
-
CreateTestEntity(3, "Third")
|
|
77
|
-
};
|
|
78
|
-
_repository.GetAllAsync(Arg.Any<CancellationToken>())
|
|
79
|
-
.Returns(entities);
|
|
80
|
-
|
|
81
|
-
// Act
|
|
82
|
-
var result = await _sut.GetAllAsync();
|
|
83
|
-
|
|
84
|
-
// Assert
|
|
85
|
-
result.Should().HaveCount(3);
|
|
86
|
-
result[0].Name.Should().Be("First");
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
[Fact]
|
|
90
|
-
public async Task GetAllAsync_WithNoItems_ReturnsEmptyList()
|
|
91
|
-
{
|
|
92
|
-
// Arrange
|
|
93
|
-
_repository.GetAllAsync(Arg.Any<CancellationToken>())
|
|
94
|
-
.Returns(new List<Domain.Entities.{Feature}>());
|
|
95
|
-
|
|
96
|
-
// Act
|
|
97
|
-
var result = await _sut.GetAllAsync();
|
|
98
|
-
|
|
99
|
-
// Assert
|
|
100
|
-
result.Should().BeEmpty();
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
#endregion
|
|
104
|
-
|
|
105
|
-
#region CreateAsync Tests
|
|
106
|
-
|
|
107
|
-
[Fact]
|
|
108
|
-
public async Task CreateAsync_WithValidRequest_CreatesAndReturnsDto()
|
|
109
|
-
{
|
|
110
|
-
// Arrange
|
|
111
|
-
var request = new Create{Feature}Request("New {Feature}");
|
|
112
|
-
|
|
113
|
-
_repository
|
|
114
|
-
.When(x => x.AddAsync(Arg.Any<Domain.Entities.{Feature}>(), Arg.Any<CancellationToken>()))
|
|
115
|
-
.Do(x =>
|
|
116
|
-
{
|
|
117
|
-
// Simulate ID assignment
|
|
118
|
-
var entity = x.Arg<Domain.Entities.{Feature}>();
|
|
119
|
-
// entity.Id would be set by EF Core
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
// Act
|
|
123
|
-
var result = await _sut.CreateAsync(request);
|
|
124
|
-
|
|
125
|
-
// Assert
|
|
126
|
-
result.Should().NotBeNull();
|
|
127
|
-
result.Name.Should().Be("New {Feature}");
|
|
128
|
-
|
|
129
|
-
await _repository.Received(1).AddAsync(Arg.Any<Domain.Entities.{Feature}>(), Arg.Any<CancellationToken>());
|
|
130
|
-
await _repository.Received(1).SaveChangesAsync(Arg.Any<CancellationToken>());
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
[Theory]
|
|
134
|
-
[InlineData("")]
|
|
135
|
-
[InlineData(" ")]
|
|
136
|
-
[InlineData(null)]
|
|
137
|
-
public async Task CreateAsync_WithInvalidName_ThrowsValidationException(string? name)
|
|
138
|
-
{
|
|
139
|
-
// Arrange
|
|
140
|
-
var request = new Create{Feature}Request(name!);
|
|
141
|
-
|
|
142
|
-
// Act
|
|
143
|
-
var act = async () => await _sut.CreateAsync(request);
|
|
144
|
-
|
|
145
|
-
// Assert
|
|
146
|
-
await act.Should().ThrowAsync<ValidationException>();
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
#endregion
|
|
150
|
-
|
|
151
|
-
#region UpdateAsync Tests
|
|
152
|
-
|
|
153
|
-
[Fact]
|
|
154
|
-
public async Task UpdateAsync_WithValidRequest_UpdatesEntity()
|
|
155
|
-
{
|
|
156
|
-
// Arrange
|
|
157
|
-
var id = 1;
|
|
158
|
-
var entity = CreateTestEntity(id, "Original");
|
|
159
|
-
var request = new Update{Feature}Request("Updated");
|
|
160
|
-
|
|
161
|
-
_repository.GetByIdAsync(id, Arg.Any<CancellationToken>())
|
|
162
|
-
.Returns(entity);
|
|
163
|
-
|
|
164
|
-
// Act
|
|
165
|
-
await _sut.UpdateAsync(id, request);
|
|
166
|
-
|
|
167
|
-
// Assert
|
|
168
|
-
_repository.Received(1).Update(Arg.Any<Domain.Entities.{Feature}>());
|
|
169
|
-
await _repository.Received(1).SaveChangesAsync(Arg.Any<CancellationToken>());
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
[Fact]
|
|
173
|
-
public async Task UpdateAsync_WithNonExistentId_ThrowsNotFoundException()
|
|
174
|
-
{
|
|
175
|
-
// Arrange
|
|
176
|
-
var id = 999;
|
|
177
|
-
var request = new Update{Feature}Request("Updated");
|
|
178
|
-
|
|
179
|
-
_repository.GetByIdAsync(id, Arg.Any<CancellationToken>())
|
|
180
|
-
.Returns((Domain.Entities.{Feature}?)null);
|
|
181
|
-
|
|
182
|
-
// Act
|
|
183
|
-
var act = async () => await _sut.UpdateAsync(id, request);
|
|
184
|
-
|
|
185
|
-
// Assert
|
|
186
|
-
await act.Should().ThrowAsync<{Feature}NotFoundException>();
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
#endregion
|
|
190
|
-
|
|
191
|
-
#region DeleteAsync Tests
|
|
192
|
-
|
|
193
|
-
[Fact]
|
|
194
|
-
public async Task DeleteAsync_WithValidId_DeletesEntity()
|
|
195
|
-
{
|
|
196
|
-
// Arrange
|
|
197
|
-
var id = 1;
|
|
198
|
-
var entity = CreateTestEntity(id, "To Delete");
|
|
199
|
-
|
|
200
|
-
_repository.GetByIdAsync(id, Arg.Any<CancellationToken>())
|
|
201
|
-
.Returns(entity);
|
|
202
|
-
|
|
203
|
-
// Act
|
|
204
|
-
await _sut.DeleteAsync(id);
|
|
205
|
-
|
|
206
|
-
// Assert
|
|
207
|
-
_repository.Received(1).Remove(entity);
|
|
208
|
-
await _repository.Received(1).SaveChangesAsync(Arg.Any<CancellationToken>());
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
[Fact]
|
|
212
|
-
public async Task DeleteAsync_WithNonExistentId_ThrowsNotFoundException()
|
|
213
|
-
{
|
|
214
|
-
// Arrange
|
|
215
|
-
var id = 999;
|
|
216
|
-
|
|
217
|
-
_repository.GetByIdAsync(id, Arg.Any<CancellationToken>())
|
|
218
|
-
.Returns((Domain.Entities.{Feature}?)null);
|
|
219
|
-
|
|
220
|
-
// Act
|
|
221
|
-
var act = async () => await _sut.DeleteAsync(id);
|
|
222
|
-
|
|
223
|
-
// Assert
|
|
224
|
-
await act.Should().ThrowAsync<{Feature}NotFoundException>();
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
#endregion
|
|
228
|
-
|
|
229
|
-
#region Helper Methods
|
|
230
|
-
|
|
231
|
-
private static Domain.Entities.{Feature} CreateTestEntity(int id, string name)
|
|
232
|
-
{
|
|
233
|
-
return Domain.Entities.{Feature}.Create(name);
|
|
234
|
-
// Note: In real tests, you might need reflection to set the Id
|
|
235
|
-
// or use a factory method that accepts an id for testing
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
#endregion
|
|
239
|
-
}
|
|
1
|
+
// ============================================================
|
|
2
|
+
// TEST TEMPLATE
|
|
3
|
+
// Generated by MORPH Framework
|
|
4
|
+
// ============================================================
|
|
5
|
+
|
|
6
|
+
using FluentAssertions;
|
|
7
|
+
using Microsoft.Extensions.Logging;
|
|
8
|
+
using NSubstitute;
|
|
9
|
+
using Xunit;
|
|
10
|
+
|
|
11
|
+
namespace MyProject.Tests.Unit.Features.{Feature};
|
|
12
|
+
|
|
13
|
+
/// <summary>
|
|
14
|
+
/// Unit tests for {Feature}Service.
|
|
15
|
+
/// </summary>
|
|
16
|
+
public class {Feature}ServiceTests
|
|
17
|
+
{
|
|
18
|
+
private readonly I{Feature}Repository _repository;
|
|
19
|
+
private readonly ILogger<{Feature}Service> _logger;
|
|
20
|
+
private readonly {Feature}Service _sut;
|
|
21
|
+
|
|
22
|
+
public {Feature}ServiceTests()
|
|
23
|
+
{
|
|
24
|
+
_repository = Substitute.For<I{Feature}Repository>();
|
|
25
|
+
_logger = Substitute.For<ILogger<{Feature}Service>>();
|
|
26
|
+
_sut = new {Feature}Service(_repository, _logger);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
#region GetByIdAsync Tests
|
|
30
|
+
|
|
31
|
+
[Fact]
|
|
32
|
+
public async Task GetByIdAsync_WithValidId_ReturnsDto()
|
|
33
|
+
{
|
|
34
|
+
// Arrange
|
|
35
|
+
var id = 1;
|
|
36
|
+
var entity = CreateTestEntity(id, "Test {Feature}");
|
|
37
|
+
_repository.GetByIdAsync(id, Arg.Any<CancellationToken>())
|
|
38
|
+
.Returns(entity);
|
|
39
|
+
|
|
40
|
+
// Act
|
|
41
|
+
var result = await _sut.GetByIdAsync(id);
|
|
42
|
+
|
|
43
|
+
// Assert
|
|
44
|
+
result.Should().NotBeNull();
|
|
45
|
+
result!.Id.Should().Be(id);
|
|
46
|
+
result.Name.Should().Be("Test {Feature}");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
[Fact]
|
|
50
|
+
public async Task GetByIdAsync_WithInvalidId_ReturnsNull()
|
|
51
|
+
{
|
|
52
|
+
// Arrange
|
|
53
|
+
var id = 999;
|
|
54
|
+
_repository.GetByIdAsync(id, Arg.Any<CancellationToken>())
|
|
55
|
+
.Returns((Domain.Entities.{Feature}?)null);
|
|
56
|
+
|
|
57
|
+
// Act
|
|
58
|
+
var result = await _sut.GetByIdAsync(id);
|
|
59
|
+
|
|
60
|
+
// Assert
|
|
61
|
+
result.Should().BeNull();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
#endregion
|
|
65
|
+
|
|
66
|
+
#region GetAllAsync Tests
|
|
67
|
+
|
|
68
|
+
[Fact]
|
|
69
|
+
public async Task GetAllAsync_ReturnsAllItems()
|
|
70
|
+
{
|
|
71
|
+
// Arrange
|
|
72
|
+
var entities = new List<Domain.Entities.{Feature}>
|
|
73
|
+
{
|
|
74
|
+
CreateTestEntity(1, "First"),
|
|
75
|
+
CreateTestEntity(2, "Second"),
|
|
76
|
+
CreateTestEntity(3, "Third")
|
|
77
|
+
};
|
|
78
|
+
_repository.GetAllAsync(Arg.Any<CancellationToken>())
|
|
79
|
+
.Returns(entities);
|
|
80
|
+
|
|
81
|
+
// Act
|
|
82
|
+
var result = await _sut.GetAllAsync();
|
|
83
|
+
|
|
84
|
+
// Assert
|
|
85
|
+
result.Should().HaveCount(3);
|
|
86
|
+
result[0].Name.Should().Be("First");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
[Fact]
|
|
90
|
+
public async Task GetAllAsync_WithNoItems_ReturnsEmptyList()
|
|
91
|
+
{
|
|
92
|
+
// Arrange
|
|
93
|
+
_repository.GetAllAsync(Arg.Any<CancellationToken>())
|
|
94
|
+
.Returns(new List<Domain.Entities.{Feature}>());
|
|
95
|
+
|
|
96
|
+
// Act
|
|
97
|
+
var result = await _sut.GetAllAsync();
|
|
98
|
+
|
|
99
|
+
// Assert
|
|
100
|
+
result.Should().BeEmpty();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
#endregion
|
|
104
|
+
|
|
105
|
+
#region CreateAsync Tests
|
|
106
|
+
|
|
107
|
+
[Fact]
|
|
108
|
+
public async Task CreateAsync_WithValidRequest_CreatesAndReturnsDto()
|
|
109
|
+
{
|
|
110
|
+
// Arrange
|
|
111
|
+
var request = new Create{Feature}Request("New {Feature}");
|
|
112
|
+
|
|
113
|
+
_repository
|
|
114
|
+
.When(x => x.AddAsync(Arg.Any<Domain.Entities.{Feature}>(), Arg.Any<CancellationToken>()))
|
|
115
|
+
.Do(x =>
|
|
116
|
+
{
|
|
117
|
+
// Simulate ID assignment
|
|
118
|
+
var entity = x.Arg<Domain.Entities.{Feature}>();
|
|
119
|
+
// entity.Id would be set by EF Core
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// Act
|
|
123
|
+
var result = await _sut.CreateAsync(request);
|
|
124
|
+
|
|
125
|
+
// Assert
|
|
126
|
+
result.Should().NotBeNull();
|
|
127
|
+
result.Name.Should().Be("New {Feature}");
|
|
128
|
+
|
|
129
|
+
await _repository.Received(1).AddAsync(Arg.Any<Domain.Entities.{Feature}>(), Arg.Any<CancellationToken>());
|
|
130
|
+
await _repository.Received(1).SaveChangesAsync(Arg.Any<CancellationToken>());
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
[Theory]
|
|
134
|
+
[InlineData("")]
|
|
135
|
+
[InlineData(" ")]
|
|
136
|
+
[InlineData(null)]
|
|
137
|
+
public async Task CreateAsync_WithInvalidName_ThrowsValidationException(string? name)
|
|
138
|
+
{
|
|
139
|
+
// Arrange
|
|
140
|
+
var request = new Create{Feature}Request(name!);
|
|
141
|
+
|
|
142
|
+
// Act
|
|
143
|
+
var act = async () => await _sut.CreateAsync(request);
|
|
144
|
+
|
|
145
|
+
// Assert
|
|
146
|
+
await act.Should().ThrowAsync<ValidationException>();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
#endregion
|
|
150
|
+
|
|
151
|
+
#region UpdateAsync Tests
|
|
152
|
+
|
|
153
|
+
[Fact]
|
|
154
|
+
public async Task UpdateAsync_WithValidRequest_UpdatesEntity()
|
|
155
|
+
{
|
|
156
|
+
// Arrange
|
|
157
|
+
var id = 1;
|
|
158
|
+
var entity = CreateTestEntity(id, "Original");
|
|
159
|
+
var request = new Update{Feature}Request("Updated");
|
|
160
|
+
|
|
161
|
+
_repository.GetByIdAsync(id, Arg.Any<CancellationToken>())
|
|
162
|
+
.Returns(entity);
|
|
163
|
+
|
|
164
|
+
// Act
|
|
165
|
+
await _sut.UpdateAsync(id, request);
|
|
166
|
+
|
|
167
|
+
// Assert
|
|
168
|
+
_repository.Received(1).Update(Arg.Any<Domain.Entities.{Feature}>());
|
|
169
|
+
await _repository.Received(1).SaveChangesAsync(Arg.Any<CancellationToken>());
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
[Fact]
|
|
173
|
+
public async Task UpdateAsync_WithNonExistentId_ThrowsNotFoundException()
|
|
174
|
+
{
|
|
175
|
+
// Arrange
|
|
176
|
+
var id = 999;
|
|
177
|
+
var request = new Update{Feature}Request("Updated");
|
|
178
|
+
|
|
179
|
+
_repository.GetByIdAsync(id, Arg.Any<CancellationToken>())
|
|
180
|
+
.Returns((Domain.Entities.{Feature}?)null);
|
|
181
|
+
|
|
182
|
+
// Act
|
|
183
|
+
var act = async () => await _sut.UpdateAsync(id, request);
|
|
184
|
+
|
|
185
|
+
// Assert
|
|
186
|
+
await act.Should().ThrowAsync<{Feature}NotFoundException>();
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
#endregion
|
|
190
|
+
|
|
191
|
+
#region DeleteAsync Tests
|
|
192
|
+
|
|
193
|
+
[Fact]
|
|
194
|
+
public async Task DeleteAsync_WithValidId_DeletesEntity()
|
|
195
|
+
{
|
|
196
|
+
// Arrange
|
|
197
|
+
var id = 1;
|
|
198
|
+
var entity = CreateTestEntity(id, "To Delete");
|
|
199
|
+
|
|
200
|
+
_repository.GetByIdAsync(id, Arg.Any<CancellationToken>())
|
|
201
|
+
.Returns(entity);
|
|
202
|
+
|
|
203
|
+
// Act
|
|
204
|
+
await _sut.DeleteAsync(id);
|
|
205
|
+
|
|
206
|
+
// Assert
|
|
207
|
+
_repository.Received(1).Remove(entity);
|
|
208
|
+
await _repository.Received(1).SaveChangesAsync(Arg.Any<CancellationToken>());
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
[Fact]
|
|
212
|
+
public async Task DeleteAsync_WithNonExistentId_ThrowsNotFoundException()
|
|
213
|
+
{
|
|
214
|
+
// Arrange
|
|
215
|
+
var id = 999;
|
|
216
|
+
|
|
217
|
+
_repository.GetByIdAsync(id, Arg.Any<CancellationToken>())
|
|
218
|
+
.Returns((Domain.Entities.{Feature}?)null);
|
|
219
|
+
|
|
220
|
+
// Act
|
|
221
|
+
var act = async () => await _sut.DeleteAsync(id);
|
|
222
|
+
|
|
223
|
+
// Assert
|
|
224
|
+
await act.Should().ThrowAsync<{Feature}NotFoundException>();
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
#endregion
|
|
228
|
+
|
|
229
|
+
#region Helper Methods
|
|
230
|
+
|
|
231
|
+
private static Domain.Entities.{Feature} CreateTestEntity(int id, string name)
|
|
232
|
+
{
|
|
233
|
+
return Domain.Entities.{Feature}.Create(name);
|
|
234
|
+
// Note: In real tests, you might need reflection to set the Id
|
|
235
|
+
// or use a factory method that accepts an id for testing
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
#endregion
|
|
239
|
+
}
|