From 2bfe9a90cd129e2fbd1d6ec5a991f362d18cd587 Mon Sep 17 00:00:00 2001 From: Ben Kreeger Date: Fri, 21 Nov 2025 16:53:24 -0600 Subject: [PATCH] Added network call for facts --- .gitignore | 62 ++++++++++++++++++ .../xcshareddata/swiftpm/Package.resolved | 15 +++++ .../UserInterfaceState.xcuserstate | Bin 24995 -> 0 bytes .../xcschemes/xcschememanagement.plist | 32 --------- Meow/.gitignore | 8 --- Meow/Package.swift | 23 ++++++- Meow/Sources/Meow/Meow.swift | 23 ++++--- Meow/Tests/MeowTests/Fixtures.swift | 20 ++++++ Meow/Tests/MeowTests/Fixtures/facts.json | 9 +++ Meow/Tests/MeowTests/MeowTests.swift | 32 ++++++++- 10 files changed, 173 insertions(+), 51 deletions(-) create mode 100644 Meow.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved delete mode 100644 Meow.xcodeproj/project.xcworkspace/xcuserdata/bkreeger.xcuserdatad/UserInterfaceState.xcuserstate delete mode 100644 Meow.xcodeproj/xcuserdata/bkreeger.xcuserdatad/xcschemes/xcschememanagement.plist delete mode 100644 Meow/.gitignore create mode 100644 Meow/Tests/MeowTests/Fixtures.swift create mode 100644 Meow/Tests/MeowTests/Fixtures/facts.json diff --git a/.gitignore b/.gitignore index e69de29..52fe2f7 100644 --- a/.gitignore +++ b/.gitignore @@ -0,0 +1,62 @@ +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## User settings +xcuserdata/ + +## Obj-C/Swift specific +*.hmap + +## App packaging +*.ipa +*.dSYM.zip +*.dSYM + +## Playgrounds +timeline.xctimeline +playground.xcworkspace + +# Swift Package Manager +# +# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. +# Packages/ +# Package.pins +# Package.resolved +# *.xcodeproj +# +# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata +# hence it is not needed unless you have added a package configuration file to your project +# .swiftpm + +.build/ + +# CocoaPods +# +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control +# +# Pods/ +# +# Add this line if you want to avoid checking in source code from the Xcode workspace +# *.xcworkspace + +# Carthage +# +# Add this line if you want to avoid checking in source code from Carthage dependencies. +# Carthage/Checkouts + +Carthage/Build/ + +# fastlane +# +# It is recommended to not store the screenshots in the git repo. +# Instead, use fastlane to re-generate the screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/#source-control + +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots/**/*.png +fastlane/test_output diff --git a/Meow.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Meow.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 0000000..7c5d580 --- /dev/null +++ b/Meow.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,15 @@ +{ + "originHash" : "b5a4d1a917ee731eae9d288a993a1e905ecfa9e87cb169c76452216c83b5b4f2", + "pins" : [ + { + "identity" : "mocker", + "kind" : "remoteSourceControl", + "location" : "https://github.com/WeTransfer/Mocker", + "state" : { + "revision" : "95fa785c751f6bc40c49e112d433c3acf8417a97", + "version" : "3.0.2" + } + } + ], + "version" : 3 +} diff --git a/Meow.xcodeproj/project.xcworkspace/xcuserdata/bkreeger.xcuserdatad/UserInterfaceState.xcuserstate b/Meow.xcodeproj/project.xcworkspace/xcuserdata/bkreeger.xcuserdatad/UserInterfaceState.xcuserstate deleted file mode 100644 index aeb479c8efa15b2f85e1cf2fc5660c55f446c098..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24995 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

