5d92820591
Timezone is now sent from the client on each request that needs it, instead of storing it in the database. This simplifies the model and allows timezone to be dynamic (useful for traveling users). Changes: - Remove timezone field from User entity - Remove timezone from user registration - GET /habits/today now requires ?timezone= query param - Add migration to drop timezone column from database - Update related tests
233 lines
4.7 KiB
Go
233 lines
4.7 KiB
Go
package sqlite
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"testing"
|
|
"time"
|
|
|
|
"apocapoc-api/internal/domain/entities"
|
|
"apocapoc-api/internal/shared/errors"
|
|
|
|
_ "modernc.org/sqlite"
|
|
)
|
|
|
|
func setupTestDB(t *testing.T) *sql.DB {
|
|
db, err := sql.Open("sqlite", ":memory:")
|
|
if err != nil {
|
|
t.Fatalf("Failed to open test database: %v", err)
|
|
}
|
|
|
|
if err := RunMigrations(db); err != nil {
|
|
t.Fatalf("Failed to run migrations: %v", err)
|
|
}
|
|
|
|
return db
|
|
}
|
|
|
|
func TestUserRepositoryCreate(t *testing.T) {
|
|
db := setupTestDB(t)
|
|
defer db.Close()
|
|
|
|
repo := NewUserRepository(db)
|
|
ctx := context.Background()
|
|
|
|
user := &entities.User{
|
|
Email: "test@example.com",
|
|
PasswordHash: "hashed_password",
|
|
CreatedAt: time.Now(),
|
|
UpdatedAt: time.Now(),
|
|
}
|
|
|
|
err := repo.Create(ctx, user)
|
|
if err != nil {
|
|
t.Fatalf("Create failed: %v", err)
|
|
}
|
|
|
|
if user.ID == "" {
|
|
t.Error("Expected user ID to be generated, got empty string")
|
|
}
|
|
}
|
|
|
|
func TestUserRepositoryCreateDuplicateEmail(t *testing.T) {
|
|
db := setupTestDB(t)
|
|
defer db.Close()
|
|
|
|
repo := NewUserRepository(db)
|
|
ctx := context.Background()
|
|
|
|
user1 := &entities.User{
|
|
Email: "duplicate@example.com",
|
|
PasswordHash: "hash1",
|
|
CreatedAt: time.Now(),
|
|
UpdatedAt: time.Now(),
|
|
}
|
|
|
|
err := repo.Create(ctx, user1)
|
|
if err != nil {
|
|
t.Fatalf("First create failed: %v", err)
|
|
}
|
|
|
|
user2 := &entities.User{
|
|
Email: "duplicate@example.com",
|
|
PasswordHash: "hash2",
|
|
CreatedAt: time.Now(),
|
|
UpdatedAt: time.Now(),
|
|
}
|
|
|
|
err = repo.Create(ctx, user2)
|
|
if err != errors.ErrAlreadyExists {
|
|
t.Errorf("Expected ErrAlreadyExists, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestUserRepositoryFindByID(t *testing.T) {
|
|
db := setupTestDB(t)
|
|
defer db.Close()
|
|
|
|
repo := NewUserRepository(db)
|
|
ctx := context.Background()
|
|
|
|
user := &entities.User{
|
|
Email: "find@example.com",
|
|
PasswordHash: "hashed",
|
|
CreatedAt: time.Now(),
|
|
UpdatedAt: time.Now(),
|
|
}
|
|
|
|
err := repo.Create(ctx, user)
|
|
if err != nil {
|
|
t.Fatalf("Create failed: %v", err)
|
|
}
|
|
|
|
found, err := repo.FindByID(ctx, user.ID)
|
|
if err != nil {
|
|
t.Fatalf("FindByID failed: %v", err)
|
|
}
|
|
|
|
if found.ID != user.ID {
|
|
t.Errorf("Expected ID %s, got %s", user.ID, found.ID)
|
|
}
|
|
if found.Email != user.Email {
|
|
t.Errorf("Expected email %s, got %s", user.Email, found.Email)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestUserRepositoryFindByIDNotFound(t *testing.T) {
|
|
db := setupTestDB(t)
|
|
defer db.Close()
|
|
|
|
repo := NewUserRepository(db)
|
|
ctx := context.Background()
|
|
|
|
_, err := repo.FindByID(ctx, "non-existent-id")
|
|
if err != errors.ErrNotFound {
|
|
t.Errorf("Expected ErrNotFound, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestUserRepositoryFindByEmail(t *testing.T) {
|
|
db := setupTestDB(t)
|
|
defer db.Close()
|
|
|
|
repo := NewUserRepository(db)
|
|
ctx := context.Background()
|
|
|
|
user := &entities.User{
|
|
Email: "email@test.com",
|
|
PasswordHash: "hashed",
|
|
CreatedAt: time.Now(),
|
|
UpdatedAt: time.Now(),
|
|
}
|
|
|
|
err := repo.Create(ctx, user)
|
|
if err != nil {
|
|
t.Fatalf("Create failed: %v", err)
|
|
}
|
|
|
|
found, err := repo.FindByEmail(ctx, user.Email)
|
|
if err != nil {
|
|
t.Fatalf("FindByEmail failed: %v", err)
|
|
}
|
|
|
|
if found.ID != user.ID {
|
|
t.Errorf("Expected ID %s, got %s", user.ID, found.ID)
|
|
}
|
|
if found.Email != user.Email {
|
|
t.Errorf("Expected email %s, got %s", user.Email, found.Email)
|
|
}
|
|
}
|
|
|
|
func TestUserRepositoryFindByEmailNotFound(t *testing.T) {
|
|
db := setupTestDB(t)
|
|
defer db.Close()
|
|
|
|
repo := NewUserRepository(db)
|
|
ctx := context.Background()
|
|
|
|
_, err := repo.FindByEmail(ctx, "nonexistent@example.com")
|
|
if err != errors.ErrNotFound {
|
|
t.Errorf("Expected ErrNotFound, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestUserRepositoryUpdate(t *testing.T) {
|
|
db := setupTestDB(t)
|
|
defer db.Close()
|
|
|
|
repo := NewUserRepository(db)
|
|
ctx := context.Background()
|
|
|
|
user := &entities.User{
|
|
Email: "original@example.com",
|
|
PasswordHash: "hash1",
|
|
CreatedAt: time.Now(),
|
|
UpdatedAt: time.Now(),
|
|
}
|
|
|
|
err := repo.Create(ctx, user)
|
|
if err != nil {
|
|
t.Fatalf("Create failed: %v", err)
|
|
}
|
|
|
|
user.Email = "updated@example.com"
|
|
user.UpdatedAt = time.Now()
|
|
|
|
err = repo.Update(ctx, user)
|
|
if err != nil {
|
|
t.Fatalf("Update failed: %v", err)
|
|
}
|
|
|
|
found, err := repo.FindByID(ctx, user.ID)
|
|
if err != nil {
|
|
t.Fatalf("FindByID failed: %v", err)
|
|
}
|
|
|
|
if found.Email != "updated@example.com" {
|
|
t.Errorf("Expected email updated@example.com, got %s", found.Email)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestUserRepositoryUpdateNotFound(t *testing.T) {
|
|
db := setupTestDB(t)
|
|
defer db.Close()
|
|
|
|
repo := NewUserRepository(db)
|
|
ctx := context.Background()
|
|
|
|
user := &entities.User{
|
|
ID: "non-existent",
|
|
Email: "test@example.com",
|
|
PasswordHash: "hash",
|
|
CreatedAt: time.Now(),
|
|
UpdatedAt: time.Now(),
|
|
}
|
|
|
|
err := repo.Update(ctx, user)
|
|
if err != errors.ErrNotFound {
|
|
t.Errorf("Expected ErrNotFound, got %v", err)
|
|
}
|
|
}
|