package http import ( "context" "encoding/json" "errors" "net/http" "net/http/httptest" "strings" "testing" "time" "primaya-api/cpone-middleware/internal/repository" ) type fakePatientRepository struct { outcome repository.MedrecOutcome err error params repository.MedrecParams } func (f *fakePatientRepository) StoreMedrec(_ context.Context, params repository.MedrecParams) (repository.MedrecOutcome, error) { f.params = params return f.outcome, f.err } func TestStoreMedrecValidPayloadAndCreatedResponseParity(t *testing.T) { name := "BUDI SANTOS" birthDate := "1990-01-15" identityType := "KTP" identityNumber := "3174011501900001" phone := "081234567890" repo := &fakePatientRepository{outcome: repository.MedrecOutcome{ HTTPStatus: http.StatusCreated, Success: true, Status: "created", Message: "Pasien berhasil disimpan", Data: repository.MedrecData{ Patient: repository.MedrecPatient{ MEDRECID: "00001235", Nama: &name, TanggalLahir: &birthDate, JenisNomerSosial: &identityType, NomerSosial: &identityNumber, Telepon: &phone, Ponsel: &phone, }, IsNewPatient: true, }, }} handler := Handler{patientRepo: repo, hospitalCode: "PRIMAYA"} body := `{ "nama":" Budi Santos ", "jenis_kelamin":"l", "tgl_lahir":"1990-01-15", "jenis_nomer_sosial":"ktp", "nomer_sosial":"3174-0115-0190-0001", "telepon":"+62 812-3456-7890", "ponsel":"0812 3456 7890", "email":" BUDI@Example.COM ", "tempat_lahir":" jakarta ", "alamat":" JL. CONTOH NO. 1 ", "propinsi":" DKI JAKARTA ", "kabupaten":" JAKARTA ", "kecamatan":" KECAMATAN CONTOH ", "kelurahan":" KELURAHAN CONTOH ", "kode_pos":" 12345 ", "perusahaan_id":" PE260700001 ", "nik_karyawan":" TSB2026002 ", "dependant_id":"D001", "nama_karyawan":" Budi Santos ", "channel":" cpone " }` request := httptest.NewRequest(http.MethodPost, "/api/cpone/patients/medrec", strings.NewReader(body)) recorder := httptest.NewRecorder() handler.StoreMedrec(recorder, request) if recorder.Code != http.StatusCreated { t.Fatalf("status = %d, want %d; body=%s", recorder.Code, http.StatusCreated, recorder.Body.String()) } const expected = `{"success":true,"status":"created","message":"Pasien berhasil disimpan","data":{"patient":{"MEDRECID":"00001235","Nama":"BUDI SANTOS","TanggalLahir":"1990-01-15","JenisNomerSosial":"KTP","NomerSosial":"3174011501900001","Telepon":"081234567890","Ponsel":"081234567890"},"is_new_patient":true}}` + "\n" if recorder.Body.String() != expected { t.Fatalf("body mismatch\n got: %s\nwant: %s", recorder.Body.String(), expected) } if repo.params.HospitalCode != "PRIMAYA" || repo.params.Name != "BUDI SANTOS" || repo.params.Gender != "L" { t.Fatalf("basic normalization mismatch: %+v", repo.params) } if repo.params.IdentityType != "KTP" || repo.params.IdentityNumber != "3174011501900001" { t.Fatalf("identity normalization mismatch: %+v", repo.params) } if repo.params.Phone != "081234567890" || repo.params.Mobile != "081234567890" { t.Fatalf("phone normalization mismatch: %+v", repo.params) } if repo.params.Email != "budi@example.com" || repo.params.PlaceOfBirth != "JAKARTA" || repo.params.Channel != "CPONE" { t.Fatalf("text normalization mismatch: %+v", repo.params) } if repo.params.CompanyID != "PE260700001" || repo.params.EmployeeID != "TSB2026002" || repo.params.DependantID != "D001" || repo.params.EmployeeName != "Budi Santos" { t.Fatalf("company patient fields mismatch: %+v", repo.params) } if repo.params.DateOfBirth.Format("2006-01-02") != "1990-01-15" { t.Fatalf("birth date mismatch: %s", repo.params.DateOfBirth) } } func TestStoreMedrecValidationParity(t *testing.T) { baseline := map[string]any{ "nama": "BUDI SANTOS", "jenis_kelamin": "L", "tgl_lahir": "1990-01-15", "jenis_nomer_sosial": "KTP", "nomer_sosial": "3174011501900001", "telepon": "081234567890", } tomorrow := time.Now().AddDate(0, 0, 1).Format("2006-01-02") tests := []struct { name string mutate func(map[string]any) message string }{ {name: "missing name", mutate: func(p map[string]any) { delete(p, "nama") }, message: "Nama pasien wajib diisi"}, {name: "invalid gender", mutate: func(p map[string]any) { p["jenis_kelamin"] = "M" }, message: "Jenis kelamin harus L atau P"}, {name: "invalid date format", mutate: func(p map[string]any) { p["tgl_lahir"] = "15-01-1990" }, message: "Tanggal lahir harus format Y-m-d"}, {name: "future birth date", mutate: func(p map[string]any) { p["tgl_lahir"] = tomorrow }, message: "Tanggal lahir tidak boleh lebih dari hari ini"}, {name: "missing identity", mutate: func(p map[string]any) { delete(p, "nomer_sosial") }, message: "NIK/nomer_sosial wajib diisi"}, {name: "invalid ktp", mutate: func(p map[string]any) { p["nomer_sosial"] = "123" }, message: "NIK/KTP harus 16 digit angka"}, {name: "missing phone", mutate: func(p map[string]any) { delete(p, "telepon") }, message: "Telepon atau ponsel wajib diisi"}, {name: "invalid email", mutate: func(p map[string]any) { p["email"] = "invalid" }, message: "The email must be a valid email address."}, {name: "long dependant id", mutate: func(p map[string]any) { p["dependant_id"] = "D0001" }, message: "The dependant id must not be greater than 4 characters."}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { payload := make(map[string]any, len(baseline)) for key, value := range baseline { payload[key] = value } test.mutate(payload) body, err := json.Marshal(payload) if err != nil { t.Fatal(err) } handler := Handler{patientRepo: &fakePatientRepository{}} request := httptest.NewRequest(http.MethodPost, "/api/cpone/patients/medrec", strings.NewReader(string(body))) recorder := httptest.NewRecorder() handler.StoreMedrec(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 TestStoreMedrecNikFallbackAndOutcomeForwarding(t *testing.T) { data := repository.PossibleDuplicateData{Candidates: []repository.MedrecPatient{}} repo := &fakePatientRepository{outcome: repository.MedrecOutcome{ HTTPStatus: http.StatusConflict, Success: false, Status: "possible_duplicate", Message: "Ditemukan pasien dengan nama, tanggal lahir, dan nomor telepon yang sama atau sangat mirip", Data: data, }} handler := Handler{patientRepo: repo} body := `{"nama":"BUDI","jenis_kelamin":"P","tgl_lahir":"1990-01-15","nik":"3174011501900001","ponsel":"08123"}` request := httptest.NewRequest(http.MethodPost, "/api/cpone/patients/medrec", strings.NewReader(body)) recorder := httptest.NewRecorder() handler.StoreMedrec(recorder, request) if recorder.Code != http.StatusConflict || repo.params.IdentityType != "KTP" || repo.params.IdentityNumber != "3174011501900001" { t.Fatalf("unexpected result: status=%d params=%+v body=%s", recorder.Code, repo.params, recorder.Body.String()) } } func TestStoreMedrecRepositoryErrorParity(t *testing.T) { handler := Handler{patientRepo: &fakePatientRepository{err: errors.New("database unavailable")}} body := `{"nama":"BUDI","jenis_kelamin":"L","tgl_lahir":"1990-01-15","nomer_sosial":"3174011501900001","telepon":"08123"}` request := httptest.NewRequest(http.MethodPost, "/api/cpone/patients/medrec", strings.NewReader(body)) recorder := httptest.NewRecorder() handler.StoreMedrec(recorder, request) if recorder.Code != http.StatusInternalServerError { t.Fatalf("status=%d body=%s", recorder.Code, recorder.Body.String()) } const expected = `{"success":false,"status":"error","message":"database unavailable","data":null}` + "\n" if recorder.Body.String() != expected { t.Fatalf("body mismatch\n got: %s\nwant: %s", recorder.Body.String(), expected) } }