Skip to content

Commit b719dba

Browse files
Improved code coverage from 34% to 50%
1 parent 158cddb commit b719dba

File tree

3 files changed

+356
-11
lines changed

3 files changed

+356
-11
lines changed

Tests/WebPushTests/Base64URLCodingTests.swift

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,26 @@ import Foundation
1010
import Testing
1111
@testable import WebPush
1212

13-
@Test func base64URLDecoding() async throws {
14-
let string = ">>> Hello, swift-webpush world??? 🎉"
15-
let base64Encoded = "Pj4+IEhlbGxvLCBzd2lmdC13ZWJwdXNoIHdvcmxkPz8/IPCfjok="
16-
let base64URLEncoded = "Pj4-IEhlbGxvLCBzd2lmdC13ZWJwdXNoIHdvcmxkPz8_IPCfjok"
17-
#expect(String(decoding: Data(base64URLEncoded: base64Encoded)!, as: UTF8.self) == string)
18-
#expect(String(decoding: Data(base64URLEncoded: base64URLEncoded)!, as: UTF8.self) == string)
19-
}
13+
@Suite("Base 64 URL Coding")
14+
struct Base64URLCoding {
15+
@Test func base64URLDecoding() async throws {
16+
let string = ">>> Hello, swift-webpush world??? 🎉"
17+
let base64Encoded = "Pj4+IEhlbGxvLCBzd2lmdC13ZWJwdXNoIHdvcmxkPz8/IPCfjok="
18+
let base64URLEncoded = "Pj4-IEhlbGxvLCBzd2lmdC13ZWJwdXNoIHdvcmxkPz8_IPCfjok"
19+
#expect(String(decoding: Data(base64URLEncoded: base64Encoded)!, as: UTF8.self) == string)
20+
#expect(String(decoding: Data(base64URLEncoded: base64URLEncoded)!, as: UTF8.self) == string)
21+
#expect(String(decoding: [UInt8](base64URLEncoded: base64Encoded)!, as: UTF8.self) == string)
22+
#expect(String(decoding: [UInt8](base64URLEncoded: base64URLEncoded)!, as: UTF8.self) == string)
23+
}
24+
25+
@Test func invalidBase64URLDecoding() async throws {
26+
#expect(Data(base64URLEncoded: " ") == nil)
27+
}
2028

21-
@Test func base64URLEncoding() async throws {
22-
let string = ">>> Hello, swift-webpush world??? 🎉"
23-
let base64URLEncoded = "Pj4-IEhlbGxvLCBzd2lmdC13ZWJwdXNoIHdvcmxkPz8_IPCfjok"
24-
#expect(Array(string.utf8).base64URLEncodedString() == base64URLEncoded)
29+
@Test func base64URLEncoding() async throws {
30+
let string = ">>> Hello, swift-webpush world??? 🎉"
31+
let base64URLEncoded = "Pj4-IEhlbGxvLCBzd2lmdC13ZWJwdXNoIHdvcmxkPz8_IPCfjok"
32+
#expect([UInt8](string.utf8).base64URLEncodedString() == base64URLEncoded)
33+
#expect(Data(string.utf8).base64URLEncodedString() == base64URLEncoded)
34+
}
2535
}
Lines changed: 304 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,304 @@
1+
//
2+
// VAPIDConfigurationTests.swift
3+
// swift-webpush
4+
//
5+
// Created by Dimitri Bouniol on 2024-12-15.
6+
// Copyright © 2024 Mochi Development, Inc. All rights reserved.
7+
//
8+
9+
import Crypto
10+
import Foundation
11+
import Testing
12+
@testable import WebPush
13+
14+
@Suite("VAPID Configuration Tests")
15+
struct VAPIDConfigurationTests {
16+
@Suite
17+
struct Initialization {
18+
let key1 = try! VAPID.Key(base64URLEncoded: "FniTgSrf0l+BdfeC6LiblKXBbY4LQm0S+4STNCoJI+0=")
19+
let key2 = try! VAPID.Key(base64URLEncoded: "wyQaGWNwvXKzVmPIhkqVQvQ+FKx1SNqHJ+re8n2ORrk=")
20+
let key3 = try! VAPID.Key(base64URLEncoded: "bcZgo/p2WFqXaKFzmYaDKO/gARjWvGi3oXyHM2QNlfE=")
21+
22+
@Test func primaryKeyOnly() {
23+
let config = VAPID.Configuration(
24+
key: key1,
25+
contactInformation: .email("test@email.com")
26+
)
27+
#expect(config.primaryKey == key1)
28+
#expect(config.keys == [key1])
29+
#expect(config.deprecatedKeys == nil)
30+
#expect(config.contactInformation == .email("test@email.com"))
31+
#expect(config.expirationDuration == .hours(22))
32+
#expect(config.validityDuration == .hours(20))
33+
}
34+
35+
@Test func emptyDeprecatedKeys() {
36+
let config = VAPID.Configuration(
37+
key: key1,
38+
deprecatedKeys: [],
39+
contactInformation: .url(URL(string: "https://example.com")!),
40+
expirationDuration: .hours(24),
41+
validityDuration: .hours(12)
42+
)
43+
#expect(config.primaryKey == key1)
44+
#expect(config.keys == [key1])
45+
#expect(config.deprecatedKeys == nil)
46+
#expect(config.contactInformation == .url(URL(string: "https://example.com")!))
47+
#expect(config.expirationDuration == .hours(24))
48+
#expect(config.validityDuration == .hours(12))
49+
}
50+
51+
@Test func deprecatedKeys() {
52+
let config = VAPID.Configuration(
53+
key: key1,
54+
deprecatedKeys: [key2, key3],
55+
contactInformation: .email("test@email.com")
56+
)
57+
#expect(config.primaryKey == key1)
58+
#expect(config.keys == [key1])
59+
#expect(config.deprecatedKeys == [key2, key3])
60+
#expect(config.contactInformation == .email("test@email.com"))
61+
#expect(config.expirationDuration == .hours(22))
62+
#expect(config.validityDuration == .hours(20))
63+
}
64+
65+
@Test func deprecatedAndPrimaryKeys() {
66+
let config = VAPID.Configuration(
67+
key: key1,
68+
deprecatedKeys: [key2, key3, key1],
69+
contactInformation: .url(URL(string: "https://example.com")!),
70+
expirationDuration: .hours(24),
71+
validityDuration: .hours(12)
72+
)
73+
#expect(config.primaryKey == key1)
74+
#expect(config.keys == [key1])
75+
#expect(config.deprecatedKeys == [key2, key3])
76+
#expect(config.contactInformation == .url(URL(string: "https://example.com")!))
77+
#expect(config.expirationDuration == .hours(24))
78+
#expect(config.validityDuration == .hours(12))
79+
}
80+
81+
@Test func multipleKeys() throws {
82+
let config = try VAPID.Configuration(
83+
primaryKey: nil,
84+
keys: [key1, key2],
85+
deprecatedKeys: nil,
86+
contactInformation: .email("test@email.com")
87+
)
88+
#expect(config.primaryKey == nil)
89+
#expect(config.keys == [key1, key2])
90+
#expect(config.deprecatedKeys == nil)
91+
#expect(config.contactInformation == .email("test@email.com"))
92+
#expect(config.expirationDuration == .hours(22))
93+
#expect(config.validityDuration == .hours(20))
94+
}
95+
96+
@Test func noKeys() throws {
97+
#expect(throws: VAPID.ConfigurationError.keysNotProvided) {
98+
try VAPID.Configuration(
99+
primaryKey: nil,
100+
keys: [],
101+
deprecatedKeys: [key2, key3],
102+
contactInformation: .email("test@email.com")
103+
)
104+
}
105+
}
106+
107+
@Test func multipleAndDeprecatedKeys() throws {
108+
let config = try VAPID.Configuration(
109+
primaryKey: nil,
110+
keys: [key1, key2],
111+
deprecatedKeys: [key2],
112+
contactInformation: .email("test@email.com")
113+
)
114+
#expect(config.primaryKey == nil)
115+
#expect(config.keys == [key1, key2])
116+
#expect(config.deprecatedKeys == nil)
117+
#expect(config.contactInformation == .email("test@email.com"))
118+
#expect(config.expirationDuration == .hours(22))
119+
#expect(config.validityDuration == .hours(20))
120+
}
121+
122+
@Test func multipleAndPrimaryKeys() throws {
123+
let config = try VAPID.Configuration(
124+
primaryKey: key1,
125+
keys: [key2],
126+
deprecatedKeys: [key2, key3, key1],
127+
contactInformation: .url(URL(string: "https://example.com")!),
128+
expirationDuration: .hours(24),
129+
validityDuration: .hours(12)
130+
)
131+
#expect(config.primaryKey == key1)
132+
#expect(config.keys == [key1, key2])
133+
#expect(config.deprecatedKeys == [key3])
134+
#expect(config.contactInformation == .url(URL(string: "https://example.com")!))
135+
#expect(config.expirationDuration == .hours(24))
136+
#expect(config.validityDuration == .hours(12))
137+
}
138+
}
139+
140+
@Suite
141+
struct Updates {
142+
let key1 = try! VAPID.Key(base64URLEncoded: "FniTgSrf0l+BdfeC6LiblKXBbY4LQm0S+4STNCoJI+0=")
143+
let key2 = try! VAPID.Key(base64URLEncoded: "wyQaGWNwvXKzVmPIhkqVQvQ+FKx1SNqHJ+re8n2ORrk=")
144+
let key3 = try! VAPID.Key(base64URLEncoded: "bcZgo/p2WFqXaKFzmYaDKO/gARjWvGi3oXyHM2QNlfE=")
145+
146+
@Test func primaryKeyOnly() throws {
147+
var config = VAPID.Configuration(key: key1, contactInformation: .email("test@email.com"))
148+
149+
try config.updateKeys(primaryKey: key2, keys: [], deprecatedKeys: nil)
150+
#expect(config.primaryKey == key2)
151+
#expect(config.keys == [key2])
152+
#expect(config.deprecatedKeys == nil)
153+
}
154+
155+
@Test func noKeys() throws {
156+
var config = VAPID.Configuration(key: key1, contactInformation: .email("test@email.com"))
157+
#expect(throws: VAPID.ConfigurationError.keysNotProvided) {
158+
try config.updateKeys(primaryKey: nil, keys: [], deprecatedKeys: nil)
159+
}
160+
#expect(throws: VAPID.ConfigurationError.keysNotProvided) {
161+
try config.updateKeys(primaryKey: nil, keys: [], deprecatedKeys: [])
162+
}
163+
#expect(throws: VAPID.ConfigurationError.keysNotProvided) {
164+
try config.updateKeys(primaryKey: nil, keys: [], deprecatedKeys: [key1])
165+
}
166+
}
167+
168+
@Test func multipleKeys() throws {
169+
var config = VAPID.Configuration(key: key1, contactInformation: .email("test@email.com"))
170+
171+
try config.updateKeys(primaryKey: nil, keys: [key2], deprecatedKeys: nil)
172+
#expect(config.primaryKey == nil)
173+
#expect(config.keys == [key2])
174+
#expect(config.deprecatedKeys == nil)
175+
176+
try config.updateKeys(primaryKey: nil, keys: [key2, key3], deprecatedKeys: nil)
177+
#expect(config.primaryKey == nil)
178+
#expect(config.keys == [key2, key3])
179+
#expect(config.deprecatedKeys == nil)
180+
}
181+
182+
@Test func multipleAndDeprecatedKeys() throws {
183+
var config = VAPID.Configuration(key: key1, contactInformation: .email("test@email.com"))
184+
185+
try config.updateKeys(primaryKey: nil, keys: [key2], deprecatedKeys: [key2, key3])
186+
#expect(config.primaryKey == nil)
187+
#expect(config.keys == [key2])
188+
#expect(config.deprecatedKeys == [key3])
189+
190+
try config.updateKeys(primaryKey: nil, keys: [key2, key3], deprecatedKeys: [key2, key3])
191+
#expect(config.primaryKey == nil)
192+
#expect(config.keys == [key2, key3])
193+
#expect(config.deprecatedKeys == nil)
194+
}
195+
196+
@Test func multipleAndPrimaryKeys() throws {
197+
var config = VAPID.Configuration(key: key1, contactInformation: .email("test@email.com"))
198+
199+
try config.updateKeys(primaryKey: key2, keys: [key3], deprecatedKeys: [key1, key2, key3])
200+
#expect(config.primaryKey == key2)
201+
#expect(config.keys == [key2, key3])
202+
#expect(config.deprecatedKeys == [key1])
203+
204+
try config.updateKeys(primaryKey: key2, keys: [key3], deprecatedKeys: [key2, key3])
205+
#expect(config.primaryKey == key2)
206+
#expect(config.keys == [key2, key3])
207+
#expect(config.deprecatedKeys == nil)
208+
}
209+
}
210+
211+
@Suite
212+
struct Coding {
213+
let key1 = try! VAPID.Key(base64URLEncoded: "FniTgSrf0l+BdfeC6LiblKXBbY4LQm0S+4STNCoJI+0=")
214+
let key2 = try! VAPID.Key(base64URLEncoded: "wyQaGWNwvXKzVmPIhkqVQvQ+FKx1SNqHJ+re8n2ORrk=")
215+
let key3 = try! VAPID.Key(base64URLEncoded: "bcZgo/p2WFqXaKFzmYaDKO/gARjWvGi3oXyHM2QNlfE=")
216+
217+
func encode(_ configuration: VAPID.Configuration) throws -> String {
218+
let encoder = JSONEncoder()
219+
encoder.outputFormatting = [.prettyPrinted, .sortedKeys, .withoutEscapingSlashes]
220+
return String(decoding: try encoder.encode(configuration), as: UTF8.self)
221+
}
222+
223+
@Test func encodesPrimaryKeyOnly() async throws {
224+
#expect(
225+
try encode(.init(key: key1, contactInformation: .email("test@example.com"))) ==
226+
"""
227+
{
228+
"contactInformation" : "mailto:test@example.com",
229+
"expirationDuration" : 79200,
230+
"primaryKey" : "FniTgSrf0l+BdfeC6LiblKXBbY4LQm0S+4STNCoJI+0=",
231+
"validityDuration" : 72000
232+
}
233+
"""
234+
)
235+
}
236+
237+
@Test func encodesMultipleKeysWithoutDuplicates() async throws {
238+
#expect(
239+
try encode(.init(
240+
primaryKey: key1,
241+
keys: [key2],
242+
deprecatedKeys: [key1, key2, key3],
243+
contactInformation: .email("test@example.com"),
244+
expirationDuration: .hours(1),
245+
validityDuration: .hours(10)
246+
)) ==
247+
"""
248+
{
249+
"contactInformation" : "mailto:test@example.com",
250+
"deprecatedKeys" : [
251+
"bcZgo/p2WFqXaKFzmYaDKO/gARjWvGi3oXyHM2QNlfE="
252+
],
253+
"expirationDuration" : 3600,
254+
"keys" : [
255+
"wyQaGWNwvXKzVmPIhkqVQvQ+FKx1SNqHJ+re8n2ORrk="
256+
],
257+
"primaryKey" : "FniTgSrf0l+BdfeC6LiblKXBbY4LQm0S+4STNCoJI+0=",
258+
"validityDuration" : 36000
259+
}
260+
"""
261+
)
262+
}
263+
}
264+
}
265+
266+
@Suite("Contact Information Coding")
267+
struct ContactInformationCoding {
268+
@Test func encodesToString() async throws {
269+
func encode(_ contactInformation: VAPID.Configuration.ContactInformation) throws -> String {
270+
String(decoding: try JSONEncoder().encode(contactInformation), as: UTF8.self)
271+
}
272+
#expect(try encode(.email("test@example.com")) == "\"mailto:test@example.com\"")
273+
#expect(try encode(.email("junk")) == "\"mailto:junk\"")
274+
#expect(try encode(.email("")) == "\"mailto:\"")
275+
#expect(try encode(.url(URL(string: "https://example.com")!)) == "\"https:\\/\\/example.com\"")
276+
#expect(try encode(.url(URL(string: "junk")!)) == "\"junk\"")
277+
}
278+
279+
@Test func decodesFromString() async throws {
280+
func decode(_ string: String) throws -> VAPID.Configuration.ContactInformation {
281+
try JSONDecoder().decode(VAPID.Configuration.ContactInformation.self, from: Data(string.utf8))
282+
}
283+
#expect(try decode("\"mailto:test@example.com\"") == .email("test@example.com"))
284+
#expect(try decode("\"mailto:junk\"") == .email("junk"))
285+
#expect(try decode("\"https://example.com\"") == .url(URL(string: "https://example.com")!))
286+
#expect(try decode("\"HTTP://example.com\"") == .url(URL(string: "HTTP://example.com")!))
287+
288+
#expect(throws: DecodingError.self) {
289+
try decode("\"\"")
290+
}
291+
292+
#expect(throws: DecodingError.self) {
293+
try decode("\"junk\"")
294+
}
295+
296+
#expect(throws: DecodingError.self) {
297+
try decode("\"file:///Users/you/Library\"")
298+
}
299+
300+
#expect(throws: DecodingError.self) {
301+
try decode("\"mailto:\"")
302+
}
303+
}
304+
}

