// // TrefleMapperTests.swift // PlantGuideTests // // Unit tests for TrefleMapper functions. // Tests all mapping functions with various input values and edge cases. // import XCTest @testable import PlantGuide final class TrefleMapperTests: XCTestCase { // MARK: - mapToLightRequirement Tests func testMapToLightRequirement_WithZero_ReturnsFullShade() { // When let result = TrefleMapper.mapToLightRequirement(from: 0) // Then XCTAssertEqual(result, .fullShade) } func testMapToLightRequirement_WithOne_ReturnsFullShade() { // When let result = TrefleMapper.mapToLightRequirement(from: 1) // Then XCTAssertEqual(result, .fullShade) } func testMapToLightRequirement_WithTwo_ReturnsFullShade() { // When let result = TrefleMapper.mapToLightRequirement(from: 2) // Then XCTAssertEqual(result, .fullShade) } func testMapToLightRequirement_WithThree_ReturnsLowLight() { // When let result = TrefleMapper.mapToLightRequirement(from: 3) // Then XCTAssertEqual(result, .lowLight) } func testMapToLightRequirement_WithFour_ReturnsLowLight() { // When let result = TrefleMapper.mapToLightRequirement(from: 4) // Then XCTAssertEqual(result, .lowLight) } func testMapToLightRequirement_WithFive_ReturnsPartialShade() { // When let result = TrefleMapper.mapToLightRequirement(from: 5) // Then XCTAssertEqual(result, .partialShade) } func testMapToLightRequirement_WithSix_ReturnsPartialShade() { // When let result = TrefleMapper.mapToLightRequirement(from: 6) // Then XCTAssertEqual(result, .partialShade) } func testMapToLightRequirement_WithSeven_ReturnsFullSun() { // When let result = TrefleMapper.mapToLightRequirement(from: 7) // Then XCTAssertEqual(result, .fullSun) } func testMapToLightRequirement_WithEight_ReturnsFullSun() { // When let result = TrefleMapper.mapToLightRequirement(from: 8) // Then XCTAssertEqual(result, .fullSun) } func testMapToLightRequirement_WithNine_ReturnsFullSun() { // When let result = TrefleMapper.mapToLightRequirement(from: 9) // Then XCTAssertEqual(result, .fullSun) } func testMapToLightRequirement_WithTen_ReturnsFullSun() { // When let result = TrefleMapper.mapToLightRequirement(from: 10) // Then XCTAssertEqual(result, .fullSun) } func testMapToLightRequirement_WithNil_ReturnsPartialShadeDefault() { // When let result = TrefleMapper.mapToLightRequirement(from: nil) // Then XCTAssertEqual(result, .partialShade) } func testMapToLightRequirement_WithOutOfRangePositive_ReturnsPartialShadeDefault() { // When let result = TrefleMapper.mapToLightRequirement(from: 15) // Then XCTAssertEqual(result, .partialShade) } func testMapToLightRequirement_WithNegative_ReturnsPartialShadeDefault() { // When let result = TrefleMapper.mapToLightRequirement(from: -1) // Then XCTAssertEqual(result, .partialShade) } // MARK: - mapToWateringSchedule Tests func testMapToWateringSchedule_WithNilGrowth_ReturnsWeeklyModerateDefault() { // When let result = TrefleMapper.mapToWateringSchedule(from: nil) // Then XCTAssertEqual(result.frequency, .weekly) XCTAssertEqual(result.amount, .moderate) } func testMapToWateringSchedule_WithHighAtmosphericHumidity_ReturnsWeeklyLight() { // Given let growth = createGrowthDTO(atmosphericHumidity: 8, soilHumidity: nil) // When let result = TrefleMapper.mapToWateringSchedule(from: growth) // Then XCTAssertEqual(result.frequency, .weekly) XCTAssertEqual(result.amount, .light) } func testMapToWateringSchedule_WithHighHumidity10_ReturnsWeeklyLight() { // Given let growth = createGrowthDTO(atmosphericHumidity: 10, soilHumidity: nil) // When let result = TrefleMapper.mapToWateringSchedule(from: growth) // Then XCTAssertEqual(result.frequency, .weekly) XCTAssertEqual(result.amount, .light) } func testMapToWateringSchedule_WithMediumHumidity_ReturnsTwiceWeeklyModerate() { // Given let growth = createGrowthDTO(atmosphericHumidity: 5, soilHumidity: nil) // When let result = TrefleMapper.mapToWateringSchedule(from: growth) // Then XCTAssertEqual(result.frequency, .twiceWeekly) XCTAssertEqual(result.amount, .moderate) } func testMapToWateringSchedule_WithMediumHumidity4_ReturnsTwiceWeeklyModerate() { // Given let growth = createGrowthDTO(atmosphericHumidity: 4, soilHumidity: nil) // When let result = TrefleMapper.mapToWateringSchedule(from: growth) // Then XCTAssertEqual(result.frequency, .twiceWeekly) XCTAssertEqual(result.amount, .moderate) } func testMapToWateringSchedule_WithMediumHumidity6_ReturnsTwiceWeeklyModerate() { // Given let growth = createGrowthDTO(atmosphericHumidity: 6, soilHumidity: nil) // When let result = TrefleMapper.mapToWateringSchedule(from: growth) // Then XCTAssertEqual(result.frequency, .twiceWeekly) XCTAssertEqual(result.amount, .moderate) } func testMapToWateringSchedule_WithLowHumidity_ReturnsWeeklyThorough() { // Given let growth = createGrowthDTO(atmosphericHumidity: 2, soilHumidity: nil) // When let result = TrefleMapper.mapToWateringSchedule(from: growth) // Then XCTAssertEqual(result.frequency, .weekly) XCTAssertEqual(result.amount, .thorough) } func testMapToWateringSchedule_WithLowHumidity0_ReturnsWeeklyThorough() { // Given let growth = createGrowthDTO(atmosphericHumidity: 0, soilHumidity: nil) // When let result = TrefleMapper.mapToWateringSchedule(from: growth) // Then XCTAssertEqual(result.frequency, .weekly) XCTAssertEqual(result.amount, .thorough) } func testMapToWateringSchedule_WithNoAtmosphericHumidity_FallsBackToSoilHumidity() { // Given let growth = createGrowthDTO(atmosphericHumidity: nil, soilHumidity: 8) // When let result = TrefleMapper.mapToWateringSchedule(from: growth) // Then XCTAssertEqual(result.frequency, .weekly) XCTAssertEqual(result.amount, .light) } func testMapToWateringSchedule_WithNoHumidityValues_ReturnsWeeklyModerateDefault() { // Given let growth = createGrowthDTO(atmosphericHumidity: nil, soilHumidity: nil) // When let result = TrefleMapper.mapToWateringSchedule(from: growth) // Then XCTAssertEqual(result.frequency, .weekly) XCTAssertEqual(result.amount, .moderate) } // MARK: - mapToTemperatureRange Tests func testMapToTemperatureRange_WithNilGrowth_ReturnsDefaultRange() { // When let result = TrefleMapper.mapToTemperatureRange(from: nil) // Then XCTAssertEqual(result.minimumCelsius, 15.0) XCTAssertEqual(result.maximumCelsius, 30.0) XCTAssertNil(result.optimalCelsius) XCTAssertFalse(result.frostTolerant) } func testMapToTemperatureRange_WithValidTemperatures_ReturnsCorrectRange() { // Given let growth = createGrowthDTO(minTempC: 10.0, maxTempC: 35.0) // When let result = TrefleMapper.mapToTemperatureRange(from: growth) // Then XCTAssertEqual(result.minimumCelsius, 10.0) XCTAssertEqual(result.maximumCelsius, 35.0) XCTAssertEqual(result.optimalCelsius, 22.5) // (10 + 35) / 2 XCTAssertFalse(result.frostTolerant) } func testMapToTemperatureRange_WithNegativeMinTemp_SetsFrostTolerantTrue() { // Given let growth = createGrowthDTO(minTempC: -5.0, maxTempC: 25.0) // When let result = TrefleMapper.mapToTemperatureRange(from: growth) // Then XCTAssertEqual(result.minimumCelsius, -5.0) XCTAssertEqual(result.maximumCelsius, 25.0) XCTAssertTrue(result.frostTolerant) } func testMapToTemperatureRange_WithZeroMinTemp_SetsFrostTolerantFalse() { // Given let growth = createGrowthDTO(minTempC: 0.0, maxTempC: 30.0) // When let result = TrefleMapper.mapToTemperatureRange(from: growth) // Then XCTAssertEqual(result.minimumCelsius, 0.0) XCTAssertFalse(result.frostTolerant) } func testMapToTemperatureRange_WithOnlyMinTemp_ReturnsDefaultMaxAndNoOptimal() { // Given let growth = createGrowthDTO(minTempC: 5.0, maxTempC: nil) // When let result = TrefleMapper.mapToTemperatureRange(from: growth) // Then XCTAssertEqual(result.minimumCelsius, 5.0) XCTAssertEqual(result.maximumCelsius, 30.0) // Default XCTAssertNil(result.optimalCelsius) // No optimal when missing data } func testMapToTemperatureRange_WithOnlyMaxTemp_ReturnsDefaultMinAndNoOptimal() { // Given let growth = createGrowthDTO(minTempC: nil, maxTempC: 40.0) // When let result = TrefleMapper.mapToTemperatureRange(from: growth) // Then XCTAssertEqual(result.minimumCelsius, 15.0) // Default XCTAssertEqual(result.maximumCelsius, 40.0) XCTAssertNil(result.optimalCelsius) // No optimal when missing data } // MARK: - mapToFertilizerSchedule Tests func testMapToFertilizerSchedule_WithNilGrowth_ReturnsNil() { // When let result = TrefleMapper.mapToFertilizerSchedule(from: nil) // Then XCTAssertNil(result) } func testMapToFertilizerSchedule_WithNilSoilNutriments_ReturnsNil() { // Given let growth = createGrowthDTO(soilNutriments: nil) // When let result = TrefleMapper.mapToFertilizerSchedule(from: growth) // Then XCTAssertNil(result) } func testMapToFertilizerSchedule_WithHighNutrients_ReturnsBiweeklyBalanced() { // Given let growth = createGrowthDTO(soilNutriments: 8) // When let result = TrefleMapper.mapToFertilizerSchedule(from: growth) // Then XCTAssertNotNil(result) XCTAssertEqual(result?.frequency, .biweekly) XCTAssertEqual(result?.type, .balanced) } func testMapToFertilizerSchedule_WithHighNutrients10_ReturnsBiweeklyBalanced() { // Given let growth = createGrowthDTO(soilNutriments: 10) // When let result = TrefleMapper.mapToFertilizerSchedule(from: growth) // Then XCTAssertNotNil(result) XCTAssertEqual(result?.frequency, .biweekly) XCTAssertEqual(result?.type, .balanced) } func testMapToFertilizerSchedule_WithHighNutrients7_ReturnsBiweeklyBalanced() { // Given let growth = createGrowthDTO(soilNutriments: 7) // When let result = TrefleMapper.mapToFertilizerSchedule(from: growth) // Then XCTAssertNotNil(result) XCTAssertEqual(result?.frequency, .biweekly) XCTAssertEqual(result?.type, .balanced) } func testMapToFertilizerSchedule_WithMediumNutrients_ReturnsMonthlyBalanced() { // Given let growth = createGrowthDTO(soilNutriments: 5) // When let result = TrefleMapper.mapToFertilizerSchedule(from: growth) // Then XCTAssertNotNil(result) XCTAssertEqual(result?.frequency, .monthly) XCTAssertEqual(result?.type, .balanced) } func testMapToFertilizerSchedule_WithMediumNutrients4_ReturnsMonthlyBalanced() { // Given let growth = createGrowthDTO(soilNutriments: 4) // When let result = TrefleMapper.mapToFertilizerSchedule(from: growth) // Then XCTAssertNotNil(result) XCTAssertEqual(result?.frequency, .monthly) XCTAssertEqual(result?.type, .balanced) } func testMapToFertilizerSchedule_WithMediumNutrients6_ReturnsMonthlyBalanced() { // Given let growth = createGrowthDTO(soilNutriments: 6) // When let result = TrefleMapper.mapToFertilizerSchedule(from: growth) // Then XCTAssertNotNil(result) XCTAssertEqual(result?.frequency, .monthly) XCTAssertEqual(result?.type, .balanced) } func testMapToFertilizerSchedule_WithLowNutrients_ReturnsQuarterlyOrganic() { // Given let growth = createGrowthDTO(soilNutriments: 2) // When let result = TrefleMapper.mapToFertilizerSchedule(from: growth) // Then XCTAssertNotNil(result) XCTAssertEqual(result?.frequency, .quarterly) XCTAssertEqual(result?.type, .organic) } func testMapToFertilizerSchedule_WithLowNutrients0_ReturnsQuarterlyOrganic() { // Given let growth = createGrowthDTO(soilNutriments: 0) // When let result = TrefleMapper.mapToFertilizerSchedule(from: growth) // Then XCTAssertNotNil(result) XCTAssertEqual(result?.frequency, .quarterly) XCTAssertEqual(result?.type, .organic) } func testMapToFertilizerSchedule_WithLowNutrients3_ReturnsQuarterlyOrganic() { // Given let growth = createGrowthDTO(soilNutriments: 3) // When let result = TrefleMapper.mapToFertilizerSchedule(from: growth) // Then XCTAssertNotNil(result) XCTAssertEqual(result?.frequency, .quarterly) XCTAssertEqual(result?.type, .organic) } // MARK: - mapToBloomingSeason Tests func testMapToBloomingSeason_WithNilBloomMonths_ReturnsNil() { // When let result = TrefleMapper.mapToBloomingSeason(from: nil) // Then XCTAssertNil(result) } func testMapToBloomingSeason_WithEmptyArray_ReturnsNil() { // When let result = TrefleMapper.mapToBloomingSeason(from: []) // Then XCTAssertNil(result) } func testMapToBloomingSeason_WithSpringMonths_ReturnsSpring() { // When let result = TrefleMapper.mapToBloomingSeason(from: ["mar", "apr", "may"]) // Then XCTAssertNotNil(result) XCTAssertEqual(result, [.spring]) } func testMapToBloomingSeason_WithSummerMonths_ReturnsSummer() { // When let result = TrefleMapper.mapToBloomingSeason(from: ["jun", "jul", "aug"]) // Then XCTAssertNotNil(result) XCTAssertEqual(result, [.summer]) } func testMapToBloomingSeason_WithFallMonths_ReturnsFall() { // When let result = TrefleMapper.mapToBloomingSeason(from: ["sep", "oct", "nov"]) // Then XCTAssertNotNil(result) XCTAssertEqual(result, [.fall]) } func testMapToBloomingSeason_WithWinterMonths_ReturnsWinter() { // When let result = TrefleMapper.mapToBloomingSeason(from: ["dec", "jan", "feb"]) // Then XCTAssertNotNil(result) XCTAssertEqual(result, [.winter]) } func testMapToBloomingSeason_WithMultipleSeasons_ReturnsOrderedSeasons() { // When let result = TrefleMapper.mapToBloomingSeason(from: ["apr", "may", "jun", "jul"]) // Then XCTAssertNotNil(result) XCTAssertEqual(result, [.spring, .summer]) } func testMapToBloomingSeason_WithMixedSeasons_ReturnsAllSeasonsOrdered() { // When let result = TrefleMapper.mapToBloomingSeason(from: ["jan", "apr", "jul", "oct"]) // Then XCTAssertNotNil(result) XCTAssertEqual(result, [.spring, .summer, .fall, .winter]) } func testMapToBloomingSeason_WithUppercaseMonths_ReturnsCorrectSeasons() { // When let result = TrefleMapper.mapToBloomingSeason(from: ["MAR", "APR", "MAY"]) // Then XCTAssertNotNil(result) XCTAssertEqual(result, [.spring]) } func testMapToBloomingSeason_WithMixedCaseMonths_ReturnsCorrectSeasons() { // When let result = TrefleMapper.mapToBloomingSeason(from: ["Mar", "Apr", "May"]) // Then XCTAssertNotNil(result) XCTAssertEqual(result, [.spring]) } func testMapToBloomingSeason_WithInvalidMonths_ReturnsNil() { // When let result = TrefleMapper.mapToBloomingSeason(from: ["xyz", "abc", "123"]) // Then XCTAssertNil(result) } func testMapToBloomingSeason_WithMixedValidAndInvalidMonths_ReturnsValidOnly() { // When let result = TrefleMapper.mapToBloomingSeason(from: ["apr", "xyz", "may"]) // Then XCTAssertNotNil(result) XCTAssertEqual(result, [.spring]) } func testMapToBloomingSeason_WithSingleMonth_ReturnsSingleSeason() { // When let result = TrefleMapper.mapToBloomingSeason(from: ["jul"]) // Then XCTAssertNotNil(result) XCTAssertEqual(result, [.summer]) } // MARK: - mapToHumidityLevel Tests func testMapToHumidityLevel_WithNil_ReturnsNil() { // When let result = TrefleMapper.mapToHumidityLevel(from: nil) // Then XCTAssertNil(result) } func testMapToHumidityLevel_WithLowValue_ReturnsLow() { // When let result = TrefleMapper.mapToHumidityLevel(from: 1) // Then XCTAssertEqual(result, .low) } func testMapToHumidityLevel_WithModerateValue_ReturnsModerate() { // When let result = TrefleMapper.mapToHumidityLevel(from: 4) // Then XCTAssertEqual(result, .moderate) } func testMapToHumidityLevel_WithHighValue_ReturnsHigh() { // When let result = TrefleMapper.mapToHumidityLevel(from: 7) // Then XCTAssertEqual(result, .high) } func testMapToHumidityLevel_WithVeryHighValue_ReturnsVeryHigh() { // When let result = TrefleMapper.mapToHumidityLevel(from: 9) // Then XCTAssertEqual(result, .veryHigh) } // MARK: - mapToGrowthRate Tests func testMapToGrowthRate_WithNil_ReturnsNil() { // When let result = TrefleMapper.mapToGrowthRate(from: nil) // Then XCTAssertNil(result) } func testMapToGrowthRate_WithSlow_ReturnsSlow() { // When let result = TrefleMapper.mapToGrowthRate(from: "slow") // Then XCTAssertEqual(result, .slow) } func testMapToGrowthRate_WithModerate_ReturnsModerate() { // When let result = TrefleMapper.mapToGrowthRate(from: "moderate") // Then XCTAssertEqual(result, .moderate) } func testMapToGrowthRate_WithMedium_ReturnsModerate() { // When let result = TrefleMapper.mapToGrowthRate(from: "medium") // Then XCTAssertEqual(result, .moderate) } func testMapToGrowthRate_WithRapid_ReturnsFast() { // When let result = TrefleMapper.mapToGrowthRate(from: "rapid") // Then XCTAssertEqual(result, .fast) } func testMapToGrowthRate_WithFast_ReturnsFast() { // When let result = TrefleMapper.mapToGrowthRate(from: "fast") // Then XCTAssertEqual(result, .fast) } func testMapToGrowthRate_WithUnknownValue_ReturnsNil() { // When let result = TrefleMapper.mapToGrowthRate(from: "unknown") // Then XCTAssertNil(result) } // MARK: - mapToPlantCareInfo Integration Test func testMapToPlantCareInfo_CreatesCompleteEntity() { // Given let species = createSpeciesDTO( id: 834, commonName: "Swiss cheese plant", scientificName: "Monstera deliciosa", light: 6, atmosphericHumidity: 8, minTempC: 15, maxTempC: 30, soilNutriments: 5, growthRate: "moderate", bloomMonths: ["may", "jun"] ) // When let result = TrefleMapper.mapToPlantCareInfo(from: species) // Then XCTAssertEqual(result.scientificName, "Monstera deliciosa") XCTAssertEqual(result.commonName, "Swiss cheese plant") XCTAssertEqual(result.lightRequirement, .partialShade) // light 6 -> partialShade XCTAssertEqual(result.wateringSchedule.frequency, .weekly) // humidity 8 -> weekly XCTAssertEqual(result.wateringSchedule.amount, .light) // humidity 8 -> light XCTAssertEqual(result.temperatureRange.minimumCelsius, 15) XCTAssertEqual(result.temperatureRange.maximumCelsius, 30) XCTAssertFalse(result.temperatureRange.frostTolerant) XCTAssertNotNil(result.fertilizerSchedule) XCTAssertEqual(result.fertilizerSchedule?.frequency, .monthly) // nutrients 5 -> monthly XCTAssertEqual(result.humidity, .high) // humidity 8 -> high XCTAssertEqual(result.growthRate, .moderate) XCTAssertEqual(result.bloomingSeason, [.spring, .summer]) XCTAssertEqual(result.trefleID, 834) } // MARK: - Helper Methods private func createGrowthDTO( atmosphericHumidity: Int? = nil, soilHumidity: Int? = nil, soilNutriments: Int? = nil, minTempC: Double? = nil, maxTempC: Double? = nil, bloomMonths: [String]? = nil ) -> TrefleGrowthDTO { // Use JSON decoding to create DTO since it has no public init var json: [String: Any] = [:] if let humidity = atmosphericHumidity { json["atmospheric_humidity"] = humidity } if let soil = soilHumidity { json["soil_humidity"] = soil } if let nutrients = soilNutriments { json["soil_nutriments"] = nutrients } if let minTemp = minTempC { json["minimum_temperature"] = ["deg_c": minTemp] } if let maxTemp = maxTempC { json["maximum_temperature"] = ["deg_c": maxTemp] } if let months = bloomMonths { json["bloom_months"] = months } let data = try! JSONSerialization.data(withJSONObject: json) let decoder = JSONDecoder() decoder.keyDecodingStrategy = .convertFromSnakeCase return try! decoder.decode(TrefleGrowthDTO.self, from: data) } private func createSpeciesDTO( id: Int, commonName: String?, scientificName: String, light: Int? = nil, atmosphericHumidity: Int? = nil, minTempC: Double? = nil, maxTempC: Double? = nil, soilNutriments: Int? = nil, growthRate: String? = nil, bloomMonths: [String]? = nil ) -> TrefleSpeciesDTO { var json: [String: Any] = [ "id": id, "slug": scientificName.lowercased().replacingOccurrences(of: " ", with: "-"), "scientific_name": scientificName ] if let name = commonName { json["common_name"] = name } var growthJson: [String: Any] = [:] if let l = light { growthJson["light"] = l } if let h = atmosphericHumidity { growthJson["atmospheric_humidity"] = h } if let min = minTempC { growthJson["minimum_temperature"] = ["deg_c": min] } if let max = maxTempC { growthJson["maximum_temperature"] = ["deg_c": max] } if let n = soilNutriments { growthJson["soil_nutriments"] = n } if let months = bloomMonths { growthJson["bloom_months"] = months } if !growthJson.isEmpty { json["growth"] = growthJson } if let rate = growthRate { json["specifications"] = ["growth_rate": rate] } let data = try! JSONSerialization.data(withJSONObject: json) let decoder = JSONDecoder() decoder.keyDecodingStrategy = .convertFromSnakeCase return try! decoder.decode(TrefleSpeciesDTO.self, from: data) } }