mirror of
https://github.com/mas-cli/mas
synced 2025-03-06 23:57:21 +00:00
Improve value, error & captured stream comparisons.
Partial #596 Signed-off-by: Ross Goldberg <484615+rgoldberg@users.noreply.github.com>
This commit is contained in:
parent
f49d63a4f3
commit
905c4f7982
24 changed files with 299 additions and 232 deletions
Tests/masTests
.swiftlint.yml
Commands
AccountSpec.swiftHomeSpec.swiftInfoSpec.swiftInstallSpec.swiftListSpec.swiftLuckySpec.swiftOpenSpec.swiftOutdatedSpec.swiftPurchaseSpec.swiftSearchSpec.swiftSignInSpec.swiftSignOutSpec.swiftUninstallSpec.swiftUpgradeSpec.swiftVendorSpec.swiftVersionSpec.swift
Controllers
Formatters
Models
Utilities
|
@ -9,4 +9,5 @@ disabled_rules:
|
|||
- force_cast
|
||||
- force_try
|
||||
- implicitly_unwrapped_optional
|
||||
- large_tuple
|
||||
- no_magic_numbers
|
||||
|
|
|
@ -11,19 +11,15 @@ import Quick
|
|||
|
||||
@testable import mas
|
||||
|
||||
/// Deprecated test.
|
||||
public final class AccountSpec: QuickSpec {
|
||||
override public func spec() {
|
||||
beforeSuite {
|
||||
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()
|
||||
}
|
||||
.to(throwError(MASError.notSupported))
|
||||
describe("account command") {
|
||||
it("displays not supported warning") {
|
||||
expect(consequencesOf(try MAS.Account.parse([]).run()))
|
||||
== (error: MASError.notSupported, stdout: "", stderr: "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,10 +18,8 @@ public final class HomeSpec: QuickSpec {
|
|||
}
|
||||
describe("home command") {
|
||||
it("can't find app with unknown ID") {
|
||||
expect {
|
||||
try MAS.Home.parse(["999"]).run(searcher: MockAppStoreSearcher())
|
||||
}
|
||||
.to(throwError(MASError.unknownAppID(999)))
|
||||
expect(consequencesOf(try MAS.Home.parse(["999"]).run(searcher: MockAppStoreSearcher())))
|
||||
== (MASError.unknownAppID(999), "", "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
// Copyright © 2018 mas-cli. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Nimble
|
||||
import Quick
|
||||
|
||||
|
@ -19,10 +18,8 @@ public final class InfoSpec: QuickSpec {
|
|||
}
|
||||
describe("Info command") {
|
||||
it("can't find app with unknown ID") {
|
||||
expect {
|
||||
try MAS.Info.parse(["999"]).run(searcher: MockAppStoreSearcher())
|
||||
}
|
||||
.to(throwError(MASError.unknownAppID(999)))
|
||||
expect(consequencesOf(try MAS.Info.parse(["999"]).run(searcher: MockAppStoreSearcher())))
|
||||
== (MASError.unknownAppID(999), "", "")
|
||||
}
|
||||
it("displays app details") {
|
||||
let mockResult = SearchResult(
|
||||
|
@ -36,21 +33,25 @@ public final class InfoSpec: QuickSpec {
|
|||
trackViewUrl: "https://awesome.app",
|
||||
version: "1.0"
|
||||
)
|
||||
expect {
|
||||
try captureStream(stdout) {
|
||||
expect(
|
||||
consequencesOf(
|
||||
try MAS.Info.parse([String(mockResult.trackId)])
|
||||
.run(searcher: MockAppStoreSearcher([mockResult.trackId: mockResult]))
|
||||
}
|
||||
}
|
||||
== """
|
||||
Awesome App 1.0 [$2.00]
|
||||
By: Awesome Dev
|
||||
Released: 2019-01-07
|
||||
Minimum OS: 10.14
|
||||
Size: 1 KB
|
||||
From: https://awesome.app
|
||||
)
|
||||
)
|
||||
== (
|
||||
nil,
|
||||
"""
|
||||
Awesome App 1.0 [$2.00]
|
||||
By: Awesome Dev
|
||||
Released: 2019-01-07
|
||||
Minimum OS: 10.14
|
||||
Size: 1 KB
|
||||
From: https://awesome.app
|
||||
|
||||
"""
|
||||
""",
|
||||
""
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,10 +18,12 @@ public final class InstallSpec: QuickSpec {
|
|||
}
|
||||
xdescribe("install command") {
|
||||
it("installs apps") {
|
||||
expect {
|
||||
try MAS.Install.parse([]).run(appLibrary: MockAppLibrary(), searcher: MockAppStoreSearcher())
|
||||
}
|
||||
.toNot(throwError())
|
||||
expect(
|
||||
consequencesOf(
|
||||
try MAS.Install.parse([]).run(appLibrary: MockAppLibrary(), searcher: MockAppStoreSearcher())
|
||||
)
|
||||
)
|
||||
== (nil, "", "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
// Copyright © 2018 mas-cli. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Nimble
|
||||
import Quick
|
||||
|
||||
|
@ -19,12 +18,8 @@ public final class ListSpec: QuickSpec {
|
|||
}
|
||||
describe("list command") {
|
||||
it("lists apps") {
|
||||
expect {
|
||||
try captureStream(stderr) {
|
||||
try MAS.List.parse([]).run(appLibrary: MockAppLibrary())
|
||||
}
|
||||
}
|
||||
== "Error: No installed apps found\n"
|
||||
expect(consequencesOf(try MAS.List.parse([]).run(appLibrary: MockAppLibrary())))
|
||||
== (nil, "", "Error: No installed apps found\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,10 +21,10 @@ public final class LuckySpec: QuickSpec {
|
|||
}
|
||||
xdescribe("lucky command") {
|
||||
it("installs the first app matching a search") {
|
||||
expect {
|
||||
try MAS.Lucky.parse(["Slack"]).run(appLibrary: MockAppLibrary(), searcher: searcher)
|
||||
}
|
||||
.toNot(throwError())
|
||||
expect(
|
||||
consequencesOf(try MAS.Lucky.parse(["Slack"]).run(appLibrary: MockAppLibrary(), searcher: searcher))
|
||||
)
|
||||
== (nil, "", "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
// Copyright © 2019 mas-cli. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Nimble
|
||||
import Quick
|
||||
|
||||
|
@ -19,10 +18,8 @@ public final class OpenSpec: QuickSpec {
|
|||
}
|
||||
describe("open command") {
|
||||
it("can't find app with unknown ID") {
|
||||
expect {
|
||||
try MAS.Open.parse(["999"]).run(searcher: MockAppStoreSearcher())
|
||||
}
|
||||
.to(throwError(MASError.unknownAppID(999)))
|
||||
expect(consequencesOf(try MAS.Open.parse(["999"]).run(searcher: MockAppStoreSearcher())))
|
||||
== (MASError.unknownAppID(999), "", "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,8 +32,8 @@ public final class OutdatedSpec: QuickSpec {
|
|||
version: "1.28"
|
||||
)
|
||||
|
||||
expect {
|
||||
try captureStream(stdout) {
|
||||
expect(
|
||||
consequencesOf(
|
||||
try MAS.Outdated.parse([])
|
||||
.run(
|
||||
appLibrary: MockAppLibrary(
|
||||
|
@ -47,9 +47,9 @@ public final class OutdatedSpec: QuickSpec {
|
|||
),
|
||||
searcher: MockAppStoreSearcher([mockSearchResult.trackId: mockSearchResult])
|
||||
)
|
||||
}
|
||||
}
|
||||
== "490461369 Bandwidth+ (1.27 -> 1.28)\n"
|
||||
)
|
||||
)
|
||||
== (nil, "490461369 Bandwidth+ (1.27 -> 1.28)\n", "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,10 +18,13 @@ public final class PurchaseSpec: QuickSpec {
|
|||
}
|
||||
xdescribe("purchase command") {
|
||||
it("purchases apps") {
|
||||
expect {
|
||||
try MAS.Purchase.parse(["999"]).run(appLibrary: MockAppLibrary(), searcher: MockAppStoreSearcher())
|
||||
}
|
||||
.toNot(throwError())
|
||||
expect(
|
||||
consequencesOf(
|
||||
try MAS.Purchase.parse(["999"])
|
||||
.run(appLibrary: MockAppLibrary(), searcher: MockAppStoreSearcher())
|
||||
)
|
||||
)
|
||||
== (nil, "", "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
// Copyright © 2018 mas-cli. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Nimble
|
||||
import Quick
|
||||
|
||||
|
@ -25,19 +24,17 @@ public final class SearchSpec: QuickSpec {
|
|||
trackViewUrl: "mas preview url",
|
||||
version: "0.0"
|
||||
)
|
||||
expect {
|
||||
try captureStream(stdout) {
|
||||
expect(
|
||||
consequencesOf(
|
||||
try MAS.Search.parse(["slack"])
|
||||
.run(searcher: MockAppStoreSearcher([mockResult.trackId: mockResult]))
|
||||
}
|
||||
}
|
||||
== " 1111 slack (0.0)\n"
|
||||
)
|
||||
)
|
||||
== (nil, " 1111 slack (0.0)\n", "")
|
||||
}
|
||||
it("fails when searching for nonexistent app") {
|
||||
expect {
|
||||
try MAS.Search.parse(["nonexistent"]).run(searcher: MockAppStoreSearcher())
|
||||
}
|
||||
.to(throwError(MASError.noSearchResultsFound))
|
||||
expect(consequencesOf(try MAS.Search.parse(["nonexistent"]).run(searcher: MockAppStoreSearcher())))
|
||||
== (MASError.noSearchResultsFound, "", "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,13 +17,10 @@ public final class SignInSpec: QuickSpec {
|
|||
beforeSuite {
|
||||
MAS.initialize()
|
||||
}
|
||||
// signin 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()
|
||||
}
|
||||
.to(throwError(MASError.notSupported))
|
||||
expect(consequencesOf(try MAS.SignIn.parse(["", ""]).run()))
|
||||
== (MASError.notSupported, "", "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,10 +18,8 @@ public final class SignOutSpec: QuickSpec {
|
|||
}
|
||||
describe("signout command") {
|
||||
it("signs out") {
|
||||
expect {
|
||||
try MAS.SignOut.parse([]).run()
|
||||
}
|
||||
.toNot(throwError())
|
||||
expect(consequencesOf(try MAS.SignOut.parse([]).run()))
|
||||
== (nil, "", "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,46 +31,30 @@ public final class UninstallSpec: QuickSpec {
|
|||
let uninstall = try! MAS.Uninstall.parse(["--dry-run", String(appID)])
|
||||
|
||||
it("can't remove a missing app") {
|
||||
expect {
|
||||
try uninstall.run(appLibrary: MockAppLibrary())
|
||||
}
|
||||
.to(throwError(MASError.notInstalled(appID: appID)))
|
||||
expect(consequencesOf(try uninstall.run(appLibrary: MockAppLibrary())))
|
||||
== (MASError.notInstalled(appID: appID), "", "")
|
||||
}
|
||||
it("finds an app") {
|
||||
expect {
|
||||
try captureStream(stdout) {
|
||||
try uninstall.run(appLibrary: MockAppLibrary(app))
|
||||
}
|
||||
}
|
||||
== "==> 'Some App' '/tmp/Some.app'\n==> (not removed, dry run)\n"
|
||||
expect(consequencesOf(try uninstall.run(appLibrary: MockAppLibrary(app))))
|
||||
== (nil, "==> 'Some App' '/tmp/Some.app'\n==> (not removed, dry run)\n", "")
|
||||
}
|
||||
}
|
||||
context("wet run") {
|
||||
let uninstall = try! MAS.Uninstall.parse([String(appID)])
|
||||
|
||||
it("can't remove a missing app") {
|
||||
expect {
|
||||
try uninstall.run(appLibrary: MockAppLibrary())
|
||||
}
|
||||
.to(throwError(MASError.notInstalled(appID: appID)))
|
||||
expect(consequencesOf(try uninstall.run(appLibrary: MockAppLibrary())))
|
||||
== (MASError.notInstalled(appID: appID), "", "")
|
||||
}
|
||||
it("removes an app") {
|
||||
expect {
|
||||
try captureStream(stdout) {
|
||||
try uninstall.run(appLibrary: MockAppLibrary(app))
|
||||
}
|
||||
}
|
||||
.toNot(throwError())
|
||||
expect(consequencesOf(try uninstall.run(appLibrary: MockAppLibrary(app))))
|
||||
== (nil, "", "")
|
||||
}
|
||||
it("fails if there is a problem with the trash command") {
|
||||
var brokenApp = app
|
||||
brokenApp.bundlePath = "/dev/null"
|
||||
expect {
|
||||
try captureStream(stdout) {
|
||||
try uninstall.run(appLibrary: MockAppLibrary(brokenApp))
|
||||
}
|
||||
}
|
||||
.to(throwError(MASError.uninstallFailed(error: nil)))
|
||||
expect(consequencesOf(try uninstall.run(appLibrary: MockAppLibrary(brokenApp))))
|
||||
== (MASError.uninstallFailed(error: nil), "", "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
// Copyright © 2018 mas-cli. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Nimble
|
||||
import Quick
|
||||
|
||||
|
@ -19,13 +18,13 @@ public final class UpgradeSpec: QuickSpec {
|
|||
}
|
||||
describe("upgrade command") {
|
||||
it("finds no upgrades") {
|
||||
expect {
|
||||
try captureStream(stderr) {
|
||||
expect(
|
||||
consequencesOf(
|
||||
try MAS.Upgrade.parse([])
|
||||
.run(appLibrary: MockAppLibrary(), searcher: MockAppStoreSearcher())
|
||||
}
|
||||
}
|
||||
.toNot(throwError())
|
||||
)
|
||||
)
|
||||
== (nil, "", "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,10 +18,8 @@ public final class VendorSpec: QuickSpec {
|
|||
}
|
||||
describe("vendor command") {
|
||||
it("can't find app with unknown ID") {
|
||||
expect {
|
||||
try MAS.Vendor.parse(["999"]).run(searcher: MockAppStoreSearcher())
|
||||
}
|
||||
.to(throwError(MASError.unknownAppID(999)))
|
||||
expect(consequencesOf(try MAS.Vendor.parse(["999"]).run(searcher: MockAppStoreSearcher())))
|
||||
== (MASError.unknownAppID(999), "", "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
// Copyright © 2018 mas-cli. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Nimble
|
||||
import Quick
|
||||
|
||||
|
@ -19,12 +18,8 @@ public final class VersionSpec: QuickSpec {
|
|||
}
|
||||
describe("version command") {
|
||||
it("displays the current version") {
|
||||
expect {
|
||||
try captureStream(stdout) {
|
||||
try MAS.Version.parse([]).run()
|
||||
}
|
||||
}
|
||||
== "\(Package.version)\n"
|
||||
expect(consequencesOf(try MAS.Version.parse([]).run()))
|
||||
== (nil, "\(Package.version)\n", "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,26 +18,41 @@ public final class ITunesSearchAppStoreSearcherSpec: QuickSpec {
|
|||
}
|
||||
describe("url string") {
|
||||
it("contains the search term") {
|
||||
expect {
|
||||
ITunesSearchAppStoreSearcher()
|
||||
.searchURL(
|
||||
for: "myapp",
|
||||
inRegion: findISORegion(forAlpha2Code: "US")
|
||||
)?
|
||||
.absoluteString
|
||||
}
|
||||
== "https://itunes.apple.com/search?media=software&entity=desktopSoftware&country=US&term=myapp"
|
||||
expect(
|
||||
consequencesOf(
|
||||
ITunesSearchAppStoreSearcher()
|
||||
.searchURL(
|
||||
for: "myapp",
|
||||
inRegion: findISORegion(forAlpha2Code: "US")
|
||||
)?
|
||||
.absoluteString
|
||||
)
|
||||
)
|
||||
== (
|
||||
"https://itunes.apple.com/search?media=software&entity=desktopSoftware&country=US&term=myapp",
|
||||
nil,
|
||||
"",
|
||||
""
|
||||
)
|
||||
}
|
||||
it("contains the encoded search term") {
|
||||
expect {
|
||||
ITunesSearchAppStoreSearcher()
|
||||
.searchURL(
|
||||
for: "My App",
|
||||
inRegion: findISORegion(forAlpha2Code: "US")
|
||||
)?
|
||||
.absoluteString
|
||||
}
|
||||
== "https://itunes.apple.com/search?media=software&entity=desktopSoftware&country=US&term=My%20App"
|
||||
expect(
|
||||
consequencesOf(
|
||||
ITunesSearchAppStoreSearcher()
|
||||
.searchURL(
|
||||
for: "My App",
|
||||
inRegion: findISORegion(forAlpha2Code: "US")
|
||||
)?
|
||||
.absoluteString
|
||||
)
|
||||
)
|
||||
== (
|
||||
// swiftlint:disable:next line_length
|
||||
"https://itunes.apple.com/search?media=software&entity=desktopSoftware&country=US&term=My%20App",
|
||||
nil,
|
||||
"",
|
||||
""
|
||||
)
|
||||
}
|
||||
}
|
||||
describe("store") {
|
||||
|
@ -46,10 +61,11 @@ public final class ITunesSearchAppStoreSearcherSpec: QuickSpec {
|
|||
let networkSession = MockFromFileNetworkSession(responseFile: "search/slack.json")
|
||||
let searcher = ITunesSearchAppStoreSearcher(networkManager: NetworkManager(session: networkSession))
|
||||
|
||||
expect {
|
||||
try searcher.search(for: "slack").wait()
|
||||
}
|
||||
.to(haveCount(39))
|
||||
let consequences = consequencesOf(try searcher.search(for: "slack").wait())
|
||||
expect(consequences.value).to(haveCount(39))
|
||||
expect(consequences.error) == nil
|
||||
expect(consequences.stdout).to(beEmpty())
|
||||
expect(consequences.stderr).to(beEmpty())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -59,13 +75,12 @@ public final class ITunesSearchAppStoreSearcherSpec: QuickSpec {
|
|||
let networkSession = MockFromFileNetworkSession(responseFile: "lookup/slack.json")
|
||||
let searcher = ITunesSearchAppStoreSearcher(networkManager: NetworkManager(session: networkSession))
|
||||
|
||||
var result: SearchResult?
|
||||
expect {
|
||||
result = try searcher.lookup(appID: appID).wait()
|
||||
}
|
||||
.toNot(throwError())
|
||||
let consequences = consequencesOf(try searcher.lookup(appID: appID).wait())
|
||||
expect(consequences.error) == nil
|
||||
expect(consequences.stdout).to(beEmpty())
|
||||
expect(consequences.stderr).to(beEmpty())
|
||||
|
||||
guard let result else {
|
||||
guard let result = consequences.value else {
|
||||
fatalError("lookup result was nil")
|
||||
}
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ public final class AppListFormatterSpec: QuickSpec {
|
|||
}
|
||||
describe("app list formatter") {
|
||||
it("formats nothing as empty string") {
|
||||
expect(format([])).to(beEmpty())
|
||||
expect(consequencesOf(format([]))) == ("", nil, "", "")
|
||||
}
|
||||
it("can format a single product") {
|
||||
let product = MockSoftwareProduct(
|
||||
|
@ -31,30 +31,32 @@ public final class AppListFormatterSpec: QuickSpec {
|
|||
bundleVersion: "19.2.1",
|
||||
itemIdentifier: 12345
|
||||
)
|
||||
expect(format([product])) == "12345 Awesome App (19.2.1)"
|
||||
expect(consequencesOf(format([product]))) == ("12345 Awesome App (19.2.1)", nil, "", "")
|
||||
}
|
||||
it("can format two products") {
|
||||
expect(
|
||||
format(
|
||||
[
|
||||
MockSoftwareProduct(
|
||||
appName: "Awesome App",
|
||||
bundleIdentifier: "",
|
||||
bundlePath: "",
|
||||
bundleVersion: "19.2.1",
|
||||
itemIdentifier: 12345
|
||||
),
|
||||
MockSoftwareProduct(
|
||||
appName: "Even Better App",
|
||||
bundleIdentifier: "",
|
||||
bundlePath: "",
|
||||
bundleVersion: "1.2.0",
|
||||
itemIdentifier: 67890
|
||||
),
|
||||
]
|
||||
consequencesOf(
|
||||
format(
|
||||
[
|
||||
MockSoftwareProduct(
|
||||
appName: "Awesome App",
|
||||
bundleIdentifier: "",
|
||||
bundlePath: "",
|
||||
bundleVersion: "19.2.1",
|
||||
itemIdentifier: 12345
|
||||
),
|
||||
MockSoftwareProduct(
|
||||
appName: "Even Better App",
|
||||
bundleIdentifier: "",
|
||||
bundlePath: "",
|
||||
bundleVersion: "1.2.0",
|
||||
itemIdentifier: 67890
|
||||
),
|
||||
]
|
||||
)
|
||||
)
|
||||
)
|
||||
== "12345 Awesome App (19.2.1)\n67890 Even Better App (1.2.0)"
|
||||
== ("12345 Awesome App (19.2.1)\n67890 Even Better App (1.2.0)", nil, "", "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ public final class SearchResultFormatterSpec: QuickSpec {
|
|||
}
|
||||
describe("search results formatter") {
|
||||
it("formats nothing as empty string") {
|
||||
expect(format([], false)).to(beEmpty())
|
||||
expect(consequencesOf(format([], false))) == ("", nil, "", "")
|
||||
}
|
||||
it("can format a single result") {
|
||||
let result = SearchResult(
|
||||
|
@ -30,7 +30,7 @@ public final class SearchResultFormatterSpec: QuickSpec {
|
|||
trackName: "Awesome App",
|
||||
version: "19.2.1"
|
||||
)
|
||||
expect(format([result], false)) == " 12345 Awesome App (19.2.1)"
|
||||
expect(consequencesOf(format([result], false))) == (" 12345 Awesome App (19.2.1)", nil, "", "")
|
||||
}
|
||||
it("can format a single result with price") {
|
||||
let result = SearchResult(
|
||||
|
@ -39,51 +39,61 @@ public final class SearchResultFormatterSpec: QuickSpec {
|
|||
trackName: "Awesome App",
|
||||
version: "19.2.1"
|
||||
)
|
||||
expect(format([result], true)) == " 12345 Awesome App (19.2.1) $9.87"
|
||||
expect(consequencesOf(format([result], true)))
|
||||
== (" 12345 Awesome App (19.2.1) $9.87", nil, "", "")
|
||||
}
|
||||
it("can format a two results") {
|
||||
expect(
|
||||
format(
|
||||
[
|
||||
SearchResult(
|
||||
formattedPrice: "$9.87",
|
||||
trackId: 12345,
|
||||
trackName: "Awesome App",
|
||||
version: "19.2.1"
|
||||
),
|
||||
SearchResult(
|
||||
formattedPrice: "$0.01",
|
||||
trackId: 67890,
|
||||
trackName: "Even Better App",
|
||||
version: "1.2.0"
|
||||
),
|
||||
],
|
||||
false
|
||||
consequencesOf(
|
||||
format(
|
||||
[
|
||||
SearchResult(
|
||||
formattedPrice: "$9.87",
|
||||
trackId: 12345,
|
||||
trackName: "Awesome App",
|
||||
version: "19.2.1"
|
||||
),
|
||||
SearchResult(
|
||||
formattedPrice: "$0.01",
|
||||
trackId: 67890,
|
||||
trackName: "Even Better App",
|
||||
version: "1.2.0"
|
||||
),
|
||||
],
|
||||
false
|
||||
)
|
||||
)
|
||||
)
|
||||
== " 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)", nil, "", "")
|
||||
}
|
||||
it("can format a two results with prices") {
|
||||
expect(
|
||||
format(
|
||||
[
|
||||
SearchResult(
|
||||
formattedPrice: "$9.87",
|
||||
trackId: 12345,
|
||||
trackName: "Awesome App",
|
||||
version: "19.2.1"
|
||||
),
|
||||
SearchResult(
|
||||
formattedPrice: "$0.01",
|
||||
trackId: 67890,
|
||||
trackName: "Even Better App",
|
||||
version: "1.2.0"
|
||||
),
|
||||
],
|
||||
true
|
||||
consequencesOf(
|
||||
format(
|
||||
[
|
||||
SearchResult(
|
||||
formattedPrice: "$9.87",
|
||||
trackId: 12345,
|
||||
trackName: "Awesome App",
|
||||
version: "19.2.1"
|
||||
),
|
||||
SearchResult(
|
||||
formattedPrice: "$0.01",
|
||||
trackId: 67890,
|
||||
trackName: "Even Better App",
|
||||
version: "1.2.0"
|
||||
),
|
||||
],
|
||||
true
|
||||
)
|
||||
)
|
||||
)
|
||||
== " 12345 Awesome App (19.2.1) $9.87\n 67890 Even Better App (1.2.0) $0.01"
|
||||
== (
|
||||
" 12345 Awesome App (19.2.1) $9.87\n 67890 Even Better App (1.2.0) $0.01",
|
||||
nil,
|
||||
"",
|
||||
""
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,15 +20,23 @@ public final class SearchResultListSpec: QuickSpec {
|
|||
describe("search result list") {
|
||||
it("can parse bbedit") {
|
||||
expect(
|
||||
try JSONDecoder().decode(SearchResultList.self, from: Data(from: "search/bbedit.json")).resultCount
|
||||
consequencesOf(
|
||||
try JSONDecoder()
|
||||
.decode(SearchResultList.self, from: Data(from: "search/bbedit.json"))
|
||||
.resultCount
|
||||
)
|
||||
)
|
||||
== 1
|
||||
== (1, nil, "", "")
|
||||
}
|
||||
it("can parse things") {
|
||||
expect(
|
||||
try JSONDecoder().decode(SearchResultList.self, from: Data(from: "search/things.json")).resultCount
|
||||
consequencesOf(
|
||||
try JSONDecoder()
|
||||
.decode(SearchResultList.self, from: Data(from: "search/things.json"))
|
||||
.resultCount
|
||||
)
|
||||
)
|
||||
== 50
|
||||
== (50, nil, "", "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,11 +20,13 @@ public final class SearchResultSpec: QuickSpec {
|
|||
describe("search result") {
|
||||
it("can parse things") {
|
||||
expect(
|
||||
try JSONDecoder()
|
||||
.decode(SearchResult.self, from: Data(from: "search/things-that-go-bump.json"))
|
||||
.trackId
|
||||
consequencesOf(
|
||||
try JSONDecoder()
|
||||
.decode(SearchResult.self, from: Data(from: "search/things-that-go-bump.json"))
|
||||
.trackId
|
||||
)
|
||||
)
|
||||
== 1_472_954_003
|
||||
== (1_472_954_003, nil, "", "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,16 +27,28 @@ public final class SoftwareProductSpec: QuickSpec {
|
|||
}
|
||||
describe("software product") {
|
||||
it("is not outdated when there is no new version available") {
|
||||
expect(app.isOutdated(comparedTo: SearchResult(version: "1.0.0"))) == false
|
||||
expect(consequencesOf(app.isOutdated(comparedTo: SearchResult(version: "1.0.0"))))
|
||||
== (false, nil, "", "")
|
||||
}
|
||||
it("is outdated when there is a new version available") {
|
||||
expect(app.isOutdated(comparedTo: SearchResult(version: "2.0.0"))) == true
|
||||
expect(consequencesOf(app.isOutdated(comparedTo: SearchResult(version: "2.0.0"))))
|
||||
== (true, nil, "", "")
|
||||
}
|
||||
it("is not outdated when the new version of mac-software requires a higher OS version") {
|
||||
expect(app.isOutdated(comparedTo: SearchResult(minimumOsVersion: "99.0.0", version: "3.0.0"))) == false
|
||||
expect(
|
||||
consequencesOf(
|
||||
app.isOutdated(comparedTo: SearchResult(minimumOsVersion: "99.0.0", version: "3.0.0"))
|
||||
)
|
||||
)
|
||||
== (false, nil, "", "")
|
||||
}
|
||||
it("is not outdated when the new version of software requires a higher OS version") {
|
||||
expect(app.isOutdated(comparedTo: SearchResult(minimumOsVersion: "99.0.0", version: "3.0.0"))) == false
|
||||
expect(
|
||||
consequencesOf(
|
||||
app.isOutdated(comparedTo: SearchResult(minimumOsVersion: "99.0.0", version: "3.0.0"))
|
||||
)
|
||||
)
|
||||
== (false, nil, "", "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,29 +8,86 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
func captureStream(
|
||||
_ stream: UnsafeMutablePointer<FILE>,
|
||||
encoding: String.Encoding = .utf8,
|
||||
_ block: @escaping () throws -> Void
|
||||
) rethrows -> String {
|
||||
let originalFd = fileno(stream)
|
||||
let duplicateFd = dup(originalFd)
|
||||
@testable import mas
|
||||
|
||||
func consequencesOf(
|
||||
streamEncoding: String.Encoding = .utf8,
|
||||
_ expression: @autoclosure @escaping () throws -> Void
|
||||
) -> (error: MASError?, stdout: String, stderr: String) {
|
||||
let consequences = consequences(streamEncoding: streamEncoding, expression)
|
||||
return (consequences.error, consequences.stdout, consequences.stderr)
|
||||
}
|
||||
|
||||
func consequencesOf<T>(
|
||||
streamEncoding: String.Encoding = .utf8,
|
||||
_ expression: @autoclosure @escaping () throws -> T
|
||||
) -> (value: T?, error: MASError?, stdout: String, stderr: String) {
|
||||
consequences(streamEncoding: streamEncoding, expression)
|
||||
}
|
||||
|
||||
// periphery:ignore
|
||||
func consequencesOf(
|
||||
streamEncoding: String.Encoding = .utf8,
|
||||
_ body: @escaping () throws -> Void
|
||||
) -> (error: MASError?, stdout: String, stderr: String) {
|
||||
let consequences = consequences(streamEncoding: streamEncoding, body)
|
||||
return (consequences.error, consequences.stdout, consequences.stderr)
|
||||
}
|
||||
|
||||
// periphery:ignore
|
||||
func consequencesOf<T>(
|
||||
streamEncoding: String.Encoding = .utf8,
|
||||
_ body: @escaping () throws -> T
|
||||
) -> (value: T?, error: MASError?, stdout: String, stderr: String) {
|
||||
consequences(streamEncoding: streamEncoding, body)
|
||||
}
|
||||
|
||||
private func consequences<T>(
|
||||
streamEncoding: String.Encoding = .utf8,
|
||||
_ body: @escaping () throws -> T
|
||||
) -> (value: T?, error: MASError?, stdout: String, stderr: String) {
|
||||
let outOriginalFD = fileno(stdout)
|
||||
let errOriginalFD = fileno(stderr)
|
||||
|
||||
let outDuplicateFD = dup(outOriginalFD)
|
||||
defer {
|
||||
close(duplicateFd)
|
||||
close(outDuplicateFD)
|
||||
}
|
||||
|
||||
let pipe = Pipe()
|
||||
dup2(pipe.fileHandleForWriting.fileDescriptor, originalFd)
|
||||
let errDuplicateFD = dup(errOriginalFD)
|
||||
defer {
|
||||
close(errDuplicateFD)
|
||||
}
|
||||
|
||||
let outPipe = Pipe()
|
||||
let errPipe = Pipe()
|
||||
|
||||
dup2(outPipe.fileHandleForWriting.fileDescriptor, outOriginalFD)
|
||||
dup2(errPipe.fileHandleForWriting.fileDescriptor, errOriginalFD)
|
||||
|
||||
var value: T?
|
||||
var thrownError: MASError?
|
||||
do {
|
||||
defer {
|
||||
fflush(stream)
|
||||
dup2(duplicateFd, originalFd)
|
||||
pipe.fileHandleForWriting.closeFile()
|
||||
fflush(stdout)
|
||||
fflush(stderr)
|
||||
dup2(outDuplicateFD, outOriginalFD)
|
||||
dup2(errDuplicateFD, errOriginalFD)
|
||||
outPipe.fileHandleForWriting.closeFile()
|
||||
errPipe.fileHandleForWriting.closeFile()
|
||||
}
|
||||
|
||||
try block()
|
||||
value = try body()
|
||||
} catch let error as MASError {
|
||||
thrownError = error
|
||||
} catch {
|
||||
thrownError = MASError.failed(error: error as NSError)
|
||||
}
|
||||
|
||||
return String(data: pipe.fileHandleForReading.readDataToEndOfFile(), encoding: encoding) ?? ""
|
||||
return (
|
||||
value,
|
||||
thrownError,
|
||||
String(data: outPipe.fileHandleForReading.readDataToEndOfFile(), encoding: streamEncoding) ?? "",
|
||||
String(data: errPipe.fileHandleForReading.readDataToEndOfFile(), encoding: streamEncoding) ?? ""
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue