Fave cards in the list and profile views now show edit, delete, and
privacy toggle buttons directly — no need to open the detail page first.
- New POST /faves/{id}/privacy route with HTMX privacy toggle partial
- New UpdatePrivacy store method for single-column update
- fave_list.html: edit link, HTMX delete, privacy toggle on every card
- profile.html: edit/delete for owner's own cards
- privacy_toggle.html: new HTMX partial that swaps inline on toggle
- CSS: compact .fave-card-actions styles
The existing handleFaveDelete already returns empty 200 for HTMX
requests, so hx-target="closest article" hx-swap="outerHTML" removes
the card from DOM seamlessly.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
254 lines
6.6 KiB
Go
254 lines
6.6 KiB
Go
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
package store
|
|
|
|
import (
|
|
"testing"
|
|
)
|
|
|
|
func TestFaveCRUD(t *testing.T) {
|
|
db := testDB(t)
|
|
users := NewUserStore(db)
|
|
faves := NewFaveStore(db)
|
|
tags := NewTagStore(db)
|
|
|
|
Argon2Memory = 1024
|
|
Argon2Time = 1
|
|
defer func() { Argon2Memory = 65536; Argon2Time = 3 }()
|
|
|
|
// Create a user first.
|
|
user, err := users.Create("testuser", "password123", "user")
|
|
if err != nil {
|
|
t.Fatalf("create user: %v", err)
|
|
}
|
|
|
|
// Create a fave.
|
|
fave, err := faves.Create(user.ID, "Blade Runner 2049", "https://example.com", "", "", "public")
|
|
if err != nil {
|
|
t.Fatalf("create fave: %v", err)
|
|
}
|
|
if fave.Description != "Blade Runner 2049" {
|
|
t.Errorf("description = %q, want %q", fave.Description, "Blade Runner 2049")
|
|
}
|
|
if fave.Username != "testuser" {
|
|
t.Errorf("username = %q, want %q", fave.Username, "testuser")
|
|
}
|
|
|
|
// Get by ID.
|
|
got, err := faves.GetByID(fave.ID)
|
|
if err != nil {
|
|
t.Fatalf("get fave: %v", err)
|
|
}
|
|
if got.Description != fave.Description {
|
|
t.Errorf("got description = %q, want %q", got.Description, fave.Description)
|
|
}
|
|
|
|
// Update.
|
|
err = faves.Update(fave.ID, "Blade Runner 2049 (Final Cut)", "https://example.com/br2049", "", "", "private")
|
|
if err != nil {
|
|
t.Fatalf("update fave: %v", err)
|
|
}
|
|
updated, _ := faves.GetByID(fave.ID)
|
|
if updated.Description != "Blade Runner 2049 (Final Cut)" {
|
|
t.Errorf("updated description = %q", updated.Description)
|
|
}
|
|
if updated.Privacy != "private" {
|
|
t.Errorf("updated privacy = %q, want private", updated.Privacy)
|
|
}
|
|
|
|
// Set tags.
|
|
err = tags.SetFaveTags(fave.ID, []string{"film", "sci-fi", "favoritt"})
|
|
if err != nil {
|
|
t.Fatalf("set tags: %v", err)
|
|
}
|
|
faveTags, err := tags.ForFave(fave.ID)
|
|
if err != nil {
|
|
t.Fatalf("for fave: %v", err)
|
|
}
|
|
if len(faveTags) != 3 {
|
|
t.Errorf("tag count = %d, want 3", len(faveTags))
|
|
}
|
|
|
|
// List by user.
|
|
list, total, err := faves.ListByUser(user.ID, 10, 0)
|
|
if err != nil {
|
|
t.Fatalf("list by user: %v", err)
|
|
}
|
|
if total != 1 || len(list) != 1 {
|
|
t.Errorf("list by user: total=%d, len=%d", total, len(list))
|
|
}
|
|
|
|
// Load tags for list.
|
|
err = faves.LoadTags(list)
|
|
if err != nil {
|
|
t.Fatalf("load tags: %v", err)
|
|
}
|
|
if len(list[0].Tags) != 3 {
|
|
t.Errorf("loaded tags = %d, want 3", len(list[0].Tags))
|
|
}
|
|
|
|
// Public list should be empty (fave is now private).
|
|
pubList, pubTotal, err := faves.ListPublicByUser(user.ID, 10, 0)
|
|
if err != nil {
|
|
t.Fatalf("list public: %v", err)
|
|
}
|
|
if pubTotal != 0 || len(pubList) != 0 {
|
|
t.Errorf("public list should be empty: total=%d, len=%d", pubTotal, len(pubList))
|
|
}
|
|
|
|
// Delete.
|
|
err = faves.Delete(fave.ID)
|
|
if err != nil {
|
|
t.Fatalf("delete fave: %v", err)
|
|
}
|
|
_, err = faves.GetByID(fave.ID)
|
|
if err != ErrFaveNotFound {
|
|
t.Errorf("deleted fave error = %v, want ErrFaveNotFound", err)
|
|
}
|
|
}
|
|
|
|
func TestFaveNotes(t *testing.T) {
|
|
db := testDB(t)
|
|
users := NewUserStore(db)
|
|
faves := NewFaveStore(db)
|
|
|
|
Argon2Memory = 1024
|
|
Argon2Time = 1
|
|
defer func() { Argon2Memory = 65536; Argon2Time = 3 }()
|
|
|
|
user, _ := users.Create("testuser", "password123", "user")
|
|
|
|
// Create with notes.
|
|
fave, err := faves.Create(user.ID, "Film", "https://example.com", "", "En fantastisk film", "public")
|
|
if err != nil {
|
|
t.Fatalf("create fave with notes: %v", err)
|
|
}
|
|
if fave.Notes != "En fantastisk film" {
|
|
t.Errorf("notes = %q, want %q", fave.Notes, "En fantastisk film")
|
|
}
|
|
|
|
// Update notes.
|
|
err = faves.Update(fave.ID, fave.Description, fave.URL, fave.ImagePath, "Oppdatert anmeldelse", fave.Privacy)
|
|
if err != nil {
|
|
t.Fatalf("update notes: %v", err)
|
|
}
|
|
updated, _ := faves.GetByID(fave.ID)
|
|
if updated.Notes != "Oppdatert anmeldelse" {
|
|
t.Errorf("updated notes = %q", updated.Notes)
|
|
}
|
|
|
|
// Notes appear in list queries.
|
|
list, _, _ := faves.ListByUser(user.ID, 10, 0)
|
|
if len(list) != 1 || list[0].Notes != "Oppdatert anmeldelse" {
|
|
t.Error("notes should be loaded in list queries")
|
|
}
|
|
|
|
// Empty notes by default.
|
|
fave2, _ := faves.Create(user.ID, "No notes", "", "", "", "public")
|
|
if fave2.Notes != "" {
|
|
t.Errorf("default notes = %q, want empty", fave2.Notes)
|
|
}
|
|
}
|
|
|
|
func TestUpdatePrivacy(t *testing.T) {
|
|
db := testDB(t)
|
|
users := NewUserStore(db)
|
|
faves := NewFaveStore(db)
|
|
|
|
Argon2Memory = 1024
|
|
Argon2Time = 1
|
|
defer func() { Argon2Memory = 65536; Argon2Time = 3 }()
|
|
|
|
user, _ := users.Create("testuser", "password123", "user")
|
|
fave, _ := faves.Create(user.ID, "Toggle me", "", "", "", "public")
|
|
|
|
// Toggle to private.
|
|
err := faves.UpdatePrivacy(fave.ID, "private")
|
|
if err != nil {
|
|
t.Fatalf("update privacy: %v", err)
|
|
}
|
|
|
|
got, _ := faves.GetByID(fave.ID)
|
|
if got.Privacy != "private" {
|
|
t.Errorf("privacy = %q, want private", got.Privacy)
|
|
}
|
|
|
|
// Toggle back to public.
|
|
faves.UpdatePrivacy(fave.ID, "public")
|
|
got, _ = faves.GetByID(fave.ID)
|
|
if got.Privacy != "public" {
|
|
t.Errorf("privacy = %q, want public", got.Privacy)
|
|
}
|
|
}
|
|
|
|
func TestListByTag(t *testing.T) {
|
|
db := testDB(t)
|
|
users := NewUserStore(db)
|
|
faves := NewFaveStore(db)
|
|
tags := NewTagStore(db)
|
|
|
|
Argon2Memory = 1024
|
|
Argon2Time = 1
|
|
defer func() { Argon2Memory = 65536; Argon2Time = 3 }()
|
|
|
|
user, _ := users.Create("testuser", "password123", "user")
|
|
|
|
// Create two public faves with overlapping tags.
|
|
f1, _ := faves.Create(user.ID, "Fave 1", "", "", "", "public")
|
|
f2, _ := faves.Create(user.ID, "Fave 2", "", "", "", "public")
|
|
f3, _ := faves.Create(user.ID, "Private Fave", "", "", "", "private")
|
|
|
|
tags.SetFaveTags(f1.ID, []string{"music", "jazz"})
|
|
tags.SetFaveTags(f2.ID, []string{"music", "rock"})
|
|
tags.SetFaveTags(f3.ID, []string{"music", "secret"})
|
|
|
|
// ListByTag only returns public faves.
|
|
list, total, err := faves.ListByTag("music", 10, 0)
|
|
if err != nil {
|
|
t.Fatalf("list by tag: %v", err)
|
|
}
|
|
if total != 2 {
|
|
t.Errorf("total = %d, want 2 (private fave should be excluded)", total)
|
|
}
|
|
if len(list) != 2 {
|
|
t.Errorf("len = %d, want 2", len(list))
|
|
}
|
|
}
|
|
|
|
func TestFavePagination(t *testing.T) {
|
|
db := testDB(t)
|
|
users := NewUserStore(db)
|
|
faves := NewFaveStore(db)
|
|
|
|
Argon2Memory = 1024
|
|
Argon2Time = 1
|
|
defer func() { Argon2Memory = 65536; Argon2Time = 3 }()
|
|
|
|
user, _ := users.Create("testuser", "password123", "user")
|
|
|
|
// Create 5 faves.
|
|
for i := 0; i < 5; i++ {
|
|
faves.Create(user.ID, "Fave "+string(rune('A'+i)), "", "", "", "public")
|
|
}
|
|
|
|
// Page 1 with limit 2.
|
|
page1, total, err := faves.ListByUser(user.ID, 2, 0)
|
|
if err != nil {
|
|
t.Fatalf("page 1: %v", err)
|
|
}
|
|
if total != 5 {
|
|
t.Errorf("total = %d, want 5", total)
|
|
}
|
|
if len(page1) != 2 {
|
|
t.Errorf("page 1 len = %d, want 2", len(page1))
|
|
}
|
|
|
|
// Page 3 with limit 2 should have 1 item.
|
|
page3, _, err := faves.ListByUser(user.ID, 2, 4)
|
|
if err != nil {
|
|
t.Fatalf("page 3: %v", err)
|
|
}
|
|
if len(page3) != 1 {
|
|
t.Errorf("page 3 len = %d, want 1", len(page3))
|
|
}
|
|
}
|