Skip to content

Commit

Permalink
#433 - Change endpoint /codeable-concept/entry/{id} to allow for mult…
Browse files Browse the repository at this point in the history
…iple ids

- Endpoint is changed to /codeable-concept/entry, expecting a list of ids as query parameter
- CodeableConceptEntry now included the id in the returned json
  • Loading branch information
michael-82 committed Jan 24, 2025
1 parent 3878f77 commit 0fcbecf
Show file tree
Hide file tree
Showing 7 changed files with 69 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@

@Builder
public record CodeableConceptEntry(
String id,
TermCode termCode,
DisplayEntry display
) {
public static CodeableConceptEntry of(CodeableConceptDocument document) {
return CodeableConceptEntry.builder()
.id(document.id())
.termCode(document.termCode())
.display(DisplayEntry.builder()
.original(document.display().original())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import de.numcodex.feasibility_gui_backend.terminology.api.CodeableConceptEntry;
import de.numcodex.feasibility_gui_backend.terminology.es.model.CodeableConceptDocument;
import de.numcodex.feasibility_gui_backend.terminology.es.repository.CodeableConceptEsRepository;
import de.numcodex.feasibility_gui_backend.terminology.es.repository.OntologyItemNotFoundException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
Expand Down Expand Up @@ -56,9 +55,11 @@ public CcSearchResult performCodeableConceptSearchWithRepoAndPaging(String keywo
.build();
}

public CodeableConceptEntry getSearchResultEntryByCode(String code) {
var document = repo.findById(code).orElseThrow(OntologyItemNotFoundException::new);
return CodeableConceptEntry.of(document);
public List<CodeableConceptEntry> getSearchResultsEntryByIds(List<String> ids) {
var documents = repo.findAllById(ids);
var codeableConceptEntries = new ArrayList<CodeableConceptEntry>();
documents.forEach(d -> codeableConceptEntries.add(CodeableConceptEntry.of(d)));
return codeableConceptEntries;
}

private SearchHits<CodeableConceptDocument> findByCodeOrDisplay(String keyword,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
@CrossOrigin
public class CodeableConceptRestController {

private CodeableConceptService codeableConceptService;
private final CodeableConceptService codeableConceptService;

@Autowired
public CodeableConceptRestController(CodeableConceptService codeableConceptService) {
Expand All @@ -33,8 +33,8 @@ public CcSearchResult searchOntologyItemsCriteriaQuery(@RequestParam("searchterm
.performCodeableConceptSearchWithRepoAndPaging(keyword, valueSets, pageSize, page);
}

@GetMapping(value = "/entry/{code}", produces = MediaType.APPLICATION_JSON_VALUE)
public CodeableConceptEntry getCodeableConceptByCode(@PathVariable("code") String code) {
return codeableConceptService.getSearchResultEntryByCode(code);
@GetMapping(value = "/entry", produces = MediaType.APPLICATION_JSON_VALUE)
public List<CodeableConceptEntry> getCodeableConceptsByCode(@RequestParam List<String> ids) {
return codeableConceptService.getSearchResultsEntryByIds(ids);
}
}
16 changes: 9 additions & 7 deletions src/main/resources/static/v3/api-docs/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -869,20 +869,22 @@ paths:
description: Unauthorized - please login first
403:
description: Forbidden - insufficient access rights
/codeable-concept/entry/{id}:
/codeable-concept/entry:
get:
tags:
- codeable concepts
summary: Search for codeable concepts by code or display. Optionally filter by value sets
operationId: getCodeableConceptByCode
summary: Retrieve Codeable Concepts by their ids
operationId: getCodeableConceptsByCode
parameters:
- name: id
in: path
description: The id (contextualized termcode hash) of the entry that shall be retrieved
- name: ids
in: query
style: form
explode: false
description: The ids (contextualized termcode hash) of the entry that shall be retrieved
required: true
schema:
type: string
example: 203e04cd-4f0a-321b-b1ad-9ec6d211e0a8
example: 203e04cd-4f0a-321b-b1ad-9ec6d211e0a8,203e04cd-4f0a-321b-b1ad-9ec6d211e0a9
responses:
200:
description: Ok, return the codeable concept
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThrows;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertNotNull;
Expand Down Expand Up @@ -117,18 +118,21 @@ void testPerformCodeableConceptSearchWithRepoAndPaging_findsTwoOrOneDependingOnF
}

@Test
void testGetSearchResultEntryByCode_succeeds() {
var result = assertDoesNotThrow(() -> codeableConceptService.getSearchResultEntryByCode("A1.1"));
void testGetSearchResultsEntryByIds_succeeds() {
var result = assertDoesNotThrow(() -> codeableConceptService.getSearchResultsEntryByIds(List.of("A1.1")));

assertNotNull(result);
Assertions.assertEquals("bar", result.termCode().display());
Assertions.assertEquals("A1.1", result.termCode().code());
Assertions.assertEquals("2012", result.termCode().version());
Assertions.assertEquals("another-system", result.termCode().system());
Assertions.assertFalse(result.isEmpty());
Assertions.assertEquals("bar", result.get(0).termCode().display());
Assertions.assertEquals("A1.1", result.get(0).termCode().code());
Assertions.assertEquals("2012", result.get(0).termCode().version());
Assertions.assertEquals("another-system", result.get(0).termCode().system());
}

@Test
void testGetSearchResultEntryByCode_throwsOnNotFound() {
assertThrows(OntologyItemNotFoundException.class, () -> codeableConceptService.getSearchResultEntryByCode("something-not-found"));
void testGetSearchResultsEntryByIds_emptyOnNotFound() {
var result = assertDoesNotThrow(() -> codeableConceptService.getSearchResultsEntryByIds(List.of("something-not-found")));
assertNotNull(result);
Assertions.assertTrue(result.isEmpty());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import de.numcodex.feasibility_gui_backend.terminology.es.model.CodeableConceptDocument;
import de.numcodex.feasibility_gui_backend.terminology.es.model.Display;
import de.numcodex.feasibility_gui_backend.terminology.es.repository.CodeableConceptEsRepository;
import de.numcodex.feasibility_gui_backend.terminology.es.repository.OntologyItemNotFoundException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
Expand All @@ -18,12 +17,12 @@

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.Mockito.doReturn;

@ExtendWith(MockitoExtension.class)
Expand Down Expand Up @@ -147,16 +146,17 @@ void testPerformCodeableConceptSearchWithRepoAndPaging_succeedsWithEmptyKeyword(
}

@Test
void testGetSearchResultEntryByCode_succeeds() {
void testGetSearchResultsEntryByIds_succeeds() {
CodeableConceptDocument dummyCodeableConceptDocument = createDummyCodeableConceptDocument("1");
doReturn(Optional.of(dummyCodeableConceptDocument)).when(repository).findById(any());
doReturn(List.of(dummyCodeableConceptDocument)).when(repository).findAllById(any());

var result = assertDoesNotThrow(() -> codeableConceptService.getSearchResultEntryByCode("1"));
var result = assertDoesNotThrow(() -> codeableConceptService.getSearchResultsEntryByIds(List.of("1")));

assertNotNull(result);
assertEquals(dummyCodeableConceptDocument.termCode(), result.termCode());
assertEquals(dummyCodeableConceptDocument.display().original(), result.display().original());
assertTrue(result.display().translations().containsAll(
assertFalse(result.isEmpty());
assertEquals(dummyCodeableConceptDocument.termCode(), result.get(0).termCode());
assertEquals(dummyCodeableConceptDocument.display().original(), result.get(0).display().original());
assertTrue(result.get(0).display().translations().containsAll(
List.of(
LocalizedValue.builder()
.value(dummyCodeableConceptDocument.display().deDe())
Expand All @@ -171,10 +171,12 @@ void testGetSearchResultEntryByCode_succeeds() {
}

@Test
void testGetSearchResultEntryByCode_throwsOnNotFound() {
doReturn(Optional.empty()).when(repository).findById(any());
void testGetSearchResultsEntryByIds_emptyOnNotFound() {
doReturn(List.of()).when(repository).findAllById(anyList());

assertThrows(OntologyItemNotFoundException.class, () -> codeableConceptService.getSearchResultEntryByCode("foo"));
var result = assertDoesNotThrow(() -> codeableConceptService.getSearchResultsEntryByIds(List.of("foo")));
assertNotNull(result);
assertTrue(result.isEmpty());
}

private SearchHits<CodeableConceptDocument> createDummySearchHitsPage(int totalHits) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@
import org.springframework.test.web.servlet.MockMvc;

import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

import static de.numcodex.feasibility_gui_backend.config.WebSecurityConfig.PATH_API;
import static de.numcodex.feasibility_gui_backend.config.WebSecurityConfig.PATH_CODEABLE_CONCEPT;
Expand Down Expand Up @@ -75,30 +77,39 @@ void testSearchOntologyItemsCriteriaQuery_succeedsWith200() throws Exception {
@Test
@WithMockUser(roles = "DATAPORTAL_TEST_USER")
void testGetCodeableConceptByCode_succeedsWith200() throws Exception {
CodeableConceptEntry dummyCodeableConceptEntry = createDummyCodeableConceptEntry();
doReturn(dummyCodeableConceptEntry).when(codeableConceptService).getSearchResultEntryByCode(any(String.class));
var id = UUID.randomUUID();
List<CodeableConceptEntry> dummyCodeableConceptEntries = createDummyCodeableConceptEntries(List.of(id));
doReturn(dummyCodeableConceptEntries).when(codeableConceptService).getSearchResultsEntryByIds(anyList());

mockMvc.perform(get(URI.create(PATH_API + PATH_CODEABLE_CONCEPT + "/entry/1")).with(csrf()))
mockMvc.perform(get(URI.create(PATH_API + PATH_CODEABLE_CONCEPT + "/entry")).param("ids", id.toString()).with(csrf()))
.andExpect(status().isOk())
.andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.termCode.code").value(dummyCodeableConceptEntry.termCode().code()))
.andExpect(jsonPath("$.termCode.system").value(dummyCodeableConceptEntry.termCode().system()))
.andExpect(jsonPath("$.termCode.version").value(dummyCodeableConceptEntry.termCode().version()))
.andExpect(jsonPath("$.termCode.display").value(dummyCodeableConceptEntry.termCode().display()));
.andExpect(jsonPath("$.[0].termCode.code").value(dummyCodeableConceptEntries.get(0).termCode().code()))
.andExpect(jsonPath("$.[0].termCode.system").value(dummyCodeableConceptEntries.get(0).termCode().system()))
.andExpect(jsonPath("$.[0].termCode.version").value(dummyCodeableConceptEntries.get(0).termCode().version()))
.andExpect(jsonPath("$.[0].termCode.display").value(dummyCodeableConceptEntries.get(0).termCode().display()));
}

private CcSearchResult createDummyCcSearchResult() {
var id = UUID.randomUUID();
return CcSearchResult.builder()
.totalHits(1)
.results(List.of(createDummyCodeableConceptEntry()))
.results(createDummyCodeableConceptEntries(List.of(id)))
.build();
}

private CodeableConceptEntry createDummyCodeableConceptEntry() {
return CodeableConceptEntry.builder()
.termCode(createDummyTermcode())
.display(createDummyDisplayEntry())
.build();
private List<CodeableConceptEntry> createDummyCodeableConceptEntries(List<UUID> ids) {
List<CodeableConceptEntry> dummyCodeableConceptEntries = new ArrayList<>();
for (UUID id : ids) {
dummyCodeableConceptEntries.add(
CodeableConceptEntry.builder()
.id(id.toString())
.termCode(createDummyTermcode())
.display(createDummyDisplayEntry())
.build()
);
}
return dummyCodeableConceptEntries;
}

private DisplayEntry createDummyDisplayEntry() {
Expand Down

0 comments on commit 0fcbecf

Please sign in to comment.