package http import ( "encoding/json" "net/http" "net/http/httptest" "strings" "testing" "primaya-api/cpone-middleware/internal/repository" ) func TestAddLabServicesValidPayloadAndResponseParity(t *testing.T) { doctorID := "DR01" registrationType := int64(3) added := []string{"6413"} skipped := []string{"6414"} repo := &fakeLabRegistrationRepository{outcome: repository.LabRegistrationOutcome{ HTTPStatus: http.StatusCreated, Success: true, Status: "services_added", Message: "Layanan laboratorium berhasil ditambahkan", Data: &repository.LabRegistrationData{ Registration: repository.LabRegistration{ RegID: "CPLAB2608180001", MEDRECID: "00001234", Tanggal: "2026-08-18 10:11:12", DepartemenID: "LAB", DokterID: &doctorID, JenisPasienID: 0, JenisRegID: ®istrationType, PeriodeTarifID: 10, }, Transaction: &repository.LabTransaction{ TrxDepartemenID: 99, TrxID: "CPLAB2608180001", GrandTotal: 125000, }, Layanan: []repository.LabService{}, AddedLayananIDs: &added, SkippedLayananIDs: &skipped, }, }} handler := Handler{labRepo: repo} body := `{ "layanan":[ {"layanan_id":"6413","jumlah":"2","cito":true,"markup_cito":"10.5","catatan":" urgent "}, {"layanan_id":"6414","cito":"0"} ], "channel":" cpone-api " }` request := httptest.NewRequest(http.MethodPost, "/api/cpone/patients/lab-registration/CPLAB2608180001/services", strings.NewReader(body)) request.SetPathValue("regId", "CPLAB2608180001") recorder := httptest.NewRecorder() handler.AddLabServices(recorder, request) if recorder.Code != http.StatusCreated { t.Fatalf("status = %d; body=%s", recorder.Code, recorder.Body.String()) } const expected = `{"success":true,"status":"services_added","message":"Layanan laboratorium berhasil ditambahkan","data":{"registration":{"RegID":"CPLAB2608180001","MEDRECID":"00001234","Tanggal":"2026-08-18 10:11:12","DepartemenID":"LAB","DokterID":"DR01","JenisPasienID":0,"JenisRegID":3,"PeriodeTarifID":10,"PerusahaanID":null,"AsuransiID":null,"ProviderID":null,"GroupTarifID":null,"NomerPolis":null},"transaction":{"TrxDepartemenID":99,"TrxID":"CPLAB2608180001","GrandTotal":125000},"layanan":[],"added_layanan_ids":["6413"],"skipped_layanan_ids":["6414"]}}` + "\n" if recorder.Body.String() != expected { t.Fatalf("body mismatch\n got: %s\nwant: %s", recorder.Body.String(), expected) } if repo.regID != "CPLAB2608180001" || repo.addParams.Channel != "CPONE-API" { t.Fatalf("unexpected repository call: regID=%q params=%+v", repo.regID, repo.addParams) } if len(repo.addParams.Layanan) != 2 { t.Fatalf("services count = %d", len(repo.addParams.Layanan)) } first := repo.addParams.Layanan[0] if first.LayananID != "6413" || first.Jumlah != 2 || !first.Cito || first.MarkupCito != 10.5 || first.Catatan != " urgent " { t.Fatalf("unexpected first service: %+v", first) } second := repo.addParams.Layanan[1] if second.Jumlah != 1 || second.Cito || second.MarkupCito != 0 { t.Fatalf("unexpected defaults: %+v", second) } } func TestAddLabServicesValidationParity(t *testing.T) { baseline := map[string]any{ "layanan": []any{ map[string]any{"layanan_id": "6413"}, }, } tests := []struct { name string mutate func(map[string]any) message string }{ { name: "missing services", mutate: func(payload map[string]any) { delete(payload, "layanan") }, message: "Minimal satu layanan wajib diisi", }, { name: "not an array", mutate: func(payload map[string]any) { payload["layanan"] = "6413" }, message: "The layanan must be an array.", }, { name: "missing service id", mutate: func(payload map[string]any) { payload["layanan"] = []any{map[string]any{}} }, message: "Layanan ID wajib diisi", }, { name: "duplicate service", mutate: func(payload map[string]any) { payload["layanan"] = []any{ map[string]any{"layanan_id": "6413"}, map[string]any{"layanan_id": "6413"}, } }, message: "Layanan yang sama tidak boleh dikirim lebih dari satu kali", }, { name: "invalid quantity", mutate: func(payload map[string]any) { payload["layanan"] = []any{map[string]any{"layanan_id": "6413", "jumlah": 0}} }, message: "The layanan.0.jumlah must be at least 1.", }, { name: "invalid cito", mutate: func(payload map[string]any) { payload["layanan"] = []any{map[string]any{"layanan_id": "6413", "cito": "true"}} }, message: "The layanan.0.cito field must be true or false.", }, { name: "invalid markup", mutate: func(payload map[string]any) { payload["layanan"] = []any{map[string]any{"layanan_id": "6413", "markup_cito": 101}} }, message: "The layanan.0.markup cito must not be greater than 100.", }, } 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{labRepo: &fakeLabRegistrationRepository{}} request := httptest.NewRequest(http.MethodPost, "/api/cpone/patients/lab-registration/CPLAB2608180001/services", strings.NewReader(string(body))) request.SetPathValue("regId", "CPLAB2608180001") recorder := httptest.NewRecorder() handler.AddLabServices(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) } }) } }