diff --git a/Modules/Package.swift b/Modules/Package.swift index 448feeabe53d..298e9ec16366 100644 --- a/Modules/Package.swift +++ b/Modules/Package.swift @@ -47,6 +47,7 @@ let package = Package( .package(url: "https://github.com/wordpress-mobile/FSInteractiveMap", from: "0.3.0"), .package(url: "https://github.com/wordpress-mobile/MediaEditor-iOS", branch: "task/spm-support"), .package(url: "https://github.com/wordpress-mobile/NSObject-SafeExpectations", from: "0.0.6"), + .package(url: "https://github.com/wordpress-mobile/wpxmlrpc", from: "0.9.0"), .package(url: "https://github.com/wordpress-mobile/NSURL-IDN", revision: "b34794c9a3f32312e1593d4a3d120572afa0d010"), .package( url: "https://github.com/wordpress-mobile/WordPressKit-iOS", @@ -221,6 +222,7 @@ enum XcodeSupport { .library(name: "XcodeTarget_App", targets: ["XcodeTarget_App"]), .library(name: "XcodeTarget_Keystone", targets: ["XcodeTarget_Keystone"]), .library(name: "XcodeTarget_WordPressTests", targets: ["XcodeTarget_WordPressTests"]), + .library(name: "XcodeTarget_WordPressKitTests", targets: ["XcodeTarget_WordPressKitTests"]), .library(name: "XcodeTarget_WordPressData", targets: ["XcodeTarget_WordPressData"]), .library(name: "XcodeTarget_WordPressAuthentificator", targets: ["XcodeTarget_WordPressAuthentificator"]), .library(name: "XcodeTarget_WordPressAuthentificatorTests", targets: ["XcodeTarget_WordPressAuthentificatorTests"]), @@ -350,6 +352,10 @@ enum XcodeSupport { .product(name: "WordPressAPI", package: "wordpress-rs"), .product(name: "WordPressKit", package: "WordPressKit-iOS"), ]), + .xcodeTarget("XcodeTarget_WordPressKitTests", dependencies: testDependencies + [ + "wpxmlrpc", + .product(name: "WordPressKit", package: "WordPressKit-iOS"), + ]), .xcodeTarget("XcodeTarget_WordPressAuthentificator", dependencies: wordPresAuthentificatorDependencies), .xcodeTarget("XcodeTarget_WordPressAuthentificatorTests", dependencies: wordPresAuthentificatorDependencies + testDependencies), .xcodeTarget("XcodeTarget_ShareExtension", dependencies: shareAndDraftExtensionsDependencies), diff --git a/Tests/KeystoneTests/WordPressUnitTests.xctestplan b/Tests/KeystoneTests/WordPressUnitTests.xctestplan index 5fbff54d114f..107323853bad 100644 --- a/Tests/KeystoneTests/WordPressUnitTests.xctestplan +++ b/Tests/KeystoneTests/WordPressUnitTests.xctestplan @@ -34,15 +34,8 @@ { "target" : { "containerPath" : "container:..\/Modules", - "identifier" : "AsyncImageKitTests", - "name" : "AsyncImageKitTests" - } - }, - { - "target" : { - "containerPath" : "container:WordPress.xcodeproj", - "identifier" : "4AD953BA2C21451700D0EEFA", - "name" : "WordPressAuthenticatorTests" + "identifier" : "WordPressFluxTests", + "name" : "WordPressFluxTests" } }, { @@ -62,15 +55,15 @@ { "target" : { "containerPath" : "container:..\/Modules", - "identifier" : "WordPressUIUnitTests", - "name" : "WordPressUIUnitTests" + "identifier" : "WordPressSharedTests", + "name" : "WordPressSharedTests" } }, { "target" : { - "containerPath" : "container:..\/Modules", - "identifier" : "WordPressSharedTests", - "name" : "WordPressSharedTests" + "containerPath" : "container:WordPress.xcodeproj", + "identifier" : "4A8280FC2E5FE9B60037E180", + "name" : "WordPressKitTests" } }, { @@ -80,11 +73,18 @@ "name" : "JetpackStatsWidgetsCoreTests" } }, + { + "target" : { + "containerPath" : "container:WordPress.xcodeproj", + "identifier" : "4AD953BA2C21451700D0EEFA", + "name" : "WordPressAuthenticatorTests" + } + }, { "target" : { "containerPath" : "container:..\/Modules", - "identifier" : "WordPressFluxTests", - "name" : "WordPressFluxTests" + "identifier" : "WordPressUIUnitTests", + "name" : "WordPressUIUnitTests" } }, { @@ -93,6 +93,13 @@ "identifier" : "WordPressSharedObjCTests", "name" : "WordPressSharedObjCTests" } + }, + { + "target" : { + "containerPath" : "container:..\/Modules", + "identifier" : "AsyncImageKitTests", + "name" : "AsyncImageKitTests" + } } ], "version" : 1 diff --git a/Tests/WordPressKitTests/CoreAPITests/AppTransportSecuritySettingsTests.swift b/Tests/WordPressKitTests/CoreAPITests/AppTransportSecuritySettingsTests.swift new file mode 100644 index 000000000000..0fd612fccfc0 --- /dev/null +++ b/Tests/WordPressKitTests/CoreAPITests/AppTransportSecuritySettingsTests.swift @@ -0,0 +1,103 @@ +import XCTest +#if SWIFT_PACKAGE +@testable import CoreAPI +#else +@testable import WordPressKit +#endif + +final class AppTransportSecuritySettingsTests: XCTestCase { + + private var exampleURL = URL(string: "https://example.com")! + + func testReturnsTrueIfAllowsLocalNetworkingIsTrue() throws { + // Given + let provider = FakeInfoDictionaryObjectProvider(appTransportSecurity: [ + "NSAllowsLocalNetworking": true, + // This will be ignored + "NSAllowsArbitraryLoads": true + ]) + let appTransportSecurity = AppTransportSecuritySettings(provider) + + // When + let secureAccessOnly = appTransportSecurity.secureAccessOnly(for: exampleURL) + + // Then + XCTAssertTrue(secureAccessOnly) + } + + func testReturnsFalseIfAllowsArbitraryLoadsIsTrue() throws { + // Given + let provider = FakeInfoDictionaryObjectProvider(appTransportSecurity: [ + "NSAllowsArbitraryLoads": true + ]) + let appTransportSecurity = AppTransportSecuritySettings(provider) + + // When + let secureAccessOnly = appTransportSecurity.secureAccessOnly(for: exampleURL) + + // Then + XCTAssertFalse(secureAccessOnly) + } + + func testReturnsTrueByDefault() throws { + // Given + let provider = FakeInfoDictionaryObjectProvider(appTransportSecurity: nil) + let appTransportSecurity = AppTransportSecuritySettings(provider) + + // When + let secureAccessOnly = appTransportSecurity.secureAccessOnly(for: exampleURL) + + // Then + XCTAssertTrue(secureAccessOnly) + } + + func testReturnsTrueIfNothingIsDefined() throws { + // Given + let provider = FakeInfoDictionaryObjectProvider(appTransportSecurity: [String: Any]()) + let appTransportSecurity = AppTransportSecuritySettings(provider) + + // When + let secureAccessOnly = appTransportSecurity.secureAccessOnly(for: exampleURL) + + // Then + XCTAssertTrue(secureAccessOnly) + } + + func testReturnsFalseIfAllowsInsecureHTTPLoadsIsTrue() throws { + // Given + let provider = FakeInfoDictionaryObjectProvider(appTransportSecurity: [ + "NSExceptionDomains": [ + "shiki.me": [ + "NSExceptionAllowsInsecureHTTPLoads": true + ] + ] + ]) + let appTransportSecurity = AppTransportSecuritySettings(provider) + let url = try XCTUnwrap(URL(string: "http://shiki.me")) + + // When + let secureAccessOnly = appTransportSecurity.secureAccessOnly(for: url) + + // Then + XCTAssertFalse(secureAccessOnly) + } + + func testReturnsTrueIfAllowsInsecureHTTPLoadsIsNotProvided() throws { + // Given + let provider = FakeInfoDictionaryObjectProvider(appTransportSecurity: [ + "NSExceptionDomains": [ + "shiki.me": [String: Any]() + ], + // This value will be ignored because there is an exception for shiki.me + "NSAllowsArbitraryLoads": true + ]) + let appTransportSecurity = AppTransportSecuritySettings(provider) + let url = try XCTUnwrap(URL(string: "http://shiki.me")) + + // When + let secureAccessOnly = appTransportSecurity.secureAccessOnly(for: url) + + // Then + XCTAssertTrue(secureAccessOnly) + } +} diff --git a/Tests/WordPressKitTests/CoreAPITests/Bundle+SPMSupport.swift b/Tests/WordPressKitTests/CoreAPITests/Bundle+SPMSupport.swift new file mode 100644 index 000000000000..0c3d02c28c0b --- /dev/null +++ b/Tests/WordPressKitTests/CoreAPITests/Bundle+SPMSupport.swift @@ -0,0 +1,25 @@ +import Foundation + +extension Bundle { + /// Returns the `Bundle` for the target. + /// + /// If installed via CocoaPods, this will be `.bundle`, otherwise it will be the framework bundle. + @objc public class var coreAPITestsBundle: Bundle { +#if SWIFT_PACKAGE + return Bundle.module +#else + let defaultBundle = Bundle(for: BundleFinder.self) + + guard let bundleURL = defaultBundle.resourceURL, + let resourceBundle = Bundle(url: bundleURL.appendingPathComponent("CoreAPITests.bundle")) else { + return defaultBundle + } + + return resourceBundle +#endif + } +} + +#if !SWIFT_PACKAGE +private class BundleFinder: NSObject {} +#endif diff --git a/Tests/WordPressKitTests/CoreAPITests/FakeInfoDictionaryObjectProvider.swift b/Tests/WordPressKitTests/CoreAPITests/FakeInfoDictionaryObjectProvider.swift new file mode 100644 index 000000000000..74569e5bfab6 --- /dev/null +++ b/Tests/WordPressKitTests/CoreAPITests/FakeInfoDictionaryObjectProvider.swift @@ -0,0 +1,21 @@ +#if SWIFT_PACKAGE +@testable import CoreAPI +#else +@testable import WordPressKit +#endif + +class FakeInfoDictionaryObjectProvider: InfoDictionaryObjectProvider { + private let appTransportSecurity: [String: Any]? + + init(appTransportSecurity: [String: Any]?) { + self.appTransportSecurity = appTransportSecurity + } + + func object(forInfoDictionaryKey key: String) -> Any? { + if key == "NSAppTransportSecurity" { + return appTransportSecurity + } + + return nil + } +} diff --git a/Tests/WordPressKitTests/CoreAPITests/HTTPRequestBuilderTests.swift b/Tests/WordPressKitTests/CoreAPITests/HTTPRequestBuilderTests.swift new file mode 100644 index 000000000000..4b455c76cb3e --- /dev/null +++ b/Tests/WordPressKitTests/CoreAPITests/HTTPRequestBuilderTests.swift @@ -0,0 +1,431 @@ +#if SWIFT_PACKAGE +@testable import CoreAPI +#else +@testable import WordPressKit +#endif +import wpxmlrpc +import XCTest + +class HTTPRequestBuilderTests: XCTestCase { + + static let nestedParameters: [String: Any] = + [ + "number": 1, + "nsnumber-true": NSNumber(value: true), + "true": true, + "false": false, + "string": "true", + "dict": ["foo": true, "bar": "string"], + "nested-dict": [ + "outer1": [ + "inner1": "value1", + "inner2": "value2" + ], + "outer2": [ + "inner1": "value1", + "inner2": "value2" + ] + ], + "array": ["true", 1, false] + ] + static let nestedParametersEncoded = [ + "number=1", + "nsnumber-true=1", + "true=1", + "false=0", + "string=true", + "dict[foo]=1", + "dict[bar]=string", + "nested-dict[outer1][inner1]=value1", + "nested-dict[outer1][inner2]=value2", + "nested-dict[outer2][inner1]=value1", + "nested-dict[outer2][inner2]=value2", + "array[]=true", + "array[]=1", + "array[]=0", + ] + + func testURL() throws { + try XCTAssertEqual(HTTPRequestBuilder(url: URL(string: "https://wordpress.org")!).build().url?.absoluteString, "https://wordpress.org") + try XCTAssertEqual(HTTPRequestBuilder(url: URL(string: "https://wordpress.com")!).build().url?.absoluteString, "https://wordpress.com") + } + + func testHTTPMethods() throws { + try XCTAssertEqual(HTTPRequestBuilder(url: URL(string: "https://wordpress.org")!).build().httpMethod, "GET") + XCTAssertFalse(HTTPRequestBuilder.Method.get.allowsHTTPBody) + + try XCTAssertEqual(HTTPRequestBuilder(url: URL(string: "https://wordpress.org")!).method(.delete).build().httpMethod, "DELETE") + XCTAssertFalse(HTTPRequestBuilder.Method.delete.allowsHTTPBody) + + try XCTAssertEqual(HTTPRequestBuilder(url: URL(string: "https://wordpress.org")!).method(.post).build().httpMethod, "POST") + XCTAssertTrue(HTTPRequestBuilder.Method.post.allowsHTTPBody) + + try XCTAssertEqual(HTTPRequestBuilder(url: URL(string: "https://wordpress.org")!).method(.patch).build().httpMethod, "PATCH") + XCTAssertTrue(HTTPRequestBuilder.Method.patch.allowsHTTPBody) + + try XCTAssertEqual(HTTPRequestBuilder(url: URL(string: "https://wordpress.org")!).method(.put).build().httpMethod, "PUT") + XCTAssertTrue(HTTPRequestBuilder.Method.put.allowsHTTPBody) + } + + func testHeader() throws { + let request = try HTTPRequestBuilder(url: URL(string: "https://wordpress.org")!) + .header(name: "X-Header-1", value: "Foo") + .header(name: "X-Header-2", value: "Bar") + .build() + XCTAssertEqual(request.value(forHTTPHeaderField: "X-Header-1"), "Foo") + XCTAssertEqual(request.value(forHTTPHeaderField: "X-Header-2"), "Bar") + } + + func testPath() throws { + var request = try HTTPRequestBuilder(url: URL(string: "https://wordpress.org")!) + .append(percentEncodedPath: "hello/world") + .build() + XCTAssertEqual(request.url?.absoluteString, "https://wordpress.org/hello/world") + + request = try HTTPRequestBuilder(url: URL(string: "https://wordpress.org")!) + .append(percentEncodedPath: "/hello/world") + .build() + XCTAssertEqual(request.url?.absoluteString, "https://wordpress.org/hello/world") + + request = try HTTPRequestBuilder(url: URL(string: "https://wordpress.org/hello")!) + .append(percentEncodedPath: "world") + .build() + XCTAssertEqual(request.url?.absoluteString, "https://wordpress.org/hello/world") + + request = try HTTPRequestBuilder(url: URL(string: "https://wordpress.org/hello")!) + .append(percentEncodedPath: "/world") + .build() + XCTAssertEqual(request.url?.absoluteString, "https://wordpress.org/hello/world") + } + + func testQueryOverride() { + try XCTAssertEqual( + HTTPRequestBuilder(url: URL(string: "https://wordpress.org")!) + .query(name: "foo", value: "bar", override: true) + .build() + .url? + .absoluteString, + "https://wordpress.org?foo=bar" + ) + + try XCTAssertEqual( + HTTPRequestBuilder(url: URL(string: "https://wordpress.org")!) + .query(name: "foo", value: "bar", override: true) + .query(name: "foo", value: "hello", override: true) + .build() + .url? + .absoluteString, + "https://wordpress.org?foo=hello" + ) + + try XCTAssertEqual( + HTTPRequestBuilder(url: URL(string: "https://wordpress.org")!) + .query(name: "foo", value: "bar", override: true) + .query(name: "foo", value: "hello", override: false) + .build() + .url? + .absoluteString, + "https://wordpress.org?foo=bar&foo=hello" + ) + + try XCTAssertEqual( + HTTPRequestBuilder(url: URL(string: "https://wordpress.org")!) + .query(name: "foo", value: "bar") + .query(name: "foo", value: "hello") + .build() + .url? + .absoluteString, + "https://wordpress.org?foo=bar&foo=hello" + ) + } + + func testQueryOverrideMany() { + try XCTAssertEqual( + HTTPRequestBuilder(url: URL(string: "https://wordpress.org")!) + .query(name: "foo", value: "bar", override: true) + .build() + .url? + .absoluteString, + "https://wordpress.org?foo=bar" + ) + + try XCTAssertEqual( + HTTPRequestBuilder(url: URL(string: "https://wordpress.org")!) + .query(name: "foo", value: "bar", override: true) + .append(query: [URLQueryItem(name: "foo", value: "hello")], override: true) + .build() + .url? + .absoluteString, + "https://wordpress.org?foo=hello" + ) + + try XCTAssertEqual( + HTTPRequestBuilder(url: URL(string: "https://wordpress.org")!) + .query(name: "foo", value: "bar", override: true) + .append(query: [URLQueryItem(name: "foo", value: "hello")], override: false) + .build() + .url? + .absoluteString, + "https://wordpress.org?foo=bar&foo=hello" + ) + + try XCTAssertEqual( + HTTPRequestBuilder(url: URL(string: "https://wordpress.org")!) + .query(name: "foo", value: "bar") + .append(query: [URLQueryItem(name: "foo", value: "hello")]) + .build() + .url? + .absoluteString, + "https://wordpress.org?foo=bar&foo=hello" + ) + } + + @available(iOS 16.0, *) + func testSetQueryWithDictionary() throws { + let query = try HTTPRequestBuilder(url: URL(string: "https://wordpress.org")!) + .query(HTTPRequestBuilderTests.nestedParameters) + .build() + .url? + .query(percentEncoded: false)? + .split(separator: "&") + .reduce(into: Set()) { $0.insert(String($1)) } + ?? [] + + XCTAssertEqual(query.count, HTTPRequestBuilderTests.nestedParametersEncoded.count) + + for item in HTTPRequestBuilderTests.nestedParametersEncoded { + XCTAssertTrue(query.contains(item), "Missing query item: \(item)") + } + } + + func testDefaultQuery() throws { + let builder = HTTPRequestBuilder(url: URL(string: "https://wordpress.org")!) + .query(defaults: [URLQueryItem(name: "locale", value: "en")]) + + try XCTAssertEqual(builder.build().url?.query, "locale=en") + try XCTAssertEqual(builder.query(name: "locale", value: "zh").build().url?.query, "locale=zh") + try XCTAssertEqual(builder.query(name: "foo", value: "bar").build().url?.query, "locale=zh&foo=bar") + } + + func testDefaultQueryDoesNotOverriedQueryItemInOriginalURL() throws { + let url = try HTTPRequestBuilder(url: URL(string: "https://wordpress.org/hello?locale=foo")!) + .query(defaults: [URLQueryItem(name: "locale", value: "en")]) + .build() + .url + + XCTAssertEqual(url?.query, "locale=foo") + } + + func testJSONBody() throws { + var request = try HTTPRequestBuilder(url: URL(string: "https://wordpress.org")!) + .method(.post) + .body(json: 42) + .build() + XCTAssertTrue(request.value(forHTTPHeaderField: "Content-Type")?.contains("application/json") == true) + try XCTAssertEqual(XCTUnwrap(request.httpBodyText), "42") + + request = try HTTPRequestBuilder(url: URL(string: "https://wordpress.org")!) + .method(.post) + .body(json: ["foo": "bar"]) + .build() + try XCTAssertEqual(XCTUnwrap(request.httpBodyText), #"{"foo":"bar"}"#) + } + + func testJSONBodyWithEncodable() throws { + struct Body: Encodable { + var foo: String + } + let body = Body(foo: "bar") + + let request = try HTTPRequestBuilder(url: URL(string: "https://wordpress.org")!) + .method(.post) + .body(json: body) + .build() + XCTAssertTrue(request.value(forHTTPHeaderField: "Content-Type")?.contains("application/json") == true) + try XCTAssertEqual(XCTUnwrap(request.httpBodyText), #"{"foo":"bar"}"#) + } + + func testFormBody() throws { + let request = try HTTPRequestBuilder(url: URL(string: "https://wordpress.org")!) + .method(.post) + .body(form: ["name": "Foo Bar"]) + .build() + XCTAssertTrue(request.value(forHTTPHeaderField: "Content-Type")?.contains("application/x-www-form-urlencoded") == true) + try XCTAssertEqual(XCTUnwrap(request.httpBodyText), #"name=Foo%20Bar"#) + } + + func testFormWithSpecialCharacters() throws { + let request = try HTTPRequestBuilder(url: URL(string: "https://wordpress.org")!) + .method(.post) + .body(form: ["text": ":#[]@!$&'()*+,;="]) + .build() + try XCTAssertEqual(XCTUnwrap(request.httpBodyText), "text=%3A%23%5B%5D%40%21%24%26%27%28%29%2A%2B%2C%3B%3D") + } + + func testFormWithRandomSpecialCharacters() throws { + let asciis = "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~" + let unicodes = "่กจๆƒ…็ฌฆๅท๐Ÿ˜‡๐Ÿค”โœ…๐Ÿ˜Ž" + let randomText: () -> String = { + let chars = (1...10).map { _ in (asciis + unicodes).randomElement()! } + return String(chars) + } + // Generate a form (key-value pairs) with random characters. + let form = [ + randomText(): randomText(), + randomText(): randomText(), + randomText(): randomText(), + ] + + let request = try HTTPRequestBuilder(url: URL(string: "https://wordpress.org")!) + .method(.post) + .body(form: form) + .build() + let encoded = try XCTUnwrap(request.httpBodyText) + + // Decoding the url-encoded form, whose format should be "=&=&...". + let keyValuePairs = try encoded.split(separator: "&").map { pair in + XCTAssertEqual(pair.firstIndex(of: "="), pair.lastIndex(of: "="), "There should be only one '=' in a key-value pair") + + let firstIndex = try XCTUnwrap(pair.firstIndex(of: "=")) + let key = pair[pair.startIndex.." + XCTAssertTrue(xmlrpcContent.contains(filePart)) + } + +} diff --git a/Tests/WordPressKitTests/CoreAPITests/MultipartFormTests.swift b/Tests/WordPressKitTests/CoreAPITests/MultipartFormTests.swift new file mode 100644 index 000000000000..88d786d914a0 --- /dev/null +++ b/Tests/WordPressKitTests/CoreAPITests/MultipartFormTests.swift @@ -0,0 +1,152 @@ +import Foundation +import XCTest +import CryptoKit +#if SWIFT_PACKAGE +@testable import CoreAPI +#else +@testable import WordPressKit +#endif + +class MutliparFormDataTests: XCTestCase { + struct Form: Codable { + struct Field: Codable { + var name: String + var content: String + } + + var fields: [Field] + + static func random(numberOfFields: Int = 10) -> Form { + let randomText: () -> String = { String(format: "%08x", Int.random(in: Int.min...Int.max)) } + let fields = (1...numberOfFields).map { _ in + Field(name: randomText(), content: randomText()) + } + return Form(fields: fields) + } + + func formData() throws -> Data { + try fields + .map { + MultipartFormField(text: $0.content, name: $0.name) + } + .multipartFormDataStream(boundary: "testboundary") + .readToEnd() + } + } + + func testRandomForm() throws { + let tempDir = FileManager.default.temporaryDirectory + let testData = tempDir.appendingPathComponent("test-form.json") + let wpOutput = tempDir.appendingPathComponent("test-form.wp.txt") + + let form = Form(fields: [ + .init(name: "key-1", content: "a"), + .init(name: "key-2", content: "b"), + ]) + try JSONEncoder().encode(form).write(to: testData) + + let encoded = try form.formData() + try encoded.write(to: wpOutput) + + add(XCTAttachment(contentsOfFile: testData)) + add(XCTAttachment(contentsOfFile: wpOutput)) + + let expected = "--testboundary\r\nContent-Disposition: form-data; name=\"key-1\"\r\n\r\na\r\n--testboundary\r\nContent-Disposition: form-data; name=\"key-2\"\r\n\r\nb\r\n--testboundary--\r\n".data(using: .utf8) + XCTAssertEqual(expected, encoded) + } + + func testPlainText() throws { + let fields = [ + MultipartFormField(text: "hello", name: "world"), + MultipartFormField(text: "foo", name: "bar"), + MultipartFormField(text: "the", name: "end"), + ] + let encoded = try fields.multipartFormDataStream(boundary: "wpkit.boundary.9d4adfc909a08bfa").readToEnd() + let expected = "--wpkit.boundary.9d4adfc909a08bfa\r\nContent-Disposition: form-data; name=\"world\"\r\n\r\nhello\r\n--wpkit.boundary.9d4adfc909a08bfa\r\nContent-Disposition: form-data; name=\"bar\"\r\n\r\nfoo\r\n--wpkit.boundary.9d4adfc909a08bfa\r\nContent-Disposition: form-data; name=\"end\"\r\n\r\nthe\r\n--wpkit.boundary.9d4adfc909a08bfa--\r\n".data(using: .utf8) + XCTAssertEqual(expected, encoded) + } + + func testEmptyForm() throws { + let formData = try [].multipartFormDataStream(boundary: "test").readToEnd() + XCTAssertTrue(formData.isEmpty) + } + + func testOneField() throws { + let formData = try [MultipartFormField(text: "hello", name: "world")] + .multipartFormDataStream(boundary: "wpkit.boundary.9d4adfc909a08bfa") + .readToEnd() + + let expected = "--wpkit.boundary.9d4adfc909a08bfa\r\nContent-Disposition: form-data; name=\"world\"\r\n\r\nhello\r\n--wpkit.boundary.9d4adfc909a08bfa--\r\n".data(using: .utf8)! + XCTAssertEqual(expected, formData) + } + + func testUploadSmallFile() throws { + let tempDir = FileManager.default.temporaryDirectory + let fileContent = Data(repeating: Character("a").asciiValue!, count: 20_000_000) + let filePath = tempDir.appendingPathComponent("file.png") + let resultPath = tempDir.appendingPathComponent("result.txt") + try fileContent.write(to: filePath) + defer { + try? FileManager.default.removeItem(at: filePath) + try? FileManager.default.removeItem(at: resultPath) + } + + let fields = [ + MultipartFormField(text: "123456", name: "site"), + try MultipartFormField(fileAtPath: filePath.path, name: "media", filename: "file.png", mimeType: "image/png"), + ] + let formData = try fields.multipartFormDataStream(boundary: "testboundary").readToEnd() + try formData.write(to: resultPath) + + // Reminder: Check the multipart form file before updating this assertion + XCTAssertTrue(SHA256.hash(data: formData).description.contains("8c985fdc03e75389b85a74996504aa10e0b054b1b5f771bd1ba0db155281bb53")) + } + + func testUploadLargeFile() throws { + let tempDir = FileManager.default.temporaryDirectory + let fileContent = Data(repeating: Character("a").asciiValue!, count: 50_000_000) + let filePath = tempDir.appendingPathComponent("file.png") + let resultPath = tempDir.appendingPathComponent("result.txt") + try fileContent.write(to: filePath) + defer { + try? FileManager.default.removeItem(at: filePath) + try? FileManager.default.removeItem(at: resultPath) + } + + let fields = [ + MultipartFormField(text: "123456", name: "site"), + try MultipartFormField(fileAtPath: filePath.path, name: "media", filename: "file.png", mimeType: "image/png"), + ] + let formData = try fields.multipartFormDataStream(boundary: "testboundary").readToEnd() + try formData.write(to: resultPath) + + // Reminder: Check the multipart form file before updating this assertion + XCTAssertTrue(SHA256.hash(data: formData).description.contains("2cedb35673a6982453a6e8e5ca901feabf92250630cdfabb961a03467f28bc8e")) + } + +} + +extension Either { + func readToEnd() -> Data { + map( + left: { $0 }, + right: { InputStream(url: $0)?.readToEnd() ?? Data() } + ) + } +} + +extension InputStream { + func readToEnd() -> Data { + open() + defer { close() } + + var data = Data() + let maxLength = 1024 + var buffer = [UInt8](repeating: 0, count: maxLength) + while hasBytesAvailable { + let bytes = read(&buffer, maxLength: maxLength) + data.append(buffer, count: bytes) + } + return data + } +} diff --git a/Tests/WordPressKitTests/CoreAPITests/NonceRetrievalTests.swift b/Tests/WordPressKitTests/CoreAPITests/NonceRetrievalTests.swift new file mode 100644 index 000000000000..eb0f54a9db3e --- /dev/null +++ b/Tests/WordPressKitTests/CoreAPITests/NonceRetrievalTests.swift @@ -0,0 +1,74 @@ +import Foundation +import XCTest +import OHHTTPStubs +import OHHTTPStubsSwift + +@testable import WordPressKit + +class NonceRetrievalTests: XCTestCase { + + static let nonce = "leg1tn0nce" + static let siteURL = URL(string: "https://test.com")! + static let siteLoginURL = URL(string: "https://test.com/wp-login.php")! + static let siteAdminURL = URL(string: "https://test.com/wp-admin/")! + static let newPostURL = URL(string: "https://test.com/wp-admin/post-new.php")! + static let ajaxURL = URL(string: "https://test.com/wp-admin/admin-ajax.php?action=rest-nonce")! + + override func tearDown() { + super.tearDown() + HTTPStubs.removeAllStubs() + } + + func testUsingNewPostPage() async { + stubLoginRedirect(dest: Self.newPostURL) + stubNewPostPage(statusCode: 200) + + let nonce = await NonceRetrievalMethod.newPostScrap.retrieveNonce( + username: "test", + password: .init("pass"), + loginURL: Self.siteLoginURL, + adminURL: Self.siteAdminURL, + using: URLSession(configuration: .ephemeral) + ) + XCTAssertEqual(nonce, Self.nonce) + } + + func testUsingRESTNonceAjax() async { + stubLoginRedirect(dest: Self.ajaxURL) + stubAjax(statusCode: 200) + + let nonce = await NonceRetrievalMethod.ajaxNonceRequest.retrieveNonce( + username: "test", + password: .init("pass"), + loginURL: Self.siteLoginURL, + adminURL: Self.siteAdminURL, + using: URLSession(configuration: .ephemeral) + ) + XCTAssertEqual(nonce, Self.nonce) + } + + private func stubLoginRedirect(dest: URL) { + stub(condition: isAbsoluteURLString(Self.siteLoginURL.absoluteString)) { _ in + HTTPStubsResponse(data: Data(), statusCode: 302, headers: ["Location": dest.absoluteString]) + } + } + + private func stubNewPostPage(nonceScript: String? = nil, statusCode: Int32) { + let script = nonceScript ?? """ + wp.apiFetch.nonceMiddleware = wp.apiFetch.createNonceMiddleware( "\(Self.nonce)" ); + wp.apiFetch.use( wp.apiFetch.nonceMiddleware ); + wp.apiFetch.use( wp.apiFetch.mediaUploadMiddleware ); + """ + let html = "\n\(script)\n" + stub(condition: isAbsoluteURLString(Self.newPostURL.absoluteString)) { _ in + HTTPStubsResponse(data: html.data(using: .utf8)!, statusCode: statusCode, headers: nil) + } + } + + private func stubAjax(statusCode: Int32) { + stub(condition: isAbsoluteURLString(Self.ajaxURL.absoluteString)) { _ in + HTTPStubsResponse(data: (statusCode == 200 ? Self.nonce : "...").data(using: .utf8)!, statusCode: statusCode, headers: nil) + } + } + +} diff --git a/Tests/WordPressKitTests/CoreAPITests/RSDParserTests.swift b/Tests/WordPressKitTests/CoreAPITests/RSDParserTests.swift new file mode 100644 index 000000000000..700c283ac3e5 --- /dev/null +++ b/Tests/WordPressKitTests/CoreAPITests/RSDParserTests.swift @@ -0,0 +1,50 @@ +import Foundation +import XCTest +#if SWIFT_PACKAGE +@testable import CoreAPI +#else +@testable import WordPressKit +#endif + +class RSDParserTests: XCTestCase { + + func testSuccess() throws { + // Grabbed from https://developer.wordpress.org/xmlrpc.php?rsd + let xml = """ + + + WordPress + https://wordpress.org/ + https://developer.wordpress.org + + + + + + + + + + """ + let parser = try XCTUnwrap(WordPressRSDParser(xmlString: xml)) + try XCTAssertEqual(parser.parsedEndpoint(), "https://developer.wordpress.org/xmlrpc.php") + } + + func testWordPressEntryOnly() throws { + // Grabbed from https://developer.wordpress.org/xmlrpc.php?rsd, but removing all other api links. + let xml = """ + + + WordPress + https://wordpress.org/ + https://developer.wordpress.org + + + + + """ + let parser = try XCTUnwrap(WordPressRSDParser(xmlString: xml)) + try XCTAssertEqual(parser.parsedEndpoint(), "https://developer.wordpress.org/xmlrpc.php") + } + +} diff --git a/Tests/WordPressKitTests/CoreAPITests/Stubs/HTML/xmlrpc-response-invalid.html b/Tests/WordPressKitTests/CoreAPITests/Stubs/HTML/xmlrpc-response-invalid.html new file mode 100644 index 000000000000..6c25e2cac0c6 --- /dev/null +++ b/Tests/WordPressKitTests/CoreAPITests/Stubs/HTML/xmlrpc-response-invalid.html @@ -0,0 +1,7 @@ + + + + website + + ๐Ÿ‘‹ + diff --git a/Tests/WordPressKitTests/CoreAPITests/Stubs/HTML/xmlrpc-response-mobile-plugin-redirect.html b/Tests/WordPressKitTests/CoreAPITests/Stubs/HTML/xmlrpc-response-mobile-plugin-redirect.html new file mode 100644 index 000000000000..fc8f70eaf3af --- /dev/null +++ b/Tests/WordPressKitTests/CoreAPITests/Stubs/HTML/xmlrpc-response-mobile-plugin-redirect.html @@ -0,0 +1,8 @@ + + + + + website + + ๐Ÿ‘‹ + diff --git a/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/WordPressComAuthenticateWithIDToken2FANeededSuccess.json b/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/WordPressComAuthenticateWithIDToken2FANeededSuccess.json new file mode 100644 index 000000000000..366fa8835ceb --- /dev/null +++ b/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/WordPressComAuthenticateWithIDToken2FANeededSuccess.json @@ -0,0 +1,13 @@ +{ + "data": { + "two_step_nonce_authenticator": "two_step_nonce_authenticator", + "two_step_nonce": "two_step_nonce", + "two_step_nonce_sms": "two_step_nonce_sms", + "two_step_nonce_backup": "two_step_nonce_backup", + "two_step_nonce_webauthn": "two_step_nonce_webauthn", + "two_step_notification_sent": "two_step_notification_sent", + "two_step_supported_auth_types": "two_step_supported_auth_types", + "phone_number": "phone_number", + "user_id": 1 + } +} diff --git a/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/WordPressComAuthenticateWithIDTokenBearerTokenSuccess.json b/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/WordPressComAuthenticateWithIDTokenBearerTokenSuccess.json new file mode 100644 index 000000000000..fe81d177cb99 --- /dev/null +++ b/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/WordPressComAuthenticateWithIDTokenBearerTokenSuccess.json @@ -0,0 +1,13 @@ +{ + "data": { + "two_step_nonce_authenticator": "two_step_nonce_authenticator", + "two_step_nonce": "two_step_nonce", + "two_step_nonce_sms": "two_step_nonce_sms", + "two_step_nonce_backup": "two_step_nonce_backup", + "two_step_notification_sent": "two_step_notification_sent", + "two_step_supported_auth_types": "two_step_supported_auth_types", + "phone_number": "phone_number", + "bearer_token": "bearer_token", + "user_id": "user_id" + } +} diff --git a/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/WordPressComAuthenticateWithIDTokenExistingUserNeedsConnection.json b/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/WordPressComAuthenticateWithIDTokenExistingUserNeedsConnection.json new file mode 100644 index 000000000000..1a8f6f3a3e85 --- /dev/null +++ b/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/WordPressComAuthenticateWithIDTokenExistingUserNeedsConnection.json @@ -0,0 +1,10 @@ +{ + "data": { + "errors": [{ + "code":"user_exists", + "message":"User exists but not connected" + }], + "two_step_nonce": "two_step_nonce", + "email": "email", + } +} diff --git a/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/WordPressComOAuthAuthenticateSignature.json b/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/WordPressComOAuthAuthenticateSignature.json new file mode 100644 index 000000000000..8307598d7d66 --- /dev/null +++ b/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/WordPressComOAuthAuthenticateSignature.json @@ -0,0 +1,12 @@ +{ + "success": true, + "data": { + "bearer_token": "bearer_token", + "token_links": [ + "https:\/\/jetpack.com\/remote-login.php?wpcom_rem...", + "https:\/\/fieldguide.automattic.com\/remote-logi...", + "https:\/\/learn.a8c.com\/remote-login.php?wpcom_remote_login...", + "https:\/\/a8c.tv\/remote-login.php?wpcom_remote_login=vali..." + ] + } +} diff --git a/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/WordPressComOAuthNeeds2FAFail.json b/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/WordPressComOAuthNeeds2FAFail.json new file mode 100644 index 000000000000..6bfbf7d62cc9 --- /dev/null +++ b/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/WordPressComOAuthNeeds2FAFail.json @@ -0,0 +1,4 @@ +{ + "error_description": "Please enter the verification code generated by your Authenticator mobile application.", + "error": "needs_2fa" +} diff --git a/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/WordPressComOAuthNeedsWebauthnMFA.json b/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/WordPressComOAuthNeedsWebauthnMFA.json new file mode 100644 index 000000000000..d68917b34253 --- /dev/null +++ b/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/WordPressComOAuthNeedsWebauthnMFA.json @@ -0,0 +1,7 @@ +{ + "success": true, + "data": { + "user_id": 1234, + "two_step_nonce_webauthn": "two_step_nonce_webauthn", + } +} diff --git a/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/WordPressComOAuthRequestChallenge.json b/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/WordPressComOAuthRequestChallenge.json new file mode 100644 index 000000000000..0442814a0ae1 --- /dev/null +++ b/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/WordPressComOAuthRequestChallenge.json @@ -0,0 +1,33 @@ +{ + "success": true, + "data": { + "challenge": "challenge", + "rpId": "wordpress.com", + "allowCredentials": [ + { + "type": "public-key", + "id": "credential-id", + "transports": [ + "usb", + "nfc", + "ble", + "hybrid", + "internal" + ] + }, + { + "type": "public-key", + "id": "credential-id-2", + "transports": [ + "usb", + "nfc", + "ble", + "hybrid", + "internal" + ] + } + ], + "timeout": 60000, + "two_step_nonce": "two_step_nonce" + } +} diff --git a/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/WordPressComOAuthSuccess.json b/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/WordPressComOAuthSuccess.json new file mode 100644 index 000000000000..5bc5c0a69b0c --- /dev/null +++ b/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/WordPressComOAuthSuccess.json @@ -0,0 +1,7 @@ +{ + "blog_id": 0, + "scope": "global", + "blog_url": null, + "token_type": "bearer", + "access_token": "fakeToken" +} diff --git a/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/WordPressComOAuthWrongPasswordFail.json b/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/WordPressComOAuthWrongPasswordFail.json new file mode 100644 index 000000000000..b6f3795019ff --- /dev/null +++ b/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/WordPressComOAuthWrongPasswordFail.json @@ -0,0 +1,4 @@ +{ + "error_description": "Incorrect username or password.", + "error": "invalid_request" +} diff --git a/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/WordPressComRestApiFailInvalidInput.json b/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/WordPressComRestApiFailInvalidInput.json new file mode 100644 index 000000000000..473d09f4429b --- /dev/null +++ b/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/WordPressComRestApiFailInvalidInput.json @@ -0,0 +1,4 @@ +{ + "message": "No media provided in input.", + "error": "invalid_input" +} \ No newline at end of file diff --git a/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/WordPressComRestApiFailInvalidJSON.json b/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/WordPressComRestApiFailInvalidJSON.json new file mode 100644 index 000000000000..9b4c74354223 --- /dev/null +++ b/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/WordPressComRestApiFailInvalidJSON.json @@ -0,0 +1,4 @@ + + "message": "The OAuth2 token is invalid.", + "error": "invalid_token" +} \ No newline at end of file diff --git a/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/WordPressComRestApiFailReauthenticationRequired.json b/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/WordPressComRestApiFailReauthenticationRequired.json new file mode 100644 index 000000000000..63be1a7c88e8 --- /dev/null +++ b/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/WordPressComRestApiFailReauthenticationRequired.json @@ -0,0 +1,4 @@ +{ + "message": "A fresh access token must be used to query information about the current user.", + "error": "reauthorization_required" +} diff --git a/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/WordPressComRestApiFailRequestInvalidToken.json b/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/WordPressComRestApiFailRequestInvalidToken.json new file mode 100644 index 000000000000..4d2dc0d0b16e --- /dev/null +++ b/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/WordPressComRestApiFailRequestInvalidToken.json @@ -0,0 +1,4 @@ +{ + "message": "The OAuth2 token is invalid.", + "error": "invalid_token" +} \ No newline at end of file diff --git a/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/WordPressComRestApiFailThrottled.json b/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/WordPressComRestApiFailThrottled.json new file mode 100644 index 000000000000..89bd200ebf60 --- /dev/null +++ b/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/WordPressComRestApiFailThrottled.json @@ -0,0 +1,3 @@ + +Limit reached + \ No newline at end of file diff --git a/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/WordPressComRestApiFailUnauthorized.json b/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/WordPressComRestApiFailUnauthorized.json new file mode 100644 index 000000000000..0dd0fc043f69 --- /dev/null +++ b/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/WordPressComRestApiFailUnauthorized.json @@ -0,0 +1,4 @@ +{ + "message": "User cannot upload media.", + "error": "unauthorized" +} \ No newline at end of file diff --git a/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/WordPressComRestApiMedia.json b/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/WordPressComRestApiMedia.json new file mode 100644 index 000000000000..e2991dd69884 --- /dev/null +++ b/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/WordPressComRestApiMedia.json @@ -0,0 +1,868 @@ +{ + "found": 429, + "media": [ + { + "ID": 969, + "URL": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb12.png", + "guid": "http:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb12.png", + "date": "2015-04-19T16:51:24+00:00", + "post_ID": 0, + "author_ID": 0, + "file": "codeispoetry-rgb12.png", + "mime_type": "image\/png", + "extension": "png", + "title": "codeispoetry-rgb12", + "caption": "", + "description": "", + "alt": "", + "thumbnails": { + "thumbnail": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb12.png?w=150", + "medium": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb12.png?w=300", + "large": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb12.png?w=500", + "post-thumbnail": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb12.png?w=500&h=34&crop=1" + }, + "height": 34, + "width": 500, + "exif": { + "aperture": 0, + "credit": "", + "camera": "", + "caption": "", + "created_timestamp": 0, + "copyright": "", + "focal_length": 0, + "iso": 0, + "shutter_speed": 0, + "title": "", + "orientation": 0 + }, + "meta": { + "links": { + "self": "https:\/\/public-api.wordpress.com\/rest\/v1.1\/sites\/82974409\/media\/969", + "help": "https:\/\/public-api.wordpress.com\/rest\/v1.1\/sites\/82974409\/media\/969\/help", + "site": "https:\/\/public-api.wordpress.com\/rest\/v1.1\/sites\/82974409" + } + } + }, + { + "ID": 967, + "URL": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb11.png", + "guid": "http:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb11.png", + "date": "2015-04-19T16:51:22+00:00", + "post_ID": 0, + "author_ID": 0, + "file": "codeispoetry-rgb11.png", + "mime_type": "image\/png", + "extension": "png", + "title": "codeispoetry-rgb11", + "caption": "", + "description": "", + "alt": "", + "thumbnails": { + "thumbnail": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb11.png?w=150", + "medium": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb11.png?w=300", + "large": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb11.png?w=500", + "post-thumbnail": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb11.png?w=500&h=34&crop=1" + }, + "height": 34, + "width": 500, + "exif": { + "aperture": 0, + "credit": "", + "camera": "", + "caption": "", + "created_timestamp": 0, + "copyright": "", + "focal_length": 0, + "iso": 0, + "shutter_speed": 0, + "title": "", + "orientation": 0 + }, + "meta": { + "links": { + "self": "https:\/\/public-api.wordpress.com\/rest\/v1.1\/sites\/82974409\/media\/967", + "help": "https:\/\/public-api.wordpress.com\/rest\/v1.1\/sites\/82974409\/media\/967\/help", + "site": "https:\/\/public-api.wordpress.com\/rest\/v1.1\/sites\/82974409" + } + } + }, + { + "ID": 965, + "URL": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb10.png", + "guid": "http:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb10.png", + "date": "2015-04-19T16:50:20+00:00", + "post_ID": 0, + "author_ID": 0, + "file": "codeispoetry-rgb10.png", + "mime_type": "image\/png", + "extension": "png", + "title": "codeispoetry-rgb10", + "caption": "", + "description": "", + "alt": "", + "thumbnails": { + "thumbnail": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb10.png?w=150", + "medium": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb10.png?w=300", + "large": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb10.png?w=500", + "post-thumbnail": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb10.png?w=500&h=34&crop=1" + }, + "height": 34, + "width": 500, + "exif": { + "aperture": 0, + "credit": "", + "camera": "", + "caption": "", + "created_timestamp": 0, + "copyright": "", + "focal_length": 0, + "iso": 0, + "shutter_speed": 0, + "title": "", + "orientation": 0 + }, + "meta": { + "links": { + "self": "https:\/\/public-api.wordpress.com\/rest\/v1.1\/sites\/82974409\/media\/965", + "help": "https:\/\/public-api.wordpress.com\/rest\/v1.1\/sites\/82974409\/media\/965\/help", + "site": "https:\/\/public-api.wordpress.com\/rest\/v1.1\/sites\/82974409" + } + } + }, + { + "ID": 963, + "URL": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb9.png", + "guid": "http:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb9.png", + "date": "2015-04-19T16:50:18+00:00", + "post_ID": 0, + "author_ID": 0, + "file": "codeispoetry-rgb9.png", + "mime_type": "image\/png", + "extension": "png", + "title": "codeispoetry-rgb9", + "caption": "", + "description": "", + "alt": "", + "thumbnails": { + "thumbnail": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb9.png?w=150", + "medium": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb9.png?w=300", + "large": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb9.png?w=500", + "post-thumbnail": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb9.png?w=500&h=34&crop=1" + }, + "height": 34, + "width": 500, + "exif": { + "aperture": 0, + "credit": "", + "camera": "", + "caption": "", + "created_timestamp": 0, + "copyright": "", + "focal_length": 0, + "iso": 0, + "shutter_speed": 0, + "title": "", + "orientation": 0 + }, + "meta": { + "links": { + "self": "https:\/\/public-api.wordpress.com\/rest\/v1.1\/sites\/82974409\/media\/963", + "help": "https:\/\/public-api.wordpress.com\/rest\/v1.1\/sites\/82974409\/media\/963\/help", + "site": "https:\/\/public-api.wordpress.com\/rest\/v1.1\/sites\/82974409" + } + } + }, + { + "ID": 961, + "URL": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb8.png", + "guid": "http:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb8.png", + "date": "2015-04-19T16:49:27+00:00", + "post_ID": 0, + "author_ID": 0, + "file": "codeispoetry-rgb8.png", + "mime_type": "image\/png", + "extension": "png", + "title": "codeispoetry-rgb8", + "caption": "", + "description": "", + "alt": "", + "thumbnails": { + "thumbnail": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb8.png?w=150", + "medium": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb8.png?w=300", + "large": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb8.png?w=500", + "post-thumbnail": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb8.png?w=500&h=34&crop=1" + }, + "height": 34, + "width": 500, + "exif": { + "aperture": 0, + "credit": "", + "camera": "", + "caption": "", + "created_timestamp": 0, + "copyright": "", + "focal_length": 0, + "iso": 0, + "shutter_speed": 0, + "title": "", + "orientation": 0 + }, + "meta": { + "links": { + "self": "https:\/\/public-api.wordpress.com\/rest\/v1.1\/sites\/82974409\/media\/961", + "help": "https:\/\/public-api.wordpress.com\/rest\/v1.1\/sites\/82974409\/media\/961\/help", + "site": "https:\/\/public-api.wordpress.com\/rest\/v1.1\/sites\/82974409" + } + } + }, + { + "ID": 959, + "URL": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb7.png", + "guid": "http:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb7.png", + "date": "2015-04-19T16:49:25+00:00", + "post_ID": 0, + "author_ID": 0, + "file": "codeispoetry-rgb7.png", + "mime_type": "image\/png", + "extension": "png", + "title": "codeispoetry-rgb7", + "caption": "", + "description": "", + "alt": "", + "thumbnails": { + "thumbnail": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb7.png?w=150", + "medium": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb7.png?w=300", + "large": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb7.png?w=500", + "post-thumbnail": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb7.png?w=500&h=34&crop=1" + }, + "height": 34, + "width": 500, + "exif": { + "aperture": 0, + "credit": "", + "camera": "", + "caption": "", + "created_timestamp": 0, + "copyright": "", + "focal_length": 0, + "iso": 0, + "shutter_speed": 0, + "title": "", + "orientation": 0 + }, + "meta": { + "links": { + "self": "https:\/\/public-api.wordpress.com\/rest\/v1.1\/sites\/82974409\/media\/959", + "help": "https:\/\/public-api.wordpress.com\/rest\/v1.1\/sites\/82974409\/media\/959\/help", + "site": "https:\/\/public-api.wordpress.com\/rest\/v1.1\/sites\/82974409" + } + } + }, + { + "ID": 957, + "URL": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb6.png", + "guid": "http:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb6.png", + "date": "2015-04-19T16:48:47+00:00", + "post_ID": 0, + "author_ID": 0, + "file": "codeispoetry-rgb6.png", + "mime_type": "image\/png", + "extension": "png", + "title": "codeispoetry-rgb6", + "caption": "", + "description": "", + "alt": "", + "thumbnails": { + "thumbnail": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb6.png?w=150", + "medium": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb6.png?w=300", + "large": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb6.png?w=500", + "post-thumbnail": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb6.png?w=500&h=34&crop=1" + }, + "height": 34, + "width": 500, + "exif": { + "aperture": 0, + "credit": "", + "camera": "", + "caption": "", + "created_timestamp": 0, + "copyright": "", + "focal_length": 0, + "iso": 0, + "shutter_speed": 0, + "title": "", + "orientation": 0 + }, + "meta": { + "links": { + "self": "https:\/\/public-api.wordpress.com\/rest\/v1.1\/sites\/82974409\/media\/957", + "help": "https:\/\/public-api.wordpress.com\/rest\/v1.1\/sites\/82974409\/media\/957\/help", + "site": "https:\/\/public-api.wordpress.com\/rest\/v1.1\/sites\/82974409" + } + } + }, + { + "ID": 956, + "URL": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb15.png", + "guid": "http:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb15.png", + "date": "2015-04-19T16:48:46+00:00", + "post_ID": 0, + "author_ID": 78972699, + "file": "codeispoetry-rgb15.png", + "mime_type": "image\/png", + "extension": "png", + "title": "codeispoetry-rgb15", + "caption": "", + "description": "", + "alt": "", + "thumbnails": { + "thumbnail": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb15.png?w=150", + "medium": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb15.png?w=300", + "large": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb15.png?w=500", + "post-thumbnail": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb15.png?w=500&h=34&crop=1" + }, + "height": 34, + "width": 500, + "exif": { + "aperture": 0, + "credit": "", + "camera": "", + "caption": "", + "created_timestamp": 0, + "copyright": "", + "focal_length": 0, + "iso": 0, + "shutter_speed": 0, + "title": "", + "orientation": 0 + }, + "meta": { + "links": { + "self": "https:\/\/public-api.wordpress.com\/rest\/v1.1\/sites\/82974409\/media\/956", + "help": "https:\/\/public-api.wordpress.com\/rest\/v1.1\/sites\/82974409\/media\/956\/help", + "site": "https:\/\/public-api.wordpress.com\/rest\/v1.1\/sites\/82974409" + } + } + }, + { + "ID": 955, + "URL": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb5.png", + "guid": "http:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb5.png", + "date": "2015-04-19T16:48:45+00:00", + "post_ID": 0, + "author_ID": 0, + "file": "codeispoetry-rgb5.png", + "mime_type": "image\/png", + "extension": "png", + "title": "codeispoetry-rgb5", + "caption": "", + "description": "", + "alt": "", + "thumbnails": { + "thumbnail": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb5.png?w=150", + "medium": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb5.png?w=300", + "large": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb5.png?w=500", + "post-thumbnail": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb5.png?w=500&h=34&crop=1" + }, + "height": 34, + "width": 500, + "exif": { + "aperture": 0, + "credit": "", + "camera": "", + "caption": "", + "created_timestamp": 0, + "copyright": "", + "focal_length": 0, + "iso": 0, + "shutter_speed": 0, + "title": "", + "orientation": 0 + }, + "meta": { + "links": { + "self": "https:\/\/public-api.wordpress.com\/rest\/v1.1\/sites\/82974409\/media\/955", + "help": "https:\/\/public-api.wordpress.com\/rest\/v1.1\/sites\/82974409\/media\/955\/help", + "site": "https:\/\/public-api.wordpress.com\/rest\/v1.1\/sites\/82974409" + } + } + }, + { + "ID": 953, + "URL": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb4.png", + "guid": "http:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb4.png", + "date": "2015-04-19T16:47:36+00:00", + "post_ID": 0, + "author_ID": 0, + "file": "codeispoetry-rgb4.png", + "mime_type": "image\/png", + "extension": "png", + "title": "codeispoetry-rgb4", + "caption": "", + "description": "", + "alt": "", + "thumbnails": { + "thumbnail": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb4.png?w=150", + "medium": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb4.png?w=300", + "large": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb4.png?w=500", + "post-thumbnail": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb4.png?w=500&h=34&crop=1" + }, + "height": 34, + "width": 500, + "exif": { + "aperture": 0, + "credit": "", + "camera": "", + "caption": "", + "created_timestamp": 0, + "copyright": "", + "focal_length": 0, + "iso": 0, + "shutter_speed": 0, + "title": "", + "orientation": 0 + }, + "meta": { + "links": { + "self": "https:\/\/public-api.wordpress.com\/rest\/v1.1\/sites\/82974409\/media\/953", + "help": "https:\/\/public-api.wordpress.com\/rest\/v1.1\/sites\/82974409\/media\/953\/help", + "site": "https:\/\/public-api.wordpress.com\/rest\/v1.1\/sites\/82974409" + } + } + }, + { + "ID": 952, + "URL": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb13.png", + "guid": "http:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb13.png", + "date": "2015-04-19T16:47:35+00:00", + "post_ID": 0, + "author_ID": 78972699, + "file": "codeispoetry-rgb13.png", + "mime_type": "image\/png", + "extension": "png", + "title": "codeispoetry-rgb13", + "caption": "", + "description": "", + "alt": "", + "thumbnails": { + "thumbnail": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb13.png?w=150", + "medium": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb13.png?w=300", + "large": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb13.png?w=500", + "post-thumbnail": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb13.png?w=500&h=34&crop=1" + }, + "height": 34, + "width": 500, + "exif": { + "aperture": 0, + "credit": "", + "camera": "", + "caption": "", + "created_timestamp": 0, + "copyright": "", + "focal_length": 0, + "iso": 0, + "shutter_speed": 0, + "title": "", + "orientation": 0 + }, + "meta": { + "links": { + "self": "https:\/\/public-api.wordpress.com\/rest\/v1.1\/sites\/82974409\/media\/952", + "help": "https:\/\/public-api.wordpress.com\/rest\/v1.1\/sites\/82974409\/media\/952\/help", + "site": "https:\/\/public-api.wordpress.com\/rest\/v1.1\/sites\/82974409" + } + } + }, + { + "ID": 951, + "URL": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb3.png", + "guid": "http:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb3.png", + "date": "2015-04-19T16:47:34+00:00", + "post_ID": 0, + "author_ID": 0, + "file": "codeispoetry-rgb3.png", + "mime_type": "image\/png", + "extension": "png", + "title": "codeispoetry-rgb3", + "caption": "", + "description": "", + "alt": "", + "thumbnails": { + "thumbnail": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb3.png?w=150", + "medium": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb3.png?w=300", + "large": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb3.png?w=500", + "post-thumbnail": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb3.png?w=500&h=34&crop=1" + }, + "height": 34, + "width": 500, + "exif": { + "aperture": 0, + "credit": "", + "camera": "", + "caption": "", + "created_timestamp": 0, + "copyright": "", + "focal_length": 0, + "iso": 0, + "shutter_speed": 0, + "title": "", + "orientation": 0 + }, + "meta": { + "links": { + "self": "https:\/\/public-api.wordpress.com\/rest\/v1.1\/sites\/82974409\/media\/951", + "help": "https:\/\/public-api.wordpress.com\/rest\/v1.1\/sites\/82974409\/media\/951\/help", + "site": "https:\/\/public-api.wordpress.com\/rest\/v1.1\/sites\/82974409" + } + } + }, + { + "ID": 950, + "URL": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb12.png", + "guid": "http:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb12.png", + "date": "2015-04-19T16:47:33+00:00", + "post_ID": 0, + "author_ID": 78972699, + "file": "codeispoetry-rgb12.png", + "mime_type": "image\/png", + "extension": "png", + "title": "codeispoetry-rgb12", + "caption": "", + "description": "", + "alt": "", + "thumbnails": { + "thumbnail": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb12.png?w=150", + "medium": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb12.png?w=300", + "large": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb12.png?w=500", + "post-thumbnail": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb12.png?w=500&h=34&crop=1" + }, + "height": 34, + "width": 500, + "exif": { + "aperture": 0, + "credit": "", + "camera": "", + "caption": "", + "created_timestamp": 0, + "copyright": "", + "focal_length": 0, + "iso": 0, + "shutter_speed": 0, + "title": "", + "orientation": 0 + }, + "meta": { + "links": { + "self": "https:\/\/public-api.wordpress.com\/rest\/v1.1\/sites\/82974409\/media\/950", + "help": "https:\/\/public-api.wordpress.com\/rest\/v1.1\/sites\/82974409\/media\/950\/help", + "site": "https:\/\/public-api.wordpress.com\/rest\/v1.1\/sites\/82974409" + } + } + }, + { + "ID": 949, + "URL": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb2.png", + "guid": "http:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb2.png", + "date": "2015-04-19T16:47:10+00:00", + "post_ID": 0, + "author_ID": 0, + "file": "codeispoetry-rgb2.png", + "mime_type": "image\/png", + "extension": "png", + "title": "codeispoetry-rgb2", + "caption": "", + "description": "", + "alt": "", + "thumbnails": { + "thumbnail": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb2.png?w=150", + "medium": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb2.png?w=300", + "large": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb2.png?w=500", + "post-thumbnail": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb2.png?w=500&h=34&crop=1" + }, + "height": 34, + "width": 500, + "exif": { + "aperture": 0, + "credit": "", + "camera": "", + "caption": "", + "created_timestamp": 0, + "copyright": "", + "focal_length": 0, + "iso": 0, + "shutter_speed": 0, + "title": "", + "orientation": 0 + }, + "meta": { + "links": { + "self": "https:\/\/public-api.wordpress.com\/rest\/v1.1\/sites\/82974409\/media\/949", + "help": "https:\/\/public-api.wordpress.com\/rest\/v1.1\/sites\/82974409\/media\/949\/help", + "site": "https:\/\/public-api.wordpress.com\/rest\/v1.1\/sites\/82974409" + } + } + }, + { + "ID": 948, + "URL": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb11.png", + "guid": "http:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb11.png", + "date": "2015-04-19T16:47:09+00:00", + "post_ID": 0, + "author_ID": 78972699, + "file": "codeispoetry-rgb11.png", + "mime_type": "image\/png", + "extension": "png", + "title": "codeispoetry-rgb11", + "caption": "", + "description": "", + "alt": "", + "thumbnails": { + "thumbnail": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb11.png?w=150", + "medium": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb11.png?w=300", + "large": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb11.png?w=500", + "post-thumbnail": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb11.png?w=500&h=34&crop=1" + }, + "height": 34, + "width": 500, + "exif": { + "aperture": 0, + "credit": "", + "camera": "", + "caption": "", + "created_timestamp": 0, + "copyright": "", + "focal_length": 0, + "iso": 0, + "shutter_speed": 0, + "title": "", + "orientation": 0 + }, + "meta": { + "links": { + "self": "https:\/\/public-api.wordpress.com\/rest\/v1.1\/sites\/82974409\/media\/948", + "help": "https:\/\/public-api.wordpress.com\/rest\/v1.1\/sites\/82974409\/media\/948\/help", + "site": "https:\/\/public-api.wordpress.com\/rest\/v1.1\/sites\/82974409" + } + } + }, + { + "ID": 947, + "URL": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb1.png", + "guid": "http:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb1.png", + "date": "2015-04-19T16:45:36+00:00", + "post_ID": 0, + "author_ID": 0, + "file": "codeispoetry-rgb1.png", + "mime_type": "image\/png", + "extension": "png", + "title": "codeispoetry-rgb1", + "caption": "", + "description": "", + "alt": "", + "thumbnails": { + "thumbnail": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb1.png?w=150", + "medium": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb1.png?w=300", + "large": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb1.png?w=500", + "post-thumbnail": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb1.png?w=500&h=34&crop=1" + }, + "height": 34, + "width": 500, + "exif": { + "aperture": 0, + "credit": "", + "camera": "", + "caption": "", + "created_timestamp": 0, + "copyright": "", + "focal_length": 0, + "iso": 0, + "shutter_speed": 0, + "title": "", + "orientation": 0 + }, + "meta": { + "links": { + "self": "https:\/\/public-api.wordpress.com\/rest\/v1.1\/sites\/82974409\/media\/947", + "help": "https:\/\/public-api.wordpress.com\/rest\/v1.1\/sites\/82974409\/media\/947\/help", + "site": "https:\/\/public-api.wordpress.com\/rest\/v1.1\/sites\/82974409" + } + } + }, + { + "ID": 946, + "URL": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb10.png", + "guid": "http:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb10.png", + "date": "2015-04-19T16:45:35+00:00", + "post_ID": 0, + "author_ID": 78972699, + "file": "codeispoetry-rgb10.png", + "mime_type": "image\/png", + "extension": "png", + "title": "codeispoetry-rgb10", + "caption": "", + "description": "", + "alt": "", + "thumbnails": { + "thumbnail": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb10.png?w=150", + "medium": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb10.png?w=300", + "large": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb10.png?w=500", + "post-thumbnail": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb10.png?w=500&h=34&crop=1" + }, + "height": 34, + "width": 500, + "exif": { + "aperture": 0, + "credit": "", + "camera": "", + "caption": "", + "created_timestamp": 0, + "copyright": "", + "focal_length": 0, + "iso": 0, + "shutter_speed": 0, + "title": "", + "orientation": 0 + }, + "meta": { + "links": { + "self": "https:\/\/public-api.wordpress.com\/rest\/v1.1\/sites\/82974409\/media\/946", + "help": "https:\/\/public-api.wordpress.com\/rest\/v1.1\/sites\/82974409\/media\/946\/help", + "site": "https:\/\/public-api.wordpress.com\/rest\/v1.1\/sites\/82974409" + } + } + }, + { + "ID": 945, + "URL": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb.png", + "guid": "http:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb.png", + "date": "2015-04-19T16:45:34+00:00", + "post_ID": 0, + "author_ID": 0, + "file": "codeispoetry-rgb.png", + "mime_type": "image\/png", + "extension": "png", + "title": "codeispoetry-rgb", + "caption": "", + "description": "", + "alt": "", + "thumbnails": { + "thumbnail": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb.png?w=150", + "medium": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb.png?w=300", + "large": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb.png?w=500", + "post-thumbnail": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb.png?w=500&h=34&crop=1" + }, + "height": 34, + "width": 500, + "exif": { + "aperture": 0, + "credit": "", + "camera": "", + "caption": "", + "created_timestamp": 0, + "copyright": "", + "focal_length": 0, + "iso": 0, + "shutter_speed": 0, + "title": "", + "orientation": 0 + }, + "meta": { + "links": { + "self": "https:\/\/public-api.wordpress.com\/rest\/v1.1\/sites\/82974409\/media\/945", + "help": "https:\/\/public-api.wordpress.com\/rest\/v1.1\/sites\/82974409\/media\/945\/help", + "site": "https:\/\/public-api.wordpress.com\/rest\/v1.1\/sites\/82974409" + } + } + }, + { + "ID": 944, + "URL": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb9.png", + "guid": "http:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb9.png", + "date": "2015-04-19T16:45:32+00:00", + "post_ID": 0, + "author_ID": 78972699, + "file": "codeispoetry-rgb9.png", + "mime_type": "image\/png", + "extension": "png", + "title": "codeispoetry-rgb9", + "caption": "", + "description": "", + "alt": "", + "thumbnails": { + "thumbnail": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb9.png?w=150", + "medium": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb9.png?w=300", + "large": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb9.png?w=500", + "post-thumbnail": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb9.png?w=500&h=34&crop=1" + }, + "height": 34, + "width": 500, + "exif": { + "aperture": 0, + "credit": "", + "camera": "", + "caption": "", + "created_timestamp": 0, + "copyright": "", + "focal_length": 0, + "iso": 0, + "shutter_speed": 0, + "title": "", + "orientation": 0 + }, + "meta": { + "links": { + "self": "https:\/\/public-api.wordpress.com\/rest\/v1.1\/sites\/82974409\/media\/944", + "help": "https:\/\/public-api.wordpress.com\/rest\/v1.1\/sites\/82974409\/media\/944\/help", + "site": "https:\/\/public-api.wordpress.com\/rest\/v1.1\/sites\/82974409" + } + } + }, + { + "ID": 943, + "URL": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb8.png", + "guid": "http:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb8.png", + "date": "2015-04-19T16:45:13+00:00", + "post_ID": 0, + "author_ID": 78972699, + "file": "codeispoetry-rgb8.png", + "mime_type": "image\/png", + "extension": "png", + "title": "codeispoetry-rgb8", + "caption": "", + "description": "", + "alt": "", + "thumbnails": { + "thumbnail": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb8.png?w=150", + "medium": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb8.png?w=300", + "large": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb8.png?w=500", + "post-thumbnail": "https:\/\/apiexamples.files.wordpress.com\/2015\/04\/codeispoetry-rgb8.png?w=500&h=34&crop=1" + }, + "height": 34, + "width": 500, + "exif": { + "aperture": 0, + "credit": "", + "camera": "", + "caption": "", + "created_timestamp": 0, + "copyright": "", + "focal_length": 0, + "iso": 0, + "shutter_speed": 0, + "title": "", + "orientation": 0 + }, + "meta": { + "links": { + "self": "https:\/\/public-api.wordpress.com\/rest\/v1.1\/sites\/82974409\/media\/943", + "help": "https:\/\/public-api.wordpress.com\/rest\/v1.1\/sites\/82974409\/media\/943\/help", + "site": "https:\/\/public-api.wordpress.com\/rest\/v1.1\/sites\/82974409" + } + } + } + ], + "meta": { + "next_page": "value=2015-04-19T16%3A45%3A13%2B00%3A00&id=943" + } +} \ No newline at end of file diff --git a/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/WordPressComRestApiMultipleErrors.json b/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/WordPressComRestApiMultipleErrors.json new file mode 100644 index 000000000000..a23c5e6f1e23 --- /dev/null +++ b/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/WordPressComRestApiMultipleErrors.json @@ -0,0 +1,8 @@ +{ + "errors":[ + { + "message": "No media provided in input.", + "error": "upload_error" + } + ] +} \ No newline at end of file diff --git a/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/WordPressComSocial2FACodeSuccess.json b/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/WordPressComSocial2FACodeSuccess.json new file mode 100644 index 000000000000..fe81d177cb99 --- /dev/null +++ b/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/WordPressComSocial2FACodeSuccess.json @@ -0,0 +1,13 @@ +{ + "data": { + "two_step_nonce_authenticator": "two_step_nonce_authenticator", + "two_step_nonce": "two_step_nonce", + "two_step_nonce_sms": "two_step_nonce_sms", + "two_step_nonce_backup": "two_step_nonce_backup", + "two_step_notification_sent": "two_step_notification_sent", + "two_step_supported_auth_types": "two_step_supported_auth_types", + "phone_number": "phone_number", + "bearer_token": "bearer_token", + "user_id": "user_id" + } +} diff --git a/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/me-settings-success.json b/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/me-settings-success.json new file mode 100644 index 000000000000..19439786dafb --- /dev/null +++ b/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/me-settings-success.json @@ -0,0 +1,47 @@ +{ + "enable_translator": false, + "surprise_me": true, + "post_post_flag": true, + "holidaysnow": true, + "user_login": "jimthetester", + "password": "", + "display_name": "Jim Tester", + "first_name": "Jim", + "last_name": "Tester", + "description": "Happy go lucky kind of tester.", + "user_email": "jimthetester@thetestemail.org", + "user_email_change_pending": false, + "new_user_email": "", + "user_URL": "http:\/\/jimthetester.blog", + "language": "en", + "avatar_URL": "https:\/\/2.gravatar.com\/avatar\/5c78d333444a3c12345ed8ff0e567890?s=200&d=mm", + "primary_site_ID": 321, + "comment_like_notification": true, + "mentions_notification": true, + "subscription_delivery_email_default": "instantly", + "subscription_delivery_jabber_default": false, + "subscription_delivery_mail_option": "html", + "subscription_delivery_day": 1, + "subscription_delivery_hour": 6, + "subscription_delivery_email_blocked": true, + "two_step_enabled": true, + "two_step_sms_enabled": false, + "two_step_backup_codes_printed": true, + "two_step_sms_country": "US", + "two_step_sms_phone_number": "2623536462", + "user_login_can_be_changed": false, + "calypso_preferences": { + "editor-mode": "html", + "editorAdvancedVisible": true, + "mediaModalGalleryInstructionsDismissed": true, + "recentSites": [ + 1234, + 56789, + 100234 + ] + }, + "jetpack_connect": [], + "is_desktop_app_user": true, + "locale_variant": false, + "tracks_opt_out": false +} diff --git a/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/wp-forbidden.json b/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/wp-forbidden.json new file mode 100644 index 000000000000..a40b581cbfd1 --- /dev/null +++ b/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/wp-forbidden.json @@ -0,0 +1,7 @@ +{ + "code": "rest_forbidden", + "data": { + "status": 401 + }, + "message": "Sorry, you are not allowed to do that." +} diff --git a/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/wp-pages.json b/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/wp-pages.json new file mode 100644 index 000000000000..9c89782e5615 --- /dev/null +++ b/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/wp-pages.json @@ -0,0 +1,729 @@ +[{ + "id": 457, + "date": "2019-02-06T00:37:47", + "date_gmt": "2019-02-06T00:37:47", + "guid": { + "rendered": "https:\/\/wordpress.org\/?page_id=457" + }, + "modified": "2019-02-06T00:45:13", + "modified_gmt": "2019-02-06T00:45:13", + "slug": "home", + "status": "publish", + "type": "page", + "link": "https:\/\/wordpress.org\/", + "title": { + "rendered": "Home" + }, + "content": { + "rendered": "", + "protected": false + }, + "excerpt": { + "rendered": "", + "protected": false + }, + "author": 5911429, + "featured_media": 0, + "parent": 0, + "menu_order": 0, + "comment_status": "closed", + "ping_status": "closed", + "template": "", + "meta": { + "spay_email": "" + }, + "jetpack_shortlink": "https:\/\/wp.me\/P1OHUb-7n", + "_links": { + "self": [{ + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/pages\/457" + }], + "collection": [{ + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/pages" + }], + "about": [{ + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/types\/page" + }], + "author": [{ + "embeddable": true, + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/users\/5911429" + }], + "replies": [{ + "embeddable": true, + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/comments?post=457" + }], + "version-history": [{ + "count": 1, + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/pages\/457\/revisions" + }], + "predecessor-version": [{ + "id": 458, + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/pages\/457\/revisions\/458" + }], + "wp:attachment": [{ + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/media?parent=457" + }], + "curies": [{ + "name": "wp", + "href": "https:\/\/api.w.org\/{rel}", + "templated": true + }] + } +}, { + "id": 385, + "date": "2018-07-09T21:23:48", + "date_gmt": "2018-07-09T21:23:48", + "guid": { + "rendered": "https:\/\/wordpress.org\/?page_id=385" + }, + "modified": "2018-11-26T07:14:22", + "modified_gmt": "2018-11-26T07:14:22", + "slug": "counter", + "status": "publish", + "type": "page", + "link": "https:\/\/wordpress.org\/download\/counter\/", + "title": { + "rendered": "Counter" + }, + "content": { + "rendered": "", + "protected": false + }, + "excerpt": { + "rendered": "", + "protected": false + }, + "author": 5911429, + "featured_media": 0, + "parent": 371, + "menu_order": 0, + "comment_status": "closed", + "ping_status": "closed", + "template": "page-download-counter.php", + "meta": { + "spay_email": "" + }, + "jetpack_shortlink": "https:\/\/wp.me\/P1OHUb-6d", + "_links": { + "self": [{ + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/pages\/385" + }], + "collection": [{ + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/pages" + }], + "about": [{ + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/types\/page" + }], + "author": [{ + "embeddable": true, + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/users\/5911429" + }], + "replies": [{ + "embeddable": true, + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/comments?post=385" + }], + "version-history": [{ + "count": 1, + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/pages\/385\/revisions" + }], + "predecessor-version": [{ + "id": 386, + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/pages\/385\/revisions\/386" + }], + "up": [{ + "embeddable": true, + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/pages\/371" + }], + "wp:attachment": [{ + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/media?parent=385" + }], + "curies": [{ + "name": "wp", + "href": "https:\/\/api.w.org\/{rel}", + "templated": true + }] + } +}, { + "id": 379, + "date": "2018-07-03T02:03:10", + "date_gmt": "2018-07-03T02:03:10", + "guid": { + "rendered": "https:\/\/wordpress.org\/?page_id=379" + }, + "modified": "2018-11-26T07:14:22", + "modified_gmt": "2018-11-26T07:14:22", + "slug": "source", + "status": "publish", + "type": "page", + "link": "https:\/\/wordpress.org\/download\/source\/", + "title": { + "rendered": "Source Code" + }, + "content": { + "rendered": "", + "protected": false + }, + "excerpt": { + "rendered": "", + "protected": false + }, + "author": 5911429, + "featured_media": 0, + "parent": 371, + "menu_order": 0, + "comment_status": "closed", + "ping_status": "closed", + "template": "page-download-source.php", + "meta": { + "spay_email": "" + }, + "jetpack_shortlink": "https:\/\/wp.me\/P1OHUb-67", + "_links": { + "self": [{ + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/pages\/379" + }], + "collection": [{ + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/pages" + }], + "about": [{ + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/types\/page" + }], + "author": [{ + "embeddable": true, + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/users\/5911429" + }], + "replies": [{ + "embeddable": true, + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/comments?post=379" + }], + "version-history": [{ + "count": 2, + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/pages\/379\/revisions" + }], + "predecessor-version": [{ + "id": 381, + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/pages\/379\/revisions\/381" + }], + "up": [{ + "embeddable": true, + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/pages\/371" + }], + "wp:attachment": [{ + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/media?parent=379" + }], + "curies": [{ + "name": "wp", + "href": "https:\/\/api.w.org\/{rel}", + "templated": true + }] + } +}, { + "id": 375, + "date": "2018-07-03T02:01:07", + "date_gmt": "2018-07-03T02:01:07", + "guid": { + "rendered": "https:\/\/wordpress.org\/?page_id=375" + }, + "modified": "2018-11-26T07:14:22", + "modified_gmt": "2018-11-26T07:14:22", + "slug": "beta-nightly", + "status": "publish", + "type": "page", + "link": "https:\/\/wordpress.org\/download\/beta-nightly\/", + "title": { + "rendered": "Beta\/Nightly" + }, + "content": { + "rendered": "", + "protected": false + }, + "excerpt": { + "rendered": "", + "protected": false + }, + "author": 5911429, + "featured_media": 0, + "parent": 371, + "menu_order": 0, + "comment_status": "closed", + "ping_status": "closed", + "template": "page-download-beta-nightly.php", + "meta": { + "spay_email": "" + }, + "jetpack_shortlink": "https:\/\/wp.me\/P1OHUb-63", + "_links": { + "self": [{ + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/pages\/375" + }], + "collection": [{ + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/pages" + }], + "about": [{ + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/types\/page" + }], + "author": [{ + "embeddable": true, + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/users\/5911429" + }], + "replies": [{ + "embeddable": true, + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/comments?post=375" + }], + "version-history": [{ + "count": 1, + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/pages\/375\/revisions" + }], + "predecessor-version": [{ + "id": 376, + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/pages\/375\/revisions\/376" + }], + "up": [{ + "embeddable": true, + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/pages\/371" + }], + "wp:attachment": [{ + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/media?parent=375" + }], + "curies": [{ + "name": "wp", + "href": "https:\/\/api.w.org\/{rel}", + "templated": true + }] + } +}, { + "id": 373, + "date": "2018-07-02T23:57:35", + "date_gmt": "2018-07-02T23:57:35", + "guid": { + "rendered": "https:\/\/wordpress.org\/?page_id=373" + }, + "modified": "2018-11-26T07:14:22", + "modified_gmt": "2018-11-26T07:14:22", + "slug": "releases", + "status": "publish", + "type": "page", + "link": "https:\/\/wordpress.org\/download\/releases\/", + "title": { + "rendered": "Releases" + }, + "content": { + "rendered": "", + "protected": false + }, + "excerpt": { + "rendered": "", + "protected": false + }, + "author": 5911429, + "featured_media": 0, + "parent": 371, + "menu_order": 0, + "comment_status": "closed", + "ping_status": "closed", + "template": "page-download-releases.php", + "meta": { + "spay_email": "" + }, + "jetpack_shortlink": "https:\/\/wp.me\/P1OHUb-61", + "_links": { + "self": [{ + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/pages\/373" + }], + "collection": [{ + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/pages" + }], + "about": [{ + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/types\/page" + }], + "author": [{ + "embeddable": true, + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/users\/5911429" + }], + "replies": [{ + "embeddable": true, + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/comments?post=373" + }], + "version-history": [{ + "count": 1, + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/pages\/373\/revisions" + }], + "predecessor-version": [{ + "id": 374, + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/pages\/373\/revisions\/374" + }], + "up": [{ + "embeddable": true, + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/pages\/371" + }], + "wp:attachment": [{ + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/media?parent=373" + }], + "curies": [{ + "name": "wp", + "href": "https:\/\/api.w.org\/{rel}", + "templated": true + }] + } +}, { + "id": 371, + "date": "2018-07-02T23:57:04", + "date_gmt": "2018-07-02T23:57:04", + "guid": { + "rendered": "https:\/\/wordpress.org\/?page_id=371" + }, + "modified": "2018-11-26T07:14:22", + "modified_gmt": "2018-11-26T07:14:22", + "slug": "download", + "status": "publish", + "type": "page", + "link": "https:\/\/wordpress.org\/download\/", + "title": { + "rendered": "Download" + }, + "content": { + "rendered": "", + "protected": false + }, + "excerpt": { + "rendered": "", + "protected": false + }, + "author": 5911429, + "featured_media": 0, + "parent": 0, + "menu_order": 0, + "comment_status": "closed", + "ping_status": "closed", + "template": "page-download.php", + "meta": { + "spay_email": "" + }, + "jetpack_shortlink": "https:\/\/wp.me\/P1OHUb-5Z", + "_links": { + "self": [{ + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/pages\/371" + }], + "collection": [{ + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/pages" + }], + "about": [{ + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/types\/page" + }], + "author": [{ + "embeddable": true, + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/users\/5911429" + }], + "replies": [{ + "embeddable": true, + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/comments?post=371" + }], + "version-history": [{ + "count": 1, + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/pages\/371\/revisions" + }], + "predecessor-version": [{ + "id": 372, + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/pages\/371\/revisions\/372" + }], + "wp:attachment": [{ + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/media?parent=371" + }], + "curies": [{ + "name": "wp", + "href": "https:\/\/api.w.org\/{rel}", + "templated": true + }] + } +}, { + "id": 329, + "date": "2018-05-30T06:49:44", + "date_gmt": "2018-05-30T06:49:44", + "guid": { + "rendered": "https:\/\/wordpress.org\/?page_id=329" + }, + "modified": "2018-05-30T06:49:44", + "modified_gmt": "2018-05-30T06:49:44", + "slug": "data-erasure-request", + "status": "publish", + "type": "page", + "link": "https:\/\/wordpress.org\/about\/privacy\/data-erasure-request\/", + "title": { + "rendered": "Data Erasure Request" + }, + "content": { + "rendered": "", + "protected": false + }, + "excerpt": { + "rendered": "", + "protected": false + }, + "author": 196012, + "featured_media": 0, + "parent": 259, + "menu_order": 0, + "comment_status": "closed", + "ping_status": "closed", + "template": "page-about-privacy-data-erasure-request.php", + "meta": { + "spay_email": "" + }, + "jetpack_shortlink": "https:\/\/wp.me\/P1OHUb-5j", + "_links": { + "self": [{ + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/pages\/329" + }], + "collection": [{ + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/pages" + }], + "about": [{ + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/types\/page" + }], + "author": [{ + "embeddable": true, + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/users\/196012" + }], + "replies": [{ + "embeddable": true, + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/comments?post=329" + }], + "version-history": [{ + "count": 2, + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/pages\/329\/revisions" + }], + "predecessor-version": [{ + "id": 331, + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/pages\/329\/revisions\/331" + }], + "up": [{ + "embeddable": true, + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/pages\/259" + }], + "wp:attachment": [{ + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/media?parent=329" + }], + "curies": [{ + "name": "wp", + "href": "https:\/\/api.w.org\/{rel}", + "templated": true + }] + } +}, { + "id": 315, + "date": "2018-05-28T09:13:38", + "date_gmt": "2018-05-28T09:13:38", + "guid": { + "rendered": "https:\/\/wordpress.org\/?page_id=315" + }, + "modified": "2019-06-21T07:58:29", + "modified_gmt": "2019-06-21T07:58:29", + "slug": "data-export-request", + "status": "publish", + "type": "page", + "link": "https:\/\/wordpress.org\/about\/privacy\/data-export-request\/", + "title": { + "rendered": "Data Export Request" + }, + "content": { + "rendered": "", + "protected": false + }, + "excerpt": { + "rendered": "", + "protected": false + }, + "author": 5911429, + "featured_media": 0, + "parent": 259, + "menu_order": 0, + "comment_status": "closed", + "ping_status": "closed", + "template": "page-about-privacy-data-export-request.php", + "meta": { + "spay_email": "" + }, + "jetpack_shortlink": "https:\/\/wp.me\/P1OHUb-55", + "_links": { + "self": [{ + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/pages\/315" + }], + "collection": [{ + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/pages" + }], + "about": [{ + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/types\/page" + }], + "author": [{ + "embeddable": true, + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/users\/5911429" + }], + "replies": [{ + "embeddable": true, + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/comments?post=315" + }], + "version-history": [{ + "count": 1, + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/pages\/315\/revisions" + }], + "predecessor-version": [{ + "id": 316, + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/pages\/315\/revisions\/316" + }], + "up": [{ + "embeddable": true, + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/pages\/259" + }], + "wp:attachment": [{ + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/media?parent=315" + }], + "curies": [{ + "name": "wp", + "href": "https:\/\/api.w.org\/{rel}", + "templated": true + }] + } +}, { + "id": 309, + "date": "2018-05-25T04:16:41", + "date_gmt": "2018-05-25T04:16:41", + "guid": { + "rendered": "https:\/\/wordpress.org\/?page_id=309" + }, + "modified": "2018-05-28T11:14:32", + "modified_gmt": "2018-05-28T11:14:32", + "slug": "cookies", + "status": "publish", + "type": "page", + "link": "https:\/\/wordpress.org\/about\/privacy\/cookies\/", + "title": { + "rendered": "Cookie Policy" + }, + "content": { + "rendered": "", + "protected": false + }, + "excerpt": { + "rendered": "", + "protected": false + }, + "author": 5911429, + "featured_media": 0, + "parent": 259, + "menu_order": 0, + "comment_status": "closed", + "ping_status": "closed", + "template": "page-about-privacy-cookies.php", + "meta": { + "spay_email": "" + }, + "jetpack_shortlink": "https:\/\/wp.me\/P1OHUb-4Z", + "_links": { + "self": [{ + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/pages\/309" + }], + "collection": [{ + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/pages" + }], + "about": [{ + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/types\/page" + }], + "author": [{ + "embeddable": true, + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/users\/5911429" + }], + "replies": [{ + "embeddable": true, + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/comments?post=309" + }], + "version-history": [{ + "count": 2, + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/pages\/309\/revisions" + }], + "predecessor-version": [{ + "id": 311, + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/pages\/309\/revisions\/311" + }], + "up": [{ + "embeddable": true, + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/pages\/259" + }], + "wp:attachment": [{ + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/media?parent=309" + }], + "curies": [{ + "name": "wp", + "href": "https:\/\/api.w.org\/{rel}", + "templated": true + }] + } +}, { + "id": 281, + "date": "2018-03-28T03:08:38", + "date_gmt": "2018-03-28T03:08:38", + "guid": { + "rendered": "https:\/\/wordpress.org\/about\/accessibility\/" + }, + "modified": "2018-03-28T03:08:38", + "modified_gmt": "2018-03-28T03:08:38", + "slug": "accessibility", + "status": "publish", + "type": "page", + "link": "https:\/\/wordpress.org\/about\/accessibility\/", + "title": { + "rendered": "Accessibility" + }, + "content": { + "rendered": "", + "protected": false + }, + "excerpt": { + "rendered": "", + "protected": false + }, + "author": 5911429, + "featured_media": 0, + "parent": 251, + "menu_order": 0, + "comment_status": "closed", + "ping_status": "closed", + "template": "page-about-accessibility.php", + "meta": { + "spay_email": "" + }, + "jetpack_shortlink": "https:\/\/wp.me\/P1OHUb-4x", + "_links": { + "self": [{ + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/pages\/281" + }], + "collection": [{ + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/pages" + }], + "about": [{ + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/types\/page" + }], + "author": [{ + "embeddable": true, + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/users\/5911429" + }], + "replies": [{ + "embeddable": true, + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/comments?post=281" + }], + "version-history": [{ + "count": 0, + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/pages\/281\/revisions" + }], + "up": [{ + "embeddable": true, + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/pages\/251" + }], + "wp:attachment": [{ + "href": "https:\/\/wordpress.org\/wp-json\/wp\/v2\/media?parent=281" + }], + "curies": [{ + "name": "wp", + "href": "https:\/\/api.w.org\/{rel}", + "templated": true + }] + } +}] \ No newline at end of file diff --git a/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/wp-reusable-blocks.json b/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/wp-reusable-blocks.json new file mode 100644 index 000000000000..5e818f84e781 --- /dev/null +++ b/Tests/WordPressKitTests/CoreAPITests/Stubs/JSON/wp-reusable-blocks.json @@ -0,0 +1,50 @@ +{ + "id": 6, + "date": "2021-02-10T11:51:53", + "date_gmt": "2021-02-10T11:51:53", + "guid": { + "rendered": "https:\/\/test-site.org\/2021\/02\/10\/untitled-reusable-block\/", + "raw": "https:\/\/test-site.org\/2021\/02\/10\/untitled-reusable-block\/" + }, + "modified": "2021-02-10T12:31:39", + "modified_gmt": "2021-02-10T12:31:39", + "password": "", + "slug": "untitled-reusable-block", + "status": "publish", + "type": "wp_block", + "link": "https:\/\/test-site.org\/2021\/02\/10\/untitled-reusable-block\/", + "title": { + "raw": "A resuable block" + }, + "content": { + "raw": "\n

Some text<\/p>\n\n\n\n