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"}, } { 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) } } }