mirror of
https://github.com/mas-cli/mas
synced 2024-11-22 03:23:08 +00:00
✅🤡 Add tests for search.
Add Result and Commandant in test bundle.
This commit is contained in:
parent
4938382104
commit
8a95fa04b8
7 changed files with 141 additions and 3 deletions
|
@ -30,7 +30,6 @@ public struct SearchCommand: CommandProtocol {
|
|||
}
|
||||
|
||||
public func run(_ options: Options) -> Result<(), MASError> {
|
||||
|
||||
guard let searchURLString = searchURLString(options.appName),
|
||||
let searchJson = urlSession.requestSynchronousJSONWithURLString(searchURLString) as? [String: Any] else {
|
||||
return .failure(.searchFailed)
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
public enum MASError: Error, CustomStringConvertible {
|
||||
public enum MASError: Error, CustomStringConvertible, Equatable {
|
||||
case notSignedIn
|
||||
case signInDisabled
|
||||
case signInFailed(error: NSError?)
|
||||
|
|
|
@ -45,7 +45,7 @@ public extension URLSession {
|
|||
}
|
||||
|
||||
/// Return JSON synchronous from specified endpoint
|
||||
public func requestSynchronousJSONWithURLString(_ requestString: String) -> Any? {
|
||||
@objc public func requestSynchronousJSONWithURLString(_ requestString: String) -> Any? {
|
||||
guard let url = URL(string: requestString) else {return nil}
|
||||
var request = URLRequest(url:url)
|
||||
request.httpMethod = "GET"
|
||||
|
|
9
MasKitTests/JSON/search_nonexistent.json
Normal file
9
MasKitTests/JSON/search_nonexistent.json
Normal file
|
@ -0,0 +1,9 @@
|
|||
|
||||
|
||||
|
||||
{
|
||||
"resultCount":0,
|
||||
"results": []
|
||||
}
|
||||
|
||||
|
75
MasKitTests/MockURLSession.swift
Normal file
75
MasKitTests/MockURLSession.swift
Normal file
|
@ -0,0 +1,75 @@
|
|||
//
|
||||
// MockURLSession.swift
|
||||
// MasKitTests
|
||||
//
|
||||
// Created by Ben Chatelain on 11/13/18.
|
||||
// Copyright © 2018 mas-cli. All rights reserved.
|
||||
//
|
||||
|
||||
@testable import MasKit
|
||||
|
||||
/// Mock URLSession for testing.
|
||||
class MockURLSession: URLSession {
|
||||
private let responseFile: String
|
||||
|
||||
/// Initializes a mock URL session with a file for the response.
|
||||
///
|
||||
/// - Parameter responseFile: Name of file containing JSON response body.
|
||||
init(responseFile: String) {
|
||||
self.responseFile = responseFile
|
||||
}
|
||||
|
||||
/// Override which returns JSON contents from a file.
|
||||
///
|
||||
/// - Parameter requestString: Ignored URL string
|
||||
/// - Returns: Contents of responseFile
|
||||
@objc override func requestSynchronousJSONWithURLString(_ requestString: String) -> Any? {
|
||||
guard let fileURL = Bundle.jsonResponse(fileName: responseFile)
|
||||
else { fatalError("Unable to load file \(responseFile)") }
|
||||
|
||||
do {
|
||||
let data = try Data(contentsOf: fileURL, options: .mappedIfSafe)
|
||||
let jsonResult = try JSONSerialization.jsonObject(with: data, options: .mutableLeaves)
|
||||
if let jsonResult = jsonResult as? Dictionary<String, AnyObject> {
|
||||
return jsonResult
|
||||
}
|
||||
} catch {
|
||||
print("Error parsing JSON: \(error)")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
extension Bundle {
|
||||
/// Locates a JSON response file from the test bundle.
|
||||
///
|
||||
/// - Parameter fileName: Name of file to locate.
|
||||
/// - Returns: URL to file.
|
||||
static func jsonResponse(fileName: String) -> URL? {
|
||||
return Bundle(for: MockURLSession.self).fileURL(fileName: fileName)
|
||||
}
|
||||
|
||||
/// Builds a URL for a file in the JSON directory of the current bundle.
|
||||
///
|
||||
/// - Parameter fileName: Name of file to locate.
|
||||
/// - Returns: URL to file.
|
||||
func fileURL(fileName: String) -> URL? {
|
||||
guard let path = self.path(forResource: fileName.fileNameWithoutExtension, ofType: fileName.fileExtension, inDirectory: "JSON")
|
||||
else { fatalError("Unable to load file \(fileName)") }
|
||||
|
||||
return URL(fileURLWithPath: path)
|
||||
}
|
||||
}
|
||||
|
||||
extension String {
|
||||
/// Returns the file name before the extension.
|
||||
var fileNameWithoutExtension: String {
|
||||
return (self as NSString).deletingPathExtension
|
||||
}
|
||||
|
||||
/// Returns the file extension.
|
||||
var fileExtension: String {
|
||||
return (self as NSString).pathExtension
|
||||
}
|
||||
}
|
|
@ -7,12 +7,32 @@
|
|||
//
|
||||
|
||||
@testable import MasKit
|
||||
import Result
|
||||
import Quick
|
||||
import Nimble
|
||||
|
||||
class SearchSpec: QuickSpec {
|
||||
override func spec() {
|
||||
describe("search") {
|
||||
context("for slack") {
|
||||
it("succeeds") {
|
||||
let search = SearchCommand(urlSession: MockURLSession(responseFile: "search_slack.json"))
|
||||
let searchOptions = SearchOptions(appName: "slack", price: false)
|
||||
let result = search.run(searchOptions)
|
||||
expect(result).to(beSuccess())
|
||||
}
|
||||
}
|
||||
context("for nonexistent") {
|
||||
it("fails") {
|
||||
let search = SearchCommand(urlSession: MockURLSession(responseFile: "search_nonexistent.json"))
|
||||
let searchOptions = SearchOptions(appName: "nonexistent", price: false)
|
||||
let result = search.run(searchOptions)
|
||||
expect(result).to(beFailure { error in
|
||||
expect(error) == .noSearchResultsFound
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
describe("url string") {
|
||||
it("contains the app name") {
|
||||
let appName = "myapp"
|
||||
|
@ -40,3 +60,26 @@ class SearchSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Nimble predicate for result enum success case, no associated value
|
||||
private func beSuccess() -> Predicate<Result<(), MASError>> {
|
||||
return Predicate.define("be <success>") { expression, message in
|
||||
if let actual = try expression.evaluate(),
|
||||
case .success = actual {
|
||||
return PredicateResult(status: .matches, message: message)
|
||||
}
|
||||
return PredicateResult(status: .fail, message: message)
|
||||
}
|
||||
}
|
||||
|
||||
/// Nimble predicate for result enum failure with associated error
|
||||
private func beFailure(test: @escaping (MASError) -> Void = { _ in }) -> Predicate<Result<(), MASError>> {
|
||||
return Predicate.define("be <failure>") { expression, message in
|
||||
if let actual = try expression.evaluate(),
|
||||
case let .failure(error) = actual {
|
||||
test(error)
|
||||
return PredicateResult(status: .matches, message: message)
|
||||
}
|
||||
return PredicateResult(status: .fail, message: message)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,10 @@
|
|||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
B537017321A0F85700538F78 /* Result.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 90CB4069213F4DDD0044E445 /* Result.framework */; };
|
||||
B537017421A0F85B00538F78 /* Commandant.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 90CB406B213F4DDD0044E445 /* Commandant.framework */; };
|
||||
B537017521A0F94000538F78 /* Result.framework in Copy Carthage Frameworks */ = {isa = PBXBuildFile; fileRef = 90CB4069213F4DDD0044E445 /* Result.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
B537017621A0F94200538F78 /* Commandant.framework in Copy Carthage Frameworks */ = {isa = PBXBuildFile; fileRef = 90CB406B213F4DDD0044E445 /* Commandant.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
B5552928219A1BB900ACB4CA /* CommerceKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F83213A62173EF75008BA8A0 /* CommerceKit.framework */; };
|
||||
B5552929219A1BC700ACB4CA /* StoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F83213A52173EF75008BA8A0 /* StoreFoundation.framework */; };
|
||||
B555292B219A1CB200ACB4CA /* MASErrorTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = B555292A219A1CB200ACB4CA /* MASErrorTestCase.swift */; };
|
||||
|
@ -16,6 +20,7 @@
|
|||
B5552936219A23FF00ACB4CA /* Nimble.framework in Copy Carthage Frameworks */ = {isa = PBXBuildFile; fileRef = 90CB406C213F4DDD0044E445 /* Nimble.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
B5552937219A23FF00ACB4CA /* Quick.framework in Copy Carthage Frameworks */ = {isa = PBXBuildFile; fileRef = 90CB406A213F4DDD0044E445 /* Quick.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
B5793E29219BDD4800135B39 /* JSON in Resources */ = {isa = PBXBuildFile; fileRef = B5793E28219BDD4800135B39 /* JSON */; };
|
||||
B5793E2B219BE0CD00135B39 /* MockURLSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5793E2A219BE0CD00135B39 /* MockURLSession.swift */; };
|
||||
ED031A7C1B5127C00097692E /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED031A7B1B5127C00097692E /* main.swift */; };
|
||||
F83213892173D3E1008BA8A0 /* CKAccountStore.h in Headers */ = {isa = PBXBuildFile; fileRef = F8FB719B20F2EC4500F56FDC /* CKAccountStore.h */; };
|
||||
F832138A2173D3E1008BA8A0 /* CKDownloadQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = F8FB719C20F2EC4500F56FDC /* CKDownloadQueue.h */; };
|
||||
|
@ -106,8 +111,10 @@
|
|||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
B537017621A0F94200538F78 /* Commandant.framework in Copy Carthage Frameworks */,
|
||||
B5552936219A23FF00ACB4CA /* Nimble.framework in Copy Carthage Frameworks */,
|
||||
B5552937219A23FF00ACB4CA /* Quick.framework in Copy Carthage Frameworks */,
|
||||
B537017521A0F94000538F78 /* Result.framework in Copy Carthage Frameworks */,
|
||||
);
|
||||
name = "Copy Carthage Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
@ -148,6 +155,7 @@
|
|||
B555292A219A1CB200ACB4CA /* MASErrorTestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MASErrorTestCase.swift; sourceTree = "<group>"; };
|
||||
B555292C219A1FE700ACB4CA /* SearchSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchSpec.swift; sourceTree = "<group>"; };
|
||||
B5793E28219BDD4800135B39 /* JSON */ = {isa = PBXFileReference; lastKnownFileType = folder; path = JSON; sourceTree = "<group>"; };
|
||||
B5793E2A219BE0CD00135B39 /* MockURLSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockURLSession.swift; sourceTree = "<group>"; };
|
||||
ED031A781B5127C00097692E /* mas */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = mas; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
ED031A7B1B5127C00097692E /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
|
||||
ED0F237E1B87522400AE40CD /* Install.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Install.swift; sourceTree = "<group>"; };
|
||||
|
@ -230,8 +238,10 @@
|
|||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
F8FB715B20F2B41400F56FDC /* MasKit.framework in Frameworks */,
|
||||
B537017421A0F85B00538F78 /* Commandant.framework in Frameworks */,
|
||||
B555292F219A219100ACB4CA /* Nimble.framework in Frameworks */,
|
||||
B555292E219A218E00ACB4CA /* Quick.framework in Frameworks */,
|
||||
B537017321A0F85700538F78 /* Result.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -346,6 +356,7 @@
|
|||
F8FB716120F2B41400F56FDC /* Info.plist */,
|
||||
B5793E28219BDD4800135B39 /* JSON */,
|
||||
B555292A219A1CB200ACB4CA /* MASErrorTestCase.swift */,
|
||||
B5793E2A219BE0CD00135B39 /* MockURLSession.swift */,
|
||||
B555292C219A1FE700ACB4CA /* SearchSpec.swift */,
|
||||
);
|
||||
path = MasKitTests;
|
||||
|
@ -609,6 +620,7 @@
|
|||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
B5793E2B219BE0CD00135B39 /* MockURLSession.swift in Sources */,
|
||||
B555292B219A1CB200ACB4CA /* MASErrorTestCase.swift in Sources */,
|
||||
B555292D219A1FE700ACB4CA /* SearchSpec.swift in Sources */,
|
||||
);
|
||||
|
|
Loading…
Reference in a new issue