Intégration du graphe et du multi-branche pour la partie campagne
This commit is contained in:
@@ -1,11 +1,14 @@
|
||||
package com.loremind.application.campaigncontext;
|
||||
|
||||
import com.loremind.domain.campaigncontext.Scene;
|
||||
import com.loremind.domain.campaigncontext.SceneBranch;
|
||||
import com.loremind.domain.campaigncontext.ports.SceneRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Service d'application pour le contexte Scene.
|
||||
@@ -66,6 +69,11 @@ public class SceneService {
|
||||
scene.setEnemies(updated.getEnemies());
|
||||
scene.setRelatedPageIds(updated.getRelatedPageIds());
|
||||
scene.setIllustrationImageIds(updated.getIllustrationImageIds());
|
||||
scene.setBranches(updated.getBranches());
|
||||
|
||||
// Validation métier : le graphe narratif doit rester cohérent.
|
||||
validateBranches(scene);
|
||||
|
||||
return sceneRepository.save(scene);
|
||||
}
|
||||
|
||||
@@ -76,4 +84,38 @@ public class SceneService {
|
||||
public boolean sceneExists(String id) {
|
||||
return sceneRepository.existsById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie les invariants du graphe narratif :
|
||||
* 1. Pas d'auto-référence (scène qui pointe sur elle-même).
|
||||
* 2. Toutes les branches pointent vers des scènes du MÊME chapitre.
|
||||
* 3. Pas de targetSceneId null/vide.
|
||||
*
|
||||
* Note : on ne vérifie PAS l'existence réelle de chaque scène cible
|
||||
* individuellement (ça serait un N+1). On charge une seule fois les
|
||||
* IDs du chapitre et on compare.
|
||||
*/
|
||||
private void validateBranches(Scene scene) {
|
||||
List<SceneBranch> branches = scene.getBranches();
|
||||
if (branches == null || branches.isEmpty()) return;
|
||||
|
||||
// IDs des scènes du chapitre courant (référentiel de validation)
|
||||
Set<String> chapterSceneIds = sceneRepository.findByChapterId(scene.getChapterId()).stream()
|
||||
.map(Scene::getId)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
for (SceneBranch b : branches) {
|
||||
String target = b.getTargetSceneId();
|
||||
if (target == null || target.isBlank()) {
|
||||
throw new IllegalArgumentException("Une branche doit avoir une scène de destination");
|
||||
}
|
||||
if (target.equals(scene.getId())) {
|
||||
throw new IllegalArgumentException("Une scène ne peut pas se brancher sur elle-même");
|
||||
}
|
||||
if (!chapterSceneIds.contains(target)) {
|
||||
throw new IllegalArgumentException(
|
||||
"La branche pointe vers la scène " + target + " qui n'appartient pas au même chapitre");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,12 +10,14 @@ import com.loremind.domain.campaigncontext.ports.ChapterRepository;
|
||||
import com.loremind.domain.campaigncontext.ports.SceneRepository;
|
||||
import com.loremind.domain.generationcontext.CampaignStructuralContext;
|
||||
import com.loremind.domain.generationcontext.CampaignStructuralContext.ArcSummary;
|
||||
import com.loremind.domain.generationcontext.CampaignStructuralContext.BranchHint;
|
||||
import com.loremind.domain.generationcontext.CampaignStructuralContext.ChapterSummary;
|
||||
import com.loremind.domain.generationcontext.CampaignStructuralContext.SceneSummary;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@@ -84,23 +86,44 @@ public class CampaignStructuralContextBuilder {
|
||||
}
|
||||
|
||||
private ChapterSummary toChapterSummary(Chapter chapter) {
|
||||
List<SceneSummary> scenes = sceneRepository.findByChapterId(chapter.getId()).stream()
|
||||
List<Scene> scenes = sceneRepository.findByChapterId(chapter.getId()).stream()
|
||||
.sorted(Comparator.comparingInt(Scene::getOrder))
|
||||
.map(this::toSceneSummary)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// Map id -> nom construite en une seule passe pour resoudre les
|
||||
// targetSceneId des branches sans re-interroger le repo (evite N+1).
|
||||
Map<String, String> nameById = scenes.stream()
|
||||
.collect(Collectors.toMap(Scene::getId, Scene::getName));
|
||||
|
||||
List<SceneSummary> summaries = scenes.stream()
|
||||
.map(s -> toSceneSummary(s, nameById))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return ChapterSummary.builder()
|
||||
.name(chapter.getName())
|
||||
.description(chapter.getDescription())
|
||||
.illustrationCount(countImages(chapter.getIllustrationImageIds()))
|
||||
.scenes(scenes)
|
||||
.scenes(summaries)
|
||||
.build();
|
||||
}
|
||||
|
||||
private SceneSummary toSceneSummary(Scene scene) {
|
||||
private SceneSummary toSceneSummary(Scene scene, Map<String, String> nameById) {
|
||||
List<BranchHint> hints = scene.getBranches() == null
|
||||
? List.of()
|
||||
: scene.getBranches().stream()
|
||||
.map(b -> BranchHint.builder()
|
||||
.label(b.getLabel())
|
||||
.targetSceneName(nameById.getOrDefault(
|
||||
b.getTargetSceneId(), "(scène inconnue)"))
|
||||
.condition(b.getCondition())
|
||||
.build())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return SceneSummary.builder()
|
||||
.name(scene.getName())
|
||||
.description(scene.getDescription())
|
||||
.illustrationCount(countImages(scene.getIllustrationImageIds()))
|
||||
.branches(hints)
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
@@ -53,6 +53,14 @@ public class Scene {
|
||||
@Builder.Default
|
||||
private List<String> illustrationImageIds = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Sorties narratives possibles depuis cette scène (graphe intra-chapitre).
|
||||
* Chaque branche décrit un choix des joueurs et la scène de destination.
|
||||
* Liste vide = scène "feuille" (fin de chapitre ou scène linéaire).
|
||||
*/
|
||||
@Builder.Default
|
||||
private List<SceneBranch> branches = new ArrayList<>();
|
||||
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
@@ -71,4 +79,21 @@ public class Scene {
|
||||
this.updatedAt = LocalDateTime.now();
|
||||
}
|
||||
}
|
||||
|
||||
public void addBranch(SceneBranch branch) {
|
||||
if (branch == null) return;
|
||||
if (branches == null) branches = new ArrayList<>();
|
||||
// Interdit l'auto-référence (scène qui pointe sur elle-même)
|
||||
if (this.id != null && this.id.equals(branch.getTargetSceneId())) {
|
||||
throw new IllegalArgumentException("Une scène ne peut pas se brancher sur elle-même");
|
||||
}
|
||||
branches.add(branch);
|
||||
this.updatedAt = LocalDateTime.now();
|
||||
}
|
||||
|
||||
public void removeBranchTo(String targetSceneId) {
|
||||
if (branches == null || targetSceneId == null) return;
|
||||
boolean removed = branches.removeIf(b -> targetSceneId.equals(b.getTargetSceneId()));
|
||||
if (removed) this.updatedAt = LocalDateTime.now();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.loremind.domain.campaigncontext;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Value;
|
||||
import lombok.extern.jackson.Jacksonized;
|
||||
|
||||
/**
|
||||
* Value Object représentant une "sortie" narrative depuis une Scene.
|
||||
* Décrit un choix offert aux joueurs et la scène de destination associée.
|
||||
*
|
||||
* Immuable (@Value) : pour "modifier" une branche on la remplace.
|
||||
* @Jacksonized : permet à Jackson (sérialisation JSON via le converter JPA)
|
||||
* de reconstruire l'objet en passant par le builder malgré l'absence de setters.
|
||||
*
|
||||
* Règle métier : targetSceneId DOIT pointer vers une Scene du MÊME Chapter
|
||||
* (validation portée par SceneService).
|
||||
*/
|
||||
@Value
|
||||
@Builder
|
||||
@Jacksonized
|
||||
public class SceneBranch {
|
||||
|
||||
/** Libellé du choix (ex: "Si les joueurs attaquent le garde"). */
|
||||
String label;
|
||||
|
||||
/** Id de la Scene de destination, intra-chapitre uniquement. */
|
||||
String targetSceneId;
|
||||
|
||||
/** Notes MJ privées sur la condition de déclenchement (optionnel). */
|
||||
String condition;
|
||||
}
|
||||
@@ -52,12 +52,25 @@ public class CampaignStructuralContext {
|
||||
@Singular List<SceneSummary> scenes;
|
||||
}
|
||||
|
||||
/** Résumé d'une scène : nom + description courte. */
|
||||
/** Résumé d'une scène : nom + description courte + branches narratives. */
|
||||
@Value
|
||||
@Builder
|
||||
public static class SceneSummary {
|
||||
String name;
|
||||
String description;
|
||||
int illustrationCount;
|
||||
@Singular List<BranchHint> branches;
|
||||
}
|
||||
|
||||
/** Indice d'une branche narrative vers une autre scène du même chapitre. */
|
||||
@Value
|
||||
@Builder
|
||||
public static class BranchHint {
|
||||
/** Libellé du choix joueur (ex: "Si les joueurs attaquent le garde"). */
|
||||
String label;
|
||||
/** Nom de la scène cible (résolu depuis targetSceneId côté builder). */
|
||||
String targetSceneName;
|
||||
/** Condition MJ privée (optionnel). */
|
||||
String condition;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.loremind.infrastructure.ai;
|
||||
|
||||
import com.loremind.domain.generationcontext.CampaignStructuralContext;
|
||||
import com.loremind.domain.generationcontext.CampaignStructuralContext.ArcSummary;
|
||||
import com.loremind.domain.generationcontext.CampaignStructuralContext.BranchHint;
|
||||
import com.loremind.domain.generationcontext.CampaignStructuralContext.ChapterSummary;
|
||||
import com.loremind.domain.generationcontext.CampaignStructuralContext.SceneSummary;
|
||||
import com.loremind.domain.generationcontext.ChatMessage;
|
||||
@@ -253,6 +254,23 @@ public class BrainAiChatClient implements AiChatProvider {
|
||||
if (s.getIllustrationCount() > 0) {
|
||||
map.put("illustration_count", s.getIllustrationCount());
|
||||
}
|
||||
// Branches narratives : serialise uniquement si presentes, pour garder
|
||||
// un payload leger sur les scenes lineaires classiques.
|
||||
if (s.getBranches() != null && !s.getBranches().isEmpty()) {
|
||||
map.put("branches", s.getBranches().stream()
|
||||
.map(this::branchHintToMap)
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
private Map<String, Object> branchHintToMap(BranchHint b) {
|
||||
Map<String, Object> map = new LinkedHashMap<>();
|
||||
map.put("label", b.getLabel());
|
||||
map.put("target_scene_name", b.getTargetSceneName());
|
||||
if (b.getCondition() != null && !b.getCondition().isBlank()) {
|
||||
map.put("condition", b.getCondition());
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.loremind.infrastructure.persistence.converter;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.loremind.domain.campaigncontext.SceneBranch;
|
||||
import jakarta.persistence.AttributeConverter;
|
||||
import jakarta.persistence.Converter;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Convertit une List<SceneBranch> du domaine en chaîne JSON stockée en base,
|
||||
* et inversement. Même pattern que StringListJsonConverter mais typé sur
|
||||
* le Value Object SceneBranch.
|
||||
*
|
||||
* Adaptateur d'infrastructure : le domaine reste pur (List<SceneBranch>)
|
||||
* pendant que PostgreSQL reçoit un TEXT JSON.
|
||||
*/
|
||||
@Converter
|
||||
public class SceneBranchListJsonConverter implements AttributeConverter<List<SceneBranch>, String> {
|
||||
|
||||
private static final ObjectMapper MAPPER = new ObjectMapper();
|
||||
|
||||
@Override
|
||||
public String convertToDatabaseColumn(List<SceneBranch> attribute) {
|
||||
if (attribute == null || attribute.isEmpty()) {
|
||||
return "[]";
|
||||
}
|
||||
try {
|
||||
return MAPPER.writeValueAsString(attribute);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Erreur sérialisation List<SceneBranch> → JSON", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SceneBranch> convertToEntityAttribute(String dbData) {
|
||||
if (dbData == null || dbData.isBlank()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
try {
|
||||
return MAPPER.readValue(dbData, new TypeReference<List<SceneBranch>>() {});
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Erreur désérialisation JSON → List<SceneBranch>", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.loremind.infrastructure.persistence.entity;
|
||||
|
||||
import com.loremind.domain.campaigncontext.SceneBranch;
|
||||
import com.loremind.infrastructure.persistence.converter.SceneBranchListJsonConverter;
|
||||
import com.loremind.infrastructure.persistence.converter.StringListJsonConverter;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.AllArgsConstructor;
|
||||
@@ -78,6 +80,13 @@ public class SceneJpaEntity {
|
||||
@Builder.Default
|
||||
private List<String> illustrationImageIds = new ArrayList<>();
|
||||
|
||||
// Graphe narratif intra-chapitre : sorties possibles vers d'autres scènes.
|
||||
// Persisté en TEXT JSON via converter (pattern homogène avec les autres listes).
|
||||
@Column(name = "branches", columnDefinition = "TEXT")
|
||||
@Convert(converter = SceneBranchListJsonConverter.class)
|
||||
@Builder.Default
|
||||
private List<SceneBranch> branches = new ArrayList<>();
|
||||
|
||||
@Column(name = "created_at", nullable = false, updatable = false)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
|
||||
@@ -85,6 +85,9 @@ public class PostgresSceneRepository implements SceneRepository {
|
||||
.illustrationImageIds(jpaEntity.getIllustrationImageIds() != null
|
||||
? new ArrayList<>(jpaEntity.getIllustrationImageIds())
|
||||
: new ArrayList<>())
|
||||
.branches(jpaEntity.getBranches() != null
|
||||
? new ArrayList<>(jpaEntity.getBranches())
|
||||
: new ArrayList<>())
|
||||
.createdAt(jpaEntity.getCreatedAt())
|
||||
.updatedAt(jpaEntity.getUpdatedAt())
|
||||
.build();
|
||||
@@ -112,6 +115,9 @@ public class PostgresSceneRepository implements SceneRepository {
|
||||
.illustrationImageIds(scene.getIllustrationImageIds() != null
|
||||
? new ArrayList<>(scene.getIllustrationImageIds())
|
||||
: new ArrayList<>())
|
||||
.branches(scene.getBranches() != null
|
||||
? new ArrayList<>(scene.getBranches())
|
||||
: new ArrayList<>())
|
||||
.createdAt(scene.getCreatedAt())
|
||||
.updatedAt(scene.getUpdatedAt())
|
||||
.build();
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.loremind.infrastructure.web.dto.campaigncontext;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
/**
|
||||
* DTO pour une branche narrative (sortie) depuis une Scene.
|
||||
* Pendant web du Value Object domaine SceneBranch.
|
||||
*
|
||||
* @Data (mutable) est approprié pour les DTO de wire : Jackson désérialise
|
||||
* via setters côté requête entrante.
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class SceneBranchDTO {
|
||||
|
||||
private String label;
|
||||
private String targetSceneId;
|
||||
private String condition;
|
||||
}
|
||||
@@ -32,4 +32,7 @@ public class SceneDTO {
|
||||
|
||||
/** IDs des images (Shared Kernel) illustrant cette scene. */
|
||||
private List<String> illustrationImageIds = new ArrayList<>();
|
||||
|
||||
/** Branches narratives : sorties possibles vers d'autres scènes du même chapitre. */
|
||||
private List<SceneBranchDTO> branches = new ArrayList<>();
|
||||
}
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
package com.loremind.infrastructure.web.mapper;
|
||||
|
||||
import com.loremind.domain.campaigncontext.Scene;
|
||||
import com.loremind.domain.campaigncontext.SceneBranch;
|
||||
import com.loremind.infrastructure.web.dto.campaigncontext.SceneBranchDTO;
|
||||
import com.loremind.infrastructure.web.dto.campaigncontext.SceneDTO;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Mapper pour convertir entre Scene (entité de domaine) et SceneDTO.
|
||||
@@ -37,6 +41,7 @@ public class SceneMapper {
|
||||
dto.setIllustrationImageIds(scene.getIllustrationImageIds() != null
|
||||
? new ArrayList<>(scene.getIllustrationImageIds())
|
||||
: new ArrayList<>());
|
||||
dto.setBranches(toBranchDTOs(scene.getBranches()));
|
||||
return dto;
|
||||
}
|
||||
|
||||
@@ -65,6 +70,27 @@ public class SceneMapper {
|
||||
.illustrationImageIds(dto.getIllustrationImageIds() != null
|
||||
? new ArrayList<>(dto.getIllustrationImageIds())
|
||||
: new ArrayList<>())
|
||||
.branches(toBranchDomain(dto.getBranches()))
|
||||
.build();
|
||||
}
|
||||
|
||||
// ─────────────── Mapping des branches (VO <-> DTO) ───────────────
|
||||
|
||||
private List<SceneBranchDTO> toBranchDTOs(List<SceneBranch> branches) {
|
||||
if (branches == null) return new ArrayList<>();
|
||||
return branches.stream()
|
||||
.map(b -> new SceneBranchDTO(b.getLabel(), b.getTargetSceneId(), b.getCondition()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private List<SceneBranch> toBranchDomain(List<SceneBranchDTO> dtos) {
|
||||
if (dtos == null) return new ArrayList<>();
|
||||
return dtos.stream()
|
||||
.map(d -> SceneBranch.builder()
|
||||
.label(d.getLabel())
|
||||
.targetSceneId(d.getTargetSceneId())
|
||||
.condition(d.getCondition())
|
||||
.build())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
package com.loremind.application.campaigncontext;
|
||||
|
||||
import com.loremind.domain.campaigncontext.Arc;
|
||||
import com.loremind.domain.campaigncontext.ports.ArcRepository;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* Test unitaire pour ArcService.
|
||||
* Utilise des mocks pour les ports de sortie (ArcRepository).
|
||||
* Teste la logique d'orchestration de la couche Application.
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class ArcServiceTest {
|
||||
|
||||
@Mock
|
||||
private ArcRepository arcRepository;
|
||||
|
||||
@InjectMocks
|
||||
private ArcService arcService;
|
||||
|
||||
private Arc testArc;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
testArc = Arc.builder()
|
||||
.id("arc-1")
|
||||
.name("Test Arc")
|
||||
.description("Test Description")
|
||||
.campaignId("campaign-1")
|
||||
.order(1)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreateArc() {
|
||||
// Arrange
|
||||
when(arcRepository.save(any(Arc.class))).thenReturn(testArc);
|
||||
|
||||
// Act
|
||||
Arc result = arcService.createArc("New Arc", "Description", "campaign-1", 1);
|
||||
|
||||
// Assert
|
||||
assertNotNull(result);
|
||||
verify(arcRepository, times(1)).save(any(Arc.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetArcById_Found() {
|
||||
// Arrange
|
||||
when(arcRepository.findById("arc-1")).thenReturn(Optional.of(testArc));
|
||||
|
||||
// Act
|
||||
Optional<Arc> result = arcService.getArcById("arc-1");
|
||||
|
||||
// Assert
|
||||
assertTrue(result.isPresent());
|
||||
assertEquals("Test Arc", result.get().getName());
|
||||
verify(arcRepository, times(1)).findById("arc-1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetArcById_NotFound() {
|
||||
// Arrange
|
||||
when(arcRepository.findById("invalid-id")).thenReturn(Optional.empty());
|
||||
|
||||
// Act
|
||||
Optional<Arc> result = arcService.getArcById("invalid-id");
|
||||
|
||||
// Assert
|
||||
assertFalse(result.isPresent());
|
||||
verify(arcRepository, times(1)).findById("invalid-id");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetAllArcs() {
|
||||
// Arrange
|
||||
Arc arc2 = Arc.builder()
|
||||
.id("arc-2")
|
||||
.name("Arc 2")
|
||||
.build();
|
||||
when(arcRepository.findAll()).thenReturn(List.of(testArc, arc2));
|
||||
|
||||
// Act
|
||||
List<Arc> result = arcService.getAllArcs();
|
||||
|
||||
// Assert
|
||||
assertEquals(2, result.size());
|
||||
verify(arcRepository, times(1)).findAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetArcsByCampaignId() {
|
||||
// Arrange
|
||||
when(arcRepository.findByCampaignId("campaign-1")).thenReturn(List.of(testArc));
|
||||
|
||||
// Act
|
||||
List<Arc> result = arcService.getArcsByCampaignId("campaign-1");
|
||||
|
||||
// Assert
|
||||
assertEquals(1, result.size());
|
||||
verify(arcRepository, times(1)).findByCampaignId("campaign-1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUpdateArc_Success() {
|
||||
// Arrange
|
||||
Arc updatedArc = Arc.builder()
|
||||
.name("Updated Arc")
|
||||
.description("Updated Description")
|
||||
.order(2)
|
||||
.themes("Theme1, Theme2")
|
||||
.stakes("High stakes")
|
||||
.gmNotes("GM notes")
|
||||
.rewards("Rewards")
|
||||
.resolution("Resolution")
|
||||
.relatedPageIds(List.of("page-1"))
|
||||
.illustrationImageIds(List.of("image-1"))
|
||||
.build();
|
||||
when(arcRepository.findById("arc-1")).thenReturn(Optional.of(testArc));
|
||||
when(arcRepository.save(any(Arc.class))).thenReturn(testArc);
|
||||
|
||||
// Act
|
||||
Arc result = arcService.updateArc("arc-1", updatedArc);
|
||||
|
||||
// Assert
|
||||
assertNotNull(result);
|
||||
verify(arcRepository, times(1)).findById("arc-1");
|
||||
verify(arcRepository, times(1)).save(any(Arc.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUpdateArc_NotFound() {
|
||||
// Arrange
|
||||
Arc updatedArc = Arc.builder()
|
||||
.name("Updated Arc")
|
||||
.build();
|
||||
when(arcRepository.findById("invalid-id")).thenReturn(Optional.empty());
|
||||
|
||||
// Act & Assert
|
||||
IllegalArgumentException exception = assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> arcService.updateArc("invalid-id", updatedArc)
|
||||
);
|
||||
assertEquals("Arc non trouvé avec l'ID: invalid-id", exception.getMessage());
|
||||
verify(arcRepository, times(1)).findById("invalid-id");
|
||||
verify(arcRepository, never()).save(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDeleteArc() {
|
||||
// Arrange
|
||||
doNothing().when(arcRepository).deleteById("arc-1");
|
||||
|
||||
// Act
|
||||
arcService.deleteArc("arc-1");
|
||||
|
||||
// Assert
|
||||
verify(arcRepository, times(1)).deleteById("arc-1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testArcExists_True() {
|
||||
// Arrange
|
||||
when(arcRepository.existsById("arc-1")).thenReturn(true);
|
||||
|
||||
// Act
|
||||
boolean result = arcService.arcExists("arc-1");
|
||||
|
||||
// Assert
|
||||
assertTrue(result);
|
||||
verify(arcRepository, times(1)).existsById("arc-1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testArcExists_False() {
|
||||
// Arrange
|
||||
when(arcRepository.existsById("invalid-id")).thenReturn(false);
|
||||
|
||||
// Act
|
||||
boolean result = arcService.arcExists("invalid-id");
|
||||
|
||||
// Assert
|
||||
assertFalse(result);
|
||||
verify(arcRepository, times(1)).existsById("invalid-id");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
package com.loremind.application.campaigncontext;
|
||||
|
||||
import com.loremind.domain.campaigncontext.Campaign;
|
||||
import com.loremind.domain.campaigncontext.ports.CampaignRepository;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* Test unitaire pour CampaignService.
|
||||
* Utilise des mocks pour les ports de sortie (CampaignRepository).
|
||||
* Teste la logique d'orchestration de la couche Application.
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class CampaignServiceTest {
|
||||
|
||||
@Mock
|
||||
private CampaignRepository campaignRepository;
|
||||
|
||||
@InjectMocks
|
||||
private CampaignService campaignService;
|
||||
|
||||
private Campaign testCampaign;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
testCampaign = Campaign.builder()
|
||||
.id("campaign-1")
|
||||
.name("Test Campaign")
|
||||
.description("Test Description")
|
||||
.loreId("lore-1")
|
||||
.arcsCount(0)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreateCampaign_WithLoreId() {
|
||||
// Arrange
|
||||
CampaignService.CampaignData data = new CampaignService.CampaignData(
|
||||
"New Campaign",
|
||||
"Description",
|
||||
"lore-123"
|
||||
);
|
||||
when(campaignRepository.save(any(Campaign.class))).thenReturn(testCampaign);
|
||||
|
||||
// Act
|
||||
Campaign result = campaignService.createCampaign(data);
|
||||
|
||||
// Assert
|
||||
assertNotNull(result);
|
||||
verify(campaignRepository, times(1)).save(any(Campaign.class));
|
||||
assertEquals("lore-123", result.getLoreId());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreateCampaign_WithNullLoreId() {
|
||||
// Arrange
|
||||
CampaignService.CampaignData data = new CampaignService.CampaignData(
|
||||
"New Campaign",
|
||||
"Description",
|
||||
null
|
||||
);
|
||||
when(campaignRepository.save(any(Campaign.class))).thenReturn(testCampaign);
|
||||
|
||||
// Act
|
||||
Campaign result = campaignService.createCampaign(data);
|
||||
|
||||
// Assert
|
||||
assertNotNull(result);
|
||||
verify(campaignRepository, times(1)).save(any(Campaign.class));
|
||||
assertNull(result.getLoreId());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreateCampaign_WithBlankLoreId() {
|
||||
// Arrange
|
||||
CampaignService.CampaignData data = new CampaignService.CampaignData(
|
||||
"New Campaign",
|
||||
"Description",
|
||||
" "
|
||||
);
|
||||
when(campaignRepository.save(any(Campaign.class))).thenReturn(testCampaign);
|
||||
|
||||
// Act
|
||||
Campaign result = campaignService.createCampaign(data);
|
||||
|
||||
// Assert
|
||||
assertNotNull(result);
|
||||
verify(campaignRepository, times(1)).save(any(Campaign.class));
|
||||
assertNull(result.getLoreId());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetCampaignById_Found() {
|
||||
// Arrange
|
||||
when(campaignRepository.findById("campaign-1")).thenReturn(Optional.of(testCampaign));
|
||||
|
||||
// Act
|
||||
Optional<Campaign> result = campaignService.getCampaignById("campaign-1");
|
||||
|
||||
// Assert
|
||||
assertTrue(result.isPresent());
|
||||
assertEquals("Test Campaign", result.get().getName());
|
||||
verify(campaignRepository, times(1)).findById("campaign-1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetCampaignById_NotFound() {
|
||||
// Arrange
|
||||
when(campaignRepository.findById("invalid-id")).thenReturn(Optional.empty());
|
||||
|
||||
// Act
|
||||
Optional<Campaign> result = campaignService.getCampaignById("invalid-id");
|
||||
|
||||
// Assert
|
||||
assertFalse(result.isPresent());
|
||||
verify(campaignRepository, times(1)).findById("invalid-id");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetAllCampaigns() {
|
||||
// Arrange
|
||||
Campaign campaign2 = Campaign.builder()
|
||||
.id("campaign-2")
|
||||
.name("Campaign 2")
|
||||
.build();
|
||||
when(campaignRepository.findAll()).thenReturn(List.of(testCampaign, campaign2));
|
||||
|
||||
// Act
|
||||
List<Campaign> result = campaignService.getAllCampaigns();
|
||||
|
||||
// Assert
|
||||
assertEquals(2, result.size());
|
||||
verify(campaignRepository, times(1)).findAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUpdateCampaign_Success() {
|
||||
// Arrange
|
||||
CampaignService.CampaignData data = new CampaignService.CampaignData(
|
||||
"Updated Campaign",
|
||||
"Updated Description",
|
||||
"lore-456"
|
||||
);
|
||||
when(campaignRepository.findById("campaign-1")).thenReturn(Optional.of(testCampaign));
|
||||
when(campaignRepository.save(any(Campaign.class))).thenReturn(testCampaign);
|
||||
|
||||
// Act
|
||||
Campaign result = campaignService.updateCampaign("campaign-1", data);
|
||||
|
||||
// Assert
|
||||
assertNotNull(result);
|
||||
verify(campaignRepository, times(1)).findById("campaign-1");
|
||||
verify(campaignRepository, times(1)).save(any(Campaign.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUpdateCampaign_NotFound() {
|
||||
// Arrange
|
||||
CampaignService.CampaignData data = new CampaignService.CampaignData(
|
||||
"Updated Campaign",
|
||||
"Updated Description",
|
||||
"lore-456"
|
||||
);
|
||||
when(campaignRepository.findById("invalid-id")).thenReturn(Optional.empty());
|
||||
|
||||
// Act & Assert
|
||||
IllegalArgumentException exception = assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> campaignService.updateCampaign("invalid-id", data)
|
||||
);
|
||||
assertEquals("Campaign non trouvé avec l'ID: invalid-id", exception.getMessage());
|
||||
verify(campaignRepository, times(1)).findById("invalid-id");
|
||||
verify(campaignRepository, never()).save(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDeleteCampaign() {
|
||||
// Arrange
|
||||
doNothing().when(campaignRepository).deleteById("campaign-1");
|
||||
|
||||
// Act
|
||||
campaignService.deleteCampaign("campaign-1");
|
||||
|
||||
// Assert
|
||||
verify(campaignRepository, times(1)).deleteById("campaign-1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCampaignExists_True() {
|
||||
// Arrange
|
||||
when(campaignRepository.existsById("campaign-1")).thenReturn(true);
|
||||
|
||||
// Act
|
||||
boolean result = campaignService.campaignExists("campaign-1");
|
||||
|
||||
// Assert
|
||||
assertTrue(result);
|
||||
verify(campaignRepository, times(1)).existsById("campaign-1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCampaignExists_False() {
|
||||
// Arrange
|
||||
when(campaignRepository.existsById("invalid-id")).thenReturn(false);
|
||||
|
||||
// Act
|
||||
boolean result = campaignService.campaignExists("invalid-id");
|
||||
|
||||
// Assert
|
||||
assertFalse(result);
|
||||
verify(campaignRepository, times(1)).existsById("invalid-id");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSearchCampaigns_WithValidQuery() {
|
||||
// Arrange
|
||||
when(campaignRepository.searchByName("Test")).thenReturn(List.of(testCampaign));
|
||||
|
||||
// Act
|
||||
List<Campaign> result = campaignService.searchCampaigns("Test");
|
||||
|
||||
// Assert
|
||||
assertEquals(1, result.size());
|
||||
verify(campaignRepository, times(1)).searchByName("Test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSearchCampaigns_WithNullQuery() {
|
||||
// Act
|
||||
List<Campaign> result = campaignService.searchCampaigns(null);
|
||||
|
||||
// Assert
|
||||
assertTrue(result.isEmpty());
|
||||
verify(campaignRepository, never()).searchByName(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSearchCampaigns_WithBlankQuery() {
|
||||
// Act
|
||||
List<Campaign> result = campaignService.searchCampaigns(" ");
|
||||
|
||||
// Assert
|
||||
assertTrue(result.isEmpty());
|
||||
verify(campaignRepository, never()).searchByName(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSearchCampaigns_WithTrim() {
|
||||
// Arrange
|
||||
when(campaignRepository.searchByName("Test")).thenReturn(List.of(testCampaign));
|
||||
|
||||
// Act
|
||||
List<Campaign> result = campaignService.searchCampaigns(" Test ");
|
||||
|
||||
// Assert
|
||||
assertEquals(1, result.size());
|
||||
verify(campaignRepository, times(1)).searchByName("Test");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
package com.loremind.application.campaigncontext;
|
||||
|
||||
import com.loremind.domain.campaigncontext.Chapter;
|
||||
import com.loremind.domain.campaigncontext.ports.ChapterRepository;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* Test unitaire pour ChapterService.
|
||||
* Utilise des mocks pour les ports de sortie (ChapterRepository).
|
||||
* Teste la logique d'orchestration de la couche Application.
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class ChapterServiceTest {
|
||||
|
||||
@Mock
|
||||
private ChapterRepository chapterRepository;
|
||||
|
||||
@InjectMocks
|
||||
private ChapterService chapterService;
|
||||
|
||||
private Chapter testChapter;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
testChapter = Chapter.builder()
|
||||
.id("chapter-1")
|
||||
.name("Test Chapter")
|
||||
.description("Test Description")
|
||||
.arcId("arc-1")
|
||||
.order(1)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreateChapter() {
|
||||
// Arrange
|
||||
when(chapterRepository.save(any(Chapter.class))).thenReturn(testChapter);
|
||||
|
||||
// Act
|
||||
Chapter result = chapterService.createChapter("New Chapter", "Description", "arc-1", 1);
|
||||
|
||||
// Assert
|
||||
assertNotNull(result);
|
||||
verify(chapterRepository, times(1)).save(any(Chapter.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetChapterById_Found() {
|
||||
// Arrange
|
||||
when(chapterRepository.findById("chapter-1")).thenReturn(Optional.of(testChapter));
|
||||
|
||||
// Act
|
||||
Optional<Chapter> result = chapterService.getChapterById("chapter-1");
|
||||
|
||||
// Assert
|
||||
assertTrue(result.isPresent());
|
||||
assertEquals("Test Chapter", result.get().getName());
|
||||
verify(chapterRepository, times(1)).findById("chapter-1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetChapterById_NotFound() {
|
||||
// Arrange
|
||||
when(chapterRepository.findById("invalid-id")).thenReturn(Optional.empty());
|
||||
|
||||
// Act
|
||||
Optional<Chapter> result = chapterService.getChapterById("invalid-id");
|
||||
|
||||
// Assert
|
||||
assertFalse(result.isPresent());
|
||||
verify(chapterRepository, times(1)).findById("invalid-id");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetAllChapters() {
|
||||
// Arrange
|
||||
Chapter chapter2 = Chapter.builder()
|
||||
.id("chapter-2")
|
||||
.name("Chapter 2")
|
||||
.build();
|
||||
when(chapterRepository.findAll()).thenReturn(List.of(testChapter, chapter2));
|
||||
|
||||
// Act
|
||||
List<Chapter> result = chapterService.getAllChapters();
|
||||
|
||||
// Assert
|
||||
assertEquals(2, result.size());
|
||||
verify(chapterRepository, times(1)).findAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetChaptersByArcId() {
|
||||
// Arrange
|
||||
when(chapterRepository.findByArcId("arc-1")).thenReturn(List.of(testChapter));
|
||||
|
||||
// Act
|
||||
List<Chapter> result = chapterService.getChaptersByArcId("arc-1");
|
||||
|
||||
// Assert
|
||||
assertEquals(1, result.size());
|
||||
verify(chapterRepository, times(1)).findByArcId("arc-1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUpdateChapter_Success() {
|
||||
// Arrange
|
||||
Chapter updatedChapter = Chapter.builder()
|
||||
.name("Updated Chapter")
|
||||
.description("Updated Description")
|
||||
.order(2)
|
||||
.gmNotes("GM notes")
|
||||
.playerObjectives("Objectives")
|
||||
.narrativeStakes("Stakes")
|
||||
.relatedPageIds(List.of("page-1"))
|
||||
.illustrationImageIds(List.of("image-1"))
|
||||
.build();
|
||||
when(chapterRepository.findById("chapter-1")).thenReturn(Optional.of(testChapter));
|
||||
when(chapterRepository.save(any(Chapter.class))).thenReturn(testChapter);
|
||||
|
||||
// Act
|
||||
Chapter result = chapterService.updateChapter("chapter-1", updatedChapter);
|
||||
|
||||
// Assert
|
||||
assertNotNull(result);
|
||||
verify(chapterRepository, times(1)).findById("chapter-1");
|
||||
verify(chapterRepository, times(1)).save(any(Chapter.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUpdateChapter_NotFound() {
|
||||
// Arrange
|
||||
Chapter updatedChapter = Chapter.builder()
|
||||
.name("Updated Chapter")
|
||||
.build();
|
||||
when(chapterRepository.findById("invalid-id")).thenReturn(Optional.empty());
|
||||
|
||||
// Act & Assert
|
||||
IllegalArgumentException exception = assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> chapterService.updateChapter("invalid-id", updatedChapter)
|
||||
);
|
||||
assertEquals("Chapter non trouvé avec l'ID: invalid-id", exception.getMessage());
|
||||
verify(chapterRepository, times(1)).findById("invalid-id");
|
||||
verify(chapterRepository, never()).save(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDeleteChapter() {
|
||||
// Arrange
|
||||
doNothing().when(chapterRepository).deleteById("chapter-1");
|
||||
|
||||
// Act
|
||||
chapterService.deleteChapter("chapter-1");
|
||||
|
||||
// Assert
|
||||
verify(chapterRepository, times(1)).deleteById("chapter-1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testChapterExists_True() {
|
||||
// Arrange
|
||||
when(chapterRepository.existsById("chapter-1")).thenReturn(true);
|
||||
|
||||
// Act
|
||||
boolean result = chapterService.chapterExists("chapter-1");
|
||||
|
||||
// Assert
|
||||
assertTrue(result);
|
||||
verify(chapterRepository, times(1)).existsById("chapter-1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testChapterExists_False() {
|
||||
// Arrange
|
||||
when(chapterRepository.existsById("invalid-id")).thenReturn(false);
|
||||
|
||||
// Act
|
||||
boolean result = chapterService.chapterExists("invalid-id");
|
||||
|
||||
// Assert
|
||||
assertFalse(result);
|
||||
verify(chapterRepository, times(1)).existsById("invalid-id");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,358 @@
|
||||
package com.loremind.application.campaigncontext;
|
||||
|
||||
import com.loremind.domain.campaigncontext.Scene;
|
||||
import com.loremind.domain.campaigncontext.SceneBranch;
|
||||
import com.loremind.domain.campaigncontext.ports.SceneRepository;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* Test unitaire pour SceneService.
|
||||
* Utilise des mocks pour les ports de sortie (SceneRepository).
|
||||
* Teste la logique d'orchestration de la couche Application.
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class SceneServiceTest {
|
||||
|
||||
@Mock
|
||||
private SceneRepository sceneRepository;
|
||||
|
||||
@InjectMocks
|
||||
private SceneService sceneService;
|
||||
|
||||
private Scene testScene;
|
||||
private Scene scene2;
|
||||
private Scene scene3;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
testScene = Scene.builder()
|
||||
.id("scene-1")
|
||||
.name("Test Scene")
|
||||
.description("Test Description")
|
||||
.chapterId("chapter-1")
|
||||
.order(1)
|
||||
.build();
|
||||
|
||||
scene2 = Scene.builder()
|
||||
.id("scene-2")
|
||||
.name("Scene 2")
|
||||
.chapterId("chapter-1")
|
||||
.order(2)
|
||||
.build();
|
||||
|
||||
scene3 = Scene.builder()
|
||||
.id("scene-3")
|
||||
.name("Scene 3")
|
||||
.chapterId("chapter-1")
|
||||
.order(3)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreateScene() {
|
||||
// Arrange
|
||||
when(sceneRepository.save(any(Scene.class))).thenReturn(testScene);
|
||||
|
||||
// Act
|
||||
Scene result = sceneService.createScene("New Scene", "Description", "chapter-1", 1);
|
||||
|
||||
// Assert
|
||||
assertNotNull(result);
|
||||
verify(sceneRepository, times(1)).save(any(Scene.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetSceneById_Found() {
|
||||
// Arrange
|
||||
when(sceneRepository.findById("scene-1")).thenReturn(Optional.of(testScene));
|
||||
|
||||
// Act
|
||||
Optional<Scene> result = sceneService.getSceneById("scene-1");
|
||||
|
||||
// Assert
|
||||
assertTrue(result.isPresent());
|
||||
assertEquals("Test Scene", result.get().getName());
|
||||
verify(sceneRepository, times(1)).findById("scene-1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetSceneById_NotFound() {
|
||||
// Arrange
|
||||
when(sceneRepository.findById("invalid-id")).thenReturn(Optional.empty());
|
||||
|
||||
// Act
|
||||
Optional<Scene> result = sceneService.getSceneById("invalid-id");
|
||||
|
||||
// Assert
|
||||
assertFalse(result.isPresent());
|
||||
verify(sceneRepository, times(1)).findById("invalid-id");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetAllScenes() {
|
||||
// Arrange
|
||||
when(sceneRepository.findAll()).thenReturn(List.of(testScene, scene2));
|
||||
|
||||
// Act
|
||||
List<Scene> result = sceneService.getAllScenes();
|
||||
|
||||
// Assert
|
||||
assertEquals(2, result.size());
|
||||
verify(sceneRepository, times(1)).findAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetScenesByChapterId() {
|
||||
// Arrange
|
||||
when(sceneRepository.findByChapterId("chapter-1")).thenReturn(List.of(testScene, scene2));
|
||||
|
||||
// Act
|
||||
List<Scene> result = sceneService.getScenesByChapterId("chapter-1");
|
||||
|
||||
// Assert
|
||||
assertEquals(2, result.size());
|
||||
verify(sceneRepository, times(1)).findByChapterId("chapter-1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUpdateScene_Success() {
|
||||
// Arrange
|
||||
Scene updatedScene = Scene.builder()
|
||||
.name("Updated Scene")
|
||||
.description("Updated Description")
|
||||
.order(2)
|
||||
.location("Tavern")
|
||||
.timing("Evening")
|
||||
.atmosphere("Cozy")
|
||||
.playerNarration("Narration")
|
||||
.gmSecretNotes("Secret")
|
||||
.choicesConsequences("Choices")
|
||||
.combatDifficulty("Medium")
|
||||
.enemies("Goblins")
|
||||
.relatedPageIds(List.of("page-1"))
|
||||
.illustrationImageIds(List.of("image-1"))
|
||||
.branches(List.of())
|
||||
.build();
|
||||
when(sceneRepository.findById("scene-1")).thenReturn(Optional.of(testScene));
|
||||
when(sceneRepository.save(any(Scene.class))).thenReturn(testScene);
|
||||
|
||||
// Act
|
||||
Scene result = sceneService.updateScene("scene-1", updatedScene);
|
||||
|
||||
// Assert
|
||||
assertNotNull(result);
|
||||
verify(sceneRepository, times(1)).findById("scene-1");
|
||||
verify(sceneRepository, times(1)).save(any(Scene.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUpdateScene_NotFound() {
|
||||
// Arrange
|
||||
Scene updatedScene = Scene.builder()
|
||||
.name("Updated Scene")
|
||||
.build();
|
||||
when(sceneRepository.findById("invalid-id")).thenReturn(Optional.empty());
|
||||
|
||||
// Act & Assert
|
||||
IllegalArgumentException exception = assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> sceneService.updateScene("invalid-id", updatedScene)
|
||||
);
|
||||
assertEquals("Scene non trouvée avec l'ID: invalid-id", exception.getMessage());
|
||||
verify(sceneRepository, times(1)).findById("invalid-id");
|
||||
verify(sceneRepository, never()).save(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUpdateScene_WithValidBranches() {
|
||||
// Arrange
|
||||
SceneBranch branch = SceneBranch.builder()
|
||||
.targetSceneId("scene-2")
|
||||
.label("Go to scene 2")
|
||||
.build();
|
||||
Scene updatedScene = Scene.builder()
|
||||
.name("Updated Scene")
|
||||
.branches(List.of(branch))
|
||||
.chapterId("chapter-1")
|
||||
.build();
|
||||
when(sceneRepository.findById("scene-1")).thenReturn(Optional.of(testScene));
|
||||
when(sceneRepository.findByChapterId("chapter-1")).thenReturn(List.of(testScene, scene2, scene3));
|
||||
when(sceneRepository.save(any(Scene.class))).thenReturn(testScene);
|
||||
|
||||
// Act
|
||||
Scene result = sceneService.updateScene("scene-1", updatedScene);
|
||||
|
||||
// Assert
|
||||
assertNotNull(result);
|
||||
verify(sceneRepository, times(1)).findById("scene-1");
|
||||
verify(sceneRepository, times(1)).save(any(Scene.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUpdateScene_WithBranchToSelf() {
|
||||
// Arrange
|
||||
SceneBranch branch = SceneBranch.builder()
|
||||
.targetSceneId("scene-1")
|
||||
.label("Self-reference")
|
||||
.build();
|
||||
Scene updatedScene = Scene.builder()
|
||||
.name("Updated Scene")
|
||||
.branches(List.of(branch))
|
||||
.chapterId("chapter-1")
|
||||
.build();
|
||||
when(sceneRepository.findById("scene-1")).thenReturn(Optional.of(testScene));
|
||||
when(sceneRepository.findByChapterId("chapter-1")).thenReturn(List.of(testScene, scene2));
|
||||
|
||||
// Act & Assert
|
||||
IllegalArgumentException exception = assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> sceneService.updateScene("scene-1", updatedScene)
|
||||
);
|
||||
assertEquals("Une scène ne peut pas se brancher sur elle-même", exception.getMessage());
|
||||
verify(sceneRepository, times(1)).findById("scene-1");
|
||||
verify(sceneRepository, never()).save(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUpdateScene_WithBranchToDifferentChapter() {
|
||||
// Arrange
|
||||
SceneBranch branch = SceneBranch.builder()
|
||||
.targetSceneId("scene-other-chapter")
|
||||
.label("Go to other chapter")
|
||||
.build();
|
||||
Scene updatedScene = Scene.builder()
|
||||
.name("Updated Scene")
|
||||
.branches(List.of(branch))
|
||||
.chapterId("chapter-1")
|
||||
.build();
|
||||
when(sceneRepository.findById("scene-1")).thenReturn(Optional.of(testScene));
|
||||
when(sceneRepository.findByChapterId("chapter-1")).thenReturn(List.of(testScene, scene2));
|
||||
|
||||
// Act & Assert
|
||||
IllegalArgumentException exception = assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> sceneService.updateScene("scene-1", updatedScene)
|
||||
);
|
||||
assertTrue(exception.getMessage().contains("n'appartient pas au même chapitre"));
|
||||
verify(sceneRepository, times(1)).findById("scene-1");
|
||||
verify(sceneRepository, never()).save(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUpdateScene_WithBranchNullTarget() {
|
||||
// Arrange
|
||||
SceneBranch branch = SceneBranch.builder()
|
||||
.targetSceneId(null)
|
||||
.label("Null target")
|
||||
.build();
|
||||
Scene updatedScene = Scene.builder()
|
||||
.name("Updated Scene")
|
||||
.branches(List.of(branch))
|
||||
.chapterId("chapter-1")
|
||||
.build();
|
||||
when(sceneRepository.findById("scene-1")).thenReturn(Optional.of(testScene));
|
||||
|
||||
// Act & Assert
|
||||
IllegalArgumentException exception = assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> sceneService.updateScene("scene-1", updatedScene)
|
||||
);
|
||||
assertEquals("Une branche doit avoir une scène de destination", exception.getMessage());
|
||||
verify(sceneRepository, times(1)).findById("scene-1");
|
||||
verify(sceneRepository, never()).save(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUpdateScene_WithBranchBlankTarget() {
|
||||
// Arrange
|
||||
SceneBranch branch = SceneBranch.builder()
|
||||
.targetSceneId(" ")
|
||||
.label("Blank target")
|
||||
.build();
|
||||
Scene updatedScene = Scene.builder()
|
||||
.name("Updated Scene")
|
||||
.branches(List.of(branch))
|
||||
.chapterId("chapter-1")
|
||||
.build();
|
||||
when(sceneRepository.findById("scene-1")).thenReturn(Optional.of(testScene));
|
||||
|
||||
// Act & Assert
|
||||
IllegalArgumentException exception = assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> sceneService.updateScene("scene-1", updatedScene)
|
||||
);
|
||||
assertEquals("Une branche doit avoir une scène de destination", exception.getMessage());
|
||||
verify(sceneRepository, times(1)).findById("scene-1");
|
||||
verify(sceneRepository, never()).save(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUpdateScene_WithEmptyBranches() {
|
||||
// Arrange
|
||||
Scene updatedScene = Scene.builder()
|
||||
.name("Updated Scene")
|
||||
.branches(List.of())
|
||||
.chapterId("chapter-1")
|
||||
.build();
|
||||
when(sceneRepository.findById("scene-1")).thenReturn(Optional.of(testScene));
|
||||
when(sceneRepository.save(any(Scene.class))).thenReturn(testScene);
|
||||
|
||||
// Act
|
||||
Scene result = sceneService.updateScene("scene-1", updatedScene);
|
||||
|
||||
// Assert
|
||||
assertNotNull(result);
|
||||
verify(sceneRepository, times(1)).findById("scene-1");
|
||||
verify(sceneRepository, times(1)).save(any(Scene.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDeleteScene() {
|
||||
// Arrange
|
||||
doNothing().when(sceneRepository).deleteById("scene-1");
|
||||
|
||||
// Act
|
||||
sceneService.deleteScene("scene-1");
|
||||
|
||||
// Assert
|
||||
verify(sceneRepository, times(1)).deleteById("scene-1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSceneExists_True() {
|
||||
// Arrange
|
||||
when(sceneRepository.existsById("scene-1")).thenReturn(true);
|
||||
|
||||
// Act
|
||||
boolean result = sceneService.sceneExists("scene-1");
|
||||
|
||||
// Assert
|
||||
assertTrue(result);
|
||||
verify(sceneRepository, times(1)).existsById("scene-1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSceneExists_False() {
|
||||
// Arrange
|
||||
when(sceneRepository.existsById("invalid-id")).thenReturn(false);
|
||||
|
||||
// Act
|
||||
boolean result = sceneService.sceneExists("invalid-id");
|
||||
|
||||
// Assert
|
||||
assertFalse(result);
|
||||
verify(sceneRepository, times(1)).existsById("invalid-id");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user