f99010fae1
CI / lint (push) Failing after 1s
CI / frontend (push) Failing after 1s
CI / scripts (push) Failing after 1s
CI / Go Test (ubuntu-latest) (push) Failing after 0s
CI / frontend-node-25 (push) Failing after 1s
CI / docs (push) Failing after 0s
CI / coverage (push) Failing after 0s
CI / e2e (push) Failing after 0s
Docker / build-and-push (push) Failing after 1s
CI / integration (push) Failing after 4m43s
CI / Go Test (windows-latest) (push) Has been cancelled
CI / Desktop Unit Tests (Windows) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux (arm64)) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Windows) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (aarch64)) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (x86_64)) (push) Has been cancelled
121 lines
4.0 KiB
Go
121 lines
4.0 KiB
Go
package server_test
|
|
|
|
import (
|
|
"net/http"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"go.kenn.io/agentsview/internal/db"
|
|
)
|
|
|
|
// sessionListPage decodes the list response including the pagination cursor,
|
|
// which sessionListResponse omits.
|
|
type sessionListPage struct {
|
|
Sessions []db.Session `json:"sessions"`
|
|
NextCursor string `json:"next_cursor"`
|
|
Total int `json:"total"`
|
|
}
|
|
|
|
// seedForMultiKey inserts three sessions whose (messages, started) values make
|
|
// a two-key sort with mixed directions observable.
|
|
func seedForMultiKey(t *testing.T, te *testEnv) {
|
|
t.Helper()
|
|
te.seedSession(t, "mk-a", "p", 1, func(s *db.Session) {
|
|
s.StartedAt = new("2024-03-01T00:00:00Z")
|
|
})
|
|
te.seedSession(t, "mk-b", "p", 1, func(s *db.Session) {
|
|
s.StartedAt = new("2024-01-01T00:00:00Z")
|
|
})
|
|
te.seedSession(t, "mk-c", "p", 2, func(s *db.Session) {
|
|
s.StartedAt = new("2024-02-01T00:00:00Z")
|
|
})
|
|
}
|
|
|
|
// TestListSessions_OrderBy_MultiKey exercises the comma-separated key:dir spec
|
|
// and the descending fallback for unsuffixed keys over HTTP.
|
|
func TestListSessions_OrderBy_MultiKey(t *testing.T) {
|
|
t.Run("explicit per-key directions", func(t *testing.T) {
|
|
te := setup(t)
|
|
seedForMultiKey(t, te)
|
|
// messages asc, then started desc.
|
|
w := te.get(t, "/api/v1/sessions?order_by=messages:asc,started:desc")
|
|
assertStatus(t, w, http.StatusOK)
|
|
resp := decode[sessionListResponse](t, w)
|
|
assert.Equal(t, []string{"mk-a", "mk-b", "mk-c"}, orderedSessionIDs(resp.Sessions))
|
|
})
|
|
|
|
t.Run("descending fallback fills unsuffixed key", func(t *testing.T) {
|
|
te := setup(t)
|
|
seedForMultiKey(t, te)
|
|
// messages (bare -> descending via param), then started:asc explicit.
|
|
w := te.get(t, "/api/v1/sessions?order_by=messages,started:asc&descending=true")
|
|
assertStatus(t, w, http.StatusOK)
|
|
resp := decode[sessionListResponse](t, w)
|
|
assert.Equal(t, []string{"mk-c", "mk-b", "mk-a"}, orderedSessionIDs(resp.Sessions))
|
|
})
|
|
}
|
|
|
|
// TestListSessions_OrderBy_MultiKeyPagination walks a multi-key sort one page at
|
|
// a time and asserts the keyset cursor reproduces the full-listing order.
|
|
func TestListSessions_OrderBy_MultiKeyPagination(t *testing.T) {
|
|
te := setup(t)
|
|
seedForMultiKey(t, te)
|
|
|
|
full := te.get(t, "/api/v1/sessions?order_by=messages:asc,started:desc")
|
|
assertStatus(t, full, http.StatusOK)
|
|
want := orderedSessionIDs(decode[sessionListPage](t, full).Sessions)
|
|
|
|
var got []string
|
|
cursor := ""
|
|
for i := 0; i < len(want)+1; i++ {
|
|
url := "/api/v1/sessions?order_by=messages:asc,started:desc&limit=1"
|
|
if cursor != "" {
|
|
url += "&cursor=" + cursor
|
|
}
|
|
w := te.get(t, url)
|
|
assertStatus(t, w, http.StatusOK)
|
|
resp := decode[sessionListPage](t, w)
|
|
got = append(got, orderedSessionIDs(resp.Sessions)...)
|
|
if resp.NextCursor == "" {
|
|
break
|
|
}
|
|
cursor = resp.NextCursor
|
|
}
|
|
assert.Equal(t, want, got)
|
|
}
|
|
|
|
// TestListSessions_OrderBy_Invalid rejects malformed order_by specs with 400.
|
|
func TestListSessions_OrderBy_Invalid(t *testing.T) {
|
|
te := setup(t)
|
|
te.seedSession(t, "s1", "p", 3)
|
|
|
|
for _, spec := range []string{
|
|
"bogus", // unknown key
|
|
"messages,bogus", // unknown key in list
|
|
"messages:up", // bad direction token
|
|
"messages,messages", // duplicate key
|
|
"messages,,started", // empty term
|
|
} {
|
|
t.Run(spec, func(t *testing.T) {
|
|
w := te.get(t, "/api/v1/sessions?order_by="+spec)
|
|
assertStatus(t, w, http.StatusBadRequest)
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestSidebarIndex_OrderByInvalid confirms the sidebar-index route, which shares
|
|
// the session-filter input struct, also rejects a malformed order_by rather than
|
|
// silently accepting and ignoring it (the dropped enum used to guard both routes).
|
|
func TestSidebarIndex_OrderByInvalid(t *testing.T) {
|
|
te := setup(t)
|
|
te.seedSession(t, "s1", "p", 3)
|
|
|
|
w := te.get(t, "/api/v1/sessions/sidebar-index?order_by=bogus")
|
|
assertStatus(t, w, http.StatusBadRequest)
|
|
|
|
// A valid spec is still accepted even though the sidebar applies its own order.
|
|
w = te.get(t, "/api/v1/sessions/sidebar-index?order_by=messages:desc")
|
|
assertStatus(t, w, http.StatusOK)
|
|
}
|