aboutsummaryrefslogtreecommitdiffhomepage
path: root/cmd/mbf/internal/pkgserver/api_test.go
diff options
context:
space:
mode:
authormae <mae@maestoso.online>2026-03-04 22:50:58 -0600
committerOphestra <cat@gensokyo.uk>2026-05-02 05:05:56 +0900
commit1d5d063d6a443c9d4a1206d2743e4824411da956 (patch)
treeb32105cf3896f394be91099cfbfb67785de39385 /cmd/mbf/internal/pkgserver/api_test.go
parente61628a34ecccae0feb5e059a2f808977261fa0e (diff)
cmd/mbf: package status dashboard
This displays package metadata with optional status from a report.
Diffstat (limited to 'cmd/mbf/internal/pkgserver/api_test.go')
-rw-r--r--cmd/mbf/internal/pkgserver/api_test.go181
1 files changed, 181 insertions, 0 deletions
diff --git a/cmd/mbf/internal/pkgserver/api_test.go b/cmd/mbf/internal/pkgserver/api_test.go
new file mode 100644
index 00000000..8449c234
--- /dev/null
+++ b/cmd/mbf/internal/pkgserver/api_test.go
@@ -0,0 +1,181 @@
+package pkgserver
+
+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 {
+ Values []*metadata `json:"values"`
+ }) bool {
+ return 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=1&sort=0", []*metadata{
+ {
+ Metadata: rosa.GetMetadata(1),
+ Version: rosa.Std.Version(1),
+ },
+ {
+ Metadata: rosa.GetMetadata(2),
+ Version: rosa.Std.Version(2),
+ },
+ })
+ 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),
+ },
+ })
+}