mirror of
https://github.com/RevenueCat/purchases-ios
synced 2026-11-11 22:55:55 +05:45
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>
194 lines
9.1 KiB
Swift
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)
|
|
])
|
|
}
|
|
|
|
}
|