ProofedMesh/services/admin-api/internal/httpapi/rates_test.go

183 lines
5.5 KiB
Go
Raw Normal View History

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)
}
}