Skip to content

Commit

Permalink
Add tests for each image repository
Browse files Browse the repository at this point in the history
  • Loading branch information
pietrocaselani committed Jan 25, 2019
1 parent c3055fe commit 8d8d27f
Show file tree
Hide file tree
Showing 17 changed files with 528 additions and 239 deletions.
38 changes: 26 additions & 12 deletions CouchTracker.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions CouchTrackerApp/Environment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -146,8 +146,8 @@ final class Environment {
tvdb: tvdb,
configurationRepository: configurationRepository)

imageRepository = ImageCachedRepository(movieImageRepository: movieImageRepository,
showImageRepository: showImageRepository,
episodeImageRepository: episodeImageRepository)
imageRepository = DefaultImageRepository(movieImageRepository: movieImageRepository,
showImageRepository: showImageRepository,
episodeImageRepository: episodeImageRepository)
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import RxSwift

public final class ImageCachedRepository: ImageRepository {
public final class DefaultImageRepository: ImageRepository {
private let movieImageRepository: MovieImageRepository
private let showImageRepository: ShowImageRepository
private let episodeImageRepository: EpisodeImageRepository
Expand Down
41 changes: 21 additions & 20 deletions CouchTrackerCore/Images/EpisodeImageCachedRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,31 +10,38 @@ public final class EpisodeImageCachedRepository: EpisodeImageRepository {
private let configurationRepository: ConfigurationRepository
private let tmdb: TMDBProvider
private let tvdb: TVDBProvider
private var cache = [Int: URL]()
private var cache: [Int: URL]

public init(tmdb: TMDBProvider,
tvdb: TVDBProvider,
configurationRepository: ConfigurationRepository) {
configurationRepository: ConfigurationRepository,
cache: [Int: URL] = [Int: URL]()) {
self.configurationRepository = configurationRepository
self.tmdb = tmdb
self.tvdb = tvdb
self.cache = cache
}

public func fetchEpisodeImages(for episode: EpisodeImageInput, size: EpisodeImageSizes? = nil) -> Maybe<URL> {
let cacheKey = EpisodeImageCachedRepository.cacheKey(episode: episode, size: size)
let cacheKey = EpisodeImageUtils.cacheKey(episode: episode, size: size)

guard let imageURL = cache[cacheKey] else {
return fetchImageFromAPIs(episode: episode, size: size).do(onNext: { [weak self] url in
self?.cache[cacheKey] = url
})
return fetchImageFromAPIs(episode: episode, size: size)
.do(onNext: { [weak self] url in
self?.cache[cacheKey] = url
})
}

return Maybe.just(imageURL)
}

private func fetchImageFromAPIs(episode: EpisodeImageInput, size: EpisodeImageSizes?) -> Maybe<URL> {
guard let tvdbId = episode.tvdb else {
return Maybe.empty()
guard let tmdbId = episode.tmdb else {
return Maybe.empty()
}

return tmdbObservable(tmdbId, episode.season, episode.number, size?.tmdb ?? .w300)
}

let tvdbObservable = fetchEpisodeImageFromTVDB(tvdbId, size?.tvdb ?? .normal)
Expand All @@ -43,11 +50,16 @@ public final class EpisodeImageCachedRepository: EpisodeImageRepository {
return tvdbObservable
}

return fetchEpisodeImageFromTMDB(tmdbId, episode.season, episode.number, size?.tmdb ?? .w300)
return tmdbObservable(tmdbId, episode.season, episode.number, size?.tmdb ?? .w300)
.catchError { _ in tvdbObservable }
.ifEmpty(switchTo: tvdbObservable)
}

private func tmdbObservable(_ showId: Int, _ season: Int,
_ number: Int, _ size: StillImageSize) -> Maybe<URL> {
return fetchEpisodeImageFromTMDB(showId, season, number, size)
}

private func fetchEpisodeImageFromTMDB(_ showId: Int, _ season: Int,
_ number: Int, _ size: StillImageSize) -> Maybe<URL> {
let target = TMDBEpisodes.images(showId: showId, season: season, episode: number)
Expand All @@ -70,19 +82,8 @@ public final class EpisodeImageCachedRepository: EpisodeImageRepository {

return api.flatMapMaybe { episodeResponse -> Maybe<URL> in
guard let filename = episodeResponse.episode.filename else { return Maybe.empty() }
let url = EpisodeImageCachedRepository.tvdbBaseURLFor(size: size).appendingPathComponent(filename)
let url = EpisodeImageUtils.tvdbBaseURLFor(size: size).appendingPathComponent(filename)
return Maybe.just(url)
}
}

private static func tvdbBaseURLFor(size: TVDBEpisodeImageSize?) -> URL {
return (size ?? .normal) == .normal ? TVDB.bannersImageURL : TVDB.smallBannersImageURL
}

private static func cacheKey(episode: EpisodeImageInput, size: EpisodeImageSizes?) -> Int {
var hasher = Hasher()
hasher.combine(HashableEpisodeImageInput(episode))
size.run { hasher.combine($0) }
return hasher.finalize()
}
}
14 changes: 14 additions & 0 deletions CouchTrackerCore/Images/EpisodeImageUtils.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import TVDBSwift

enum EpisodeImageUtils {
static func tvdbBaseURLFor(size: TVDBEpisodeImageSize?) -> URL {
return (size ?? .normal) == .normal ? TVDB.bannersImageURL : TVDB.smallBannersImageURL
}

static func cacheKey(episode: EpisodeImageInput, size: EpisodeImageSizes?) -> Int {
var hasher = Hasher()
hasher.combine(HashableEpisodeImageInput(episode))
size.run { hasher.combine($0) }
return hasher.finalize()
}
}
6 changes: 4 additions & 2 deletions CouchTrackerCore/Images/MovieImageCachedRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ import TMDBSwift
public final class MovieImageCachedRepository: MovieImageRepository {
private let configurationRepository: ConfigurationRepository
private let tmdb: TMDBProvider
private var cache = [Int: ImagesEntity]()
private var cache: [Int: ImagesEntity]

public init(tmdb: TMDBProvider,
configurationRepository: ConfigurationRepository) {
configurationRepository: ConfigurationRepository,
cache: [Int: ImagesEntity] = [Int: ImagesEntity]()) {
self.configurationRepository = configurationRepository
self.tmdb = tmdb
self.cache = cache
}

public func fetchMovieImages(for movieId: Int,
Expand Down
6 changes: 4 additions & 2 deletions CouchTrackerCore/Images/ShowImageCachedRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ import TMDBSwift
public final class ShowImageCachedRepository: ShowImageRepository {
private let configurationRepository: ConfigurationRepository
private let tmdb: TMDBProvider
private var cache = [Int: ImagesEntity]()
private var cache: [Int: ImagesEntity]

public init(tmdb: TMDBProvider,
configurationRepository: ConfigurationRepository) {
configurationRepository: ConfigurationRepository,
cache: [Int: ImagesEntity] = [Int: ImagesEntity]()) {
self.configurationRepository = configurationRepository
self.tmdb = tmdb
self.cache = cache
}

public func fetchShowImages(for showId: Int,
Expand Down
2 changes: 1 addition & 1 deletion CouchTrackerCore/Images/TMDBImageUtils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import Moya
import RxSwift
import TMDBSwift

public enum TMDBImageUtils {
enum TMDBImageUtils {
static func createImagesEntities(_ configurationRepository: ConfigurationRepository,
_ imagesObservable: Observable<Images>,
posterSize: PosterImageSize? = nil,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,7 @@ public final class ShowEpisodeDefaultPresenter: ShowEpisodePresenter {
}

public func handleWatch() -> Completable {
guard let viewState = try? viewStateSubject.value(),
case let ShowEpisodeViewState.showing(episode, _) = viewState else {
return Completable.empty()
}
guard let episode = (try? viewStateSubject.value())?.episode else { return Completable.empty() }

return interactor.toggleWatch(for: episode)
.observeOn(schedulers.mainScheduler)
Expand Down
10 changes: 10 additions & 0 deletions CouchTrackerCore/Show/Episode/ShowEpisodeViewState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,16 @@ public enum ShowEpisodeViewState: Hashable {
}
}

var episode: WatchedEpisodeEntity? {
switch self {
case .showing(let episode, _):
return episode
case let .showingEpisode(episode):
return episode
default: return nil
}
}

public static func == (lhs: ShowEpisodeViewState, rhs: ShowEpisodeViewState) -> Bool {
return lhs.hashValue == rhs.hashValue
}
Expand Down
169 changes: 169 additions & 0 deletions CouchTrackerCoreTests/Images/EpisodeImageCachedRepositoryTest.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
@testable import CouchTrackerCore
import RxTest
import XCTest

final class EpisodeImageCachedRepositoryTest: XCTestCase {
private var tmdb: TMDBProviderMock!
private var tvdb: TVDBProviderMock!
private var configRepository: ConfigurationRepositoryMock!
private var scheduler: TestSchedulers!

override func setUp() {
super.setUp()

tmdb = createTMDBProviderMock()
tvdb = createTVDBProviderMock()
configRepository = ConfigurationRepositoryMock(tmdbProvider: tmdbProviderMock)
scheduler = TestSchedulers()
}

override func tearDown() {
tmdb = nil
tvdb = nil
configRepository = nil
scheduler = nil
super.tearDown()
}

func testEpisodeImageCachedRepository_fetchEpisodeImages_withDefaultSize() {
// Given
let repository = EpisodeImageCachedRepository(tmdb: tmdb, tvdb: tvdb, configurationRepository: configRepository)
let input = EpisodeImageInputMock(tmdb: 1399, tvdb: 3_254_641, season: 1, number: 1)

// When
let res = scheduler.start {
repository.fetchEpisodeImages(for: input).asObservable()
}

// Then
let expectedURL = URL(validURL: "https:/image.tmdb.org/t/p/w300/wrGWeW4WKxnaeA8sxJb2T9O6ryo.jpg")

let expectedEvents = [Recorded.next(200, expectedURL), Recorded.completed(200)]

XCTAssertEqual(res.events, expectedEvents)
}

func testEpisodeImageCachedRepository_fetchEpisodeImages_witSpecifSize() {
// Given
let repository = EpisodeImageCachedRepository(tmdb: tmdb, tvdb: tvdb, configurationRepository: configRepository)
let input = EpisodeImageInputMock(tmdb: 1399, tvdb: 3_254_641, season: 1, number: 1)
let size = EpisodeImageSizes(tvdb: .normal, tmdb: .w185)

// When
let res = scheduler.start {
repository.fetchEpisodeImages(for: input, size: size).asObservable()
}

// Then
let expectedURL = URL(validURL: "https:/image.tmdb.org/t/p/w185/wrGWeW4WKxnaeA8sxJb2T9O6ryo.jpg")

let expectedEvents = [Recorded.next(200, expectedURL), Recorded.completed(200)]

XCTAssertEqual(res.events, expectedEvents)
}

func testEpisodeImageCachedRepository_fetchEpisodeImages_fromCache() {
// Given
let input = EpisodeImageInputMock(tmdb: 1399, tvdb: 3_254_641, season: 1, number: 1)
let size = EpisodeImageSizes(tvdb: .normal, tmdb: .w185)

let url = URL(validURL: "https:/image.tmdb.org/t/p/w185/wrGWeW4WKxnaeA8sxJb2T9O6ryo.jpg")
let key = EpisodeImageUtils.cacheKey(episode: input, size: size)
let cache = [key: url]

let repository = EpisodeImageCachedRepository(tmdb: tmdb,
tvdb: tvdb,
configurationRepository: configRepository,
cache: cache)
// When
let res = scheduler.start {
repository.fetchEpisodeImages(for: input, size: size).asObservable()
}

// Then
let expectedURL = URL(validURL: "https:/image.tmdb.org/t/p/w185/wrGWeW4WKxnaeA8sxJb2T9O6ryo.jpg")

let expectedEvents = [Recorded.next(200, expectedURL), Recorded.completed(200)]

XCTAssertEqual(res.events, expectedEvents)
XCTAssertEqual((tmdb.episodes as! MoyaProviderMock).requestInvokedCount, 0)
XCTAssertEqual((tvdb.episodes as! MoyaProviderMock).requestInvokedCount, 0)
}

func testEpisodeImageCachedRepository_fetchEpisodeImages_shouldPopulateCache() {
// Given
let repository = EpisodeImageCachedRepository(tmdb: tmdb, tvdb: tvdb, configurationRepository: configRepository)
let input = EpisodeImageInputMock(tmdb: 1399, tvdb: 3_254_641, season: 1, number: 1)
let size = EpisodeImageSizes(tvdb: .normal, tmdb: .w185)

// When
let res1 = scheduler.start {
repository.fetchEpisodeImages(for: input, size: size).asObservable()
}

let res2 = scheduler.start {
repository.fetchEpisodeImages(for: input, size: size).asObservable()
}

// Then
let expectedURL = URL(validURL: "https:/image.tmdb.org/t/p/w185/wrGWeW4WKxnaeA8sxJb2T9O6ryo.jpg")

let expectedEvents1 = [Recorded.next(200, expectedURL), Recorded.completed(200)]
let expectedEvents2 = [Recorded.next(1000, expectedURL), Recorded.completed(1000)]

XCTAssertEqual(res1.events, expectedEvents1)
XCTAssertEqual(res2.events, expectedEvents2)
XCTAssertEqual((tmdb.episodes as! MoyaProviderMock).requestInvokedCount, 1)
XCTAssertEqual((tvdb.episodes as! MoyaProviderMock).requestInvokedCount, 0)
}

func testEpisodeImageCachedRepository_tryTofetchFromTMDBBefore_thenTVDB() {
// Given
let tmdb = createTMDBProviderMock(error: TraktError.loginRequired)
let tvdb = createTVDBProviderMock()
let configRepository = ConfigurationRepositoryMock(tmdbProvider: tmdbProviderMock)
let repository = EpisodeImageCachedRepository(tmdb: tmdb, tvdb: tvdb, configurationRepository: configRepository)

let input = EpisodeImageInputMock(tmdb: 1399, tvdb: 3_254_641, season: 1, number: 1)
let size = EpisodeImageSizes(tvdb: .normal, tmdb: .w185)

// When
let res = scheduler.start {
repository.fetchEpisodeImages(for: input, size: size).asObservable()
}

// Then
let expectedURL = URL(validURL: "https://www.thetvdb.com/banners/episodes/121361/3254641.jpg")

let expectedEvents = [Recorded.next(200, expectedURL), Recorded.completed(200)]

XCTAssertEqual(res.events, expectedEvents)
XCTAssertEqual((tmdb.episodes as! MoyaProviderMock).requestInvokedCount, 1)
XCTAssertEqual((tvdb.episodes as! MoyaProviderMock).requestInvokedCount, 1)
}

func testEpisodeImageCachedRepository_whenThereIsNoTVDBId_fetchesFromTMDB() {
// Given
let tmdb = createTMDBProviderMock()
let tvdb = createTVDBProviderMock()
let configRepository = ConfigurationRepositoryMock(tmdbProvider: tmdbProviderMock)
let repository = EpisodeImageCachedRepository(tmdb: tmdb, tvdb: tvdb, configurationRepository: configRepository)

let input = EpisodeImageInputMock(tmdb: 1399, tvdb: nil, season: 1, number: 1)
let size = EpisodeImageSizes(tvdb: .normal, tmdb: .w185)

// When
let res = scheduler.start {
repository.fetchEpisodeImages(for: input, size: size).asObservable()
}

// Then
let expectedURL = URL(validURL: "https:/image.tmdb.org/t/p/w185/wrGWeW4WKxnaeA8sxJb2T9O6ryo.jpg")

let expectedEvents = [Recorded.next(200, expectedURL), Recorded.completed(200)]

XCTAssertEqual(res.events, expectedEvents)
XCTAssertEqual((tmdb.episodes as! MoyaProviderMock).requestInvokedCount, 1)
XCTAssertEqual((tvdb.episodes as! MoyaProviderMock).requestInvokedCount, 0)
}
}
Loading

0 comments on commit 8d8d27f

Please sign in to comment.