ShareLinkShortenerTests.swift (7569B)
1 import Foundation 2 import Testing 3 4 @testable import Crossmate 5 6 @Suite("Share link shortener") 7 struct ShareLinkShortenerTests { 8 9 private let base = URL(string: "https://crossmate.example.net")! 10 private let token = "0a1BcDeFgHiJkLmNoPqRsTuVw" 11 private var share: URL { URL(string: "https://www.icloud.com/share/\(token)")! } 12 13 @Test("rewrites an iCloud share URL to the worker short form") 14 func rewritesShareURL() { 15 let short = ShareLinkShortener.shortURL(for: share, title: nil, shape: nil, baseURL: base) 16 #expect(short.absoluteString == "https://crossmate.example.net/s/\(token)") 17 } 18 19 @Test("drops the title-slug fragment from the short link") 20 func dropsFragment() { 21 let fragmented = URL(string: "https://www.icloud.com/share/\(token)#Saturday_Stumper")! 22 let short = ShareLinkShortener.shortURL(for: fragmented, title: nil, shape: nil, baseURL: base) 23 #expect(short.absoluteString == "https://crossmate.example.net/s/\(token)") 24 } 25 26 @Test("encodes a custom game title as a t-tagged base64url segment") 27 func encodesTitleSegment() { 28 let short = ShareLinkShortener.shortURL( 29 for: share, title: "Saturday Stumper", shape: nil, baseURL: base 30 ) 31 #expect( 32 short.absoluteString 33 == "https://crossmate.example.net/s/\(token)/tU2F0dXJkYXkgU3R1bXBlcg" 34 ) 35 } 36 37 @Test("maps base64 plus and slash into the URL-safe alphabet") 38 func encodesTitleURLSafely() { 39 // The em dash and π force '+' into the standard base64 encoding. 40 let short = ShareLinkShortener.shortURL( 41 for: share, title: "Sunday? Yes — π!", shape: nil, baseURL: base 42 ) 43 #expect( 44 short.absoluteString 45 == "https://crossmate.example.net/s/\(token)/tU3VuZGF5PyBZZXMg4oCUIM-AIQ" 46 ) 47 } 48 49 @Test("collapses a standard NYT weekday title to a single digit") 50 func encodesWeekdayTitle() { 51 let cases: [(String, String)] = [ 52 ("Sunday Crossword", "0"), 53 ("Monday Crossword", "1"), 54 ("Saturday Crossword", "6"), 55 (" Monday Crossword ", "1") 56 ] 57 for (title, digit) in cases { 58 let short = ShareLinkShortener.shortURL(for: share, title: title, shape: nil, baseURL: base) 59 #expect(short.absoluteString == "https://crossmate.example.net/s/\(token)/\(digit)") 60 } 61 } 62 63 @Test("a near-miss weekday title falls back to the base64url form") 64 func nonWeekdayTitleStaysCustom() { 65 // No trailing "Crossword", so it is not the NYT shortcut. 66 let short = ShareLinkShortener.shortURL(for: share, title: "Monday", shape: nil, baseURL: base) 67 #expect( 68 short.absoluteString == "https://crossmate.example.net/s/\(token)/tTW9uZGF5" 69 ) 70 } 71 72 @Test("truncates an over-long custom title to the cap") 73 func truncatesLongTitle() { 74 let long = String(repeating: "A", count: ShareLinkShortener.maxCustomTitleLength + 12) 75 let capped = String(long.prefix(ShareLinkShortener.maxCustomTitleLength)) 76 let expected = "t" + GridSilhouette.base64URLEncode([UInt8](capped.utf8)) 77 let short = ShareLinkShortener.shortURL(for: share, title: long, shape: nil, baseURL: base) 78 #expect(short.absoluteString == "https://crossmate.example.net/s/\(token)/\(expected)") 79 } 80 81 @Test("appends the grid silhouette after the title") 82 func appendsShapeAfterTitle() { 83 // 3×3, centre cell blocked → symmetric → "s3CA". 84 var blocks = [Bool](repeating: false, count: 9) 85 blocks[4] = true 86 let grid = GridSilhouette.Grid(side: 3, blocks: blocks) 87 let short = ShareLinkShortener.shortURL( 88 for: share, title: "Monday Crossword", shape: grid, baseURL: base 89 ) 90 #expect(short.absoluteString == "https://crossmate.example.net/s/\(token)/1/s3CA") 91 } 92 93 @Test("emits the silhouette segment even when there is no title") 94 func appendsShapeWithoutTitle() { 95 var blocks = [Bool](repeating: false, count: 9) 96 blocks[4] = true 97 let grid = GridSilhouette.Grid(side: 3, blocks: blocks) 98 let short = ShareLinkShortener.shortURL(for: share, title: nil, shape: grid, baseURL: base) 99 #expect(short.absoluteString == "https://crossmate.example.net/s/\(token)/s3CA") 100 } 101 102 @Test("appends a rectangular silhouette under the uppercase form") 103 func appendsRectangularShape() { 104 // 2×3, all empty → symmetric → "S23AA". 105 let grid = GridSilhouette.Grid(width: 2, height: 3, blocks: [Bool](repeating: false, count: 6)) 106 let short = ShareLinkShortener.shortURL(for: share, title: nil, shape: grid, baseURL: base) 107 #expect(short.absoluteString == "https://crossmate.example.net/s/\(token)/S23AA") 108 } 109 110 @Test("omits a silhouette whose block count doesn't match its dimensions") 111 func omitsMismatchedShape() { 112 let grid = GridSilhouette.Grid(side: 3, blocks: [Bool](repeating: false, count: 6)) 113 let short = ShareLinkShortener.shortURL(for: share, title: nil, shape: grid, baseURL: base) 114 #expect(short.absoluteString == "https://crossmate.example.net/s/\(token)") 115 } 116 117 @Test("omits the title segment for an empty or whitespace title") 118 func omitsBlankTitle() { 119 for title in [nil, "", " ", "\n"] as [String?] { 120 let short = ShareLinkShortener.shortURL(for: share, title: title, shape: nil, baseURL: base) 121 #expect(short.absoluteString == "https://crossmate.example.net/s/\(token)") 122 } 123 } 124 125 @Test("accepts the bare icloud.com host") 126 func acceptsBareHost() { 127 let bare = URL(string: "https://icloud.com/share/\(token)")! 128 let short = ShareLinkShortener.shortURL(for: bare, title: nil, shape: nil, baseURL: base) 129 #expect(short.absoluteString == "https://crossmate.example.net/s/\(token)") 130 } 131 132 @Test("passes through unchanged when no base URL is configured") 133 func passesThroughWithoutBase() { 134 let fragmented = URL(string: "https://www.icloud.com/share/\(token)#Title")! 135 #expect( 136 ShareLinkShortener.shortURL(for: fragmented, title: "Title", shape: nil, baseURL: nil) 137 == fragmented 138 ) 139 } 140 141 @Test( 142 "passes through URLs that are not a CKShare link", 143 arguments: [ 144 "https://example.com/share/0a1BcDeFgHiJkLmNoPqRsTuVw", 145 "http://www.icloud.com/share/0a1BcDeFgHiJkLmNoPqRsTuVw", 146 "https://www.icloud.com/notes/0a1BcDeFgHiJkLmNoPqRsTuVw", 147 "https://www.icloud.com/share", 148 "https://www.icloud.com/share/0a1BcDeF/extra" 149 ] 150 ) 151 func passesThroughForeignURLs(raw: String) { 152 let url = URL(string: raw)! 153 #expect(ShareLinkShortener.shortURL(for: url, title: nil, shape: nil, baseURL: base) == url) 154 } 155 156 @Test("rejects tokens outside the unreserved charset or length bounds") 157 func rejectsMalformedTokens() { 158 // The encoded slash decodes into the last path component; passing it 159 // through to the worker would change the redirect target's shape. 160 let smuggled = URL(string: "https://www.icloud.com/share/0a1BcDeF%2F..%2FbAd")! 161 #expect(ShareLinkShortener.shortURL(for: smuggled, title: nil, shape: nil, baseURL: base) == smuggled) 162 163 let tooShort = URL(string: "https://www.icloud.com/share/0a1Bc")! 164 #expect(ShareLinkShortener.shortURL(for: tooShort, title: nil, shape: nil, baseURL: base) == tooShort) 165 } 166 }