Implemented the GetReviewsAPIEndpoint endpoint in the iTunes library.

This commit is contained in:
Javier Cicchelli 2024-03-17 23:13:13 +01:00
parent fc58ccf091
commit 7b9a067713
2 changed files with 284 additions and 0 deletions

View File

@ -0,0 +1,70 @@
//
// GetReviewsAPIEndpoint.swift
// ReviewsiTunesKit
//
// Created by Javier Cicchelli on 17/03/2024.
// Copyright © 2024 Röck+Cöde VoF. All rights reserved.
//
import Foundation
import ReviewsFoundationKit
import ReviewsFeedKit
struct GetReviewsAPIEndpoint: GetReviewsEndpoint {
// MARK: Constants
let host: URL
let decoder: JSONDecoder
let session: URLSession
// MARK: Functions
@discardableResult func callAsFunction(
_ input: ReviewsFeedKit.GetReviewsInput
) async throws -> ReviewsFeedKit.GetReviewsOutput {
let path = try makePath(with: input)
let url = {
if #available(iOS 16.0, *) {
host.appending(path: path)
} else {
host.appendingPathComponent(path)
}
}()
let (data, response) = try await session.data(from: url)
guard let urlResponse = response as? HTTPURLResponse else {
throw EndpointError.responseNotFound
}
guard urlResponse.statusCode == 200 else {
throw EndpointError.requestFailed(statusCode: urlResponse.statusCode)
}
let feed = try decoder.decode(Feed.self, from: data)
return .init(reviews: feed.entries)
}
func makePath(with input: GetReviewsInput) throws -> String {
guard
!input.appID.isEmpty,
!input.countryCode.isEmpty
else{
throw EndpointError.inputParametersEmpty
}
return .init(
format: .Format.recentReviewsPath,
input.countryCode,
input.appID
)
}
}
// MARK: - String+Formats
private extension String {
enum Format {
static let recentReviewsPath = "/%@/rss/customerreviews/id=%@/sortby=mostrecent/json"
}
}

View File

@ -0,0 +1,214 @@
//
// GetReviewsAPIEndpointTests.swift
// ReviewsiTunesTest
//
// Created by Javier Cicchelli on 17/03/2024.
// Copyright © 2024 Röck+Cöde VoF. All rights reserved.
//
import ReviewsFeedKit
import ReviewsFoundationKit
import XCTest
@testable import ReviewsiTunesKit
final class GetReviewsAPIEndpointTests: XCTestCase {
// MARK: Properties
private var input: GetReviewsAPIEndpoint.Input!
private var sut: GetReviewsAPIEndpoint!
// MARK: Setup
override func setUp() async throws {
sut = .init(
host: .iTunes,
decoder: .default,
session: .init(configuration: .mock)
)
}
// MARK: Functions tests
func testCallAsFunction_whenResponseOK_withOneReview() async throws {
// GIVEN
let reviews: [Review] = .one
input = .init(
appID: "1234567890",
countryCode: "abc"
)
MockURLProtocol.response = .init(
statusCode: 200,
object: Feed(entries: reviews)
)
// WHEN
let output = try await sut(input)
// THEN
XCTAssertFalse(output.reviews.isEmpty)
XCTAssertEqual(output.reviews.count, reviews.count)
XCTAssertEqual(output.reviews, reviews)
}
func testCallAsFunction_whenResponseOK_withManyReviews() async throws {
// GIVEN
let reviews: [Review] = .many
input = .init(
appID: "1234567890",
countryCode: "abc"
)
MockURLProtocol.response = .init(
statusCode: 200,
object: Feed(entries: reviews)
)
// WHEN
let output = try await sut(input)
// THEN
XCTAssertFalse(output.reviews.isEmpty)
XCTAssertEqual(output.reviews.count, reviews.count)
XCTAssertEqual(output.reviews, reviews)
}
func testCallAsFunction_whenResponseOK_withEmptyReviews() async throws {
// GIVEN
let reviews: [Review] = .empty
input = .init(
appID: "1234567890",
countryCode: "abc"
)
MockURLProtocol.response = .init(
statusCode: 200,
object: Feed(entries: reviews)
)
// WHEN
let output = try await sut(input)
// THEN
XCTAssertTrue(output.reviews.isEmpty)
XCTAssertEqual(output.reviews.count, reviews.count)
XCTAssertEqual(output.reviews, reviews)
}
func testCallAsFunction_whenResponseNotOK() async throws {
// GIVEN
let statusCode = 404
input = .init(
appID: "1234567890",
countryCode: "abc"
)
MockURLProtocol.response = .init(statusCode: statusCode)
// WHEN
// THEN
do {
try await sut(input)
} catch EndpointError.requestFailed(statusCode: statusCode) {
XCTAssertTrue(true)
} catch {
XCTAssertTrue(false)
}
}
func testMakePath_withFilledInput() throws {
// GIVEN
input = .init(
appID: "1234567890",
countryCode: "abc"
)
// WHEN
let path = try sut.makePath(with: input)
// THEN
XCTAssertEqual(path, "/\(input.countryCode)/rss/customerreviews/id=\(input.appID)/sortby=mostrecent/json")
}
func testMakePath_withPartiallyFilledInput() throws {
// GIVEN
input = .init(
appID: .empty,
countryCode: "abc"
)
// WHEN
// THEN
XCTAssertThrowsError(try sut.makePath(with: input)) { error in
XCTAssertEqual(error as? EndpointError, .inputParametersEmpty)
}
}
func testMakePath_withEmptyInput() throws {
// GIVEN
input = .init(
appID: .empty,
countryCode: .empty
)
// WHEN
// THEN
XCTAssertThrowsError(try sut.makePath(with: input)) { error in
XCTAssertEqual(error as? EndpointError, .inputParametersEmpty)
}
}
}
// MARK: - Array+Constants
private extension Array where Element == Review {
// MARK: Constants
static let empty: [Review] = []
static let many: [Review] = [
.init(
id: 1,
author: "Some author name #1 goes here...",
title: "Some title #1 goes here...",
content: "Some content #1 goes here...",
rating: 1,
version: "Some version #1 goes here...",
updated: .init()
),
.init(
id: 2,
author: "Some author name #2 goes here...",
title: "Some title #2 goes here...",
content: "Some content #2 goes here...",
rating: 5,
version: "Some version #2 goes here...",
updated: .init()
),
.init(
id: 3,
author: "Some author name #3 goes here...",
title: "Some title #3 goes here...",
content: "Some content #3 goes here...",
rating: 3,
version: "Some version #3 goes here...",
updated: .init()
)
]
static let one: [Review] = [
.init(
id: 1,
author: "Some author name goes here...",
title: "Some title goes here...",
content: "Some content goes here...",
rating: 3,
version: "Some version goes here...",
updated: .init()
)
]
}