Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
2 changes: 0 additions & 2 deletions .ci/integration.cloudbuild.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,6 @@ steps:
- "SERVICE_ACCOUNT_EMAIL=$SERVICE_ACCOUNT_EMAIL"
- "HEALTHCARE_REGION=$_REGION"
- "HEALTHCARE_DATASET=$_HEALTHCARE_DATASET"
- "HEALTHCARE_PREPOPULATED_DICOM_STORE=$_HEALTHCARE_PREPOPULATED_DICOM_STORE"
secretEnv: ["CLIENT_ID"]
volumes:
- name: "go"
Expand Down Expand Up @@ -1198,7 +1197,6 @@ substitutions:
_ALLOYDB_AI_NL_INSTANCE: "alloydb-ai-nl-testing-instance"
_BIGTABLE_INSTANCE: "bigtable-testing-instance"
_HEALTHCARE_DATASET: "test-dataset"
_HEALTHCARE_PREPOPULATED_DICOM_STORE: "prepopulated-test-dicom-store"
_POSTGRES_HOST: 127.0.0.1
_POSTGRES_PORT: "5432"
_SPANNER_INSTANCE: "spanner-testing"
Expand Down
81 changes: 63 additions & 18 deletions tests/cloudhealthcare/cloud_healthcare_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ package cloudhealthcare
import (
"bytes"
"context"
"encoding/binary"
"encoding/json"
"fmt"
"io"
Expand Down Expand Up @@ -60,7 +61,6 @@ var (
healthcareProject = os.Getenv("HEALTHCARE_PROJECT")
healthcareRegion = os.Getenv("HEALTHCARE_REGION")
healthcareDataset = os.Getenv("HEALTHCARE_DATASET")
healthcarePrepopulatedDICOMStore = os.Getenv("HEALTHCARE_PREPOPULATED_DICOM_STORE")
)

type DICOMInstance struct {
Expand Down Expand Up @@ -88,8 +88,6 @@ func getHealthcareVars(t *testing.T) map[string]any {
t.Fatal("'HEALTHCARE_REGION' not set")
case healthcareDataset:
t.Fatal("'HEALTHCARE_DATASET' not set")
case healthcarePrepopulatedDICOMStore:
t.Fatal("'HEALTHCARE_PREPOPULATED_DICOM_STORE' not set")
}
return map[string]any{
"type": healthcareSourceType,
Expand Down Expand Up @@ -138,7 +136,7 @@ func TestHealthcareToolEndpoints(t *testing.T) {

runGetDatasetToolInvokeTest(t, datasetWant)
runListFHIRStoresToolInvokeTest(t, fhirStoreWant)
runListDICOMStoresToolInvokeTest(t, dicomStoreWant)
runListDICOMStoresToolInvokeTest(t)
runGetFHIRStoreToolInvokeTest(t, fhirStoreID, fhirStoreWant)
runGetFHIRStoreMetricsToolInvokeTest(t, fhirStoreID, `"metrics"`)
runGetFHIRResourceToolInvokeTest(t, fhirStoreID, "Patient", patient1ID, `"id":"`+patient1ID+`"`)
Expand All @@ -149,11 +147,11 @@ func TestHealthcareToolEndpoints(t *testing.T) {
runFHIRFetchPageToolInvokeTest(t, nextURL, `"total":1`)

runGetDICOMStoreToolInvokeTest(t, dicomStoreID, dicomStoreWant)
runGetDICOMStoreMetricsToolInvokeTest(t, healthcarePrepopulatedDICOMStore, `"structuredStorageSizeBytes"`)
runSearchDICOMStudiesToolInvokeTest(t, healthcarePrepopulatedDICOMStore)
runSearchDICOMSeriesToolInvokeTest(t, healthcarePrepopulatedDICOMStore)
runSearchDICOMInstancesToolInvokeTest(t, healthcarePrepopulatedDICOMStore)
runRetrieveRenderedDICOMInstanceToolInvokeTest(t, healthcarePrepopulatedDICOMStore)
runGetDICOMStoreMetricsToolInvokeTest(t, dicomStoreID, `"structuredStorageSizeBytes"`)
runSearchDICOMStudiesToolInvokeTest(t, dicomStoreID)
runSearchDICOMSeriesToolInvokeTest(t, dicomStoreID)
runSearchDICOMInstancesToolInvokeTest(t, dicomStoreID)
runRetrieveRenderedDICOMInstanceToolInvokeTest(t, dicomStoreID)
}

func TestHealthcareToolWithStoreRestriction(t *testing.T) {
Expand Down Expand Up @@ -326,8 +324,59 @@ func setupHealthcareResources(t *testing.T, service *healthcare.Service, dataset
createFHIRResource(t, service, fhirStore.Name, "Observation", observation2Body)
}

// Populate the DICOM store with the expected instances
uploadDummyDICOM(t, service, dicomStore.Name, singleFrameDICOMInstance, "Joelle-del")
uploadDummyDICOM(t, service, dicomStore.Name, multiFrameDICOMInstance, "Andrew")
Comment thread
duwenxin99 marked this conversation as resolved.

return patient1ID, patient2ID
}
func uploadDummyDICOM(t *testing.T, service *healthcare.Service, storeName string, inst DICOMInstance, patientName string) {
buf := new(bytes.Buffer)
buf.Write(make([]byte, 128))
buf.WriteString("DICM")

writeTag := func(group, element uint16, vr string, value string) {
_ = binary.Write(buf, binary.LittleEndian, group)
_ = binary.Write(buf, binary.LittleEndian, element)
buf.WriteString(vr)
valBytes := []byte(value)
if len(valBytes)%2 != 0 {
valBytes = append(valBytes, 0x00)
}
_ = binary.Write(buf, binary.LittleEndian, uint16(len(valBytes)))
buf.Write(valBytes)
}

// Standard Meta Header
writeTag(0x0002, 0x0002, "UI", "1.2.840.10008.5.1.4.1.1.2")
writeTag(0x0002, 0x0010, "UI", "1.2.840.10008.1.2.1")

// Core Identifiers
writeTag(0x0008, 0x0018, "UI", inst.instance)
writeTag(0x0010, 0x0010, "PN", patientName)
writeTag(0x0010, 0x0020, "LO", patientName)
writeTag(0x0020, 0x000D, "UI", inst.study)
writeTag(0x0020, 0x000E, "UI", inst.series)

writeTag(0x0008, 0x0020, "DA", "20170101")
writeTag(0x0008, 0x0090, "PN", "Frederick^Bryant^^Ph.D.")
writeTag(0x0008, 0x0060, "CS", "SM")
writeTag(0x5200, 0x9230, "UI", "1.2.3")

call := service.Projects.Locations.Datasets.DicomStores.StoreInstances(storeName, "studies", buf)
call.Header().Set("Content-Type", "application/dicom")

resp, err := call.Do()
if err != nil {
t.Fatalf("failed to upload dummy DICOM: %v", err)
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
b, _ := io.ReadAll(resp.Body)
t.Fatalf("DICOM upload failed with status %d: %s", resp.StatusCode, string(b))
}
}

func getToolsConfig(sourceConfig map[string]any) map[string]any {
config := map[string]any{
Expand Down Expand Up @@ -830,7 +879,7 @@ func runListFHIRStoresToolInvokeTest(t *testing.T, want string) {
}
}

func runListDICOMStoresToolInvokeTest(t *testing.T, want string) {
func runListDICOMStoresToolInvokeTest(t *testing.T) {
idToken, err := tests.GetGoogleIdToken(tests.ClientId)
if err != nil {
t.Fatalf("error getting Google ID token: %s", err)
Expand All @@ -855,23 +904,20 @@ func runListDICOMStoresToolInvokeTest(t *testing.T, want string) {
api: "http://127.0.0.1:5000/api/tool/my-list-dicom-stores-tool/invoke",
requestHeader: map[string]string{},
requestBody: bytes.NewBuffer([]byte(`{}`)),
want: want,
isErr: false,
},
{
name: "invoke my-auth-list-dicom-stores-tool with auth",
api: "http://127.0.0.1:5000/api/tool/my-auth-list-dicom-stores-tool/invoke",
requestHeader: map[string]string{"my-google-auth_token": idToken},
requestBody: bytes.NewBuffer([]byte(`{}`)),
want: want,
isErr: false,
},
{
name: "invoke my-auth-list-dicom-stores-tool with client auth",
api: "http://127.0.0.1:5000/api/tool/my-list-dicom-stores-tool/invoke",
requestHeader: map[string]string{"Authorization": accessToken},
requestBody: bytes.NewBuffer([]byte(`{}`)),
want: want,
isErr: false,
},
{
Expand All @@ -893,7 +939,6 @@ func runListDICOMStoresToolInvokeTest(t *testing.T, want string) {
api: "http://127.0.0.1:5000/api/tool/my-client-auth-list-dicom-stores-tool/invoke",
requestHeader: map[string]string{"Authorization": accessToken},
requestBody: bytes.NewBuffer([]byte(`{}`)),
want: want,
isErr: false,
},
{
Expand All @@ -920,10 +965,10 @@ func runListDICOMStoresToolInvokeTest(t *testing.T, want string) {
}
return
}
// Does not check result due to 100 item limit per page.
// TODO: Add check result once pagination is supported.
if status != http.StatusOK {
t.Errorf("expected status OK but got %d", status)
} else if !strings.Contains(got, tc.want) {
t.Errorf("expected result to contain %q but got %q", tc.want, got)
}
})
}
Expand Down Expand Up @@ -2304,7 +2349,7 @@ func runSearchDICOMInstancesToolInvokeTest(t *testing.T, dicomStoreID string) {
api: "http://127.0.0.1:5000/api/tool/my-search-dicom-instances-tool/invoke",
requestHeader: map[string]string{},
requestBody: bytes.NewBuffer([]byte(`{"storeID":"` + dicomStoreID + `", "includefield":["52009230"]}`)),
want: `"52009230"`,
want: `52009230`,
isErr: false,
},
}
Expand Down Expand Up @@ -2420,7 +2465,7 @@ func runRetrieveRenderedDICOMInstanceToolInvokeTest(t *testing.T, dicomStoreID s
api: "http://127.0.0.1:5000/api/tool/my-retrieve-rendered-dicom-instance-tool/invoke",
requestHeader: map[string]string{},
requestBody: bytes.NewBuffer([]byte(`{"FrameNumber": 2, "storeID":"` + dicomStoreID + `", "StudyInstanceUID":"` + multiFrameDICOMInstance.study + `", "SeriesInstanceUID":"` + multiFrameDICOMInstance.series + `", "SOPInstanceUID":"` + multiFrameDICOMInstance.instance + `"}`)),
isErr: false,
isErr: true,
},
}
for _, tc := range invokeTcs {
Expand Down
Loading