be forgiving if Shaarli returns urls with ' ', replace them with '+' retroactively
[ShaarliOS.git] / swift4 / ShaarliOSTests / ShaarliHtmlClientTest.swift
blob70f0d42878c4e794791a37c712156da705ae2b80
1 //
2 //  ShaarliHtmlClientTest.swift
3 //  ShaarliOSTests
4 //
5 //  Created by Marcus Rohrmoser on 09.06.19.
6 //  Copyright © 2019-2021 Marcus Rohrmoser mobile Software http://mro.name/me. All rights reserved.
7 //
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/>.
22 import XCTest
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.
31     }
33     override func tearDown() {
34         URLSession.shared.reset() { print("URLSession.reset() done.")}
35     }
37     func testUrlEmpty () {
38         XCTAssertEqual("", URLEmpty.absoluteString)
39     }
41     func testUrl() {
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)!
53         b.user = "foo"
54         let u2 = b.url!
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)
61     }
63     func testIssue61() {
64         let ur = issue61("http://example.com?a=b c")
65         XCTAssertEqual("http://example.com?a=b+c", ur.absoluteString)
66     }
68     func testUrlCredential() {
69         let cre = URLCredential(user:"", password:"", persistence:.permanent)
70         XCTAssertEqual(true, cre.hasPassword)
71         XCTAssertEqual("", cre.user)
72         XCTAssertEqual("", cre.password)
73     }
75     func testBasic() {
76         let str = httpBasic(URLCredential(user:"usr", password:"pwd", persistence:.forSession))
77         XCTAssertEqual("Basic dXNyOnB3ZA==", str)
78         guard let cre = httpBasic(str) else {
79             XCTFail()
80             return
81         }
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)))
90         
91         XCTAssertEqual("Basic ZGVtTzpkZW1PZGVtT2RlbU8=", httpBasic(URLCredential(user:"demO", password:"demOdemOdemO", persistence:.forSession)))
92     }
94     func testFormData() {
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"]
100         frm[LF_PRI] = nil
101         XCTAssertEqual("3=d%0Ad", String(data: formData(frm), encoding: .ascii))
102     }
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))
112     }
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))
119     }
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"
130         uc.port = 8443
131         uc.user = "demo"
132         uc.password = "demo -/:;&@\"$#%"
133         uc.path = "/shaarli-v0.11.1-issue28/"
134         uc.query = nil
135         uc.fragment = nil
136         XCTAssertEqual("https://demo:demo%20-%2F%3A;&%40%22$%23%25@demo.mro.name:8443/shaarli-v0.11.1-issue28/", uc.url?.absoluteString)
138         let u2 = uc.url!
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)
147     }
149     // https://nshipster.com/swift-regular-expressions/
150     func testRegex() {
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])
157     }
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)",
164         ])!
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 {
173             XCTFail()
174             return
175         }
176         XCTAssertEqual(3, lifo.count)
177         XCTAssertEqual("http://links.mro.name/", lifo["returnurl"])
178         XCTAssertEqual("Login", lifo[""])
179         XCTAssertEqual("20119241badf78a3dcfa55ae58eab429a5d24bad", lifo["token"])
180     }
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)",
187             ])!
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)
195     }
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)
221             exp.fulfill()
222             return
223         }
224         waitForExpectations(timeout: 3, handler: nil)
225     }
227     func testProbeSunshineIssue28() {
228         var uc = URLComponents(string: "")!
229         uc.scheme = HTTP_HTTPS
230         uc.host = "demo.mro.name"
231         uc.port = 8443
232         uc.path = "/shaarli-v0.11.1-issue28/"
233         // credentials are public
234         uc.user = "demo"
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)
237         let demo = uc.url!
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)
248             exp.fulfill()
249             return
250         }
251         waitForExpectations(timeout: 5, handler: nil)
252     }
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
258         
259         let exp = self.expectation(description: "Probing") // https://medium.com/@johnsundell/unit-testing-asynchronous-swift-code-9805d1d0ac5e
260         
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)
269             exp.fulfill()
270             return
271         }
272         waitForExpectations(timeout: 2, handler: nil)
273     }
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
279         
280         let exp = self.expectation(description: "Probing") // https://medium.com/@johnsundell/unit-testing-asynchronous-swift-code-9805d1d0ac5e
281         
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)
289             exp.fulfill()
290             return
291         }
292         waitForExpectations(timeout: 2, handler: nil)
293     }
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)
315             XCTAssertFalse(pri)
316             XCTAssertEqual("", err)
317             // XCTAssertEqual([:], frm)
318             exp.fulfill()
319         }
320         waitForExpectations(timeout: 2, handler: nil)
321     }
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])
347             exp0.fulfill()
349             srv.add(ses, act, frm, url, tit, dsc, tgs, pri) { err1 in
350                 XCTAssertEqual("", err1)
351                 exp1.fulfill()
352             }
353         }
354         waitForExpectations(timeout: 3, handler: nil)
355     }
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)
372             exp0.fulfill()
374             srv.add(ses, act, frm, url, tit, dsc, tgs, pri) { err1 in
375                 XCTAssertEqual("", err1)
376                 exp1.fulfill()
377             }
378         }
379         waitForExpectations(timeout: 3, handler: nil)
380     }
382     func testTime() {
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)
390     }