init push
This commit is contained in:
803
internal/transport/http/handlers/icao_handler_test.go
Normal file
803
internal/transport/http/handlers/icao_handler_test.go
Normal file
@@ -0,0 +1,803 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
|
||||
"wucher/internal/domain/federal_state"
|
||||
"wucher/internal/domain/icao"
|
||||
"wucher/internal/domain/land"
|
||||
"wucher/internal/service"
|
||||
"wucher/internal/shared/pkg/uuidv7"
|
||||
)
|
||||
|
||||
type memICAORepo struct {
|
||||
rows map[string]*icao.ICAO
|
||||
|
||||
createErr error
|
||||
updateErr error
|
||||
deleteErr error
|
||||
getByIDErr error
|
||||
listErr error
|
||||
|
||||
listRows []icao.ICAO
|
||||
listTotal int64
|
||||
|
||||
getByIDFn func(id []byte, call int) (*icao.ICAO, error)
|
||||
getCalls int
|
||||
|
||||
lastCreate *icao.ICAO
|
||||
lastUpdate *icao.ICAO
|
||||
lastDeleteID []byte
|
||||
lastDeleteBy []byte
|
||||
lastListFilter string
|
||||
lastListSort string
|
||||
lastListLimit int
|
||||
lastListOffset int
|
||||
}
|
||||
|
||||
func newMemICAORepo() *memICAORepo {
|
||||
return &memICAORepo{
|
||||
rows: map[string]*icao.ICAO{},
|
||||
}
|
||||
}
|
||||
|
||||
func (r *memICAORepo) Create(_ context.Context, row *icao.ICAO) 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 *memICAORepo) Update(_ context.Context, row *icao.ICAO) error {
|
||||
if r.updateErr != nil {
|
||||
return r.updateErr
|
||||
}
|
||||
r.rows[string(row.ID)] = row
|
||||
r.lastUpdate = row
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *memICAORepo) 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 *memICAORepo) GetByID(_ context.Context, id []byte) (*icao.ICAO, 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 *memICAORepo) List(_ context.Context, filter, sort string, limit, offset int) ([]icao.ICAO, 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 newICAOTestApp(h *ICAOHandler, 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/icao/create", h.Create)
|
||||
app.Patch("/api/v1/icao/update/:uuid", h.Update)
|
||||
app.Delete("/api/v1/icao/delete/:uuid", h.Delete)
|
||||
app.Get("/api/v1/icao/get-all", h.List)
|
||||
app.Get("/api/v1/icao/get-all/dt", h.ListDatatable)
|
||||
return app
|
||||
}
|
||||
|
||||
func doJSONRequestICAO(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 mustUUIDStringICAO(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 TestNewICAOHandler(t *testing.T) {
|
||||
h := NewICAOHandler(service.NewICAOService(newMemICAORepo()))
|
||||
if h == nil || h.validate == nil {
|
||||
t.Fatalf("expected handler and validator initialized")
|
||||
}
|
||||
}
|
||||
|
||||
func TestICAOHandlerCreate(t *testing.T) {
|
||||
t.Run("invalid json", func(t *testing.T) {
|
||||
repo := newMemICAORepo()
|
||||
h := NewICAOHandler(service.NewICAOService(repo))
|
||||
app := newICAOTestApp(h, nil)
|
||||
|
||||
resp, _ := doJSONRequestICAO(t, app, http.MethodPost, "/api/v1/icao/create", []byte(`{"data":`))
|
||||
if resp.StatusCode != http.StatusBadRequest {
|
||||
t.Fatalf("expected 400, got %d", resp.StatusCode)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("validation error", func(t *testing.T) {
|
||||
repo := newMemICAORepo()
|
||||
h := NewICAOHandler(service.NewICAOService(repo))
|
||||
app := newICAOTestApp(h, nil)
|
||||
|
||||
resp, _ := doJSONRequestICAO(t, app, http.MethodPost, "/api/v1/icao/create", []byte(`{}`))
|
||||
if resp.StatusCode != http.StatusUnprocessableEntity {
|
||||
t.Fatalf("expected 422, got %d", resp.StatusCode)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("name empty", func(t *testing.T) {
|
||||
repo := newMemICAORepo()
|
||||
h := NewICAOHandler(service.NewICAOService(repo))
|
||||
app := newICAOTestApp(h, nil)
|
||||
|
||||
body := []byte(`{"data":{"type":"icao","attributes":{"icao_code":" "}}}`)
|
||||
resp, _ := doJSONRequestICAO(t, app, http.MethodPost, "/api/v1/icao/create", body)
|
||||
if resp.StatusCode != http.StatusUnprocessableEntity {
|
||||
t.Fatalf("expected 422, got %d", resp.StatusCode)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("create error", func(t *testing.T) {
|
||||
repo := newMemICAORepo()
|
||||
repo.createErr = errors.New("create failed")
|
||||
h := NewICAOHandler(service.NewICAOService(repo))
|
||||
app := newICAOTestApp(h, nil)
|
||||
|
||||
body := []byte(`{"data":{"type":"icao","attributes":{"icao_code":"EDDB"}}}`)
|
||||
resp, _ := doJSONRequestICAO(t, app, http.MethodPost, "/api/v1/icao/create", body)
|
||||
if resp.StatusCode != http.StatusBadRequest {
|
||||
t.Fatalf("expected 400, got %d", resp.StatusCode)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("invalid federal_state_id", func(t *testing.T) {
|
||||
repo := newMemICAORepo()
|
||||
h := NewICAOHandler(service.NewICAOService(repo))
|
||||
app := newICAOTestApp(h, nil)
|
||||
|
||||
body := []byte(`{"data":{"type":"icao","attributes":{"icao_code":"EDDB","federal_state_id":"not-uuid"}}}`)
|
||||
resp, _ := doJSONRequestICAO(t, app, http.MethodPost, "/api/v1/icao/create", body)
|
||||
if resp.StatusCode != http.StatusUnprocessableEntity {
|
||||
t.Fatalf("expected 422, got %d", resp.StatusCode)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("success with refetch", func(t *testing.T) {
|
||||
repo := newMemICAORepo()
|
||||
h := NewICAOHandler(service.NewICAOService(repo))
|
||||
actor := uuidv7.MustBytes()
|
||||
app := newICAOTestApp(h, actor)
|
||||
|
||||
body := []byte(`{"data":{"type":"icao","attributes":{"icao_code":"EDDB","sortkey":3,"address":"Main Street","landline_number":"021123456","mobile_number":"+491234","email":"main@hic.local","is_active":false}}}`)
|
||||
resp, _ := doJSONRequestICAO(t, app, http.MethodPost, "/api/v1/icao/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.ICAOCode != "EDDB" || repo.lastCreate.Address != "Main Street" || repo.lastCreate.LandlineNumber != "021123456" || repo.lastCreate.MobileNumber != "+491234" || repo.lastCreate.Email != "main@hic.local" {
|
||||
t.Fatalf("expected optional attributes to be mapped")
|
||||
}
|
||||
if repo.lastCreate.SortKey == nil || *repo.lastCreate.SortKey != 3 {
|
||||
t.Fatalf("expected sortkey mapped")
|
||||
}
|
||||
if repo.lastCreate.IsActive {
|
||||
t.Fatalf("expected is_active false")
|
||||
}
|
||||
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("success with federal_state_id", func(t *testing.T) {
|
||||
repo := newMemICAORepo()
|
||||
h := NewICAOHandler(service.NewICAOService(repo))
|
||||
app := newICAOTestApp(h, nil)
|
||||
fsID := mustUUIDStringICAO(t, uuidv7.MustBytes())
|
||||
|
||||
body := []byte(`{"data":{"type":"icao","attributes":{"icao_code":"EDDF","federal_state_id":"` + fsID + `","note":" test note "}}}`)
|
||||
resp, _ := doJSONRequestICAO(t, app, http.MethodPost, "/api/v1/icao/create", body)
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
t.Fatalf("expected 201, got %d", resp.StatusCode)
|
||||
}
|
||||
if repo.lastCreate == nil || len(repo.lastCreate.FederalStateID) == 0 {
|
||||
t.Fatalf("expected federal_state_id mapped")
|
||||
}
|
||||
if repo.lastCreate.Note != "test note" {
|
||||
t.Fatalf("expected note trimmed")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("success fallback when refetch error", func(t *testing.T) {
|
||||
repo := newMemICAORepo()
|
||||
repo.getByIDFn = func(_ []byte, call int) (*icao.ICAO, error) {
|
||||
if call > 0 {
|
||||
return nil, errors.New("lookup failed")
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
h := NewICAOHandler(service.NewICAOService(repo))
|
||||
app := newICAOTestApp(h, nil)
|
||||
|
||||
body := []byte(`{"data":{"type":"icao","attributes":{"icao_code":"EDDB"}}}`)
|
||||
resp, _ := doJSONRequestICAO(t, app, http.MethodPost, "/api/v1/icao/create", body)
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
t.Fatalf("expected 201, got %d", resp.StatusCode)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestICAOHandlerUpdate(t *testing.T) {
|
||||
t.Run("invalid uuid", func(t *testing.T) {
|
||||
repo := newMemICAORepo()
|
||||
h := NewICAOHandler(service.NewICAOService(repo))
|
||||
app := newICAOTestApp(h, nil)
|
||||
|
||||
resp, _ := doJSONRequestICAO(t, app, http.MethodPatch, "/api/v1/icao/update/not-uuid", []byte(`{}`))
|
||||
if resp.StatusCode != http.StatusUnprocessableEntity {
|
||||
t.Fatalf("expected 422, got %d", resp.StatusCode)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("invalid json", func(t *testing.T) {
|
||||
repo := newMemICAORepo()
|
||||
h := NewICAOHandler(service.NewICAOService(repo))
|
||||
app := newICAOTestApp(h, nil)
|
||||
id := mustUUIDStringICAO(t, uuidv7.MustBytes())
|
||||
|
||||
resp, _ := doJSONRequestICAO(t, app, http.MethodPatch, "/api/v1/icao/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 := newMemICAORepo()
|
||||
h := NewICAOHandler(service.NewICAOService(repo))
|
||||
app := newICAOTestApp(h, nil)
|
||||
id := mustUUIDStringICAO(t, uuidv7.MustBytes())
|
||||
|
||||
resp, _ := doJSONRequestICAO(t, app, http.MethodPatch, "/api/v1/icao/update/"+id, []byte(`{"data":{"type":"x","id":"`+id+`","attributes":{"icao_code":"A"}}}`))
|
||||
if resp.StatusCode != http.StatusUnprocessableEntity {
|
||||
t.Fatalf("expected 422, got %d", resp.StatusCode)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("id required", func(t *testing.T) {
|
||||
repo := newMemICAORepo()
|
||||
h := NewICAOHandler(service.NewICAOService(repo))
|
||||
app := newICAOTestApp(h, nil)
|
||||
id := mustUUIDStringICAO(t, uuidv7.MustBytes())
|
||||
|
||||
resp, _ := doJSONRequestICAO(t, app, http.MethodPatch, "/api/v1/icao/update/"+id, []byte(`{"data":{"type":"icao","id":"","attributes":{"icao_code":"A"}}}`))
|
||||
if resp.StatusCode != http.StatusUnprocessableEntity {
|
||||
t.Fatalf("expected 422, got %d", resp.StatusCode)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("id mismatch", func(t *testing.T) {
|
||||
repo := newMemICAORepo()
|
||||
h := NewICAOHandler(service.NewICAOService(repo))
|
||||
app := newICAOTestApp(h, nil)
|
||||
id := mustUUIDStringICAO(t, uuidv7.MustBytes())
|
||||
other := mustUUIDStringICAO(t, uuidv7.MustBytes())
|
||||
|
||||
resp, _ := doJSONRequestICAO(t, app, http.MethodPatch, "/api/v1/icao/update/"+id, []byte(`{"data":{"type":"icao","id":"`+other+`","attributes":{"icao_code":"A"}}}`))
|
||||
if resp.StatusCode != http.StatusUnprocessableEntity {
|
||||
t.Fatalf("expected 422, got %d", resp.StatusCode)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("no attributes", func(t *testing.T) {
|
||||
repo := newMemICAORepo()
|
||||
h := NewICAOHandler(service.NewICAOService(repo))
|
||||
app := newICAOTestApp(h, nil)
|
||||
id := mustUUIDStringICAO(t, uuidv7.MustBytes())
|
||||
|
||||
resp, _ := doJSONRequestICAO(t, app, http.MethodPatch, "/api/v1/icao/update/"+id, []byte(`{"data":{"type":"icao","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 := newMemICAORepo()
|
||||
repo.getByIDErr = errors.New("lookup failed")
|
||||
h := NewICAOHandler(service.NewICAOService(repo))
|
||||
app := newICAOTestApp(h, nil)
|
||||
id := mustUUIDStringICAO(t, uuidv7.MustBytes())
|
||||
|
||||
resp, _ := doJSONRequestICAO(t, app, http.MethodPatch, "/api/v1/icao/update/"+id, []byte(`{"data":{"type":"icao","id":"`+id+`","attributes":{"icao_code":"A"}}}`))
|
||||
if resp.StatusCode != http.StatusNotFound {
|
||||
t.Fatalf("expected 404, got %d", resp.StatusCode)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("name empty", func(t *testing.T) {
|
||||
repo := newMemICAORepo()
|
||||
id := uuidv7.MustBytes()
|
||||
repo.rows[string(id)] = &icao.ICAO{ID: id, ICAOCode: "OLD"}
|
||||
h := NewICAOHandler(service.NewICAOService(repo))
|
||||
app := newICAOTestApp(h, nil)
|
||||
idStr := mustUUIDStringICAO(t, id)
|
||||
|
||||
resp, _ := doJSONRequestICAO(t, app, http.MethodPatch, "/api/v1/icao/update/"+idStr, []byte(`{"data":{"type":"icao","id":"`+idStr+`","attributes":{"icao_code":" "}}}`))
|
||||
if resp.StatusCode != http.StatusUnprocessableEntity {
|
||||
t.Fatalf("expected 422, got %d", resp.StatusCode)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("update error", func(t *testing.T) {
|
||||
repo := newMemICAORepo()
|
||||
id := uuidv7.MustBytes()
|
||||
repo.rows[string(id)] = &icao.ICAO{ID: id, ICAOCode: "OLD"}
|
||||
repo.updateErr = errors.New("update failed")
|
||||
h := NewICAOHandler(service.NewICAOService(repo))
|
||||
app := newICAOTestApp(h, nil)
|
||||
idStr := mustUUIDStringICAO(t, id)
|
||||
|
||||
resp, _ := doJSONRequestICAO(t, app, http.MethodPatch, "/api/v1/icao/update/"+idStr, []byte(`{"data":{"type":"icao","id":"`+idStr+`","attributes":{"icao_code":"NEW"}}}`))
|
||||
if resp.StatusCode != http.StatusBadRequest {
|
||||
t.Fatalf("expected 400, got %d", resp.StatusCode)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("invalid federal_state_id", func(t *testing.T) {
|
||||
repo := newMemICAORepo()
|
||||
id := uuidv7.MustBytes()
|
||||
repo.rows[string(id)] = &icao.ICAO{ID: id, ICAOCode: "OLD"}
|
||||
h := NewICAOHandler(service.NewICAOService(repo))
|
||||
app := newICAOTestApp(h, nil)
|
||||
idStr := mustUUIDStringICAO(t, id)
|
||||
|
||||
resp, _ := doJSONRequestICAO(t, app, http.MethodPatch, "/api/v1/icao/update/"+idStr, []byte(`{"data":{"type":"icao","id":"`+idStr+`","attributes":{"federal_state_id":"not-uuid"}}}`))
|
||||
if resp.StatusCode != http.StatusUnprocessableEntity {
|
||||
t.Fatalf("expected 422, got %d", resp.StatusCode)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("clear federal_state_id by empty string", func(t *testing.T) {
|
||||
repo := newMemICAORepo()
|
||||
id := uuidv7.MustBytes()
|
||||
repo.rows[string(id)] = &icao.ICAO{
|
||||
ID: id,
|
||||
ICAOCode: "OLD",
|
||||
FederalStateID: uuidv7.MustBytes(),
|
||||
}
|
||||
h := NewICAOHandler(service.NewICAOService(repo))
|
||||
app := newICAOTestApp(h, nil)
|
||||
idStr := mustUUIDStringICAO(t, id)
|
||||
|
||||
resp, _ := doJSONRequestICAO(t, app, http.MethodPatch, "/api/v1/icao/update/"+idStr, []byte(`{"data":{"type":"icao","id":"`+idStr+`","attributes":{"federal_state_id":" "}}}`))
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d", resp.StatusCode)
|
||||
}
|
||||
if repo.lastUpdate == nil {
|
||||
t.Fatalf("expected update called")
|
||||
}
|
||||
if repo.lastUpdate.FederalStateID != nil {
|
||||
t.Fatalf("expected federal_state_id to be cleared")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("success with refetch", func(t *testing.T) {
|
||||
repo := newMemICAORepo()
|
||||
id := uuidv7.MustBytes()
|
||||
repo.rows[string(id)] = &icao.ICAO{ID: id, ICAOCode: "OLD"}
|
||||
h := NewICAOHandler(service.NewICAOService(repo))
|
||||
actor := uuidv7.MustBytes()
|
||||
app := newICAOTestApp(h, actor)
|
||||
idStr := mustUUIDStringICAO(t, id)
|
||||
|
||||
resp, _ := doJSONRequestICAO(t, app, http.MethodPatch, "/api/v1/icao/update/"+idStr, []byte(`{"data":{"type":"icao","id":"`+idStr+`","attributes":{"icao_code":"NEW","address":"Street 1","landline_number":"021999","mobile_number":"+49888","email":"new@hic.local"}}}`))
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d", resp.StatusCode)
|
||||
}
|
||||
if repo.lastUpdate == nil || repo.lastUpdate.ICAOCode != "NEW" || repo.lastUpdate.Address != "Street 1" || repo.lastUpdate.LandlineNumber != "021999" || repo.lastUpdate.MobileNumber != "+49888" || repo.lastUpdate.Email != "new@hic.local" {
|
||||
t.Fatalf("expected updated values")
|
||||
}
|
||||
if string(repo.lastUpdate.UpdatedBy) != string(actor) {
|
||||
t.Fatalf("expected updated_by from actor")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("success update sortkey and is_active", func(t *testing.T) {
|
||||
repo := newMemICAORepo()
|
||||
id := uuidv7.MustBytes()
|
||||
repo.rows[string(id)] = &icao.ICAO{ID: id, ICAOCode: "OLD", IsActive: true}
|
||||
h := NewICAOHandler(service.NewICAOService(repo))
|
||||
app := newICAOTestApp(h, nil)
|
||||
idStr := mustUUIDStringICAO(t, id)
|
||||
|
||||
resp, _ := doJSONRequestICAO(t, app, http.MethodPatch, "/api/v1/icao/update/"+idStr, []byte(`{"data":{"type":"icao","id":"`+idStr+`","attributes":{"sortkey":2,"is_active":false}}}`))
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d", resp.StatusCode)
|
||||
}
|
||||
if repo.lastUpdate == nil {
|
||||
t.Fatalf("expected update called")
|
||||
}
|
||||
if repo.lastUpdate.SortKey == nil || *repo.lastUpdate.SortKey != 2 {
|
||||
t.Fatalf("expected sortkey updated")
|
||||
}
|
||||
if repo.lastUpdate.IsActive {
|
||||
t.Fatalf("expected is_active false")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("success clear sortkey with null", func(t *testing.T) {
|
||||
repo := newMemICAORepo()
|
||||
id := uuidv7.MustBytes()
|
||||
repo.rows[string(id)] = &icao.ICAO{ID: id, ICAOCode: "OLD", SortKey: intPtrICAOHandler(7)}
|
||||
h := NewICAOHandler(service.NewICAOService(repo))
|
||||
app := newICAOTestApp(h, nil)
|
||||
idStr := mustUUIDStringICAO(t, id)
|
||||
|
||||
resp, _ := doJSONRequestICAO(t, app, http.MethodPatch, "/api/v1/icao/update/"+idStr, []byte(`{"data":{"type":"icao","id":"`+idStr+`","attributes":{"sortkey":null}}}`))
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d", resp.StatusCode)
|
||||
}
|
||||
if repo.lastUpdate == nil || repo.lastUpdate.SortKey != nil {
|
||||
t.Fatalf("expected sortkey cleared")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("success set federal_state_id", func(t *testing.T) {
|
||||
repo := newMemICAORepo()
|
||||
id := uuidv7.MustBytes()
|
||||
oldFSID := uuidv7.MustBytes()
|
||||
repo.rows[string(id)] = &icao.ICAO{
|
||||
ID: id,
|
||||
ICAOCode: "OLD",
|
||||
FederalStateID: oldFSID,
|
||||
FederalState: &federal_state.FederalState{ID: oldFSID, Name: "Old FS"},
|
||||
}
|
||||
h := NewICAOHandler(service.NewICAOService(repo))
|
||||
app := newICAOTestApp(h, nil)
|
||||
idStr := mustUUIDStringICAO(t, id)
|
||||
fsID := mustUUIDStringICAO(t, uuidv7.MustBytes())
|
||||
|
||||
resp, _ := doJSONRequestICAO(t, app, http.MethodPatch, "/api/v1/icao/update/"+idStr, []byte(`{"data":{"type":"icao","id":"`+idStr+`","attributes":{"federal_state_id":"`+fsID+`"}}}`))
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d", resp.StatusCode)
|
||||
}
|
||||
if repo.lastUpdate == nil || len(repo.lastUpdate.FederalStateID) == 0 {
|
||||
t.Fatalf("expected federal_state_id set")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("success update note", func(t *testing.T) {
|
||||
repo := newMemICAORepo()
|
||||
id := uuidv7.MustBytes()
|
||||
repo.rows[string(id)] = &icao.ICAO{ID: id, ICAOCode: "OLD", Note: "old"}
|
||||
h := NewICAOHandler(service.NewICAOService(repo))
|
||||
app := newICAOTestApp(h, nil)
|
||||
idStr := mustUUIDStringICAO(t, id)
|
||||
|
||||
resp, _ := doJSONRequestICAO(t, app, http.MethodPatch, "/api/v1/icao/update/"+idStr, []byte(`{"data":{"type":"icao","id":"`+idStr+`","attributes":{"note":" updated note "}}}`))
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d", resp.StatusCode)
|
||||
}
|
||||
if repo.lastUpdate == nil || repo.lastUpdate.Note != "updated note" {
|
||||
t.Fatalf("expected note updated and trimmed")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("success fallback when refetch fails", func(t *testing.T) {
|
||||
repo := newMemICAORepo()
|
||||
id := uuidv7.MustBytes()
|
||||
existing := &icao.ICAO{ID: id, ICAOCode: "OLD"}
|
||||
repo.getByIDFn = func(_ []byte, call int) (*icao.ICAO, error) {
|
||||
if call == 1 {
|
||||
return existing, nil
|
||||
}
|
||||
return nil, errors.New("second lookup failed")
|
||||
}
|
||||
h := NewICAOHandler(service.NewICAOService(repo))
|
||||
app := newICAOTestApp(h, nil)
|
||||
idStr := mustUUIDStringICAO(t, id)
|
||||
|
||||
resp, _ := doJSONRequestICAO(t, app, http.MethodPatch, "/api/v1/icao/update/"+idStr, []byte(`{"data":{"type":"icao","id":"`+idStr+`","attributes":{"address":"Only addr"}}}`))
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d", resp.StatusCode)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestICAOHandlerDelete(t *testing.T) {
|
||||
t.Run("invalid uuid", func(t *testing.T) {
|
||||
repo := newMemICAORepo()
|
||||
h := NewICAOHandler(service.NewICAOService(repo))
|
||||
app := newICAOTestApp(h, nil)
|
||||
|
||||
resp, _ := doJSONRequestICAO(t, app, http.MethodDelete, "/api/v1/icao/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 := newMemICAORepo()
|
||||
repo.deleteErr = errors.New("delete failed")
|
||||
h := NewICAOHandler(service.NewICAOService(repo))
|
||||
app := newICAOTestApp(h, nil)
|
||||
id := mustUUIDStringICAO(t, uuidv7.MustBytes())
|
||||
|
||||
resp, _ := doJSONRequestICAO(t, app, http.MethodDelete, "/api/v1/icao/delete/"+id, nil)
|
||||
if resp.StatusCode != http.StatusBadRequest {
|
||||
t.Fatalf("expected 400, got %d", resp.StatusCode)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("success", func(t *testing.T) {
|
||||
repo := newMemICAORepo()
|
||||
h := NewICAOHandler(service.NewICAOService(repo))
|
||||
actor := uuidv7.MustBytes()
|
||||
app := newICAOTestApp(h, actor)
|
||||
id := uuidv7.MustBytes()
|
||||
idStr := mustUUIDStringICAO(t, id)
|
||||
|
||||
resp, _ := doJSONRequestICAO(t, app, http.MethodDelete, "/api/v1/icao/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 TestICAOHandlerList(t *testing.T) {
|
||||
t.Run("list error", func(t *testing.T) {
|
||||
repo := newMemICAORepo()
|
||||
repo.listErr = errors.New("list failed")
|
||||
h := NewICAOHandler(service.NewICAOService(repo))
|
||||
app := newICAOTestApp(h, nil)
|
||||
|
||||
resp, _ := doJSONRequestICAO(t, app, http.MethodGet, "/api/v1/icao/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 := newMemICAORepo()
|
||||
repo.listRows = []icao.ICAO{{ID: uuidv7.MustBytes(), ICAOCode: "A", SortKey: intPtrICAOHandler(3), IsActive: true, CreatedAt: time.Now(), UpdatedAt: time.Now()}}
|
||||
repo.listTotal = 1
|
||||
h := NewICAOHandler(service.NewICAOService(repo))
|
||||
app := newICAOTestApp(h, nil)
|
||||
|
||||
resp, _ := doJSONRequestICAO(t, app, http.MethodGet, "/api/v1/icao/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, icao_code 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 := newMemICAORepo()
|
||||
repo.listRows = []icao.ICAO{}
|
||||
repo.listTotal = 0
|
||||
h := NewICAOHandler(service.NewICAOService(repo))
|
||||
app := newICAOTestApp(h, nil)
|
||||
|
||||
resp, _ := doJSONRequestICAO(t, app, http.MethodGet, "/api/v1/icao/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 TestICAOHandlerListDatatable(t *testing.T) {
|
||||
t.Run("list error", func(t *testing.T) {
|
||||
repo := newMemICAORepo()
|
||||
repo.listErr = errors.New("list failed")
|
||||
h := NewICAOHandler(service.NewICAOService(repo))
|
||||
app := newICAOTestApp(h, nil)
|
||||
|
||||
resp, _ := doJSONRequestICAO(t, app, http.MethodGet, "/api/v1/icao/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 := newMemICAORepo()
|
||||
repo.listRows = []icao.ICAO{{ID: uuidv7.MustBytes(), ICAOCode: "A", CreatedAt: time.Now(), UpdatedAt: time.Now()}}
|
||||
repo.listTotal = 1
|
||||
h := NewICAOHandler(service.NewICAOService(repo))
|
||||
app := newICAOTestApp(h, nil)
|
||||
|
||||
resp, _ := doJSONRequestICAO(t, app, http.MethodGet, "/api/v1/icao/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 := newMemICAORepo()
|
||||
repo.listRows = []icao.ICAO{}
|
||||
repo.listTotal = 0
|
||||
h := NewICAOHandler(service.NewICAOService(repo))
|
||||
app := newICAOTestApp(h, nil)
|
||||
|
||||
resp, _ := doJSONRequestICAO(t, app, http.MethodGet, "/api/v1/icao/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 TestICAOResource(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 := &icao.ICAO{
|
||||
ID: id,
|
||||
ICAOCode: "EDDB",
|
||||
Name: "Berlin Brandenburg Airport",
|
||||
SortKey: intPtrICAOHandler(2),
|
||||
Address: "Street 1",
|
||||
LandlineNumber: "021123456",
|
||||
MobileNumber: "+491234",
|
||||
Email: "main@hic.local",
|
||||
IsActive: true,
|
||||
CreatedAt: now,
|
||||
CreatedBy: createdBy,
|
||||
UpdatedAt: now,
|
||||
UpdatedBy: updatedBy,
|
||||
DeletedAt: &deletedAt,
|
||||
DeletedBy: deletedBy,
|
||||
FederalState: &federal_state.FederalState{
|
||||
Name: "Bayern",
|
||||
Land: &land.Land{Name: "Germany", LandISOCode: "DE"},
|
||||
},
|
||||
}
|
||||
|
||||
res := icaoResource(row)
|
||||
if res.Type != "icao" {
|
||||
t.Fatalf("unexpected type: %s", res.Type)
|
||||
}
|
||||
if res.Attributes.SortKey == nil || *res.Attributes.SortKey != 2 || !res.Attributes.IsActive {
|
||||
t.Fatalf("expected sortkey and is_active in resource")
|
||||
}
|
||||
if res.Attributes.Name != "Berlin Brandenburg Airport" {
|
||||
t.Fatalf("expected name in resource")
|
||||
}
|
||||
if res.Attributes.FederalState != "Bayern" || res.Attributes.LandName != "Germany" || res.Attributes.LandISOCode != "DE" {
|
||||
t.Fatalf("expected federal and land fields in resource")
|
||||
}
|
||||
if res.ID == "" || res.Attributes.CreatedBy == "" || res.Attributes.UpdatedBy == "" || res.Attributes.DeletedBy == "" {
|
||||
t.Fatalf("expected UUID fields in resource")
|
||||
}
|
||||
if res.Attributes.CreatedAt == "" || res.Attributes.UpdatedAt == "" || res.Attributes.DeletedAt == "" {
|
||||
t.Fatalf("expected time fields in resource")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseNullableUUID(t *testing.T) {
|
||||
t.Run("empty becomes nil", func(t *testing.T) {
|
||||
got, err := parseNullableUUID(" ")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
if got != nil {
|
||||
t.Fatalf("expected nil")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("valid uuid", func(t *testing.T) {
|
||||
id := uuidv7.MustBytes()
|
||||
idStr := mustUUIDStringICAO(t, id)
|
||||
got, err := parseNullableUUID(idStr)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
if string(got) != string(id) {
|
||||
t.Fatalf("expected same bytes")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("invalid uuid", func(t *testing.T) {
|
||||
if _, err := parseNullableUUID("not-uuid"); err == nil {
|
||||
t.Fatalf("expected err")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestICAOFederalStateLandNameHelpers(t *testing.T) {
|
||||
if federalStateNameOrEmpty(nil) != "" || landNameOrEmpty(nil) != "" || landISOCodeOrEmpty(nil) != "" {
|
||||
t.Fatalf("expected empty for nil row")
|
||||
}
|
||||
rowWithoutFS := &icao.ICAO{}
|
||||
if federalStateNameOrEmpty(rowWithoutFS) != "" || landNameOrEmpty(rowWithoutFS) != "" || landISOCodeOrEmpty(rowWithoutFS) != "" {
|
||||
t.Fatalf("expected empty when relation missing")
|
||||
}
|
||||
rowWithoutLand := &icao.ICAO{FederalState: &federal_state.FederalState{Name: "Bayern"}}
|
||||
if federalStateNameOrEmpty(rowWithoutLand) != "Bayern" {
|
||||
t.Fatalf("expected federal state name")
|
||||
}
|
||||
if landNameOrEmpty(rowWithoutLand) != "" || landISOCodeOrEmpty(rowWithoutLand) != "" {
|
||||
t.Fatalf("expected empty land fields")
|
||||
}
|
||||
rowWithLand := &icao.ICAO{
|
||||
FederalState: &federal_state.FederalState{
|
||||
Name: "Bayern",
|
||||
Land: &land.Land{Name: "Germany", LandISOCode: "DE"},
|
||||
},
|
||||
}
|
||||
if federalStateNameOrEmpty(rowWithLand) != "Bayern" || landNameOrEmpty(rowWithLand) != "Germany" || landISOCodeOrEmpty(rowWithLand) != "DE" {
|
||||
t.Fatalf("expected names from relation")
|
||||
}
|
||||
if strings.TrimSpace(federalStateNameOrEmpty(rowWithLand)) == "" {
|
||||
t.Fatalf("sanity check non-empty federal state")
|
||||
}
|
||||
}
|
||||
|
||||
func intPtrICAOHandler(v int) *int { return &v }
|
||||
Reference in New Issue
Block a user