public function testAddUserSettingMultiple() { $settingKey = "tagColors"; $value = array(array("name" => "_READ", "color" => "#990000")); // TODO: multiple, once more settings are supported $libraryVersion = API::getLibraryVersion(); $json = array($settingKey => array("value" => $value)); $response = API::userPost(self::$config['userID'], "settings?key=" . self::$config['apiKey'], json_encode($json), array("Content-Type: application/json")); $this->assert204($response); // Multi-object GET $response = API::userGet(self::$config['userID'], "settings?key=" . self::$config['apiKey']); $this->assert200($response); $this->assertContentType("application/json", $response); $json = json_decode($response->getBody(), true); $this->assertNotNull($json); $this->assertArrayHasKey($settingKey, $json); $this->assertEquals($value, $json[$settingKey]['value']); $this->assertEquals($libraryVersion + 1, $json[$settingKey]['version']); // Single-object GET $response = API::userGet(self::$config['userID'], "settings/{$settingKey}?key=" . self::$config['apiKey']); $this->assert200($response); $this->assertContentType("application/json", $response); $json = json_decode($response->getBody(), true); $this->assertNotNull($json); $this->assertEquals($value, $json['value']); $this->assertEquals($libraryVersion + 1, $json['version']); }
public function testZoteroWriteToken() { $json = API::getItemTemplate("book"); $token = md5(uniqid()); $response = API::userPost(self::$config['userID'], "items?key=" . self::$config['apiKey'], json_encode(array("items" => array($json))), array("Content-Type: application/json", "Zotero-Write-Token: {$token}")); $this->assert200ForObject($response); $response = API::userPost(self::$config['userID'], "items?key=" . self::$config['apiKey'], json_encode(array("items" => array($json))), array("Content-Type: application/json", "Zotero-Write-Token: {$token}")); $this->assert412($response); }
public function testNewSingleCollectionWithoutParentProperty() { $name = "Test Collection"; $json = array('name' => $name); $response = API::userPost(self::$config['userID'], "collections?key=" . self::$config['apiKey'], json_encode($json), array("Content-Type: application/json")); $this->assert200($response); $xml = API::getXMLFromResponse($response); $this->assertEquals(1, (int) array_shift($xml->xpath('/atom:feed/zapi:totalResults'))); $data = API::parseDataFromAtomEntry($xml); $json = json_decode($data['content']); $this->assertEquals($name, (string) $json->name); }
public function testCreateItemWithChildren() { $json = API::getItemTemplate("newspaperArticle"); $noteJSON = API::getItemTemplate("note"); $noteJSON->note = "<p>Here's a test note</p>"; $json->notes = array($noteJSON); $response = API::userPost(self::$config['userID'], "items?key=" . self::$config['apiKey'], json_encode(array("items" => array($json)))); $this->assert201($response); $xml = API::getXMLFromResponse($response); $this->assertNumResults(1, $response); $this->assertEquals(1, (int) array_shift($xml->xpath('//atom:entry/zapi:numChildren'))); }
public function testWebTranslationMultiple() { $response = API::userPost(self::$config['userID'], "items?key=" . self::$config['apiKey'], json_encode(["url" => "http://www.amazon.com/s/field-keywords=zotero+guide+librarians"]), array("Content-Type: application/json")); $this->assert300($response); $json = json_decode($response->getBody()); $results = get_object_vars($json); $key = array_keys($results)[0]; $val = array_values($results)[0]; $this->assertEquals(0, strpos($key, 'http')); $this->assertEquals('Zotero: A Guide for Librarians, Researchers and Educators', $val); // Can't test posting on v1, because generated token isn't returned }
public function testKeyNoteAccess() { API::userClear(self::$config['userID']); API::setKeyOption(self::$config['userID'], self::$config['apiKey'], 'libraryNotes', 1); $keys = array(); $topLevelKeys = array(); $bookKeys = array(); $xml = API::createItem('book', array("title" => "A"), $this); $data = API::parseDataFromAtomEntry($xml); $keys[] = $data['key']; $topKeys[] = $data['key']; $bookKeys[] = $data['key']; $xml = API::createNoteItem("B", false, $this); $data = API::parseDataFromAtomEntry($xml); $keys[] = $data['key']; $topKeys[] = $data['key']; $xml = API::createNoteItem("C", false, $this); $data = API::parseDataFromAtomEntry($xml); $keys[] = $data['key']; $topKeys[] = $data['key']; $xml = API::createNoteItem("D", false, $this); $data = API::parseDataFromAtomEntry($xml); $keys[] = $data['key']; $topKeys[] = $data['key']; $xml = API::createNoteItem("E", false, $this); $data = API::parseDataFromAtomEntry($xml); $keys[] = $data['key']; $topKeys[] = $data['key']; $xml = API::createItem('book', array("title" => "F"), $this); $data = API::parseDataFromAtomEntry($xml); $keys[] = $data['key']; $topKeys[] = $data['key']; $bookKeys[] = $data['key']; $xml = API::createNoteItem("G", $data['key'], $this); $data = API::parseDataFromAtomEntry($xml); $keys[] = $data['key']; // Create collection and add items to it $response = API::userPost(self::$config['userID'], "collections?key=" . self::$config['apiKey'], json_encode(array("collections" => array(array("name" => "Test", "parentCollection" => false)))), array("Content-Type: application/json")); $this->assert200ForObject($response); $collectionKey = API::getFirstSuccessKeyFromResponse($response); $response = API::userPost(self::$config['userID'], "collections/{$collectionKey}/items?key=" . self::$config['apiKey'], implode(" ", $topKeys)); $this->assert204($response); // // format=atom // // Root $response = API::userGet(self::$config['userID'], "items?key=" . self::$config['apiKey']); $this->assertNumResults(sizeOf($keys), $response); $this->assertTotalResults(sizeOf($keys), $response); // Top $response = API::userGet(self::$config['userID'], "items/top?key=" . self::$config['apiKey']); $this->assertNumResults(sizeOf($topKeys), $response); $this->assertTotalResults(sizeOf($topKeys), $response); // Collection $response = API::userGet(self::$config['userID'], "collections/{$collectionKey}/items/top?key=" . self::$config['apiKey']); $this->assertNumResults(sizeOf($topKeys), $response); $this->assertTotalResults(sizeOf($topKeys), $response); // // format=keys // // Root $response = API::userGet(self::$config['userID'], "items?key=" . self::$config['apiKey'] . "&format=keys"); $this->assert200($response); $this->assertCount(sizeOf($keys), explode("\n", trim($response->getBody()))); // Top $response = API::userGet(self::$config['userID'], "items/top?key=" . self::$config['apiKey'] . "&format=keys"); $this->assert200($response); $this->assertCount(sizeOf($topKeys), explode("\n", trim($response->getBody()))); // Collection $response = API::userGet(self::$config['userID'], "collections/{$collectionKey}/items/top?key=" . self::$config['apiKey'] . "&format=keys"); $this->assert200($response); $this->assertCount(sizeOf($topKeys), explode("\n", trim($response->getBody()))); // Remove notes privilege from key API::setKeyOption(self::$config['userID'], self::$config['apiKey'], 'libraryNotes', 0); // // format=atom // // totalResults with limit $response = API::userGet(self::$config['userID'], "items?key=" . self::$config['apiKey'] . "&limit=1"); $this->assertNumResults(1, $response); $this->assertTotalResults(sizeOf($bookKeys), $response); // And without limit $response = API::userGet(self::$config['userID'], "items?key=" . self::$config['apiKey']); $this->assertNumResults(sizeOf($bookKeys), $response); $this->assertTotalResults(sizeOf($bookKeys), $response); // Top $response = API::userGet(self::$config['userID'], "items/top?key=" . self::$config['apiKey']); $this->assertNumResults(sizeOf($bookKeys), $response); $this->assertTotalResults(sizeOf($bookKeys), $response); // Collection $response = API::userGet(self::$config['userID'], "collections/{$collectionKey}/items?key=" . self::$config['apiKey']); $this->assertNumResults(sizeOf($bookKeys), $response); $this->assertTotalResults(sizeOf($bookKeys), $response); // // format=keys // $response = API::userGet(self::$config['userID'], "items?key=" . self::$config['apiKey'] . "&format=keys"); $keys = explode("\n", trim($response->getBody())); sort($keys); $this->assertEmpty(array_merge(array_diff($bookKeys, $keys), array_diff($keys, $bookKeys))); }
public function testInvalidCollectionRelation() { $json = array("name" => "Test", "relations" => array("foo:unknown" => "http://zotero.org/groups/1/collections/AAAAAAAA")); $response = API::userPost(self::$config['userID'], "collections?key=" . self::$config['apiKey'], json_encode(array("collections" => array($json)))); $this->assert400ForObject($response, "Unsupported predicate 'foo:unknown'"); $json["relations"] = array("owl:sameAs" => "Not a URI"); $response = API::userPost(self::$config['userID'], "collections?key=" . self::$config['apiKey'], json_encode(array("collections" => array($json)))); $this->assert400ForObject($response, "'relations' values currently must be Zotero collection URIs"); $json["relations"] = ["http://zotero.org/groups/1/collections/AAAAAAAA"]; $response = API::userPost(self::$config['userID'], "collections?key=" . self::$config['apiKey'], json_encode(array("collections" => array($json)))); $this->assert400ForObject($response, "'relations' property must be an object"); }
public function testNoteTooLongWithinHTMLTags() { $this->json->note = " \n<p><!-- " . $this->content . " --></p>"; $response = API::userPost(self::$config['userID'], "items?key=" . self::$config['apiKey'], json_encode(array("items" => array($this->json))), array("Content-Type: application/json")); $this->assert400ForObject($response, "Note '<p><!-- 1234567890123456789012345678901234567890123456789012345678901234...' too long"); }
public function testAddFileLinkedAttachment() { $xml = API::createAttachmentItem("linked_file", [], false, $this); $data = API::parseDataFromAtomEntry($xml); $file = "work/file"; $fileContents = self::getRandomUnicodeString(); file_put_contents($file, $fileContents); $hash = md5_file($file); $filename = "test_" . $fileContents; $mtime = filemtime($file) * 1000; $size = filesize($file); $contentType = "text/plain"; $charset = "utf-8"; // Get upload authorization $response = API::userPost(self::$config['userID'], "items/{$data['key']}/file?key=" . self::$config['apiKey'], $this->implodeParams(array("md5" => $hash, "filename" => $filename, "filesize" => $size, "mtime" => $mtime, "contentType" => $contentType, "charset" => $charset)), array("Content-Type: application/x-www-form-urlencoded", "If-None-Match: *")); $this->assert400($response); }
public function testMappedCreatorTypes() { $json = array("items" => array(array('itemType' => 'presentation', 'title' => 'Test', 'creators' => array(array("creatorType" => "author", "name" => "Foo"))), array('itemType' => 'presentation', 'title' => 'Test', 'creators' => array(array("creatorType" => "editor", "name" => "Foo"))))); $response = API::userPost(self::$config['userID'], "items?key=" . self::$config['apiKey'], json_encode($json)); // 'author' gets mapped automatically $this->assert200ForObject($response); // Others don't $this->assert400ForObject($response, false, 1); }
public function testReverseSameAs() { $items = array(); $item = ["key" => API::createItem("book", false, null, 'key'), "relations" => [["owl:sameAs", "http://zotero.org/groups/1/items/AAAAAAAA"]]]; $xml = Sync::updated(self::$sessionID); $updateKey = $xml['updateKey']; $lastSyncTimestamp = $xml['timestamp']; $xmlstr = '<data version="9">' . '<relations>'; $subject = 'http://zotero.org/users/' . self::$config['userID'] . '/items/' . $item['key']; // Insert backwards, as client does via classic sync // if group item is dragged to personal library foreach ($item['relations'] as $rel) { $xmlstr .= '<relation libraryID="' . self::$config['libraryID'] . '">' . "<subject>{$rel[1]}</subject>" . "<predicate>{$rel[0]}</predicate>" . "<object>{$subject}</object>" . '</relation>'; } $xmlstr .= '</relations>' . '</data>'; $response = Sync::upload(self::$sessionID, $updateKey, $xmlstr); Sync::waitForUpload(self::$sessionID, $response, $this); // Check via API $response = API::userGet(self::$config['userID'], "items/{$item['key']}?key=" . self::$config['apiKey'] . "&content=json"); $content = API::getContentFromResponse($response); $json = json_decode($content, true); $uniquePredicates = array_unique(array_map(function ($x) { return $x[0]; }, $item['relations'])); $this->assertCount(sizeOf($uniquePredicates), $json['relations']); foreach ($item['relations'] as $rel) { $this->assertArrayHasKey($rel[0], $json['relations']); $this->assertContains($rel[1], $json['relations'][$rel[0]]); } // PUT via API, which should be unchanged $response = API::userPost(self::$config['userID'], "items?key=" . self::$config['apiKey'], json_encode(["items" => [$json]])); $results = json_decode($response->getBody(), true); $this->assertArrayHasKey('unchanged', $results); $this->assertContains($item['key'], $results['unchanged']); // Add another owl:sameAs via API if (is_string($json['relations']['owl:sameAs'])) { $json['relations']['owl:sameAs'] = [$json['relations']['owl:sameAs']]; } $newURI = "http://zotero.org/groups/1/items/BBBBBBBB"; $json['relations']['owl:sameAs'][] = $newURI; $item['relations'][] = ['owl:sameAs', $newURI]; $response = API::userPost(self::$config['userID'], "items?key=" . self::$config['apiKey'], json_encode(["items" => [$json]])); $this->assertEquals(200, $response->getStatus()); $results = json_decode($response->getBody(), true); $this->assertArrayHasKey('success', $results); $this->assertContains($item['key'], $results['success']); // Check via API $response = API::userGet(self::$config['userID'], "items/{$item['key']}?key=" . self::$config['apiKey'] . "&content=json"); $content = API::getContentFromResponse($response); $json = json_decode($content, true); $uniquePredicates = array_unique(array_map(function ($x) { return $x[0]; }, $item['relations'])); $this->assertCount(sizeOf($uniquePredicates), $json['relations']); foreach ($item['relations'] as $rel) { $this->assertArrayHasKey($rel[0], $json['relations']); $this->assertContains($rel[1], $json['relations'][$rel[0]]); } $this->assertArrayHasKey("owl:sameAs", $json['relations']); $this->assertContains($newURI, $json['relations']['owl:sameAs']); $xml = Sync::updated(self::$sessionID); $updateKey = $xml['updateKey']; // First URL should still be in reverse order $this->assertEquals(2, sizeOf($xml->updated[0]->relations->xpath("//relations/relation"))); $subRel = $xml->updated[0]->relations->xpath("//relations/relation[subject/text() = '{$item['relations'][0][1]}']"); $objRel = $xml->updated[0]->relations->xpath("//relations/relation[object/text() = '{$newURI}']"); $this->assertEquals(1, sizeOf($subRel)); $this->assertEquals($subject, $subRel[0]->object); $this->assertEquals(1, sizeOf($objRel)); $this->assertEquals($subject, $objRel[0]->subject); // Resave second relation via classic sync in reverse order $xmlstr = '<data version="9"><relations>'; $xmlstr .= '<relation libraryID="' . self::$config['libraryID'] . '">' . "<subject>{$newURI}</subject>" . "<predicate>owl:sameAs</predicate>" . "<object>{$subject}</object>" . "</relation></relations></data>"; $response = Sync::upload(self::$sessionID, $updateKey, $xmlstr); Sync::waitForUpload(self::$sessionID, $response, $this); $xml = Sync::updated(self::$sessionID); $this->assertEquals(2, sizeOf($xml->updated[0]->relations->xpath("//relations/relation"))); // Delete reverse relation via API $response = API::userGet(self::$config['userID'], "items/{$item['key']}?key=" . self::$config['apiKey'] . "&content=json"); $content = API::getContentFromResponse($response); $json = json_decode($content, true); // Leave just the relation that's entered in normal order $json['relations']['owl:sameAs'] = [$newURI]; $item['relations'] = ['owl:sameAs', $newURI]; $response = API::userPost(self::$config['userID'], "items?key=" . self::$config['apiKey'], json_encode(["items" => [$json]])); $this->assertEquals(200, $response->getStatus()); $results = json_decode($response->getBody(), true); $this->assertArrayHasKey('success', $results); $this->assertContains($item['key'], $results['success']); $response = API::userGet(self::$config['userID'], "items/{$item['key']}?key=" . self::$config['apiKey'] . "&content=json"); $content = API::getContentFromResponse($response); $json = json_decode($content, true); $this->assertArrayHasKey("owl:sameAs", $json['relations']); $this->assertContains($newURI, $json['relations']['owl:sameAs']); // Should only have one relation left $this->assertEquals(1, sizeOf($json['relations']['owl:sameAs'])); }
private function _testMultiObjectLastModifiedVersion($objectType) { $objectTypePlural = API::getPluralObjectType($objectType); $objectKeyProp = $objectType . "Key"; $objectVersionProp = $objectType . "Version"; $response = API::userGet(self::$config['userID'], "{$objectTypePlural}?key=" . self::$config['apiKey'] . "&limit=1"); $version = $response->getHeader("Last-Modified-Version"); $this->assertTrue(is_numeric($version)); switch ($objectType) { case 'collection': $json = new \stdClass(); $json->name = "Name"; break; case 'item': $json = API::getItemTemplate("book"); break; case 'search': $json = new \stdClass(); $json->name = "Name"; $json->conditions = array(array("condition" => "title", "operator" => "contains", "value" => "test")); break; } // Outdated library version $response = API::userPost(self::$config['userID'], "{$objectTypePlural}?key=" . self::$config['apiKey'], json_encode(array($objectTypePlural => array($json))), array("Content-Type: application/json", "If-Unmodified-Since-Version: " . ($version - 1))); $this->assert412($response); // Make sure version didn't change during failure $response = API::userGet(self::$config['userID'], "{$objectTypePlural}?key=" . self::$config['apiKey'] . "&limit=1"); $this->assertEquals($version, $response->getHeader("Last-Modified-Version")); // Create a new object, using library timestamp $response = API::userPost(self::$config['userID'], "{$objectTypePlural}?key=" . self::$config['apiKey'], json_encode(array($objectTypePlural => array($json))), array("Content-Type: application/json", "If-Unmodified-Since-Version: {$version}")); $this->assert200($response); $version2 = $response->getHeader("Last-Modified-Version"); $this->assertTrue(is_numeric($version2)); // Version should be incremented on new object $this->assertGreaterThan($version, $version2); $objectKey = API::getFirstSuccessKeyFromResponse($response); // Check single-object request $response = API::userGet(self::$config['userID'], "{$objectTypePlural}/{$objectKey}?key=" . self::$config['apiKey'] . "&content=json"); $this->assert200($response); $version = $response->getHeader("Last-Modified-Version"); $this->assertTrue(is_numeric($version)); $this->assertEquals($version, $version2); $json = json_decode(API::getContentFromResponse($response)); // Modify object $json->{$objectKeyProp} = $objectKey; switch ($objectType) { case 'collection': $json->name = "New Name"; break; case 'item': $json->title = "New Title"; break; case 'search': $json->name = "New Name"; break; } // No If-Unmodified-Since-Version or object version property unset($json->{$objectVersionProp}); $response = API::userPost(self::$config['userID'], "{$objectTypePlural}?key=" . self::$config['apiKey'], json_encode(array($objectTypePlural => array($json))), array("Content-Type: application/json")); $this->assert428ForObject($response); // Outdated object version property $json->{$objectVersionProp} = $version - 1; $response = API::userPost(self::$config['userID'], "{$objectTypePlural}?key=" . self::$config['apiKey'], json_encode(array($objectTypePlural => array($json))), array("Content-Type: application/json")); $this->assert412ForObject($response, ucwords($objectType) . " has been modified since specified version " . "(expected {$json->{$objectVersionProp}}, found {$version})"); // Modify object, using object version property $json->{$objectVersionProp} = $version; $response = API::userPost(self::$config['userID'], "{$objectTypePlural}?key=" . self::$config['apiKey'], json_encode(array($objectTypePlural => array($json))), array("Content-Type: application/json")); $this->assert200($response); // Version should be incremented on modified object $version3 = $response->getHeader("Last-Modified-Version"); $this->assertTrue(is_numeric($version3)); $this->assertGreaterThan($version2, $version3); // Check library version $response = API::userGet(self::$config['userID'], "{$objectTypePlural}?key=" . self::$config['apiKey']); $version = $response->getHeader("Last-Modified-Version"); $this->assertTrue(is_numeric($version)); $this->assertEquals($version, $version3); // Check single-object request $response = API::userGet(self::$config['userID'], "{$objectTypePlural}/{$objectKey}?key=" . self::$config['apiKey']); $version = $response->getHeader("Last-Modified-Version"); $this->assertTrue(is_numeric($version)); $this->assertEquals($version, $version3); // TODO: Version should be incremented on deleted item }
public function testEditMultipleCollections() { $xml = API::createCollection("Test 1", false, $this, 'atom'); $data = API::parseDataFromAtomEntry($xml); $key1 = $data['key']; $xml = API::createCollection("Test 2", false, $this, 'atom'); $data = API::parseDataFromAtomEntry($xml); $key2 = $data['key']; $newName1 = "Test 1 Modified"; $newName2 = "Test 2 Modified"; $response = API::userPost(self::$config['userID'], "collections?key=" . self::$config['apiKey'], json_encode(array("collections" => array(array('collectionKey' => $key1, 'name' => $newName1), array('collectionKey' => $key2, 'name' => $newName2)))), array("Content-Type: application/json", "If-Unmodified-Since-Version: " . $data['version'])); $this->assert200($response); $json = API::getJSONFromResponse($response); $this->assertCount(2, $json['success']); $xml = API::getCollectionXML($json['success']); $this->assertEquals(2, (int) array_shift($xml->xpath('/atom:feed/zapi:totalResults'))); $contents = $xml->xpath('/atom:feed/atom:entry/atom:content'); $content = json_decode(array_shift($contents)); $this->assertEquals($newName1, $content->name); $this->assertFalse($content->parentCollection); $content = json_decode(array_shift($contents)); $this->assertEquals($newName2, $content->name); $this->assertFalse($content->parentCollection); }
private function _testMultiObjectWriteInvalidObject($objectType) { $objectTypePlural = API::getPluralObjectType($objectType); $response = API::userPost(self::$config['userID'], "{$objectTypePlural}?key=" . self::$config['apiKey'], json_encode([[]]), array("Content-Type: application/json")); $this->assert400($response, "Uploaded data must be a JSON object"); $response = API::userPost(self::$config['userID'], "{$objectTypePlural}?key=" . self::$config['apiKey'], json_encode(array("{$objectTypePlural}" => array("foo" => "bar"))), array("Content-Type: application/json")); $this->assert400($response, "'{$objectTypePlural}' must be an array"); }