package http import ( "context" "errors" "net/http" "net/http/httptest" "strings" "testing" "primaya-api/cpone-middleware/internal/repository" ) type fakePaketRepository struct { page repository.PaketPage detail *repository.PaketDetail create repository.CreatePaketResult err error listParams repository.PaketListParams detailID string createParams repository.CreatePaketParams } func (f *fakePaketRepository) ListPaket(_ context.Context, params repository.PaketListParams) (repository.PaketPage, error) { f.listParams = params return f.page, f.err } func (f *fakePaketRepository) GetPaket(_ context.Context, id string) (*repository.PaketDetail, error) { f.detailID = id return f.detail, f.err } func (f *fakePaketRepository) CreatePaket(_ context.Context, params repository.CreatePaketParams) (repository.CreatePaketResult, error) { f.createParams = params return f.create, f.err } func TestListPaketResponseParity(t *testing.T) { kodeCorp := "ANP" jenisPaket := "Medical Check Up" status := "approved" repo := &fakePaketRepository{page: repository.PaketPage{ Total: 188, Items: []repository.PaketSummary{{ PaketID: "DISP-260800007", KodeCorp: &kodeCorp, Nama: "PAKET ANTI PLENGER", JenisPaketID: "MCU", JenisPaket: &jenisPaket, Harga: 500000, RawatJalan: true, TglDari: "2026-08-05", TglSampai: "2038-08-31", KelasID: "KelasI", JumlahHari: 1, StatusID: 2, Status: &status, NA: "N", Aktif: true, Keterangan: "GRWGRW", }}, }} handler := Handler{paketRepo: repo} request := httptest.NewRequest(http.MethodGet, "http://127.0.0.1:8282/api/cpone/paket?search=PLENGER&care_type=RJ&active=N&page=1&per_page=15", nil) recorder := httptest.NewRecorder() handler.ListPaket(recorder, request) if recorder.Code != http.StatusOK { t.Fatalf("status=%d body=%s", recorder.Code, recorder.Body.String()) } if repo.listParams.Status != 2 || repo.listParams.Search != "PLENGER" || repo.listParams.CareType != "RJ" || repo.listParams.Active != "N" { t.Fatalf("params mismatch: %+v", repo.listParams) } const expected = `{"current_page":1,"data":[{"PaketID":"DISP-260800007","KodeCorp":"ANP","Nama":"PAKET ANTI PLENGER","JenisPaketID":"MCU","JenisPaket":"Medical Check Up","Harga":500000,"RawatInap":false,"RawatJalan":true,"MultiVisit":false,"TglDari":"2026-08-05","TglSampai":"2038-08-31","KelasID":"KelasI","Kelas":null,"JumlahHari":1,"StatusID":2,"Status":"approved","NA":"N","Aktif":true,"Keterangan":"GRWGRW"}],"first_page_url":"","from":1,"last_page":13,"last_page_url":"","links":{},"next_page_url":"http://127.0.0.1:8282/api/cpone/paket?page=2","total":188,"per_page":15}` + "\n" if recorder.Body.String() != expected { t.Fatalf("body mismatch\n got: %s\nwant: %s", recorder.Body.String(), expected) } } func TestListPaketValidationParity(t *testing.T) { tests := []struct { query, message string }{ {"?care_type=IGD", "The selected care type is invalid."}, {"?status=x", "The status must be an integer."}, {"?status=3", "The selected status is invalid."}, {"?active=X", "The selected active is invalid."}, {"?start_date=20-08-2026", "The start date does not match the format Y-m-d."}, {"?start_date=2026-08-20&end_date=2026-08-19", "The end date must be a date after or equal to start date."}, {"?page=0", "The page must be at least 1."}, {"?per_page=101", "The per page must be between 1 and 100."}, } for _, test := range tests { t.Run(test.query, func(t *testing.T) { handler := Handler{paketRepo: &fakePaketRepository{}} request := httptest.NewRequest(http.MethodGet, "/api/cpone/paket"+test.query, nil) recorder := httptest.NewRecorder() handler.ListPaket(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 TestGetPaketResponseAndNotFoundParity(t *testing.T) { keteranganPeriode := "Tarif Dasar 2026" detail := &repository.PaketDetail{ PaketID: "DISP-260800002", Nama: "Test paket", JenisPaket: "DIA", RI: 1, TglDari: "2026-08-06", TglSampai: "2026-08-31", KelasID: "KelasII", JumlahHari: 1, TotalHarga: "1550913.00", StatusID: 2, LoginBuat: "API_CRM", TanggalBuat: "2026-08-05T10:05:03.000000Z", LoginEdit: "API_CRM", TanggalEdit: "2026-08-19T06:05:20.000000Z", NA: "N", PeriodeTarif: &repository.PaketPeriodeTarif{ PeriodeTarifID: 42, Keterangan: &keteranganPeriode, TanggalBerlaku: "2026-01-01", TanggalBerakhir: "2026-12-31", }, Layanan: []repository.PaketLayanan{{HargaDasar: "100,000", HargaDasarSource: "TARIF_PERIODE"}}, Operasi: []repository.PaketOperasi{{HargaDasar: "250,000.00"}}, Item: []repository.PaketItem{{HargaDasar: "50,000.00"}}, Kamar: []repository.PaketKamar{{HargaDasar: "200,000.00"}}, } repo := &fakePaketRepository{detail: detail} handler := Handler{paketRepo: repo} request := httptest.NewRequest(http.MethodGet, "/api/cpone/paket/DISP-260800002", nil) request.SetPathValue("id", "DISP-260800002") recorder := httptest.NewRecorder() handler.GetPaket(recorder, request) if recorder.Code != http.StatusOK || repo.detailID != "DISP-260800002" { t.Fatalf("status=%d id=%q body=%s", recorder.Code, repo.detailID, recorder.Body.String()) } for _, expected := range []string{ `"PeriodeTarif":{"PeriodeTarifID":42,"Keterangan":"Tarif Dasar 2026","TanggalBerlaku":"2026-01-01","TanggalBerakhir":"2026-12-31"}`, `"HargaDasar":"100,000"`, `"HargaDasarSource":"TARIF_PERIODE"`, `"HargaDasar":"250,000.00"`, `"HargaDasar":"50,000.00"`, `"HargaDasar":"200,000.00"`, } { if !strings.Contains(recorder.Body.String(), expected) { t.Fatalf("response does not contain %s: %s", expected, recorder.Body.String()) } } notFoundHandler := Handler{paketRepo: &fakePaketRepository{}} notFoundRequest := httptest.NewRequest(http.MethodGet, "/api/cpone/paket/UNKNOWN", nil) notFoundRequest.SetPathValue("id", "UNKNOWN") notFoundRecorder := httptest.NewRecorder() notFoundHandler.GetPaket(notFoundRecorder, notFoundRequest) const expected = `{"success":false,"message":"Data tidak ditemukan","data":null}` + "\n" if notFoundRecorder.Code != http.StatusNotFound || notFoundRecorder.Body.String() != expected { t.Fatalf("status=%d body=%s", notFoundRecorder.Code, notFoundRecorder.Body.String()) } } func TestPaketRepositoryErrorIsHidden(t *testing.T) { handler := Handler{paketRepo: &fakePaketRepository{err: errors.New("database unavailable")}} request := httptest.NewRequest(http.MethodGet, "/api/cpone/paket", nil) recorder := httptest.NewRecorder() handler.ListPaket(recorder, request) const expected = `{"success":false,"message":"internal server error","data":null}` + "\n" if recorder.Code != http.StatusInternalServerError || recorder.Body.String() != expected { t.Fatalf("status=%d body=%s", recorder.Code, recorder.Body.String()) } }