chore: initial ui wiring, swiftlint

Signed-off-by: Alan Brault <alan.brault@visus.io>
This commit is contained in:
2026-08-09 11:10:09 -04:00
parent e1da561ff6
commit 461c516016
19 changed files with 140 additions and 42 deletions
@@ -51,7 +51,9 @@ struct OpenMeteoApiClient: WeatherApiClient {
}
func request(latitude: Double, longitude: Double) async throws -> WeatherApiResponseContract? {
var components = URLComponents(url: Self.baseURL, resolvingAgainstBaseURL: false)!
guard var components = URLComponents(url: Self.baseURL, resolvingAgainstBaseURL: false) else {
throw WeatherApiClientError.invalidURL
}
components.queryItems = [
URLQueryItem(name: "latitude", value: String(latitude)),
URLQueryItem(name: "longitude", value: String(longitude)),
@@ -62,13 +64,17 @@ struct OpenMeteoApiClient: WeatherApiClient {
URLQueryItem(name: "precipitation_unit", value: precipitationUnit)
]
let (data, response) = try await session.data(from: components.url!)
guard let url = components.url else {
throw WeatherApiClientError.invalidURL
}
let (data, response) = try await session.data(from: url)
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
return nil
}
do {
return try decoder.decode(WeatherApiResponseContract.self, from: data)
} catch let error as DecodingError {
@@ -3,5 +3,6 @@ protocol WeatherApiClient: Sendable {
}
enum WeatherApiClientError: Error {
case invalidURL
case decodingFailed(DecodingError)
}
@@ -41,7 +41,7 @@ public actor DefaultForecastRepository: ForecastRepository {
let cutoff = Date().addingTimeInterval(-Self.cacheDuration)
return records
.max(by: { $0.fetchedAt < $1.fetchedAt })
.max { $0.fetchedAt < $1.fetchedAt }
.flatMap { $0.fetchedAt >= cutoff ? $0 : nil }
}
@@ -1,6 +1,6 @@
import Foundation
import Testing
@testable import TempestasInfrastructure
import Testing
@Suite struct OpenMeteoDateDecodingTests {
private static let json = """
@@ -27,7 +27,7 @@ import Testing
"""
@Test func decodesTruncatedIso8601SunriseInRequestedTimeZone() throws {
let timeZone = TimeZone(identifier: "Asia/Tokyo")!
let timeZone = try #require(TimeZone(identifier: "Asia/Tokyo"))
let decoder = OpenMeteoDateDecoding.decoder(timeZone: timeZone)
let contract = try decoder.decode(WeatherApiResponseContract.self, from: Data(Self.json.utf8))
@@ -1,5 +1,5 @@
import Testing
@testable import TempestasInfrastructure
import Testing
@Test func example() async throws {
// Write your test here and use APIs like `#expect(...)` to check expected conditions.
@@ -1,7 +1,7 @@
import Foundation
import SwiftData
import Testing
@testable import TempestasInfrastructure
import Testing
@Suite struct WeatherResponseRecordMapperTests {
private static let json = """
@@ -28,7 +28,8 @@ import Testing
"""
private func makeContract() throws -> WeatherApiResponseContract {
let decoder = OpenMeteoDateDecoding.decoder(timeZone: TimeZone(identifier: "America/Toronto")!)
let timeZone = try #require(TimeZone(identifier: "America/Toronto"))
let decoder = OpenMeteoDateDecoding.decoder(timeZone: timeZone)
return try decoder.decode(WeatherApiResponseContract.self, from: Data(Self.json.utf8))
}
@@ -57,7 +58,7 @@ import Testing
#expect(fetched.count == 1)
#expect(fetched.first?.dailyForecasts.count == 2)
let firstDay = fetched.first?.dailyForecasts.sorted { $0.day < $1.day }.first
let firstDay = fetched.first?.dailyForecasts.min { $0.day < $1.day }
#expect(firstDay?.year == 2026)
#expect(firstDay?.month == 8)
#expect(firstDay?.day == 5)