|
| 1 | +# User Story 2: Edit and Delete - Implementation Checklist |
| 2 | + |
| 3 | +**Feature**: Custom Menu Entries |
| 4 | +**User Story**: US2 - Edit and Delete Menu Entries |
| 5 | +**Tasks**: T036-T056 (21 tasks total) |
| 6 | + |
| 7 | +--- |
| 8 | + |
| 9 | +## 📋 Pre-Implementation Checklist |
| 10 | + |
| 11 | +- [x] Plan.md reviewed and understood |
| 12 | +- [x] Quickstart.md guide ready |
| 13 | +- [x] API contracts reviewed (contracts/menu-entries-api.yaml) |
| 14 | +- [x] Data model understood (data-model.md) |
| 15 | +- [x] Research findings reviewed (research.md) |
| 16 | +- [x] Tasks.md reviewed (T036-T056) |
| 17 | +- [ ] Development environment ready |
| 18 | +- [ ] User Story 1 (US1) implementation verified |
| 19 | + |
| 20 | +--- |
| 21 | + |
| 22 | +## 🧪 Phase 1: Write Tests (TDD - Tests MUST Fail First) |
| 23 | + |
| 24 | +### Service Layer Tests (Tasks T036, T037) |
| 25 | + |
| 26 | +**File**: `src/IoTHub.Portal.Tests.Unit/Infrastructure/Services/MenuEntryServiceTests.cs` |
| 27 | + |
| 28 | +- [ ] T036: `UpdateMenuEntry_ValidData_UpdatesSuccessfully` |
| 29 | +- [ ] T036: `UpdateMenuEntry_DuplicateName_ThrowsResourceAlreadyExistsException` |
| 30 | +- [ ] T036: `UpdateMenuEntry_InvalidUrl_ThrowsArgumentException` |
| 31 | +- [ ] T036: `UpdateMenuEntry_NonExistentId_ThrowsResourceNotFoundException` |
| 32 | +- [ ] T036: `UpdateMenuEntry_NameTooLong_ThrowsArgumentException` |
| 33 | +- [ ] T037: `DeleteMenuEntry_ExistingId_DeletesSuccessfully` |
| 34 | +- [ ] T037: `DeleteMenuEntry_NonExistentId_ThrowsResourceNotFoundException` |
| 35 | + |
| 36 | +**Verification**: Run `dotnet test` → Should see 7 FAILURES ✅ |
| 37 | + |
| 38 | +### Controller Layer Tests (Tasks T038, T039) |
| 39 | + |
| 40 | +**File**: `src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/MenuEntriesControllerTests.cs` |
| 41 | + |
| 42 | +- [ ] T038: `Put_ValidData_Returns200Ok` |
| 43 | +- [ ] T038: `Put_DuplicateName_Returns400BadRequest` |
| 44 | +- [ ] T038: `Put_InvalidModelState_Returns400BadRequest` |
| 45 | +- [ ] T038: `Put_NonExistentId_Returns404NotFound` |
| 46 | +- [ ] T039: `Delete_ExistingId_Returns204NoContent` |
| 47 | +- [ ] T039: `Delete_NonExistentId_Returns404NotFound` |
| 48 | + |
| 49 | +**Verification**: Run `dotnet test` → Should see 6 MORE FAILURES ✅ |
| 50 | + |
| 51 | +--- |
| 52 | + |
| 53 | +## 🔧 Phase 2: Backend Implementation |
| 54 | + |
| 55 | +### Service Interface (Task T040) |
| 56 | + |
| 57 | +**File**: `src/IoTHub.Portal.Application/Services/IMenuEntryService.cs` |
| 58 | + |
| 59 | +- [ ] T040: Verify `UpdateMenuEntryAsync(MenuEntryDto dto)` method signature exists |
| 60 | +- [ ] T040: Verify `DeleteMenuEntryAsync(string id)` method signature exists |
| 61 | + |
| 62 | +### Service Implementation (Tasks T041, T042) |
| 63 | + |
| 64 | +**File**: `src/IoTHub.Portal.Infrastructure/Services/MenuEntryService.cs` |
| 65 | + |
| 66 | +- [ ] T041: Implement `UpdateMenuEntryAsync()` with: |
| 67 | + - [ ] Existence check (throw ResourceNotFoundException if not found) |
| 68 | + - [ ] Name validation (required, max 100 chars) |
| 69 | + - [ ] Duplicate name check (exclude current ID) |
| 70 | + - [ ] URL format validation (HTTP/HTTPS or relative path) |
| 71 | + - [ ] Auto-detect IsExternal |
| 72 | + - [ ] Update UpdatedAt timestamp |
| 73 | + - [ ] Preserve CreatedAt |
| 74 | + - [ ] Call repository.Update() and unitOfWork.SaveAsync() |
| 75 | + |
| 76 | +- [ ] T042: Implement `DeleteMenuEntryAsync()` with: |
| 77 | + - [ ] Existence check (throw ResourceNotFoundException if not found) |
| 78 | + - [ ] Call repository.Delete() and unitOfWork.SaveAsync() |
| 79 | + - [ ] Add logging |
| 80 | + |
| 81 | +**Verification**: Run `dotnet test` → Service tests should PASS ✅ |
| 82 | + |
| 83 | +### Controller Implementation (Tasks T043, T044, T045, T046) |
| 84 | + |
| 85 | +**File**: `src/IoTHub.Portal.Server/Controllers/v1.0/MenuEntriesController.cs` |
| 86 | + |
| 87 | +- [ ] T043: Verify GET by ID endpoint exists with `[Authorize("menuentry:read")]` |
| 88 | +- [ ] T044: Implement/enhance PUT endpoint with: |
| 89 | + - [ ] `[HttpPut("{id}")]` attribute |
| 90 | + - [ ] `[Authorize("menuentry:write")]` attribute |
| 91 | + - [ ] ModelState validation check |
| 92 | + - [ ] ID mismatch check (URL id vs body id) |
| 93 | + - [ ] Return 200 OK with updated DTO |
| 94 | + - [ ] Proper XML documentation |
| 95 | + |
| 96 | +- [ ] T045: Implement/enhance DELETE endpoint with: |
| 97 | + - [ ] `[HttpDelete("{id}")]` attribute |
| 98 | + - [ ] `[Authorize("menuentry:write")]` attribute |
| 99 | + - [ ] Return 204 No Content on success |
| 100 | + - [ ] Proper XML documentation |
| 101 | + |
| 102 | +- [ ] T046: Enhance error handling for all endpoints: |
| 103 | + - [ ] 404 Not Found for ResourceNotFoundException |
| 104 | + - [ ] 400 Bad Request for validation errors |
| 105 | + - [ ] 400 Bad Request for ResourceAlreadyExistsException |
| 106 | + - [ ] ProblemDetails format for all errors |
| 107 | + |
| 108 | +**Verification**: Run `dotnet test` → Controller tests should PASS ✅ |
| 109 | + |
| 110 | +--- |
| 111 | + |
| 112 | +## 🎨 Phase 3: Frontend Implementation |
| 113 | + |
| 114 | +### Client Service Interface (Task T047) |
| 115 | + |
| 116 | +**File**: `src/IoTHub.Portal.Client/Services/IMenuEntryClientService.cs` |
| 117 | + |
| 118 | +- [ ] T047: Verify `GetByIdAsync(string id)` method exists |
| 119 | +- [ ] T047: Verify `UpdateAsync(MenuEntryDto dto)` method exists |
| 120 | +- [ ] T047: Verify `DeleteAsync(string id)` method exists |
| 121 | + |
| 122 | +### Client Service Implementation (Task T048) |
| 123 | + |
| 124 | +**File**: `src/IoTHub.Portal.Client/Services/MenuEntryClientService.cs` |
| 125 | + |
| 126 | +- [ ] T048: Implement `GetByIdAsync()` with GET request to `/api/menu-entries/{id}` |
| 127 | +- [ ] T048: Implement `UpdateAsync()` with PUT request to `/api/menu-entries/{id}` |
| 128 | +- [ ] T048: Implement `DeleteAsync()` with DELETE request to `/api/menu-entries/{id}` |
| 129 | +- [ ] T048: Ensure proper error propagation (EnsureSuccessStatusCode) |
| 130 | + |
| 131 | +### Edit Dialog (Tasks T049, T050) |
| 132 | + |
| 133 | +**File**: `src/IoTHub.Portal.Client/Dialogs/MenuEntries/EditMenuEntryDialog.razor` |
| 134 | + |
| 135 | +- [ ] T049: Verify dialog component exists (may be from US1) |
| 136 | +- [ ] T050: Add/enhance form validation: |
| 137 | + - [ ] Real-time character counter for Name (max 100) |
| 138 | + - [ ] URL format validation on blur |
| 139 | + - [ ] External/Internal indicator chip |
| 140 | + - [ ] Disabled Save button when form invalid |
| 141 | + - [ ] Loading indicator during save |
| 142 | + - [ ] Error/success Snackbar notifications |
| 143 | + |
| 144 | +### List Page Enhancements (Tasks T051, T052, T053) |
| 145 | + |
| 146 | +**File**: `src/IoTHub.Portal.Client/Pages/MenuEntries/MenuEntryListPage.razor` |
| 147 | + |
| 148 | +- [ ] T051: Add Edit button to each table row |
| 149 | +- [ ] T051: Implement `OpenEditDialog(MenuEntryDto entry)` method |
| 150 | +- [ ] T051: Refresh list on dialog close with success |
| 151 | + |
| 152 | +- [ ] T052: Add Delete button to each table row |
| 153 | +- [ ] T052: Implement `OpenDeleteConfirmation(MenuEntryDto entry)` method |
| 154 | +- [ ] T052: Show confirmation dialog with entry name |
| 155 | +- [ ] T052: Implement `DeleteEntry(string id)` method |
| 156 | +- [ ] T052: Call delete API only after confirmation |
| 157 | + |
| 158 | +- [ ] T053: Implement list refresh logic: |
| 159 | + - [ ] Refresh after successful edit |
| 160 | + - [ ] Refresh after successful delete |
| 161 | + - [ ] Show success Snackbar on operations |
| 162 | + - [ ] Show error Snackbar on failures |
| 163 | + |
| 164 | +### Navigation Menu (Task T054) |
| 165 | + |
| 166 | +**File**: `src/IoTHub.Portal.Client/Shared/NavMenu.razor` |
| 167 | + |
| 168 | +- [ ] T054: Add try-catch in `OnInitializedAsync()` |
| 169 | +- [ ] T054: Gracefully handle missing entries (log error, continue rendering) |
| 170 | +- [ ] T054: Reload menu entries on navigation |
| 171 | + |
| 172 | +**Verification**: Manual testing - all UI flows should work ✅ |
| 173 | + |
| 174 | +--- |
| 175 | + |
| 176 | +## 🧪 Phase 4: Client Tests |
| 177 | + |
| 178 | +### Edit Dialog Tests (Task T055) |
| 179 | + |
| 180 | +**File**: `src/IoTHub.Portal.Tests.Unit/Client/Dialogs/MenuEntries/EditMenuEntryDialogTests.cs` |
| 181 | + |
| 182 | +- [ ] T055: `Dialog_ValidData_SubmitsSuccessfully` |
| 183 | +- [ ] T055: `Dialog_InvalidUrl_ShowsValidationError` |
| 184 | +- [ ] T055: `Dialog_NameTooLong_ShowsValidationError` |
| 185 | +- [ ] T055: `Dialog_ClosesOnSuccess` |
| 186 | + |
| 187 | +### List Page Tests (Task T056) |
| 188 | + |
| 189 | +**File**: `src/IoTHub.Portal.Tests.Unit/Client/Pages/MenuEntries/MenuEntryListPageTests.cs` |
| 190 | + |
| 191 | +- [ ] T056: `EditButton_Click_OpensDialogWithCorrectData` |
| 192 | +- [ ] T056: `DeleteButton_Click_ShowsConfirmationDialog` |
| 193 | +- [ ] T056: `DeleteConfirmed_CallsApiAndRefreshesList` |
| 194 | +- [ ] T056: `DeleteCancelled_DoesNotCallApi` |
| 195 | + |
| 196 | +**Verification**: Run `dotnet test` → All tests (including client) should PASS ✅ |
| 197 | + |
| 198 | +--- |
| 199 | + |
| 200 | +## ✅ Phase 5: Manual Testing & Validation |
| 201 | + |
| 202 | +### Manual Testing Scenarios |
| 203 | + |
| 204 | +- [ ] **Test 1: Edit menu entry name** |
| 205 | + - [ ] Navigate to Menu Entry management page |
| 206 | + - [ ] Click Edit button for an existing entry |
| 207 | + - [ ] Change name from "Old Name" to "New Name" |
| 208 | + - [ ] Click Save |
| 209 | + - [ ] Verify success message appears |
| 210 | + - [ ] Verify "New Name" appears in list |
| 211 | + - [ ] Verify navigation menu shows "New Name" |
| 212 | + |
| 213 | +- [ ] **Test 2: Edit menu entry URL** |
| 214 | + - [ ] Click Edit button for an entry |
| 215 | + - [ ] Change URL from old to new |
| 216 | + - [ ] Click Save |
| 217 | + - [ ] Verify success message |
| 218 | + - [ ] Verify clicking link goes to new URL |
| 219 | + |
| 220 | +- [ ] **Test 3: Delete menu entry** |
| 221 | + - [ ] Click Delete button |
| 222 | + - [ ] Verify confirmation dialog appears with entry name |
| 223 | + - [ ] Click Cancel → verify entry still exists |
| 224 | + - [ ] Click Delete button again |
| 225 | + - [ ] Click Delete in confirmation → verify success message |
| 226 | + - [ ] Verify entry removed from list |
| 227 | + - [ ] Verify entry removed from navigation menu |
| 228 | + |
| 229 | +- [ ] **Test 4: Edit with duplicate name** |
| 230 | + - [ ] Create two entries: "Entry A" and "Entry B" |
| 231 | + - [ ] Edit "Entry A" to have name "Entry B" |
| 232 | + - [ ] Verify error message about duplicate name |
| 233 | + - [ ] Verify entry NOT updated |
| 234 | + |
| 235 | +- [ ] **Test 5: Edit with invalid URL** |
| 236 | + - [ ] Click Edit on entry |
| 237 | + - [ ] Change URL to "invalid-url" |
| 238 | + - [ ] Verify inline validation error |
| 239 | + - [ ] Verify Save button disabled |
| 240 | + |
| 241 | +- [ ] **Test 6: Authorization check** |
| 242 | + - [ ] Log in as user without `menuentry:write` permission |
| 243 | + - [ ] Verify Edit/Delete buttons hidden or disabled |
| 244 | + - [ ] (Optional) Try API call → verify 401 Unauthorized |
| 245 | + |
| 246 | +### Acceptance Criteria Validation |
| 247 | + |
| 248 | +- [ ] **AC1: Edit entry name** - updated name appears in navigation |
| 249 | +- [ ] **AC2: Delete entry** - removed from navigation and storage |
| 250 | +- [ ] **AC3: Edit with invalid URL** - validation prevents saving |
| 251 | +- [ ] **AC4: Delete confirmation** - system confirms before removal |
| 252 | + |
| 253 | +--- |
| 254 | + |
| 255 | +## 📊 Phase 6: Code Quality & Review |
| 256 | + |
| 257 | +### Code Quality Checks |
| 258 | + |
| 259 | +- [ ] All unit tests pass (100% success rate) |
| 260 | +- [ ] No compiler warnings |
| 261 | +- [ ] All methods have proper XML documentation |
| 262 | +- [ ] Code follows IoT Hub Portal naming conventions |
| 263 | +- [ ] Authorization attributes present on all endpoints |
| 264 | +- [ ] Error responses use ProblemDetails format |
| 265 | +- [ ] No hardcoded strings (use constants) |
| 266 | +- [ ] Proper async/await patterns used |
| 267 | + |
| 268 | +### Security Review |
| 269 | + |
| 270 | +- [ ] PUT endpoint has `[Authorize("menuentry:write")]` |
| 271 | +- [ ] DELETE endpoint has `[Authorize("menuentry:write")]` |
| 272 | +- [ ] GET by ID has `[Authorize("menuentry:read")]` |
| 273 | +- [ ] URL validation prevents script injection |
| 274 | +- [ ] No SQL injection risk (EF Core parameterized queries) |
| 275 | +- [ ] Input sanitization via DataAnnotations |
| 276 | + |
| 277 | +### Performance Review |
| 278 | + |
| 279 | +- [ ] No N+1 query issues |
| 280 | +- [ ] Async methods used throughout |
| 281 | +- [ ] List refresh doesn't cause UI lag |
| 282 | +- [ ] API operations complete within 200ms |
| 283 | + |
| 284 | +--- |
| 285 | + |
| 286 | +## 🚀 Phase 7: Deployment Preparation |
| 287 | + |
| 288 | +### Pre-Deployment |
| 289 | + |
| 290 | +- [ ] All tests passing |
| 291 | +- [ ] Manual testing complete |
| 292 | +- [ ] Code review approved |
| 293 | +- [ ] Documentation updated |
| 294 | +- [ ] No breaking changes confirmed |
| 295 | +- [ ] Staging environment ready |
| 296 | + |
| 297 | +### Deployment Steps |
| 298 | + |
| 299 | +- [ ] Deploy backend changes (service + controller) |
| 300 | +- [ ] Deploy frontend changes (client service + UI) |
| 301 | +- [ ] Verify staging deployment |
| 302 | +- [ ] Smoke test on staging |
| 303 | +- [ ] Get stakeholder approval |
| 304 | +- [ ] Deploy to production |
| 305 | + |
| 306 | +### Post-Deployment Validation |
| 307 | + |
| 308 | +- [ ] Edit existing entry → success |
| 309 | +- [ ] Delete entry → success + confirmation |
| 310 | +- [ ] Navigation menu reflects changes |
| 311 | +- [ ] Authorization working correctly |
| 312 | +- [ ] No errors in application logs |
| 313 | + |
| 314 | +--- |
| 315 | + |
| 316 | +## 📈 Completion Metrics |
| 317 | + |
| 318 | +### Task Completion |
| 319 | + |
| 320 | +- **Total Tasks**: 21 (T036-T056) |
| 321 | +- **Tests Written**: 21 new unit tests |
| 322 | +- **Files Modified**: ~10 files |
| 323 | +- **Files Created**: ~2 test files |
| 324 | +- **Time Spent**: ___ days |
| 325 | + |
| 326 | +### Quality Metrics |
| 327 | + |
| 328 | +- **Test Pass Rate**: ___% (target: 100%) |
| 329 | +- **Code Coverage**: ___% (existing coverage maintained) |
| 330 | +- **Bugs Found**: ___ |
| 331 | +- **Bugs Fixed**: ___ |
| 332 | + |
| 333 | +--- |
| 334 | + |
| 335 | +## ✅ Final Sign-Off |
| 336 | + |
| 337 | +- [ ] All tasks complete (T036-T056) |
| 338 | +- [ ] All tests passing |
| 339 | +- [ ] All acceptance criteria met |
| 340 | +- [ ] Manual testing complete |
| 341 | +- [ ] Code review approved |
| 342 | +- [ ] Documentation complete |
| 343 | +- [ ] Deployed to staging |
| 344 | +- [ ] Stakeholder approval received |
| 345 | +- [ ] Ready for production deployment |
| 346 | + |
| 347 | +--- |
| 348 | + |
| 349 | +**Implementation Status**: 🚧 IN PROGRESS |
| 350 | +**Started**: ___________ |
| 351 | +**Completed**: ___________ |
| 352 | +**Total Time**: ___________ |
| 353 | + |
| 354 | +--- |
| 355 | + |
| 356 | +*Use this checklist to track progress through User Story 2 implementation* |
0 commit comments