Skip to content

Commit

Permalink
Merge pull request #435 from medizininformatik-initiative/feature/433…
Browse files Browse the repository at this point in the history
…-change-endpoint-codeable-conceptentryid-to-allow-for-multiple-ids

#433 - Change endpoint /codeable-concept/entry/{id} to allow for multiple ids
  • Loading branch information
michael-82 authored Jan 24, 2025
2 parents ac2ae46 + c779765 commit a26f0c0
Show file tree
Hide file tree
Showing 8 changed files with 74 additions and 49 deletions.
4 changes: 2 additions & 2 deletions .github/scripts/post-elastic-test-queries.sh
Original file line number Diff line number Diff line change
Expand Up @@ -95,12 +95,12 @@ else
exit 1
fi

response=$(curl -s -w "%{http_code}" --header "Authorization: Bearer $access_token" -o response_body "http://localhost:8091/api/v4/codeable-concept/entry/$cc_example_id")
response=$(curl -s -w "%{http_code}" --header "Authorization: Bearer $access_token" -o response_body "http://localhost:8091/api/v4/codeable-concept/entry?ids=$cc_example_id")
http_code="${response: -3}"
json_body=$(cat response_body)

if [ "$http_code" -eq 200 ]; then
if echo "$json_body" | jq -e '.termCode.code and .termCode.code != ""' | grep -q true; then
if echo "$json_body" | jq -e '.[0].termCode.code and .[0].termCode.code != ""' | grep -q true; then
echo "OK response with non-empty array on get cc by id"
else
echo "Empty or nonexistent response on get cc by id"
Expand Down
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);
}
}
19 changes: 12 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 Expand Up @@ -1459,6 +1461,9 @@ components:
CodeableConceptEntry:
type: object
properties:
id:
type: string
format: uuid
termCode:
$ref: "#/components/schemas/TermCode"
display:
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 a26f0c0

Please sign in to comment.