feat: add lab result endpoint
This commit is contained in:
@@ -25,20 +25,26 @@ type PatientRepository interface {
|
||||
StoreMedrec(ctx context.Context, params repository.MedrecParams) (repository.MedrecOutcome, error)
|
||||
}
|
||||
|
||||
type LabResultRepository interface {
|
||||
GetLabResult(ctx context.Context, labNumber string) (repository.LabResultOutcome, error)
|
||||
}
|
||||
|
||||
type Handler struct {
|
||||
layananRepo LayananRepository
|
||||
labRepo LabRegistrationRepository
|
||||
patientRepo PatientRepository
|
||||
layananRepo LayananRepository
|
||||
labRepo LabRegistrationRepository
|
||||
patientRepo PatientRepository
|
||||
labResultRepo LabResultRepository
|
||||
}
|
||||
|
||||
type CPONERepository interface {
|
||||
LayananRepository
|
||||
LabRegistrationRepository
|
||||
PatientRepository
|
||||
LabResultRepository
|
||||
}
|
||||
|
||||
func NewHandler(repo CPONERepository) Handler {
|
||||
return Handler{layananRepo: repo, labRepo: repo, patientRepo: repo}
|
||||
return Handler{layananRepo: repo, labRepo: repo, patientRepo: repo, labResultRepo: repo}
|
||||
}
|
||||
|
||||
func (h Handler) MappingHarga(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
58
internal/http/lab_result.go
Normal file
58
internal/http/lab_result.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"primaya-api/cpone-middleware/internal/repository"
|
||||
)
|
||||
|
||||
var validLabNumber = regexp.MustCompile(`^[A-Za-z0-9-]+$`)
|
||||
|
||||
type LabResultResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
Data *repository.LabResultData `json:"data"`
|
||||
}
|
||||
|
||||
func (h Handler) GetLabResult(w http.ResponseWriter, r *http.Request) {
|
||||
labNumber := strings.TrimSpace(r.PathValue("labNumber"))
|
||||
if labNumber == "" {
|
||||
writeLabResultResponse(w, http.StatusUnprocessableEntity, false, "validation_error", "Nomor laboratorium wajib diisi", nil)
|
||||
return
|
||||
}
|
||||
if utf8.RuneCountInString(labNumber) > 50 {
|
||||
writeLabResultResponse(w, http.StatusUnprocessableEntity, false, "validation_error", "Nomor laboratorium maksimal 50 karakter", nil)
|
||||
return
|
||||
}
|
||||
if !validLabNumber.MatchString(labNumber) {
|
||||
writeLabResultResponse(w, http.StatusUnprocessableEntity, false, "validation_error", "Nomor laboratorium hanya boleh berisi huruf, angka, dan tanda hubung", nil)
|
||||
return
|
||||
}
|
||||
|
||||
outcome, err := h.labResultRepo.GetLabResult(r.Context(), strings.ToUpper(labNumber))
|
||||
if err != nil {
|
||||
var validationErr repository.ValidationError
|
||||
if errors.As(err, &validationErr) {
|
||||
writeLabResultResponse(w, http.StatusUnprocessableEntity, false, "validation_error", validationErr.Message, nil)
|
||||
return
|
||||
}
|
||||
writeLabResultResponse(w, http.StatusInternalServerError, false, "error", err.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
writeLabResultResponse(w, outcome.HTTPStatus, outcome.Success, outcome.Status, outcome.Message, outcome.Data)
|
||||
}
|
||||
|
||||
func writeLabResultResponse(w http.ResponseWriter, statusCode int, success bool, status, message string, data *repository.LabResultData) {
|
||||
WriteJSON(w, statusCode, LabResultResponse{
|
||||
Success: success,
|
||||
Status: status,
|
||||
Message: message,
|
||||
Data: data,
|
||||
})
|
||||
}
|
||||
126
internal/http/lab_result_test.go
Normal file
126
internal/http/lab_result_test.go
Normal file
@@ -0,0 +1,126 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"primaya-api/cpone-middleware/internal/repository"
|
||||
)
|
||||
|
||||
type fakeLabResultRepository struct {
|
||||
outcome repository.LabResultOutcome
|
||||
err error
|
||||
labNumber string
|
||||
}
|
||||
|
||||
func (f *fakeLabResultRepository) GetLabResult(_ context.Context, labNumber string) (repository.LabResultOutcome, error) {
|
||||
f.labNumber = labNumber
|
||||
return f.outcome, f.err
|
||||
}
|
||||
|
||||
func TestGetLabResultResponseParity(t *testing.T) {
|
||||
verifiedAt := "2026-08-19 10:00:00"
|
||||
name := "Hemoglobin"
|
||||
result := "14.2"
|
||||
reference := "13.0 - 17.0"
|
||||
unit := "g/dL"
|
||||
groupID := "HEMATOLOGI"
|
||||
groupName := "Hematologi"
|
||||
repo := &fakeLabResultRepository{outcome: repository.LabResultOutcome{
|
||||
HTTPStatus: http.StatusOK,
|
||||
Success: true,
|
||||
Status: "lab_result_found",
|
||||
Message: "Hasil laboratorium ditemukan",
|
||||
Data: &repository.LabResultData{
|
||||
Lab: repository.LabResultHeader{
|
||||
LabNumber: "CPLAB2608190001", TrxDepartemenID: 6103991,
|
||||
Status: "authorized", VerifiedAt: &verifiedAt,
|
||||
},
|
||||
Groups: []repository.LabResultGroup{{
|
||||
GroupID: &groupID, GroupName: &groupName,
|
||||
Items: []repository.LabResultItem{{
|
||||
TrxLayananID: 123456, ServiceID: "HB", Name: &name, Result: &result,
|
||||
ReferenceValue: &reference, Unit: &unit, CriticalFlag: "",
|
||||
}},
|
||||
}},
|
||||
},
|
||||
}}
|
||||
handler := Handler{labResultRepo: repo}
|
||||
request := httptest.NewRequest(http.MethodGet, "/api/cpone/patients/lab-results/cplab2608190001", nil)
|
||||
request.SetPathValue("labNumber", "cplab2608190001")
|
||||
recorder := httptest.NewRecorder()
|
||||
|
||||
handler.GetLabResult(recorder, request)
|
||||
|
||||
if recorder.Code != http.StatusOK {
|
||||
t.Fatalf("status=%d body=%s", recorder.Code, recorder.Body.String())
|
||||
}
|
||||
if repo.labNumber != "CPLAB2608190001" {
|
||||
t.Fatalf("lab number=%q", repo.labNumber)
|
||||
}
|
||||
const expected = `{"success":true,"status":"lab_result_found","message":"Hasil laboratorium ditemukan","data":{"lab":{"lab_number":"CPLAB2608190001","trx_departemen_id":6103991,"registration_id":null,"tanggal":null,"status":"authorized","verified_at":"2026-08-19 10:00:00","authorized_at":null,"catatan":null},"patient":{"medrec_id":null,"nama":null,"tanggal_lahir":null,"jenis_kelamin":null},"doctor":{"penanggung_jawab":null,"pengirim":null},"groups":[{"group_id":"HEMATOLOGI","group_name":"Hematologi","items":[{"trx_layanan_id":123456,"service_id":"HB","service_code":null,"name":"Hemoglobin","result":"14.2","reference_value":"13.0 - 17.0","unit":"g/dL","critical_flag":"","value_type":null,"note":null}]}]}}` + "\n"
|
||||
if recorder.Body.String() != expected {
|
||||
t.Fatalf("body mismatch\n got: %s\nwant: %s", recorder.Body.String(), expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetLabResultValidationParity(t *testing.T) {
|
||||
tests := []struct {
|
||||
name, labNumber, message string
|
||||
}{
|
||||
{name: "invalid characters", labNumber: "CPLAB_260819", message: "Nomor laboratorium hanya boleh berisi huruf, angka, dan tanda hubung"},
|
||||
{name: "too long", labNumber: "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890ABCDEFGHIJKLMNO", message: "Nomor laboratorium maksimal 50 karakter"},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
handler := Handler{labResultRepo: &fakeLabResultRepository{}}
|
||||
request := httptest.NewRequest(http.MethodGet, "/api/cpone/patients/lab-results/"+test.labNumber, nil)
|
||||
request.SetPathValue("labNumber", test.labNumber)
|
||||
recorder := httptest.NewRecorder()
|
||||
handler.GetLabResult(recorder, request)
|
||||
if recorder.Code != http.StatusUnprocessableEntity {
|
||||
t.Fatalf("status=%d body=%s", recorder.Code, recorder.Body.String())
|
||||
}
|
||||
expected := `{"success":false,"status":"validation_error","message":` + strconvQuote(test.message) + `,"data":null}` + "\n"
|
||||
if recorder.Body.String() != expected {
|
||||
t.Fatalf("body mismatch\n got: %s\nwant: %s", recorder.Body.String(), expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetLabResultForwardsDomainAndDatabaseOutcomes(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
repo *fakeLabResultRepository
|
||||
statusCode int
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "not found",
|
||||
repo: &fakeLabResultRepository{outcome: repository.LabResultOutcome{HTTPStatus: 404, Status: "lab_result_not_found", Message: "Hasil laboratorium tidak ditemukan"}},
|
||||
statusCode: 404,
|
||||
expected: `{"success":false,"status":"lab_result_not_found","message":"Hasil laboratorium tidak ditemukan","data":null}` + "\n",
|
||||
},
|
||||
{
|
||||
name: "database error", repo: &fakeLabResultRepository{err: errors.New("database unavailable")},
|
||||
statusCode: 500,
|
||||
expected: `{"success":false,"status":"error","message":"database unavailable","data":null}` + "\n",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
handler := Handler{labResultRepo: test.repo}
|
||||
request := httptest.NewRequest(http.MethodGet, "/api/cpone/patients/lab-results/CPLAB2608190001", nil)
|
||||
request.SetPathValue("labNumber", "CPLAB2608190001")
|
||||
recorder := httptest.NewRecorder()
|
||||
handler.GetLabResult(recorder, request)
|
||||
if recorder.Code != test.statusCode || recorder.Body.String() != test.expected {
|
||||
t.Fatalf("status=%d body=%s", recorder.Code, recorder.Body.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user