Mostly-implemented basic AtomPub tests
This commit is contained in:
parent
654d1749da
commit
3426f65736
@ -1,7 +1,49 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
/*
|
||||
* StatusNet - the distributed open-source microblogging tool
|
||||
* Copyright (C) 2010, StatusNet, Inc.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
define('INSTALLDIR', realpath(dirname(__FILE__) . '/../..'));
|
||||
|
||||
$shortoptions = 'n:p:';
|
||||
$longoptions = array('nickname=', 'password=', 'dry-run');
|
||||
|
||||
$helptext = <<<END_OF_HELP
|
||||
USAGE: atompub_test.php [options]
|
||||
|
||||
Runs some tests on the AtomPub interface for the site. You must provide
|
||||
a user account to authenticate as; it will be used to make some test
|
||||
posts on the site.
|
||||
|
||||
Options:
|
||||
-n<user> --nickname=<user> Nickname of account to post as
|
||||
-p<pass> --password=<pass> Password for account
|
||||
--dry-run Skip tests that modify the site (post, delete)
|
||||
|
||||
END_OF_HELP;
|
||||
|
||||
require_once INSTALLDIR.'/scripts/commandline.inc';
|
||||
|
||||
class AtomPubClient
|
||||
{
|
||||
public $url;
|
||||
private $user, $pass;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string $url collection feed URL
|
||||
@ -16,76 +58,212 @@ class AtomPubClient
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $baseUrl to attempt feed discovery from
|
||||
* @return AtomPubClient
|
||||
* Set up an HTTPClient with auth for our resource.
|
||||
*
|
||||
* @param string $method
|
||||
* @return HTTPClient
|
||||
*/
|
||||
static function discoverFromPage($baseUrl)
|
||||
private function httpClient($method='GET')
|
||||
{
|
||||
|
||||
$client = new HTTPClient($this->url, 'GET');
|
||||
// basic auth, whee
|
||||
$client->setAuth($this->user, $this->password);
|
||||
return $client;
|
||||
}
|
||||
|
||||
function get()
|
||||
{
|
||||
|
||||
$client = $this->httpClient('GET');
|
||||
$response = $client->send();
|
||||
if ($response->isOk()) {
|
||||
return $response->getBody();
|
||||
} else {
|
||||
throw new Exception("Bogus return code: " . $response->getStatus());
|
||||
}
|
||||
}
|
||||
|
||||
function post($stuff, $type='application/atom+xml;type=entry')
|
||||
/**
|
||||
* Create a new resource by POSTing it to the collection.
|
||||
* If successful, will return the URL representing the
|
||||
* canonical location of the new resource. Neat!
|
||||
*
|
||||
* @param string $data
|
||||
* @param string $type defaults to Atom entry
|
||||
* @return string URL to the created resource
|
||||
*
|
||||
* @throws exceptions on failure
|
||||
*/
|
||||
function post($data, $type='application/atom+xml;type=entry')
|
||||
{
|
||||
// post it up!
|
||||
// optional 'Slug' header too
|
||||
// .. receive ..
|
||||
if ($response->getStatus() == '201') {
|
||||
// yay
|
||||
// MUST have a "Location" header
|
||||
// if it has a Content-Location header, it MUST match Location
|
||||
// and if it does, check the response body -- it should match what we posted, more or less.
|
||||
} else {
|
||||
$client = $this->httpClient('POST');
|
||||
$client->setHeader('Content-Type', $type);
|
||||
// optional Slug header not used in this case
|
||||
$client->setBody($data);
|
||||
$response = $client->send();
|
||||
|
||||
if ($response->getStatus() != '201') {
|
||||
throw new Exception("Expected HTTP 201 on POST, got " . $response->getStatus());
|
||||
}
|
||||
$loc = $response->getHeader('Location');
|
||||
$contentLoc = $response->getHeader('Content-Location');
|
||||
|
||||
if (empty($loc)) {
|
||||
throw new Exception("AtomPub POST response missing Location header.");
|
||||
}
|
||||
if (!empty($contentLoc)) {
|
||||
if ($loc != $contentLoc) {
|
||||
throw new Exception("AtomPub POST response Location and Content-Location headers do not match.");
|
||||
}
|
||||
|
||||
// If Content-Location and Location match, that means the response
|
||||
// body is safe to interpret as the resource itself.
|
||||
if ($type == 'application/atom+xml;type=entry') {
|
||||
self::validateAtomEntry($response->getBody());
|
||||
}
|
||||
}
|
||||
|
||||
return $loc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Note that StatusNet currently doesn't allow PUT editing on notices.
|
||||
*
|
||||
* @param string $data
|
||||
* @param string $type defaults to Atom entry
|
||||
* @return true on success
|
||||
*
|
||||
* @throws exceptions on failure
|
||||
*/
|
||||
function put($data, $type='application/atom+xml;type=entry')
|
||||
{
|
||||
// PUT it up!
|
||||
// must get a 200 back.
|
||||
// unlike post, we don't get the location too.
|
||||
$client = $this->httpClient('PUT');
|
||||
$client->setHeader('Content-Type', $type);
|
||||
$client->setBody($data);
|
||||
$response = $client->send();
|
||||
|
||||
if ($response->getStatus() != '200' && $response->getStatus() != '204') {
|
||||
throw new Exception("Expected HTTP 200 or 204 on PUT, got " . $response->getStatus());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the resource.
|
||||
*
|
||||
* @return true on success
|
||||
*
|
||||
* @throws exceptions on failure
|
||||
*/
|
||||
function delete()
|
||||
{
|
||||
$client = $this->httpClient('GET');
|
||||
$client->setBody($data);
|
||||
$response = $client->send();
|
||||
|
||||
if ($response->getStatus() != '200' && $response->getStatus() != '204') {
|
||||
throw new Exception("Expected HTTP 200 or 204 on DELETE, got " . $response->getStatus());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the given string is a parseable Atom entry.
|
||||
*
|
||||
* @param string $str
|
||||
* @return boolean
|
||||
* @throws Exception on invalid input
|
||||
*/
|
||||
static function validateAtomEntry($str)
|
||||
{
|
||||
if (empty($str)) {
|
||||
throw new Exception('Bad Atom entry: empty');
|
||||
}
|
||||
$dom = new DOMDocument;
|
||||
if (!$dom->loadXML($str)) {
|
||||
throw new Exception('Bad Atom entry: XML is not well formed.');
|
||||
}
|
||||
|
||||
$activity = new Activity($dom);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$user = get_option_value('n', 'nickname');
|
||||
$pass = get_option_value('p', 'password');
|
||||
|
||||
if (!$user) {
|
||||
die("Must set a user: --nickname=<username>\n");
|
||||
}
|
||||
if (!$pass) {
|
||||
die("Must set a password: --password=<username>\n");
|
||||
}
|
||||
|
||||
// discover the feed...
|
||||
// @fixme will this actually work?
|
||||
$url = common_local_url('ApiTimelineUser', array('format' => 'atom', 'id' => $user));
|
||||
|
||||
echo "Collection URL is: $url\n";
|
||||
|
||||
$collection = new AtomPubClient($url, $user, $pass);
|
||||
|
||||
// confirm the feed has edit links ..... ?
|
||||
|
||||
$pub = new AtomPubClient($url, $user, $pass);
|
||||
// $atom = '';
|
||||
|
||||
// post!
|
||||
$target = $pub->post($atom);
|
||||
echo "Posting a new message... ";
|
||||
$noticeUrl = $collection->post($atom);
|
||||
echo "ok, got $noticeUrl\n";
|
||||
|
||||
// make sure it's accessible
|
||||
// fetch $target -- should give us the atom entry
|
||||
echo "Fetching the new notice... ";
|
||||
$notice = new AtomPubClient($noticeUrl, $user, $pass);
|
||||
$body = $notice->get();
|
||||
AtomPubClient::validateAtomEntry($body);
|
||||
echo "ok\n";
|
||||
|
||||
echo "Confirming new entry looks right... ";
|
||||
// confirm that it actually is what we expected
|
||||
// confirm it has an edit URL that matches $target
|
||||
echo "NYI\n";
|
||||
|
||||
echo "Refetching the collection... ";
|
||||
$feed = $collection->get();
|
||||
echo "ok\n";
|
||||
|
||||
echo "Confirming new entry is in the feed... ";
|
||||
// make sure the new entry is in there
|
||||
// edit URL should match
|
||||
echo "NYI\n";
|
||||
|
||||
// refetch the feed; make sure the new entry is in there
|
||||
// edit URL should match
|
||||
|
||||
// try editing! it should fail.
|
||||
echo "Editing notice (should fail)... ";
|
||||
try {
|
||||
$target2 = $pub->put($target, $atom2);
|
||||
// FAIL! this shouldn't work.
|
||||
} catch (AtomPubPermissionDeniedException $e) {
|
||||
// yay
|
||||
$notice->put($target, $atom2);
|
||||
die("ERROR: editing a notice should have failed.\n");
|
||||
} catch (Exception $e) {
|
||||
echo "ok (failed as expected)\n";
|
||||
}
|
||||
|
||||
// try deleting!
|
||||
$pub->delete();
|
||||
|
||||
// fetch $target -- it should be gone now
|
||||
|
||||
// fetch the feed again; the new entry should be gone again
|
||||
|
||||
echo "Deleting notice... ";
|
||||
$notice->delete();
|
||||
echo "ok\n";
|
||||
|
||||
echo "Refetching deleted notice to confirm it's gone... ";
|
||||
try {
|
||||
$body = $notice->get();
|
||||
die("ERROR: notice should be gone now.\n");
|
||||
} catch (Exception $e) {
|
||||
echo "ok\n";
|
||||
}
|
||||
|
||||
echo "Refetching the collection.. ";
|
||||
$feed = $collection->get();
|
||||
echo "ok\n";
|
||||
|
||||
echo "Confirming deleted notice is no longer in the feed... ";
|
||||
echo "NYI\n";
|
||||
|
||||
// make subscriptions
|
||||
// make some posts
|
||||
|
Loading…
Reference in New Issue
Block a user