mirror of
https://github.com/mas-cli/mas
synced 2025-03-06 23:57:21 +00:00
commit
441991a791
32 changed files with 363 additions and 577 deletions
.swiftlint.yml
Tests/masTests
.swiftlint.yml
Commands
AccountSpec.swiftHomeSpec.swiftInfoSpec.swiftInstallSpec.swiftListSpec.swiftLuckySpec.swiftOpenSpec.swiftOutdatedSpec.swiftPurchaseSpec.swiftResetSpec.swiftSearchSpec.swiftSignInSpec.swiftSignOutSpec.swiftUninstallSpec.swiftUpgradeSpec.swiftVendorSpec.swiftVersionSpec.swift
Controllers
Errors
Formatters
Models
Network
Utilities
script
|
@ -8,7 +8,6 @@
|
|||
opt_in_rules:
|
||||
- all
|
||||
disabled_rules:
|
||||
- balanced_xctest_lifecycle
|
||||
- closure_body_length
|
||||
- contrasted_opening_brace
|
||||
- explicit_acl
|
||||
|
@ -17,7 +16,6 @@ disabled_rules:
|
|||
- explicit_type_interface
|
||||
- file_header
|
||||
- file_name
|
||||
- final_test_case
|
||||
- force_unwrapping
|
||||
- function_body_length
|
||||
- inert_defer
|
||||
|
@ -26,7 +24,6 @@ disabled_rules:
|
|||
- no_grouping_extension
|
||||
- number_separator
|
||||
- one_declaration_per_file
|
||||
- prefer_nimble
|
||||
- prefixed_toplevel_constant
|
||||
- quick_discouraged_call
|
||||
- quick_discouraged_pending_test
|
||||
|
|
|
@ -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 class AccountSpec: QuickSpec {
|
||||
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: "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,17 +11,15 @@ import Quick
|
|||
|
||||
@testable import mas
|
||||
|
||||
public class HomeSpec: QuickSpec {
|
||||
public final class HomeSpec: QuickSpec {
|
||||
override public func spec() {
|
||||
beforeSuite {
|
||||
MAS.initialize()
|
||||
}
|
||||
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,23 +6,20 @@
|
|||
// Copyright © 2018 mas-cli. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Nimble
|
||||
import Quick
|
||||
|
||||
@testable import mas
|
||||
|
||||
public class InfoSpec: QuickSpec {
|
||||
public final class InfoSpec: QuickSpec {
|
||||
override public func spec() {
|
||||
beforeSuite {
|
||||
MAS.initialize()
|
||||
}
|
||||
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 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
|
||||
|
||||
"""
|
||||
""",
|
||||
""
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,17 +11,19 @@ import Quick
|
|||
|
||||
@testable import mas
|
||||
|
||||
public class InstallSpec: QuickSpec {
|
||||
public final class InstallSpec: QuickSpec {
|
||||
override public func spec() {
|
||||
beforeSuite {
|
||||
MAS.initialize()
|
||||
}
|
||||
xdescribe("install command") {
|
||||
xit("installs apps") {
|
||||
expect {
|
||||
try MAS.Install.parse([]).run(appLibrary: MockAppLibrary(), searcher: MockAppStoreSearcher())
|
||||
}
|
||||
.toNot(throwError())
|
||||
it("installs apps") {
|
||||
expect(
|
||||
consequencesOf(
|
||||
try MAS.Install.parse([]).run(appLibrary: MockAppLibrary(), searcher: MockAppStoreSearcher())
|
||||
)
|
||||
)
|
||||
== (nil, "", "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,25 +6,20 @@
|
|||
// Copyright © 2018 mas-cli. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Nimble
|
||||
import Quick
|
||||
|
||||
@testable import mas
|
||||
|
||||
public class ListSpec: QuickSpec {
|
||||
public final class ListSpec: QuickSpec {
|
||||
override public func spec() {
|
||||
beforeSuite {
|
||||
MAS.initialize()
|
||||
}
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ import Quick
|
|||
|
||||
@testable import mas
|
||||
|
||||
public class LuckySpec: QuickSpec {
|
||||
public final class LuckySpec: QuickSpec {
|
||||
override public func spec() {
|
||||
let networkSession = MockFromFileNetworkSession(responseFile: "search/slack.json")
|
||||
let searcher = ITunesSearchAppStoreSearcher(networkManager: NetworkManager(session: networkSession))
|
||||
|
@ -20,11 +20,11 @@ public class LuckySpec: QuickSpec {
|
|||
MAS.initialize()
|
||||
}
|
||||
xdescribe("lucky command") {
|
||||
xit("installs the first app matching a search") {
|
||||
expect {
|
||||
try MAS.Lucky.parse(["Slack"]).run(appLibrary: MockAppLibrary(), searcher: searcher)
|
||||
}
|
||||
.toNot(throwError())
|
||||
it("installs the first app matching a search") {
|
||||
expect(
|
||||
consequencesOf(try MAS.Lucky.parse(["Slack"]).run(appLibrary: MockAppLibrary(), searcher: searcher))
|
||||
)
|
||||
== (nil, "", "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,23 +6,20 @@
|
|||
// Copyright © 2019 mas-cli. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Nimble
|
||||
import Quick
|
||||
|
||||
@testable import mas
|
||||
|
||||
public class OpenSpec: QuickSpec {
|
||||
public final class OpenSpec: QuickSpec {
|
||||
override public func spec() {
|
||||
beforeSuite {
|
||||
MAS.initialize()
|
||||
}
|
||||
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), "", "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ import Quick
|
|||
|
||||
@testable import mas
|
||||
|
||||
public class OutdatedSpec: QuickSpec {
|
||||
public final class OutdatedSpec: QuickSpec {
|
||||
override public func spec() {
|
||||
beforeSuite {
|
||||
MAS.initialize()
|
||||
|
@ -32,8 +32,8 @@ public class OutdatedSpec: QuickSpec {
|
|||
version: "1.28"
|
||||
)
|
||||
|
||||
expect {
|
||||
try captureStream(stdout) {
|
||||
expect(
|
||||
consequencesOf(
|
||||
try MAS.Outdated.parse([])
|
||||
.run(
|
||||
appLibrary: MockAppLibrary(
|
||||
|
@ -47,9 +47,9 @@ public class OutdatedSpec: QuickSpec {
|
|||
),
|
||||
searcher: MockAppStoreSearcher([mockSearchResult.trackId: mockSearchResult])
|
||||
)
|
||||
}
|
||||
}
|
||||
== "490461369 Bandwidth+ (1.27 -> 1.28)\n"
|
||||
)
|
||||
)
|
||||
== (nil, "490461369 Bandwidth+ (1.27 -> 1.28)\n", "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,17 +11,20 @@ import Quick
|
|||
|
||||
@testable import mas
|
||||
|
||||
public class PurchaseSpec: QuickSpec {
|
||||
public final class PurchaseSpec: QuickSpec {
|
||||
override public func spec() {
|
||||
beforeSuite {
|
||||
MAS.initialize()
|
||||
}
|
||||
xdescribe("purchase command") {
|
||||
xit("purchases apps") {
|
||||
expect {
|
||||
try MAS.Purchase.parse(["999"]).run(appLibrary: MockAppLibrary(), searcher: MockAppStoreSearcher())
|
||||
}
|
||||
.toNot(throwError())
|
||||
it("purchases apps") {
|
||||
expect(
|
||||
consequencesOf(
|
||||
try MAS.Purchase.parse(["999"])
|
||||
.run(appLibrary: MockAppLibrary(), searcher: MockAppStoreSearcher())
|
||||
)
|
||||
)
|
||||
== (nil, "", "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
//
|
||||
// ResetSpec.swift
|
||||
// masTests
|
||||
//
|
||||
// Created by Ben Chatelain on 2018-12-28.
|
||||
// Copyright © 2018 mas-cli. All rights reserved.
|
||||
//
|
||||
|
||||
import Nimble
|
||||
import Quick
|
||||
|
||||
@testable import mas
|
||||
|
||||
public class ResetSpec: QuickSpec {
|
||||
override public func spec() {
|
||||
beforeSuite {
|
||||
MAS.initialize()
|
||||
}
|
||||
describe("reset command") {
|
||||
it("resets the App Store state") {
|
||||
expect {
|
||||
try MAS.Reset.parse([]).run()
|
||||
}
|
||||
.toNot(throwError())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,13 +6,12 @@
|
|||
// Copyright © 2018 mas-cli. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Nimble
|
||||
import Quick
|
||||
|
||||
@testable import mas
|
||||
|
||||
public class SearchSpec: QuickSpec {
|
||||
public final class SearchSpec: QuickSpec {
|
||||
override public func spec() {
|
||||
beforeSuite {
|
||||
MAS.initialize()
|
||||
|
@ -25,19 +24,17 @@ public 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, "", "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,18 +12,15 @@ import Quick
|
|||
@testable import mas
|
||||
|
||||
/// Deprecated test.
|
||||
public class SignInSpec: QuickSpec {
|
||||
public final class SignInSpec: QuickSpec {
|
||||
override public func spec() {
|
||||
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, "", "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,17 +11,15 @@ import Quick
|
|||
|
||||
@testable import mas
|
||||
|
||||
public class SignOutSpec: QuickSpec {
|
||||
public final class SignOutSpec: QuickSpec {
|
||||
override public func spec() {
|
||||
beforeSuite {
|
||||
MAS.initialize()
|
||||
}
|
||||
describe("signout command") {
|
||||
it("signs out") {
|
||||
expect {
|
||||
try MAS.SignOut.parse([]).run()
|
||||
}
|
||||
.toNot(throwError())
|
||||
expect(consequencesOf(try MAS.SignOut.parse([]).run()))
|
||||
== (nil, "", "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,65 +12,49 @@ import Quick
|
|||
|
||||
@testable import mas
|
||||
|
||||
public class UninstallSpec: QuickSpec {
|
||||
public final class UninstallSpec: QuickSpec {
|
||||
override public func spec() {
|
||||
let appID: AppID = 12345
|
||||
let app = MockSoftwareProduct(
|
||||
appName: "Some App",
|
||||
bundleIdentifier: "com.some.app",
|
||||
bundlePath: "/tmp/Some.app",
|
||||
bundleVersion: "1.0",
|
||||
itemIdentifier: NSNumber(value: appID)
|
||||
)
|
||||
|
||||
beforeSuite {
|
||||
MAS.initialize()
|
||||
}
|
||||
xdescribe("uninstall command") {
|
||||
let appID: AppID = 12345
|
||||
let app = MockSoftwareProduct(
|
||||
appName: "Some App",
|
||||
bundleIdentifier: "com.some.app",
|
||||
bundlePath: "/tmp/Some.app",
|
||||
bundleVersion: "1.0",
|
||||
itemIdentifier: NSNumber(value: appID)
|
||||
)
|
||||
|
||||
context("dry run") {
|
||||
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,26 +6,25 @@
|
|||
// Copyright © 2018 mas-cli. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Nimble
|
||||
import Quick
|
||||
|
||||
@testable import mas
|
||||
|
||||
public class UpgradeSpec: QuickSpec {
|
||||
public final class UpgradeSpec: QuickSpec {
|
||||
override public func spec() {
|
||||
beforeSuite {
|
||||
MAS.initialize()
|
||||
}
|
||||
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, "", "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,17 +11,15 @@ import Quick
|
|||
|
||||
@testable import mas
|
||||
|
||||
public class VendorSpec: QuickSpec {
|
||||
public final class VendorSpec: QuickSpec {
|
||||
override public func spec() {
|
||||
beforeSuite {
|
||||
MAS.initialize()
|
||||
}
|
||||
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,25 +6,20 @@
|
|||
// Copyright © 2018 mas-cli. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Nimble
|
||||
import Quick
|
||||
|
||||
@testable import mas
|
||||
|
||||
public class VersionSpec: QuickSpec {
|
||||
public final class VersionSpec: QuickSpec {
|
||||
override public func spec() {
|
||||
beforeSuite {
|
||||
MAS.initialize()
|
||||
}
|
||||
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", "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,33 +11,48 @@ import Quick
|
|||
|
||||
@testable import mas
|
||||
|
||||
public class ITunesSearchAppStoreSearcherSpec: QuickSpec {
|
||||
public final class ITunesSearchAppStoreSearcherSpec: QuickSpec {
|
||||
override public func spec() {
|
||||
beforeSuite {
|
||||
MAS.initialize()
|
||||
}
|
||||
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 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 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")
|
||||
}
|
||||
|
||||
|
|
|
@ -1,47 +0,0 @@
|
|||
//
|
||||
// SoftwareMapAppLibrarySpec.swift
|
||||
// masTests
|
||||
//
|
||||
// Created by Ben Chatelain on 3/1/20.
|
||||
// Copyright © 2020 mas-cli. All rights reserved.
|
||||
//
|
||||
|
||||
import Nimble
|
||||
import Quick
|
||||
|
||||
@testable import mas
|
||||
|
||||
public class SoftwareMapAppLibrarySpec: QuickSpec {
|
||||
override public func spec() {
|
||||
let myApp = MockSoftwareProduct(
|
||||
appName: "MyApp",
|
||||
bundleIdentifier: "com.example",
|
||||
bundlePath: "/Applications/MyApp.app",
|
||||
bundleVersion: "1.0.0",
|
||||
itemIdentifier: 1234
|
||||
)
|
||||
|
||||
let apps = [myApp]
|
||||
|
||||
let library = SoftwareMapAppLibrary(softwareMap: MockSoftwareMap(products: apps))
|
||||
|
||||
beforeSuite {
|
||||
MAS.initialize()
|
||||
}
|
||||
describe("mas app library") {
|
||||
it("contains all installed apps") {
|
||||
expect(library.installedApps).to(haveCount(apps.count))
|
||||
expect(library.installedApps.first!.appName) == myApp.appName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - MockSoftwareMap
|
||||
struct MockSoftwareMap: SoftwareMap {
|
||||
let products: [SoftwareProduct]
|
||||
|
||||
func allSoftwareProducts() -> [SoftwareProduct] {
|
||||
products
|
||||
}
|
||||
}
|
|
@ -1,92 +0,0 @@
|
|||
//
|
||||
// MASErrorTestCase.swift
|
||||
// masTests
|
||||
//
|
||||
// Created by Ben Chatelain on 2/11/18.
|
||||
// Copyright © 2018 Andrew Naylor. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import XCTest
|
||||
|
||||
@testable import mas
|
||||
|
||||
class MASErrorTestCase: XCTestCase {
|
||||
private static let error = NSError(domain: "MAS", code: 999, userInfo: [NSLocalizedDescriptionKey: "foo"])
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
MAS.initialize()
|
||||
}
|
||||
|
||||
func testNotSignedIn() {
|
||||
XCTAssertEqual(MASError.notSignedIn.description, "Not signed in")
|
||||
}
|
||||
|
||||
func testSignInFailed() {
|
||||
XCTAssertEqual(MASError.signInFailed(error: nil).description, "Sign in failed")
|
||||
}
|
||||
|
||||
func testSignInFailedError() {
|
||||
XCTAssertEqual(MASError.signInFailed(error: Self.error).description, "Sign in failed: foo")
|
||||
}
|
||||
|
||||
func testAlreadySignedIn() {
|
||||
XCTAssertEqual(
|
||||
MASError.alreadySignedIn(asAppleID: "person@example.com").description,
|
||||
"Already signed in as person@example.com"
|
||||
)
|
||||
}
|
||||
|
||||
func testPurchaseFailed() {
|
||||
XCTAssertEqual(MASError.purchaseFailed(error: nil).description, "Download request failed")
|
||||
}
|
||||
|
||||
func testPurchaseFailedError() {
|
||||
XCTAssertEqual(MASError.purchaseFailed(error: Self.error).description, "Download request failed: foo")
|
||||
}
|
||||
|
||||
func testDownloadFailed() {
|
||||
XCTAssertEqual(MASError.downloadFailed(error: nil).description, "Download failed")
|
||||
}
|
||||
|
||||
func testDownloadFailedError() {
|
||||
XCTAssertEqual(MASError.downloadFailed(error: Self.error).description, "Download failed: foo")
|
||||
}
|
||||
|
||||
func testNoDownloads() {
|
||||
XCTAssertEqual(MASError.noDownloads.description, "No downloads began")
|
||||
}
|
||||
|
||||
func testCancelled() {
|
||||
XCTAssertEqual(MASError.cancelled.description, "Download cancelled")
|
||||
}
|
||||
|
||||
func testSearchFailed() {
|
||||
XCTAssertEqual(MASError.searchFailed.description, "Search failed")
|
||||
}
|
||||
|
||||
func testNoSearchResultsFound() {
|
||||
XCTAssertEqual(MASError.noSearchResultsFound.description, "No apps found")
|
||||
}
|
||||
|
||||
func testNoVendorWebsite() {
|
||||
XCTAssertEqual(MASError.noVendorWebsite.description, "App does not have a vendor website")
|
||||
}
|
||||
|
||||
func testNotInstalled() {
|
||||
XCTAssertEqual(MASError.notInstalled(appID: 123).description, "No apps installed with app ID 123")
|
||||
}
|
||||
|
||||
func testUninstallFailed() {
|
||||
XCTAssertEqual(MASError.uninstallFailed(error: nil).description, "Uninstall failed")
|
||||
}
|
||||
|
||||
func testNoData() {
|
||||
XCTAssertEqual(MASError.noData.description, "Service did not return data")
|
||||
}
|
||||
|
||||
func testJsonParsing() {
|
||||
XCTAssertEqual(MASError.jsonParsing(data: Data()).description, "Unable to parse response as JSON:\n")
|
||||
}
|
||||
}
|
|
@ -11,7 +11,7 @@ import Quick
|
|||
|
||||
@testable import mas
|
||||
|
||||
public class AppListFormatterSpec: QuickSpec {
|
||||
public final class AppListFormatterSpec: QuickSpec {
|
||||
override public func spec() {
|
||||
// static func reference
|
||||
let format = AppListFormatter.format(products:)
|
||||
|
@ -21,7 +21,7 @@ public 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 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, "", "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ import Quick
|
|||
|
||||
@testable import mas
|
||||
|
||||
public class SearchResultFormatterSpec: QuickSpec {
|
||||
public final class SearchResultFormatterSpec: QuickSpec {
|
||||
override public func spec() {
|
||||
// static func reference
|
||||
let format = SearchResultFormatter.format(results:includePrice:)
|
||||
|
@ -21,7 +21,7 @@ public 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 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 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,
|
||||
"",
|
||||
""
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ import Quick
|
|||
|
||||
@testable import mas
|
||||
|
||||
public class SearchResultListSpec: QuickSpec {
|
||||
public final class SearchResultListSpec: QuickSpec {
|
||||
override public func spec() {
|
||||
beforeSuite {
|
||||
MAS.initialize()
|
||||
|
@ -20,15 +20,23 @@ public 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, "", "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ import Quick
|
|||
|
||||
@testable import mas
|
||||
|
||||
public class SearchResultSpec: QuickSpec {
|
||||
public final class SearchResultSpec: QuickSpec {
|
||||
override public func spec() {
|
||||
beforeSuite {
|
||||
MAS.initialize()
|
||||
|
@ -20,11 +20,13 @@ public 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, "", "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,36 +12,43 @@ import Quick
|
|||
|
||||
@testable import mas
|
||||
|
||||
public class SoftwareProductSpec: QuickSpec {
|
||||
public final class SoftwareProductSpec: QuickSpec {
|
||||
override public func spec() {
|
||||
let app = MockSoftwareProduct(
|
||||
appName: "App",
|
||||
bundleIdentifier: "",
|
||||
bundlePath: "",
|
||||
bundleVersion: "1.0.0",
|
||||
itemIdentifier: 111
|
||||
)
|
||||
|
||||
beforeSuite {
|
||||
MAS.initialize()
|
||||
}
|
||||
describe("software product") {
|
||||
let app = MockSoftwareProduct(
|
||||
appName: "App",
|
||||
bundleIdentifier: "",
|
||||
bundlePath: "",
|
||||
bundleVersion: "1.0.0",
|
||||
itemIdentifier: 111
|
||||
)
|
||||
|
||||
let currentApp = SearchResult(version: "1.0.0")
|
||||
let appUpdate = SearchResult(version: "2.0.0")
|
||||
let higherOs = SearchResult(minimumOsVersion: "99.0.0", version: "3.0.0")
|
||||
let updateIos = SearchResult(minimumOsVersion: "99.0.0", version: "3.0.0")
|
||||
|
||||
it("is not outdated when there is no new version available") {
|
||||
expect(app.isOutdated(comparedTo: currentApp)) == 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: appUpdate)) == 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: higherOs)) == 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: updateIos)) == false
|
||||
expect(
|
||||
consequencesOf(
|
||||
app.isOutdated(comparedTo: SearchResult(minimumOsVersion: "99.0.0", version: "3.0.0"))
|
||||
)
|
||||
)
|
||||
== (false, nil, "", "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
//
|
||||
// MockNetworkSession
|
||||
// masTests
|
||||
//
|
||||
// Created by Ben Chatelain on 11/13/18.
|
||||
// Copyright © 2018 mas-cli. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import PromiseKit
|
||||
|
||||
@testable import mas
|
||||
|
||||
/// Mock NetworkSession for testing.
|
||||
struct MockNetworkSession: NetworkSession {
|
||||
// Properties that enable us to set exactly what data or error
|
||||
// we want our mocked URLSession to return for any request.
|
||||
private let data: Data?
|
||||
private let error: Error?
|
||||
|
||||
init(data: Data) {
|
||||
self.data = data
|
||||
error = nil
|
||||
}
|
||||
|
||||
init(error: Error) {
|
||||
self.error = error
|
||||
data = nil
|
||||
}
|
||||
|
||||
func loadData(from _: URL) -> Promise<Data> {
|
||||
guard let data else {
|
||||
return Promise(error: error ?? MASError.noData)
|
||||
}
|
||||
|
||||
return .value(data)
|
||||
}
|
||||
}
|
|
@ -1,68 +0,0 @@
|
|||
//
|
||||
// NetworkManagerTests.swift
|
||||
// masTests
|
||||
//
|
||||
// Created by Ben Chatelain on 1/5/19.
|
||||
// Copyright © 2019 mas-cli. All rights reserved.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
@testable import mas
|
||||
|
||||
class NetworkManagerTests: XCTestCase {
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
MAS.initialize()
|
||||
}
|
||||
|
||||
func testSuccessfulAsyncResponse() throws {
|
||||
let data = Data([0, 1, 0, 1])
|
||||
XCTAssertEqual(
|
||||
try NetworkManager(session: MockNetworkSession(data: data))
|
||||
.loadData(from: URL(fileURLWithPath: "url"))
|
||||
.wait(),
|
||||
data
|
||||
)
|
||||
}
|
||||
|
||||
func testSuccessfulSyncResponse() throws {
|
||||
let data = Data([0, 1, 0, 1])
|
||||
XCTAssertEqual(
|
||||
try NetworkManager(session: MockNetworkSession(data: data))
|
||||
.loadData(from: URL(fileURLWithPath: "url"))
|
||||
.wait(),
|
||||
data
|
||||
)
|
||||
}
|
||||
|
||||
func testFailureAsyncResponse() {
|
||||
XCTAssertThrowsError(
|
||||
try NetworkManager(session: MockNetworkSession(error: MASError.noData))
|
||||
.loadData(from: URL(fileURLWithPath: "url"))
|
||||
.wait()
|
||||
) { error in
|
||||
guard let masError = error as? MASError else {
|
||||
XCTFail("Error is of unexpected type.")
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssertEqual(masError, MASError.noData)
|
||||
}
|
||||
}
|
||||
|
||||
func testFailureSyncResponse() {
|
||||
XCTAssertThrowsError(
|
||||
try NetworkManager(session: MockNetworkSession(error: MASError.noData))
|
||||
.loadData(from: URL(fileURLWithPath: "url"))
|
||||
.wait()
|
||||
) { error in
|
||||
guard let error = error as? MASError else {
|
||||
XCTFail("Error is of unexpected type.")
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssertEqual(error, MASError.noData)
|
||||
}
|
||||
}
|
||||
}
|
93
Tests/masTests/Utilities/Consequences.swift
Normal file
93
Tests/masTests/Utilities/Consequences.swift
Normal file
|
@ -0,0 +1,93 @@
|
|||
//
|
||||
// Consequences.swift
|
||||
// masTests
|
||||
//
|
||||
// Created by Ross Goldberg on 2024-12-29.
|
||||
// Copyright © 2016 mas-cli. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
@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(outDuplicateFD)
|
||||
}
|
||||
|
||||
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(stdout)
|
||||
fflush(stderr)
|
||||
dup2(outDuplicateFD, outOriginalFD)
|
||||
dup2(errDuplicateFD, errOriginalFD)
|
||||
outPipe.fileHandleForWriting.closeFile()
|
||||
errPipe.fileHandleForWriting.closeFile()
|
||||
}
|
||||
|
||||
value = try body()
|
||||
} catch let error as MASError {
|
||||
thrownError = error
|
||||
} catch {
|
||||
thrownError = MASError.failed(error: error as NSError)
|
||||
}
|
||||
|
||||
return (
|
||||
value,
|
||||
thrownError,
|
||||
String(data: outPipe.fileHandleForReading.readDataToEndOfFile(), encoding: streamEncoding) ?? "",
|
||||
String(data: errPipe.fileHandleForReading.readDataToEndOfFile(), encoding: streamEncoding) ?? ""
|
||||
)
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
//
|
||||
// Streams.swift
|
||||
// masTests
|
||||
//
|
||||
// Created by Ross Goldberg on 2024-12-29.
|
||||
// Copyright © 2016 mas-cli. All rights reserved.
|
||||
//
|
||||
|
||||
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)
|
||||
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) ?? ""
|
||||
}
|
|
@ -12,5 +12,5 @@ printf $'==> ✅ Testing mas %s\n' "$(script/version)"
|
|||
|
||||
script/generate_version_info_for_swift
|
||||
|
||||
script -q /dev/null swift test |
|
||||
script -q /dev/null swift test "${@}" |
|
||||
(grep -vxE $'Test Suite \'.+\' (?:started|passed) at \\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}\\.\\d{3}\\.?\\r|Test Case \'-\\[.+\\]\' (?:started|passed \\(\\d+\\.\\d+ seconds\\))\\.\\r|\\t Executed \\d+ tests?, with 0 failures \\(0 unexpected\\) in \\d+\\.\\d+ \\(\\d+\\.\\d+\\) seconds\\r' || true)
|
||||
|
|
Loading…
Add table
Reference in a new issue