diff --git a/backend/pkg/web/handler/account.go b/backend/pkg/web/handler/account.go index c90aad64..453cb959 100644 --- a/backend/pkg/web/handler/account.go +++ b/backend/pkg/web/handler/account.go @@ -8,7 +8,7 @@ import ( "net/http" ) -// SECURITY: this is a secure endpoint, and should only be called after a double confirmation +// UX: this is a secure endpoint, and should only be called after a double confirmation func DeleteAccount(c *gin.Context) { logger := c.MustGet(pkg.ContextKeyTypeLogger).(*logrus.Entry) databaseRepo := c.MustGet(pkg.ContextKeyTypeDatabase).(database.DatabaseRepository) diff --git a/backend/pkg/web/handler/resource_related_test.go b/backend/pkg/web/handler/resource_related_test.go new file mode 100644 index 00000000..a215f4dc --- /dev/null +++ b/backend/pkg/web/handler/resource_related_test.go @@ -0,0 +1,143 @@ +package handler + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "github.com/fastenhealth/fasten-onprem/backend/pkg" + mock_config "github.com/fastenhealth/fasten-onprem/backend/pkg/config/mock" + "github.com/fastenhealth/fasten-onprem/backend/pkg/database" + "github.com/fastenhealth/fasten-onprem/backend/pkg/event_bus" + "github.com/fastenhealth/fasten-onprem/backend/pkg/models" + "github.com/gin-gonic/gin" + "github.com/golang/mock/gomock" + "github.com/sirupsen/logrus" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + "io" + "io/ioutil" + "log" + "mime/multipart" + "net/http" + "net/http/httptest" + "os" + "testing" +) + +// Go through this page to understand how this file is structured. +// https://pkg.go.dev/github.com/stretchr/testify/suite#section-documentation + +// Define the suite, and absorb the built-in basic suite +// functionality from testify - including a T() method which +// returns the current testing context +type ResourceRelatedHandlerTestSuite struct { + suite.Suite + MockCtrl *gomock.Controller + TestDatabase *os.File + + AppConfig *mock_config.MockInterface + AppRepository database.DatabaseRepository + AppEventBus event_bus.Interface +} + +// BeforeTest has a function to be executed right before the test starts and receives the suite and test names as input +func (suite *ResourceRelatedHandlerTestSuite) BeforeTest(suiteName, testName string) { + suite.MockCtrl = gomock.NewController(suite.T()) + + dbFile, err := ioutil.TempFile("", fmt.Sprintf("%s.*.db", testName)) + if err != nil { + log.Fatal(err) + } + suite.TestDatabase = dbFile + + appConfig := mock_config.NewMockInterface(suite.MockCtrl) + appConfig.EXPECT().GetString("database.location").Return(suite.TestDatabase.Name()).AnyTimes() + appConfig.EXPECT().GetString("database.type").Return("sqlite").AnyTimes() + appConfig.EXPECT().IsSet("database.encryption.key").Return(false).AnyTimes() + appConfig.EXPECT().GetString("log.level").Return("INFO").AnyTimes() + suite.AppConfig = appConfig + + appRepo, err := database.NewRepository(suite.AppConfig, logrus.WithField("test", suite.T().Name()), event_bus.NewNoopEventBusServer()) + suite.AppRepository = appRepo + + suite.AppEventBus = event_bus.NewNoopEventBusServer() + + appRepo.CreateUser(context.Background(), &models.User{ + Username: "test_username", + Password: "test", + }) + +} + +// AfterTest has a function to be executed right after the test finishes and receives the suite and test names as input +func (suite *ResourceRelatedHandlerTestSuite) AfterTest(suiteName, testName string) { + suite.MockCtrl.Finish() + os.Remove(suite.TestDatabase.Name()) +} + +// In order for 'go test' to run this suite, we need to create +// a normal test function and pass our suite to suite.Run +func TestResourceRelatedHandlerTestSuite(t *testing.T) { + suite.Run(t, new(ResourceRelatedHandlerTestSuite)) +} + +func (suite *ResourceRelatedHandlerTestSuite) TestResourceRelatedHandlerTestSuite() { + //setup + w := httptest.NewRecorder() + ctx, _ := gin.CreateTestContext(w) + ctx.Set(pkg.ContextKeyTypeLogger, logrus.WithField("test", suite.T().Name())) + ctx.Set(pkg.ContextKeyTypeDatabase, suite.AppRepository) + ctx.Set(pkg.ContextKeyTypeConfig, suite.AppConfig) + ctx.Set(pkg.ContextKeyTypeEventBusServer, suite.AppEventBus) + ctx.Set(pkg.ContextKeyTypeAuthUsername, "test_username") + + //test + relatedJsonForm, relatedJsonWriter := createMultipartFormData(suite.T(), "file", "testdata/related.json") // just pass the file name + relatedReq, err := http.NewRequest("POST", "/api/v1/resource/related", &relatedJsonForm) + // set the content type, this will contain the boundary. + relatedReq.Header.Set("Content-Type", relatedJsonWriter.FormDataContentType()) + ctx.Request = relatedReq + require.NoError(suite.T(), err) + + CreateRelatedResources(ctx) + + var responseWrapper models.ResponseWrapper + err = json.Unmarshal(w.Body.Bytes(), &responseWrapper) + require.NoError(suite.T(), err) + + summary := responseWrapper.Data.(map[string]interface{}) + + //assert + assert.EqualValues(suite.T(), http.StatusOK, w.Code) + assert.Equal(suite.T(), summary["TotalResources"], float64(3)) + +} + +// https://stackoverflow.com/a/56696333/1157633 +func createMultipartFormData(t *testing.T, fieldName, fileName string) (bytes.Buffer, *multipart.Writer) { + var b bytes.Buffer + var err error + w := multipart.NewWriter(&b) + var fw io.Writer + file := mustOpen(fileName) + if fw, err = w.CreateFormFile(fieldName, file.Name()); err != nil { + t.Errorf("Error creating writer: %v", err) + } + if _, err = io.Copy(fw, file); err != nil { + t.Errorf("Error with io.Copy: %v", err) + } + w.Close() + return b, w +} + +func mustOpen(f string) *os.File { + r, err := os.Open(f) + if err != nil { + pwd, _ := os.Getwd() + fmt.Println("PWD: ", pwd) + panic(err) + } + return r +} diff --git a/backend/pkg/web/handler/testdata/related.json b/backend/pkg/web/handler/testdata/related.json new file mode 100644 index 00000000..b4d279b5 --- /dev/null +++ b/backend/pkg/web/handler/testdata/related.json @@ -0,0 +1,69 @@ +{ + "resourceType": "List", + "entry": [], + "encounter": { + "reference": "Encounter/4e4e6346-2350-47c0-ba73-ee0fdaf3a8a7" + }, + "contained": [ + { + "resourceType": "Encounter", + "id": "4e4e6346-2350-47c0-ba73-ee0fdaf3a8a7", + "serviceType": { + "id": "562", + "identifier": [ + { + "system": "http://terminology.hl7.org/CodeSystem/service-type", + "code": "562", + "display": "Test Message" + } + ], + "text": "Test Message" + }, + "status": "finished", + "participant": [], + "period": { + "start": "2024-01-04T08:00:00.000Z", + "end": "2024-01-27T08:00:00.000Z" + }, + "reasonReference": [], + "serviceProvider": {} + }, + { + "id": "4e2611e4-df0d-4dae-ad32-1c90537b32af", + "resourceType": "Binary", + "contentType": "application/pdf", + "data": "JVBERi0xLjcKCjEgMCBvYmogICUgZW50cnkgcG9pbnQKPDwKICAvVHlwZSAvQ2F0YWxvZwogIC9QYWdlcyAyIDAgUgo+PgplbmRvYmoKCjIgMCBvYmoKPDwKICAvVHlwZSAvUGFnZXMKICAvTWVkaWFCb3ggWyAwIDAgMjAwIDIwMCBdCiAgL0NvdW50IDEKICAvS2lkcyBbIDMgMCBSIF0KPj4KZW5kb2JqCgozIDAgb2JqCjw8CiAgL1R5cGUgL1BhZ2UKICAvUGFyZW50IDIgMCBSCiAgL1Jlc291cmNlcyA8PAogICAgL0ZvbnQgPDwKICAgICAgL0YxIDQgMCBSIAogICAgPj4KICA+PgogIC9Db250ZW50cyA1IDAgUgo+PgplbmRvYmoKCjQgMCBvYmoKPDwKICAvVHlwZSAvRm9udAogIC9TdWJ0eXBlIC9UeXBlMQogIC9CYXNlRm9udCAvVGltZXMtUm9tYW4KPj4KZW5kb2JqCgo1IDAgb2JqICAlIHBhZ2UgY29udGVudAo8PAogIC9MZW5ndGggNDQKPj4Kc3RyZWFtCkJUCjcwIDUwIFRECi9GMSAxMiBUZgooSGVsbG8sIHdvcmxkISkgVGoKRVQKZW5kc3RyZWFtCmVuZG9iagoKeHJlZgowIDYKMDAwMDAwMDAwMCA2NTUzNSBmIAowMDAwMDAwMDEwIDAwMDAwIG4gCjAwMDAwMDAwNzkgMDAwMDAgbiAKMDAwMDAwMDE3MyAwMDAwMCBuIAowMDAwMDAwMzAxIDAwMDAwIG4gCjAwMDAwMDAzODAgMDAwMDAgbiAKdHJhaWxlcgo8PAogIC9TaXplIDYKICAvUm9vdCAxIDAgUgo+PgpzdGFydHhyZWYKNDkyCiUlRU9G" + }, + { + "id": "53066674-9e89-41e9-95d9-8eaf24b41ea6", + "resourceType": "DocumentReference", + "status": "current", + "category": [ + { + "coding": [ + { + "system": "http://loinc.org", + "code": "11488-4", + "display": "Consult Note" + } + ], + "text": "Consult Note" + } + ], + "content": [ + { + "attachment": { + "contentType": "application/pdf", + "url": "urn:uuid:4e2611e4-df0d-4dae-ad32-1c90537b32af", + "title": "test attachment" + } + } + ], + "context": { + "encounter": [{ + "reference": "Encounter/4e4e6346-2350-47c0-ba73-ee0fdaf3a8a7" + }] + } + } + ] +} diff --git a/frontend/src/app/components/medical-record-wizard-add-attachment/medical-record-wizard-add-attachment.component.html b/frontend/src/app/components/medical-record-wizard-add-attachment/medical-record-wizard-add-attachment.component.html index c086344c..f5e97822 100644 --- a/frontend/src/app/components/medical-record-wizard-add-attachment/medical-record-wizard-add-attachment.component.html +++ b/frontend/src/app/components/medical-record-wizard-add-attachment/medical-record-wizard-add-attachment.component.html @@ -30,7 +30,7 @@
- +
diff --git a/frontend/src/app/components/medical-record-wizard-add-attachment/medical-record-wizard-add-attachment.component.ts b/frontend/src/app/components/medical-record-wizard-add-attachment/medical-record-wizard-add-attachment.component.ts index ed39600b..c94ed3b4 100644 --- a/frontend/src/app/components/medical-record-wizard-add-attachment/medical-record-wizard-add-attachment.component.ts +++ b/frontend/src/app/components/medical-record-wizard-add-attachment/medical-record-wizard-add-attachment.component.ts @@ -41,15 +41,16 @@ export class MedicalRecordWizardAddAttachmentComponent implements OnInit { console.log("onAttachmentFileChange") let fileInput = $event.target as HTMLInputElement; if (fileInput.files && fileInput.files[0]) { + let processingFile = fileInput.files[0] let reader = new FileReader(); reader.onloadend = () => { // use a regex to remove data url part const base64String = (reader.result as string).replace('data:', '').replace(/^.+,/, ''); this.newAttachmentForm.get('file_content').setValue(base64String) }; - reader.readAsDataURL(fileInput.files[0]); - this.newAttachmentForm.get('file_name').setValue(fileInput.files[0].name) - this.newAttachmentForm.get('file_size').setValue(fileInput.files[0].size) + reader.readAsDataURL(processingFile); + this.newAttachmentForm.get('file_name').setValue(processingFile.name) + this.newAttachmentForm.get('file_size').setValue(processingFile.size) } } diff --git a/frontend/src/app/components/medical-record-wizard/medical-record-wizard.utilities.ts b/frontend/src/app/components/medical-record-wizard/medical-record-wizard.utilities.ts index 80569ee2..fbbd4ff4 100644 --- a/frontend/src/app/components/medical-record-wizard/medical-record-wizard.utilities.ts +++ b/frontend/src/app/components/medical-record-wizard/medical-record-wizard.utilities.ts @@ -371,13 +371,12 @@ function resourceAttachmentToR4DocumentReference(resourceStorage: ResourceStorag } } ], - context: [ - { - encounter: { - reference: generateReferenceUriFromResourceOrReference(encounterResource) //Encounter - }, - } - ] + context: { + encounter: [{ + reference: generateReferenceUriFromResourceOrReference(encounterResource) //Encounter + }], + } + // date: `${new Date(resourceDocumentReference.date.year,resourceDocumentReference.date.month-1,resourceDocumentReference.date.day).toISOString()}`, } as DocumentReference resourceStorage['DocumentReference'][documentReferenceResource.id] = documentReferenceResource diff --git a/frontend/src/app/components/medical-sources-connected/medical-sources-connected.component.ts b/frontend/src/app/components/medical-sources-connected/medical-sources-connected.component.ts index 4508b58d..9f5e2ec5 100644 --- a/frontend/src/app/components/medical-sources-connected/medical-sources-connected.component.ts +++ b/frontend/src/app/components/medical-sources-connected/medical-sources-connected.component.ts @@ -124,7 +124,7 @@ export class MedicalSourcesConnectedComponent implements OnInit { //TOOD: print this message in the UI let errMsg = "an error occurred while authenticating to this source. Please try again later" console.error(errMsg, callbackErrorDescription) - throw new Error(errMsg) + throw new Error(`callback error: ${callbackError}, description: ${callbackErrorDescription}`) } console.log("callback code:", callbackCode) diff --git a/frontend/src/app/pages/medical-sources/medical-sources.component.html b/frontend/src/app/pages/medical-sources/medical-sources.component.html index ed1266e2..dcc3390b 100644 --- a/frontend/src/app/pages/medical-sources/medical-sources.component.html +++ b/frontend/src/app/pages/medical-sources/medical-sources.component.html @@ -19,7 +19,7 @@

In Sandbox mode Fasten Health cannot access real patient information. You must use Sandbox credentials when authenticating to healthcare sources. - These credentials are available on Github + These credentials are available in our Sandbox Guide diff --git a/frontend/src/assets/favicon/android-chrome-300x300.png b/frontend/src/assets/favicon/android-chrome-300x300.png new file mode 100644 index 00000000..c5ae4f84 Binary files /dev/null and b/frontend/src/assets/favicon/android-chrome-300x300.png differ diff --git a/go.mod b/go.mod index bdb38408..c3c331ab 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/dave/jennifer v1.6.1 github.com/dominikbraun/graph v0.15.0 github.com/dop251/goja v0.0.0-20230605162241-28ee0ee714f3 - github.com/fastenhealth/fasten-sources v0.5.5 + github.com/fastenhealth/fasten-sources v0.5.6 github.com/fastenhealth/gofhir-models v0.0.6 github.com/gin-gonic/gin v1.9.0 github.com/go-gormigrate/gormigrate/v2 v2.1.1 diff --git a/go.sum b/go.sum index 7678d3bc..f1e1a216 100644 --- a/go.sum +++ b/go.sum @@ -101,8 +101,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/fastenhealth/fasten-sources v0.5.5 h1:+vOIG7R/J7QxEZrrfPFfaBzqYU5eDu7x/bc/cgOjvW4= -github.com/fastenhealth/fasten-sources v0.5.5/go.mod h1:hUQATAu5KrxKbACJoVt4iEKIGnRtmiOmHz+5TLfyiCM= +github.com/fastenhealth/fasten-sources v0.5.6 h1:F4Qmw9ABLSkqkWncoSnChBRDVWLrzkJv+z4z/Ue/fdc= +github.com/fastenhealth/fasten-sources v0.5.6/go.mod h1:hUQATAu5KrxKbACJoVt4iEKIGnRtmiOmHz+5TLfyiCM= github.com/fastenhealth/gofhir-models v0.0.6 h1:yJYYaV1eJtHiGEfA1rXLsyOm/9hIi6s2cGoZzGfW1tM= github.com/fastenhealth/gofhir-models v0.0.6/go.mod h1:xB8ikGxu3bUq2b1JYV+CZpHqBaLXpOizFR0eFBCunis= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=