Files
memby/server/internal/api/admin_preferences_test.go
T

160 lines
6.1 KiB
Go

package api
import (
"encoding/json"
"testing"
"time"
"github.com/ponzischeme89/memby/server/internal/store"
)
func documentJSON(t *testing.T, values map[string]any) []byte {
t.Helper()
raw, err := json.Marshal(normalizePreferences(values))
if err != nil {
t.Fatalf("marshal document: %v", err)
}
return raw
}
// The whole point of the history table: one line per setting, in the words the catalogue
// uses, rather than two JSON documents for somebody to compare by eye.
func TestPreferenceChangesDescribesOneSetting(t *testing.T) {
before := normalizePreferences(map[string]any{"homeCardDensity": "standard"})
after := normalizePreferences(map[string]any{"homeCardDensity": "large"})
changes := preferenceChanges(before, after)
if len(changes) != 1 {
t.Fatalf("got %d changes, want 1: %+v", len(changes), changes)
}
if changes[0].Key != "homeCardDensity" {
t.Errorf("key = %q, want homeCardDensity", changes[0].Key)
}
if changes[0].Before != "Standard" || changes[0].After != "Large" {
t.Errorf("got %q → %q, want Standard → Large", changes[0].Before, changes[0].After)
}
}
// A television pushing back the document it already held is an ordinary event. Listing it
// as a change would fill the history with rows nobody made.
func TestPreferenceChangesIgnoresIdenticalDocuments(t *testing.T) {
document := normalizePreferences(map[string]any{"subtitlesEnabled": false})
if changes := preferenceChanges(document, document); len(changes) != 0 {
t.Fatalf("got %d changes for an unchanged document: %+v", len(changes), changes)
}
}
// A key the catalogue no longer knows is not a change anybody can act on, and a value
// stored before a setting existed must not read as one appearing out of nothing.
func TestPreferenceChangesIgnoresUnknownKeys(t *testing.T) {
before := map[string]any{"retiredSetting": "one"}
after := map[string]any{"retiredSetting": "two"}
if changes := preferenceChanges(before, after); len(changes) != 0 {
t.Fatalf("an unknown key produced changes: %+v", changes)
}
}
// Order is the setting for a multi, so a reordered list is a change even though the same
// rows are ticked.
func TestPreferenceChangesNoticesReorderedRows(t *testing.T) {
before := normalizePreferences(map[string]any{
"homeSections": []any{"continue", "favorites", "latest"}})
after := normalizePreferences(map[string]any{
"homeSections": []any{"favorites", "continue", "latest"}})
changes := preferenceChanges(before, after)
if len(changes) != 1 || changes[0].Key != "homeSections" {
t.Fatalf("got %+v, want one homeSections change", changes)
}
}
func TestPreferenceValueLabelWordsEveryKind(t *testing.T) {
for _, testCase := range []struct {
key string
value any
want string
}{
{"showTitleLogo", true, "On"},
{"showTitleLogo", false, "Off"},
{"homeArtworkStyle", "backdrop", "Backdrops"},
{"seekIntervalSeconds", 30, "30 seconds"},
{"forYouMinutes", 0, "No limit"},
{"homeSections", []string{"latest", "continue"}, "Latest movies, Continue watching"},
{"homeHiddenRows", []string{}, "None"},
{"homeHiddenRows", []string{"recommended"}, "recommended"},
{"profileInitials", "MC", "MC"},
{"profileInitials", "", "Automatic"},
} {
definition, ok := preferenceDefinitionFor(testCase.key)
if !ok {
t.Fatalf("%s is not in the catalogue", testCase.key)
}
got := preferenceValueLabel(definition, normalizePreference(definition, testCase.value))
if got != testCase.want {
t.Errorf("%s = %q, want %q", testCase.key, got, testCase.want)
}
}
}
// The history arrives newest first, so a row's diff is against the element *after* it. Get
// this backwards and every line in the table reads inverted.
func TestAdminRevisionsDiffsAgainstThePrecedingRevision(t *testing.T) {
history := []store.PreferenceRevision{
{Revision: 3, Preferences: documentJSON(t, map[string]any{"homeCardDensity": "large"}),
Source: "admin", CreatedAt: time.Now()},
{Revision: 2, Preferences: documentJSON(t, map[string]any{"homeCardDensity": "compact"}),
Source: "device", DeviceName: "Lounge", CreatedAt: time.Now()},
{Revision: 1, Preferences: documentJSON(t, nil), Source: "device", CreatedAt: time.Now()},
}
rows := adminRevisions(history, 3)
if len(rows) != 3 {
t.Fatalf("got %d rows, want 3", len(rows))
}
if !rows[0].Current {
t.Error("the newest revision is not marked current")
}
if len(rows[0].Changes) != 1 || rows[0].Changes[0].Before != "Compact" ||
rows[0].Changes[0].After != "Large" {
t.Fatalf("r3 changes = %+v, want compact → large", rows[0].Changes)
}
// The oldest entry held has nothing below it, so it is labelled rather than diffed
// against the defaults — which would claim decisions nobody made.
if !rows[2].Initial || len(rows[2].Changes) != 0 {
t.Errorf("r1 initial = %v with %d changes, want true with 0",
rows[2].Initial, len(rows[2].Changes))
}
if rows[0].Author != "Admin console" || rows[1].Author != "Lounge" {
t.Errorf("authors = %q, %q; want Admin console, Lounge", rows[0].Author, rows[1].Author)
}
// A write from before the history recorded a device name must not be attributed to
// an invented one.
if rows[2].Author != "A television" {
t.Errorf("nameless device author = %q, want A television", rows[2].Author)
}
}
// A restore is announced as what it is, and the row it came from stays named.
func TestAdminRevisionsCarriesRestoreProvenance(t *testing.T) {
history := []store.PreferenceRevision{
{Revision: 5, Preferences: documentJSON(t, nil), Source: "admin", RestoredFrom: 2},
{Revision: 4, Preferences: documentJSON(t, nil), Source: "device"},
}
rows := adminRevisions(history, 5)
if rows[0].RestoredFrom != 2 {
t.Errorf("restoredFrom = %d, want 2", rows[0].RestoredFrom)
}
}
// "Behind" is what the page colours by, and a set holding something newer than the current
// revision is an anomaly rather than a negative number to render.
func TestBehindByNeverGoesNegative(t *testing.T) {
for _, testCase := range []struct{ current, held, want int64 }{
{7, 7, 0}, {7, 4, 3}, {7, 9, 0}, {7, 0, 7},
} {
if got := behindBy(testCase.current, testCase.held); got != testCase.want {
t.Errorf("behindBy(%d, %d) = %d, want %d",
testCase.current, testCase.held, got, testCase.want)
}
}
}