182 lines
5.5 KiB
Go
182 lines
5.5 KiB
Go
package httpapi
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"basicstack.de/proofedmesh/services/admin-api/internal/store"
|
|
)
|
|
|
|
func newDeps(t *testing.T) Dependencies {
|
|
t.Helper()
|
|
return Dependencies{
|
|
Store: store.NewMemory(),
|
|
Now: func() time.Time { return time.Date(2026, 10, 25, 12, 0, 0, 0, time.UTC) },
|
|
}
|
|
}
|
|
|
|
func newMux(deps Dependencies) http.Handler {
|
|
mux := http.NewServeMux()
|
|
Mount(mux, deps)
|
|
// inject a principal for tests
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
ctx := WithPrincipal(r.Context(), "test-admin")
|
|
mux.ServeHTTP(w, r.WithContext(ctx))
|
|
})
|
|
}
|
|
|
|
func TestList_Empty(t *testing.T) {
|
|
srv := httptest.NewServer(newMux(newDeps(t)))
|
|
defer srv.Close()
|
|
resp, err := http.Get(srv.URL + "/admin/per-diem-rates/")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != 200 {
|
|
t.Fatalf("status %d", resp.StatusCode)
|
|
}
|
|
}
|
|
|
|
func TestCreateAndGet_JSON(t *testing.T) {
|
|
deps := newDeps(t)
|
|
srv := httptest.NewServer(newMux(deps))
|
|
defer srv.Close()
|
|
body := `{
|
|
"country":"DE","currency":"EUR",
|
|
"full_day":"28.00","partial_day_min8h":"14.00",
|
|
"arrival_day":"14.00","departure_day":"14.00",
|
|
"valid_from":"2026-01-01T00:00:00Z",
|
|
"source_regulation":"BMF 2026"
|
|
}`
|
|
req, _ := http.NewRequest("POST", srv.URL+"/admin/per-diem-rates/", strings.NewReader(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusCreated {
|
|
t.Fatalf("expected 201, got %d", resp.StatusCode)
|
|
}
|
|
var out map[string]map[string]any
|
|
_ = json.NewDecoder(resp.Body).Decode(&out)
|
|
rateID, _ := out["rate"]["rate_id"].(string)
|
|
if rateID == "" {
|
|
t.Fatalf("no rate_id in response: %v", out)
|
|
}
|
|
|
|
// Fetch it back
|
|
resp2, err := http.Get(srv.URL + "/admin/per-diem-rates/" + rateID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer resp2.Body.Close()
|
|
if resp2.StatusCode != 200 {
|
|
t.Fatalf("get status %d", resp2.StatusCode)
|
|
}
|
|
}
|
|
|
|
func TestCreate_InvalidBody_Rejected(t *testing.T) {
|
|
srv := httptest.NewServer(newMux(newDeps(t)))
|
|
defer srv.Close()
|
|
body := `{"country":"XYZ","currency":"EUR","full_day":"28.00","partial_day_min8h":"14.00","arrival_day":"14.00","departure_day":"14.00","valid_from":"2026-01-01T00:00:00Z","source_regulation":"BMF 2026"}`
|
|
req, _ := http.NewRequest("POST", srv.URL+"/admin/per-diem-rates/", strings.NewReader(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != 400 {
|
|
t.Fatalf("expected 400, got %d", resp.StatusCode)
|
|
}
|
|
}
|
|
|
|
func TestGet_NotFound(t *testing.T) {
|
|
srv := httptest.NewServer(newMux(newDeps(t)))
|
|
defer srv.Close()
|
|
resp, err := http.Get(srv.URL + "/admin/per-diem-rates/PDR-999")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusNotFound {
|
|
t.Fatalf("expected 404, got %d", resp.StatusCode)
|
|
}
|
|
}
|
|
|
|
func TestRetire_UpdatesValidTo(t *testing.T) {
|
|
deps := newDeps(t)
|
|
srv := httptest.NewServer(newMux(deps))
|
|
defer srv.Close()
|
|
|
|
createBody := `{"country":"DE","currency":"EUR","full_day":"28.00","partial_day_min8h":"14.00","arrival_day":"14.00","departure_day":"14.00","valid_from":"2026-01-01T00:00:00Z","source_regulation":"BMF 2026"}`
|
|
req, _ := http.NewRequest("POST", srv.URL+"/admin/per-diem-rates/", strings.NewReader(createBody))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer resp.Body.Close()
|
|
var out map[string]map[string]any
|
|
_ = json.NewDecoder(resp.Body).Decode(&out)
|
|
rateID := out["rate"]["rate_id"].(string)
|
|
|
|
retireBody := `{"valid_to":"2026-06-30"}`
|
|
req2, _ := http.NewRequest("POST", srv.URL+"/admin/per-diem-rates/"+rateID+"/retire", strings.NewReader(retireBody))
|
|
req2.Header.Set("Content-Type", "application/json")
|
|
resp2, err := http.DefaultClient.Do(req2)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer resp2.Body.Close()
|
|
if resp2.StatusCode != 200 {
|
|
t.Fatalf("retire: expected 200, got %d", resp2.StatusCode)
|
|
}
|
|
var out2 map[string]map[string]any
|
|
_ = json.NewDecoder(resp2.Body).Decode(&out2)
|
|
if _, ok := out2["rate"]["valid_to"]; !ok {
|
|
t.Fatal("expected valid_to to be set after retire")
|
|
}
|
|
}
|
|
|
|
func TestOverlap_Rejected(t *testing.T) {
|
|
srv := httptest.NewServer(newMux(newDeps(t)))
|
|
defer srv.Close()
|
|
|
|
body := func(validFrom string, validTo *string) string {
|
|
base := `{"country":"DE","currency":"EUR","full_day":"28.00","partial_day_min8h":"14.00","arrival_day":"14.00","departure_day":"14.00","valid_from":"` + validFrom + `T00:00:00Z","source_regulation":"BMF 2026"`
|
|
if validTo != nil {
|
|
base += `,"valid_to":"` + *validTo + `T00:00:00Z"`
|
|
}
|
|
return base + "}"
|
|
}
|
|
|
|
vt := "2026-12-31"
|
|
req1, _ := http.NewRequest("POST", srv.URL+"/admin/per-diem-rates/", strings.NewReader(body("2026-01-01", &vt)))
|
|
req1.Header.Set("Content-Type", "application/json")
|
|
resp1, err := http.DefaultClient.Do(req1)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer resp1.Body.Close()
|
|
if resp1.StatusCode != http.StatusCreated {
|
|
t.Fatalf("first create expected 201, got %d", resp1.StatusCode)
|
|
}
|
|
|
|
req2, _ := http.NewRequest("POST", srv.URL+"/admin/per-diem-rates/", strings.NewReader(body("2026-12-31", nil)))
|
|
req2.Header.Set("Content-Type", "application/json")
|
|
resp2, err := http.DefaultClient.Do(req2)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer resp2.Body.Close()
|
|
if resp2.StatusCode != http.StatusConflict {
|
|
t.Fatalf("overlapping create: expected 409, got %d", resp2.StatusCode)
|
|
}
|
|
}
|