feat: add admin backfill-versions endpoint for documents without history
Adds POST /api/admin/backfill-versions which creates an initial snapshot (editorName="Datenimport", changedFields=[]) for every document that has no version entry yet, using the document's createdAt as the version timestamp. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
package org.raddatz.familienarchiv.controller;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.raddatz.familienarchiv.config.SecurityConfig;
|
||||
import org.raddatz.familienarchiv.model.Document;
|
||||
import org.raddatz.familienarchiv.security.PermissionAspect;
|
||||
import org.raddatz.familienarchiv.service.CustomUserDetailsService;
|
||||
import org.raddatz.familienarchiv.service.DocumentService;
|
||||
import org.raddatz.familienarchiv.service.DocumentVersionService;
|
||||
import org.raddatz.familienarchiv.service.MassImportService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.aop.AopAutoConfiguration;
|
||||
import org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.security.test.context.support.WithMockUser;
|
||||
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.anyList;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
@WebMvcTest(AdminController.class)
|
||||
@Import({SecurityConfig.class, PermissionAspect.class, AopAutoConfiguration.class})
|
||||
class AdminControllerTest {
|
||||
|
||||
@Autowired MockMvc mockMvc;
|
||||
|
||||
@MockitoBean MassImportService massImportService;
|
||||
@MockitoBean DocumentService documentService;
|
||||
@MockitoBean DocumentVersionService documentVersionService;
|
||||
@MockitoBean CustomUserDetailsService customUserDetailsService;
|
||||
|
||||
@Test
|
||||
void backfillVersions_returns401_whenUnauthenticated() throws Exception {
|
||||
mockMvc.perform(post("/api/admin/backfill-versions"))
|
||||
.andExpect(status().isUnauthorized());
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser(roles = "USER")
|
||||
void backfillVersions_returns403_whenNotAdmin() throws Exception {
|
||||
mockMvc.perform(post("/api/admin/backfill-versions"))
|
||||
.andExpect(status().isForbidden());
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser(authorities = "ADMIN")
|
||||
void backfillVersions_returns200_withCount_whenAdmin() throws Exception {
|
||||
when(documentService.getDocumentsWithoutVersions()).thenReturn(List.of(Document.builder().build()));
|
||||
when(documentVersionService.backfillMissingVersions(anyList())).thenReturn(1);
|
||||
|
||||
mockMvc.perform(post("/api/admin/backfill-versions"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.count").value(1));
|
||||
}
|
||||
}
|
||||
@@ -304,6 +304,76 @@ class DocumentVersionServiceTest {
|
||||
.isInstanceOf(DomainException.class);
|
||||
}
|
||||
|
||||
// ─── backfillMissingVersions ──────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
void backfill_createsVersion_withEditorNameDatenimport() {
|
||||
Document doc = minimalDocument();
|
||||
when(versionRepository.findByDocumentIdOrderBySavedAtAsc(doc.getId())).thenReturn(List.of());
|
||||
when(versionRepository.save(any())).thenAnswer(inv -> inv.getArgument(0));
|
||||
|
||||
versionService.backfillMissingVersions(List.of(doc));
|
||||
|
||||
ArgumentCaptor<DocumentVersion> captor = ArgumentCaptor.forClass(DocumentVersion.class);
|
||||
verify(versionRepository).save(captor.capture());
|
||||
assertThat(captor.getValue().getEditorName()).isEqualTo("Datenimport");
|
||||
}
|
||||
|
||||
@Test
|
||||
void backfill_usesDocumentCreatedAt_asSavedAt() {
|
||||
LocalDateTime createdAt = LocalDateTime.of(2020, 3, 15, 10, 0);
|
||||
Document doc = Document.builder()
|
||||
.id(UUID.randomUUID()).title("T").createdAt(createdAt).build();
|
||||
when(versionRepository.findByDocumentIdOrderBySavedAtAsc(doc.getId())).thenReturn(List.of());
|
||||
when(versionRepository.save(any())).thenAnswer(inv -> inv.getArgument(0));
|
||||
|
||||
versionService.backfillMissingVersions(List.of(doc));
|
||||
|
||||
ArgumentCaptor<DocumentVersion> captor = ArgumentCaptor.forClass(DocumentVersion.class);
|
||||
verify(versionRepository).save(captor.capture());
|
||||
assertThat(captor.getValue().getSavedAt()).isEqualTo(createdAt);
|
||||
}
|
||||
|
||||
@Test
|
||||
void backfill_setsChangedFieldsEmpty() {
|
||||
Document doc = minimalDocument();
|
||||
when(versionRepository.findByDocumentIdOrderBySavedAtAsc(doc.getId())).thenReturn(List.of());
|
||||
when(versionRepository.save(any())).thenAnswer(inv -> inv.getArgument(0));
|
||||
|
||||
versionService.backfillMissingVersions(List.of(doc));
|
||||
|
||||
ArgumentCaptor<DocumentVersion> captor = ArgumentCaptor.forClass(DocumentVersion.class);
|
||||
verify(versionRepository).save(captor.capture());
|
||||
assertThat(captor.getValue().getChangedFields()).isEqualTo("[]");
|
||||
}
|
||||
|
||||
@Test
|
||||
void backfill_skipsDocuments_thatAlreadyHaveVersions() {
|
||||
Document doc = minimalDocument();
|
||||
DocumentVersion existing = DocumentVersion.builder()
|
||||
.id(UUID.randomUUID()).documentId(doc.getId()).snapshot("{}")
|
||||
.changedFields("[]").editorName("user").savedAt(LocalDateTime.now()).build();
|
||||
when(versionRepository.findByDocumentIdOrderBySavedAtAsc(doc.getId())).thenReturn(List.of(existing));
|
||||
|
||||
int count = versionService.backfillMissingVersions(List.of(doc));
|
||||
|
||||
verify(versionRepository, never()).save(any());
|
||||
assertThat(count).isZero();
|
||||
}
|
||||
|
||||
@Test
|
||||
void backfill_returnsCountOfCreatedVersions() {
|
||||
Document d1 = minimalDocument();
|
||||
Document d2 = minimalDocument();
|
||||
when(versionRepository.findByDocumentIdOrderBySavedAtAsc(d1.getId())).thenReturn(List.of());
|
||||
when(versionRepository.findByDocumentIdOrderBySavedAtAsc(d2.getId())).thenReturn(List.of());
|
||||
when(versionRepository.save(any())).thenAnswer(inv -> inv.getArgument(0));
|
||||
|
||||
int count = versionService.backfillMissingVersions(List.of(d1, d2));
|
||||
|
||||
assertThat(count).isEqualTo(2);
|
||||
}
|
||||
|
||||
// ─── helpers ──────────────────────────────────────────────────────────────
|
||||
|
||||
private void authenticateAs(String username) {
|
||||
|
||||
Reference in New Issue
Block a user