Files
fm_be/internal/transport/http/handlers/hospital_handler_test.go
2026-07-16 22:16:45 +07:00

644 lines
24 KiB
Go

package handlers
import (
"bytes"
"context"
"errors"
"io"
"net/http"
"strings"
"testing"
"time"
"github.com/gofiber/fiber/v2"
"wucher/internal/domain/hospital"
"wucher/internal/service"
"wucher/internal/shared/pkg/apperrors"
"wucher/internal/shared/pkg/uuidv7"
)
type memHospitalRepo struct {
rows map[string]*hospital.Hospital
createErr error
updateErr error
deleteErr error
getByIDErr error
listErr error
listRows []hospital.Hospital
listTotal int64
getByIDFn func(id []byte, call int) (*hospital.Hospital, error)
getCalls int
lastCreate *hospital.Hospital
lastUpdate *hospital.Hospital
lastDeleteID []byte
lastDeleteBy []byte
lastListFilter string
lastListSort string
lastListLimit int
lastListOffset int
}
func newMemHospitalRepo() *memHospitalRepo {
return &memHospitalRepo{rows: map[string]*hospital.Hospital{}}
}
func (r *memHospitalRepo) Create(_ context.Context, row *hospital.Hospital) error {
if r.createErr != nil {
return r.createErr
}
if len(row.ID) == 0 {
row.ID = uuidv7.MustBytes()
}
r.rows[string(row.ID)] = row
r.lastCreate = row
return nil
}
func (r *memHospitalRepo) Update(_ context.Context, row *hospital.Hospital) error {
if r.updateErr != nil {
return r.updateErr
}
r.rows[string(row.ID)] = row
r.lastUpdate = row
return nil
}
func (r *memHospitalRepo) Delete(_ context.Context, id []byte, deletedBy []byte) error {
if r.deleteErr != nil {
return r.deleteErr
}
r.lastDeleteID = append([]byte(nil), id...)
r.lastDeleteBy = append([]byte(nil), deletedBy...)
delete(r.rows, string(id))
return nil
}
func (r *memHospitalRepo) GetByID(_ context.Context, id []byte) (*hospital.Hospital, error) {
r.getCalls++
if r.getByIDFn != nil {
return r.getByIDFn(id, r.getCalls)
}
if r.getByIDErr != nil {
return nil, r.getByIDErr
}
return r.rows[string(id)], nil
}
func (r *memHospitalRepo) List(_ context.Context, filter, sort string, limit, offset int) ([]hospital.Hospital, int64, error) {
r.lastListFilter = filter
r.lastListSort = sort
r.lastListLimit = limit
r.lastListOffset = offset
if r.listErr != nil {
return nil, 0, r.listErr
}
return r.listRows, r.listTotal, nil
}
func newHospitalTestApp(h *HospitalHandler, actor []byte) *fiber.App {
app := fiber.New()
app.Use(func(c *fiber.Ctx) error {
if len(actor) > 0 {
c.Locals("user_id", actor)
}
return c.Next()
})
app.Post("/api/v1/hospital/create", h.Create)
app.Patch("/api/v1/hospital/update/:uuid", h.Update)
app.Delete("/api/v1/hospital/delete/:uuid", h.Delete)
app.Get("/api/v1/hospital/get-all", h.List)
app.Get("/api/v1/hospital/get-all/dt", h.ListDatatable)
return app
}
func doJSONRequestHospital(t *testing.T, app *fiber.App, method, path string, body []byte) (*http.Response, []byte) {
t.Helper()
req, err := http.NewRequest(method, path, bytes.NewReader(body))
if err != nil {
t.Fatalf("new request: %v", err)
}
req.Header.Set("Content-Type", "application/json")
resp, err := app.Test(req)
if err != nil {
t.Fatalf("app.Test: %v", err)
}
respBody, err := io.ReadAll(resp.Body)
if err != nil {
t.Fatalf("read body: %v", err)
}
return resp, respBody
}
func assertContainsHospital(t *testing.T, body []byte, substr string) {
t.Helper()
if !strings.Contains(string(body), substr) {
t.Fatalf("expected response contain %q, got %s", substr, string(body))
}
}
func mustUUIDStringHospital(t *testing.T, id []byte) string {
t.Helper()
s, err := uuidv7.BytesToString(id)
if err != nil {
t.Fatalf("uuid to string: %v", err)
}
return s
}
func TestNewHospitalHandler(t *testing.T) {
h := NewHospitalHandler(service.NewHospitalService(newMemHospitalRepo()))
if h == nil || h.validate == nil {
t.Fatalf("expected handler and validator initialized")
}
}
func TestHospitalHandlerCreate(t *testing.T) {
t.Run("invalid json", func(t *testing.T) {
repo := newMemHospitalRepo()
h := NewHospitalHandler(service.NewHospitalService(repo))
app := newHospitalTestApp(h, nil)
resp, body := doJSONRequestHospital(t, app, http.MethodPost, "/api/v1/hospital/create", []byte(`{"data":`))
if resp.StatusCode != http.StatusBadRequest {
t.Fatalf("expected 400, got %d", resp.StatusCode)
}
assertContainsHospital(t, body, `"code":"NO_ICAO_CODE_INVALID_JSON"`)
assertContainsHospital(t, body, `"error_code":"4001104"`)
})
t.Run("validation error", func(t *testing.T) {
repo := newMemHospitalRepo()
h := NewHospitalHandler(service.NewHospitalService(repo))
app := newHospitalTestApp(h, nil)
resp, _ := doJSONRequestHospital(t, app, http.MethodPost, "/api/v1/hospital/create", []byte(`{}`))
if resp.StatusCode != http.StatusUnprocessableEntity {
t.Fatalf("expected 422, got %d", resp.StatusCode)
}
})
t.Run("name empty", func(t *testing.T) {
repo := newMemHospitalRepo()
h := NewHospitalHandler(service.NewHospitalService(repo))
app := newHospitalTestApp(h, nil)
body := []byte(`{"data":{"type":"hospital","attributes":{"name":" "}}}`)
resp, body := doJSONRequestHospital(t, app, http.MethodPost, "/api/v1/hospital/create", body)
if resp.StatusCode != http.StatusUnprocessableEntity {
t.Fatalf("expected 422, got %d", resp.StatusCode)
}
assertContainsHospital(t, body, `"code":"NO_ICAO_CODE_NAME_REQUIRED"`)
assertContainsHospital(t, body, `"error_code":"4221109"`)
})
t.Run("create error", func(t *testing.T) {
repo := newMemHospitalRepo()
repo.createErr = errors.New("create failed")
h := NewHospitalHandler(service.NewHospitalService(repo))
app := newHospitalTestApp(h, nil)
body := []byte(`{"data":{"type":"hospital","attributes":{"name":"RSUD Kota"}}}`)
resp, body := doJSONRequestHospital(t, app, http.MethodPost, "/api/v1/hospital/create", body)
if resp.StatusCode != http.StatusBadRequest {
t.Fatalf("expected 400, got %d", resp.StatusCode)
}
assertContainsHospital(t, body, `"code":"NO_ICAO_CODE_CREATE_FAILED"`)
assertContainsHospital(t, body, `"error_code":"4001110"`)
})
t.Run("success with refetch", func(t *testing.T) {
repo := newMemHospitalRepo()
h := NewHospitalHandler(service.NewHospitalService(repo))
actor := uuidv7.MustBytes()
app := newHospitalTestApp(h, actor)
body := []byte(`{"data":{"type":"hospital","attributes":{"name":"RSUD Kota","sortkey":3,"address":"Jl. Merdeka","landline_number":"021123456","mobile_number":"08123456","email":"rsud@hospital.local"}}}`)
resp, _ := doJSONRequestHospital(t, app, http.MethodPost, "/api/v1/hospital/create", body)
if resp.StatusCode != http.StatusCreated {
t.Fatalf("expected 201, got %d", resp.StatusCode)
}
if repo.lastCreate == nil {
t.Fatalf("expected row created")
}
if repo.lastCreate.Name != "RSUD Kota" || repo.lastCreate.Address != "Jl. Merdeka" || repo.lastCreate.LandlineNumber != "021123456" || repo.lastCreate.MobileNumber != "08123456" || repo.lastCreate.Email != "rsud@hospital.local" {
t.Fatalf("expected created values")
}
if repo.lastCreate.SortKey == nil || *repo.lastCreate.SortKey != 3 {
t.Fatalf("expected sortkey mapped")
}
if !repo.lastCreate.IsActive {
t.Fatalf("expected default is_active=true on create")
}
if string(repo.lastCreate.CreatedBy) != string(actor) || string(repo.lastCreate.UpdatedBy) != string(actor) {
t.Fatalf("expected created_by and updated_by from actor")
}
})
t.Run("create with inactive flag", func(t *testing.T) {
repo := newMemHospitalRepo()
h := NewHospitalHandler(service.NewHospitalService(repo))
app := newHospitalTestApp(h, nil)
body := []byte(`{"data":{"type":"hospital","attributes":{"name":"RS Inactive","is_active":false}}}`)
resp, _ := doJSONRequestHospital(t, app, http.MethodPost, "/api/v1/hospital/create", body)
if resp.StatusCode != http.StatusCreated {
t.Fatalf("expected 201, got %d", resp.StatusCode)
}
if repo.lastCreate == nil || repo.lastCreate.IsActive {
t.Fatalf("expected created row is_active=false")
}
})
t.Run("success fallback when refetch fails", func(t *testing.T) {
repo := newMemHospitalRepo()
repo.getByIDFn = func(_ []byte, _ int) (*hospital.Hospital, error) {
return nil, errors.New("lookup failed")
}
h := NewHospitalHandler(service.NewHospitalService(repo))
app := newHospitalTestApp(h, nil)
body := []byte(`{"data":{"type":"hospital","attributes":{"name":"RSUD Kota"}}}`)
resp, _ := doJSONRequestHospital(t, app, http.MethodPost, "/api/v1/hospital/create", body)
if resp.StatusCode != http.StatusCreated {
t.Fatalf("expected 201, got %d", resp.StatusCode)
}
if repo.lastCreate == nil {
t.Fatalf("expected create to be called")
}
})
}
func TestHospitalHandlerUpdate(t *testing.T) {
t.Run("invalid uuid", func(t *testing.T) {
repo := newMemHospitalRepo()
h := NewHospitalHandler(service.NewHospitalService(repo))
app := newHospitalTestApp(h, nil)
resp, body := doJSONRequestHospital(t, app, http.MethodPatch, "/api/v1/hospital/update/not-uuid", []byte(`{}`))
if resp.StatusCode != http.StatusUnprocessableEntity {
t.Fatalf("expected 422, got %d", resp.StatusCode)
}
assertContainsHospital(t, body, `"code":"NO_ICAO_CODE_INVALID_UUID"`)
assertContainsHospital(t, body, `"error_code":"4221105"`)
})
t.Run("invalid json", func(t *testing.T) {
repo := newMemHospitalRepo()
h := NewHospitalHandler(service.NewHospitalService(repo))
app := newHospitalTestApp(h, nil)
id := mustUUIDStringHospital(t, uuidv7.MustBytes())
resp, _ := doJSONRequestHospital(t, app, http.MethodPatch, "/api/v1/hospital/update/"+id, []byte(`{"data":`))
if resp.StatusCode != http.StatusBadRequest {
t.Fatalf("expected 400, got %d", resp.StatusCode)
}
})
t.Run("validation error", func(t *testing.T) {
repo := newMemHospitalRepo()
h := NewHospitalHandler(service.NewHospitalService(repo))
app := newHospitalTestApp(h, nil)
id := mustUUIDStringHospital(t, uuidv7.MustBytes())
resp, _ := doJSONRequestHospital(t, app, http.MethodPatch, "/api/v1/hospital/update/"+id, []byte(`{"data":{"type":"x","id":"`+id+`","attributes":{"name":"A"}}}`))
if resp.StatusCode != http.StatusUnprocessableEntity {
t.Fatalf("expected 422, got %d", resp.StatusCode)
}
})
t.Run("id required", func(t *testing.T) {
repo := newMemHospitalRepo()
h := NewHospitalHandler(service.NewHospitalService(repo))
app := newHospitalTestApp(h, nil)
id := mustUUIDStringHospital(t, uuidv7.MustBytes())
resp, _ := doJSONRequestHospital(t, app, http.MethodPatch, "/api/v1/hospital/update/"+id, []byte(`{"data":{"type":"hospital","id":"","attributes":{"name":"A"}}}`))
if resp.StatusCode != http.StatusUnprocessableEntity {
t.Fatalf("expected 422, got %d", resp.StatusCode)
}
})
t.Run("id mismatch", func(t *testing.T) {
repo := newMemHospitalRepo()
h := NewHospitalHandler(service.NewHospitalService(repo))
app := newHospitalTestApp(h, nil)
id := mustUUIDStringHospital(t, uuidv7.MustBytes())
other := mustUUIDStringHospital(t, uuidv7.MustBytes())
resp, body := doJSONRequestHospital(t, app, http.MethodPatch, "/api/v1/hospital/update/"+id, []byte(`{"data":{"type":"hospital","id":"`+other+`","attributes":{"name":"A"}}}`))
if resp.StatusCode != http.StatusUnprocessableEntity {
t.Fatalf("expected 422, got %d", resp.StatusCode)
}
assertContainsHospital(t, body, `"code":"NO_ICAO_CODE_ID_MISMATCH"`)
assertContainsHospital(t, body, `"error_code":"4221107"`)
})
t.Run("no attributes", func(t *testing.T) {
repo := newMemHospitalRepo()
h := NewHospitalHandler(service.NewHospitalService(repo))
app := newHospitalTestApp(h, nil)
id := mustUUIDStringHospital(t, uuidv7.MustBytes())
resp, _ := doJSONRequestHospital(t, app, http.MethodPatch, "/api/v1/hospital/update/"+id, []byte(`{"data":{"type":"hospital","id":"`+id+`","attributes":{}}}`))
if resp.StatusCode != http.StatusUnprocessableEntity {
t.Fatalf("expected 422, got %d", resp.StatusCode)
}
})
t.Run("not found", func(t *testing.T) {
repo := newMemHospitalRepo()
h := NewHospitalHandler(service.NewHospitalService(repo))
app := newHospitalTestApp(h, nil)
id := mustUUIDStringHospital(t, uuidv7.MustBytes())
resp, _ := doJSONRequestHospital(t, app, http.MethodPatch, "/api/v1/hospital/update/"+id, []byte(`{"data":{"type":"hospital","id":"`+id+`","attributes":{"name":"A"}}}`))
if resp.StatusCode != http.StatusNotFound {
t.Fatalf("expected 404, got %d", resp.StatusCode)
}
})
t.Run("name empty", func(t *testing.T) {
repo := newMemHospitalRepo()
id := uuidv7.MustBytes()
repo.rows[string(id)] = &hospital.Hospital{ID: id, Name: "Old", Address: "Old"}
h := NewHospitalHandler(service.NewHospitalService(repo))
app := newHospitalTestApp(h, nil)
idStr := mustUUIDStringHospital(t, id)
resp, _ := doJSONRequestHospital(t, app, http.MethodPatch, "/api/v1/hospital/update/"+idStr, []byte(`{"data":{"type":"hospital","id":"`+idStr+`","attributes":{"name":" "}}}`))
if resp.StatusCode != http.StatusUnprocessableEntity {
t.Fatalf("expected 422, got %d", resp.StatusCode)
}
})
t.Run("update error", func(t *testing.T) {
repo := newMemHospitalRepo()
id := uuidv7.MustBytes()
repo.rows[string(id)] = &hospital.Hospital{ID: id, Name: "Old", Address: "Old"}
repo.updateErr = errors.New("update failed")
h := NewHospitalHandler(service.NewHospitalService(repo))
app := newHospitalTestApp(h, nil)
idStr := mustUUIDStringHospital(t, id)
resp, body := doJSONRequestHospital(t, app, http.MethodPatch, "/api/v1/hospital/update/"+idStr, []byte(`{"data":{"type":"hospital","id":"`+idStr+`","attributes":{"name":"New"}}}`))
if resp.StatusCode != http.StatusBadRequest {
t.Fatalf("expected 400, got %d", resp.StatusCode)
}
assertContainsHospital(t, body, `"code":"NO_ICAO_CODE_UPDATE_FAILED"`)
assertContainsHospital(t, body, `"error_code":"4001111"`)
})
t.Run("success with refetch", func(t *testing.T) {
repo := newMemHospitalRepo()
id := uuidv7.MustBytes()
repo.rows[string(id)] = &hospital.Hospital{ID: id, Name: "Old", Address: "Old", LandlineNumber: "1", MobileNumber: "2", Email: "old@h.local", IsActive: true}
h := NewHospitalHandler(service.NewHospitalService(repo))
actor := uuidv7.MustBytes()
app := newHospitalTestApp(h, actor)
idStr := mustUUIDStringHospital(t, id)
body := `{"data":{"type":"hospital","id":"` + idStr + `","attributes":{"name":"New","sortkey":2,"address":"New Addr","landline_number":"021999","mobile_number":"089999","email":"new@hospital.local","is_active":false}}}`
resp, _ := doJSONRequestHospital(t, app, http.MethodPatch, "/api/v1/hospital/update/"+idStr, []byte(body))
if resp.StatusCode != http.StatusOK {
t.Fatalf("expected 200, got %d", resp.StatusCode)
}
if repo.lastUpdate == nil || repo.lastUpdate.Name != "New" || repo.lastUpdate.Address != "New Addr" || repo.lastUpdate.LandlineNumber != "021999" || repo.lastUpdate.MobileNumber != "089999" || repo.lastUpdate.Email != "new@hospital.local" {
t.Fatalf("expected updated values")
}
if repo.lastUpdate.SortKey == nil || *repo.lastUpdate.SortKey != 2 {
t.Fatalf("expected updated sortkey=2")
}
if repo.lastUpdate.IsActive {
t.Fatalf("expected updated is_active=false")
}
if string(repo.lastUpdate.UpdatedBy) != string(actor) {
t.Fatalf("expected updated_by from actor")
}
})
t.Run("success fallback when refetch fails", func(t *testing.T) {
repo := newMemHospitalRepo()
id := uuidv7.MustBytes()
existing := &hospital.Hospital{ID: id, Name: "Old", Address: "Old"}
repo.getByIDFn = func(_ []byte, call int) (*hospital.Hospital, error) {
if call == 1 {
return existing, nil
}
return nil, errors.New("second lookup failed")
}
h := NewHospitalHandler(service.NewHospitalService(repo))
app := newHospitalTestApp(h, nil)
idStr := mustUUIDStringHospital(t, id)
resp, _ := doJSONRequestHospital(t, app, http.MethodPatch, "/api/v1/hospital/update/"+idStr, []byte(`{"data":{"type":"hospital","id":"`+idStr+`","attributes":{"address":"Only addr"}}}`))
if resp.StatusCode != http.StatusOK {
t.Fatalf("expected 200, got %d", resp.StatusCode)
}
})
}
func TestHospitalHandlerDelete(t *testing.T) {
t.Run("invalid uuid", func(t *testing.T) {
repo := newMemHospitalRepo()
h := NewHospitalHandler(service.NewHospitalService(repo))
app := newHospitalTestApp(h, nil)
resp, _ := doJSONRequestHospital(t, app, http.MethodDelete, "/api/v1/hospital/delete/not-uuid", nil)
if resp.StatusCode != http.StatusUnprocessableEntity {
t.Fatalf("expected 422, got %d", resp.StatusCode)
}
})
t.Run("delete error", func(t *testing.T) {
repo := newMemHospitalRepo()
repo.deleteErr = errors.New("delete failed")
h := NewHospitalHandler(service.NewHospitalService(repo))
app := newHospitalTestApp(h, nil)
id := mustUUIDStringHospital(t, uuidv7.MustBytes())
resp, body := doJSONRequestHospital(t, app, http.MethodDelete, "/api/v1/hospital/delete/"+id, nil)
if resp.StatusCode != http.StatusBadRequest {
t.Fatalf("expected 400, got %d", resp.StatusCode)
}
assertContainsHospital(t, body, `"code":"NO_ICAO_CODE_DELETE_FAILED"`)
assertContainsHospital(t, body, `"error_code":"4001112"`)
})
t.Run("delete relation conflict", func(t *testing.T) {
repo := newMemHospitalRepo()
repo.deleteErr = apperrors.NewDeleteConflictError("flight_data")
h := NewHospitalHandler(service.NewHospitalService(repo))
app := newHospitalTestApp(h, nil)
id := mustUUIDStringHospital(t, uuidv7.MustBytes())
resp, body := doJSONRequestHospital(t, app, http.MethodDelete, "/api/v1/hospital/delete/"+id, nil)
if resp.StatusCode != http.StatusConflict {
t.Fatalf("expected 409, got %d", resp.StatusCode)
}
assertContainsHospital(t, body, `"code":"NO_ICAO_CODE_RELATION_DELETE_ERROR"`)
assertContainsHospital(t, body, `"error_code":"4091102"`)
})
t.Run("success", func(t *testing.T) {
repo := newMemHospitalRepo()
h := NewHospitalHandler(service.NewHospitalService(repo))
actor := uuidv7.MustBytes()
app := newHospitalTestApp(h, actor)
id := uuidv7.MustBytes()
idStr := mustUUIDStringHospital(t, id)
resp, _ := doJSONRequestHospital(t, app, http.MethodDelete, "/api/v1/hospital/delete/"+idStr, nil)
if resp.StatusCode != http.StatusOK {
t.Fatalf("expected 200, got %d", resp.StatusCode)
}
if string(repo.lastDeleteBy) != string(actor) {
t.Fatalf("expected deleted_by from actor")
}
})
}
func TestHospitalHandlerList(t *testing.T) {
t.Run("list error", func(t *testing.T) {
repo := newMemHospitalRepo()
repo.listErr = errors.New("list failed")
h := NewHospitalHandler(service.NewHospitalService(repo))
app := newHospitalTestApp(h, nil)
resp, _ := doJSONRequestHospital(t, app, http.MethodGet, "/api/v1/hospital/get-all", nil)
if resp.StatusCode != http.StatusBadRequest {
t.Fatalf("expected 400, got %d", resp.StatusCode)
}
})
t.Run("success with filter name and clamp max", func(t *testing.T) {
repo := newMemHospitalRepo()
repo.listRows = []hospital.Hospital{{ID: uuidv7.MustBytes(), Name: "RSUD Kota", SortKey: intPtrHospitalHandler(3), Address: "Jl. Merdeka", CreatedAt: time.Now(), UpdatedAt: time.Now()}}
repo.listTotal = 1
h := NewHospitalHandler(service.NewHospitalService(repo))
app := newHospitalTestApp(h, nil)
resp, _ := doJSONRequestHospital(t, app, http.MethodGet, "/api/v1/hospital/get-all?filter[name]=abc&page[number]=0&page=0&page[size]=0&limit=101&sort=-sortkey", nil)
if resp.StatusCode != http.StatusOK {
t.Fatalf("expected 200, got %d", resp.StatusCode)
}
if repo.lastListFilter != "abc" || repo.lastListSort != "is_active DESC, CASE WHEN is_active = 1 AND sortkey IS NOT NULL AND sortkey >= 0 THEN 0 ELSE 1 END ASC, CASE WHEN is_active = 1 AND sortkey IS NOT NULL AND sortkey >= 0 THEN sortkey END DESC, hospital_name ASC" || repo.lastListLimit != 0 || repo.lastListOffset != 0 {
t.Fatalf("unexpected list params: filter=%q sort=%q limit=%d offset=%d", repo.lastListFilter, repo.lastListSort, repo.lastListLimit, repo.lastListOffset)
}
})
t.Run("success with search and clamp min", func(t *testing.T) {
repo := newMemHospitalRepo()
repo.listRows = []hospital.Hospital{}
repo.listTotal = 0
h := NewHospitalHandler(service.NewHospitalService(repo))
app := newHospitalTestApp(h, nil)
resp, _ := doJSONRequestHospital(t, app, http.MethodGet, "/api/v1/hospital/get-all?page=2&size=-1&search=needle", nil)
if resp.StatusCode != http.StatusOK {
t.Fatalf("expected 200, got %d", resp.StatusCode)
}
if repo.lastListFilter != "needle" || repo.lastListLimit != 0 || repo.lastListOffset != 0 {
t.Fatalf("unexpected list params: filter=%q limit=%d offset=%d", repo.lastListFilter, repo.lastListLimit, repo.lastListOffset)
}
})
}
func TestHospitalHandlerListDatatable(t *testing.T) {
t.Run("list error", func(t *testing.T) {
repo := newMemHospitalRepo()
repo.listErr = errors.New("list failed")
h := NewHospitalHandler(service.NewHospitalService(repo))
app := newHospitalTestApp(h, nil)
resp, _ := doJSONRequestHospital(t, app, http.MethodGet, "/api/v1/hospital/get-all/dt", nil)
if resp.StatusCode != http.StatusBadRequest {
t.Fatalf("expected 400, got %d", resp.StatusCode)
}
})
t.Run("success clamp max and page floor", func(t *testing.T) {
repo := newMemHospitalRepo()
repo.listRows = []hospital.Hospital{{ID: uuidv7.MustBytes(), Name: "RSUD Kota", Address: "Jl. Merdeka", CreatedAt: time.Now(), UpdatedAt: time.Now()}}
repo.listTotal = 1
h := NewHospitalHandler(service.NewHospitalService(repo))
app := newHospitalTestApp(h, nil)
resp, _ := doJSONRequestHospital(t, app, http.MethodGet, "/api/v1/hospital/get-all/dt?page=0&limit=0&size=101&draw=7&search=abc", nil)
if resp.StatusCode != http.StatusOK {
t.Fatalf("expected 200, got %d", resp.StatusCode)
}
if repo.lastListFilter != "abc" || repo.lastListLimit != 100 || repo.lastListOffset != 0 {
t.Fatalf("unexpected list params: filter=%q limit=%d offset=%d", repo.lastListFilter, repo.lastListLimit, repo.lastListOffset)
}
})
t.Run("success clamp min", func(t *testing.T) {
repo := newMemHospitalRepo()
repo.listRows = []hospital.Hospital{}
repo.listTotal = 0
h := NewHospitalHandler(service.NewHospitalService(repo))
app := newHospitalTestApp(h, nil)
resp, _ := doJSONRequestHospital(t, app, http.MethodGet, "/api/v1/hospital/get-all/dt?page=2&limit=0&size=-1", nil)
if resp.StatusCode != http.StatusOK {
t.Fatalf("expected 200, got %d", resp.StatusCode)
}
if repo.lastListLimit != 20 || repo.lastListOffset != 20 {
t.Fatalf("unexpected list params: limit=%d offset=%d", repo.lastListLimit, repo.lastListOffset)
}
})
}
func TestHospitalResource(t *testing.T) {
id := uuidv7.MustBytes()
createdBy := uuidv7.MustBytes()
updatedBy := uuidv7.MustBytes()
deletedBy := uuidv7.MustBytes()
now := time.Now().UTC().Truncate(time.Second)
deletedAt := now.Add(time.Hour)
row := &hospital.Hospital{
ID: id,
Name: "RSUD Kota",
SortKey: intPtrHospitalHandler(2),
Address: "Jl. Merdeka",
LandlineNumber: "021123456",
MobileNumber: "08123456",
Email: "rsud@hospital.local",
IsActive: true,
CreatedAt: now,
CreatedBy: createdBy,
UpdatedAt: now,
UpdatedBy: updatedBy,
DeletedAt: &deletedAt,
DeletedBy: deletedBy,
}
res := hospitalResource(row)
if res.Type != "hospital" {
t.Fatalf("unexpected type: %s", res.Type)
}
if res.ID == "" || res.Attributes.CreatedBy == "" || res.Attributes.UpdatedBy == "" || res.Attributes.DeletedBy == "" {
t.Fatalf("expected UUID fields in resource")
}
if res.Attributes.Name != "RSUD Kota" || res.Attributes.Address != "Jl. Merdeka" || res.Attributes.LandlineNumber != "021123456" || res.Attributes.MobileNumber != "08123456" || res.Attributes.Email != "rsud@hospital.local" {
t.Fatalf("expected hospital fields in resource")
}
if res.Attributes.SortKey == nil || *res.Attributes.SortKey != 2 {
t.Fatalf("expected sortkey mapped in resource")
}
if !res.Attributes.IsActive {
t.Fatalf("expected is_active mapped in resource")
}
if res.Attributes.CreatedAt == "" || res.Attributes.UpdatedAt == "" || res.Attributes.DeletedAt == "" {
t.Fatalf("expected time fields in resource")
}
}
func intPtrHospitalHandler(v int) *int { return &v }