mirror of
https://github.com/mas-cli/mas
synced 2024-11-25 04:50:24 +00:00
Merge pull request #272 from mas-cli/purchase-cleanup
🧹 Purchase cleanup
This commit is contained in:
commit
38f20a8607
39 changed files with 141 additions and 136 deletions
|
@ -8,4 +8,3 @@
|
|||
excluded:
|
||||
- Carthage
|
||||
- docs
|
||||
- MasKitTests
|
||||
|
|
|
@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
- ✨ New `purchase` command for purchasing free apps #264 (resolves #2, #145)
|
||||
thanks, [@blochberger](https://github.com/blochberger)!
|
||||
- 🐟 Seriously more interactive fish completions #242
|
||||
thanks, [@lwolfsonkin](https://github.com/lwolfsonkin)!
|
||||
- 🧹 Purchase cleanup #272
|
||||
- ♻️ SoftwareMap Protocol #271
|
||||
- 🕊 Swift 5 #255
|
||||
- ⚒️ Xcode 10.2 and macOS 10.14 required to build
|
||||
|
@ -14,10 +19,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|||
- ⬆️ Nimble (8.0.4) #255
|
||||
- ⬆️ Quick (2.2.0) #255
|
||||
- ➖ Result #255
|
||||
- Seriously more interactive fish completions #242
|
||||
thanks, [@lwolfsonkin](https://github.com/lwolfsonkin)!
|
||||
- 💡 Update readme with simpler tap usage #241
|
||||
- Added support for purchasing apps (#2, #145)
|
||||
|
||||
## [v1.6.4] 🔎 Search Fix - 2020-05-11
|
||||
|
||||
|
|
|
@ -12,15 +12,17 @@ import StoreFoundation
|
|||
/// Monitors app download progress.
|
||||
///
|
||||
/// - Parameter adamId: An app ID?
|
||||
/// - Parameter purchase: Flag indicating whether the app needs to be purchased.
|
||||
/// Only works for free apps. Defaults to false.
|
||||
/// - Returns: An error, if one occurred.
|
||||
func download(_ adamId: UInt64, isPurchase: Bool) -> MASError? {
|
||||
func download(_ adamId: UInt64, purchase: Bool = false) -> MASError? {
|
||||
guard let account = ISStoreAccount.primaryAccount else {
|
||||
return .notSignedIn
|
||||
}
|
||||
|
||||
guard let storeAccount = account as? ISStoreAccount
|
||||
else { fatalError("Unable to cast StoreAccount to ISStoreAccount") }
|
||||
let purchase = SSPurchase(adamId: adamId, account: storeAccount, isPurchase: isPurchase)
|
||||
else { fatalError("Unable to cast StoreAccount to ISStoreAccount") }
|
||||
let purchase = SSPurchase(adamId: adamId, account: storeAccount, purchase: purchase)
|
||||
|
||||
var purchaseError: MASError?
|
||||
var observerIdentifier: CKDownloadQueueObserver?
|
||||
|
|
|
@ -21,7 +21,7 @@ import StoreFoundation
|
|||
func downloadQueue(_ queue: CKDownloadQueue, statusChangedFor download: SSDownload) {
|
||||
guard download.metadata.itemIdentifier == purchase.itemIdentifier,
|
||||
let status = download.status else {
|
||||
return
|
||||
return
|
||||
}
|
||||
|
||||
if status.isFailed || status.isCancelled {
|
||||
|
@ -42,7 +42,7 @@ import StoreFoundation
|
|||
func downloadQueue(_: CKDownloadQueue, changedWithRemoval download: SSDownload) {
|
||||
guard download.metadata.itemIdentifier == purchase.itemIdentifier,
|
||||
let status = download.status else {
|
||||
return
|
||||
return
|
||||
}
|
||||
|
||||
clearLine()
|
||||
|
|
|
@ -13,36 +13,36 @@ typealias SSPurchaseCompletion =
|
|||
(_ purchase: SSPurchase?, _ completed: Bool, _ error: Error?, _ response: SSPurchaseResponse?) -> Void
|
||||
|
||||
extension SSPurchase {
|
||||
convenience init(adamId: UInt64, account: ISStoreAccount, isPurchase: Bool) {
|
||||
convenience init(adamId: UInt64, account: ISStoreAccount, purchase: Bool = false) {
|
||||
self.init()
|
||||
|
||||
var parameters: [String: Any] = [
|
||||
"productType": "C",
|
||||
"price": 0,
|
||||
"salableAdamId": adamId,
|
||||
"pg": "default",
|
||||
"appExtVrsId": 0
|
||||
]
|
||||
var parameters: [String: Any] = [
|
||||
"productType": "C",
|
||||
"price": 0,
|
||||
"salableAdamId": adamId,
|
||||
"pg": "default",
|
||||
"appExtVrsId": 0
|
||||
]
|
||||
|
||||
if isPurchase {
|
||||
parameters["macappinstalledconfirmed"] = 1
|
||||
parameters["pricingParameters"] = "STDQ"
|
||||
if purchase {
|
||||
parameters["macappinstalledconfirmed"] = 1
|
||||
parameters["pricingParameters"] = "STDQ"
|
||||
|
||||
} else {
|
||||
// is redownload, use existing functionality
|
||||
parameters["pricingParameters"] = "STDRDL"
|
||||
parameters["pricingParameters"] = "STDRDL"
|
||||
}
|
||||
|
||||
buyParameters = parameters.map { key, value in
|
||||
return "\(key)=\(value)"
|
||||
}.joined(separator: "&")
|
||||
buyParameters = parameters.map { key, value in
|
||||
return "\(key)=\(value)"
|
||||
}.joined(separator: "&")
|
||||
|
||||
itemIdentifier = adamId
|
||||
accountIdentifier = account.dsID
|
||||
appleID = account.identifier
|
||||
|
||||
// Not sure if this is needed, but lets use it here.
|
||||
if isPurchase {
|
||||
if purchase {
|
||||
isRedownload = false
|
||||
}
|
||||
|
||||
|
|
|
@ -29,8 +29,7 @@ public struct HomeCommand: CommandProtocol {
|
|||
/// Runs the command.
|
||||
public func run(_ options: HomeOptions) -> Result<(), MASError> {
|
||||
do {
|
||||
guard let result = try storeSearch.lookup(app: options.appId)
|
||||
else {
|
||||
guard let result = try storeSearch.lookup(app: options.appId) else {
|
||||
print("No results found")
|
||||
return .failure(.noSearchResultsFound)
|
||||
}
|
||||
|
|
|
@ -25,8 +25,7 @@ public struct InfoCommand: CommandProtocol {
|
|||
/// Runs the command.
|
||||
public func run(_ options: InfoOptions) -> Result<(), MASError> {
|
||||
do {
|
||||
guard let result = try storeSearch.lookup(app: options.appId)
|
||||
else {
|
||||
guard let result = try storeSearch.lookup(app: options.appId) else {
|
||||
print("No results found")
|
||||
return .failure(.noSearchResultsFound)
|
||||
}
|
||||
|
|
|
@ -18,7 +18,6 @@ public struct InstallCommand: CommandProtocol {
|
|||
private let appLibrary: AppLibrary
|
||||
|
||||
/// Public initializer.
|
||||
/// - Parameter appLibrary: AppLibrary manager.
|
||||
public init() {
|
||||
self.init(appLibrary: MasAppLibrary())
|
||||
}
|
||||
|
@ -38,7 +37,7 @@ public struct InstallCommand: CommandProtocol {
|
|||
return nil
|
||||
}
|
||||
|
||||
return download(appId, isPurchase: false)
|
||||
return download(appId)
|
||||
}
|
||||
|
||||
switch downloadResults.count {
|
||||
|
|
|
@ -30,8 +30,8 @@ public struct LuckyCommand: CommandProtocol {
|
|||
/// - Parameter storeSearch: Search manager.
|
||||
init(appLibrary: AppLibrary = MasAppLibrary(),
|
||||
storeSearch: StoreSearch = MasStoreSearch()) {
|
||||
self.appLibrary = appLibrary
|
||||
self.storeSearch = storeSearch
|
||||
self.appLibrary = appLibrary
|
||||
self.storeSearch = storeSearch
|
||||
}
|
||||
|
||||
/// Runs the command.
|
||||
|
@ -73,7 +73,7 @@ public struct LuckyCommand: CommandProtocol {
|
|||
return nil
|
||||
}
|
||||
|
||||
return download(appId, isPurchase: false)
|
||||
return download(appId)
|
||||
}
|
||||
|
||||
switch downloadResults.count {
|
||||
|
|
|
@ -39,20 +39,20 @@ public struct OpenCommand: CommandProtocol {
|
|||
}
|
||||
|
||||
guard let appId = Int(options.appId)
|
||||
else {
|
||||
print("Invalid app ID")
|
||||
return .failure(.noSearchResultsFound)
|
||||
else {
|
||||
print("Invalid app ID")
|
||||
return .failure(.noSearchResultsFound)
|
||||
}
|
||||
|
||||
guard let result = try storeSearch.lookup(app: appId)
|
||||
else {
|
||||
print("No results found")
|
||||
return .failure(.noSearchResultsFound)
|
||||
else {
|
||||
print("No results found")
|
||||
return .failure(.noSearchResultsFound)
|
||||
}
|
||||
|
||||
guard var url = URLComponents(string: result.trackViewUrl)
|
||||
else {
|
||||
return .failure(.searchFailed)
|
||||
else {
|
||||
return .failure(.searchFailed)
|
||||
}
|
||||
url.scheme = masScheme
|
||||
|
||||
|
|
|
@ -38,8 +38,8 @@ public struct OutdatedCommand: CommandProtocol {
|
|||
if let installed = appLibrary.installedApp(forBundleId: update.bundleID) {
|
||||
// Display version of installed app compared to available update.
|
||||
print("""
|
||||
\(update.itemIdentifier) \(update.title) (\(installed.bundleVersion) -> \(update.bundleVersion))
|
||||
""")
|
||||
\(update.itemIdentifier) \(update.title) (\(installed.bundleVersion) -> \(update.bundleVersion))
|
||||
""")
|
||||
} else {
|
||||
print("\(update.itemIdentifier) \(update.title) (unknown -> \(update.bundleVersion))")
|
||||
}
|
||||
|
|
|
@ -10,53 +10,55 @@ import Commandant
|
|||
import CommerceKit
|
||||
|
||||
public struct PurchaseCommand: CommandProtocol {
|
||||
public typealias Options = PurchaseOptions
|
||||
public let verb = "purchase"
|
||||
public let function = "Purchase and download free apps from the Mac App Store"
|
||||
public typealias Options = PurchaseOptions
|
||||
public let verb = "purchase"
|
||||
public let function = "Purchase and download free apps from the Mac App Store"
|
||||
|
||||
/// Designated initializer.
|
||||
public init() {
|
||||
}
|
||||
private let appLibrary: AppLibrary
|
||||
|
||||
/// Runs the command.
|
||||
public func run(_ options: Options) -> Result<(), MASError> {
|
||||
// Try to download applications with given identifiers and collect results
|
||||
let downloadResults = options.appIds.compactMap { (appId) -> MASError? in
|
||||
if let product = installedApp(appId) {
|
||||
printWarning("\(product.appName) has already been purchased.")
|
||||
return nil
|
||||
}
|
||||
/// Public initializer.
|
||||
public init() {
|
||||
self.init(appLibrary: MasAppLibrary())
|
||||
}
|
||||
|
||||
return download(appId, isPurchase: true)
|
||||
}
|
||||
/// Internal initializer.
|
||||
/// - Parameter appLibrary: AppLibrary manager.
|
||||
init(appLibrary: AppLibrary = MasAppLibrary()) {
|
||||
self.appLibrary = appLibrary
|
||||
}
|
||||
|
||||
switch downloadResults.count {
|
||||
case 0:
|
||||
return .success(())
|
||||
case 1:
|
||||
return .failure(downloadResults[0])
|
||||
default:
|
||||
return .failure(.downloadFailed(error: nil))
|
||||
}
|
||||
}
|
||||
/// Runs the command.
|
||||
public func run(_ options: Options) -> Result<(), MASError> {
|
||||
// Try to download applications with given identifiers and collect results
|
||||
let downloadResults = options.appIds.compactMap { (appId) -> MASError? in
|
||||
if let product = appLibrary.installedApp(forId: appId) {
|
||||
printWarning("\(product.appName) has already been purchased.")
|
||||
return nil
|
||||
}
|
||||
|
||||
fileprivate func installedApp(_ appId: UInt64) -> CKSoftwareProduct? {
|
||||
let appId = NSNumber(value: appId)
|
||||
return download(appId, purchase: true)
|
||||
}
|
||||
|
||||
let softwareMap = CKSoftwareMap.shared()
|
||||
return softwareMap.allProducts()?.first { $0.itemIdentifier == appId }
|
||||
}
|
||||
switch downloadResults.count {
|
||||
case 0:
|
||||
return .success(())
|
||||
case 1:
|
||||
return .failure(downloadResults[0])
|
||||
default:
|
||||
return .failure(.downloadFailed(error: nil))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct PurchaseOptions: OptionsProtocol {
|
||||
let appIds: [UInt64]
|
||||
let appIds: [UInt64]
|
||||
|
||||
public static func create(_ appIds: [Int]) -> PurchaseOptions {
|
||||
return PurchaseOptions(appIds: appIds.map { UInt64($0) })
|
||||
}
|
||||
public static func create(_ appIds: [Int]) -> PurchaseOptions {
|
||||
return PurchaseOptions(appIds: appIds.map { UInt64($0) })
|
||||
}
|
||||
|
||||
public static func evaluate(_ mode: CommandMode) -> Result<PurchaseOptions, CommandantError<MASError>> {
|
||||
return create
|
||||
<*> mode <| Argument(usage: "app ID(s) to install")
|
||||
}
|
||||
public static func evaluate(_ mode: CommandMode) -> Result<PurchaseOptions, CommandantError<MASError>> {
|
||||
return create
|
||||
<*> mode <| Argument(usage: "app ID(s) to install")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,7 +56,7 @@ public struct SignInOptions: OptionsProtocol {
|
|||
static func create(username: String) -> (_ password: String) -> (_ dialog: Bool) -> SignInOptions {
|
||||
return { password in { dialog in
|
||||
SignInOptions(username: username, password: password, dialog: dialog)
|
||||
} }
|
||||
} }
|
||||
}
|
||||
|
||||
public static func evaluate(_ mode: CommandMode) -> Result<SignInOptions, CommandantError<MASError>> {
|
||||
|
|
|
@ -71,7 +71,7 @@ public struct UpgradeCommand: CommandProtocol {
|
|||
print(updates.map({ "\($0.title) (\($0.bundleVersion))" }).joined(separator: ", "))
|
||||
|
||||
let updateResults = updates.compactMap {
|
||||
download($0.itemIdentifier.uint64Value, isPurchase: false)
|
||||
download($0.itemIdentifier.uint64Value)
|
||||
}
|
||||
|
||||
switch updateResults.count {
|
||||
|
|
|
@ -30,13 +30,13 @@ public struct VendorCommand: CommandProtocol {
|
|||
public func run(_ options: VendorOptions) -> Result<(), MASError> {
|
||||
do {
|
||||
guard let result = try storeSearch.lookup(app: options.appId)
|
||||
else {
|
||||
print("No results found")
|
||||
return .failure(.noSearchResultsFound)
|
||||
else {
|
||||
print("No results found")
|
||||
return .failure(.noSearchResultsFound)
|
||||
}
|
||||
|
||||
guard let vendorWebsite = result.sellerUrl
|
||||
else { throw MASError.noVendorWebsite }
|
||||
else { throw MASError.noVendorWebsite }
|
||||
|
||||
do {
|
||||
try openCommand.run(arguments: vendorWebsite)
|
||||
|
|
|
@ -22,17 +22,17 @@ public class MasStoreSearch: StoreSearch {
|
|||
/// - Throws: Error if there is a problem with the network request.
|
||||
public func search(for appName: String) throws -> SearchResultList {
|
||||
guard let url = searchURL(for: appName)
|
||||
else { throw MASError.urlEncoding }
|
||||
else { throw MASError.urlEncoding }
|
||||
|
||||
let result = networkManager.loadDataSync(from: url)
|
||||
|
||||
// Unwrap network result
|
||||
guard case let .success(data) = result
|
||||
else {
|
||||
if case let .failure(error) = result {
|
||||
throw error
|
||||
}
|
||||
throw MASError.noData
|
||||
else {
|
||||
if case let .failure(error) = result {
|
||||
throw error
|
||||
}
|
||||
throw MASError.noData
|
||||
}
|
||||
|
||||
do {
|
||||
|
@ -50,24 +50,24 @@ public class MasStoreSearch: StoreSearch {
|
|||
/// - Throws: Error if there is a problem with the network request.
|
||||
public func lookup(app appId: Int) throws -> SearchResult? {
|
||||
guard let url = lookupURL(forApp: appId)
|
||||
else { throw MASError.urlEncoding }
|
||||
else { throw MASError.urlEncoding }
|
||||
|
||||
let result = networkManager.loadDataSync(from: url)
|
||||
|
||||
// Unwrap network result
|
||||
guard case let .success(data) = result
|
||||
else {
|
||||
if case let .failure(error) = result {
|
||||
throw error
|
||||
}
|
||||
throw MASError.noData
|
||||
else {
|
||||
if case let .failure(error) = result {
|
||||
throw error
|
||||
}
|
||||
throw MASError.noData
|
||||
}
|
||||
|
||||
do {
|
||||
let results = try JSONDecoder().decode(SearchResultList.self, from: data)
|
||||
|
||||
guard let searchResult = results.results.first
|
||||
else { return nil }
|
||||
else { return nil }
|
||||
|
||||
return searchResult
|
||||
} catch {
|
||||
|
|
|
@ -42,7 +42,7 @@ extension MASError: CustomStringConvertible {
|
|||
return "The 'signin' command has been disabled on this macOS version. " +
|
||||
"Please sign into the Mac App Store app manually." +
|
||||
"\nFor more info see: " +
|
||||
"https://github.com/mas-cli/mas/issues/164"
|
||||
"https://github.com/mas-cli/mas/issues/164"
|
||||
|
||||
case let .signInFailed(error):
|
||||
if let error = error {
|
||||
|
|
|
@ -19,7 +19,7 @@ struct AppInfoFormatter {
|
|||
"\(app.trackName)",
|
||||
"\(app.version)",
|
||||
"[\(app.price)]"
|
||||
].joined(separator: " ")
|
||||
].joined(separator: " ")
|
||||
|
||||
return [
|
||||
headline,
|
||||
|
@ -28,7 +28,7 @@ struct AppInfoFormatter {
|
|||
"Minimum OS: \(app.minimumOsVersion)",
|
||||
"Size: \(humanReadableSize(app.fileSizeBytes))",
|
||||
"From: \(app.trackViewUrl)"
|
||||
].joined(separator: "\n")
|
||||
].joined(separator: "\n")
|
||||
}
|
||||
|
||||
/// Formats a file size.
|
||||
|
|
|
@ -20,9 +20,6 @@ extension NetworkResult: Equatable {
|
|||
case let (.failure(error1), .failure(error2)):
|
||||
return error1.localizedDescription == error2.localizedDescription
|
||||
|
||||
// case (.none, .none):
|
||||
// return true
|
||||
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
|
10
MasKitTests/.swiftlint.yml
Normal file
10
MasKitTests/.swiftlint.yml
Normal file
|
@ -0,0 +1,10 @@
|
|||
#
|
||||
# .swiftlint.yml
|
||||
# MasKitTests
|
||||
#
|
||||
# https://github.com/realm/SwiftLint#configuration
|
||||
#
|
||||
---
|
||||
disabled_rules:
|
||||
- force_cast
|
||||
- function_body_length
|
|
@ -17,7 +17,7 @@ class AccountCommandSpec: QuickSpec {
|
|||
let cmd = AccountCommand()
|
||||
let result = cmd.run(AccountCommand.Options())
|
||||
print(result)
|
||||
// expect(result).to(beSuccess())
|
||||
// expect(result).to(beSuccess())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ class InstallCommandSpec: QuickSpec {
|
|||
let cmd = InstallCommand()
|
||||
let result = cmd.run(InstallCommand.Options(appIds: [], forceInstall: false))
|
||||
print(result)
|
||||
// expect(result).to(beSuccess())
|
||||
// expect(result).to(beSuccess())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ class ListCommandSpec: QuickSpec {
|
|||
let list = ListCommand()
|
||||
let result = list.run(ListCommand.Options())
|
||||
print(result)
|
||||
// expect(result).to(beSuccess())
|
||||
// expect(result).to(beSuccess())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ class LuckyCommandSpec: QuickSpec {
|
|||
let cmd = LuckyCommand()
|
||||
let result = cmd.run(LuckyCommand.Options(appName: "", forceInstall: false))
|
||||
print(result)
|
||||
// expect(result).to(beSuccess())
|
||||
// expect(result).to(beSuccess())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ class OutdatedCommandSpec: QuickSpec {
|
|||
let cmd = OutdatedCommand()
|
||||
let result = cmd.run(OutdatedCommand.Options())
|
||||
print(result)
|
||||
// expect(result).to(beSuccess())
|
||||
// expect(result).to(beSuccess())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ class PurchaseCommandSpec: QuickSpec {
|
|||
let cmd = PurchaseCommand()
|
||||
let result = cmd.run(PurchaseCommand.Options(appIds: []))
|
||||
print(result)
|
||||
// expect(result).to(beSuccess())
|
||||
// expect(result).to(beSuccess())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ class ResetCommandSpec: QuickSpec {
|
|||
let cmd = ResetCommand()
|
||||
let result = cmd.run(ResetCommand.Options(debug: false))
|
||||
print(result)
|
||||
// expect(result).to(beSuccess())
|
||||
// expect(result).to(beSuccess())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ class SignInCommandSpec: QuickSpec {
|
|||
let cmd = SignInCommand()
|
||||
let result = cmd.run(SignInCommand.Options(username: "", password: "", dialog: false))
|
||||
print(result)
|
||||
// expect(result).to(beSuccess())
|
||||
// expect(result).to(beSuccess())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ class SignOutCommandSpec: QuickSpec {
|
|||
let cmd = SignOutCommand()
|
||||
let result = cmd.run(SignOutCommand.Options())
|
||||
print(result)
|
||||
// expect(result).to(beSuccess())
|
||||
// expect(result).to(beSuccess())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ class UpgradeCommandSpec: QuickSpec {
|
|||
let cmd = UpgradeCommand()
|
||||
let result = cmd.run(UpgradeCommand.Options(apps: [""]))
|
||||
print(result)
|
||||
// expect(result).to(beSuccess())
|
||||
// expect(result).to(beSuccess())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ class VersionCommandSpec: QuickSpec {
|
|||
let cmd = VersionCommand()
|
||||
let result = cmd.run(VersionCommand.Options())
|
||||
print(result)
|
||||
// expect(result).to(beSuccess())
|
||||
// expect(result).to(beSuccess())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,15 +48,9 @@ struct SoftwareMapMock: SoftwareMap {
|
|||
}
|
||||
|
||||
func product(for bundleIdentifier: String) -> SoftwareProduct? {
|
||||
for product in products {
|
||||
if product.bundleIdentifier == bundleIdentifier {
|
||||
return product
|
||||
}
|
||||
for product in products where product.bundleIdentifier == bundleIdentifier {
|
||||
return product
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
//public func == (lhs: Expectation<[SoftwareProduct]>, rhs: [SoftwareProduct]) {
|
||||
// lhs.to(beCloseTo(rhs))
|
||||
//}
|
||||
|
|
|
@ -23,7 +23,7 @@ class StoreSearchMock: StoreSearch {
|
|||
}
|
||||
|
||||
guard let result = apps[appId]
|
||||
else { throw MASError.noSearchResultsFound }
|
||||
else { throw MASError.noSearchResultsFound }
|
||||
|
||||
return result
|
||||
}
|
||||
|
|
|
@ -24,14 +24,14 @@ class StoreSearchSpec: QuickSpec {
|
|||
let appName = "myapp"
|
||||
let urlString = storeSearch.searchURLString(forApp: appName)
|
||||
expect(urlString) ==
|
||||
"https://itunes.apple.com/search?media=software&entity=macSoftware&term=\(appName)"
|
||||
"https://itunes.apple.com/search?media=software&entity=macSoftware&term=\(appName)"
|
||||
}
|
||||
it("contains the encoded app name") {
|
||||
let appName = "My App"
|
||||
let appNameEncoded = "My%20App"
|
||||
let urlString = storeSearch.searchURLString(forApp: appName)
|
||||
expect(urlString) ==
|
||||
"https://itunes.apple.com/search?media=software&entity=macSoftware&term=\(appNameEncoded)"
|
||||
"https://itunes.apple.com/search?media=software&entity=macSoftware&term=\(appNameEncoded)"
|
||||
}
|
||||
// Find a character that causes addingPercentEncoding(withAllowedCharacters to return nil
|
||||
xit("is nil when app name cannot be url encoded") {
|
||||
|
|
|
@ -25,7 +25,7 @@ extension Bundle {
|
|||
guard let path = self.path(forResource: fileName.fileNameWithoutExtension,
|
||||
ofType: fileName.fileExtension,
|
||||
inDirectory: "JSON")
|
||||
else { fatalError("Unable to load file \(fileName)") }
|
||||
else { fatalError("Unable to load file \(fileName)") }
|
||||
|
||||
return URL(fileURLWithPath: path)
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ class SearchResultsFormatterSpec: QuickSpec {
|
|||
trackId: 12345,
|
||||
trackName: "Awesome App",
|
||||
version: "19.2.1"
|
||||
)]
|
||||
)]
|
||||
let output = format(results, false)
|
||||
expect(output) == " 12345 Awesome App (19.2.1)"
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ class SearchResultsFormatterSpec: QuickSpec {
|
|||
trackId: 12345,
|
||||
trackName: "Awesome App",
|
||||
version: "19.2.1"
|
||||
)]
|
||||
)]
|
||||
let output = format(results, true)
|
||||
expect(output) == " 12345 Awesome App $ 9.87 (19.2.1)"
|
||||
}
|
||||
|
@ -61,7 +61,7 @@ class SearchResultsFormatterSpec: QuickSpec {
|
|||
]
|
||||
let output = format(results, false)
|
||||
expect(output) ==
|
||||
" 12345 Awesome App (19.2.1)\n 67890 Even Better App (1.2.0)"
|
||||
" 12345 Awesome App (19.2.1)\n 67890 Even Better App (1.2.0)"
|
||||
}
|
||||
it("can format a two results with prices") {
|
||||
results = [
|
||||
|
@ -80,7 +80,7 @@ class SearchResultsFormatterSpec: QuickSpec {
|
|||
]
|
||||
let output = format(results, true)
|
||||
expect(output) ==
|
||||
" 12345 Awesome App $ 9.87 (19.2.1)\n 67890 Even Better App $ 0.01 (1.2.0)"
|
||||
" 12345 Awesome App $ 9.87 (19.2.1)\n 67890 Even Better App $ 0.01 (1.2.0)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ class NetworkSessionMockFromFile: NetworkSessionMock {
|
|||
/// - completionHandler: Closure which is delivered either data or an error.
|
||||
@objc override func loadData(from _: URL, completionHandler: @escaping (Data?, Error?) -> Void) {
|
||||
guard let fileURL = Bundle.jsonResponse(fileName: responseFile)
|
||||
else { fatalError("Unable to load file \(responseFile)") }
|
||||
else { fatalError("Unable to load file \(responseFile)") }
|
||||
|
||||
do {
|
||||
let data = try Data(contentsOf: fileURL, options: .mappedIfSafe)
|
||||
|
|
|
@ -13,7 +13,8 @@ class TestURLSessionDelegate: NSObject, URLSessionDelegate {
|
|||
func urlSession(_: URLSession,
|
||||
didReceive challenge: URLAuthenticationChallenge,
|
||||
completionHandler: (URLSession.AuthChallengeDisposition,
|
||||
URLCredential?) -> Void) {
|
||||
URLCredential?) -> Void) {
|
||||
|
||||
// For example, you may want to override this to accept some self-signed certs here.
|
||||
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust,
|
||||
Constants.selfSignedHosts.contains(challenge.protectionSpace.host) {
|
||||
|
|
|
@ -19,6 +19,7 @@ fi
|
|||
# : command: usage: command [-pVv] command [arg ...]
|
||||
|
||||
if command -v swiftlint > /dev/null; then
|
||||
swiftlint autocorrect --format
|
||||
swiftlint lint --quiet
|
||||
else
|
||||
echo "warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint"
|
||||
|
|
Loading…
Reference in a new issue