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 @@