package webutils_test import ( "encoding/json" "io" "net/http" "net/http/httptest" "testing" webutils "git.mstar.dev/mstar/goutils/http" "git.mstar.dev/mstar/goutils/maputils" "git.mstar.dev/mstar/goutils/other" "git.mstar.dev/mstar/goutils/sliceutils" ) func TestHttpErr(t *testing.T) { recorder := httptest.NewRecorder() err := webutils.HttpErr(recorder, 1, "some error", 500) if err != nil { t.Fatalf("Expected no error, got %v", err) } res := recorder.Result() if res.StatusCode != 500 { t.Fatalf("Expected status code 500, got %v", res.StatusCode) } body := string(other.Must(io.ReadAll(res.Body))) expected := `{"id": 1, "message": "some error"}` if body != expected { t.Fatalf("Expected body %q, got %q", expected, body) } } func TestProblemDetailsStatusOnly(t *testing.T) { recorder := httptest.NewRecorder() err := webutils.ProblemDetailsStatusOnly(recorder, 500) if err != nil { t.Fatalf("expected no error, got %v", err) } res := recorder.Result() if ct := res.Header.Get("Content-Type"); ct != "application/problem+json" { t.Fatalf("Expected content type to be \"application/problem+json\" got %v", ct) } body := string(other.Must(io.ReadAll(res.Body))) data := map[string]any{} err = json.Unmarshal([]byte(body), &data) if err != nil { t.Fatalf("response body is not json: %v", err) } if dt, ok := data["type"].(string); !ok || dt != "about:blank" { t.Fatalf(`Expected key "type" as "about:blank", got %v`, dt) } if dt, ok := data["title"].(string); !ok || dt != http.StatusText(500) { t.Fatalf(`Expected key "title" as "%v", got %v`, http.StatusText(500), dt) } if dt, ok := data["status"].(float64); !ok || dt != 500 { t.Fatalf(`Expected key "status" as 500, got %v`, dt) } if dt, ok := data["reference"].(string); !ok || dt != "RFC 9457" { t.Fatalf(`Expected key "reference" as "RFC 9457", got %v`, dt) } } func TestProblemDetailsNoDetailsOrExtras(t *testing.T) { recorder := httptest.NewRecorder() err := webutils.ProblemDetails( recorder, 500, "error-type", "error-title", nil, nil, ) if err != nil { t.Fatalf("expected no error, got %v", err) } res := recorder.Result() if ct := res.Header.Get("Content-Type"); ct != "application/problem+json" { t.Fatalf("Expected content type to be \"application/problem+json\" got %v", ct) } body := string(other.Must(io.ReadAll(res.Body))) data := map[string]any{} err = json.Unmarshal([]byte(body), &data) if err != nil { t.Fatalf("response body is not json: %v", err) } expectedFields := []string{"type", "title", "status"} gotFields := maputils.KeysFromMap(data) if !sliceutils.CompareUnordered(gotFields, expectedFields) { t.Fatalf("Expected fields %v, got %v", expectedFields, gotFields) } if dt, ok := data["type"].(string); !ok || dt != "error-type" { t.Fatalf(`Expected key "type" as "error-type", got %v`, dt) } if dt, ok := data["title"].(string); !ok || dt != "error-title" { t.Fatalf(`Expected key "title" as "error-title", got %v`, dt) } if dt, ok := data["status"].(float64); !ok || dt != 500 { t.Fatalf(`Expected key "status" as 500, got %v`, dt) } } func TestProblemDetailsNoDetailsWithExtras(t *testing.T) { recorder := httptest.NewRecorder() err := webutils.ProblemDetails( recorder, 500, "error-type", "error-title", nil, map[string]any{"foo": "bar"}, ) if err != nil { t.Fatalf("expected no error, got %v", err) } res := recorder.Result() if ct := res.Header.Get("Content-Type"); ct != "application/problem+json" { t.Fatalf("Expected content type to be \"application/problem+json\" got %v", ct) } body := string(other.Must(io.ReadAll(res.Body))) data := map[string]any{} err = json.Unmarshal([]byte(body), &data) if err != nil { t.Fatalf("response body is not json: %v", err) } expectedFields := []string{"type", "title", "status", "foo"} gotFields := maputils.KeysFromMap(data) if !sliceutils.CompareUnordered(gotFields, expectedFields) { t.Fatalf("Expected fields %v, got %v", expectedFields, gotFields) } if dt, ok := data["type"].(string); !ok || dt != "error-type" { t.Fatalf(`Expected key "type" as "error-type", got %v`, dt) } if dt, ok := data["title"].(string); !ok || dt != "error-title" { t.Fatalf(`Expected key "title" as "error-title", got %v`, dt) } if dt, ok := data["status"].(float64); !ok || dt != 500 { t.Fatalf(`Expected key "status" as 500, got %v`, dt) } if dt, ok := data["foo"].(string); !ok || dt != "bar" { t.Fatalf(`Expected key "foo" as "bar", got %v`, dt) } } func TestProblemDetailsWithDetailsNoExtras(t *testing.T) { recorder := httptest.NewRecorder() err := webutils.ProblemDetails( recorder, 500, "error-type", "error-title", other.IntoPointer("error-details"), nil, ) if err != nil { t.Fatalf("expected no error, got %v", err) } res := recorder.Result() if ct := res.Header.Get("Content-Type"); ct != "application/problem+json" { t.Fatalf("Expected content type to be \"application/problem+json\" got %v", ct) } body := string(other.Must(io.ReadAll(res.Body))) data := map[string]any{} err = json.Unmarshal([]byte(body), &data) if err != nil { t.Fatalf("response body is not json: %v", err) } expectedFields := []string{"type", "title", "status", "detail"} gotFields := maputils.KeysFromMap(data) if !sliceutils.CompareUnordered(gotFields, expectedFields) { t.Fatalf("Expected fields %v, got %v", expectedFields, gotFields) } if dt, ok := data["type"].(string); !ok || dt != "error-type" { t.Fatalf(`Expected key "type" as "error-type", got %v`, dt) } if dt, ok := data["title"].(string); !ok || dt != "error-title" { t.Fatalf(`Expected key "title" as "error-title", got %v`, dt) } if dt, ok := data["status"].(float64); !ok || dt != 500 { t.Fatalf(`Expected key "status" as 500, got %v`, dt) } if dt, ok := data["detail"].(string); !ok || dt != "error-details" { t.Fatalf(`Expected key "detail" as "error-details", got %v`, dt) } } func TestProblemDetailsWithDetailsWithExtras(t *testing.T) { recorder := httptest.NewRecorder() err := webutils.ProblemDetails( recorder, 500, "error-type", "error-title", other.IntoPointer("error-details"), map[string]any{"foo": "bar", "type": "should not be set in the end"}, ) if err != nil { t.Fatalf("expected no error, got %v", err) } res := recorder.Result() if ct := res.Header.Get("Content-Type"); ct != "application/problem+json" { t.Fatalf("Expected content type to be \"application/problem+json\" got %v", ct) } body := string(other.Must(io.ReadAll(res.Body))) data := map[string]any{} err = json.Unmarshal([]byte(body), &data) if err != nil { t.Fatalf("response body is not json: %v", err) } expectedFields := []string{"type", "title", "status", "detail", "foo"} gotFields := maputils.KeysFromMap(data) if !sliceutils.CompareUnordered(gotFields, expectedFields) { t.Fatalf("Expected fields %v, got %v", expectedFields, gotFields) } if dt, ok := data["type"].(string); !ok || dt != "error-type" { t.Fatalf(`Expected key "type" as "error-type", got %v`, dt) } if dt, ok := data["title"].(string); !ok || dt != "error-title" { t.Fatalf(`Expected key "title" as "error-title", got %v`, dt) } if dt, ok := data["status"].(float64); !ok || dt != 500 { t.Fatalf(`Expected key "status" as 500, got %v`, dt) } if dt, ok := data["detail"].(string); !ok || dt != "error-details" { t.Fatalf(`Expected key "detail" as "error-details", got %v`, dt) } if dt, ok := data["foo"].(string); !ok || dt != "bar" { t.Fatalf(`Expected key "foo" as "bar", got %v`, dt) } }