114 lines
3.4 KiB
Go
114 lines
3.4 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
"testing"
|
|
"testing/iotest"
|
|
)
|
|
|
|
func snapshotResponse(target, body string) *http.Response {
|
|
request, _ := http.NewRequest(http.MethodGet, target, nil)
|
|
return &http.Response{
|
|
StatusCode: http.StatusOK,
|
|
Header: make(http.Header),
|
|
Body: io.NopCloser(strings.NewReader(body)),
|
|
Request: request,
|
|
}
|
|
}
|
|
|
|
func TestBoundSessionSnapshotKeepsOnlyRecentMessages(t *testing.T) {
|
|
messages := make([]map[string]int, 30)
|
|
for index := range messages {
|
|
messages[index] = map[string]int{"index": index}
|
|
}
|
|
body, _ := json.Marshal(map[string]any{
|
|
"session_id": "resolved", "messages": messages,
|
|
})
|
|
response := snapshotResponse(
|
|
"http://tenant/api/sessions/root/messages?limit=24&hermes_fallback=1",
|
|
string(body),
|
|
)
|
|
response.Header.Set("ETag", "stale")
|
|
if err := boundSessionSnapshot(response); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var bounded sessionSnapshot
|
|
if err := json.NewDecoder(response.Body).Decode(&bounded); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(bounded.Messages) != 24 || bounded.TotalMessages != 30 {
|
|
t.Fatalf("snapshot was not bounded: %#v", bounded)
|
|
}
|
|
var first map[string]int
|
|
if err := json.Unmarshal(bounded.Messages[0], &first); err != nil || first["index"] != 6 {
|
|
t.Fatalf("snapshot did not retain the recent tail: %#v, %v", first, err)
|
|
}
|
|
if response.Header.Get("Cache-Control") != "no-store" || response.Header.Get("ETag") != "" {
|
|
t.Fatal("bounded snapshot retained cache metadata")
|
|
}
|
|
}
|
|
|
|
func TestBoundSessionSnapshotPreservesLargerReportedTotal(t *testing.T) {
|
|
response := snapshotResponse(
|
|
"http://tenant/api/sessions/root/messages?hermes_fallback=1",
|
|
`{"session_id":"leaf","messages":[],"total_messages":100}`,
|
|
)
|
|
if err := boundSessionSnapshot(response); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var bounded sessionSnapshot
|
|
if err := json.NewDecoder(response.Body).Decode(&bounded); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if bounded.SessionID != "leaf" || bounded.TotalMessages != 100 {
|
|
t.Fatalf("reported total was lost: %#v", bounded)
|
|
}
|
|
}
|
|
|
|
func TestBoundSessionSnapshotFailsClosedOnMalformedOrOversizedBody(t *testing.T) {
|
|
for name, body := range map[string]string{
|
|
"malformed": `{`,
|
|
"oversized": strings.Repeat("x", sessionSnapshotBytes+1),
|
|
} {
|
|
t.Run(name, func(t *testing.T) {
|
|
response := snapshotResponse(
|
|
"http://tenant/api/sessions/root/messages?hermes_fallback=1",
|
|
body,
|
|
)
|
|
if err := boundSessionSnapshot(response); err == nil {
|
|
t.Fatal("unsafe snapshot was accepted")
|
|
}
|
|
})
|
|
}
|
|
response := snapshotResponse(
|
|
"http://tenant/api/sessions/root/messages?hermes_fallback=1", `{}`,
|
|
)
|
|
response.Body = io.NopCloser(iotest.ErrReader(errors.New("read failed")))
|
|
if err := boundSessionSnapshot(response); err == nil {
|
|
t.Fatal("snapshot body read failure was ignored")
|
|
}
|
|
}
|
|
|
|
func TestBoundSessionSnapshotLeavesNativeAndErrorResponsesUntouched(t *testing.T) {
|
|
for _, response := range []*http.Response{
|
|
{StatusCode: http.StatusOK},
|
|
snapshotResponse("http://tenant/api/sessions/root/messages", `{}`),
|
|
snapshotResponse("http://tenant/api/sessions/root", `{}`),
|
|
} {
|
|
if err := boundSessionSnapshot(response); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
errorResponse := snapshotResponse(
|
|
"http://tenant/api/sessions/root/messages?hermes_fallback=1", `{}`,
|
|
)
|
|
errorResponse.StatusCode = http.StatusNotFound
|
|
if err := boundSessionSnapshot(errorResponse); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|