Skip to content

Commit 7c8c13e

Browse files
committed
Add facade request configuration tests
Expose internal request builders for summaries, recommendations, history, and API test endpoints, and add unit tests that validate their configuration without network calls.
1 parent c069d67 commit 7c8c13e

File tree

8 files changed

+262
-25
lines changed

8 files changed

+262
-25
lines changed

Sources/MusadoraKit/History/MHistory+RecentlyPlayed.swift

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,14 @@ public extension MHistory {
1212
static func recentlyPlayed(limit: Int? = nil) async throws -> MusicItemCollection<
1313
RecentlyPlayedMusicItem
1414
> {
15-
var request = MusicRecentlyPlayedContainerRequest()
16-
request.limit = limit
15+
let request = recentlyPlayedContainerRequest(limit: limit)
1716
let response = try await request.response()
1817
return try await response.items.collectingAll(upTo: limit)
1918
}
2019

2120
@available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, *)
2221
static func recentlyPlayedSongs(limit: Int? = nil) async throws -> Songs {
23-
var request = MusicRecentlyPlayedRequest<Song>()
24-
request.limit = limit
22+
let request = recentlyPlayedSongsRequest(limit: limit)
2523
let response = try await request.response()
2624
return response.items
2725
}
@@ -43,11 +41,34 @@ public extension MHistory {
4341
/// ```
4442
@available(iOS 16.0, tvOS 16.0, watchOS 9.0, *, macOS 14.0, macCatalyst 17.0, *)
4543
static func mostPlayedSongs(limit: Int = 100) async throws -> Songs {
44+
let request = mostPlayedSongsRequest(limit: limit)
45+
let response = try await request.response()
46+
return try await response.items.collectingAll(upTo: limit)
47+
}
48+
}
49+
50+
@available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, *)
51+
extension MHistory {
52+
internal static func recentlyPlayedContainerRequest(limit: Int?) -> MusicRecentlyPlayedContainerRequest {
53+
var request = MusicRecentlyPlayedContainerRequest()
54+
request.limit = limit
55+
return request
56+
}
57+
58+
internal static func recentlyPlayedSongsRequest(limit: Int?) -> MusicRecentlyPlayedRequest<Song> {
59+
var request = MusicRecentlyPlayedRequest<Song>()
60+
request.limit = limit
61+
return request
62+
}
63+
}
64+
65+
@available(iOS 16.0, tvOS 16.0, watchOS 9.0, *, macOS 14.0, macCatalyst 17.0, *)
66+
extension MHistory {
67+
internal static func mostPlayedSongsRequest(limit: Int) -> MusicLibraryRequest<Song> {
4668
var request = MusicLibraryRequest<Song>()
4769
request.limit = limit
4870
request.sort(by: \.playCount, ascending: false)
49-
let response = try await request.response()
50-
return try await response.items.collectingAll(upTo: limit)
71+
return request
5172
}
5273
}
5374

Sources/MusadoraKit/MusadoraKit.swift

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,7 @@ extension MusadoraKit {
5858
/// }
5959
/// ```
6060
public static func test() async throws {
61-
var components = AppleMusicURLComponents()
62-
components.path = "test"
63-
64-
guard let url = components.url else {
65-
throw URLError(.badURL)
66-
}
61+
let url = try testEndpointURL()
6762

6863
let urlRequest = URLRequest(url: url)
6964
let request = MusicDataRequest(urlRequest: urlRequest)
@@ -89,4 +84,15 @@ extension MusadoraKit {
8984
throw error
9085
}
9186
}
87+
88+
internal static func testEndpointURL(components: MusicURLComponents = AppleMusicURLComponents()) throws -> URL {
89+
var components = components
90+
components.path = "test"
91+
92+
guard let url = components.url else {
93+
throw URLError(.badURL)
94+
}
95+
96+
return url
97+
}
9298
}

Sources/MusadoraKit/MusicSummaries/MSummary+Replay.swift

Lines changed: 87 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,13 @@ public extension MSummary {
2424
include: [String]? = nil,
2525
extend: [String]? = nil
2626
) async throws -> MusicSummaryResponse {
27-
try await response(for: .latestYear, views: views, languageTag: languageTag, include: include, extend: extend)
27+
let request = requestForLatest(
28+
views: views,
29+
languageTag: languageTag,
30+
include: include,
31+
extend: extend
32+
)
33+
return try await request.response()
2834
}
2935

3036
/// Fetch the user's latest monthly Replay summary (previous calendar month).
@@ -45,17 +51,15 @@ public extension MSummary {
4551
now: Date = .now,
4652
timeZone: TimeZone = TimeZone(secondsFromGMT: 0) ?? .current
4753
) async throws -> MusicSummaryResponse {
48-
guard let period = MusicSummaryPeriod.latestMonth(calendar: calendar, now: now, timeZone: timeZone) else {
49-
throw MusadoraKitError.invalidSummaryPeriod
50-
}
51-
52-
return try await response(
53-
for: period,
54+
let context = LatestMonthContext(calendar: calendar, now: now, timeZone: timeZone)
55+
let request = try requestForLatestMonth(
5456
views: views,
5557
languageTag: languageTag,
5658
include: include,
57-
extend: extend
59+
extend: extend,
60+
context: context
5861
)
62+
return try await request.response()
5963
}
6064

6165
/// Fetch the user's latest Replay top artists for the most recent eligible year.
@@ -161,20 +165,92 @@ public extension MSummary {
161165
}
162166
}
163167

164-
private extension MSummary {
165-
static func response(
168+
extension MSummary {
169+
internal struct LatestMonthContext {
170+
let calendar: Calendar
171+
let now: Date
172+
let timeZone: TimeZone
173+
174+
init(
175+
calendar: Calendar = .init(identifier: .gregorian),
176+
now: Date = .now,
177+
timeZone: TimeZone = TimeZone(secondsFromGMT: 0) ?? .current
178+
) {
179+
self.calendar = calendar
180+
self.now = now
181+
self.timeZone = timeZone
182+
}
183+
}
184+
185+
internal static func requestForLatest(
186+
views: Set<MusicSummaryView>,
187+
languageTag: String?,
188+
include: [String]?,
189+
extend: [String]?
190+
) -> MusicSummaryRequest {
191+
makeRequest(
192+
period: .latestYear,
193+
views: views,
194+
languageTag: languageTag,
195+
include: include,
196+
extend: extend
197+
)
198+
}
199+
200+
internal static func requestForLatestMonth(
201+
views: Set<MusicSummaryView>,
202+
languageTag: String?,
203+
include: [String]?,
204+
extend: [String]?,
205+
context: LatestMonthContext
206+
) throws -> MusicSummaryRequest {
207+
guard let period = MusicSummaryPeriod.latestMonth(
208+
calendar: context.calendar,
209+
now: context.now,
210+
timeZone: context.timeZone
211+
) else {
212+
throw MusadoraKitError.invalidSummaryPeriod
213+
}
214+
215+
return makeRequest(
216+
period: period,
217+
views: views,
218+
languageTag: languageTag,
219+
include: include,
220+
extend: extend
221+
)
222+
}
223+
224+
private static func response(
166225
for period: MusicSummaryPeriod,
167226
views: Set<MusicSummaryView>,
168227
languageTag: String?,
169228
include: [String]? = nil,
170229
extend: [String]? = nil
171230
) async throws -> MusicSummaryResponse {
231+
let request = makeRequest(
232+
period: period,
233+
views: views,
234+
languageTag: languageTag,
235+
include: include,
236+
extend: extend
237+
)
238+
return try await request.response()
239+
}
240+
241+
internal static func makeRequest(
242+
period: MusicSummaryPeriod,
243+
views: Set<MusicSummaryView>,
244+
languageTag: String?,
245+
include: [String]? = nil,
246+
extend: [String]? = nil
247+
) -> MusicSummaryRequest {
172248
var request = MusicSummaryRequest()
173249
request.period = period
174250
request.views = views
175251
request.languageTag = languageTag
176252
request.include = include
177253
request.extend = extend
178-
return try await request.response()
254+
return request
179255
}
180256
}

Sources/MusadoraKit/Recommendations/MRecommendation+personal.swift

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,14 @@ public extension MRecommendation {
9292
@available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, visionOS 1.0, *)
9393
extension MRecommendation {
9494
private static func personalRecommendations(_ limit: Int? = nil) async throws -> PersonalRecommendations {
95-
var request = MusicPersonalRecommendationsRequest()
96-
request.limit = limit
95+
let request = personalRecommendationsRequest(limit: limit)
9796
let response = try await request.response()
9897
return try await response.recommendations.collectingAll(upTo: limit)
9998
}
99+
100+
internal static func personalRecommendationsRequest(limit: Int?) -> MusicPersonalRecommendationsRequest {
101+
var request = MusicPersonalRecommendationsRequest()
102+
request.limit = limit
103+
return request
104+
}
100105
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
@testable import MusadoraKit
2+
import MusicKit
3+
import Testing
4+
5+
@Suite
6+
struct MHistoryRecentlyPlayedRequestTests {
7+
@available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, *)
8+
@Test
9+
func recentlyPlayedContainerRequestSetsLimit() {
10+
let request = MHistory.recentlyPlayedContainerRequest(limit: 25)
11+
#expect(request.limit == 25)
12+
}
13+
14+
@available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, *)
15+
@Test
16+
func recentlyPlayedSongsRequestSetsLimit() {
17+
let request = MHistory.recentlyPlayedSongsRequest(limit: 10)
18+
#expect(request.limit == 10)
19+
}
20+
21+
@available(iOS 16.0, tvOS 16.0, watchOS 9.0, macOS 14.0, macCatalyst 17.0, *)
22+
@Test
23+
func mostPlayedSongsRequestSetsLimit() {
24+
let request = MHistory.mostPlayedSongsRequest(limit: 100)
25+
#expect(request.limit == 100)
26+
}
27+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import Foundation
2+
@testable import MusadoraKit
3+
import Testing
4+
5+
private struct BadURLMusicComponents: MusicURLComponents {
6+
var queryItems: [URLQueryItem]?
7+
var path: String = ""
8+
9+
var url: URL? {
10+
nil
11+
}
12+
}
13+
14+
@Suite
15+
struct MusadoraKitTestEndpointTests {
16+
@Test
17+
func testEndpointURLBuilds() throws {
18+
let url = try MusadoraKit.testEndpointURL()
19+
expectEndpoint(url, equals: "https://api.music.apple.com/v1/test")
20+
}
21+
22+
@Test
23+
func testEndpointURLWithBadComponentsThrows() throws {
24+
let error = #expect(throws: URLError.self) {
25+
try MusadoraKit.testEndpointURL(components: BadURLMusicComponents())
26+
}
27+
28+
#expect(error == URLError(.badURL))
29+
}
30+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
@testable import MusadoraKit
2+
import MusicKit
3+
import Testing
4+
5+
@Suite
6+
struct MRecommendationPersonalTests {
7+
@Test
8+
func personalRecommendationsRequestSetsLimit() {
9+
let request = MRecommendation.personalRecommendationsRequest(limit: 12)
10+
#expect(request.limit == 12)
11+
}
12+
13+
@Test
14+
func personalRecommendationsRequestAllowsNilLimit() {
15+
let request = MRecommendation.personalRecommendationsRequest(limit: nil)
16+
#expect(request.limit == nil)
17+
}
18+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import Foundation
2+
@testable import MusadoraKit
3+
import Testing
4+
5+
@Suite
6+
struct MSummaryFacadeTests {
7+
@Test
8+
func latestRequestUsesDefaults() throws {
9+
let request = MSummary.requestForLatest(
10+
views: [.topAlbums, .topArtists, .topSongs],
11+
languageTag: nil,
12+
include: nil,
13+
extend: nil
14+
)
15+
16+
#expect(request.period == .latestYear)
17+
#expect(request.views == [.topAlbums, .topArtists, .topSongs])
18+
#expect(request.languageTag == nil)
19+
#expect(request.include == nil)
20+
#expect(request.extend == nil)
21+
}
22+
23+
@Test
24+
func latestMonthRequestBuildsExpectedPeriod() throws {
25+
let calendar = Calendar(identifier: .gregorian)
26+
let timeZone = TimeZone(secondsFromGMT: 0) ?? .current
27+
var components = DateComponents()
28+
components.calendar = calendar
29+
components.timeZone = timeZone
30+
components.year = 2025
31+
components.month = 2
32+
components.day = 15
33+
let now = try #require(components.date)
34+
35+
let context = MSummary.LatestMonthContext(
36+
calendar: calendar,
37+
now: now,
38+
timeZone: timeZone
39+
)
40+
let request = try MSummary.requestForLatestMonth(
41+
views: [.topSongs],
42+
languageTag: "en-US",
43+
include: ["relationships"],
44+
extend: ["extended-attributes"],
45+
context: context
46+
)
47+
48+
#expect(request.period == .month(year: 2025, month: 1))
49+
#expect(request.views == [.topSongs])
50+
#expect(request.languageTag == "en-US")
51+
#expect(request.include == ["relationships"])
52+
#expect(request.extend == ["extended-attributes"])
53+
}
54+
}

0 commit comments

Comments
 (0)