// // CreateCareScheduleUseCaseTests.swift // PlantGuideTests // // Unit tests for CreateCareScheduleUseCase - the use case for creating plant // care schedules based on care requirements and user preferences. // import XCTest @testable import PlantGuide // MARK: - CreateCareScheduleUseCaseTests final class CreateCareScheduleUseCaseTests: XCTestCase { // MARK: - Properties private var sut: CreateCareScheduleUseCase! // MARK: - Test Lifecycle override func setUp() { super.setUp() sut = CreateCareScheduleUseCase() } override func tearDown() { sut = nil super.tearDown() } // MARK: - Test Helpers private func createBasicCareInfo( wateringFrequency: WateringFrequency = .weekly, fertilizerSchedule: FertilizerSchedule? = nil ) -> PlantCareInfo { PlantCareInfo( scientificName: "Monstera deliciosa", commonName: "Swiss Cheese Plant", lightRequirement: .partialShade, wateringSchedule: WateringSchedule(frequency: wateringFrequency, amount: .moderate), temperatureRange: TemperatureRange(minimumCelsius: 18, maximumCelsius: 27), fertilizerSchedule: fertilizerSchedule ) } // MARK: - execute() Basic Schedule Creation Tests func testExecute_WhenCalled_ReturnsScheduleWithCorrectPlantID() async throws { // Given let plant = Plant.mock() let careInfo = createBasicCareInfo() // When let result = try await sut.execute(for: plant, careInfo: careInfo, preferences: nil) // Then XCTAssertEqual(result.plantID, plant.id) } func testExecute_WhenCalled_ReturnsScheduleWithCorrectLightRequirement() async throws { // Given let plant = Plant.mock() let careInfo = PlantCareInfo( scientificName: "Test Plant", commonName: nil, lightRequirement: .fullSun, wateringSchedule: WateringSchedule(frequency: .weekly, amount: .moderate), temperatureRange: TemperatureRange(minimumCelsius: 15, maximumCelsius: 30) ) // When let result = try await sut.execute(for: plant, careInfo: careInfo, preferences: nil) // Then XCTAssertEqual(result.lightRequirement, .fullSun) } func testExecute_WhenCalled_ReturnsScheduleWithCorrectTemperatureRange() async throws { // Given let plant = Plant.mock() let careInfo = PlantCareInfo( scientificName: "Test Plant", commonName: nil, lightRequirement: .partialShade, wateringSchedule: WateringSchedule(frequency: .weekly, amount: .moderate), temperatureRange: TemperatureRange(minimumCelsius: 10, maximumCelsius: 25) ) // When let result = try await sut.execute(for: plant, careInfo: careInfo, preferences: nil) // Then XCTAssertEqual(result.temperatureRange, 10...25) } // MARK: - Watering Task Generation Tests func testExecute_WithWeeklyWatering_GeneratesWateringTasks() async throws { // Given let plant = Plant.mock() let careInfo = createBasicCareInfo(wateringFrequency: .weekly) // When let result = try await sut.execute(for: plant, careInfo: careInfo, preferences: nil) // Then let wateringTasks = result.tasks.filter { $0.type == .watering } XCTAssertFalse(wateringTasks.isEmpty) // With 30 days and weekly watering (7-day interval), expect at least 4 tasks XCTAssertGreaterThanOrEqual(wateringTasks.count, 4) } func testExecute_WithDailyWatering_GeneratesMoreFrequentTasks() async throws { // Given let plant = Plant.mock() let careInfo = createBasicCareInfo(wateringFrequency: .daily) // When let result = try await sut.execute(for: plant, careInfo: careInfo, preferences: nil) // Then let wateringTasks = result.tasks.filter { $0.type == .watering } // With 30 days and daily watering, expect 30 tasks XCTAssertGreaterThanOrEqual(wateringTasks.count, 30) } func testExecute_WithBiweeklyWatering_GeneratesLessFrequentTasks() async throws { // Given let plant = Plant.mock() let careInfo = createBasicCareInfo(wateringFrequency: .biweekly) // When let result = try await sut.execute(for: plant, careInfo: careInfo, preferences: nil) // Then let wateringTasks = result.tasks.filter { $0.type == .watering } // With 30 days and biweekly watering (14-day interval), expect 2 tasks XCTAssertGreaterThanOrEqual(wateringTasks.count, 2) XCTAssertLessThanOrEqual(wateringTasks.count, 3) } func testExecute_WateringTasks_HaveCorrectNotes() async throws { // Given let plant = Plant.mock() let careInfo = PlantCareInfo( scientificName: "Test Plant", commonName: nil, lightRequirement: .partialShade, wateringSchedule: WateringSchedule(frequency: .weekly, amount: .thorough), temperatureRange: TemperatureRange(minimumCelsius: 18, maximumCelsius: 27) ) // When let result = try await sut.execute(for: plant, careInfo: careInfo, preferences: nil) // Then let wateringTasks = result.tasks.filter { $0.type == .watering } XCTAssertTrue(wateringTasks.allSatisfy { $0.notes?.contains("thorough") ?? false }) } func testExecute_WateringTasks_HaveCorrectPlantID() async throws { // Given let plant = Plant.mock() let careInfo = createBasicCareInfo() // When let result = try await sut.execute(for: plant, careInfo: careInfo, preferences: nil) // Then XCTAssertTrue(result.tasks.allSatisfy { $0.plantID == plant.id }) } // MARK: - Fertilizer Task Generation Tests func testExecute_WithFertilizerSchedule_GeneratesFertilizingTasks() async throws { // Given let plant = Plant.mock() let fertilizerSchedule = FertilizerSchedule(frequency: .monthly, type: .balanced) let careInfo = createBasicCareInfo(fertilizerSchedule: fertilizerSchedule) // When let result = try await sut.execute(for: plant, careInfo: careInfo, preferences: nil) // Then let fertilizerTasks = result.tasks.filter { $0.type == .fertilizing } XCTAssertFalse(fertilizerTasks.isEmpty) } func testExecute_WithoutFertilizerSchedule_DoesNotGenerateFertilizingTasks() async throws { // Given let plant = Plant.mock() let careInfo = createBasicCareInfo(fertilizerSchedule: nil) // When let result = try await sut.execute(for: plant, careInfo: careInfo, preferences: nil) // Then let fertilizerTasks = result.tasks.filter { $0.type == .fertilizing } XCTAssertTrue(fertilizerTasks.isEmpty) } func testExecute_WithWeeklyFertilizer_GeneratesWeeklyFertilizerTasks() async throws { // Given let plant = Plant.mock() let fertilizerSchedule = FertilizerSchedule(frequency: .weekly, type: .organic) let careInfo = createBasicCareInfo(fertilizerSchedule: fertilizerSchedule) // When let result = try await sut.execute(for: plant, careInfo: careInfo, preferences: nil) // Then let fertilizerTasks = result.tasks.filter { $0.type == .fertilizing } // With 30 days and weekly fertilizer (7-day interval), expect at least 4 tasks XCTAssertGreaterThanOrEqual(fertilizerTasks.count, 4) } func testExecute_WithQuarterlyFertilizer_GeneratesSingleFertilizerTask() async throws { // Given let plant = Plant.mock() let fertilizerSchedule = FertilizerSchedule(frequency: .quarterly, type: .balanced) let careInfo = createBasicCareInfo(fertilizerSchedule: fertilizerSchedule) // When let result = try await sut.execute(for: plant, careInfo: careInfo, preferences: nil) // Then let fertilizerTasks = result.tasks.filter { $0.type == .fertilizing } // With 30 days and quarterly fertilizer (90-day interval), expect 1 task XCTAssertEqual(fertilizerTasks.count, 1) } func testExecute_FertilizerTasks_HaveCorrectNotes() async throws { // Given let plant = Plant.mock() let fertilizerSchedule = FertilizerSchedule(frequency: .monthly, type: .highNitrogen) let careInfo = createBasicCareInfo(fertilizerSchedule: fertilizerSchedule) // When let result = try await sut.execute(for: plant, careInfo: careInfo, preferences: nil) // Then let fertilizerTasks = result.tasks.filter { $0.type == .fertilizing } XCTAssertTrue(fertilizerTasks.allSatisfy { $0.notes?.contains("highNitrogen") ?? false }) } // MARK: - User Preferences Tests func testExecute_WithPreferredWateringHour_UsesPreferredTime() async throws { // Given let plant = Plant.mock() let careInfo = createBasicCareInfo(wateringFrequency: .weekly) let preferences = CarePreferences(preferredWateringHour: 18, preferredWateringMinute: 30) // When let result = try await sut.execute(for: plant, careInfo: careInfo, preferences: preferences) // Then let calendar = Calendar.current let wateringTasks = result.tasks.filter { $0.type == .watering } for task in wateringTasks { let hour = calendar.component(.hour, from: task.scheduledDate) let minute = calendar.component(.minute, from: task.scheduledDate) XCTAssertEqual(hour, 18) XCTAssertEqual(minute, 30) } } func testExecute_WithoutPreferences_UsesDefaultTime() async throws { // Given let plant = Plant.mock() let careInfo = createBasicCareInfo(wateringFrequency: .weekly) // When let result = try await sut.execute(for: plant, careInfo: careInfo, preferences: nil) // Then let calendar = Calendar.current let wateringTasks = result.tasks.filter { $0.type == .watering } for task in wateringTasks { let hour = calendar.component(.hour, from: task.scheduledDate) XCTAssertEqual(hour, 8) // Default is 8 AM } } func testExecute_WithPreferences_AppliesTimeToFertilizerTasks() async throws { // Given let plant = Plant.mock() let fertilizerSchedule = FertilizerSchedule(frequency: .weekly, type: .balanced) let careInfo = createBasicCareInfo(fertilizerSchedule: fertilizerSchedule) let preferences = CarePreferences(preferredWateringHour: 9, preferredWateringMinute: 15) // When let result = try await sut.execute(for: plant, careInfo: careInfo, preferences: preferences) // Then let calendar = Calendar.current let fertilizerTasks = result.tasks.filter { $0.type == .fertilizing } for task in fertilizerTasks { let hour = calendar.component(.hour, from: task.scheduledDate) let minute = calendar.component(.minute, from: task.scheduledDate) XCTAssertEqual(hour, 9) XCTAssertEqual(minute, 15) } } // MARK: - Task Scheduling Tests func testExecute_TasksStartFromTomorrow() async throws { // Given let plant = Plant.mock() let careInfo = createBasicCareInfo() // When let result = try await sut.execute(for: plant, careInfo: careInfo, preferences: nil) // Then let calendar = Calendar.current let today = calendar.startOfDay(for: Date()) let tomorrow = calendar.date(byAdding: .day, value: 1, to: today)! for task in result.tasks { let taskDay = calendar.startOfDay(for: task.scheduledDate) XCTAssertGreaterThanOrEqual(taskDay, tomorrow) } } func testExecute_TasksAreSortedByDate() async throws { // Given let plant = Plant.mock() let fertilizerSchedule = FertilizerSchedule(frequency: .weekly, type: .balanced) let careInfo = createBasicCareInfo( wateringFrequency: .twiceWeekly, fertilizerSchedule: fertilizerSchedule ) // When let result = try await sut.execute(for: plant, careInfo: careInfo, preferences: nil) // Then for index in 0..<(result.tasks.count - 1) { XCTAssertLessThanOrEqual( result.tasks[index].scheduledDate, result.tasks[index + 1].scheduledDate ) } } func testExecute_TasksHaveUniqueIDs() async throws { // Given let plant = Plant.mock() let fertilizerSchedule = FertilizerSchedule(frequency: .weekly, type: .balanced) let careInfo = createBasicCareInfo( wateringFrequency: .daily, fertilizerSchedule: fertilizerSchedule ) // When let result = try await sut.execute(for: plant, careInfo: careInfo, preferences: nil) // Then let taskIDs = result.tasks.map { $0.id } let uniqueIDs = Set(taskIDs) XCTAssertEqual(taskIDs.count, uniqueIDs.count, "All task IDs should be unique") } // MARK: - Schedule Metadata Tests func testExecute_WateringScheduleString_MatchesFrequency() async throws { // Given let plant = Plant.mock() let careInfo = createBasicCareInfo(wateringFrequency: .biweekly) // When let result = try await sut.execute(for: plant, careInfo: careInfo, preferences: nil) // Then XCTAssertEqual(result.wateringSchedule, "biweekly") } func testExecute_FertilizerScheduleString_WhenNoFertilizer_ReturnsNotRequired() async throws { // Given let plant = Plant.mock() let careInfo = createBasicCareInfo(fertilizerSchedule: nil) // When let result = try await sut.execute(for: plant, careInfo: careInfo, preferences: nil) // Then XCTAssertEqual(result.fertilizerSchedule, "Not required") } func testExecute_FertilizerScheduleString_WithFertilizer_ReturnsFrequency() async throws { // Given let plant = Plant.mock() let fertilizerSchedule = FertilizerSchedule(frequency: .monthly, type: .organic) let careInfo = createBasicCareInfo(fertilizerSchedule: fertilizerSchedule) // When let result = try await sut.execute(for: plant, careInfo: careInfo, preferences: nil) // Then XCTAssertEqual(result.fertilizerSchedule, "monthly") } // MARK: - Protocol Conformance Tests func testCreateCareScheduleUseCase_ConformsToProtocol() { XCTAssertTrue(sut is CreateCareScheduleUseCaseProtocol) } // MARK: - Edge Cases func testExecute_WithAllFertilizerFrequencies_GeneratesCorrectTaskCounts() async throws { let frequencies: [(FertilizerFrequency, Int)] = [ (.weekly, 4), // 30 / 7 = at least 4 (.biweekly, 2), // 30 / 14 = 2 (.monthly, 1), // 30 / 30 = 1 (.quarterly, 1), // 30 / 90 = 1 (minimum 1) (.biannually, 1) // 30 / 182 = 1 (minimum 1) ] for (frequency, expectedMinCount) in frequencies { // Given let plant = Plant.mock() let fertilizerSchedule = FertilizerSchedule(frequency: frequency, type: .balanced) let careInfo = createBasicCareInfo(fertilizerSchedule: fertilizerSchedule) // When let result = try await sut.execute(for: plant, careInfo: careInfo, preferences: nil) // Then let fertilizerTasks = result.tasks.filter { $0.type == .fertilizing } XCTAssertGreaterThanOrEqual( fertilizerTasks.count, expectedMinCount, "Expected at least \(expectedMinCount) tasks for \(frequency.rawValue) frequency" ) } } func testExecute_WithAllWateringFrequencies_GeneratesCorrectTaskCounts() async throws { let frequencies: [(WateringFrequency, Int)] = [ (.daily, 30), // 30 / 1 = 30 (.everyOtherDay, 15), // 30 / 2 = 15 (.twiceWeekly, 10), // 30 / 3 = 10 (.weekly, 4), // 30 / 7 = 4 (.biweekly, 2), // 30 / 14 = 2 (.monthly, 1) // 30 / 30 = 1 ] for (frequency, expectedMinCount) in frequencies { // Given let plant = Plant.mock() let careInfo = createBasicCareInfo(wateringFrequency: frequency) // When let result = try await sut.execute(for: plant, careInfo: careInfo, preferences: nil) // Then let wateringTasks = result.tasks.filter { $0.type == .watering } XCTAssertGreaterThanOrEqual( wateringTasks.count, expectedMinCount, "Expected at least \(expectedMinCount) tasks for \(frequency.rawValue) frequency" ) } } func testExecute_WithMidnightPreferredTime_GeneratesTasksAtMidnight() async throws { // Given let plant = Plant.mock() let careInfo = createBasicCareInfo() let preferences = CarePreferences(preferredWateringHour: 0, preferredWateringMinute: 0) // When let result = try await sut.execute(for: plant, careInfo: careInfo, preferences: preferences) // Then let calendar = Calendar.current for task in result.tasks { let hour = calendar.component(.hour, from: task.scheduledDate) let minute = calendar.component(.minute, from: task.scheduledDate) XCTAssertEqual(hour, 0) XCTAssertEqual(minute, 0) } } func testExecute_WithLateNightPreferredTime_GeneratesTasksAtLateNight() async throws { // Given let plant = Plant.mock() let careInfo = createBasicCareInfo() let preferences = CarePreferences(preferredWateringHour: 23, preferredWateringMinute: 59) // When let result = try await sut.execute(for: plant, careInfo: careInfo, preferences: preferences) // Then let calendar = Calendar.current for task in result.tasks { let hour = calendar.component(.hour, from: task.scheduledDate) let minute = calendar.component(.minute, from: task.scheduledDate) XCTAssertEqual(hour, 23) XCTAssertEqual(minute, 59) } } }