Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@
public record AdminScenarioNodeResponse(
Long nodeId,
String nodeName,
Boolean isStartNode,
Integer audioDuration,
Long autoNextNodeId,
String autoNextNode,
List<AdminScenarioScriptResponse> scripts,
List<AdminScenarioOptionResponse> interactiveOptions
) {}
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@
public record AdminScenarioOptionResponse(
String label,
String title,
Long nextNodeId
Long nextNodeId,
String nextNodeName
) {}
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,19 @@ public UserScenarioResponse toUserResponse(Scenario scenario, AudioPathType reco
}

public AdminScenarioDetailResponse toAdminDetailResponse(Scenario scenario) {
// 어드민은 노드 이름으로 연결을 편집하므로 ID 기반 링크를 이름으로도 내려준다.
Map<Long, String> nodeNameById = scenario.getNodes().stream()
.filter(node -> node.getId() != null && node.getNodeName() != null)
.collect(Collectors.toMap(ScenarioNode::getId, ScenarioNode::getNodeName, (first, second) -> first));

return AdminScenarioDetailResponse.builder()
.scenarioId(scenario.getId())
.battleId(scenario.getBattle().getId())
.title(scenario.getBattle().getTitle())
.isInteractive(scenario.getIsInteractive())
.voiceSettings(new HashMap<>(scenario.getVoiceSettings()))
.nodes(scenario.getNodes().stream()
.map(this::toAdminNodeResponse)
.map(node -> toAdminNodeResponse(node, nodeNameById))
.collect(Collectors.toList()))
.build();
}
Expand Down Expand Up @@ -99,17 +104,19 @@ private ScriptResponse toUserScriptResponse(Script script) {
.build();
}

private AdminScenarioNodeResponse toAdminNodeResponse(ScenarioNode node) {
private AdminScenarioNodeResponse toAdminNodeResponse(ScenarioNode node, Map<Long, String> nodeNameById) {
return AdminScenarioNodeResponse.builder()
.nodeId(node.getId())
.nodeName(node.getNodeName())
.isStartNode(Boolean.TRUE.equals(node.getIsStartNode()))
.audioDuration(node.getAudioDuration())
.autoNextNodeId(node.getAutoNextNodeId())
.autoNextNode(nodeNameById.get(node.getAutoNextNodeId()))
.scripts(node.getScripts().stream()
.map(this::toAdminScriptResponse)
.collect(Collectors.toList()))
.interactiveOptions(node.getOptions().stream()
.map(this::toAdminOptionResponse)
.map(option -> toAdminOptionResponse(option, nodeNameById))
.collect(Collectors.toList()))
.build();
}
Expand All @@ -133,11 +140,12 @@ private OptionResponse toUserOptionResponse(InteractiveOption option) {
.build();
}

private AdminScenarioOptionResponse toAdminOptionResponse(InteractiveOption option) {
private AdminScenarioOptionResponse toAdminOptionResponse(InteractiveOption option, Map<Long, String> nodeNameById) {
return AdminScenarioOptionResponse.builder()
.label(option.getLabel())
.title(option.getTitle())
.nextNodeId(option.getNextNodeId())
.nextNodeName(nodeNameById.get(option.getNextNodeId()))
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
Expand Down Expand Up @@ -111,6 +112,66 @@ void patchScenarioStatus_toPublished_triggersAudioPipeline() throws Exception {
verify(scenarioAudioPipelineService, timeout(1000)).generateAndMergeAudioAsync(scenarioId);
}

@Test
void getAdminScenario_returnsNodeLinksByName() throws Exception {
String adminToken = createAdminToken();
Battle battle = createBattle();

mockMvc.perform(post("/api/v1/admin/scenarios")
.header("Authorization", "Bearer " + adminToken)
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(interactiveScenarioPayload(battle.getId()))))
.andExpect(status().isCreated());

mockMvc.perform(get("/api/v1/admin/battles/{battleId}/scenario", battle.getId())
.header("Authorization", "Bearer " + adminToken))
.andExpect(status().isOk())
.andExpect(jsonPath("$.data.nodes[?(@.nodeName == 'START')].isStartNode").value(true))
.andExpect(jsonPath("$.data.nodes[?(@.nodeName == 'START')].interactiveOptions[0].nextNodeName").value("BRANCH_A"))
.andExpect(jsonPath("$.data.nodes[?(@.nodeName == 'START')].interactiveOptions[1].nextNodeName").value("BRANCH_B"))
.andExpect(jsonPath("$.data.nodes[?(@.nodeName == 'BRANCH_A')].isStartNode").value(false))
.andExpect(jsonPath("$.data.nodes[?(@.nodeName == 'BRANCH_A')].autoNextNode").value("CLOSING"))
.andExpect(jsonPath("$.data.nodes[?(@.nodeName == 'BRANCH_B')].autoNextNode").value("CLOSING"));
}

private Map<String, Object> interactiveScenarioPayload(Long battleId) {
return Map.of(
"battleId", battleId,
"isInteractive", true,
"status", "PENDING",
"nodes", List.of(
Map.of(
"nodeName", "START",
"isStartNode", true,
"autoNextNode", "",
"scripts", List.of(script("A", "Opening")),
"interactiveOptions", List.of(
Map.of("label", "A", "title", "A side", "nextNodeName", "BRANCH_A"),
Map.of("label", "B", "title", "B side", "nextNodeName", "BRANCH_B")
)
),
node("BRANCH_A", "CLOSING", script("A", "Branch A")),
node("BRANCH_B", "CLOSING", script("B", "Branch B")),
node("CLOSING", "", script("NARRATOR", "Closing"))
),
"voiceSettings", Map.of("NARRATOR", "voice-narrator")
);
}

private Map<String, Object> node(String nodeName, String autoNextNode, Map<String, Object> script) {
return Map.of(
"nodeName", nodeName,
"isStartNode", false,
"autoNextNode", autoNextNode,
"scripts", List.of(script),
"interactiveOptions", List.of()
);
}

private Map<String, Object> script(String speakerType, String text) {
return Map.of("speakerType", speakerType, "speakerName", speakerType, "text", text);
}

private Map<String, Object> scenarioPayload(Long battleId, String status) {
return Map.of(
"battleId", battleId,
Expand Down
Loading