Files
familienarchiv/docs/audits/test-mutation-report.md
Marcel 2394b020ef docs(audit): add mutation test report for 7 Tier-1 service domains
35/35 mutations DETECTED across document, person, tag, user, geschichte,
notification, and OCR domains. No tautological tests found — the suite
is trustworthy on all critical paths. Closes issue #403.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 18:10:17 +02:00

101 lines
5.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Test Mutation Report
**Date:** 2026-05-05
**Branch:** `worktree-test-issue-402-legibility-preflight`
**Method:** Manual targeted mutation (Approach A from issue #403)
**Scope:** 7 Tier-1 backend service domains × 5 mutations each = 35 total
For each mutation: the service method was broken, the paired test was run in isolation, and the result recorded. All mutations were reverted before proceeding to the next.
**Summary: 35/35 DETECTED (100%)**
---
## Document Domain
| ID | Test | Mutation | Result |
|----|------|----------|--------|
| D1 | `deleteDocument_deletesById_whenExists` | Removed `documentRepository.deleteById(id)` call | **DETECTED** |
| D2 | `deleteDocument_throwsNotFound_whenMissing` | Removed `existsById` guard — always proceed to delete | **DETECTED** |
| D3 | `deleteTagCascading_removesTagFromAllDocumentsAndDeletesTag` | Removed `tagService.delete(tagId)` at end of cascade | **DETECTED** |
| D4 | `updateDocument_setsArchiveBoxAndFolder` | Removed `doc.setArchiveBox(dto.getArchiveBox())` | **DETECTED** |
| D5 | `createDocument_setsFileHashFromUpload_whenFileProvided` | Removed `doc.setFileHash(upload.fileHash())` | **DETECTED** |
---
## Person Domain
| ID | Test | Mutation | Result |
|----|------|----------|--------|
| P1 | `createPerson_savesTrimmedAlias_whenAliasIsNonBlank` | Removed `.trim()` — stored raw whitespace-padded alias | **DETECTED** |
| P2 | `mergePersons_reassignsDocumentsAndDeletesSource` | Removed `personRepository.reassignSender(sourceId, targetId)` | **DETECTED** |
| P3 | `findOrCreateByAlias_createsMaidenNameAlias_whenGebPresent` | Changed `MAIDEN_NAME``BIRTH` alias type | **DETECTED** |
| P4 | `addAlias_savesWithAutoIncrementedSortOrder` | Removed `+1` from `findMaxSortOrder(personId) + 1` | **DETECTED** |
| P5 | `removeAlias_throwsForbidden_whenAliasDoesNotBelongToPerson` | Removed ownership check — allowed cross-person alias deletion | **DETECTED** |
---
## Tag Domain
| ID | Test | Mutation | Result |
|----|------|----------|--------|
| T1 | `update_savesNewName` | Removed `tag.setName(dto.name())` | **DETECTED** |
| T2 | `mergeTags_reassignsDocumentsReparentsChildrenAndDeletesSource` | Removed `tagRepository.reparentChildren(sourceId, targetId)` | **DETECTED** |
| T3 | `deleteWithDescendants_deletesSubtreeDocTagsAndAllTags` | Removed `tagRepository.deleteDocumentTagsByTagIds(ids)` | **DETECTED** |
| T4 | `update_throwsCycleDetected_whenTagIsAncestorOfProposedParent` | Flipped `ancestors.contains(tagId)` to `!ancestors.contains(tagId)` | **DETECTED** |
| T5 | `update_savesColor` | Removed `tag.setColor(dto.color())` | **DETECTED** |
---
## User Domain
| ID | Test | Mutation | Result |
|----|------|----------|--------|
| U1 | `changePassword_updatesHash_whenCurrentPasswordCorrect` | Removed `passwordEncoder.encode()` — stored raw new password | **DETECTED** |
| U2 | `adminUpdateUser_updatesGroups_whenGroupIdsProvided` | Removed `user.setGroups(after)` | **DETECTED** |
| U3 | `updateProfile_allowsSameEmailForSameUser` | Removed ID equality check — threw conflict even for own email | **DETECTED** |
| U4 | `adminUpdateUser_setsPassword_whenNewPasswordProvided` | Removed `passwordEncoder.encode()` in admin password update | **DETECTED** |
| U5 | `updateProfile_setsContactToNull_whenContactIsBlank` | Removed blank→null normalization and trim — stored raw contact | **DETECTED** |
---
## Geschichte Domain
| ID | Test | Mutation | Result |
|----|------|----------|--------|
| G1 | `getById_throws_NOT_FOUND_for_draft_when_user_lacks_BLOG_WRITE` | Removed draft visibility check — exposed drafts to all users | **DETECTED** |
| G2 | `create_sanitizes_body_HTML_dropping_disallowed_tags` | Removed HTML sanitization — returned raw body string | **DETECTED** |
| G3 | `update_sets_publishedAt_when_status_transitions_to_PUBLISHED` | Removed `g.setPublishedAt(LocalDateTime.now())` on PUBLISH | **DETECTED** |
| G4 | `update_clears_publishedAt_when_status_transitions_back_to_DRAFT` | Removed `g.setPublishedAt(null)` on DRAFT transition | **DETECTED** |
| G5 | `delete_throws_NOT_FOUND_when_unknown` | Removed `existsById` guard — silently deleted non-existent IDs | **DETECTED** |
---
## Notification Domain
| ID | Test | Mutation | Result |
|----|------|----------|--------|
| N1 | `notifyReply_createsNotificationForThreadParticipants` | Changed `NotificationType.REPLY``MENTION` in `notifyReply` | **DETECTED** |
| N2 | `markRead_throwsForbidden_whenNotificationBelongsToDifferentUser` | Removed ownership check in `markRead` | **DETECTED** |
| N3 | `markRead_marksNotificationAsRead_whenRecipientMatches` | Removed `notification.setRead(true)` | **DETECTED** |
| N4 | `notifyReply_sendsEmailOnlyToUsersWithReplyNotificationsEnabled` | Removed `isNotifyOnReply()` guard — sent email unconditionally | **DETECTED** |
| N5 | `notifyMentions_createsNotificationPerMentionedUser` | Changed `NotificationType.MENTION``REPLY` in `notifyMentions` | **DETECTED** |
---
## OCR Domain
| ID | Test | Mutation | Result |
|----|------|----------|--------|
| O1 | `getJob_throwsNotFound_whenJobDoesNotExist` | Changed error code `OCR_JOB_NOT_FOUND``INTERNAL_ERROR` | **DETECTED** |
| O2 | `startOcr_throwsBadRequest_whenDocumentIsPlaceholder` | Removed `PLACEHOLDER` status guard | **DETECTED** |
| O3 | `startOcr_throwsServiceUnavailable_whenOcrServiceIsDown` | Removed `ocrHealthClient.isHealthy()` check | **DETECTED** |
| O4 | `startOcr_createsJobAndDispatchesAsync` | Removed `ocrAsyncRunner.runSingleDocument(...)` call | **DETECTED** |
| O5 | `startOcr_updatesScriptType_whenProvided` | Removed `documentService.updateScriptType(documentId, scriptTypeOverride)` | **DETECTED** |
---
## Verdict
**All 35 mutations were DETECTED.** No tautological tests found. TEST-2 (rewrite phase) has no work to do — the suite is already trustworthy on these critical paths.