Files
kleiax 904d14b64c
CI / test (push) Canceled after 0s
Initial commit
2026-09-12 22:22:17 +02:00

90 lines
3.5 KiB
Go

package postgres
import (
"database/sql"
"errors"
"os"
"strings"
"testing"
"time"
"gardomatic.kleiax.de/internal/storage"
_ "github.com/lib/pq"
)
func TestDeleteUserAnonymizesAccountAndPreservesAuthoredContent(t *testing.T) {
dsn := os.Getenv("GARDOMATIC_TEST_DB_DSN")
if dsn == "" {
t.Skip("set GARDOMATIC_TEST_DB_DSN to run PostgreSQL integration tests")
}
db, err := sql.Open("postgres", dsn)
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = db.Close() })
if err = db.Ping(); err != nil {
t.Fatal(err)
}
stamp := strings.ReplaceAll(time.Now().Format("150405.000000000"), ".", "")
originalEmail := "delete-" + stamp + "@example.com"
var ownerID, targetID, gardenID, entryID int
if err = db.QueryRow(`INSERT INTO users (name,email,password_hash,activated) VALUES ('Owner',$1,'hash',true) RETURNING id`, "owner-delete-"+stamp+"@example.com").Scan(&ownerID); err != nil {
t.Fatal(err)
}
if err = db.QueryRow(`INSERT INTO users (name,email,password_hash,activated) VALUES ('Personal Name',$1,'hash',true) RETURNING id`, originalEmail).Scan(&targetID); err != nil {
t.Fatal(err)
}
if err = db.QueryRow(`INSERT INTO gardens (name) VALUES ('Deletion integration') RETURNING id`).Scan(&gardenID); err != nil {
t.Fatal(err)
}
t.Cleanup(func() {
_, _ = db.Exec(`DELETE FROM gardens WHERE id=$1`, gardenID)
_, _ = db.Exec(`DELETE FROM users WHERE id IN ($1,$2)`, ownerID, targetID)
})
if _, err = db.Exec(`INSERT INTO garden_members (garden_id,user_id,role) VALUES ($1,$2,'owner'),($1,$3,'member')`, gardenID, ownerID, targetID); err != nil {
t.Fatal(err)
}
if err = db.QueryRow(`INSERT INTO journal_entries (garden_id,author_id,title) VALUES ($1,$2,'Bleibt erhalten') RETURNING id`, gardenID, targetID).Scan(&entryID); err != nil {
t.Fatal(err)
}
users := UserModel{DB: db}
if err = users.Delete(targetID); err != nil {
t.Fatal(err)
}
if _, err = users.GetByEmail(originalEmail); !errors.Is(err, storage.ErrRecordNotFound) {
t.Fatalf("deleted email still resolves: %v", err)
}
if _, err = users.GetByID(targetID); !errors.Is(err, storage.ErrRecordNotFound) {
t.Fatalf("deleted user ID still resolves: %v", err)
}
var name, email string
var activated bool
var deletedAt time.Time
if err = db.QueryRow(`SELECT name,email,activated,deleted_at FROM users WHERE id=$1`, targetID).Scan(&name, &email, &activated, &deletedAt); err != nil {
t.Fatal(err)
}
if name != "Gelöschter Nutzer" || email == originalEmail || activated || deletedAt.IsZero() {
t.Fatalf("account was not anonymized: name=%q email=%q activated=%t deleted_at=%v", name, email, activated, deletedAt)
}
var membershipCount, entryCount int
if err = db.QueryRow(`SELECT count(*) FROM garden_members WHERE user_id=$1`, targetID).Scan(&membershipCount); err != nil {
t.Fatal(err)
}
if err = db.QueryRow(`SELECT count(*) FROM journal_entries WHERE id=$1 AND author_id=$2`, entryID, targetID).Scan(&entryCount); err != nil {
t.Fatal(err)
}
if membershipCount != 0 || entryCount != 1 {
t.Fatalf("membership=%d preserved entries=%d", membershipCount, entryCount)
}
var replacementID int
if err = db.QueryRow(`INSERT INTO users (name,email,password_hash,activated) VALUES ('Replacement',$1,'hash',true) RETURNING id`, originalEmail).Scan(&replacementID); err != nil {
t.Fatalf("original email was not released: %v", err)
}
t.Cleanup(func() { _, _ = db.Exec(`DELETE FROM users WHERE id=$1`, replacementID) })
if err = users.Delete(ownerID); !errors.Is(err, storage.ErrConflict) {
t.Fatalf("owner deletion error: got %v, want conflict", err)
}
}