From 662b6f1d3758dd5ec9387840aca426d0b4787c18 Mon Sep 17 00:00:00 2001 From: jyt6640 Date: Wed, 12 Aug 2026 20:15:49 +0900 Subject: [PATCH 1/2] =?UTF-8?q?test:=20=EB=AC=B8=EC=84=9C=20=EC=B0=B8?= =?UTF-8?q?=EC=A1=B0=20Repository=20=ED=86=B5=ED=95=A9=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fixture/DocumentReferenceFixture.java | 18 + .../DocumentReferenceRepositoryTest.java | 315 ++++++++++++++++++ 2 files changed, 333 insertions(+) create mode 100644 src/test/java/com/wooteco/wiki/graph/fixture/DocumentReferenceFixture.java create mode 100644 src/test/java/com/wooteco/wiki/graph/repository/DocumentReferenceRepositoryTest.java diff --git a/src/test/java/com/wooteco/wiki/graph/fixture/DocumentReferenceFixture.java b/src/test/java/com/wooteco/wiki/graph/fixture/DocumentReferenceFixture.java new file mode 100644 index 0000000..7320319 --- /dev/null +++ b/src/test/java/com/wooteco/wiki/graph/fixture/DocumentReferenceFixture.java @@ -0,0 +1,18 @@ +package com.wooteco.wiki.graph.fixture; + +import com.wooteco.wiki.document.domain.CrewDocument; +import com.wooteco.wiki.document.domain.Document; +import com.wooteco.wiki.graph.domain.DocumentReference; + +public final class DocumentReferenceFixture { + + public static DocumentReference create( + CrewDocument sourceDocument, + Document targetDocument + ) { + return DocumentReference.of(sourceDocument, targetDocument); + } + + private DocumentReferenceFixture() { + } +} diff --git a/src/test/java/com/wooteco/wiki/graph/repository/DocumentReferenceRepositoryTest.java b/src/test/java/com/wooteco/wiki/graph/repository/DocumentReferenceRepositoryTest.java new file mode 100644 index 0000000..67b0d4b --- /dev/null +++ b/src/test/java/com/wooteco/wiki/graph/repository/DocumentReferenceRepositoryTest.java @@ -0,0 +1,315 @@ +package com.wooteco.wiki.graph.repository; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.SoftAssertions.assertSoftly; + +import com.wooteco.wiki.document.domain.CrewDocument; +import com.wooteco.wiki.document.domain.Document; +import com.wooteco.wiki.document.domain.DocumentType; +import com.wooteco.wiki.document.fixture.CrewDocumentFixture; +import com.wooteco.wiki.document.repository.CrewDocumentRepository; +import com.wooteco.wiki.document.repository.DocumentRepository; +import com.wooteco.wiki.graph.domain.DocumentReference; +import com.wooteco.wiki.graph.fixture.DocumentReferenceFixture; +import com.wooteco.wiki.organizationdocument.domain.OrganizationDocument; +import com.wooteco.wiki.organizationdocument.fixture.OrganizationDocumentFixture; +import com.wooteco.wiki.organizationdocument.repository.OrganizationDocumentRepository; +import jakarta.persistence.EntityManager; +import java.util.List; +import java.util.UUID; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.dao.DataIntegrityViolationException; + +@DataJpaTest +class DocumentReferenceRepositoryTest { + + @Autowired + private DocumentReferenceRepository documentReferenceRepository; + + @Autowired + private CrewDocumentRepository crewDocumentRepository; + + @Autowired + private OrganizationDocumentRepository organizationDocumentRepository; + + @Autowired + private DocumentRepository documentRepository; + + @Autowired + private EntityManager entityManager; + + @Nested + class Save { + + @Test + void save_success_byValidReference() { + // given + CrewDocument sourceDocument = saveCrewDocument("source"); + CrewDocument targetDocument = saveCrewDocument("target"); + DocumentReference documentReference = DocumentReferenceFixture.create( + sourceDocument, + targetDocument + ); + + // when + documentReferenceRepository.saveAndFlush(documentReference); + entityManager.clear(); + List references = documentReferenceRepository.findAllBySourceDocument(sourceDocument); + DocumentReference savedReference = references.get(0); + + // then + assertSoftly(softly -> { + softly.assertThat(references).hasSize(1); + softly.assertThat(savedReference.getSourceDocument()).isEqualTo(sourceDocument); + softly.assertThat(savedReference.getTargetDocument()).isEqualTo(targetDocument); + }); + } + + @Test + void save_success_byOrganizationDocumentTarget() { + // given + CrewDocument sourceDocument = saveCrewDocument("source"); + OrganizationDocument targetDocument = saveOrganizationDocument("organization-target"); + DocumentReference documentReference = DocumentReferenceFixture.create( + sourceDocument, + targetDocument + ); + + // when + documentReferenceRepository.saveAndFlush(documentReference); + entityManager.clear(); + Document actualTarget = documentReferenceRepository.findAllBySourceDocument(sourceDocument) + .get(0) + .getTargetDocument(); + + // then + assertSoftly(softly -> { + softly.assertThat(actualTarget).isEqualTo(targetDocument); + softly.assertThat(actualTarget.getDocumentType()).isEqualTo(DocumentType.ORGANIZATION); + }); + } + + @Test + void save_success_byDifferentTargets() { + // given + CrewDocument sourceDocument = saveCrewDocument("source"); + CrewDocument firstTargetDocument = saveCrewDocument("first-target"); + CrewDocument secondTargetDocument = saveCrewDocument("second-target"); + + // when + saveReference(sourceDocument, firstTargetDocument); + saveAndFlushReference(sourceDocument, secondTargetDocument); + entityManager.clear(); + List references = documentReferenceRepository.findAllBySourceDocument(sourceDocument); + + // then + assertSoftly(softly -> { + softly.assertThat(references).hasSize(2); + softly.assertThat(references) + .extracting(DocumentReference::getTargetDocument) + .containsExactlyInAnyOrder(firstTargetDocument, secondTargetDocument); + }); + } + + @Test + void save_success_bySameTarget() { + // given + CrewDocument firstSourceDocument = saveCrewDocument("first-source"); + CrewDocument secondSourceDocument = saveCrewDocument("second-source"); + CrewDocument targetDocument = saveCrewDocument("target"); + + // when + saveReference(firstSourceDocument, targetDocument); + saveAndFlushReference(secondSourceDocument, targetDocument); + entityManager.clear(); + List references = documentReferenceRepository.findAll(); + + // then + assertSoftly(softly -> { + softly.assertThat(references).hasSize(2); + softly.assertThat(references) + .extracting(DocumentReference::getSourceDocument) + .containsExactlyInAnyOrder(firstSourceDocument, secondSourceDocument); + }); + } + + @Test + void save_fail_byDuplicateSourceAndTarget() { + // given + CrewDocument sourceDocument = saveCrewDocument("source"); + CrewDocument targetDocument = saveCrewDocument("target"); + saveAndFlushReference(sourceDocument, targetDocument); + DocumentReference duplicateReference = DocumentReferenceFixture.create( + sourceDocument, + targetDocument + ); + + // when & then + assertThatThrownBy(() -> documentReferenceRepository.saveAndFlush(duplicateReference)) + .isInstanceOf(DataIntegrityViolationException.class); + } + } + + @Nested + class FindAllBySourceDocument { + + @Test + void findAllBySourceDocument_success_byExistingReferences() { + // given + CrewDocument selectedSourceDocument = saveCrewDocument("selected-source"); + CrewDocument otherSourceDocument = saveCrewDocument("other-source"); + CrewDocument firstTargetDocument = saveCrewDocument("first-target"); + CrewDocument secondTargetDocument = saveCrewDocument("second-target"); + saveReference(selectedSourceDocument, firstTargetDocument); + saveReference(selectedSourceDocument, secondTargetDocument); + saveAndFlushReference(otherSourceDocument, firstTargetDocument); + entityManager.clear(); + + // when + List references = documentReferenceRepository.findAllBySourceDocument( + selectedSourceDocument + ); + + // then + assertSoftly(softly -> { + softly.assertThat(references).hasSize(2); + softly.assertThat(references) + .extracting(DocumentReference::getTargetDocument) + .containsExactlyInAnyOrder(firstTargetDocument, secondTargetDocument); + }); + } + } + + @Nested + class DeleteAllBySourceDocument { + + @Test + void deleteAllBySourceDocument_success_byOutgoingReferences() { + // given + CrewDocument selectedSourceDocument = saveCrewDocument("selected-source"); + CrewDocument otherSourceDocument = saveCrewDocument("other-source"); + CrewDocument firstTargetDocument = saveCrewDocument("first-target"); + CrewDocument secondTargetDocument = saveCrewDocument("second-target"); + saveReference(selectedSourceDocument, firstTargetDocument); + saveReference(selectedSourceDocument, secondTargetDocument); + saveAndFlushReference(otherSourceDocument, firstTargetDocument); + + // when + documentReferenceRepository.deleteAllBySourceDocument(selectedSourceDocument); + documentReferenceRepository.flush(); + entityManager.clear(); + List remainingReferences = documentReferenceRepository.findAll(); + DocumentReference remainingReference = remainingReferences.get(0); + + // then + assertSoftly(softly -> { + softly.assertThat(remainingReferences).hasSize(1); + softly.assertThat(remainingReference.getSourceDocument()).isEqualTo(otherSourceDocument); + softly.assertThat(remainingReference.getTargetDocument()).isEqualTo(firstTargetDocument); + }); + } + } + + @Nested + class DeleteAllByTargetDocument { + + @Test + void deleteAllByTargetDocument_success_byIncomingReferences() { + // given + CrewDocument firstSourceDocument = saveCrewDocument("first-source"); + CrewDocument secondSourceDocument = saveCrewDocument("second-source"); + CrewDocument selectedTargetDocument = saveCrewDocument("selected-target"); + CrewDocument otherTargetDocument = saveCrewDocument("other-target"); + saveReference(firstSourceDocument, selectedTargetDocument); + saveReference(secondSourceDocument, selectedTargetDocument); + saveAndFlushReference(firstSourceDocument, otherTargetDocument); + + // when + documentReferenceRepository.deleteAllByTargetDocument(selectedTargetDocument); + documentReferenceRepository.flush(); + entityManager.clear(); + List remainingReferences = documentReferenceRepository.findAll(); + DocumentReference remainingReference = remainingReferences.get(0); + + // then + assertSoftly(softly -> { + softly.assertThat(remainingReferences).hasSize(1); + softly.assertThat(remainingReference.getSourceDocument()).isEqualTo(firstSourceDocument); + softly.assertThat(remainingReference.getTargetDocument()).isEqualTo(otherTargetDocument); + }); + } + } + + @Nested + class Delete { + + @Test + void delete_success_withoutDocumentCascade() { + // given + CrewDocument sourceDocument = saveCrewDocument("source"); + CrewDocument targetDocument = saveCrewDocument("target"); + Long sourceDocumentId = sourceDocument.getId(); + Long targetDocumentId = targetDocument.getId(); + DocumentReference documentReference = saveAndFlushReference(sourceDocument, targetDocument); + + // when + documentReferenceRepository.delete(documentReference); + documentReferenceRepository.flush(); + entityManager.clear(); + + // then + assertSoftly(softly -> { + softly.assertThat(documentReferenceRepository.findAll()).isEmpty(); + softly.assertThat(documentRepository.findById(sourceDocumentId)).contains(sourceDocument); + softly.assertThat(documentRepository.findById(targetDocumentId)).contains(targetDocument); + }); + } + } + + private CrewDocument saveCrewDocument(String title) { + CrewDocument crewDocument = CrewDocumentFixture.createCrewDocument( + title, + "contents", + "writer", + 10L, + UUID.randomUUID() + ); + return crewDocumentRepository.save(crewDocument); + } + + private OrganizationDocument saveOrganizationDocument(String title) { + OrganizationDocument organizationDocument = OrganizationDocumentFixture.create( + title, + "contents", + "writer", + 10L, + UUID.randomUUID() + ); + return organizationDocumentRepository.save(organizationDocument); + } + + private DocumentReference saveReference( + CrewDocument sourceDocument, + Document targetDocument + ) { + DocumentReference documentReference = DocumentReferenceFixture.create( + sourceDocument, + targetDocument + ); + return documentReferenceRepository.save(documentReference); + } + + private DocumentReference saveAndFlushReference( + CrewDocument sourceDocument, + Document targetDocument + ) { + DocumentReference documentReference = DocumentReferenceFixture.create( + sourceDocument, + targetDocument + ); + return documentReferenceRepository.saveAndFlush(documentReference); + } +} From a595e285e6ae0bab721c0eb6dca6948e3545249c Mon Sep 17 00:00:00 2001 From: jyt6640 Date: Wed, 12 Aug 2026 20:15:56 +0900 Subject: [PATCH 2/2] =?UTF-8?q?feat:=20=EB=AC=B8=EC=84=9C=20=EC=B0=B8?= =?UTF-8?q?=EC=A1=B0=20=EC=98=81=EC=86=8D=20=EB=AA=A8=EB=8D=B8=EA=B3=BC=20?= =?UTF-8?q?Repository=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../wiki/graph/domain/DocumentReference.java | 76 +++++++++++++++++++ .../DocumentReferenceRepository.java | 16 ++++ 2 files changed, 92 insertions(+) create mode 100644 src/main/java/com/wooteco/wiki/graph/domain/DocumentReference.java create mode 100644 src/main/java/com/wooteco/wiki/graph/repository/DocumentReferenceRepository.java diff --git a/src/main/java/com/wooteco/wiki/graph/domain/DocumentReference.java b/src/main/java/com/wooteco/wiki/graph/domain/DocumentReference.java new file mode 100644 index 0000000..92695c5 --- /dev/null +++ b/src/main/java/com/wooteco/wiki/graph/domain/DocumentReference.java @@ -0,0 +1,76 @@ +package com.wooteco.wiki.graph.domain; + +import com.wooteco.wiki.document.domain.CrewDocument; +import com.wooteco.wiki.document.domain.Document; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.ForeignKey; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Index; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import jakarta.persistence.UniqueConstraint; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Entity +@Table( + name = "document_reference", + uniqueConstraints = @UniqueConstraint( + name = "uk_document_reference_source_target", + columnNames = {"source_document_id", "target_document_id"} + ), + indexes = { + @Index(name = "idx_document_reference_source", columnList = "source_document_id"), + @Index(name = "idx_document_reference_target", columnList = "target_document_id") + } +) +public class DocumentReference { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY, optional = false) + @JoinColumn( + name = "source_document_id", + nullable = false, + foreignKey = @ForeignKey(name = "fk_document_reference_source") + ) + private CrewDocument sourceDocument; + + @ManyToOne(fetch = FetchType.LAZY, optional = false) + @JoinColumn( + name = "target_document_id", + nullable = false, + foreignKey = @ForeignKey(name = "fk_document_reference_target") + ) + private Document targetDocument; + + public static DocumentReference of( + CrewDocument sourceDocument, + Document targetDocument + ) { + return new DocumentReference(sourceDocument, targetDocument); + } + + private DocumentReference( + CrewDocument sourceDocument, + Document targetDocument + ) { + this.sourceDocument = sourceDocument; + this.targetDocument = targetDocument; + } + + public CrewDocument getSourceDocument() { + return sourceDocument; + } + + public Document getTargetDocument() { + return targetDocument; + } +} diff --git a/src/main/java/com/wooteco/wiki/graph/repository/DocumentReferenceRepository.java b/src/main/java/com/wooteco/wiki/graph/repository/DocumentReferenceRepository.java new file mode 100644 index 0000000..7a30b29 --- /dev/null +++ b/src/main/java/com/wooteco/wiki/graph/repository/DocumentReferenceRepository.java @@ -0,0 +1,16 @@ +package com.wooteco.wiki.graph.repository; + +import com.wooteco.wiki.document.domain.CrewDocument; +import com.wooteco.wiki.document.domain.Document; +import com.wooteco.wiki.graph.domain.DocumentReference; +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface DocumentReferenceRepository extends JpaRepository { + + List findAllBySourceDocument(CrewDocument sourceDocument); + + void deleteAllBySourceDocument(CrewDocument sourceDocument); + + void deleteAllByTargetDocument(Document targetDocument); +}