initial commit
Some checks failed
ci / go (push) Has been cancelled
ci / admin-ui (push) Has been cancelled
ci / runtime-sdk (push) Has been cancelled
ci / helm (push) Has been cancelled
ci / openapi (push) Has been cancelled
ci / container (push) Has been cancelled
ci / release-config (push) Has been cancelled
ci / shared styling (push) Has been cancelled
ci / security (push) Has been cancelled
ci / all green (push) Has been cancelled
Some checks failed
ci / go (push) Has been cancelled
ci / admin-ui (push) Has been cancelled
ci / runtime-sdk (push) Has been cancelled
ci / helm (push) Has been cancelled
ci / openapi (push) Has been cancelled
ci / container (push) Has been cancelled
ci / release-config (push) Has been cancelled
ci / shared styling (push) Has been cancelled
ci / security (push) Has been cancelled
ci / all green (push) Has been cancelled
This commit is contained in:
commit
951dd8d26b
175 changed files with 50041 additions and 0 deletions
262
internal/store/user_preferences_test.go
Normal file
262
internal/store/user_preferences_test.go
Normal file
|
|
@ -0,0 +1,262 @@
|
|||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/urpi/go-bananas/internal/domain"
|
||||
)
|
||||
|
||||
func TestPreferencesDefaultToNoChoice(t *testing.T) {
|
||||
s := newTestStore(t)
|
||||
ctx := context.Background()
|
||||
|
||||
// A subject nobody has saved anything for reads as empty rather than failing,
|
||||
// because empty is the documented "no choice" state.
|
||||
prefs, err := s.Preferences(ctx, "nobody")
|
||||
if err != nil {
|
||||
t.Fatalf("read preferences: %v", err)
|
||||
}
|
||||
if prefs.SelectedTheme != "" || prefs.SelectedAppearance != "" {
|
||||
t.Fatalf("preferences = %+v, want the zero value", prefs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPreferencesRoundTrip(t *testing.T) {
|
||||
s := newTestStore(t)
|
||||
ctx := context.Background()
|
||||
published := publishTheme(t, s, "midnight")
|
||||
|
||||
stored, err := s.SetPreferences(ctx, "user-1", domain.UserPreferences{
|
||||
SelectedTheme: published.Slug,
|
||||
SelectedAppearance: domain.AppearanceDark,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("set preferences: %v", err)
|
||||
}
|
||||
if stored.SelectedTheme != "midnight" || stored.SelectedAppearance != domain.AppearanceDark {
|
||||
t.Fatalf("stored = %+v", stored)
|
||||
}
|
||||
|
||||
read, err := s.Preferences(ctx, "user-1")
|
||||
if err != nil {
|
||||
t.Fatalf("read preferences: %v", err)
|
||||
}
|
||||
if read != stored {
|
||||
t.Fatalf("read = %+v, want %+v", read, stored)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPreferencesCanBeClearedAndOverwritten(t *testing.T) {
|
||||
s := newTestStore(t)
|
||||
ctx := context.Background()
|
||||
publishTheme(t, s, "midnight")
|
||||
|
||||
if _, err := s.SetPreferences(ctx, "user-1", domain.UserPreferences{
|
||||
SelectedTheme: "midnight",
|
||||
SelectedAppearance: domain.AppearanceLight,
|
||||
}); err != nil {
|
||||
t.Fatalf("set preferences: %v", err)
|
||||
}
|
||||
|
||||
// Writing again replaces rather than accumulating.
|
||||
if _, err := s.SetPreferences(ctx, "user-1", domain.UserPreferences{
|
||||
SelectedTheme: "midnight",
|
||||
SelectedAppearance: domain.AppearanceDark,
|
||||
}); err != nil {
|
||||
t.Fatalf("overwrite preferences: %v", err)
|
||||
}
|
||||
read, err := s.Preferences(ctx, "user-1")
|
||||
if err != nil {
|
||||
t.Fatalf("read preferences: %v", err)
|
||||
}
|
||||
if read.SelectedAppearance != domain.AppearanceDark {
|
||||
t.Fatalf("appearance = %q, want dark", read.SelectedAppearance)
|
||||
}
|
||||
|
||||
// An empty theme is the absence of a choice and is storable.
|
||||
if _, err := s.SetPreferences(ctx, "user-1", domain.UserPreferences{}); err != nil {
|
||||
t.Fatalf("clear by writing empty: %v", err)
|
||||
}
|
||||
read, err = s.Preferences(ctx, "user-1")
|
||||
if err != nil {
|
||||
t.Fatalf("read preferences: %v", err)
|
||||
}
|
||||
if read.SelectedTheme != "" {
|
||||
t.Fatalf("theme = %q, want empty", read.SelectedTheme)
|
||||
}
|
||||
|
||||
removed, err := s.ClearPreferences(ctx, "user-1")
|
||||
if err != nil {
|
||||
t.Fatalf("clear preferences: %v", err)
|
||||
}
|
||||
if !removed {
|
||||
t.Error("clear reported no row removed")
|
||||
}
|
||||
}
|
||||
|
||||
// TestPreferencesRejectUnknownAndDraftThemes covers the two ways a name can fail:
|
||||
// it does not exist, or it exists but runtime consumers cannot load it.
|
||||
func TestPreferencesRejectUnknownAndDraftThemes(t *testing.T) {
|
||||
s := newTestStore(t)
|
||||
ctx := context.Background()
|
||||
|
||||
if err := s.Create(ctx, sampleTheme("draft-one"), ""); err != nil {
|
||||
t.Fatalf("create theme: %v", err)
|
||||
}
|
||||
|
||||
for _, slug := range []string{"no-such-theme", "draft-one"} {
|
||||
_, err := s.SetPreferences(ctx, "user-1", domain.UserPreferences{
|
||||
SelectedTheme: slug,
|
||||
SelectedAppearance: domain.AppearanceLight,
|
||||
})
|
||||
if !errors.Is(err, domain.ErrInvalid) {
|
||||
t.Errorf("SetPreferences(%q) = %v, want ErrInvalid", slug, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestPreferencesFallBackWhenTheThemeDisappears is the reason Preferences resolves
|
||||
// the name on read: a theme that is later deleted must not leave somebody pinned to
|
||||
// a selection the UI cannot load.
|
||||
func TestPreferencesFallBackWhenTheThemeDisappears(t *testing.T) {
|
||||
s := newTestStore(t)
|
||||
ctx := context.Background()
|
||||
theme := publishTheme(t, s, "midnight")
|
||||
|
||||
if _, err := s.SetPreferences(ctx, "user-1", domain.UserPreferences{
|
||||
SelectedTheme: theme.Slug,
|
||||
SelectedAppearance: domain.AppearanceDark,
|
||||
}); err != nil {
|
||||
t.Fatalf("set preferences: %v", err)
|
||||
}
|
||||
if err := s.Delete(ctx, theme.Slug); err != nil {
|
||||
t.Fatalf("delete theme: %v", err)
|
||||
}
|
||||
|
||||
read, err := s.Preferences(ctx, "user-1")
|
||||
if err != nil {
|
||||
t.Fatalf("read preferences: %v", err)
|
||||
}
|
||||
if read.SelectedTheme != "" || read.SelectedAppearance != "" {
|
||||
t.Fatalf("preferences = %+v, want the zero value once the theme is gone", read)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPreferencesRejectMalformedInput(t *testing.T) {
|
||||
s := newTestStore(t)
|
||||
ctx := context.Background()
|
||||
publishTheme(t, s, "midnight")
|
||||
|
||||
rejected := map[string]domain.UserPreferences{
|
||||
"uppercase slug": {SelectedTheme: "Midnight"},
|
||||
"slug with a space": {SelectedTheme: "mid night"},
|
||||
"leading dash": {SelectedTheme: "-midnight"},
|
||||
"unknown appearance": {
|
||||
SelectedTheme: "midnight",
|
||||
SelectedAppearance: "sepia",
|
||||
},
|
||||
}
|
||||
for name, prefs := range rejected {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
if _, err := s.SetPreferences(ctx, "user-1", prefs); !errors.Is(err, domain.ErrInvalid) {
|
||||
t.Fatalf("SetPreferences(%+v) = %v, want ErrInvalid", prefs, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Clearing with an empty theme is valid, and an appearance sent alongside it
|
||||
// is dropped rather than stored as a scheme with nothing to apply it to.
|
||||
stored, err := s.SetPreferences(ctx, "user-1", domain.UserPreferences{
|
||||
SelectedTheme: "",
|
||||
SelectedAppearance: domain.AppearanceDark,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("clearing with a stray appearance: %v", err)
|
||||
}
|
||||
if stored.SelectedAppearance != "" {
|
||||
t.Fatalf("stored = %+v, want the appearance dropped", stored)
|
||||
}
|
||||
|
||||
if _, err := s.Preferences(ctx, ""); !errors.Is(err, domain.ErrInvalid) {
|
||||
t.Errorf("Preferences(\"\") = %v, want ErrInvalid", err)
|
||||
}
|
||||
if _, err := s.SetPreferences(ctx, "", domain.UserPreferences{}); !errors.Is(err, domain.ErrInvalid) {
|
||||
t.Errorf("SetPreferences with empty subject = %v, want ErrInvalid", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestPreferencesSurviveLogin matters because UpsertUser rewrites the users row on
|
||||
// every login, which is exactly why preferences are not columns on it.
|
||||
func TestPreferencesSurviveLogin(t *testing.T) {
|
||||
s := newTestStore(t)
|
||||
ctx := context.Background()
|
||||
publishTheme(t, s, "midnight")
|
||||
|
||||
if _, err := s.SetPreferences(ctx, "user-1", domain.UserPreferences{
|
||||
SelectedTheme: "midnight",
|
||||
SelectedAppearance: domain.AppearanceDark,
|
||||
}); err != nil {
|
||||
t.Fatalf("set preferences: %v", err)
|
||||
}
|
||||
for range 2 {
|
||||
if err := s.UpsertUser(ctx, &domain.Identity{
|
||||
Subject: "user-1", Email: "u@example.test", Roles: []domain.Role{domain.RoleViewer},
|
||||
}); err != nil {
|
||||
t.Fatalf("upsert user: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
read, err := s.Preferences(ctx, "user-1")
|
||||
if err != nil {
|
||||
t.Fatalf("read preferences: %v", err)
|
||||
}
|
||||
if read.SelectedTheme != "midnight" || read.SelectedAppearance != domain.AppearanceDark {
|
||||
t.Fatalf("preferences = %+v, want them preserved across logins", read)
|
||||
}
|
||||
}
|
||||
|
||||
func TestThemePreferenceCount(t *testing.T) {
|
||||
s := newTestStore(t)
|
||||
ctx := context.Background()
|
||||
publishTheme(t, s, "midnight")
|
||||
|
||||
for _, subject := range []string{"a", "b"} {
|
||||
if _, err := s.SetPreferences(ctx, subject, domain.UserPreferences{
|
||||
SelectedTheme: "midnight",
|
||||
SelectedAppearance: domain.AppearanceLight,
|
||||
}); err != nil {
|
||||
t.Fatalf("set preferences for %s: %v", subject, err)
|
||||
}
|
||||
}
|
||||
|
||||
count, err := s.ThemePreferenceCount(ctx, "midnight")
|
||||
if err != nil {
|
||||
t.Fatalf("count: %v", err)
|
||||
}
|
||||
if count != 2 {
|
||||
t.Fatalf("count = %d, want 2", count)
|
||||
}
|
||||
if _, err := s.ThemePreferenceCount(ctx, "Not A Slug"); !errors.Is(err, domain.ErrInvalid) {
|
||||
t.Errorf("ThemePreferenceCount with a bad slug = %v, want ErrInvalid", err)
|
||||
}
|
||||
}
|
||||
|
||||
// publishTheme creates a theme and publishes it, returning the stored value.
|
||||
//
|
||||
// StatusPublished is a constant rather than a variable, so the pointer
|
||||
// UpdateFields wants has to be taken from a local.
|
||||
func publishTheme(t *testing.T, s *Store, slug string) *domain.Theme {
|
||||
t.Helper()
|
||||
ctx := context.Background()
|
||||
if err := s.Create(ctx, sampleTheme(slug), ""); err != nil {
|
||||
t.Fatalf("create theme %s: %v", slug, err)
|
||||
}
|
||||
published := domain.StatusPublished
|
||||
theme, err := s.Update(ctx, slug, UpdateFields{Status: &published})
|
||||
if err != nil {
|
||||
t.Fatalf("publish theme %s: %v", slug, err)
|
||||
}
|
||||
return theme
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue