2022-03-04 13:42:23 -08:00
|
|
|
//
|
|
|
|
|
// Copyright RevenueCat Inc. All Rights Reserved.
|
|
|
|
|
//
|
|
|
|
|
// Licensed under the MIT License (the "License");
|
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
|
//
|
|
|
|
|
// https://opensource.org/licenses/MIT
|
|
|
|
|
//
|
|
|
|
|
// HTTPRequestTests.swift
|
|
|
|
|
//
|
|
|
|
|
// Created by Nacho Soto on 3/4/22.
|
|
|
|
|
|
|
|
|
|
import Foundation
|
|
|
|
|
import Nimble
|
|
|
|
|
import XCTest
|
|
|
|
|
|
|
|
|
|
@testable import RevenueCat
|
|
|
|
|
|
2022-05-10 12:40:40 -07:00
|
|
|
class HTTPRequestTests: TestCase {
|
2022-03-04 13:42:23 -08:00
|
|
|
|
|
|
|
|
// MARK: - Paths
|
|
|
|
|
|
|
|
|
|
private static let userID = "the_user"
|
2023-07-05 15:41:51 -07:00
|
|
|
private static let anonymousUser = "$RCAnonymousID:8252eb283bbc4453a3f81c978f1a6ee1"
|
2022-03-04 13:42:23 -08:00
|
|
|
|
|
|
|
|
private static let paths: [HTTPRequest.Path] = [
|
|
|
|
|
.getCustomerInfo(appUserID: userID),
|
|
|
|
|
.getOfferings(appUserID: userID),
|
|
|
|
|
.getIntroEligibility(appUserID: userID),
|
|
|
|
|
.logIn,
|
|
|
|
|
.postAttributionData(appUserID: userID),
|
|
|
|
|
.postOfferForSigning,
|
|
|
|
|
.postReceiptData,
|
2022-10-20 13:03:15 -05:00
|
|
|
.postSubscriberAttributes(appUserID: userID),
|
2023-03-23 09:18:51 -07:00
|
|
|
.health,
|
|
|
|
|
.getProductEntitlementMapping
|
2022-10-20 13:03:15 -05:00
|
|
|
]
|
|
|
|
|
private static let unauthenticatedPaths: Set<HTTPRequest.Path> = [
|
|
|
|
|
.health
|
2022-03-04 13:42:23 -08:00
|
|
|
]
|
2023-02-13 07:02:18 -08:00
|
|
|
private static let pathsWithoutETags: Set<HTTPRequest.Path> = [
|
|
|
|
|
.health
|
|
|
|
|
]
|
2023-07-06 10:08:45 -07:00
|
|
|
private static let pathsWithSignatureVerification: Set<HTTPRequest.Path> = [
|
|
|
|
|
.getCustomerInfo(appUserID: userID),
|
|
|
|
|
.logIn,
|
|
|
|
|
.postReceiptData,
|
|
|
|
|
.health,
|
|
|
|
|
.getOfferings(appUserID: userID),
|
|
|
|
|
.getProductEntitlementMapping
|
|
|
|
|
]
|
|
|
|
|
private static let pathsThatRequireNonce: Set<HTTPRequest.Path> = [
|
2023-02-22 09:20:25 -08:00
|
|
|
.getCustomerInfo(appUserID: userID),
|
|
|
|
|
.logIn,
|
|
|
|
|
.postReceiptData,
|
|
|
|
|
.health
|
|
|
|
|
]
|
2023-07-05 15:41:51 -07:00
|
|
|
private static let pathsWithUserID: [HTTPRequest.Path] = [
|
|
|
|
|
.getCustomerInfo(appUserID: anonymousUser),
|
|
|
|
|
.getOfferings(appUserID: anonymousUser),
|
|
|
|
|
.getIntroEligibility(appUserID: anonymousUser),
|
|
|
|
|
.postAttributionData(appUserID: anonymousUser),
|
|
|
|
|
.postSubscriberAttributes(appUserID: anonymousUser)
|
|
|
|
|
]
|
2022-03-04 13:42:23 -08:00
|
|
|
|
|
|
|
|
func testPathsDontHaveLeadingSlash() {
|
|
|
|
|
for path in Self.paths {
|
2023-08-10 15:23:13 -07:00
|
|
|
expect(path.pathComponent).toNot(beginWith("/"))
|
2022-03-04 13:42:23 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func testPathsHaveValidURLs() {
|
|
|
|
|
for path in Self.paths {
|
|
|
|
|
expect(path.url).toNot(beNil())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-20 13:03:15 -05:00
|
|
|
func testPathIsAuthenticated() {
|
|
|
|
|
for path in Self.paths where !Self.unauthenticatedPaths.contains(path) {
|
|
|
|
|
expect(path.authenticated).to(
|
|
|
|
|
beTrue(),
|
|
|
|
|
description: "Path '\(path)' should be authenticated"
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func testPathIsNotAuthenticated() {
|
|
|
|
|
for path in Self.unauthenticatedPaths {
|
|
|
|
|
expect(path.authenticated).to(
|
|
|
|
|
beFalse(),
|
|
|
|
|
description: "Path '\(path)' should not be authenticated"
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-13 07:02:18 -08:00
|
|
|
func testPathsSendETag() {
|
|
|
|
|
for path in Self.paths where !Self.pathsWithoutETags.contains(path) {
|
|
|
|
|
expect(path.shouldSendEtag).to(
|
|
|
|
|
beTrue(),
|
|
|
|
|
description: "Path '\(path)' should send etag"
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func testPathsDontSendEtag() {
|
|
|
|
|
for path in Self.pathsWithoutETags {
|
|
|
|
|
expect(path.shouldSendEtag).to(
|
|
|
|
|
beFalse(),
|
|
|
|
|
description: "Path '\(path)' should not send etag"
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-06 10:08:45 -07:00
|
|
|
func testPathsSupportingSignatureSignatureVerification() {
|
|
|
|
|
for path in Self.pathsWithSignatureVerification {
|
|
|
|
|
expect(path.supportsSignatureVerification).to(
|
2023-02-22 09:20:25 -08:00
|
|
|
beTrue(),
|
2023-07-06 10:08:45 -07:00
|
|
|
description: "Path '\(path)' should have signature verification"
|
2023-02-22 09:20:25 -08:00
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-06 10:08:45 -07:00
|
|
|
func testPathsNotSupportingSignatureVerification() {
|
|
|
|
|
for path in Self.paths where !Self.pathsWithSignatureVerification.contains(path) {
|
|
|
|
|
expect(path.supportsSignatureVerification).to(
|
2023-02-22 09:20:25 -08:00
|
|
|
beFalse(),
|
2023-07-06 10:08:45 -07:00
|
|
|
description: "Path '\(path)' should not have signature verification"
|
2023-02-22 09:20:25 -08:00
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-06 10:08:45 -07:00
|
|
|
func testPathsRequiringNonceForSignature() {
|
|
|
|
|
for path in Self.pathsThatRequireNonce {
|
|
|
|
|
expect(path.needsNonceForSigning).to(
|
|
|
|
|
beTrue(),
|
|
|
|
|
description: "Path '\(path)' requires nonce for signing"
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func testPathsNotRequiringNonceForSignature() {
|
|
|
|
|
for path in Self.paths where !Self.pathsThatRequireNonce.contains(path) {
|
|
|
|
|
expect(path.needsNonceForSigning).to(
|
|
|
|
|
beFalse(),
|
|
|
|
|
description: "Path '\(path)' does not require nonce for signing"
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func testPathsThatRequireANonceSupportSignatureVerification() {
|
|
|
|
|
for path in Self.paths where path.needsNonceForSigning {
|
|
|
|
|
expect(path.supportsSignatureVerification).to(
|
|
|
|
|
beTrue(),
|
|
|
|
|
description: "Path '\(path)' should support signature verification"
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func testStaticEndpoints() {
|
|
|
|
|
let staticEndpoints = Self.paths
|
|
|
|
|
.filter { $0.supportsSignatureVerification }
|
|
|
|
|
.filter { !$0.needsNonceForSigning }
|
|
|
|
|
|
|
|
|
|
expect(staticEndpoints) == [
|
|
|
|
|
.getOfferings(appUserID: Self.userID),
|
|
|
|
|
.getProductEntitlementMapping
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-05 15:41:51 -07:00
|
|
|
func testPathsEscapeUserID() {
|
|
|
|
|
for path in Self.pathsWithUserID {
|
2023-08-10 15:23:13 -07:00
|
|
|
expect(path.relativePath).toNot(
|
2023-07-05 15:41:51 -07:00
|
|
|
contain(Self.anonymousUser),
|
|
|
|
|
description: "Path '\(path)' should escape user ID"
|
|
|
|
|
)
|
2023-08-10 15:23:13 -07:00
|
|
|
expect(path.relativePath).to(
|
2023-07-05 15:41:51 -07:00
|
|
|
contain(Self.anonymousUser.trimmedAndEscaped),
|
|
|
|
|
description: "Path '\(path)' should escape user ID"
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func testUserIDEscaping() {
|
|
|
|
|
let encodeableUserID = "userid with spaces"
|
|
|
|
|
let encodedUserID = "userid%20with%20spaces"
|
|
|
|
|
let expectedPath = "subscribers/\(encodedUserID)"
|
|
|
|
|
|
2023-08-10 15:23:13 -07:00
|
|
|
expect(HTTPRequest.Path.getCustomerInfo(appUserID: encodeableUserID).pathComponent) == expectedPath
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func testUserIDEscapingOnURL() {
|
|
|
|
|
let encodeableUserID = "userid with spaces"
|
|
|
|
|
let encodedUserID = "userid%20with%20spaces"
|
|
|
|
|
let expectedURL = "https://api.revenuecat.com/v1/subscribers/\(encodedUserID)"
|
|
|
|
|
let result = HTTPRequest.Path.getCustomerInfo(appUserID: encodeableUserID).url
|
|
|
|
|
|
|
|
|
|
expect(result?.absoluteString) == expectedURL
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func testURLWithNoProxy() {
|
|
|
|
|
let path: HTTPRequest.Path = .health
|
|
|
|
|
expect(path.url?.absoluteString) == "https://api.revenuecat.com/v1/health"
|
|
|
|
|
expect(path.url(proxyURL: nil)?.absoluteString) == "https://api.revenuecat.com/v1/health"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func testURLWithProxy() {
|
|
|
|
|
let path: HTTPRequest.Path = .health
|
|
|
|
|
expect(path.url(proxyURL: URL(string: "https://test_url"))?.absoluteString) == "https://test_url/v1/health"
|
2023-07-05 15:41:51 -07:00
|
|
|
}
|
|
|
|
|
|
2023-02-22 09:20:25 -08:00
|
|
|
func testAddNonceIfRequiredWithExistingNonceDoesNotReplaceNonce() throws {
|
|
|
|
|
let existingNonce = Data.randomNonce()
|
|
|
|
|
let request: HTTPRequest = .init(method: .get, path: .health, nonce: existingNonce)
|
2023-03-13 11:42:52 -07:00
|
|
|
let mode = Signing.enforcedVerificationMode()
|
2023-02-22 09:20:25 -08:00
|
|
|
|
|
|
|
|
expect(request.requestAddingNonceIfRequired(with: mode).nonce) == existingNonce
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func testAddNonceIfRequiredWithDisabledVerification() throws {
|
|
|
|
|
let request: HTTPRequest = .init(method: .get, path: .mockPath)
|
|
|
|
|
expect(request.requestAddingNonceIfRequired(with: .disabled).nonce).to(beNil())
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-06 10:08:45 -07:00
|
|
|
func testAddNonceIfRequiredWithPathWithNoSignatureVerification() throws {
|
2023-02-22 09:20:25 -08:00
|
|
|
let request: HTTPRequest = .init(method: .get, path: .postOfferForSigning)
|
2023-03-13 11:42:52 -07:00
|
|
|
let mode = Signing.enforcedVerificationMode()
|
2023-02-22 09:20:25 -08:00
|
|
|
|
|
|
|
|
expect(request.requestAddingNonceIfRequired(with: mode).nonce).to(beNil())
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-06 10:08:45 -07:00
|
|
|
func testAddNonceIfRequiredWithPathNotRequiringNonce() throws {
|
|
|
|
|
let request: HTTPRequest = .init(method: .get, path: .getOfferings(appUserID: Self.userID))
|
|
|
|
|
let mode = Signing.enforcedVerificationMode()
|
|
|
|
|
|
|
|
|
|
expect(request.requestAddingNonceIfRequired(with: mode).nonce).to(beNil())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func testAddNonceIfRequiredForPathWithSignatureVerificationWhenEnforced() throws {
|
2023-02-22 09:20:25 -08:00
|
|
|
let request: HTTPRequest = .init(method: .get, path: .getCustomerInfo(appUserID: "user"))
|
2023-03-13 11:42:52 -07:00
|
|
|
let mode = Signing.enforcedVerificationMode()
|
2023-02-22 09:20:25 -08:00
|
|
|
|
|
|
|
|
expect(request.requestAddingNonceIfRequired(with: mode).nonce).toNot(beNil())
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-06 10:08:45 -07:00
|
|
|
func testAddNonceIfRequiredForPathWithSignatureVerificationWhenModeInformational() throws {
|
2023-02-22 09:20:25 -08:00
|
|
|
let request: HTTPRequest = .init(method: .get, path: .getCustomerInfo(appUserID: "user"))
|
|
|
|
|
let mode = Signing.verificationMode(with: .informational)
|
|
|
|
|
|
|
|
|
|
expect(request.requestAddingNonceIfRequired(with: mode).nonce).toNot(beNil())
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-25 16:46:31 -05:00
|
|
|
func testRequestIsNotRetryableByDefault() {
|
|
|
|
|
let request: HTTPRequest = .init(method: .get, path: .getCustomerInfo(appUserID: "user"))
|
|
|
|
|
expect(request.isRetryable).to(beFalse())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func testRequestIsRetryableIfSet() {
|
|
|
|
|
let request: HTTPRequest = .init(method: .get, path: .getCustomerInfo(appUserID: "user"), isRetryable: true)
|
|
|
|
|
expect(request.isRetryable).to(beTrue())
|
|
|
|
|
}
|
2022-03-04 13:42:23 -08:00
|
|
|
}
|