Skip to content

Commit f435131

Browse files
authored
Expand test coverage and remove low-value tests (#255)
1 parent aa3be69 commit f435131

8 files changed

Lines changed: 463 additions & 40 deletions

Tests/APNSTests/APNSAuthenticationTokenManagerTests.swift

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -88,28 +88,6 @@ final class APNSAuthenticationTokenManagerTests: XCTestCase {
8888
XCTAssertEqual(Double(payload.iat), now, accuracy: 5)
8989
}
9090

91-
func testTokenIsReused() async throws {
92-
93-
let token1 = try await tokenManager.nextValidToken
94-
// 48 minutes later
95-
let temp = clock.now.advanced(by: .init(secondsComponent: 2880, attosecondsComponent: 0))
96-
clock.now = temp
97-
let token2 = try await tokenManager.nextValidToken
98-
99-
XCTAssertEqual(token1, token2)
100-
}
101-
102-
func testTokenIsRefreshed() async throws {
103-
let token1 = try await tokenManager.nextValidToken
104-
105-
// 56 minutes later
106-
let temp = clock.now.advanced(by: .init(secondsComponent: 3360, attosecondsComponent: 0))
107-
clock.now = temp
108-
let token2 = try await tokenManager.nextValidToken
109-
110-
XCTAssertNotEqual(token1, token2)
111-
}
112-
11391
/// The refresh window is `[0, 55min)`: a token is still considered valid one
11492
/// second before 55 minutes, and refreshed at exactly 55 minutes.
11593
func testTokenReusedJustBeforeBoundaryAndRefreshedAtBoundary() async throws {

Tests/APNSTests/APNSClientIntegrationTests.swift

Lines changed: 178 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ final class APNSClientIntegrationTests: XCTestCase {
5050
override func tearDown() async throws {
5151
try await client?.shutdown()
5252
try await server?.shutdown()
53+
client = nil
54+
server = nil
5355
try await super.tearDown()
5456
}
5557

@@ -108,6 +110,10 @@ final class APNSClientIntegrationTests: XCTestCase {
108110
XCTAssertEqual(sent.count, 1)
109111
XCTAssertEqual(sent[0].deviceToken, "1111111111111111111111111111111111111111111111111111111111111111")
110112
XCTAssertEqual(sent[0].pushType, "alert")
113+
114+
let json = try XCTUnwrap(JSONSerialization.jsonObject(with: sent[0].payload) as? NSDictionary)
115+
let aps = try XCTUnwrap(json["aps"] as? NSDictionary)
116+
XCTAssertEqual(aps["badge"] as? Int, 5)
111117
}
112118

113119
func testSendAlertNotification_withSound() async throws {
@@ -128,6 +134,10 @@ final class APNSClientIntegrationTests: XCTestCase {
128134
let sent = server.getSentNotifications()
129135
XCTAssertEqual(sent.count, 1)
130136
XCTAssertEqual(sent[0].deviceToken, "2222222222222222222222222222222222222222222222222222222222222222")
137+
138+
let json = try XCTUnwrap(JSONSerialization.jsonObject(with: sent[0].payload) as? NSDictionary)
139+
let aps = try XCTUnwrap(json["aps"] as? NSDictionary)
140+
XCTAssertEqual(aps["sound"] as? String, "default")
131141
}
132142

133143
// MARK: - Background Notifications
@@ -222,11 +232,177 @@ final class APNSClientIntegrationTests: XCTestCase {
222232
XCTAssertEqual(sent[0].pushType, "complication")
223233
}
224234

235+
// MARK: - Live Activity Notifications
236+
237+
func testSendLiveActivityNotification() async throws {
238+
struct ContentState: Codable {
239+
let value: String
240+
}
241+
242+
let notification = APNSLiveActivityNotification(
243+
expiration: .immediately,
244+
priority: .immediately,
245+
appID: "com.example.app",
246+
contentState: ContentState(value: "update-value"),
247+
event: .update,
248+
timestamp: 1_234_567_890
249+
)
250+
251+
_ = try await client.sendLiveActivityNotification(
252+
notification,
253+
deviceToken: "7777777777777777777777777777777777777777777777777777777777777777"
254+
)
255+
256+
let sent = try XCTUnwrap(server.getSentNotifications().first)
257+
XCTAssertEqual(sent.pushType, "liveactivity")
258+
XCTAssertEqual(sent.topic, "com.example.app.push-type.liveactivity")
259+
260+
let json = try XCTUnwrap(JSONSerialization.jsonObject(with: sent.payload) as? NSDictionary)
261+
let aps = try XCTUnwrap(json["aps"] as? NSDictionary)
262+
XCTAssertEqual(aps["event"] as? String, "update")
263+
XCTAssertNotNil(aps["timestamp"])
264+
XCTAssertNotNil(aps["content-state"])
265+
}
266+
267+
func testSendStartLiveActivityNotification() async throws {
268+
struct Attributes: Codable {}
269+
struct ContentState: Codable {
270+
let value: String
271+
}
272+
273+
let notification = APNSStartLiveActivityNotification(
274+
expiration: .immediately,
275+
priority: .immediately,
276+
appID: "com.example.app",
277+
contentState: ContentState(value: "start-value"),
278+
timestamp: 1_234_567_890,
279+
attributes: Attributes(),
280+
attributesType: "MyActivityAttributes",
281+
alert: .init(title: .raw("Live Activity Started"))
282+
)
283+
284+
_ = try await client.sendStartLiveActivityNotification(
285+
notification,
286+
pushToStartToken: "8888888888888888888888888888888888888888888888888888888888888888"
287+
)
288+
289+
let sent = try XCTUnwrap(server.getSentNotifications().first)
290+
XCTAssertEqual(sent.pushType, "liveactivity")
291+
XCTAssertEqual(sent.topic, "com.example.app.push-type.liveactivity")
292+
293+
let json = try XCTUnwrap(JSONSerialization.jsonObject(with: sent.payload) as? NSDictionary)
294+
let aps = try XCTUnwrap(json["aps"] as? NSDictionary)
295+
XCTAssertEqual(aps["event"] as? String, "start")
296+
XCTAssertEqual(aps["attributes-type"] as? String, "MyActivityAttributes")
297+
}
298+
299+
// MARK: - Push To Talk Notifications
300+
301+
func testSendPushToTalkNotification() async throws {
302+
struct Payload: Encodable {
303+
let activeSpeaker = "Alice"
304+
}
305+
306+
let notification = APNSPushToTalkNotification(
307+
appID: "com.example.app",
308+
payload: Payload()
309+
)
310+
311+
_ = try await client.sendPushToTalkNotification(
312+
notification,
313+
deviceToken: "9999999999999999999999999999999999999999999999999999999999999999"
314+
)
315+
316+
let sent = try XCTUnwrap(server.getSentNotifications().first)
317+
XCTAssertEqual(sent.pushType, "pushtotalk")
318+
XCTAssertEqual(sent.topic, "com.example.app.voip-ptt")
319+
XCTAssertEqual(sent.deviceToken, "9999999999999999999999999999999999999999999999999999999999999999")
320+
// payload shape asserted in encode tests (see PR1)
321+
}
322+
323+
// MARK: - Widgets Notifications
324+
325+
func testSendWidgetsNotification() async throws {
326+
let notification = APNSWidgetsNotification(appID: "com.example.app")
327+
328+
_ = try await client.sendWidgetsNotification(
329+
notification: notification,
330+
deviceToken: "aaaa1111aaaa1111aaaa1111aaaa1111aaaa1111aaaa1111aaaa1111aaaa1111"
331+
)
332+
333+
let sent = try XCTUnwrap(server.getSentNotifications().first)
334+
XCTAssertEqual(sent.pushType, "widgets")
335+
XCTAssertEqual(sent.topic, "com.example.app.push-type.widgets")
336+
337+
let expectedJSONString = """
338+
{"aps":{"content-changed":true}}
339+
"""
340+
let jsonObject1 = try JSONSerialization.jsonObject(with: sent.payload) as! NSDictionary
341+
let jsonObject2 = try JSONSerialization.jsonObject(with: expectedJSONString.data(using: .utf8)!) as! NSDictionary
342+
XCTAssertEqual(jsonObject1, jsonObject2)
343+
}
344+
345+
// MARK: - Location Notifications
346+
347+
func testSendLocationNotification() async throws {
348+
let notification = APNSLocationNotification(
349+
priority: .immediately,
350+
appID: "com.example.app"
351+
)
352+
353+
_ = try await client.sendLocationNotification(
354+
notification,
355+
deviceToken: "bbbb2222bbbb2222bbbb2222bbbb2222bbbb2222bbbb2222bbbb2222bbbb2222"
356+
)
357+
358+
let sent = try XCTUnwrap(server.getSentNotifications().first)
359+
XCTAssertEqual(sent.pushType, "location")
360+
XCTAssertEqual(sent.topic, "com.example.app.location-query")
361+
// sendLocationNotification forces expiration to `.none`, so no header should be sent.
362+
XCTAssertNil(sent.expiration)
363+
}
364+
365+
// MARK: - Alert Notification Header Variants
366+
367+
func testSendAlert_consideringDevicePower() async throws {
368+
let notification = APNSAlertNotification(
369+
alert: .init(title: .raw("Power Test")),
370+
expiration: .immediately,
371+
priority: .consideringDevicePower,
372+
topic: "com.example.app",
373+
payload: EmptyPayload()
374+
)
375+
376+
_ = try await client.sendAlertNotification(
377+
notification,
378+
deviceToken: "cccc3333cccc3333cccc3333cccc3333cccc3333cccc3333cccc3333cccc3333"
379+
)
380+
381+
let sent = try XCTUnwrap(server.getSentNotifications().first)
382+
XCTAssertEqual(sent.priority, "5")
383+
}
384+
385+
func testSendAlert_noExpirationHeaderWhenNone() async throws {
386+
let notification = APNSAlertNotification(
387+
alert: .init(title: .raw("Expiration None Test")),
388+
expiration: .none,
389+
priority: .immediately,
390+
topic: "com.example.app",
391+
payload: EmptyPayload()
392+
)
393+
394+
_ = try await client.sendAlertNotification(
395+
notification,
396+
deviceToken: "dddd4444dddd4444dddd4444dddd4444dddd4444dddd4444dddd4444dddd4444"
397+
)
398+
399+
let sent = try XCTUnwrap(server.getSentNotifications().first)
400+
XCTAssertNil(sent.expiration)
401+
}
402+
225403
// MARK: - Multiple Notifications
226404

227405
func testSendMultipleNotifications() async throws {
228-
server.clearSentNotifications()
229-
230406
// Send 3 different notifications
231407
let alert = APNSAlertNotification(
232408
alert: .init(title: .raw("Alert")),
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the APNSwift open source project
4+
//
5+
// Copyright (c) 2024 the APNSwift project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of APNSwift project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import APNSCore
16+
import XCTest
17+
18+
final class APNSEnvironmentTests: XCTestCase {
19+
func testProduction() {
20+
XCTAssertEqual(APNSEnvironment.production.url, "https://api.push.apple.com")
21+
XCTAssertEqual(APNSEnvironment.production.port, 443)
22+
XCTAssertEqual(APNSEnvironment.production.absoluteURL, "https://api.push.apple.com:443/3/device")
23+
}
24+
25+
func testDevelopment() {
26+
XCTAssertEqual(APNSEnvironment.development.url, "https://api.development.push.apple.com")
27+
XCTAssertEqual(APNSEnvironment.development.port, 443)
28+
XCTAssertEqual(APNSEnvironment.development.absoluteURL, "https://api.development.push.apple.com:443/3/device")
29+
}
30+
31+
@available(*, deprecated, message: "Intentionally exercising the deprecated `.sandbox` alias.")
32+
func testSandbox_isAliasForDevelopment() {
33+
// `.sandbox` is deprecated in favor of `.development`, but must remain behaviorally identical.
34+
let sandbox = APNSEnvironment.sandbox
35+
XCTAssertEqual(sandbox.url, APNSEnvironment.development.url)
36+
XCTAssertEqual(sandbox.port, APNSEnvironment.development.port)
37+
XCTAssertEqual(sandbox.absoluteURL, APNSEnvironment.development.absoluteURL)
38+
}
39+
40+
func testCustom_composesURLAndPort() {
41+
let custom = APNSEnvironment.custom(url: "http://127.0.0.1", port: 8080)
42+
XCTAssertEqual(custom.url, "http://127.0.0.1")
43+
XCTAssertEqual(custom.port, 8080)
44+
XCTAssertEqual(custom.absoluteURL, "http://127.0.0.1:8080/3/device")
45+
}
46+
47+
func testCustom_defaultsPortTo443() {
48+
let custom = APNSEnvironment.custom(url: "https://example.com")
49+
XCTAssertEqual(custom.port, 443)
50+
XCTAssertEqual(custom.absoluteURL, "https://example.com:443/3/device")
51+
}
52+
}
53+
54+
final class APNSBroadcastEnvironmentTests: XCTestCase {
55+
func testProduction() {
56+
XCTAssertEqual(APNSBroadcastEnvironment.production.url, "https://api-manage-broadcast.push.apple.com")
57+
XCTAssertEqual(APNSBroadcastEnvironment.production.port, 2196)
58+
}
59+
60+
func testDevelopment() {
61+
XCTAssertEqual(APNSBroadcastEnvironment.development.url, "https://api-manage-broadcast.sandbox.push.apple.com")
62+
XCTAssertEqual(APNSBroadcastEnvironment.development.port, 2195)
63+
}
64+
65+
func testCustom_composesURLAndPort() {
66+
let custom = APNSBroadcastEnvironment.custom(url: "http://127.0.0.1", port: 9090)
67+
XCTAssertEqual(custom.url, "http://127.0.0.1")
68+
XCTAssertEqual(custom.port, 9090)
69+
}
70+
71+
func testCustom_defaultsPortTo443() {
72+
let custom = APNSBroadcastEnvironment.custom(url: "https://example.com")
73+
XCTAssertEqual(custom.port, 443)
74+
}
75+
}

0 commit comments

Comments
 (0)