mirror of
https://github.com/mas-cli/mas
synced 2025-02-16 12:38:30 +00:00
commit
ac3599d050
35 changed files with 247 additions and 323 deletions
|
@ -10,6 +10,7 @@
|
|||
# Disabled rules
|
||||
--disable blankLinesAroundMark
|
||||
--disable consecutiveSpaces
|
||||
--disable hoistAwait
|
||||
--disable hoistPatternLet
|
||||
--disable hoistTry
|
||||
--disable indent
|
||||
|
|
|
@ -23,8 +23,8 @@
|
|||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/Quick/Nimble.git",
|
||||
"state" : {
|
||||
"revision" : "1f3bde57bde12f5e7b07909848c071e9b73d6edc",
|
||||
"version" : "10.0.0"
|
||||
"revision" : "6416749c3c0488664fff6b42f8bf3ea8dc282ca1",
|
||||
"version" : "13.6.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -41,8 +41,8 @@
|
|||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/Quick/Quick.git",
|
||||
"state" : {
|
||||
"revision" : "f9d519828bb03dfc8125467d8f7b93131951124c",
|
||||
"version" : "5.0.1"
|
||||
"revision" : "1163a1b1b114a657c7432b63dd1f92ce99fe11a6",
|
||||
"version" : "7.6.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -54,6 +54,15 @@
|
|||
"version" : "2.1.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-algorithms",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-algorithms.git",
|
||||
"state" : {
|
||||
"revision" : "f6919dfc309e7f1b56224378b11e28bab5bccc42",
|
||||
"version" : "1.2.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-argument-parser",
|
||||
"kind" : "remoteSourceControl",
|
||||
|
@ -63,6 +72,15 @@
|
|||
"version" : "1.5.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-numerics",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-numerics.git",
|
||||
"state" : {
|
||||
"revision" : "0a5bc04095a675662cf24757cc0640aa2204253b",
|
||||
"version" : "1.0.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "version",
|
||||
"kind" : "remoteSourceControl",
|
||||
|
|
|
@ -17,8 +17,8 @@ let package = Package(
|
|||
],
|
||||
dependencies: [
|
||||
// Dependencies declare other packages that this package depends on.
|
||||
.package(url: "https://github.com/Quick/Nimble.git", from: "10.0.0"),
|
||||
.package(url: "https://github.com/Quick/Quick.git", from: "5.0.0"),
|
||||
.package(url: "https://github.com/Quick/Nimble.git", from: "13.6.0"),
|
||||
.package(url: "https://github.com/Quick/Quick.git", from: "7.6.2"),
|
||||
.package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.5.0"),
|
||||
.package(url: "https://github.com/mxcl/PromiseKit.git", from: "6.22.1"),
|
||||
.package(url: "https://github.com/mxcl/Version.git", from: "2.1.0"),
|
||||
|
|
|
@ -13,63 +13,6 @@ import Foundation
|
|||
/// Terminal Control Sequence Indicator
|
||||
let csi = "\u{001B}["
|
||||
|
||||
#if DEBUG
|
||||
|
||||
var printObserver: ((String) -> Void)?
|
||||
|
||||
// Override global print for testability.
|
||||
// See masTests/OutputListener.swift.
|
||||
func print(
|
||||
_ items: Any...,
|
||||
separator: String = " ",
|
||||
terminator: String = "\n"
|
||||
) {
|
||||
if let observer = printObserver {
|
||||
let output =
|
||||
items
|
||||
.map { "\($0)" }
|
||||
.joined(separator: separator)
|
||||
.appending(terminator)
|
||||
observer(output)
|
||||
}
|
||||
|
||||
var prefix = ""
|
||||
for item in items {
|
||||
Swift.print(prefix, terminator: "")
|
||||
Swift.print(item, terminator: "")
|
||||
prefix = separator
|
||||
}
|
||||
|
||||
Swift.print(terminator, terminator: "")
|
||||
}
|
||||
|
||||
func print(
|
||||
_ items: Any...,
|
||||
separator: String = " ",
|
||||
terminator: String = "\n",
|
||||
to output: inout some TextOutputStream
|
||||
) {
|
||||
if let observer = printObserver {
|
||||
let output =
|
||||
items
|
||||
.map { "\($0)" }
|
||||
.joined(separator: separator)
|
||||
.appending(terminator)
|
||||
observer(output)
|
||||
}
|
||||
|
||||
var prefix = ""
|
||||
for item in items {
|
||||
Swift.print(prefix, terminator: "", to: &output)
|
||||
Swift.print(item, terminator: "", to: &output)
|
||||
prefix = separator
|
||||
}
|
||||
|
||||
Swift.print(terminator, terminator: "", to: &output)
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
private var standardError = FileHandle.standardError
|
||||
|
||||
extension FileHandle: TextOutputStream {
|
||||
|
@ -121,3 +64,30 @@ func clearLine() {
|
|||
print("\(csi)2K\(csi)0G", terminator: "")
|
||||
fflush(stdout)
|
||||
}
|
||||
|
||||
func captureStream(
|
||||
_ stream: UnsafeMutablePointer<FILE>,
|
||||
encoding: String.Encoding = .utf8,
|
||||
_ block: @escaping () throws -> Void
|
||||
) throws -> String {
|
||||
let originalFd = fileno(stream)
|
||||
let duplicateFd = dup(originalFd)
|
||||
defer {
|
||||
close(duplicateFd)
|
||||
}
|
||||
|
||||
let pipe = Pipe()
|
||||
dup2(pipe.fileHandleForWriting.fileDescriptor, originalFd)
|
||||
|
||||
do {
|
||||
defer {
|
||||
fflush(stream)
|
||||
dup2(duplicateFd, originalFd)
|
||||
pipe.fileHandleForWriting.closeFile()
|
||||
}
|
||||
|
||||
try block()
|
||||
}
|
||||
|
||||
return String(data: pipe.fileHandleForReading.readDataToEndOfFile(), encoding: encoding) ?? ""
|
||||
}
|
||||
|
|
|
@ -13,17 +13,17 @@ import Quick
|
|||
|
||||
// Deprecated test
|
||||
public class AccountSpec: QuickSpec {
|
||||
override public func spec() {
|
||||
override public static func spec() {
|
||||
beforeSuite {
|
||||
Mas.initialize()
|
||||
}
|
||||
// account command disabled since macOS 12 Monterey https://github.com/mas-cli/mas#%EF%B8%8F-known-issues
|
||||
xdescribe("Account command") {
|
||||
xit("displays active account") {
|
||||
describe("Account command") {
|
||||
it("displays active account") {
|
||||
expect {
|
||||
try Mas.Account.parse([]).run()
|
||||
}
|
||||
.toNot(throwError())
|
||||
.to(throwError(MASError.notSupported))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,12 +12,7 @@ import Quick
|
|||
@testable import mas
|
||||
|
||||
public class HomeSpec: QuickSpec {
|
||||
override public func spec() {
|
||||
let result = SearchResult(
|
||||
trackId: 1111,
|
||||
trackViewUrl: "mas preview url",
|
||||
version: "0.0"
|
||||
)
|
||||
override public static func spec() {
|
||||
let storeSearch = StoreSearchMock()
|
||||
let openCommand = OpenSystemCommandMock()
|
||||
|
||||
|
@ -41,13 +36,18 @@ public class HomeSpec: QuickSpec {
|
|||
.to(throwError(MASError.noSearchResultsFound))
|
||||
}
|
||||
it("opens app on MAS Preview") {
|
||||
storeSearch.apps[result.trackId] = result
|
||||
let mockResult = SearchResult(
|
||||
trackId: 1111,
|
||||
trackViewUrl: "mas preview url",
|
||||
version: "0.0"
|
||||
)
|
||||
storeSearch.apps[mockResult.trackId] = mockResult
|
||||
expect {
|
||||
try Mas.Home.parse([String(result.trackId)]).run(storeSearch: storeSearch, openCommand: openCommand)
|
||||
try Mas.Home.parse([String(mockResult.trackId)])
|
||||
.run(storeSearch: storeSearch, openCommand: openCommand)
|
||||
return openCommand.arguments
|
||||
}
|
||||
.toNot(throwError())
|
||||
expect(openCommand.arguments).toNot(beNil())
|
||||
expect(openCommand.arguments!.first!) == result.trackViewUrl
|
||||
== [mockResult.trackViewUrl]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,34 +6,15 @@
|
|||
// Copyright © 2018 mas-cli. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Nimble
|
||||
import Quick
|
||||
|
||||
@testable import mas
|
||||
|
||||
public class InfoSpec: QuickSpec {
|
||||
override public func spec() {
|
||||
let result = SearchResult(
|
||||
currentVersionReleaseDate: "2019-01-07T18:53:13Z",
|
||||
fileSizeBytes: "1024",
|
||||
minimumOsVersion: "10.14",
|
||||
price: 2.0,
|
||||
sellerName: "Awesome Dev",
|
||||
trackId: 1111,
|
||||
trackName: "Awesome App",
|
||||
trackViewUrl: "https://awesome.app",
|
||||
version: "1.0"
|
||||
)
|
||||
override public static func spec() {
|
||||
let storeSearch = StoreSearchMock()
|
||||
let expectedOutput = """
|
||||
Awesome App 1.0 [2.0]
|
||||
By: Awesome Dev
|
||||
Released: 2019-01-07
|
||||
Minimum OS: 10.14
|
||||
Size: 1 KB
|
||||
From: https://awesome.app
|
||||
|
||||
"""
|
||||
|
||||
beforeSuite {
|
||||
Mas.initialize()
|
||||
|
@ -55,13 +36,32 @@ public class InfoSpec: QuickSpec {
|
|||
.to(throwError(MASError.noSearchResultsFound))
|
||||
}
|
||||
it("displays app details") {
|
||||
storeSearch.apps[result.trackId] = result
|
||||
let output = OutputListener()
|
||||
let mockResult = SearchResult(
|
||||
currentVersionReleaseDate: "2019-01-07T18:53:13Z",
|
||||
fileSizeBytes: "1024",
|
||||
minimumOsVersion: "10.14",
|
||||
price: 2.0,
|
||||
sellerName: "Awesome Dev",
|
||||
trackId: 1111,
|
||||
trackName: "Awesome App",
|
||||
trackViewUrl: "https://awesome.app",
|
||||
version: "1.0"
|
||||
)
|
||||
storeSearch.apps[mockResult.trackId] = mockResult
|
||||
expect {
|
||||
try Mas.Info.parse([String(result.trackId)]).run(storeSearch: storeSearch)
|
||||
try captureStream(stdout) {
|
||||
try Mas.Info.parse([String(mockResult.trackId)]).run(storeSearch: storeSearch)
|
||||
}
|
||||
}
|
||||
.toNot(throwError())
|
||||
expect(output.contents) == expectedOutput
|
||||
== """
|
||||
Awesome App 1.0 [2.0]
|
||||
By: Awesome Dev
|
||||
Released: 2019-01-07
|
||||
Minimum OS: 10.14
|
||||
Size: 1 KB
|
||||
From: https://awesome.app
|
||||
|
||||
"""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ import Quick
|
|||
@testable import mas
|
||||
|
||||
public class InstallSpec: QuickSpec {
|
||||
override public func spec() {
|
||||
override public static func spec() {
|
||||
beforeSuite {
|
||||
Mas.initialize()
|
||||
}
|
||||
|
|
|
@ -6,22 +6,25 @@
|
|||
// Copyright © 2018 mas-cli. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Nimble
|
||||
import Quick
|
||||
|
||||
@testable import mas
|
||||
|
||||
public class ListSpec: QuickSpec {
|
||||
override public func spec() {
|
||||
override public static func spec() {
|
||||
beforeSuite {
|
||||
Mas.initialize()
|
||||
}
|
||||
describe("list command") {
|
||||
it("lists apps") {
|
||||
expect {
|
||||
try Mas.List.parse([]).run(appLibrary: AppLibraryMock())
|
||||
try captureStream(stderr) {
|
||||
try Mas.List.parse([]).run(appLibrary: AppLibraryMock())
|
||||
}
|
||||
}
|
||||
.toNot(throwError())
|
||||
== "Error: No installed apps found\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,14 +12,14 @@ import Quick
|
|||
@testable import mas
|
||||
|
||||
public class LuckySpec: QuickSpec {
|
||||
override public func spec() {
|
||||
override public static func spec() {
|
||||
let networkSession = NetworkSessionMockFromFile(responseFile: "search/slack.json")
|
||||
let storeSearch = MasStoreSearch(networkManager: NetworkManager(session: networkSession))
|
||||
|
||||
beforeSuite {
|
||||
Mas.initialize()
|
||||
}
|
||||
describe("lucky command") {
|
||||
xdescribe("lucky command") {
|
||||
xit("installs the first app matching a search") {
|
||||
expect {
|
||||
try Mas.Lucky.parse(["Slack"]).run(appLibrary: AppLibraryMock(), storeSearch: storeSearch)
|
||||
|
|
|
@ -13,12 +13,7 @@ import Quick
|
|||
@testable import mas
|
||||
|
||||
public class OpenSpec: QuickSpec {
|
||||
override public func spec() {
|
||||
let result = SearchResult(
|
||||
trackId: 1111,
|
||||
trackViewUrl: "fakescheme://some/url",
|
||||
version: "0.0"
|
||||
)
|
||||
override public static func spec() {
|
||||
let storeSearch = StoreSearchMock()
|
||||
let openCommand = OpenSystemCommandMock()
|
||||
|
||||
|
@ -42,26 +37,25 @@ public class OpenSpec: QuickSpec {
|
|||
.to(throwError(MASError.noSearchResultsFound))
|
||||
}
|
||||
it("opens app in MAS") {
|
||||
storeSearch.apps[result.trackId] = result
|
||||
let mockResult = SearchResult(
|
||||
trackId: 1111,
|
||||
trackViewUrl: "fakescheme://some/url",
|
||||
version: "0.0"
|
||||
)
|
||||
storeSearch.apps[mockResult.trackId] = mockResult
|
||||
expect {
|
||||
try Mas.Open.parse([result.trackId.description])
|
||||
try Mas.Open.parse([mockResult.trackId.description])
|
||||
.run(storeSearch: storeSearch, openCommand: openCommand)
|
||||
return openCommand.arguments
|
||||
}
|
||||
.toNot(throwError())
|
||||
expect(openCommand.arguments).toNot(beNil())
|
||||
let url = URL(string: openCommand.arguments!.first!)
|
||||
expect(url).toNot(beNil())
|
||||
expect(url?.scheme) == "macappstore"
|
||||
== ["macappstore://some/url"]
|
||||
}
|
||||
it("just opens MAS if no app specified") {
|
||||
expect {
|
||||
try Mas.Open.parse([]).run(storeSearch: storeSearch, openCommand: openCommand)
|
||||
return openCommand.arguments
|
||||
}
|
||||
.toNot(throwError())
|
||||
expect(openCommand.arguments).toNot(beNil())
|
||||
let url = URL(string: openCommand.arguments!.first!)
|
||||
expect(url).toNot(beNil())
|
||||
expect(url) == URL(string: "macappstore://")
|
||||
== ["macappstore://"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,23 +6,52 @@
|
|||
// Copyright © 2018 mas-cli. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Nimble
|
||||
import Quick
|
||||
|
||||
@testable import mas
|
||||
|
||||
public class OutdatedSpec: QuickSpec {
|
||||
override public func spec() {
|
||||
override public static func spec() {
|
||||
beforeSuite {
|
||||
Mas.initialize()
|
||||
}
|
||||
describe("outdated command") {
|
||||
it("displays apps with pending updates") {
|
||||
let mockSearchResult =
|
||||
SearchResult(
|
||||
bundleId: "au.id.haroldchu.mac.Bandwidth",
|
||||
currentVersionReleaseDate: "2024-09-02T00:27:00Z",
|
||||
fileSizeBytes: "998130",
|
||||
minimumOsVersion: "10.13",
|
||||
price: 0,
|
||||
sellerName: "Harold Chu",
|
||||
sellerUrl: "https://example.com",
|
||||
trackId: 490_461_369,
|
||||
trackName: "Bandwidth+",
|
||||
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 mockAppLibrary = AppLibraryMock()
|
||||
mockAppLibrary.installedApps.append(
|
||||
SoftwareProductMock(
|
||||
appName: mockSearchResult.trackName,
|
||||
bundleIdentifier: mockSearchResult.bundleId,
|
||||
bundlePath: "/Applications/Bandwidth+.app",
|
||||
bundleVersion: "1.27",
|
||||
itemIdentifier: NSNumber(value: mockSearchResult.trackId)
|
||||
)
|
||||
)
|
||||
expect {
|
||||
try Mas.Outdated.parse(["--verbose"])
|
||||
.run(appLibrary: AppLibraryMock(), storeSearch: StoreSearchMock())
|
||||
try captureStream(stdout) {
|
||||
try Mas.Outdated.parse([]).run(appLibrary: mockAppLibrary, storeSearch: mockStoreSearch)
|
||||
}
|
||||
}
|
||||
.toNot(throwError())
|
||||
== "490461369 Bandwidth+ (1.27 -> 1.28)\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ import Quick
|
|||
@testable import mas
|
||||
|
||||
public class PurchaseSpec: QuickSpec {
|
||||
override public func spec() {
|
||||
override public static func spec() {
|
||||
beforeSuite {
|
||||
Mas.initialize()
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ import Quick
|
|||
@testable import mas
|
||||
|
||||
public class ResetSpec: QuickSpec {
|
||||
override public func spec() {
|
||||
override public static func spec() {
|
||||
beforeSuite {
|
||||
Mas.initialize()
|
||||
}
|
||||
|
|
|
@ -12,13 +12,7 @@ import Quick
|
|||
@testable import mas
|
||||
|
||||
public class SearchSpec: QuickSpec {
|
||||
override public func spec() {
|
||||
let result = SearchResult(
|
||||
trackId: 1111,
|
||||
trackName: "slack",
|
||||
trackViewUrl: "mas preview url",
|
||||
version: "0.0"
|
||||
)
|
||||
override public static func spec() {
|
||||
let storeSearch = StoreSearchMock()
|
||||
|
||||
beforeSuite {
|
||||
|
@ -29,7 +23,13 @@ public class SearchSpec: QuickSpec {
|
|||
storeSearch.reset()
|
||||
}
|
||||
it("can find slack") {
|
||||
storeSearch.apps[result.trackId] = result
|
||||
let mockResult = SearchResult(
|
||||
trackId: 1111,
|
||||
trackName: "slack",
|
||||
trackViewUrl: "mas preview url",
|
||||
version: "0.0"
|
||||
)
|
||||
storeSearch.apps[mockResult.trackId] = mockResult
|
||||
expect {
|
||||
try Mas.Search.parse(["slack"]).run(storeSearch: storeSearch)
|
||||
}
|
||||
|
|
|
@ -13,17 +13,17 @@ import Quick
|
|||
|
||||
// Deprecated test
|
||||
public class SignInSpec: QuickSpec {
|
||||
override public func spec() {
|
||||
override public static func spec() {
|
||||
beforeSuite {
|
||||
Mas.initialize()
|
||||
}
|
||||
// account command disabled since macOS 10.13 High Sierra https://github.com/mas-cli/mas#%EF%B8%8F-known-issues
|
||||
xdescribe("signin command") {
|
||||
xit("signs in") {
|
||||
describe("signin command") {
|
||||
it("signs in") {
|
||||
expect {
|
||||
try Mas.SignIn.parse(["", ""]).run()
|
||||
}
|
||||
.toNot(throwError())
|
||||
.to(throwError(MASError.notSupported))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ import Quick
|
|||
@testable import mas
|
||||
|
||||
public class SignOutSpec: QuickSpec {
|
||||
override public func spec() {
|
||||
override public static func spec() {
|
||||
beforeSuite {
|
||||
Mas.initialize()
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ import Quick
|
|||
@testable import mas
|
||||
|
||||
public class UninstallSpec: QuickSpec {
|
||||
override public func spec() {
|
||||
override public static func spec() {
|
||||
beforeSuite {
|
||||
Mas.initialize()
|
||||
}
|
||||
|
@ -63,9 +63,11 @@ public class UninstallSpec: QuickSpec {
|
|||
it("removes an app") {
|
||||
mockLibrary.installedApps.append(app)
|
||||
expect {
|
||||
try uninstall.run(appLibrary: mockLibrary)
|
||||
try captureStream(stdout) {
|
||||
try uninstall.run(appLibrary: mockLibrary)
|
||||
}
|
||||
}
|
||||
.toNot(throwError())
|
||||
== " 1111 slack (0.0)\n==> Some App /tmp/Some.app\n==> (not removed, dry run)\n"
|
||||
}
|
||||
it("fails if there is a problem with the trash command") {
|
||||
var brokenUninstall = app // make mutable copy
|
||||
|
|
|
@ -6,22 +6,26 @@
|
|||
// Copyright © 2018 mas-cli. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Nimble
|
||||
import Quick
|
||||
|
||||
@testable import mas
|
||||
|
||||
public class UpgradeSpec: QuickSpec {
|
||||
override public func spec() {
|
||||
override public static func spec() {
|
||||
beforeSuite {
|
||||
Mas.initialize()
|
||||
}
|
||||
describe("upgrade command") {
|
||||
it("upgrades stuff") {
|
||||
it("finds no upgrades") {
|
||||
expect {
|
||||
try Mas.Upgrade.parse([]).run(appLibrary: AppLibraryMock(), storeSearch: StoreSearchMock())
|
||||
try captureStream(stderr) {
|
||||
try Mas.Upgrade.parse([])
|
||||
.run(appLibrary: AppLibraryMock(), storeSearch: StoreSearchMock())
|
||||
}
|
||||
}
|
||||
.toNot(throwError())
|
||||
== "Warning: Nothing found to upgrade\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,12 +12,7 @@ import Quick
|
|||
@testable import mas
|
||||
|
||||
public class VendorSpec: QuickSpec {
|
||||
override public func spec() {
|
||||
let result = SearchResult(
|
||||
trackId: 1111,
|
||||
trackViewUrl: "https://awesome.app",
|
||||
version: "0.0"
|
||||
)
|
||||
override public static func spec() {
|
||||
let storeSearch = StoreSearchMock()
|
||||
let openCommand = OpenSystemCommandMock()
|
||||
|
||||
|
@ -41,14 +36,19 @@ public class VendorSpec: QuickSpec {
|
|||
.to(throwError(MASError.noSearchResultsFound))
|
||||
}
|
||||
it("opens vendor app page in browser") {
|
||||
storeSearch.apps[result.trackId] = result
|
||||
let mockResult = SearchResult(
|
||||
sellerUrl: "https://awesome.app",
|
||||
trackId: 1111,
|
||||
trackViewUrl: "https://apps.apple.com/us/app/awesome/id1111?mt=12&uo=4",
|
||||
version: "0.0"
|
||||
)
|
||||
storeSearch.apps[mockResult.trackId] = mockResult
|
||||
expect {
|
||||
try Mas.Vendor.parse([String(result.trackId)])
|
||||
try Mas.Vendor.parse([String(mockResult.trackId)])
|
||||
.run(storeSearch: storeSearch, openCommand: openCommand)
|
||||
return openCommand.arguments
|
||||
}
|
||||
.toNot(throwError())
|
||||
expect(openCommand.arguments).toNot(beNil())
|
||||
expect(openCommand.arguments!.first!) == result.sellerUrl
|
||||
== [mockResult.sellerUrl]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,22 +6,25 @@
|
|||
// Copyright © 2018 mas-cli. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Nimble
|
||||
import Quick
|
||||
|
||||
@testable import mas
|
||||
|
||||
public class VersionSpec: QuickSpec {
|
||||
override public func spec() {
|
||||
override public static func spec() {
|
||||
beforeSuite {
|
||||
Mas.initialize()
|
||||
}
|
||||
describe("version command") {
|
||||
it("displays the current version") {
|
||||
expect {
|
||||
try Mas.Version.parse([]).run()
|
||||
try captureStream(stdout) {
|
||||
try Mas.Version.parse([]).run()
|
||||
}
|
||||
}
|
||||
.toNot(throwError())
|
||||
== Package.version + "\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ import Quick
|
|||
@testable import mas
|
||||
|
||||
public class MasAppLibrarySpec: QuickSpec {
|
||||
override public func spec() {
|
||||
override public static func spec() {
|
||||
let library = MasAppLibrary(softwareMap: SoftwareMapMock(products: apps))
|
||||
|
||||
beforeSuite {
|
||||
|
@ -20,12 +20,11 @@ public class MasAppLibrarySpec: QuickSpec {
|
|||
}
|
||||
describe("mas app library") {
|
||||
it("contains all installed apps") {
|
||||
expect(library.installedApps.count) == apps.count
|
||||
expect(library.installedApps).to(haveCount(apps.count))
|
||||
expect(library.installedApps.first!.appName) == myApp.appName
|
||||
}
|
||||
it("can locate an app by bundle id") {
|
||||
let app = library.installedApp(forBundleId: "com.example")!
|
||||
expect(app.bundleIdentifier) == myApp.bundleIdentifier
|
||||
expect(library.installedApp(forBundleId: "com.example")!.bundleIdentifier) == myApp.bundleIdentifier
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,25 +12,23 @@ import Quick
|
|||
@testable import mas
|
||||
|
||||
public class MasStoreSearchSpec: QuickSpec {
|
||||
override public func spec() {
|
||||
override public static func spec() {
|
||||
beforeSuite {
|
||||
Mas.initialize()
|
||||
}
|
||||
describe("url string") {
|
||||
it("contains the app name") {
|
||||
let appName = "myapp"
|
||||
let urlString = MasStoreSearch().searchURL(for: appName, inCountry: "US")?.absoluteString
|
||||
expect(urlString) == """
|
||||
https://itunes.apple.com/search?media=software&entity=macSoftware&term=\(appName)&country=US
|
||||
"""
|
||||
expect {
|
||||
MasStoreSearch().searchURL(for: appName, inCountry: "US")?.absoluteString
|
||||
}
|
||||
== "https://itunes.apple.com/search?media=software&entity=macSoftware&term=\(appName)&country=US"
|
||||
}
|
||||
it("contains the encoded app name") {
|
||||
let appName = "My App"
|
||||
let appNameEncoded = "My%20App"
|
||||
let urlString = MasStoreSearch().searchURL(for: appName, inCountry: "US")?.absoluteString
|
||||
expect(urlString) == """
|
||||
https://itunes.apple.com/search?media=software&entity=macSoftware&term=\(appNameEncoded)&country=US
|
||||
"""
|
||||
expect {
|
||||
MasStoreSearch().searchURL(for: "My App", inCountry: "US")?.absoluteString
|
||||
}
|
||||
== "https://itunes.apple.com/search?media=software&entity=macSoftware&term=My%20App&country=US"
|
||||
}
|
||||
}
|
||||
describe("store") {
|
||||
|
@ -39,16 +37,10 @@ public class MasStoreSearchSpec: QuickSpec {
|
|||
let networkSession = NetworkSessionMockFromFile(responseFile: "search/slack.json")
|
||||
let storeSearch = MasStoreSearch(networkManager: NetworkManager(session: networkSession))
|
||||
|
||||
var results: [SearchResult]
|
||||
do {
|
||||
results = try storeSearch.search(for: "slack").wait()
|
||||
expect(results.count) == 39
|
||||
} catch {
|
||||
let maserror = error as! MASError
|
||||
if case .jsonParsing(let nserror) = maserror {
|
||||
fail("\(maserror) \(nserror!)")
|
||||
}
|
||||
expect {
|
||||
try storeSearch.search(for: "slack").wait()
|
||||
}
|
||||
.to(haveCount(39))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -58,9 +50,9 @@ public class MasStoreSearchSpec: QuickSpec {
|
|||
let networkSession = NetworkSessionMockFromFile(responseFile: "lookup/slack.json")
|
||||
let storeSearch = MasStoreSearch(networkManager: NetworkManager(session: networkSession))
|
||||
|
||||
var lookup: SearchResult?
|
||||
var result: SearchResult?
|
||||
do {
|
||||
lookup = try storeSearch.lookup(appID: appID).wait()
|
||||
result = try storeSearch.lookup(appID: appID).wait()
|
||||
} catch {
|
||||
let maserror = error as! MASError
|
||||
if case .jsonParsing(let nserror) = maserror {
|
||||
|
@ -68,7 +60,9 @@ public class MasStoreSearchSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
guard let result = lookup else { fatalError("lookup result was nil") }
|
||||
guard let result else {
|
||||
fatalError("lookup result was nil")
|
||||
}
|
||||
|
||||
expect(result.trackId) == appID
|
||||
expect(result.bundleId) == "com.tinyspeck.slackmacgap"
|
||||
|
|
|
@ -14,9 +14,7 @@ class StoreSearchMock: StoreSearch {
|
|||
var apps: [AppID: SearchResult] = [:]
|
||||
|
||||
func search(for appName: String) -> Promise<[SearchResult]> {
|
||||
let filtered = apps.filter { $1.trackName.contains(appName) }
|
||||
let results = filtered.map { $1 }
|
||||
return .value(results)
|
||||
.value(apps.filter { $1.trackName.contains(appName) }.map { $1 })
|
||||
}
|
||||
|
||||
func lookup(appID: AppID) -> Promise<SearchResult?> {
|
||||
|
|
|
@ -13,7 +13,7 @@ import Foundation
|
|||
class OpenSystemCommandMock: ExternalCommand {
|
||||
// Stub out protocol logic
|
||||
var succeeded = true
|
||||
var arguments: [String]?
|
||||
var arguments: [String] = []
|
||||
|
||||
// unused
|
||||
var binaryPath = "/dev/null"
|
||||
|
|
|
@ -12,19 +12,17 @@ import Quick
|
|||
@testable import mas
|
||||
|
||||
public class OpenSystemCommandSpec: QuickSpec {
|
||||
override public func spec() {
|
||||
override public static func spec() {
|
||||
beforeSuite {
|
||||
Mas.initialize()
|
||||
}
|
||||
describe("open system command") {
|
||||
context("binary path") {
|
||||
it("defaults to the macOS open command") {
|
||||
let cmd = OpenSystemCommand()
|
||||
expect(cmd.binaryPath) == "/usr/bin/open"
|
||||
expect(OpenSystemCommand().binaryPath) == "/usr/bin/open"
|
||||
}
|
||||
it("can be overridden") {
|
||||
let cmd = OpenSystemCommand(binaryPath: "/dev/null")
|
||||
expect(cmd.binaryPath) == "/dev/null"
|
||||
expect(OpenSystemCommand(binaryPath: "/dev/null").binaryPath) == "/dev/null"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ import Quick
|
|||
@testable import mas
|
||||
|
||||
public class AppListsFormatterSpec: QuickSpec {
|
||||
override public func spec() {
|
||||
override public static func spec() {
|
||||
// static func reference
|
||||
let format = AppListFormatter.format(products:)
|
||||
var products: [SoftwareProduct] = []
|
||||
|
@ -25,8 +25,7 @@ public class AppListsFormatterSpec: QuickSpec {
|
|||
products = []
|
||||
}
|
||||
it("formats nothing as empty string") {
|
||||
let output = format(products)
|
||||
expect(output) == ""
|
||||
expect(format(products)) == ""
|
||||
}
|
||||
it("can format a single product") {
|
||||
let product = SoftwareProductMock(
|
||||
|
@ -36,8 +35,7 @@ public class AppListsFormatterSpec: QuickSpec {
|
|||
bundleVersion: "19.2.1",
|
||||
itemIdentifier: 12345
|
||||
)
|
||||
let output = format([product])
|
||||
expect(output) == "12345 Awesome App (19.2.1)"
|
||||
expect(format([product])) == "12345 Awesome App (19.2.1)"
|
||||
}
|
||||
it("can format two products") {
|
||||
products = [
|
||||
|
@ -56,8 +54,8 @@ public class AppListsFormatterSpec: QuickSpec {
|
|||
itemIdentifier: 67890
|
||||
),
|
||||
]
|
||||
let output = format(products)
|
||||
expect(output) == "12345 Awesome App (19.2.1)\n67890 Even Better App (1.2.0)"
|
||||
expect(format(products))
|
||||
== "12345 Awesome App (19.2.1)\n67890 Even Better App (1.2.0)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ import Quick
|
|||
@testable import mas
|
||||
|
||||
public class SearchResultsFormatterSpec: QuickSpec {
|
||||
override public func spec() {
|
||||
override public static func spec() {
|
||||
// static func reference
|
||||
let format = SearchResultFormatter.format(results:includePrice:)
|
||||
var results: [SearchResult] = []
|
||||
|
@ -25,8 +25,7 @@ public class SearchResultsFormatterSpec: QuickSpec {
|
|||
results = []
|
||||
}
|
||||
it("formats nothing as empty string") {
|
||||
let output = format(results, false)
|
||||
expect(output) == ""
|
||||
expect(format(results, false)) == ""
|
||||
}
|
||||
it("can format a single result") {
|
||||
let result = SearchResult(
|
||||
|
@ -35,8 +34,7 @@ public class SearchResultsFormatterSpec: QuickSpec {
|
|||
trackName: "Awesome App",
|
||||
version: "19.2.1"
|
||||
)
|
||||
let output = format([result], false)
|
||||
expect(output) == " 12345 Awesome App (19.2.1)"
|
||||
expect(format([result], false)) == " 12345 Awesome App (19.2.1)"
|
||||
}
|
||||
it("can format a single result with price") {
|
||||
let result = SearchResult(
|
||||
|
@ -45,8 +43,7 @@ public class SearchResultsFormatterSpec: QuickSpec {
|
|||
trackName: "Awesome App",
|
||||
version: "19.2.1"
|
||||
)
|
||||
let output = format([result], true)
|
||||
expect(output) == " 12345 Awesome App $ 9.87 (19.2.1)"
|
||||
expect(format([result], true)) == " 12345 Awesome App $ 9.87 (19.2.1)"
|
||||
}
|
||||
it("can format a two results") {
|
||||
results = [
|
||||
|
@ -63,8 +60,8 @@ public class SearchResultsFormatterSpec: QuickSpec {
|
|||
version: "1.2.0"
|
||||
),
|
||||
]
|
||||
let output = format(results, false)
|
||||
expect(output) == " 12345 Awesome App (19.2.1)\n 67890 Even Better App (1.2.0)"
|
||||
expect(format(results, false))
|
||||
== " 12345 Awesome App (19.2.1)\n 67890 Even Better App (1.2.0)"
|
||||
}
|
||||
it("can format a two results with prices") {
|
||||
results = [
|
||||
|
@ -81,8 +78,7 @@ public class SearchResultsFormatterSpec: QuickSpec {
|
|||
version: "1.2.0"
|
||||
),
|
||||
]
|
||||
let output = format(results, true)
|
||||
expect(output)
|
||||
expect(format(results, true))
|
||||
== " 12345 Awesome App $ 9.87 (19.2.1)\n 67890 Even Better App $ 0.01 (1.2.0)"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,24 +13,22 @@ import Quick
|
|||
@testable import mas
|
||||
|
||||
public class SearchResultListSpec: QuickSpec {
|
||||
override public func spec() {
|
||||
override public static func spec() {
|
||||
beforeSuite {
|
||||
Mas.initialize()
|
||||
}
|
||||
describe("search result list") {
|
||||
it("can parse bbedit") {
|
||||
let data = Data(from: "search/bbedit.json")
|
||||
let decoder = JSONDecoder()
|
||||
let results = try decoder.decode(SearchResultList.self, from: data)
|
||||
|
||||
expect(results.resultCount) == 1
|
||||
expect(
|
||||
try JSONDecoder().decode(SearchResultList.self, from: Data(from: "search/bbedit.json")).resultCount
|
||||
)
|
||||
== 1
|
||||
}
|
||||
it("can parse things") {
|
||||
let data = Data(from: "search/things.json")
|
||||
let decoder = JSONDecoder()
|
||||
let results = try decoder.decode(SearchResultList.self, from: data)
|
||||
|
||||
expect(results.resultCount) == 50
|
||||
expect(
|
||||
try JSONDecoder().decode(SearchResultList.self, from: Data(from: "search/things.json")).resultCount
|
||||
)
|
||||
== 50
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,17 +13,18 @@ import Quick
|
|||
@testable import mas
|
||||
|
||||
public class SearchResultSpec: QuickSpec {
|
||||
override public func spec() {
|
||||
override public static func spec() {
|
||||
beforeSuite {
|
||||
Mas.initialize()
|
||||
}
|
||||
describe("search result") {
|
||||
it("can parse things") {
|
||||
let data = Data(from: "search/things-that-go-bump.json")
|
||||
let decoder = JSONDecoder()
|
||||
let result = try decoder.decode(SearchResult.self, from: data)
|
||||
|
||||
expect(result.bundleId) == "uikitformac.com.tinybop.thingamabops"
|
||||
expect(
|
||||
try JSONDecoder()
|
||||
.decode(SearchResult.self, from: Data(from: "search/things-that-go-bump.json"))
|
||||
.bundleId
|
||||
)
|
||||
== "uikitformac.com.tinybop.thingamabops"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ import Quick
|
|||
@testable import mas
|
||||
|
||||
public class SoftwareProductSpec: QuickSpec {
|
||||
override public func spec() {
|
||||
override public static func spec() {
|
||||
beforeSuite {
|
||||
Mas.initialize()
|
||||
}
|
||||
|
|
|
@ -31,8 +31,7 @@ class NetworkSessionMockFromFile: NetworkSessionMock {
|
|||
else { fatalError("Unable to load file \(responseFile)") }
|
||||
|
||||
do {
|
||||
let data = try Data(contentsOf: fileURL, options: .mappedIfSafe)
|
||||
return .value(data)
|
||||
return .value(try Data(contentsOf: fileURL, options: .mappedIfSafe))
|
||||
} catch {
|
||||
print("Error opening file: \(error)")
|
||||
return Promise(error: error)
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
//
|
||||
// OutputListener.swift
|
||||
// masTests
|
||||
//
|
||||
// Created by Ben Chatelain on 1/7/19.
|
||||
// Copyright © 2019 mas-cli. All rights reserved.
|
||||
//
|
||||
|
||||
@testable import mas
|
||||
|
||||
/// Test helper for monitoring strings written to stdout. Modified from:
|
||||
/// https://stackoverflow.com/a/53569018
|
||||
class OutputListener {
|
||||
/// Buffers strings written to stdout
|
||||
var contents = ""
|
||||
|
||||
init() {
|
||||
printObserver = { [weak self] text in
|
||||
strongify(self) { context in
|
||||
context.contents += text
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
printObserver = nil
|
||||
}
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
//
|
||||
// OutputListenerSpec.swift
|
||||
// masTests
|
||||
//
|
||||
// Created by Ben Chatelain on 1/8/19.
|
||||
// Copyright © 2019 mas-cli. All rights reserved.
|
||||
//
|
||||
|
||||
import Nimble
|
||||
import Quick
|
||||
|
||||
@testable import mas
|
||||
|
||||
public class OutputListenerSpec: QuickSpec {
|
||||
override public func spec() {
|
||||
beforeSuite {
|
||||
Mas.initialize()
|
||||
}
|
||||
describe("output listener") {
|
||||
it("can intercept a single line written stdout") {
|
||||
let output = OutputListener()
|
||||
|
||||
print("hi there", terminator: "")
|
||||
|
||||
expect(output.contents) == "hi there"
|
||||
}
|
||||
it("can intercept multiple lines written stdout") {
|
||||
let output = OutputListener()
|
||||
|
||||
print("hi there")
|
||||
|
||||
expect(output.contents) == """
|
||||
hi there
|
||||
|
||||
"""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
//
|
||||
// Strongify.swift
|
||||
// masTests
|
||||
//
|
||||
// Created by Ben Chatelain on 1/8/19.
|
||||
// Copyright © 2019 mas-cli. All rights reserved.
|
||||
//
|
||||
|
||||
// https://medium.com/@merowing_/stop-weak-strong-dance-in-swift-3aec6d3563d4
|
||||
|
||||
func strongify<Context: AnyObject>(_ context: Context?, closure: (Context) -> Void) {
|
||||
guard let strongContext = context else { return }
|
||||
closure(strongContext)
|
||||
}
|
Loading…
Add table
Reference in a new issue