Tests/WebPushTests/VAPIDTokenTests.swift

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,17 @@ struct MockVAPIDKey<Bytes: ContiguousBytes & Sendable>: VAPIDKeyProtocol {
2121
}
2222

2323
@Suite struct VAPIDTokenTests {
24+
@Test func expirationProperlyConfigured() throws {
25+
let date = Date(timeIntervalSince1970: 1_234_567)
26+
let token = VAPID.Token(
27+
origin: "https://push.example.net",
28+
contactInformation: .email("push@example.com"),
29+
expiration: date
30+
)
31+
32+
#expect(token.expiration == 1_234_567)
33+
}
34+
2435
@Test func generatesValidSignedToken() throws {
2536
let key = VAPID.Key()
2637

@@ -66,4 +77,24 @@ struct MockVAPIDKey<Bytes: ContiguousBytes & Sendable>: VAPIDKeyProtocol {
6677
let generatedHeader = try expectedToken.generateAuthorization(signedBy: mockKey)
6778
#expect(generatedHeader == "vapid t=eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJhdWQiOiJodHRwczovL3B1c2guZXhhbXBsZS5uZXQiLCJleHAiOjE0NTM1MjM3NjgsInN1YiI6Im1haWx0bzpwdXNoQGV4YW1wbGUuY29tIn0.i3CYb7t4xfxCDquptFOepC9GAu_HLGkMlMuCGSK2rpiUfnK9ojFwDXb1JrErtmysazNjjvW2L9OkSSHzvoD1oA, k=BA1Hxzyi1RUM1b5wjxsn7nGxAszw2u61m164i3MrAIxHF6YK5h4SDYic-dRuU_RCPCfA5aq9ojSwk5Y2EmClBPs")
6879
}
80+
81+
@Test func invalidTokenInitialization() {
82+
let invalidToken = VAPID.Token(token: "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJhdWQiOiJodHRwczovL3B1c2guZXhhbXBsZS5uZXQiLCJleHAiOjE0NTM1MjM3NjgsInN1YiI6Im1haWx0bzpwdXNoQGV4YW1wbGUuY29tIn0.i3CYb7t4xfxCDquptFOepC9GAu_HLGkMlMuCGSK2rpiUfnK9ojFwDXb1JrErtmysazNjjvW2L9OkSSHzvoD1oA", key: "")
83+
#expect(invalidToken == nil)
84+
85+
let incompleteToken = VAPID.Token(token: "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJhdWQiOiJodHRwczovL3B1c2guZXhhbXBs", key: "BA1Hxzyi1RUM1b5wjxsn7nGxAszw2u61m164i3MrAIxHF6YK5h4SDYic-dRuU_RCPCfA5aq9ojSwk5Y2EmClBPs")
86+
#expect(incompleteToken == nil)
87+
88+
let invalidTokenHeader = VAPID.Token(token: "eyJ0eXAiOiJKV1QiL CJhbGciOiJFUzI1NiJ9.eyJhdWQiOiJodHRwczovL3B1c2guZXhhbXBsZS5uZXQiLCJleHAiOjE0NTM1MjM3NjgsInN1YiI6Im1haWx0bzpwdXNoQGV4YW1wbGUuY29tIn0.i3CYb7t4xfxCDquptFOepC9GAu_HLGkMlMuCGSK2rpiUfnK9ojFwDXb1JrErtmysazNjjvW2L9OkSSHzvoD1oA", key: "BA1Hxzyi1RUM1b5wjxsn7nGxAszw2u61m164i3MrAIxHF6YK5h4SDYic-dRuU_RCPCfA5aq9ojSwk5Y2EmClBPs")
89+
#expect(invalidTokenHeader == nil)
90+
91+
let invalidTokenBody = VAPID.Token(token: "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJhdWQiOiJodHRwczovL3B1c2guZXhhbXBsZS5uZXQiLCJleHA iOjE0NTM1MjM3NjgsInN1YiI6Im1haWx0bzpwdXNoQGV4YW1wbGUuY29tIn0.i3CYb7t4xfxCDquptFOepC9GAu_HLGkMlMuCGSK2rpiUfnK9ojFwDXb1JrErtmysazNjjvW2L9OkSSHzvoD1oA", key: "BA1Hxzyi1RUM1b5wjxsn7nGxAszw2u61m164i3MrAIxHF6YK5h4SDYic-dRuU_RCPCfA5aq9ojSwk5Y2EmClBPs")
92+
#expect(invalidTokenBody == nil)
93+
94+
let invalidTokenSignature = VAPID.Token(token: "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJhdWQiOiJodHRwczovL3B1c2guZXhhbXBsZS5uZXQiLCJleHAiOjE0NTM1MjM3NjgsInN1YiI6Im1haWx0bzpwdXNoQGV4YW1wbGUuY29tIn0.i3CYb7t4xfxCDquptFOepC9GAu_HLGkMlMuCGSK2rpiUfnK 9ojFwDXb1JrErtmysazNjjvW2L9OkSSHzvoD1oA", key: "BA1Hxzyi1RUM1b5wjxsn7nGxAszw2u61m164i3MrAIxHF6YK5h4SDYic-dRuU_RCPCfA5aq9ojSwk5Y2EmClBPs")
95+
#expect(invalidTokenSignature == nil)
96+
97+
let invalidTokenKey = VAPID.Token(token: "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJhdWQiOiJodHRwczovL3B1c2guZXhhbXBsZS5uZXQiLCJleHAiOjE0NTM1MjM3NjgsInN1YiI6Im1haWx0bzpwdXNoQGV4YW1wbGUuY29tIn0.i3CYb7t4xfxCDquptFOepC9GAu_HLGkMlMuCGSK2rpiUfnK9ojFwDXb1JrErtmysazNjjvW2L9OkSSHzvoD1oA", key: "BA1Hxzyi1RUM1b5wjxsn7nGxAszw2u61m164i3MrAIxHF6 YK5h4SDYic-dRuU_RCPCfA5aq9ojSwk5Y2EmClBPs")
98+
#expect(invalidTokenKey == nil)
99+
}
69100
}

0 commit comments

Comments
 (0)