Skip to content

Commit 60a34c2

Browse files
author
Clément Le Provost
committed
[offline][test] Add test cases for request strategy
1 parent 85ffe29 commit 60a34c2

File tree

3 files changed

+220
-3
lines changed

3 files changed

+220
-3
lines changed

AlgoliaSearch.xcodeproj/project.pbxproj

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@
151151
BCE2E9851E23949D00F189F3 /* MockNetworkReachability.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCE2E9841E23949D00F189F3 /* MockNetworkReachability.swift */; };
152152
BCE2E9861E23949D00F189F3 /* MockNetworkReachability.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCE2E9841E23949D00F189F3 /* MockNetworkReachability.swift */; };
153153
BCE2E9871E23949D00F189F3 /* MockNetworkReachability.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCE2E9841E23949D00F189F3 /* MockNetworkReachability.swift */; };
154-
BCE2E9881E23949D00F189F3 /* MockNetworkReachability.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCE2E9841E23949D00F189F3 /* MockNetworkReachability.swift */; };
154+
BCE2E98A1E239D5C00F189F3 /* MockNetworkReachability.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCE2E9891E239D5C00F189F3 /* MockNetworkReachability.swift */; };
155155
BCEE36811E0830810091D113 /* settings.json in Resources */ = {isa = PBXBuildFile; fileRef = BCEE36801E0830810091D113 /* settings.json */; };
156156
BCEE36831E08309F0091D113 /* objects.json in Resources */ = {isa = PBXBuildFile; fileRef = BCEE36821E08309F0091D113 /* objects.json */; };
157157
BCFC281C1D92660C00BFE0A0 /* MirroredIndexTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCFC281B1D92660C00BFE0A0 /* MirroredIndexTests.swift */; };
@@ -247,6 +247,7 @@
247247
BCDA73771CA546800082B197 /* NetworkTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkTests.swift; sourceTree = "<group>"; };
248248
BCDA737D1CA555070082B197 /* MockURLSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockURLSession.swift; sourceTree = "<group>"; };
249249
BCE2E9841E23949D00F189F3 /* MockNetworkReachability.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockNetworkReachability.swift; sourceTree = "<group>"; };
250+
BCE2E9891E239D5C00F189F3 /* MockNetworkReachability.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockNetworkReachability.swift; sourceTree = "<group>"; };
250251
BCEE36801E0830810091D113 /* settings.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = settings.json; sourceTree = "<group>"; };
251252
BCEE36821E08309F0091D113 /* objects.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = objects.json; sourceTree = "<group>"; };
252253
BCFC281B1D92660C00BFE0A0 /* MirroredIndexTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MirroredIndexTests.swift; sourceTree = "<group>"; };
@@ -463,6 +464,7 @@
463464
isa = PBXGroup;
464465
children = (
465466
BCFC281B1D92660C00BFE0A0 /* MirroredIndexTests.swift */,
467+
BCE2E9891E239D5C00F189F3 /* MockNetworkReachability.swift */,
466468
BCD57D391D89B1EA00C5DE68 /* OfflineClientTests.swift */,
467469
BCD57D3A1D89B1EA00C5DE68 /* OfflineIndexTests.swift */,
468470
BCD57D3B1D89B1EA00C5DE68 /* OfflineTestCase.swift */,
@@ -1085,7 +1087,7 @@
10851087
files = (
10861088
BCD57D3D1D89B1EA00C5DE68 /* OfflineIndexTests.swift in Sources */,
10871089
BCFC281D1D92670100BFE0A0 /* Helpers.swift in Sources */,
1088-
BCE2E9881E23949D00F189F3 /* MockNetworkReachability.swift in Sources */,
1090+
BCE2E98A1E239D5C00F189F3 /* MockNetworkReachability.swift in Sources */,
10891091
BCD57D3E1D89B1EA00C5DE68 /* OfflineTestCase.swift in Sources */,
10901092
BCFC281C1D92660C00BFE0A0 /* MirroredIndexTests.swift in Sources */,
10911093
BCD57D3C1D89B1EA00C5DE68 /* OfflineClientTests.swift in Sources */,

Tests/Offline/MirroredIndexTests.swift

Lines changed: 174 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
// THE SOFTWARE.
2222
//
2323

24-
import AlgoliaSearchOffline
24+
@testable import AlgoliaSearchOffline
2525
import XCTest
2626

2727

@@ -353,4 +353,177 @@ class MirroredIndexTests: OfflineTestCase {
353353
}
354354
waitForExpectations(timeout: onlineExpectationTimeout, handler: nil)
355355
}
356+
357+
/// Test the `onlineOnly` request strategy.
358+
///
359+
func testRequestStrategyOnlineOnly() {
360+
let expectation = self.expectation(description: #function)
361+
362+
// Mock reachability.
363+
let reachability = MockNetworkReachability()
364+
client.reachability = reachability
365+
366+
// Populate the online index & sync the offline mirror.
367+
let index: MirroredIndex = client.index(withName: safeIndexName(#function))
368+
index.requestStrategy = .onlineOnly
369+
sync(index: index) { (error) in
370+
if let error = error { XCTFail("\(error)"); return }
371+
372+
// Test success.
373+
index.search(Query()) { (content, error) in
374+
XCTAssertNil(error)
375+
XCTAssertEqual(5, content?["nbHits"] as? Int)
376+
XCTAssertEqual("remote", content?["origin"] as? String)
377+
378+
// Test that reachability is observed.
379+
reachability.reachable = false
380+
let startTime = Date()
381+
index.search(Query()) { (content, error) in
382+
let stopTime = Date()
383+
let duration = stopTime.timeIntervalSince(startTime)
384+
guard let error = error as? NSError else { XCTFail("Request should have failed"); expectation.fulfill(); return }
385+
XCTAssertEqual(NSURLErrorDomain, error.domain)
386+
XCTAssertEqual(NSURLErrorNotConnectedToInternet, error.code)
387+
XCTAssert(duration < min(self.client.searchTimeout, self.client.timeout)) // check that we failed without waiting for the timeout
388+
389+
// Test real network failure.
390+
reachability.reachable = true
391+
self.client.readHosts = [ "unknown.algolia.com" ]
392+
index.search(Query()) { (content, error) in
393+
guard let error = error as? NSError else { XCTFail("Request should have failed"); expectation.fulfill(); return }
394+
XCTAssertEqual(NSURLErrorDomain, error.domain)
395+
// Check that we failed with something else than a connectivity error caused by reachability.
396+
XCTAssertNotEqual(NSURLErrorNotConnectedToInternet, error.code)
397+
398+
expectation.fulfill()
399+
}
400+
}
401+
}
402+
}
403+
waitForExpectations(timeout: onlineExpectationTimeout, handler: nil)
404+
}
405+
406+
/// Test the `offlineOnly` request strategy.
407+
///
408+
func testRequestStrategyOfflineOnly() {
409+
let expectation = self.expectation(description: #function)
410+
411+
let index: MirroredIndex = client.index(withName: safeIndexName(#function))
412+
index.mirrored = true
413+
index.requestStrategy = .offlineOnly
414+
415+
// Check that a request without local data fails.
416+
index.search(Query()) { (content, error) in
417+
XCTAssertNotNil(error)
418+
419+
// Populate the online index & sync the offline mirror.
420+
self.sync(index: index) { (error) in
421+
if let error = error { XCTFail("\(error)"); expectation.fulfill(); return }
422+
423+
// Test success.
424+
index.search(Query()) { (content, error) in
425+
XCTAssertNil(error)
426+
XCTAssertEqual(3, content?["nbHits"] as? Int)
427+
XCTAssertEqual("local", content?["origin"] as? String)
428+
429+
expectation.fulfill()
430+
}
431+
}
432+
}
433+
waitForExpectations(timeout: onlineExpectationTimeout, handler: nil)
434+
}
435+
436+
/// Test the `fallbackOnFailure` request strategy.
437+
///
438+
func testRequestStrategyFallbackOnFailure() {
439+
let expectation = self.expectation(description: #function)
440+
441+
// Mock reachability.
442+
let reachability = MockNetworkReachability()
443+
client.reachability = reachability
444+
445+
// Populate the online index & sync the offline mirror.
446+
let index: MirroredIndex = client.index(withName: safeIndexName(#function))
447+
index.requestStrategy = .fallbackOnFailure
448+
sync(index: index) { (error) in
449+
if let error = error { XCTFail("\(error)"); return }
450+
451+
// Test success.
452+
index.search(Query()) { (content, error) in
453+
XCTAssertNil(error)
454+
XCTAssertEqual(5, content?["nbHits"] as? Int)
455+
XCTAssertEqual("remote", content?["origin"] as? String)
456+
457+
// Test that reachability is observed.
458+
reachability.reachable = false
459+
index.search(Query()) { (content, error) in
460+
XCTAssertNil(error)
461+
XCTAssertEqual(3, content?["nbHits"] as? Int)
462+
XCTAssertEqual("local", content?["origin"] as? String)
463+
464+
// Test real network failure.
465+
reachability.reachable = true
466+
self.client.readHosts = [ "unknown.algolia.com" ]
467+
index.search(Query()) { (content, error) in
468+
XCTAssertNil(error)
469+
XCTAssertEqual(3, content?["nbHits"] as? Int)
470+
XCTAssertEqual("local", content?["origin"] as? String)
471+
472+
expectation.fulfill()
473+
}
474+
}
475+
}
476+
}
477+
waitForExpectations(timeout: onlineExpectationTimeout, handler: nil)
478+
}
479+
480+
/// Test the `fallbackOnTimeout` request strategy.
481+
///
482+
func testRequestStrategyFallbackOnTimeout() {
483+
let expectation = self.expectation(description: #function)
484+
485+
// Mock reachability.
486+
let reachability = MockNetworkReachability()
487+
client.reachability = reachability
488+
489+
// Populate the online index & sync the offline mirror.
490+
let index: MirroredIndex = client.index(withName: safeIndexName(#function))
491+
index.requestStrategy = .fallbackOnTimeout
492+
sync(index: index) { (error) in
493+
if let error = error { XCTFail("\(error)"); return }
494+
495+
// Test success.
496+
index.search(Query()) { (content, error) in
497+
XCTAssertNil(error)
498+
XCTAssertEqual(5, content?["nbHits"] as? Int)
499+
XCTAssertEqual("remote", content?["origin"] as? String)
500+
501+
// Test that reachability is observed.
502+
reachability.reachable = false
503+
index.search(Query()) { (content, error) in
504+
XCTAssertNil(error)
505+
XCTAssertEqual(3, content?["nbHits"] as? Int)
506+
XCTAssertEqual("local", content?["origin"] as? String)
507+
508+
// Test real network failure.
509+
reachability.reachable = true
510+
self.client.readHosts = [ uniqueAlgoliaBizHost() ]
511+
let startTime = Date()
512+
index.search(Query()) { (content, error) in
513+
let stopTime = Date()
514+
let duration = stopTime.timeIntervalSince(startTime)
515+
XCTAssertNil(error)
516+
XCTAssertEqual(3, content?["nbHits"] as? Int)
517+
XCTAssertEqual("local", content?["origin"] as? String)
518+
// Check that we hit the fallback time out, but not the complete online timeout.
519+
XCTAssert(duration >= index.offlineFallbackTimeout)
520+
XCTAssert(duration < min(self.client.searchTimeout, self.client.timeout))
521+
522+
expectation.fulfill()
523+
}
524+
}
525+
}
526+
}
527+
waitForExpectations(timeout: onlineExpectationTimeout, handler: nil)
528+
}
356529
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
//
2+
// Copyright (c) 2016 Algolia
3+
// http://www.algolia.com/
4+
//
5+
// Permission is hereby granted, free of charge, to any person obtaining a copy
6+
// of this software and associated documentation files (the "Software"), to deal
7+
// in the Software without restriction, including without limitation the rights
8+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
// copies of the Software, and to permit persons to whom the Software is
10+
// furnished to do so, subject to the following conditions:
11+
//
12+
// The above copyright notice and this permission notice shall be included in
13+
// all copies or substantial portions of the Software.
14+
//
15+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
// THE SOFTWARE.
22+
//
23+
24+
@testable import AlgoliaSearchOffline
25+
import Foundation
26+
27+
// ----------------------------------------------------------------------
28+
// NOTE: Duplicate of the file with the same name in the online tests.
29+
// Required because of the juggling we have to do with the package name :/
30+
// (thanks Cocoapods!).
31+
// ----------------------------------------------------------------------
32+
33+
34+
/// A simple mock of `NetworkReachability` that just returns a stored state.
35+
///
36+
public class MockNetworkReachability: AlgoliaSearchOffline.NetworkReachability {
37+
public var reachable: Bool = true
38+
39+
public func isReachable() -> Bool {
40+
return reachable
41+
}
42+
}

0 commit comments

Comments
 (0)