Add domain layer tests (TDD - Red phase)
Following TDD principles, adding tests before implementation: - Add HabitType value object tests (validation) - Add Frequency value object tests (validation) - Add User entity tests (creation, default timezone) - Add Habit entity tests (creation, archive, active status) - Add HabitEntry entity tests (creation, soft delete) Tests cover core domain logic and business rules. All tests will fail until implementation is added.
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestNewHabitEntry(t *testing.T) {
|
||||
habitID := "habit-123"
|
||||
scheduledDate := time.Now().Truncate(24 * time.Hour)
|
||||
value := 5.0
|
||||
|
||||
entry := NewHabitEntry(habitID, scheduledDate, &value)
|
||||
|
||||
if entry.HabitID != habitID {
|
||||
t.Errorf("Expected HabitID %s, got %s", habitID, entry.HabitID)
|
||||
}
|
||||
|
||||
if !entry.ScheduledDate.Equal(scheduledDate) {
|
||||
t.Errorf("Expected ScheduledDate %v, got %v", scheduledDate, entry.ScheduledDate)
|
||||
}
|
||||
|
||||
if entry.Value == nil {
|
||||
t.Error("Value should not be nil")
|
||||
}
|
||||
|
||||
if *entry.Value != value {
|
||||
t.Errorf("Expected Value %f, got %f", value, *entry.Value)
|
||||
}
|
||||
|
||||
if entry.CompletedAt.IsZero() {
|
||||
t.Error("CompletedAt should not be zero")
|
||||
}
|
||||
|
||||
if entry.DeletedAt != nil {
|
||||
t.Error("DeletedAt should be nil for new entry")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewHabitEntry_BooleanHabit(t *testing.T) {
|
||||
habitID := "habit-123"
|
||||
scheduledDate := time.Now().Truncate(24 * time.Hour)
|
||||
|
||||
entry := NewHabitEntry(habitID, scheduledDate, nil)
|
||||
|
||||
if entry.Value != nil {
|
||||
t.Error("Value should be nil for boolean habit")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHabitEntry_SoftDelete(t *testing.T) {
|
||||
entry := NewHabitEntry("habit-123", time.Now(), nil)
|
||||
|
||||
if entry.DeletedAt != nil {
|
||||
t.Error("New entry should not be deleted")
|
||||
}
|
||||
|
||||
entry.SoftDelete()
|
||||
|
||||
if entry.DeletedAt == nil {
|
||||
t.Error("Entry should be deleted after calling SoftDelete()")
|
||||
}
|
||||
|
||||
if entry.DeletedAt.After(time.Now()) {
|
||||
t.Error("DeletedAt should not be in the future")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"habit-tracker-api/internal/domain/value_objects"
|
||||
)
|
||||
|
||||
func TestNewHabit(t *testing.T) {
|
||||
userID := "user-123"
|
||||
name := "Morning Exercise"
|
||||
habitType := value_objects.HabitTypeBoolean
|
||||
frequency := value_objects.FrequencyDaily
|
||||
carryOver := false
|
||||
|
||||
habit := NewHabit(userID, name, habitType, frequency, carryOver)
|
||||
|
||||
if habit.UserID != userID {
|
||||
t.Errorf("Expected UserID %s, got %s", userID, habit.UserID)
|
||||
}
|
||||
|
||||
if habit.Name != name {
|
||||
t.Errorf("Expected Name %s, got %s", name, habit.Name)
|
||||
}
|
||||
|
||||
if habit.Type != habitType {
|
||||
t.Errorf("Expected Type %s, got %s", habitType, habit.Type)
|
||||
}
|
||||
|
||||
if habit.Frequency != frequency {
|
||||
t.Errorf("Expected Frequency %s, got %s", frequency, habit.Frequency)
|
||||
}
|
||||
|
||||
if habit.CarryOver != carryOver {
|
||||
t.Errorf("Expected CarryOver %v, got %v", carryOver, habit.CarryOver)
|
||||
}
|
||||
|
||||
if habit.CreatedAt.IsZero() {
|
||||
t.Error("CreatedAt should not be zero")
|
||||
}
|
||||
|
||||
if habit.ArchivedAt != nil {
|
||||
t.Error("ArchivedAt should be nil for new habit")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHabit_Archive(t *testing.T) {
|
||||
habit := NewHabit("user-123", "Test Habit", value_objects.HabitTypeBoolean, value_objects.FrequencyDaily, false)
|
||||
|
||||
if habit.ArchivedAt != nil {
|
||||
t.Error("New habit should not be archived")
|
||||
}
|
||||
|
||||
habit.Archive()
|
||||
|
||||
if habit.ArchivedAt == nil {
|
||||
t.Error("Habit should be archived after calling Archive()")
|
||||
}
|
||||
|
||||
if habit.ArchivedAt.After(time.Now()) {
|
||||
t.Error("ArchivedAt should not be in the future")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHabit_IsActive(t *testing.T) {
|
||||
habit := NewHabit("user-123", "Test Habit", value_objects.HabitTypeBoolean, value_objects.FrequencyDaily, false)
|
||||
|
||||
if !habit.IsActive() {
|
||||
t.Error("New habit should be active")
|
||||
}
|
||||
|
||||
habit.Archive()
|
||||
|
||||
if habit.IsActive() {
|
||||
t.Error("Archived habit should not be active")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHabit_WithSpecificDays(t *testing.T) {
|
||||
habit := NewHabit("user-123", "Workout", value_objects.HabitTypeBoolean, value_objects.FrequencyWeekly, false)
|
||||
habit.SpecificDays = []int{1, 3, 5} // Monday, Wednesday, Friday
|
||||
|
||||
if len(habit.SpecificDays) != 3 {
|
||||
t.Errorf("Expected 3 specific days, got %d", len(habit.SpecificDays))
|
||||
}
|
||||
|
||||
if habit.SpecificDays[0] != 1 || habit.SpecificDays[1] != 3 || habit.SpecificDays[2] != 5 {
|
||||
t.Errorf("Expected days [1,3,5], got %v", habit.SpecificDays)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHabit_WithTargetValue(t *testing.T) {
|
||||
habit := NewHabit("user-123", "Drink Water", value_objects.HabitTypeCounter, value_objects.FrequencyDaily, false)
|
||||
targetValue := 8.0
|
||||
habit.TargetValue = &targetValue
|
||||
|
||||
if habit.TargetValue == nil {
|
||||
t.Error("TargetValue should not be nil")
|
||||
}
|
||||
|
||||
if *habit.TargetValue != 8.0 {
|
||||
t.Errorf("Expected TargetValue 8.0, got %f", *habit.TargetValue)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestNewUser(t *testing.T) {
|
||||
email := "test@example.com"
|
||||
passwordHash := "hashed_password_123"
|
||||
timezone := "Europe/Madrid"
|
||||
|
||||
user := NewUser(email, passwordHash, timezone)
|
||||
|
||||
if user.Email != email {
|
||||
t.Errorf("Expected email %s, got %s", email, user.Email)
|
||||
}
|
||||
|
||||
if user.PasswordHash != passwordHash {
|
||||
t.Errorf("Expected password hash %s, got %s", passwordHash, user.PasswordHash)
|
||||
}
|
||||
|
||||
if user.Timezone != timezone {
|
||||
t.Errorf("Expected timezone %s, got %s", timezone, user.Timezone)
|
||||
}
|
||||
|
||||
if user.CreatedAt.IsZero() {
|
||||
t.Error("CreatedAt should not be zero")
|
||||
}
|
||||
|
||||
if user.UpdatedAt.IsZero() {
|
||||
t.Error("UpdatedAt should not be zero")
|
||||
}
|
||||
|
||||
// CreatedAt and UpdatedAt should be very close in time
|
||||
diff := user.UpdatedAt.Sub(user.CreatedAt)
|
||||
if diff < 0 || diff > time.Second {
|
||||
t.Errorf("CreatedAt and UpdatedAt should be nearly identical, diff: %v", diff)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUser_DefaultTimezone(t *testing.T) {
|
||||
user := NewUser("test@example.com", "hash", "")
|
||||
|
||||
if user.Timezone != "UTC" {
|
||||
t.Errorf("Expected default timezone UTC, got %s", user.Timezone)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package value_objects
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestFrequency_IsValid(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
freq Frequency
|
||||
expected bool
|
||||
}{
|
||||
{"Daily frequency is valid", FrequencyDaily, true},
|
||||
{"Weekly frequency is valid", FrequencyWeekly, true},
|
||||
{"Monthly frequency is valid", FrequencyMonthly, true},
|
||||
{"Empty string is invalid", Frequency(""), false},
|
||||
{"Random string is invalid", Frequency("YEARLY"), false},
|
||||
{"Lowercase is invalid", Frequency("daily"), false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := tt.freq.IsValid(); got != tt.expected {
|
||||
t.Errorf("Frequency.IsValid() = %v, want %v", got, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package value_objects
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestHabitType_IsValid(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
habType HabitType
|
||||
expected bool
|
||||
}{
|
||||
{"Boolean type is valid", HabitTypeBoolean, true},
|
||||
{"Counter type is valid", HabitTypeCounter, true},
|
||||
{"Value type is valid", HabitTypeValue, true},
|
||||
{"Empty string is invalid", HabitType(""), false},
|
||||
{"Random string is invalid", HabitType("RANDOM"), false},
|
||||
{"Lowercase is invalid", HabitType("boolean"), false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := tt.habType.IsValid(); got != tt.expected {
|
||||
t.Errorf("HabitType.IsValid() = %v, want %v", got, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user