Implemented the GetReviewsAPIEndpoint endpoint in the iTunes library.
This commit is contained in:
parent
fc58ccf091
commit
7b9a067713
70
Libraries/iTunes/Kit/Endpoints/GetReviewsAPIEndpoint.swift
Normal file
70
Libraries/iTunes/Kit/Endpoints/GetReviewsAPIEndpoint.swift
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
@ -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()
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user