@polymorphism-tech/morph-spec 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +279 -0
- package/bin/morph-spec.js +53 -0
- package/content/.claude/commands/morph-apply.md +66 -0
- package/content/.claude/commands/morph-archive.md +79 -0
- package/content/.claude/commands/morph-costs.md +206 -0
- package/content/.claude/commands/morph-infra.md +209 -0
- package/content/.claude/commands/morph-proposal.md +60 -0
- package/content/.claude/commands/morph-status.md +71 -0
- package/content/.claude/settings.local.json +15 -0
- package/content/.claude/skills/infra/bicep-architect.md +419 -0
- package/content/.claude/skills/infra/container-specialist.md +437 -0
- package/content/.claude/skills/infra/devops-engineer.md +405 -0
- package/content/.claude/skills/integrations/asaas-financial.md +333 -0
- package/content/.claude/skills/integrations/azure-identity.md +309 -0
- package/content/.claude/skills/integrations/clerk-auth.md +290 -0
- package/content/.claude/skills/specialists/azure-architect.md +142 -0
- package/content/.claude/skills/specialists/cost-guardian.md +110 -0
- package/content/.claude/skills/specialists/ef-modeler.md +200 -0
- package/content/.claude/skills/specialists/hangfire-orchestrator.md +245 -0
- package/content/.claude/skills/specialists/ms-agent-expert.md +209 -0
- package/content/.claude/skills/specialists/po-pm-advisor.md +197 -0
- package/content/.claude/skills/specialists/standards-architect.md +78 -0
- package/content/.claude/skills/specialists/ui-ux-designer.md +325 -0
- package/content/.claude/skills/stacks/dotnet-blazor.md +352 -0
- package/content/.claude/skills/stacks/dotnet-nextjs.md +402 -0
- package/content/.claude/skills/stacks/shopify.md +445 -0
- package/content/.morph/archive/.gitkeep +25 -0
- package/content/.morph/config/agents.json +149 -0
- package/content/.morph/config/config.template.json +96 -0
- package/content/.morph/examples/api-nextjs/README.md +241 -0
- package/content/.morph/examples/api-nextjs/contracts.ts +307 -0
- package/content/.morph/examples/api-nextjs/spec.md +399 -0
- package/content/.morph/examples/api-nextjs/tasks.md +168 -0
- package/content/.morph/examples/micro-saas/README.md +125 -0
- package/content/.morph/examples/micro-saas/contracts.cs +358 -0
- package/content/.morph/examples/micro-saas/decisions.md +246 -0
- package/content/.morph/examples/micro-saas/spec.md +236 -0
- package/content/.morph/examples/micro-saas/tasks.md +150 -0
- package/content/.morph/examples/multi-agent/README.md +309 -0
- package/content/.morph/examples/multi-agent/contracts.cs +433 -0
- package/content/.morph/examples/multi-agent/spec.md +479 -0
- package/content/.morph/examples/multi-agent/tasks.md +185 -0
- package/content/.morph/features/.gitkeep +25 -0
- package/content/.morph/project.md +159 -0
- package/content/.morph/specs/.gitkeep +20 -0
- package/content/.morph/standards/architecture.md +190 -0
- package/content/.morph/standards/azure.md +184 -0
- package/content/.morph/standards/coding.md +342 -0
- package/content/.morph/templates/agent.cs +172 -0
- package/content/.morph/templates/component.razor +239 -0
- package/content/.morph/templates/contracts.cs +217 -0
- package/content/.morph/templates/decisions.md +106 -0
- package/content/.morph/templates/infra/app-insights.bicep +63 -0
- package/content/.morph/templates/infra/container-app-env.bicep +49 -0
- package/content/.morph/templates/infra/container-app.bicep +156 -0
- package/content/.morph/templates/infra/key-vault.bicep +91 -0
- package/content/.morph/templates/infra/main.bicep +155 -0
- package/content/.morph/templates/infra/parameters.dev.json +23 -0
- package/content/.morph/templates/infra/parameters.prod.json +23 -0
- package/content/.morph/templates/infra/sql-database.bicep +103 -0
- package/content/.morph/templates/infra/storage.bicep +106 -0
- package/content/.morph/templates/integrations/asaas-client.cs +387 -0
- package/content/.morph/templates/integrations/asaas-webhook.cs +351 -0
- package/content/.morph/templates/integrations/azure-identity-config.cs +288 -0
- package/content/.morph/templates/integrations/clerk-config.cs +258 -0
- package/content/.morph/templates/job.cs +171 -0
- package/content/.morph/templates/migration.cs +83 -0
- package/content/.morph/templates/proposal.md +155 -0
- package/content/.morph/templates/recap.md +105 -0
- package/content/.morph/templates/repository.cs +141 -0
- package/content/.morph/templates/saas/subscription.cs +347 -0
- package/content/.morph/templates/saas/tenant.cs +338 -0
- package/content/.morph/templates/service.cs +139 -0
- package/content/.morph/templates/spec.md +147 -0
- package/content/.morph/templates/tasks.md +235 -0
- package/content/.morph/templates/test.cs +239 -0
- package/content/CLAUDE.md +318 -0
- package/package.json +50 -0
- package/src/commands/doctor.js +132 -0
- package/src/commands/init.js +121 -0
- package/src/commands/update.js +84 -0
- package/src/utils/file-copier.js +50 -0
- package/src/utils/logger.js +32 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# Tasks - {FEATURE_NAME}
|
|
2
|
+
|
|
3
|
+
> Checklist de implementação para a feature.
|
|
4
|
+
> Atualize o status conforme progresso.
|
|
5
|
+
|
|
6
|
+
## Summary
|
|
7
|
+
|
|
8
|
+
| Metric | Value |
|
|
9
|
+
|--------|-------|
|
|
10
|
+
| **Total Tasks** | 0 |
|
|
11
|
+
| **Completed** | 0 |
|
|
12
|
+
| **In Progress** | 0 |
|
|
13
|
+
| **Pending** | 0 |
|
|
14
|
+
| **Estimated** | 0h |
|
|
15
|
+
| **Actual** | 0h |
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Checkpoints
|
|
20
|
+
|
|
21
|
+
| After | Type | Description | Status |
|
|
22
|
+
|-------|------|-------------|--------|
|
|
23
|
+
| T003 | Review | Review contracts and data model | Pending |
|
|
24
|
+
| T006 | Review | Review service implementation | Pending |
|
|
25
|
+
| T009 | Approval | Approve UI before tests | Pending |
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Phase 1: Contracts & Data Model
|
|
30
|
+
|
|
31
|
+
### T001: Define service interfaces
|
|
32
|
+
- **Type:** Contract
|
|
33
|
+
- **Priority:** 1
|
|
34
|
+
- **Estimate:** 30min
|
|
35
|
+
- **Status:** [ ] Pending
|
|
36
|
+
|
|
37
|
+
**Description:**
|
|
38
|
+
Create I{Feature}Service interface with all required methods.
|
|
39
|
+
|
|
40
|
+
**Acceptance Criteria:**
|
|
41
|
+
- [ ] Interface follows naming conventions
|
|
42
|
+
- [ ] All CRUD methods defined
|
|
43
|
+
- [ ] Async methods with CancellationToken
|
|
44
|
+
|
|
45
|
+
**Files:**
|
|
46
|
+
- [ ] Create: `Application/Features/{Feature}/Services/I{Feature}Service.cs`
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
### T002: Define repository interfaces
|
|
51
|
+
- **Type:** Contract
|
|
52
|
+
- **Priority:** 1
|
|
53
|
+
- **Estimate:** 20min
|
|
54
|
+
- **Status:** [ ] Pending
|
|
55
|
+
|
|
56
|
+
**Description:**
|
|
57
|
+
Create I{Feature}Repository interface.
|
|
58
|
+
|
|
59
|
+
**Files:**
|
|
60
|
+
- [ ] Create: `Application/Features/{Feature}/Services/I{Feature}Repository.cs`
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
### T003: Create domain entities
|
|
65
|
+
- **Type:** Domain
|
|
66
|
+
- **Priority:** 1
|
|
67
|
+
- **Estimate:** 45min
|
|
68
|
+
- **Status:** [ ] Pending
|
|
69
|
+
- **Checkpoint:** Review after this task
|
|
70
|
+
|
|
71
|
+
**Description:**
|
|
72
|
+
Create {Feature} entity with factory methods.
|
|
73
|
+
|
|
74
|
+
**Files:**
|
|
75
|
+
- [ ] Create: `Domain/Entities/{Feature}.cs`
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Phase 2: Infrastructure
|
|
80
|
+
|
|
81
|
+
### T004: Create EF configuration and migration
|
|
82
|
+
- **Type:** Infrastructure
|
|
83
|
+
- **Priority:** 2
|
|
84
|
+
- **Estimate:** 30min
|
|
85
|
+
- **Status:** [ ] Pending
|
|
86
|
+
- **Dependencies:** T003
|
|
87
|
+
|
|
88
|
+
**Description:**
|
|
89
|
+
Configure entity mapping and create migration.
|
|
90
|
+
|
|
91
|
+
**Files:**
|
|
92
|
+
- [ ] Create: `Infrastructure/Data/Configurations/{Feature}Configuration.cs`
|
|
93
|
+
- [ ] Create: `Infrastructure/Data/Migrations/{timestamp}_Add{Feature}.cs`
|
|
94
|
+
- [ ] Modify: `Infrastructure/Data/AppDbContext.cs`
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
### T005: Implement repository
|
|
99
|
+
- **Type:** Infrastructure
|
|
100
|
+
- **Priority:** 2
|
|
101
|
+
- **Estimate:** 45min
|
|
102
|
+
- **Status:** [ ] Pending
|
|
103
|
+
- **Dependencies:** T002, T004
|
|
104
|
+
|
|
105
|
+
**Description:**
|
|
106
|
+
Implement I{Feature}Repository.
|
|
107
|
+
|
|
108
|
+
**Files:**
|
|
109
|
+
- [ ] Create: `Infrastructure/Data/Repositories/{Feature}Repository.cs`
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Phase 3: Implementation
|
|
114
|
+
|
|
115
|
+
### T006: Implement service layer
|
|
116
|
+
- **Type:** Service
|
|
117
|
+
- **Priority:** 2
|
|
118
|
+
- **Estimate:** 1h
|
|
119
|
+
- **Status:** [ ] Pending
|
|
120
|
+
- **Dependencies:** T001, T005
|
|
121
|
+
- **Checkpoint:** Review after this task
|
|
122
|
+
|
|
123
|
+
**Description:**
|
|
124
|
+
Implement I{Feature}Service with business logic.
|
|
125
|
+
|
|
126
|
+
**Files:**
|
|
127
|
+
- [ ] Create: `Application/Features/{Feature}/Services/{Feature}Service.cs`
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
### T007: Implement AI agent (if applicable)
|
|
132
|
+
- **Type:** Agent
|
|
133
|
+
- **Priority:** 3
|
|
134
|
+
- **Estimate:** 1h
|
|
135
|
+
- **Status:** [ ] Pending
|
|
136
|
+
- **Dependencies:** T006
|
|
137
|
+
|
|
138
|
+
**Description:**
|
|
139
|
+
Create AI agent for analysis/processing.
|
|
140
|
+
|
|
141
|
+
**Files:**
|
|
142
|
+
- [ ] Create: `Agents/{Feature}Analyzer/{Feature}AnalyzerAgent.cs`
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
### T008: Implement background job (if applicable)
|
|
147
|
+
- **Type:** Job
|
|
148
|
+
- **Priority:** 3
|
|
149
|
+
- **Estimate:** 45min
|
|
150
|
+
- **Status:** [ ] Pending
|
|
151
|
+
- **Dependencies:** T006
|
|
152
|
+
|
|
153
|
+
**Description:**
|
|
154
|
+
Create Hangfire job for scheduled processing.
|
|
155
|
+
|
|
156
|
+
**Files:**
|
|
157
|
+
- [ ] Create: `Application/Features/{Feature}/Jobs/{Feature}ProcessorJob.cs`
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## Phase 4: UI Components
|
|
162
|
+
|
|
163
|
+
### T009: Create list component
|
|
164
|
+
- **Type:** UI
|
|
165
|
+
- **Priority:** 2
|
|
166
|
+
- **Estimate:** 1h
|
|
167
|
+
- **Status:** [ ] Pending
|
|
168
|
+
- **Dependencies:** T006
|
|
169
|
+
- **Checkpoint:** Approval after this task
|
|
170
|
+
|
|
171
|
+
**Description:**
|
|
172
|
+
Create Blazor component to list items.
|
|
173
|
+
|
|
174
|
+
**Files:**
|
|
175
|
+
- [ ] Create: `Web/Components/Pages/{Feature}/{Feature}List.razor`
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
### T010: Create form component
|
|
180
|
+
- **Type:** UI
|
|
181
|
+
- **Priority:** 2
|
|
182
|
+
- **Estimate:** 1h
|
|
183
|
+
- **Status:** [ ] Pending
|
|
184
|
+
- **Dependencies:** T009
|
|
185
|
+
|
|
186
|
+
**Description:**
|
|
187
|
+
Create Blazor component for create/edit.
|
|
188
|
+
|
|
189
|
+
**Files:**
|
|
190
|
+
- [ ] Create: `Web/Components/Pages/{Feature}/{Feature}Form.razor`
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## Phase 5: Testing
|
|
195
|
+
|
|
196
|
+
### T011: Write unit tests
|
|
197
|
+
- **Type:** Test
|
|
198
|
+
- **Priority:** 2
|
|
199
|
+
- **Estimate:** 1h
|
|
200
|
+
- **Status:** [ ] Pending
|
|
201
|
+
- **Dependencies:** T006
|
|
202
|
+
|
|
203
|
+
**Description:**
|
|
204
|
+
Unit tests for service layer.
|
|
205
|
+
|
|
206
|
+
**Files:**
|
|
207
|
+
- [ ] Create: `Tests/Unit/{Feature}ServiceTests.cs`
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
### T012: Write integration tests
|
|
212
|
+
- **Type:** Test
|
|
213
|
+
- **Priority:** 3
|
|
214
|
+
- **Estimate:** 1h
|
|
215
|
+
- **Status:** [ ] Pending
|
|
216
|
+
- **Dependencies:** T011
|
|
217
|
+
|
|
218
|
+
**Description:**
|
|
219
|
+
Integration tests for repository and API.
|
|
220
|
+
|
|
221
|
+
**Files:**
|
|
222
|
+
- [ ] Create: `Tests/Integration/{Feature}IntegrationTests.cs`
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## Status Legend
|
|
227
|
+
|
|
228
|
+
- [ ] Pending
|
|
229
|
+
- [~] In Progress
|
|
230
|
+
- [x] Completed
|
|
231
|
+
- [!] Blocked
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
*Generated by MORPH Framework*
|
|
@@ -0,0 +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
|
+
}
|