Files
purchases-ios/Tests/UnitTests/LocalReceiptParsing/ReceiptParserTests.swift
T
Mark VillacampaandNachoSoto 84806ad86e Improve sandbox detector for macOS apps (#3549)
Checking for the presence of `Xcode/DerivedData` in the receipt path was
failing for TestFlight builds or if the .app bundle generated by Xcode
was moved from the folder.

Additionally, checking for `MASReceipt/receipt` seems less robust than
simply using compiler directives.

Instead, for macOS apps, we check the environment embedded in the receipt and alternatively check the signature of the app bundle.

---------

Co-authored-by: NachoSoto <NachoSoto@users.noreply.github.com>
2024-01-09 19:12:47 +01:00

194 lines
9.1 KiB
Swift

import Nimble
import XCTest
@testable import RevenueCat
class ReceiptParserTests: TestCase {
private var receiptParser: PurchasesReceiptParser!
private var mockAppleReceiptBuilder: MockAppleReceiptBuilder!
private var mockASN1ContainerBuilder: MockASN1ContainerBuilder!
private let containerFactory = ContainerFactory()
override func setUp() {
super.setUp()
self.mockAppleReceiptBuilder = MockAppleReceiptBuilder()
self.mockASN1ContainerBuilder = MockASN1ContainerBuilder()
self.receiptParser = PurchasesReceiptParser(logger: Logger(),
containerBuilder: self.mockASN1ContainerBuilder,
receiptBuilder: self.mockAppleReceiptBuilder)
}
func testParseFromReceiptDataBuildsContainerAfterObjectIdentifier() throws {
let receiptContainer = containerFactory.receiptContainerFromContainers(containers: [])
let dataObjectIdentifierContainer = containerFactory.objectIdentifierContainer(.data)
let constructedContainer = containerFactory.constructedContainer(containers: [
dataObjectIdentifierContainer,
receiptContainer
])
mockASN1ContainerBuilder.stubbedBuildResult = constructedContainer
let expectedReceipt = mockAppleReceiptWithoutPurchases()
mockAppleReceiptBuilder.stubbedBuildResult = expectedReceipt
let receivedReceipt = try self.receiptParser.parse(from: Data())
expect(self.mockAppleReceiptBuilder.invokedBuildCount) == 1
expect(self.mockAppleReceiptBuilder.invokedBuildParameters) == receiptContainer
expect(receivedReceipt) == expectedReceipt
}
func testParseFromReceiptDataBuildsContainerAfterObjectIdentifierInComplexContainer() throws {
let receiptContainer = containerFactory.receiptContainerFromContainers(containers: [])
let dataObjectIdentifierContainer = containerFactory.objectIdentifierContainer(.data)
let complexContainer = containerFactory.constructedContainer(containers: [
containerFactory.simpleDataContainer(),
containerFactory.objectIdentifierContainer(.signedData),
containerFactory.constructedContainer(containers: [
containerFactory.simpleDataContainer(),
containerFactory.intContainer(int: 656)
]),
containerFactory.simpleDataContainer(),
containerFactory.stringContainer(string: "some string"),
containerFactory.constructedContainer(containers: [
containerFactory.simpleDataContainer(),
containerFactory.intContainer(int: 656),
containerFactory.constructedContainer(containers: [
dataObjectIdentifierContainer,
receiptContainer
]),
containerFactory.dateContainer(date: Date())
]),
containerFactory.objectIdentifierContainer(.encryptedData)
])
mockASN1ContainerBuilder.stubbedBuildResult = complexContainer
let expectedReceipt = mockAppleReceiptWithoutPurchases()
mockAppleReceiptBuilder.stubbedBuildResult = expectedReceipt
let receivedReceipt = try self.receiptParser.parse(from: Data())
expect(self.mockAppleReceiptBuilder.invokedBuildCount) == 1
expect(self.mockAppleReceiptBuilder.invokedBuildParameters) == receiptContainer
expect(receivedReceipt) == expectedReceipt
}
func testParseFromReceiptThrowsIfReceiptBuilderThrows() {
let container = containerWithDataObjectIdentifier()
mockASN1ContainerBuilder.stubbedBuildResult = container
mockAppleReceiptBuilder.stubbedBuildError = .receiptParsingError
expect { try self.receiptParser.parse(from: Data()) }
.to(throwError(PurchasesReceiptParser.Error.receiptParsingError))
}
func testParseFromReceiptThrowsIfNoDataObjectIdentifierFound() {
let container = containerFactory.constructedContainer(containers: [
containerFactory.objectIdentifierContainer(.signedAndEnvelopedData),
containerFactory.receiptContainerFromContainers(containers: [])
])
mockASN1ContainerBuilder.stubbedBuildResult = container
expect { try self.receiptParser.parse(from: Data()) }
.to(throwError(PurchasesReceiptParser.Error.dataObjectIdentifierMissing))
}
func testParseFromReceiptThrowsIfReceiptPayloadIsntLocatedAfterDataObjectIdentifierContainer() {
let container = containerFactory.constructedContainer(containers: [
containerFactory.receiptContainerFromContainers(containers: []),
containerFactory.objectIdentifierContainer(.data)
])
mockASN1ContainerBuilder.stubbedBuildResult = container
expect { try self.receiptParser.parse(from: Data()) }
.to(throwError(PurchasesReceiptParser.Error.dataObjectIdentifierMissing))
}
func testReceiptHasTransactionsTrueIfReceiptHasTransactions() {
mockASN1ContainerBuilder.stubbedBuildResult = containerWithDataObjectIdentifier()
mockAppleReceiptBuilder.stubbedBuildResult = mockAppleReceiptWithPurchases()
expect(self.receiptParser.receiptHasTransactions(receiptData: Data())) == true
}
func testReceiptHasTransactionsFalseIfNoIAPsInReceipt() {
mockASN1ContainerBuilder.stubbedBuildResult = containerWithDataObjectIdentifier()
mockAppleReceiptBuilder.stubbedBuildResult = mockAppleReceiptWithoutPurchases()
expect(self.receiptParser.receiptHasTransactions(receiptData: Data())) == false
}
func testReceiptHasTransactionsTrueIfReceiptCantBeParsed() {
mockASN1ContainerBuilder.stubbedBuildError = .receiptParsingError
expect(self.receiptParser.receiptHasTransactions(receiptData: Data())) == true
}
}
private extension ReceiptParserTests {
func containerWithDataObjectIdentifier() -> ASN1Container {
let receiptContainer = containerFactory.receiptContainerFromContainers(containers: [])
let dataObjectIdentifierContainer = containerFactory.objectIdentifierContainer(.data)
let constructedContainer = containerFactory.constructedContainer(containers: [
dataObjectIdentifierContainer,
receiptContainer
])
return constructedContainer
}
func mockAppleReceiptWithoutPurchases() -> AppleReceipt {
return AppleReceipt(environment: .sandbox,
bundleId: "com.revenuecat.testapp",
applicationVersion: "3.2.3",
originalApplicationVersion: "3.1.1",
opaqueValue: Data(),
sha1Hash: Data(),
creationDate: Date(),
expirationDate: nil,
inAppPurchases: [])
}
func mockAppleReceiptWithPurchases() -> AppleReceipt {
return AppleReceipt(environment: .sandbox,
bundleId: "com.revenuecat.testapp",
applicationVersion: "3.2.3",
originalApplicationVersion: "3.1.1",
opaqueValue: Data(),
sha1Hash: Data(),
creationDate: Date(),
expirationDate: nil,
inAppPurchases: [
.init(quantity: 1,
productId: "com.revenuecat.test",
transactionId: "892398531",
originalTransactionId: "892398531",
productType: .autoRenewableSubscription,
purchaseDate: Date(),
originalPurchaseDate: Date(),
expiresDate: nil,
cancellationDate: Date(),
isInTrialPeriod: false,
isInIntroOfferPeriod: false,
webOrderLineItemId: 79238531,
promotionalOfferIdentifier: nil),
.init(quantity: 1,
productId: "com.revenuecat.test",
transactionId: "892398532",
originalTransactionId: "892398531",
productType: .autoRenewableSubscription,
purchaseDate: Date(),
originalPurchaseDate: Date(),
expiresDate: nil,
cancellationDate: Date(),
isInTrialPeriod: false,
isInIntroOfferPeriod: false,
webOrderLineItemId: 79238532,
promotionalOfferIdentifier: nil)
])
}
}