2 // ShaarliHtmlClientTest.swift
5 // Created by Marcus Rohrmoser on 09.06.19.
6 // Copyright © 2019-2021 Marcus Rohrmoser mobile Software http://mro.name/me. All rights reserved.
8 // This program is free software: you can redistribute it and/or modify
9 // it under the terms of the GNU General Public License as published by
10 // the Free Software Foundation, either version 3 of the License, or
11 // (at your option) any later version.
13 // This program is distributed in the hope that it will be useful,
14 // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 // GNU General Public License for more details.
18 // You should have received a copy of the GNU General Public License
19 // along with this program. If not, see <http://www.gnu.org/licenses/>.
24 private let AGENT = "test"
25 private let TO : TimeInterval = 4.0
27 class ShaarliHtmlClientTest: XCTestCase {
29 override func setUp() {
30 // Put setup code here. This method is called before the invocation of each test method in the class.
33 override func tearDown() {
34 URLSession.shared.reset() { print("URLSession.reset() done.")}
37 func testUrlEmpty () {
38 XCTAssertEqual("", URLEmpty.absoluteString)
42 let url = URL(string: "https://uid:pwd@example.com/foo")!
43 XCTAssertEqual("https://uid:pwd@example.com/foo", url.absoluteString)
44 XCTAssertEqual("https", url.scheme)
45 XCTAssertEqual("example.com", url.host)
46 XCTAssertEqual("uid", url.user)
47 XCTAssertEqual("pwd", url.password)
48 XCTAssertEqual("/foo", url.path)
49 XCTAssertEqual(nil, url.query)
50 XCTAssertEqual(nil, url.fragment)
52 var b = URLComponents(string:url.absoluteString)!
55 XCTAssertEqual("foo", u2.user)
56 XCTAssertEqual("pwd", u2.password)
58 let url1 = URL(string: "a.b")!
59 XCTAssertNil(url1.host)
60 XCTAssertNil(url1.scheme)
64 let ur = issue61("http://example.com?a=b c")
65 XCTAssertEqual("http://example.com?a=b+c", ur.absoluteString)
68 func testUrlCredential() {
69 let cre = URLCredential(user:"", password:"", persistence:.permanent)
70 XCTAssertEqual(true, cre.hasPassword)
71 XCTAssertEqual("", cre.user)
72 XCTAssertEqual("", cre.password)
76 let str = httpBasic(URLCredential(user:"usr", password:"pwd", persistence:.forSession))
77 XCTAssertEqual("Basic dXNyOnB3ZA==", str)
78 guard let cre = httpBasic(str) else {
82 XCTAssertTrue(cre.hasPassword)
83 XCTAssertEqual("usr", cre.user)
84 XCTAssertEqual("pwd", cre.password)
86 let c : URLCredential? = nil
87 XCTAssertNil(httpBasic(c))
88 XCTAssertNil(httpBasic(URLCredential(user:"", password:"uhu", persistence:.permanent)))
89 XCTAssertEqual("Basic dWh1Og==", httpBasic(URLCredential(user:"uhu", password:"", persistence:.forSession)))
91 XCTAssertEqual("Basic ZGVtTzpkZW1PZGVtT2RlbU8=", httpBasic(URLCredential(user:"demO", password:"demOdemOdemO", persistence:.forSession)))
95 XCTAssertEqual("1=a%26b", String(data: formData(["1":"a&b"]), encoding: .ascii))
96 XCTAssertEqual("2%3D2=c%3Dc", String(data: formData(["2=2":"c=c"]), encoding: .ascii))
97 XCTAssertEqual("3=d%0Ad", String(data: formData(["3":"d\nd"]), encoding: .ascii))
99 var frm = ["3":"d\nd", LF_PRI:"on"]
101 XCTAssertEqual("3=d%0Ad", String(data: formData(frm), encoding: .ascii))
104 func testFormDataIssue59() {
105 let pwd = ":8-$;(B%Z_rM]]?i?p{'+]1|xQk008]$,L}\\z2HxTB^%YpEl"
106 let pw1 = ":8-$;(B%Z_rM]]?i?p{\'+]1|xQk008]$,L}\\z2HxTB^%YpEl"
107 XCTAssertEqual(pw1, pwd, "literal escaping")
108 // $ curl --trace-ascii - --data-urlencode password=':8-$;(B%Z_rM]]?i?p{\'+]1|xQk008]$,L}\\z2HxTB^%YpEl' https://demo.mro.name/
109 let pwcurl = "%3A8-%24%3B%28B%25Z_rM%5D%5D%3Fi%3Fp%7B%27%2B%5D1%7CxQk008%5D%24%2CL%7D%5Cz2HxTB%5E%25YpEl"
111 XCTAssertEqual("password=\(pwcurl)", String(data: formData(["password":pwd]), encoding: .ascii))
114 func testEncoding() {
115 let str = "Hello, wörld!"
116 let byt = str.data(using: .utf8, allowLossyConversion: false)!
117 XCTAssertEqual(str, String(bytes: byt, encoding:.utf8))
118 XCTAssertEqual("Hello, wörld!", String(bytes: byt, encoding:.isoLatin1))
121 func testUrlEscaping() {
122 var uc = URLComponents(string: "scheme://uid:pwd@host:123/path?q=s#frag")!
123 uc.user = "my uid_with_:_/_@_?_&_$_ä_🚀_end"
124 uc.password = "my pwd_with_:_/_@_?_&_$_ä_🚀_end"
125 XCTAssertEqual("scheme://my%20uid_with_%3A_%2F_%40_%3F_&_$_%C3%A4_%F0%9F%9A%80_end:my%20pwd_with_%3A_%2F_%40_%3F_&_$_%C3%A4_%F0%9F%9A%80_end@host:123/path?q=s#frag", uc.url?.absoluteString)
126 XCTAssertEqual("my pwd_with_:_/_@_?_&_$_ä_🚀_end", uc.password)
128 uc.scheme = HTTP_HTTPS
129 uc.host = "demo.mro.name"
132 uc.password = "demo -/:;&@\"$#%"
133 uc.path = "/shaarli-v0.11.1-issue28/"
136 XCTAssertEqual("https://demo:demo%20-%2F%3A;&%40%22$%23%25@demo.mro.name:8443/shaarli-v0.11.1-issue28/", uc.url?.absoluteString)
139 XCTAssertEqual(uc.scheme, u2.scheme)
140 XCTAssertEqual(uc.host, u2.host)
141 XCTAssertEqual(uc.port, u2.port)
142 XCTAssertEqual(uc.user, u2.user)
143 XCTAssertEqual("demo%20-%2F%3A;&%40%22$%23%25", u2.password) // URL.password is raw!
144 XCTAssertEqual("/shaarli-v0.11.1-issue28", u2.path)
145 XCTAssertEqual(uc.query, u2.query)
146 XCTAssertEqual(uc.fragment, u2.fragment)
149 // https://nshipster.com/swift-regular-expressions/
151 let ra0 = "Fancy a game of Cluedo™️?".range(of: "Clue(do)?™️?", options:.regularExpression)
152 XCTAssertEqual(16, ra0?.lowerBound.encodedOffset)
154 let msg = "<script>alert(\"foo\"); // bar \");"
155 let ra1 = msg.range(of: PAT_WRONG_LOGIN, options:.regularExpression)!
156 XCTAssertEqual("<script>alert(\"foo\");", msg[ra1])
159 func testResponseLoginFormSunshine() {
160 let raw = dataWithContentsOfFixture(me:self, fileName: "login.0", extensio:"html")
161 let re = HTTPURLResponse(url: URLEmpty, statusCode: 200, httpVersion: "1.1", headerFields:[
162 "Content-Type":"text/html; charset=utf-8",
163 "Content-Length":"\(raw.count)",
165 XCTAssertEqual(200, re.statusCode)
166 XCTAssertEqual("text/html", re.mimeType)
167 XCTAssertEqual("utf-8", re.textEncodingName)
168 XCTAssertEqual(2509, re.expectedContentLength)
169 let res = check(raw, re, nil)
170 XCTAssertEqual("", res.1)
171 XCTAssertEqual(1, res.0.count)
172 guard let lifo = res.0[LOGIN_FORM] else {
176 XCTAssertEqual(3, lifo.count)
177 XCTAssertEqual("http://links.mro.name/", lifo["returnurl"])
178 XCTAssertEqual("Login", lifo[""])
179 XCTAssertEqual("20119241badf78a3dcfa55ae58eab429a5d24bad", lifo["token"])
182 func testResponseLoginFormBanned() {
183 let raw = dataWithContentsOfFixture(me:self, fileName: "login.banned", extensio:"html")
184 let re = HTTPURLResponse(url: URLEmpty, statusCode: 200, httpVersion: "1.1", headerFields:[
185 "Content-Type":"text/html; charset=utf-8",
186 "Content-Length":"\(raw.count)",
188 XCTAssertEqual(200, re.statusCode)
189 XCTAssertEqual("text/html", re.mimeType)
190 XCTAssertEqual("utf-8", re.textEncodingName)
191 XCTAssertEqual(1982, re.expectedContentLength)
192 let res = check(raw, re, nil)
193 XCTAssertEqual("You have been banned from login after too many failed attempts. Try later.", res.1)
194 XCTAssertEqual(0, res.0.count)
197 func testProbeSunshine() {
198 let demo = URL(string:"https://demo:demo@demo.shaarli.org/")! // credentials are public
199 // let demo = URL(string:"https://demo:demodemodemo@demo.mro.name:8443/shaarli-v0.10.2/")! // credentials are public
200 // let demo = URL(string:"https://demo:demo@shaarli-next.hoa.ro/")! // credentials are public
201 // let demo = URL(string:"https://demo:demodemodemo@demo.mro.name/shaarli-v0.41b/")! // credentials are public
202 // let demo = URL(string:"https://demo:demodemodemo@demo.mro.name/shaarligo/")! // credentials are public
204 let exp = self.expectation(description: "Probing") // https://medium.com/@johnsundell/unit-testing-asynchronous-swift-code-9805d1d0ac5e
206 let srv = ShaarliHtmlClient(AGENT)
207 srv.probe(demo, nil, TO) { (url, tit, pride, tizo, err) in
208 XCTAssertEqual("", err)
209 // XCTAssertEqual("https://demo.shaarli.org/", url.absoluteString)
210 XCTAssertEqual("Shaarli demo", tit)
212 //XCTAssertEqual("https://demo:demodemodemo@demo.mro.name:8443/shaarli-v0.10.2/", url.absoluteString)
213 XCTAssertEqual("https://demo:demo@demo.shaarli.org/", url.absoluteString)
214 // XCTAssertEqual("https://demo:demo@shaarli-next.hoa.ro/", url.absoluteString)
215 // XCTAssertEqual("https://demo:demodemodemo@demo.mro.name:8443/shaarli-v0.41b/", url.absoluteString)
216 // XCTAssertEqual("https://demo:demodemodemo@demo.mro.name/shaarligo/shaarligo.cgi", url.absoluteString)
217 // XCTAssertEqual("ShaarliGo 🚀", tit)
218 XCTAssertEqual(false, pride)
219 XCTAssertEqual(nil, tizo?.identifier)
220 // XCTAssertEqual("Europe/Paris", tizo?.identifier)
224 waitForExpectations(timeout: 3, handler: nil)
227 func testProbeSunshineIssue28() {
228 var uc = URLComponents(string: "")!
229 uc.scheme = HTTP_HTTPS
230 uc.host = "demo.mro.name"
232 uc.path = "/shaarli-v0.11.1-issue28/"
233 // credentials are public
235 uc.password = "demo -/:;&@\"$#%"
236 XCTAssertEqual("https://demo:demo%20-%2F%3A;&%40%22$%23%25@demo.mro.name:8443/shaarli-v0.11.1-issue28/", uc.url?.absoluteString)
238 XCTAssertEqual("demo", demo.user)
239 XCTAssertEqual("demo%20-%2F%3A;&%40%22$%23%25", demo.password)
240 let exp = self.expectation(description: "Probing") // https://medium.com/@johnsundell/unit-testing-asynchronous-swift-code-9805d1d0ac5e
242 let srv = ShaarliHtmlClient(AGENT)
243 srv.probe(demo, nil, TO) { (url, tit, pride, tizo, err) in
244 XCTAssertEqual("", err)
245 XCTAssertEqual("https://demo:demo%20-%2F%3A;&%40%22$%23%25@demo.mro.name:8443/shaarli-v0.11.1-issue28/", url.absoluteString)
246 XCTAssertEqual(false, pride)
247 XCTAssertEqual("Europe/Berlin", tizo?.identifier)
251 waitForExpectations(timeout: 5, handler: nil)
254 func testProbe403() {
255 // let demo = URL(string:"https://demo:foo@demo.shaarli.org/")! // credentials are public
256 let demo = URL(string:"https://tast:foo@demo.mro.name/shaarli-v0.10.2/")! // credentials are public
257 // let demo = URL(string:"https://tast:foo@demo.mro.name/shaarli-v0.41b/")! // credentials are public
259 let exp = self.expectation(description: "Probing") // https://medium.com/@johnsundell/unit-testing-asynchronous-swift-code-9805d1d0ac5e
261 let srv = ShaarliHtmlClient(AGENT)
262 srv.probe(demo, nil, TO) { (url, pong, pride, tizo, err) in
263 XCTAssertEqual(URLEmpty, url)
264 XCTAssertEqual("", pong)
265 XCTAssertEqual("Wrong login/password.", err)
266 XCTAssertEqual(false, pride)
267 XCTAssertEqual(nil, tizo?.identifier)
268 // XCTAssertEqual(ShaarliHtmlClient.STR_BANNED, err)
272 waitForExpectations(timeout: 2, handler: nil)
275 func testProbe404() {
276 // let demo = URL(string:"https://demo:foo@demo.shaarli.org/hgr/")! // credentials are public
277 // let demo = URL(string:"https://tast:foo@demo.mro.name/shaarli-v0.10.2/")! // credentials are public
278 let demo = URL(string:"https://demo.mro.name/bogus")! // credentials are public
280 let exp = self.expectation(description: "Probing") // https://medium.com/@johnsundell/unit-testing-asynchronous-swift-code-9805d1d0ac5e
282 let srv = ShaarliHtmlClient(AGENT)
283 srv.probe(demo, nil, TO) { (url, pong, pride, tizo, err) in
284 XCTAssertEqual(URLEmpty, url)
285 XCTAssertEqual("", pong)
286 XCTAssertEqual("Expected response HTTP status '200 Ok' but got '404 not found'", err)
287 XCTAssertEqual(false, pride)
288 XCTAssertEqual(nil, tizo?.identifier)
292 waitForExpectations(timeout: 2, handler: nil)
295 func testGetSunshine() {
296 // let end = URL(string:"https://demo:demo@demo.shaarli.org/")! // credentials are public
297 // let end = URL(string:"https://demo:demodemodemo@demo.mro.name/shaarli-v0.11.1/")! // credentials are public
298 // let end = URL(string:"https://demo:demodemodemo@demo.mro.name/shaarli-v0.10.4/")! // credentials are public
299 // let end = URL(string:"https://demo:demodemodemo@demo.mro.name/shaarli-v0.10.2/")! // credentials are public
300 // let end = URL(string:"https://demo:demodemodemo@demo.mro.name:8443/shaarli-v0.10.2/")! // credentials are public
301 let end = URL(string:"https://demo:demodemodemo@demo.mro.name/shaarli-v0.41b/")! // credentials are public
302 // let end = URL(string:"https://demo:demodemodemo@demo.mro.name/shaarligo")! // credentials are public
303 let url = URL(string:"https://shaarli.readthedocs.io")!
305 let cre = URLCredential(user:"demO", password:"demOdemOdemO", persistence:.forSession)
307 let exp = self.expectation(description: "Reading") // https://medium.com/@johnsundell/unit-testing-asynchronous-swift-code-9805d1d0ac5e
309 let srv = ShaarliHtmlClient(AGENT)
310 srv.get(end, cre, TO, url) { (_, act, frm, url, tit, dsc, tgs, pri, tim, seti, err) in
311 XCTAssertEqual("https://demo.mro.name/shaarli-v0.41b/?post=https%3A%2F%2Fshaarli.readthedocs.io", act.absoluteString)
312 XCTAssertEqual("https://shaarli.readthedocs.io", url.absoluteString)
313 XCTAssertEqual("", tit)
314 XCTAssertEqual([], tgs)
316 XCTAssertEqual("", err)
317 // XCTAssertEqual([:], frm)
320 waitForExpectations(timeout: 2, handler: nil)
323 func testPostSunshine() {
324 // let end = URL(string:"https://demo:demo@demo.shaarli.org/")! // credentials are public
325 // let end = URL(string:"https://demo:demodemodemo@demo.mro.name/shaarli-v0.11.1/")! // credentials are public
326 // let end = URL(string:"https://demo:demodemodemo@demo.mro.name/shaarli-v0.10.4/")! // credentials are public
327 // let end = URL(string:"https://demo:demo@shaarli-next.hoa.ro/")! // credentials are public
328 // let end = URL(string:"https://demo:demodemodemo@demo.mro.name/shaarli-v0.10.2/")! // credentials are public
329 // let end = URL(string:"https://demo:demodemodemo@demo.mro.name:8443/shaarli-v0.10.2/")! // credentials are public
330 let end = URL(string:"https://demo:demodemodemo@demo.mro.name/shaarli-v0.41b/")! // credentials are public
331 let url = URL(string:"http://idlewords.com/talks/website_obesity.htm?foo=bar#minimalism")!
333 let exp0 = self.expectation(description: "Reading") // https://medium.com/@johnsundell/unit-testing-asynchronous-swift-code-9805d1d0ac5e
334 let exp1 = self.expectation(description: "Posting")
336 let srv = ShaarliHtmlClient(AGENT)
337 srv.get(end, nil, TO, url) { (ses, act, frm, url, tit, dsc, tgs, pri, tim, seti, err0) in
338 XCTAssertEqual("", err0)
339 // XCTAssertEqual("http://idlewords.com/talks/website_obesity.htm#minimalism", url.absoluteString)
340 XCTAssertEqual("http://idlewords.com/talks/website_obesity.htm?foo=bar#minimalism", url.absoluteString)
341 // XCTAssertEqual("The Website Obesity Crisis", tit)
342 XCTAssertEqual("", dsc, "why is dsc empty?")
343 XCTAssertEqual([], tgs)
344 //XCTAssertFalse(pri)
346 //XCTAssertNil(ctx[LF_PRI])
349 srv.add(ses, act, frm, url, tit, dsc, tgs, pri) { err1 in
350 XCTAssertEqual("", err1)
354 waitForExpectations(timeout: 3, handler: nil)
357 func testPostUrlIssue61() {
358 let end = URL(string:"https://demo:demodemodemo@demo.mro.name/shaarli-v0.11.1/")! // credentials are public
359 let url0 = URL(string:"https://example.com?a=b+c")!
361 let exp0 = self.expectation(description: "Reading")
362 let exp1 = self.expectation(description: "Posting")
364 let srv = ShaarliHtmlClient(AGENT)
365 srv.get(end, nil, TO, url0) { (ses, act, frm, url, tit, dsc, tgs, pri, tim, seti, err0) in
366 XCTAssertEqual("", err0)
367 XCTAssertEqual("https://example.com?a=b+c", url.absoluteString)
368 // XCTAssertEqual("The Website Obesity Crisis", tit)
369 XCTAssertEqual("", dsc, "why is dsc empty?")
370 XCTAssertEqual([], tgs)
374 srv.add(ses, act, frm, url, tit, dsc, tgs, pri) { err1 in
375 XCTAssertEqual("", err1)
379 waitForExpectations(timeout: 3, handler: nil)
383 let cli = ShaarliHtmlClient(AGENT)
384 let tz = TimeZone(secondsFromGMT:2*60*60)!
386 XCTAssertNil(cli.timeShaarli(tz, nil))
387 XCTAssertNil(cli.timeShaarli(tz, "bogus"))
388 XCTAssertNil(cli.timeShaarli(tz, ""))
389 XCTAssertEqual("2021-04-07 14:15:06 +0000", cli.timeShaarli(tz, "20210407_161506")?.description)