KHNlm(Gc}N!s4U< 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 diff --git a/Meow.xcodeproj/xcuserdata/bkreeger.xcuserdatad/xcschemes/xcschememanagement.plist b/Meow.xcodeproj/xcuserdata/bkreeger.xcuserdatad/xcschemes/xcschememanagement.plist deleted file mode 100644 index d3f60a2..0000000 --- a/Meow.xcodeproj/xcuserdata/bkreeger.xcuserdatad/xcschemes/xcschememanagement.plist +++ /dev/null @@ -1,32 +0,0 @@ - - - - - SchemeUserState - - App.xcscheme_^#shared#^_ - - orderHint - 0 - - - SuppressBuildableAutocreation - - C3BB763A2ED0C38800D56534 - - primary - - - C3BB76472ED0C38900D56534 - - primary - - - C3BB76512ED0C38900D56534 - - primary - - - - - diff --git a/Meow/.gitignore b/Meow/.gitignore deleted file mode 100644 index 0023a53..0000000 --- a/Meow/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -.DS_Store -/.build -/Packages -xcuserdata/ -DerivedData/ -.swiftpm/configuration/registries.json -.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata -.netrc diff --git a/Meow/Package.swift b/Meow/Package.swift index f410d81..3c48479 100644 --- a/Meow/Package.swift +++ b/Meow/Package.swift @@ -3,6 +3,14 @@ import PackageDescription +let commonSwiftSettings: [SwiftSetting] = [ + .enableExperimentalFeature("StrictConcurrency"), + .enableUpcomingFeature("InferIsolatedConformances"), + .enableUpcomingFeature("NonisolatedNonsendingByDefault"), + .defaultIsolation(nil), + .swiftLanguageMode(.v6) +] + let package = Package( name: "Meow", platforms: [.iOS(.v18), .macOS(.v15)], @@ -13,15 +21,26 @@ let package = Package( targets: ["Meow"] ), ], + dependencies: [ + .package(url: "https://github.com/WeTransfer/Mocker", from: "3.0.2"), + ], targets: [ // Targets are the basic building blocks of a package, defining a module or a test suite. // Targets can depend on other targets in this package and products from dependencies. .target( - name: "Meow" + name: "Meow", + swiftSettings: commonSwiftSettings ), .testTarget( name: "MeowTests", - dependencies: ["Meow"] + dependencies: [ + "Meow", + "Mocker" + ], + resources: [ + .process("Fixtures") + ], + swiftSettings: commonSwiftSettings ), ] ) diff --git a/Meow/Sources/Meow/Meow.swift b/Meow/Sources/Meow/Meow.swift index 0a53ca7..f9a08dd 100644 --- a/Meow/Sources/Meow/Meow.swift +++ b/Meow/Sources/Meow/Meow.swift @@ -7,30 +7,37 @@ import Foundation public struct Meow { let baseURL: URL - let session: URLSession + let urlSession: URLSession - public init(baseURL: URL, session: URLSession = .shared) { + public init(baseURL: URL, urlSession: URLSession = .shared) { self.baseURL = baseURL - self.session = session + self.urlSession = urlSession } - public func getFacts() async throws -> [String] { - let request = try generateRequest(path: "/") + public func getFacts(count: Int? = nil) async throws -> [String] { + let params = count.map { ["count": $0] } ?? [:] + let request = try generateRequest(path: "/", params: params) let response: FactsResponse = try await decodeRequest(request: request) return response.data } // MARK: - Private functionality - private func generateRequest(path: String) throws -> URLRequest { + private func generateRequest(path: String, params: [String: CustomStringConvertible] = [:]) throws -> URLRequest { guard let url = URL(string: path, relativeTo: baseURL) else { throw MeowError.requestError("Couldn't generate URL from path: \(path), baseURL: \(baseURL)") } - return URLRequest(url: url) + + let queryItems = params + .compactMap { URLQueryItem(name: $0, value: $1.description) } + .sorted { a, b in a.name < b.name } + return queryItems.count > 0 + ? URLRequest(url: url.appending(queryItems: queryItems)) + : URLRequest(url: url) } private func decodeRequest(request: URLRequest) async throws -> T { - let (data, response) = try await session.data(for: request) + let (data, response) = try await urlSession.data(for: request) guard let response = response as? HTTPURLResponse else { throw MeowError.connectionError("Couldn't get HTTP response from request \(request)") } diff --git a/Meow/Tests/MeowTests/Fixtures.swift b/Meow/Tests/MeowTests/Fixtures.swift new file mode 100644 index 0000000..dbd192c --- /dev/null +++ b/Meow/Tests/MeowTests/Fixtures.swift @@ -0,0 +1,20 @@ +// +// Fixtures.swift +// MeowTests +// + +import Foundation + +final class Fixtures { + static let facts: Data = loadFixture("facts") + + private static func loadFixture(_ name: String) -> Data { + guard let url = Bundle.module.url(forResource: name, withExtension: "json") else { + fatalError("Failed to load fixture: \(name).json") + } + guard let data = try? Data(contentsOf: url) else { + fatalError("Unable to get Data from contents of \(url)") + } + return data + } +} diff --git a/Meow/Tests/MeowTests/Fixtures/facts.json b/Meow/Tests/MeowTests/Fixtures/facts.json new file mode 100644 index 0000000..4b26196 --- /dev/null +++ b/Meow/Tests/MeowTests/Fixtures/facts.json @@ -0,0 +1,9 @@ +{ + "data": [ + "Abraham Lincoln loved cats. He had four of them while he lived in the White House.", + "Many cats cannot properly digest cows milk. Milk and milk products give them diarrhea.", + "Tylenol and chocolate are both poisonous to cats.", + "The average cat food meal is the equivalent to about five mice.", + "The way you treat kittens in the early stages of it's life will render it's personality traits later in life." + ] +} diff --git a/Meow/Tests/MeowTests/MeowTests.swift b/Meow/Tests/MeowTests/MeowTests.swift index 1456482..c442d0e 100644 --- a/Meow/Tests/MeowTests/MeowTests.swift +++ b/Meow/Tests/MeowTests/MeowTests.swift @@ -5,15 +5,45 @@ import Foundation import Testing +import Mocker @testable import Meow @Suite("Meow Tests") struct MeowTests { private let baseURL = URL(string: "https://meow.meow")! + let urlSession: URLSession = { + let configuration = URLSessionConfiguration.default + configuration.protocolClasses = [MockingURLProtocol.self] + return URLSession(configuration: configuration) + }() @Test func constructor() async throws { - let instance = Meow(baseURL: baseURL) + let instance = vendInstance() #expect(instance.baseURL == baseURL) } + + @Test + func getFacts() async throws { + let instance = vendInstance() + let count = 5 + mockPath("/", queryItems: ["count": count], data: [.get: Fixtures.facts]) + let facts = try await instance.getFacts(count: count) + #expect(facts.count == count) + } + + // MARK: - Private functionality + + private func vendInstance() -> Meow { + return Meow(baseURL: baseURL, urlSession: urlSession) + } + + private func mockPath(_ path: String, headers: [String: String] = [:], queryItems: [String: CustomStringConvertible] = [:], data: [Mock.HTTPMethod: Data] = [:]) { + let urlQueryItems = queryItems + .compactMap({ URLQueryItem(name: $0, value: $1.description) }) + .sorted { a, b in a.name < b.name } + let base = baseURL.appendingPathComponent(path) + let url = urlQueryItems.isEmpty ? base : base.appending(queryItems: urlQueryItems) + Mock(url: url, ignoreQuery: false, contentType: .json, statusCode: 200, data: data, additionalHeaders: headers).register() + } }