Merge pull request #600 from rgoldberg/585-naming

Improve naming
This commit is contained in:
Ross Goldberg 2024-10-25 23:44:08 -04:00 committed by GitHub
commit 12832f293d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
62 changed files with 226 additions and 216 deletions

View file

@ -47,7 +47,8 @@ private func downloadWithRetries(_ appID: AppID, purchase: Bool = false, attempt
}
// If the download failed due to network issues, try again. Otherwise, fail immediately.
guard case MASError.downloadFailed(let downloadError) = error,
guard
case MASError.downloadFailed(let downloadError) = error,
case NSURLErrorDomain = downloadError?.domain
else {
throw error

View file

@ -32,7 +32,7 @@ extension ISStoreAccount: StoreAccount {
return .value(CKAccountStore.shared().primaryAccount)
}
static func signIn(username: String, password: String, systemDialog: Bool) -> Promise<ISStoreAccount> {
static func signIn(appleID: String, password: String, systemDialog: Bool) -> Promise<ISStoreAccount> {
// swift-format-ignore: UseEarlyExits
if #available(macOS 10.13, *) {
// Signing in is no longer possible as of High Sierra.
@ -44,7 +44,7 @@ extension ISStoreAccount: StoreAccount {
primaryAccount
.then { account -> Promise<ISStoreAccount> in
if account.isSignedIn {
return Promise(error: MASError.alreadySignedIn(asAccountId: account.identifier))
return Promise(error: MASError.alreadySignedIn(asAppleID: account.identifier))
}
let password =
@ -57,7 +57,7 @@ extension ISStoreAccount: StoreAccount {
}
let context = ISAuthenticationContext(accountID: 0)
context.appleIDOverride = username
context.appleIDOverride = appleID
let signInPromise =
Promise<ISStoreAccount> { seal in
@ -77,7 +77,7 @@ extension ISStoreAccount: StoreAccount {
}
context.demoMode = true
context.demoAccountName = username
context.demoAccountName = appleID
context.demoAccountPassword = password
context.demoAutologinMode = true

View file

@ -20,7 +20,8 @@ class PurchaseDownloadObserver: NSObject, CKDownloadQueueObserver {
}
func downloadQueue(_ queue: CKDownloadQueue, statusChangedFor download: SSDownload) {
guard download.metadata.itemIdentifier == purchase.itemIdentifier,
guard
download.metadata.itemIdentifier == purchase.itemIdentifier,
let status = download.status
else {
return
@ -42,7 +43,8 @@ class PurchaseDownloadObserver: NSObject, CKDownloadQueueObserver {
}
func downloadQueue(_: CKDownloadQueue, changedWithRemoval download: SSDownload) {
guard download.metadata.itemIdentifier == purchase.itemIdentifier,
guard
download.metadata.itemIdentifier == purchase.itemIdentifier,
let status = download.status
else {
return

View file

@ -9,7 +9,7 @@
import ArgumentParser
import StoreFoundation
extension Mas {
extension MAS {
struct Account: ParsableCommand {
static let configuration = CommandConfiguration(
abstract: "Prints the primary account Apple ID"

View file

@ -8,7 +8,7 @@
import ArgumentParser
extension Mas {
extension MAS {
/// Opens app page on MAS Preview. Uses the iTunes Lookup API:
/// https://performance-partners.apple.com/search-api
struct Home: ParsableCommand {
@ -21,12 +21,12 @@ extension Mas {
/// Runs the command.
func run() throws {
try run(storeSearch: MasStoreSearch(), openCommand: OpenSystemCommand())
try run(searcher: ITunesSearchAppStoreSearcher(), openCommand: OpenSystemCommand())
}
func run(storeSearch: StoreSearch, openCommand: ExternalCommand) throws {
func run(searcher: AppStoreSearcher, openCommand: ExternalCommand) throws {
do {
guard let result = try storeSearch.lookup(appID: appID).wait() else {
guard let result = try searcher.lookup(appID: appID).wait() else {
throw MASError.noSearchResultsFound
}

View file

@ -9,7 +9,7 @@
import ArgumentParser
import Foundation
extension Mas {
extension MAS {
/// Displays app details. Uses the iTunes Lookup API:
/// https://performance-partners.apple.com/search-api
struct Info: ParsableCommand {
@ -22,12 +22,12 @@ extension Mas {
/// Runs the command.
func run() throws {
try run(storeSearch: MasStoreSearch())
try run(searcher: ITunesSearchAppStoreSearcher())
}
func run(storeSearch: StoreSearch) throws {
func run(searcher: AppStoreSearcher) throws {
do {
guard let result = try storeSearch.lookup(appID: appID).wait() else {
guard let result = try searcher.lookup(appID: appID).wait() else {
throw MASError.noSearchResultsFound
}

View file

@ -9,7 +9,7 @@
import ArgumentParser
import CommerceKit
extension Mas {
extension MAS {
/// Installs previously purchased apps from the Mac App Store.
struct Install: ParsableCommand {
static let configuration = CommandConfiguration(
@ -23,7 +23,7 @@ extension Mas {
/// Runs the command.
func run() throws {
try run(appLibrary: MasAppLibrary())
try run(appLibrary: SoftwareMapAppLibrary())
}
func run(appLibrary: AppLibrary) throws {

View file

@ -8,7 +8,7 @@
import ArgumentParser
extension Mas {
extension MAS {
/// Command which lists all installed apps.
struct List: ParsableCommand {
static let configuration = CommandConfiguration(
@ -17,7 +17,7 @@ extension Mas {
/// Runs the command.
func run() throws {
try run(appLibrary: MasAppLibrary())
try run(appLibrary: SoftwareMapAppLibrary())
}
func run(appLibrary: AppLibrary) throws {

View file

@ -9,7 +9,7 @@
import ArgumentParser
import CommerceKit
extension Mas {
extension MAS {
/// Command which installs the first search result.
///
/// This is handy as many MAS titles can be long with embedded keywords.
@ -21,18 +21,18 @@ extension Mas {
@Flag(help: "force reinstall")
var force = false
@Argument(help: "the app name to install")
var appName: String
var searchTerm: String
/// Runs the command.
func run() throws {
try run(appLibrary: MasAppLibrary(), storeSearch: MasStoreSearch())
try run(appLibrary: SoftwareMapAppLibrary(), searcher: ITunesSearchAppStoreSearcher())
}
func run(appLibrary: AppLibrary, storeSearch: StoreSearch) throws {
func run(appLibrary: AppLibrary, searcher: AppStoreSearcher) throws {
var appID: AppID?
do {
let results = try storeSearch.search(for: appName).wait()
let results = try searcher.search(for: searchTerm).wait()
guard let result = results.first else {
printError("No results found")
throw MASError.noSearchResultsFound

View file

@ -11,12 +11,12 @@ import Foundation
private let masScheme = "macappstore"
extension Mas {
extension MAS {
/// Opens app page in MAS app. Uses the iTunes Lookup API:
/// https://performance-partners.apple.com/search-api
struct Open: ParsableCommand {
static let configuration = CommandConfiguration(
abstract: "Opens app page in AppStore.app"
abstract: "Opens app page in 'App Store.app'"
)
@Argument(help: "the app ID")
@ -24,10 +24,10 @@ extension Mas {
/// Runs the command.
func run() throws {
try run(storeSearch: MasStoreSearch(), openCommand: OpenSystemCommand())
try run(searcher: ITunesSearchAppStoreSearcher(), openCommand: OpenSystemCommand())
}
func run(storeSearch: StoreSearch, openCommand: ExternalCommand) throws {
func run(searcher: AppStoreSearcher, openCommand: ExternalCommand) throws {
do {
guard let appID else {
// If no app ID is given, just open the MAS GUI app
@ -35,7 +35,7 @@ extension Mas {
return
}
guard let result = try storeSearch.lookup(appID: appID).wait() else {
guard let result = try searcher.lookup(appID: appID).wait() else {
throw MASError.noSearchResultsFound
}

View file

@ -10,7 +10,7 @@ import ArgumentParser
import Foundation
import PromiseKit
extension Mas {
extension MAS {
/// Command which displays a list of installed apps which have available updates
/// ready to be installed from the Mac App Store.
struct Outdated: ParsableCommand {
@ -23,15 +23,15 @@ extension Mas {
/// Runs the command.
func run() throws {
try run(appLibrary: MasAppLibrary(), storeSearch: MasStoreSearch())
try run(appLibrary: SoftwareMapAppLibrary(), searcher: ITunesSearchAppStoreSearcher())
}
func run(appLibrary: AppLibrary, storeSearch: StoreSearch) throws {
func run(appLibrary: AppLibrary, searcher: AppStoreSearcher) throws {
_ = try when(
fulfilled:
appLibrary.installedApps.map { installedApp in
firstly {
storeSearch.lookup(appID: installedApp.itemIdentifier.appIDValue)
searcher.lookup(appID: installedApp.itemIdentifier.appIDValue)
}
.done { storeApp in
guard let storeApp else {

View file

@ -9,7 +9,7 @@
import ArgumentParser
import CommerceKit
extension Mas {
extension MAS {
struct Purchase: ParsableCommand {
static let configuration = CommandConfiguration(
abstract: "Purchase and download free apps from the Mac App Store"
@ -20,7 +20,7 @@ extension Mas {
/// Runs the command.
func run() throws {
try run(appLibrary: MasAppLibrary())
try run(appLibrary: SoftwareMapAppLibrary())
}
func run(appLibrary: AppLibrary) throws {

View file

@ -9,7 +9,7 @@
import ArgumentParser
import CommerceKit
extension Mas {
extension MAS {
/// Kills several macOS processes as a means to reset the app store.
struct Reset: ParsableCommand {
static let configuration = CommandConfiguration(

View file

@ -8,7 +8,7 @@
import ArgumentParser
extension Mas {
extension MAS {
/// Search the Mac App Store using the iTunes Search API.
///
/// See - https://performance-partners.apple.com/search-api
@ -20,15 +20,15 @@ extension Mas {
@Flag(help: "Show price of found apps")
var price = false
@Argument(help: "the app name to search")
var appName: String
var searchTerm: String
func run() throws {
try run(storeSearch: MasStoreSearch())
try run(searcher: ITunesSearchAppStoreSearcher())
}
func run(storeSearch: StoreSearch) throws {
func run(searcher: AppStoreSearcher) throws {
do {
let results = try storeSearch.search(for: appName).wait()
let results = try searcher.search(for: searchTerm).wait()
if results.isEmpty {
throw MASError.noSearchResultsFound
}

View file

@ -9,7 +9,7 @@
import ArgumentParser
import StoreFoundation
extension Mas {
extension MAS {
struct SignIn: ParsableCommand {
static let configuration = CommandConfiguration(
commandName: "signin",
@ -19,14 +19,14 @@ extension Mas {
@Flag(help: "Complete login with graphical dialog")
var dialog = false
@Argument(help: "Apple ID")
var username: String
var appleID: String
@Argument(help: "Password")
var password: String = ""
/// Runs the command.
func run() throws {
do {
_ = try ISStoreAccount.signIn(username: username, password: password, systemDialog: dialog).wait()
_ = try ISStoreAccount.signIn(appleID: appleID, password: password, systemDialog: dialog).wait()
} catch {
throw error as? MASError ?? MASError.signInFailed(error: error as NSError)
}

View file

@ -9,7 +9,7 @@
import ArgumentParser
import CommerceKit
extension Mas {
extension MAS {
struct SignOut: ParsableCommand {
static let configuration = CommandConfiguration(
commandName: "signout",

View file

@ -9,7 +9,7 @@
import ArgumentParser
import Foundation
extension Mas {
extension MAS {
/// Command which uninstalls apps managed by the Mac App Store.
struct Uninstall: ParsableCommand {
static let configuration = CommandConfiguration(
@ -24,7 +24,7 @@ extension Mas {
/// Runs the uninstall command.
func run() throws {
try run(appLibrary: MasAppLibrary())
try run(appLibrary: SoftwareMapAppLibrary())
}
func run(appLibrary: AppLibrary) throws {

View file

@ -10,7 +10,7 @@ import ArgumentParser
import Foundation
import PromiseKit
extension Mas {
extension MAS {
/// Command which upgrades apps with new versions available in the Mac App Store.
struct Upgrade: ParsableCommand {
static let configuration = CommandConfiguration(
@ -22,13 +22,13 @@ extension Mas {
/// Runs the command.
func run() throws {
try run(appLibrary: MasAppLibrary(), storeSearch: MasStoreSearch())
try run(appLibrary: SoftwareMapAppLibrary(), searcher: ITunesSearchAppStoreSearcher())
}
func run(appLibrary: AppLibrary, storeSearch: StoreSearch) throws {
func run(appLibrary: AppLibrary, searcher: AppStoreSearcher) throws {
let apps: [(installedApp: SoftwareProduct, storeApp: SearchResult)]
do {
apps = try findOutdatedApps(appLibrary: appLibrary, storeSearch: storeSearch)
apps = try findOutdatedApps(appLibrary: appLibrary, searcher: searcher)
} catch {
throw error as? MASError ?? .searchFailed
}
@ -53,7 +53,7 @@ extension Mas {
private func findOutdatedApps(
appLibrary: AppLibrary,
storeSearch: StoreSearch
searcher: AppStoreSearcher
) throws -> [(SoftwareProduct, SearchResult)] {
let apps =
appIDs.isEmpty
@ -71,7 +71,7 @@ extension Mas {
let promises = apps.map { installedApp in
// only upgrade apps whose local version differs from the store version
firstly {
storeSearch.lookup(appID: installedApp.itemIdentifier.appIDValue)
searcher.lookup(appID: installedApp.itemIdentifier.appIDValue)
}
.map { result -> (SoftwareProduct, SearchResult)? in
guard let storeApp = result, installedApp.isOutdatedWhenComparedTo(storeApp) else {

View file

@ -8,7 +8,7 @@
import ArgumentParser
extension Mas {
extension MAS {
/// Opens vendor's app page in a browser. Uses the iTunes Lookup API:
/// https://performance-partners.apple.com/search-api
struct Vendor: ParsableCommand {
@ -21,12 +21,12 @@ extension Mas {
/// Runs the command.
func run() throws {
try run(storeSearch: MasStoreSearch(), openCommand: OpenSystemCommand())
try run(searcher: ITunesSearchAppStoreSearcher(), openCommand: OpenSystemCommand())
}
func run(storeSearch: StoreSearch, openCommand: ExternalCommand) throws {
func run(searcher: AppStoreSearcher, openCommand: ExternalCommand) throws {
do {
guard let result = try storeSearch.lookup(appID: appID).wait() else {
guard let result = try searcher.lookup(appID: appID).wait() else {
throw MASError.noSearchResultsFound
}

View file

@ -8,7 +8,7 @@
import ArgumentParser
extension Mas {
extension MAS {
/// Command which displays the version of the mas tool.
struct Version: ParsableCommand {
static let configuration = CommandConfiguration(

View file

@ -1,5 +1,5 @@
//
// StoreSearch.swift
// AppStoreSearcher.swift
// mas
//
// Created by Ben Chatelain on 12/29/18.
@ -10,9 +10,9 @@ import Foundation
import PromiseKit
/// Protocol for searching the MAS catalog.
protocol StoreSearch {
protocol AppStoreSearcher {
func lookup(appID: AppID) -> Promise<SearchResult?>
func search(for appName: String) -> Promise<[SearchResult]>
func search(for searchTerm: String) -> Promise<[SearchResult]>
}
enum Entity: String {
@ -37,7 +37,7 @@ private enum URLAction {
}
// MARK: - Common methods
extension StoreSearch {
extension AppStoreSearcher {
/// Builds the search URL for an app.
///
/// - Parameters:

View file

@ -1,5 +1,5 @@
//
// MasStoreSearch.swift
// ITunesSearchAppStoreSearcher.swift
// mas
//
// Created by Ben Chatelain on 12/29/18.
@ -12,7 +12,7 @@ import Regex
import Version
/// Manages searching the MAS catalog through the iTunes Search and Lookup APIs.
class MasStoreSearch: StoreSearch {
class ITunesSearchAppStoreSearcher: AppStoreSearcher {
private static let appVersionExpression = Regex(#"\"versionDisplay\"\:\"([^\"]+)\""#)
// CommerceKit and StoreFoundation don't seem to expose the region of the Apple ID signed
@ -77,15 +77,16 @@ class MasStoreSearch: StoreSearch {
return .value(nil)
}
guard let pageUrl = URL(string: result.trackViewUrl) else {
guard let pageURL = URL(string: result.trackViewUrl) else {
return .value(result)
}
return firstly {
self.scrapeAppStoreVersion(pageUrl)
self.scrapeAppStoreVersion(pageURL)
}
.map { pageVersion in
guard let pageVersion,
guard
let pageVersion,
let searchVersion = Version(tolerant: result.version),
pageVersion > searchVersion
else {
@ -120,12 +121,13 @@ class MasStoreSearch: StoreSearch {
/// Scrape the app version from the App Store webpage at the given URL.
///
/// App Store webpages frequently report a version that is newer than what is reported by the iTunes Search API.
private func scrapeAppStoreVersion(_ pageUrl: URL) -> Promise<Version?> {
private func scrapeAppStoreVersion(_ pageURL: URL) -> Promise<Version?> {
firstly {
networkManager.loadData(from: pageUrl)
networkManager.loadData(from: pageURL)
}
.map { data in
guard let html = String(data: data, encoding: .utf8),
guard
let html = String(data: data, encoding: .utf8),
let capture = Self.appVersionExpression.firstMatch(in: html)?.captures[0],
let version = Version(tolerant: capture)
else {

View file

@ -1,5 +1,5 @@
//
// MasAppLibrary.swift
// SoftwareMapAppLibrary.swift
// mas
//
// Created by Ben Chatelain on 12/27/18.
@ -10,7 +10,7 @@ import CommerceKit
import ScriptingBridge
/// Utility for managing installed apps.
class MasAppLibrary: AppLibrary {
class SoftwareMapAppLibrary: AppLibrary {
/// CommerceKit's singleton manager of installed software.
private let softwareMap: SoftwareMap
@ -28,10 +28,10 @@ class MasAppLibrary: AppLibrary {
/// Finds an app using a bundle identifier.
///
/// - Parameter bundleId: Bundle identifier of app.
/// - Returns: Software Product of app if found; nil otherwise.
func installedApp(forBundleId bundleId: String) -> SoftwareProduct? {
softwareMap.product(for: bundleId)
/// - Parameter bundleID: Bundle identifier of app.
/// - Returns: `SoftwareProduct` for app if found; `nil` otherwise.
func installedApp(forBundleID bundleID: String) -> SoftwareProduct? {
softwareMap.product(for: bundleID)
}
/// Uninstalls all apps located at any of the elements of `appPaths`.

View file

@ -18,7 +18,7 @@ enum MASError: Error, Equatable {
case notSignedIn
case noPasswordProvided
case signInFailed(error: NSError?)
case alreadySignedIn(asAccountId: String)
case alreadySignedIn(asAppleID: String)
case purchaseFailed(error: NSError?)
case downloadFailed(error: NSError?)
@ -63,8 +63,8 @@ extension MASError: CustomStringConvertible {
return "Sign in failed: \(error.localizedDescription)"
}
return "Sign in failed"
case .alreadySignedIn(let accountId):
return "Already signed in as \(accountId)"
case .alreadySignedIn(let appleID):
return "Already signed in as \(appleID)"
case .purchaseFailed(let error):
if let error {
return "Download request failed: \(error.localizedDescription)"

View file

@ -1,5 +1,5 @@
//
// Mas.swift
// MAS.swift
// mas
//
// Created by Chris Araman on 4/22/21.
@ -11,7 +11,7 @@ import Foundation
import PromiseKit
@main
struct Mas: ParsableCommand {
struct MAS: ParsableCommand {
static let configuration = CommandConfiguration(
abstract: "Mac App Store command-line interface",
subcommands: [

View file

@ -45,7 +45,8 @@ extension SoftwareProduct {
// The App Store does not enforce semantic versioning, but we assume most apps follow versioning
// schemes that increase numerically over time.
guard let semanticBundleVersion = Version(tolerant: bundleVersion),
guard
let semanticBundleVersion = Version(tolerant: bundleVersion),
let semanticAppStoreVersion = Version(tolerant: storeApp.version)
else {
// If a version string can't be parsed as a Semantic Version, our best effort is to check for

View file

@ -15,13 +15,13 @@ import Quick
public class AccountSpec: QuickSpec {
override public func spec() {
beforeSuite {
Mas.initialize()
MAS.initialize()
}
// account command disabled since macOS 12 Monterey https://github.com/mas-cli/mas#known-issues
describe("Account command") {
it("displays active account") {
expect {
try Mas.Account.parse([]).run()
try MAS.Account.parse([]).run()
}
.to(throwError(MASError.notSupported))
}

View file

@ -13,25 +13,25 @@ import Quick
public class HomeSpec: QuickSpec {
override public func spec() {
let storeSearch = StoreSearchMock()
let openCommand = OpenSystemCommandMock()
let searcher = MockAppStoreSearcher()
let openCommand = MockOpenSystemCommand()
beforeSuite {
Mas.initialize()
MAS.initialize()
}
describe("home command") {
beforeEach {
storeSearch.reset()
searcher.reset()
}
it("fails to open app with invalid ID") {
expect {
try Mas.Home.parse(["--", "-999"]).run(storeSearch: storeSearch, openCommand: openCommand)
try MAS.Home.parse(["--", "-999"]).run(searcher: searcher, openCommand: openCommand)
}
.to(throwError())
}
it("can't find app with unknown ID") {
expect {
try Mas.Home.parse(["999"]).run(storeSearch: storeSearch, openCommand: openCommand)
try MAS.Home.parse(["999"]).run(searcher: searcher, openCommand: openCommand)
}
.to(throwError(MASError.noSearchResultsFound))
}
@ -41,10 +41,10 @@ public class HomeSpec: QuickSpec {
trackViewUrl: "mas preview url",
version: "0.0"
)
storeSearch.apps[mockResult.trackId] = mockResult
searcher.apps[mockResult.trackId] = mockResult
expect {
try Mas.Home.parse([String(mockResult.trackId)])
.run(storeSearch: storeSearch, openCommand: openCommand)
try MAS.Home.parse([String(mockResult.trackId)])
.run(searcher: searcher, openCommand: openCommand)
return openCommand.arguments
}
== [mockResult.trackViewUrl]

View file

@ -14,24 +14,24 @@ import Quick
public class InfoSpec: QuickSpec {
override public func spec() {
let storeSearch = StoreSearchMock()
let searcher = MockAppStoreSearcher()
beforeSuite {
Mas.initialize()
MAS.initialize()
}
describe("Info command") {
beforeEach {
storeSearch.reset()
searcher.reset()
}
it("fails to open app with invalid ID") {
expect {
try Mas.Info.parse(["--", "-999"]).run(storeSearch: storeSearch)
try MAS.Info.parse(["--", "-999"]).run(searcher: searcher)
}
.to(throwError())
}
it("can't find app with unknown ID") {
expect {
try Mas.Info.parse(["999"]).run(storeSearch: storeSearch)
try MAS.Info.parse(["999"]).run(searcher: searcher)
}
.to(throwError(MASError.noSearchResultsFound))
}
@ -47,10 +47,10 @@ public class InfoSpec: QuickSpec {
trackViewUrl: "https://awesome.app",
version: "1.0"
)
storeSearch.apps[mockResult.trackId] = mockResult
searcher.apps[mockResult.trackId] = mockResult
expect {
try captureStream(stdout) {
try Mas.Info.parse([String(mockResult.trackId)]).run(storeSearch: storeSearch)
try MAS.Info.parse([String(mockResult.trackId)]).run(searcher: searcher)
}
}
== """

View file

@ -14,12 +14,12 @@ import Quick
public class InstallSpec: QuickSpec {
override public func spec() {
beforeSuite {
Mas.initialize()
MAS.initialize()
}
xdescribe("install command") {
xit("installs apps") {
expect {
try Mas.Install.parse([]).run(appLibrary: AppLibraryMock())
try MAS.Install.parse([]).run(appLibrary: MockAppLibrary())
}
.toNot(throwError())
}

View file

@ -15,13 +15,13 @@ import Quick
public class ListSpec: QuickSpec {
override public func spec() {
beforeSuite {
Mas.initialize()
MAS.initialize()
}
describe("list command") {
it("lists apps") {
expect {
try captureStream(stderr) {
try Mas.List.parse([]).run(appLibrary: AppLibraryMock())
try MAS.List.parse([]).run(appLibrary: MockAppLibrary())
}
}
== "Error: No installed apps found\n"

View file

@ -13,16 +13,16 @@ import Quick
public class LuckySpec: QuickSpec {
override public func spec() {
let networkSession = NetworkSessionMockFromFile(responseFile: "search/slack.json")
let storeSearch = MasStoreSearch(networkManager: NetworkManager(session: networkSession))
let networkSession = MockFromFileNetworkSession(responseFile: "search/slack.json")
let searcher = ITunesSearchAppStoreSearcher(networkManager: NetworkManager(session: networkSession))
beforeSuite {
Mas.initialize()
MAS.initialize()
}
xdescribe("lucky command") {
xit("installs the first app matching a search") {
expect {
try Mas.Lucky.parse(["Slack"]).run(appLibrary: AppLibraryMock(), storeSearch: storeSearch)
try MAS.Lucky.parse(["Slack"]).run(appLibrary: MockAppLibrary(), searcher: searcher)
}
.toNot(throwError())
}

View file

@ -14,25 +14,25 @@ import Quick
public class OpenSpec: QuickSpec {
override public func spec() {
let storeSearch = StoreSearchMock()
let openCommand = OpenSystemCommandMock()
let searcher = MockAppStoreSearcher()
let openCommand = MockOpenSystemCommand()
beforeSuite {
Mas.initialize()
MAS.initialize()
}
describe("open command") {
beforeEach {
storeSearch.reset()
searcher.reset()
}
it("fails to open app with invalid ID") {
expect {
try Mas.Open.parse(["--", "-999"]).run(storeSearch: storeSearch, openCommand: openCommand)
try MAS.Open.parse(["--", "-999"]).run(searcher: searcher, openCommand: openCommand)
}
.to(throwError())
}
it("can't find app with unknown ID") {
expect {
try Mas.Open.parse(["999"]).run(storeSearch: storeSearch, openCommand: openCommand)
try MAS.Open.parse(["999"]).run(searcher: searcher, openCommand: openCommand)
}
.to(throwError(MASError.noSearchResultsFound))
}
@ -42,17 +42,17 @@ public class OpenSpec: QuickSpec {
trackViewUrl: "fakescheme://some/url",
version: "0.0"
)
storeSearch.apps[mockResult.trackId] = mockResult
searcher.apps[mockResult.trackId] = mockResult
expect {
try Mas.Open.parse([mockResult.trackId.description])
.run(storeSearch: storeSearch, openCommand: openCommand)
try MAS.Open.parse([mockResult.trackId.description])
.run(searcher: searcher, openCommand: openCommand)
return openCommand.arguments
}
== ["macappstore://some/url"]
}
it("just opens MAS if no app specified") {
expect {
try Mas.Open.parse([]).run(storeSearch: storeSearch, openCommand: openCommand)
try MAS.Open.parse([]).run(searcher: searcher, openCommand: openCommand)
return openCommand.arguments
}
== ["macappstore://"]

View file

@ -15,7 +15,7 @@ import Quick
public class OutdatedSpec: QuickSpec {
override public func spec() {
beforeSuite {
Mas.initialize()
MAS.initialize()
}
describe("outdated command") {
it("displays apps with pending updates") {
@ -33,12 +33,12 @@ public class OutdatedSpec: QuickSpec {
trackViewUrl: "https://apps.apple.com/us/app/bandwidth/id490461369?mt=12&uo=4",
version: "1.28"
)
let mockStoreSearch = StoreSearchMock()
mockStoreSearch.apps[mockSearchResult.trackId] = mockSearchResult
let searcher = MockAppStoreSearcher()
searcher.apps[mockSearchResult.trackId] = mockSearchResult
let mockAppLibrary = AppLibraryMock()
let mockAppLibrary = MockAppLibrary()
mockAppLibrary.installedApps.append(
SoftwareProductMock(
MockSoftwareProduct(
appName: mockSearchResult.trackName,
bundleIdentifier: mockSearchResult.bundleId,
bundlePath: "/Applications/Bandwidth+.app",
@ -48,7 +48,7 @@ public class OutdatedSpec: QuickSpec {
)
expect {
try captureStream(stdout) {
try Mas.Outdated.parse([]).run(appLibrary: mockAppLibrary, storeSearch: mockStoreSearch)
try MAS.Outdated.parse([]).run(appLibrary: mockAppLibrary, searcher: searcher)
}
}
== "490461369 Bandwidth+ (1.27 -> 1.28)\n"

View file

@ -14,12 +14,12 @@ import Quick
public class PurchaseSpec: QuickSpec {
override public func spec() {
beforeSuite {
Mas.initialize()
MAS.initialize()
}
xdescribe("purchase command") {
xit("purchases apps") {
expect {
try Mas.Purchase.parse(["999"]).run(appLibrary: AppLibraryMock())
try MAS.Purchase.parse(["999"]).run(appLibrary: MockAppLibrary())
}
.toNot(throwError())
}

View file

@ -14,12 +14,12 @@ import Quick
public class ResetSpec: QuickSpec {
override public func spec() {
beforeSuite {
Mas.initialize()
MAS.initialize()
}
describe("reset command") {
it("resets the App Store state") {
expect {
try Mas.Reset.parse([]).run()
try MAS.Reset.parse([]).run()
}
.toNot(throwError())
}

View file

@ -14,14 +14,14 @@ import Quick
public class SearchSpec: QuickSpec {
override public func spec() {
let storeSearch = StoreSearchMock()
let searcher = MockAppStoreSearcher()
beforeSuite {
Mas.initialize()
MAS.initialize()
}
describe("search command") {
beforeEach {
storeSearch.reset()
searcher.reset()
}
it("can find slack") {
let mockResult = SearchResult(
@ -30,17 +30,17 @@ public class SearchSpec: QuickSpec {
trackViewUrl: "mas preview url",
version: "0.0"
)
storeSearch.apps[mockResult.trackId] = mockResult
searcher.apps[mockResult.trackId] = mockResult
expect {
try captureStream(stdout) {
try Mas.Search.parse(["slack"]).run(storeSearch: storeSearch)
try MAS.Search.parse(["slack"]).run(searcher: searcher)
}
}
== " 1111 slack (0.0)\n"
}
it("fails when searching for nonexistent app") {
expect {
try Mas.Search.parse(["nonexistent"]).run(storeSearch: storeSearch)
try MAS.Search.parse(["nonexistent"]).run(searcher: searcher)
}
.to(throwError(MASError.noSearchResultsFound))
}

View file

@ -15,13 +15,13 @@ import Quick
public class SignInSpec: QuickSpec {
override public func spec() {
beforeSuite {
Mas.initialize()
MAS.initialize()
}
// account command disabled since macOS 10.13 High Sierra https://github.com/mas-cli/mas#known-issues
describe("signin command") {
it("signs in") {
expect {
try Mas.SignIn.parse(["", ""]).run()
try MAS.SignIn.parse(["", ""]).run()
}
.to(throwError(MASError.notSupported))
}

View file

@ -14,12 +14,12 @@ import Quick
public class SignOutSpec: QuickSpec {
override public func spec() {
beforeSuite {
Mas.initialize()
MAS.initialize()
}
describe("signout command") {
it("signs out") {
expect {
try Mas.SignOut.parse([]).run()
try MAS.SignOut.parse([]).run()
}
.toNot(throwError())
}

View file

@ -15,21 +15,21 @@ import Quick
public class UninstallSpec: QuickSpec {
override public func spec() {
beforeSuite {
Mas.initialize()
MAS.initialize()
}
xdescribe("uninstall command") {
let appID: AppID = 12345
let app = SoftwareProductMock(
let app = MockSoftwareProduct(
appName: "Some App",
bundleIdentifier: "com.some.app",
bundlePath: "/tmp/Some.app",
bundleVersion: "1.0",
itemIdentifier: NSNumber(value: appID)
)
let mockLibrary = AppLibraryMock()
let mockLibrary = MockAppLibrary()
context("dry run") {
let uninstall = try! Mas.Uninstall.parse(["--dry-run", String(appID)])
let uninstall = try! MAS.Uninstall.parse(["--dry-run", String(appID)])
beforeEach {
mockLibrary.reset()
@ -51,7 +51,7 @@ public class UninstallSpec: QuickSpec {
}
}
context("wet run") {
let uninstall = try! Mas.Uninstall.parse([String(appID)])
let uninstall = try! MAS.Uninstall.parse([String(appID)])
beforeEach {
mockLibrary.reset()

View file

@ -15,14 +15,14 @@ import Quick
public class UpgradeSpec: QuickSpec {
override public func spec() {
beforeSuite {
Mas.initialize()
MAS.initialize()
}
describe("upgrade command") {
it("finds no upgrades") {
expect {
try captureStream(stderr) {
try Mas.Upgrade.parse([])
.run(appLibrary: AppLibraryMock(), storeSearch: StoreSearchMock())
try MAS.Upgrade.parse([])
.run(appLibrary: MockAppLibrary(), searcher: MockAppStoreSearcher())
}
}
== "Warning: Nothing found to upgrade\n"

View file

@ -13,25 +13,25 @@ import Quick
public class VendorSpec: QuickSpec {
override public func spec() {
let storeSearch = StoreSearchMock()
let openCommand = OpenSystemCommandMock()
let searcher = MockAppStoreSearcher()
let openCommand = MockOpenSystemCommand()
beforeSuite {
Mas.initialize()
MAS.initialize()
}
describe("vendor command") {
beforeEach {
storeSearch.reset()
searcher.reset()
}
it("fails to open app with invalid ID") {
expect {
try Mas.Vendor.parse(["--", "-999"]).run(storeSearch: storeSearch, openCommand: openCommand)
try MAS.Vendor.parse(["--", "-999"]).run(searcher: searcher, openCommand: openCommand)
}
.to(throwError())
}
it("can't find app with unknown ID") {
expect {
try Mas.Vendor.parse(["999"]).run(storeSearch: storeSearch, openCommand: openCommand)
try MAS.Vendor.parse(["999"]).run(searcher: searcher, openCommand: openCommand)
}
.to(throwError(MASError.noSearchResultsFound))
}
@ -42,10 +42,10 @@ public class VendorSpec: QuickSpec {
trackViewUrl: "https://apps.apple.com/us/app/awesome/id1111?mt=12&uo=4",
version: "0.0"
)
storeSearch.apps[mockResult.trackId] = mockResult
searcher.apps[mockResult.trackId] = mockResult
expect {
try Mas.Vendor.parse([String(mockResult.trackId)])
.run(storeSearch: storeSearch, openCommand: openCommand)
try MAS.Vendor.parse([String(mockResult.trackId)])
.run(searcher: searcher, openCommand: openCommand)
return openCommand.arguments
}
== [mockResult.sellerUrl]

View file

@ -15,13 +15,13 @@ import Quick
public class VersionSpec: QuickSpec {
override public func spec() {
beforeSuite {
Mas.initialize()
MAS.initialize()
}
describe("version command") {
it("displays the current version") {
expect {
try captureStream(stdout) {
try Mas.Version.parse([]).run()
try MAS.Version.parse([]).run()
}
}
== "\(Package.version)\n"

View file

@ -1,5 +1,5 @@
//
// MasStoreSearchSpec.swift
// ITunesSearchAppStoreSearcherSpec.swift
// masTests
//
// Created by Ben Chatelain on 1/4/19.
@ -11,21 +11,21 @@ import Quick
@testable import mas
public class MasStoreSearchSpec: QuickSpec {
public class ITunesSearchAppStoreSearcherSpec: QuickSpec {
override public func spec() {
beforeSuite {
Mas.initialize()
MAS.initialize()
}
describe("url string") {
it("contains the app name") {
expect {
MasStoreSearch().searchURL(for: "myapp", inCountry: "US")?.absoluteString
ITunesSearchAppStoreSearcher().searchURL(for: "myapp", inCountry: "US")?.absoluteString
}
== "https://itunes.apple.com/search?media=software&entity=desktopSoftware&country=US&term=myapp"
}
it("contains the encoded app name") {
expect {
MasStoreSearch().searchURL(for: "My App", inCountry: "US")?.absoluteString
ITunesSearchAppStoreSearcher().searchURL(for: "My App", inCountry: "US")?.absoluteString
}
== "https://itunes.apple.com/search?media=software&entity=desktopSoftware&country=US&term=My%20App"
}
@ -33,11 +33,11 @@ public class MasStoreSearchSpec: QuickSpec {
describe("store") {
context("when searched") {
it("can find slack") {
let networkSession = NetworkSessionMockFromFile(responseFile: "search/slack.json")
let storeSearch = MasStoreSearch(networkManager: NetworkManager(session: networkSession))
let networkSession = MockFromFileNetworkSession(responseFile: "search/slack.json")
let searcher = ITunesSearchAppStoreSearcher(networkManager: NetworkManager(session: networkSession))
expect {
try storeSearch.search(for: "slack").wait()
try searcher.search(for: "slack").wait()
}
.to(haveCount(39))
}
@ -46,12 +46,12 @@ public class MasStoreSearchSpec: QuickSpec {
context("when lookup used") {
it("can find slack") {
let appID: AppID = 803_453_959
let networkSession = NetworkSessionMockFromFile(responseFile: "lookup/slack.json")
let storeSearch = MasStoreSearch(networkManager: NetworkManager(session: networkSession))
let networkSession = MockFromFileNetworkSession(responseFile: "lookup/slack.json")
let searcher = ITunesSearchAppStoreSearcher(networkManager: NetworkManager(session: networkSession))
var result: SearchResult?
do {
result = try storeSearch.lookup(appID: appID).wait()
result = try searcher.lookup(appID: appID).wait()
} catch {
let maserror = error as! MASError
if case .jsonParsing(let nserror) = maserror {

View file

@ -1,5 +1,5 @@
//
// AppLibraryMock.swift
// MockAppLibrary.swift
// masTests
//
// Created by Ben Chatelain on 12/27/18.
@ -8,7 +8,7 @@
@testable import mas
class AppLibraryMock: AppLibrary {
class MockAppLibrary: AppLibrary {
var installedApps: [SoftwareProduct] = []
func uninstallApps(atPaths appPaths: [String]) throws {
@ -20,7 +20,7 @@ class AppLibraryMock: AppLibrary {
}
/// Members not part of the AppLibrary protocol that are only for test state management.
extension AppLibraryMock {
extension MockAppLibrary {
/// Clears out the list of installed apps.
func reset() {
installedApps = []

View file

@ -1,5 +1,5 @@
//
// StoreSearchMock.swift
// MockAppStoreSearcher.swift
// masTests
//
// Created by Ben Chatelain on 1/4/19.
@ -10,11 +10,11 @@ import PromiseKit
@testable import mas
class StoreSearchMock: StoreSearch {
class MockAppStoreSearcher: AppStoreSearcher {
var apps: [AppID: SearchResult] = [:]
func search(for appName: String) -> Promise<[SearchResult]> {
.value(apps.filter { $1.trackName.contains(appName) }.map { $1 })
func search(for searchTerm: String) -> Promise<[SearchResult]> {
.value(apps.filter { $1.trackName.contains(searchTerm) }.map { $1 })
}
func lookup(appID: AppID) -> Promise<SearchResult?> {

View file

@ -1,5 +1,5 @@
//
// MasAppLibrarySpec.swift
// SoftwareMapAppLibrarySpec.swift
// masTests
//
// Created by Ben Chatelain on 3/1/20.
@ -11,12 +11,12 @@ import Quick
@testable import mas
public class MasAppLibrarySpec: QuickSpec {
public class SoftwareMapAppLibrarySpec: QuickSpec {
override public func spec() {
let library = MasAppLibrary(softwareMap: SoftwareMapMock(products: apps))
let library = SoftwareMapAppLibrary(softwareMap: MockSoftwareMap(products: apps))
beforeSuite {
Mas.initialize()
MAS.initialize()
}
describe("mas app library") {
it("contains all installed apps") {
@ -24,14 +24,14 @@ public class MasAppLibrarySpec: QuickSpec {
expect(library.installedApps.first!.appName) == myApp.appName
}
it("can locate an app by bundle id") {
expect(library.installedApp(forBundleId: "com.example")!.bundleIdentifier) == myApp.bundleIdentifier
expect(library.installedApp(forBundleID: "com.example")!.bundleIdentifier) == myApp.bundleIdentifier
}
}
}
}
// MARK: - Test Data
let myApp = SoftwareProductMock(
let myApp = MockSoftwareProduct(
appName: "MyApp",
bundleIdentifier: "com.example",
bundlePath: "/Applications/MyApp.app",
@ -41,8 +41,8 @@ let myApp = SoftwareProductMock(
var apps: [SoftwareProduct] = [myApp]
// MARK: - SoftwareMapMock
struct SoftwareMapMock: SoftwareMap {
// MARK: - MockSoftwareMap
struct MockSoftwareMap: SoftwareMap {
var products: [SoftwareProduct] = []
func allSoftwareProducts() -> [SoftwareProduct] {

View file

@ -32,7 +32,7 @@ class MASErrorTestCase: XCTestCase {
override func setUp() {
super.setUp()
Mas.initialize()
MAS.initialize()
nserror = NSError(domain: errorDomain, code: 999)
localizedDescription = "foo"
}
@ -59,7 +59,7 @@ class MASErrorTestCase: XCTestCase {
}
func testAlreadySignedIn() {
error = .alreadySignedIn(asAccountId: "person@example.com")
error = .alreadySignedIn(asAppleID: "person@example.com")
XCTAssertEqual(error.description, "Already signed in as person@example.com")
}

View file

@ -26,11 +26,12 @@ extension Bundle {
static func url(for fileName: String) -> URL? {
// The Swift Package Manager places resources in a separate bundle from the executable.
// https://forums.swift.org/t/swift-5-3-spm-resources-in-tests-uses-wrong-bundle-path/37051
let bundleURL = Bundle(for: NetworkSessionMock.self)
let bundleURL = Bundle(for: MockNetworkSession.self)
.bundleURL
.deletingLastPathComponent()
.appendingPathComponent("mas_masTests.bundle")
guard let bundle = Bundle(url: bundleURL),
guard
let bundle = Bundle(url: bundleURL),
let url = bundle.url(for: fileName)
else {
fatalError("Unable to load file \(fileName)")

View file

@ -1,5 +1,5 @@
//
// OpenSystemCommandMock.swift
// MockOpenSystemCommand.swift
// masTests
//
// Created by Ben Chatelain on 1/4/19.
@ -10,7 +10,7 @@ import Foundation
@testable import mas
class OpenSystemCommandMock: ExternalCommand {
class MockOpenSystemCommand: ExternalCommand {
// Stub out protocol logic
var succeeded = true
var arguments: [String] = []

View file

@ -14,7 +14,7 @@ import Quick
public class OpenSystemCommandSpec: QuickSpec {
override public func spec() {
beforeSuite {
Mas.initialize()
MAS.initialize()
}
describe("open system command") {
context("binary path") {

View file

@ -18,7 +18,7 @@ public class AppListFormatterSpec: QuickSpec {
var products: [SoftwareProduct] = []
beforeSuite {
Mas.initialize()
MAS.initialize()
}
describe("app list formatter") {
beforeEach {
@ -28,7 +28,7 @@ public class AppListFormatterSpec: QuickSpec {
expect(format(products)).to(beEmpty())
}
it("can format a single product") {
let product = SoftwareProductMock(
let product = MockSoftwareProduct(
appName: "Awesome App",
bundleIdentifier: "",
bundlePath: "",
@ -39,14 +39,14 @@ public class AppListFormatterSpec: QuickSpec {
}
it("can format two products") {
products = [
SoftwareProductMock(
MockSoftwareProduct(
appName: "Awesome App",
bundleIdentifier: "",
bundlePath: "",
bundleVersion: "19.2.1",
itemIdentifier: 12345
),
SoftwareProductMock(
MockSoftwareProduct(
appName: "Even Better App",
bundleIdentifier: "",
bundlePath: "",

View file

@ -18,7 +18,7 @@ public class SearchResultFormatterSpec: QuickSpec {
var results: [SearchResult] = []
beforeSuite {
Mas.initialize()
MAS.initialize()
}
describe("search results formatter") {
beforeEach {

View file

@ -1,5 +1,5 @@
//
// SoftwareProductMock.swift
// MockSoftwareProduct.swift
// masTests
//
// Created by Ben Chatelain on 12/27/18.
@ -10,7 +10,7 @@ import Foundation
@testable import mas
struct SoftwareProductMock: SoftwareProduct {
struct MockSoftwareProduct: SoftwareProduct {
var appName: String
var bundleIdentifier: String
var bundlePath: String

View file

@ -15,7 +15,7 @@ import Quick
public class SearchResultListSpec: QuickSpec {
override public func spec() {
beforeSuite {
Mas.initialize()
MAS.initialize()
}
describe("search result list") {
it("can parse bbedit") {

View file

@ -15,7 +15,7 @@ import Quick
public class SearchResultSpec: QuickSpec {
override public func spec() {
beforeSuite {
Mas.initialize()
MAS.initialize()
}
describe("search result") {
it("can parse things") {

View file

@ -15,10 +15,10 @@ import Quick
public class SoftwareProductSpec: QuickSpec {
override public func spec() {
beforeSuite {
Mas.initialize()
MAS.initialize()
}
describe("software product") {
let app = SoftwareProductMock(
let app = MockSoftwareProduct(
appName: "App",
bundleIdentifier: "",
bundlePath: "",

View file

@ -1,5 +1,5 @@
//
// NetworkSessionMockFromFile.swift
// MockFromFileNetworkSession.swift
// masTests
//
// Created by Ben Chatelain on 2019-01-05.
@ -10,7 +10,7 @@ import Foundation
import PromiseKit
/// Mock NetworkSession for testing with saved JSON response payload files.
class NetworkSessionMockFromFile: NetworkSessionMock {
class MockFromFileNetworkSession: MockNetworkSession {
/// Path to response payload file relative to test bundle.
private let responseFile: String

View file

@ -1,5 +1,5 @@
//
// NetworkSessionMock
// MockNetworkSession
// masTests
//
// Created by Ben Chatelain on 11/13/18.
@ -12,7 +12,7 @@ import PromiseKit
@testable import mas
/// Mock NetworkSession for testing.
class NetworkSessionMock: NetworkSession {
class MockNetworkSession: NetworkSession {
// Properties that enable us to set exactly what data or error
// we want our mocked URLSession to return for any request.
var data: Data?

View file

@ -13,12 +13,12 @@ import XCTest
class NetworkManagerTests: XCTestCase {
override func setUp() {
super.setUp()
Mas.initialize()
MAS.initialize()
}
func testSuccessfulAsyncResponse() throws {
// Setup our objects
let session = NetworkSessionMock()
let session = MockNetworkSession()
let manager = NetworkManager(session: session)
// Create data and tell the session to always return it
@ -35,7 +35,7 @@ class NetworkManagerTests: XCTestCase {
func testSuccessfulSyncResponse() throws {
// Setup our objects
let session = NetworkSessionMock()
let session = MockNetworkSession()
let manager = NetworkManager(session: session)
// Create data and tell the session to always return it
@ -52,7 +52,7 @@ class NetworkManagerTests: XCTestCase {
func testFailureAsyncResponse() {
// Setup our objects
let session = NetworkSessionMock()
let session = MockNetworkSession()
let manager = NetworkManager(session: session)
session.error = MASError.noData
@ -73,7 +73,7 @@ class NetworkManagerTests: XCTestCase {
func testFailureSyncResponse() {
// Setup our objects
let session = NetworkSessionMock()
let session = MockNetworkSession()
let manager = NetworkManager(session: session)
session.error = MASError.noData

View file

@ -46,7 +46,7 @@ complete -c mas -n "__fish_seen_subcommand_from help" -xa "list"
complete -c mas -n "__fish_use_subcommand" -f -a lucky -d "Install the first result from the Mac App Store"
complete -c mas -n "__fish_seen_subcommand_from help" -xa "lucky"
### open
complete -c mas -n "__fish_use_subcommand" -f -a open -d "Opens app page in AppStore.app"
complete -c mas -n "__fish_use_subcommand" -f -a open -d "Opens app page in 'App Store.app'"
complete -c mas -n "__fish_seen_subcommand_from help" -xa "open"
### outdated
complete -c mas -n "__fish_use_subcommand" -f -a outdated -d "Lists pending updates from the Mac App Store"

View file

@ -90,11 +90,14 @@ private extension MyClass {
guard let singleTest = somethingFailable() else { return }
guard statementThatShouldBeTrue else { return }
// If there is one long expression to guard or multiple expressions
// move else to next line
guard let oneItem = somethingFailable(),
// If a guard clause requires multiple lines, chop down, then start `else` new line
// In this case, always chop down else clause.
guard
let oneItem = somethingFailable(),
let secondItem = somethingFailable2()
else { return }
else {
return
}
// If the return in else is long, move to next line
guard let something = somethingFailable() else {