package main import ( "net/http" "net/http/httptest" "slices" "strconv" "testing" "hakurei.app/internal/info" "hakurei.app/internal/rosa" ) // prefix is prepended to every API path. const prefix = "/api/" + ApiVersion + "/" func TestAPIInfo(t *testing.T) { t.Parallel() w := httptest.NewRecorder() handleInfo(w, httptest.NewRequestWithContext( t.Context(), http.MethodGet, prefix+"info", nil, )) resp := w.Result() checkStatus(t, resp, http.StatusOK) checkAPIHeader(t, w.Header()) checkPayload(t, resp, struct { Count int `json:"count"` HakureiVersion string `json:"hakurei_version"` }{int(rosa.PresetUnexportedStart), info.Version()}) } func TestAPIGet(t *testing.T) { t.Parallel() const target = prefix + "get" index := newIndex(t) newRequest := func(suffix string) *httptest.ResponseRecorder { w := httptest.NewRecorder() index.handleGet(w, httptest.NewRequestWithContext( t.Context(), http.MethodGet, target+suffix, nil, )) return w } checkValidate := func(t *testing.T, suffix string, vmin, vmax int, wantErr string) { t.Run("invalid", func(t *testing.T) { t.Parallel() w := newRequest("?" + suffix + "=invalid") resp := w.Result() checkError(t, resp, wantErr, http.StatusBadRequest) }) t.Run("min", func(t *testing.T) { t.Parallel() w := newRequest("?" + suffix + "=" + strconv.Itoa(vmin-1)) resp := w.Result() checkError(t, resp, wantErr, http.StatusBadRequest) w = newRequest("?" + suffix + "=" + strconv.Itoa(vmin)) resp = w.Result() checkStatus(t, resp, http.StatusOK) }) t.Run("max", func(t *testing.T) { t.Parallel() w := newRequest("?" + suffix + "=" + strconv.Itoa(vmax+1)) resp := w.Result() checkError(t, resp, wantErr, http.StatusBadRequest) w = newRequest("?" + suffix + "=" + strconv.Itoa(vmax)) resp = w.Result() checkStatus(t, resp, http.StatusOK) }) } t.Run("limit", func(t *testing.T) { t.Parallel() checkValidate( t, "index=0&sort=0&limit", 1, 100, "limit must be an integer between 1 and 100", ) }) t.Run("index", func(t *testing.T) { t.Parallel() checkValidate( t, "limit=1&sort=0&index", 0, int(rosa.PresetUnexportedStart-1), "index must be an integer between 0 and "+strconv.Itoa(int(rosa.PresetUnexportedStart-1)), ) }) t.Run("sort", func(t *testing.T) { t.Parallel() checkValidate( t, "index=0&limit=1&sort", 0, int(sortOrderEnd), "sort must be an integer between 0 and "+strconv.Itoa(int(sortOrderEnd)), ) }) checkWithSuffix := func(name, suffix string, want []*metadata) { t.Run(name, func(t *testing.T) { t.Parallel() w := newRequest(suffix) resp := w.Result() checkStatus(t, resp, http.StatusOK) checkAPIHeader(t, w.Header()) checkPayloadFunc(t, resp, func(got *struct { Count int `json:"count"` Values []*metadata `json:"values"` }) bool { return got.Count == len(want) && slices.EqualFunc(got.Values, want, func(a, b *metadata) bool { return (a.Version == b.Version || a.Version == rosa.Unversioned || b.Version == rosa.Unversioned) && a.HasReport == b.HasReport && a.Name == b.Name && a.Description == b.Description && a.Website == b.Website }) }) }) } checkWithSuffix("declarationAscending", "?limit=2&index=0&sort=0", []*metadata{ { Metadata: rosa.GetMetadata(0), Version: rosa.Std.Version(0), }, { Metadata: rosa.GetMetadata(1), Version: rosa.Std.Version(1), }, }) checkWithSuffix("declarationAscending offset", "?limit=3&index=5&sort=0", []*metadata{ { Metadata: rosa.GetMetadata(5), Version: rosa.Std.Version(5), }, { Metadata: rosa.GetMetadata(6), Version: rosa.Std.Version(6), }, { Metadata: rosa.GetMetadata(7), Version: rosa.Std.Version(7), }, }) checkWithSuffix("declarationDescending", "?limit=3&index=0&sort=1", []*metadata{ { Metadata: rosa.GetMetadata(rosa.PresetUnexportedStart - 1), Version: rosa.Std.Version(rosa.PresetUnexportedStart - 1), }, { Metadata: rosa.GetMetadata(rosa.PresetUnexportedStart - 2), Version: rosa.Std.Version(rosa.PresetUnexportedStart - 2), }, { Metadata: rosa.GetMetadata(rosa.PresetUnexportedStart - 3), Version: rosa.Std.Version(rosa.PresetUnexportedStart - 3), }, }) checkWithSuffix("declarationDescending offset", "?limit=1&index=37&sort=1", []*metadata{ { Metadata: rosa.GetMetadata(rosa.PresetUnexportedStart - 38), Version: rosa.Std.Version(rosa.PresetUnexportedStart - 38), }, }) }