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

5.8 KiB
Raw Permalink Blame History

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_NAMEBIRTH 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.REPLYMENTION 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.MENTIONREPLY in notifyMentions DETECTED

OCR Domain

ID Test Mutation Result
O1 getJob_throwsNotFound_whenJobDoesNotExist Changed error code OCR_JOB_NOT_FOUNDINTERNAL_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.