3 if (php_sapi_name() != 'cli') {
7 define('INSTALLDIR', dirname(dirname(dirname(dirname(__FILE__)))));
8 set_include_path(INSTALLDIR . '/extlib' . PATH_SEPARATOR . get_include_path());
10 require_once 'PEAR.php';
11 require_once 'Net/URL2.php';
12 require_once 'HTTP/Request2.php';
15 // ostatus test script, client-side :)
21 $args = func_get_args();
24 $msg = vsprintf($str, $args);
28 function assertEqual($a, $b)
31 throw new Exception("Failed to assert equality: expected $a, got $b");
36 function assertNotEqual($a, $b)
39 throw new Exception("Failed to assert inequality: expected not $a, got $b");
45 class OStatusTester extends TestBase
48 * @param string $a base URL of test site A (eg http://localhost/mublog)
49 * @param string $b base URL of test site B (eg http://localhost/mublog2)
51 function __construct($a, $b) {
55 $base = 'test' . mt_rand(1, 1000000);
56 $this->pub = new SNTestClient($this->a, 'pub' . $base, 'pw-' . mt_rand(1, 1000000));
57 $this->sub = new SNTestClient($this->b, 'sub' . $base, 'pw-' . mt_rand(1, 1000000));
63 $this->testLocalPost();
64 $this->testMentionUrl();
70 $this->pub->register();
71 $this->pub->assertRegistered();
73 $this->sub->register();
74 $this->sub->assertRegistered();
77 function testLocalPost()
79 $post = $this->pub->post("Local post, no subscribers yet.");
80 $this->assertNotEqual('', $post);
82 $post = $this->sub->post("Local post, no subscriptions yet.");
83 $this->assertNotEqual('', $post);
89 function testMentionUrl()
91 $bits = parse_url($this->b);
92 $base = $bits['host'];
93 if (isset($bits['path'])) {
94 $base .= $bits['path'];
96 $name = $this->sub->username;
98 $post = $this->pub->post("@$base/$name should have this in home and replies");
99 $this->sub->assertReceived($post);
103 class SNTestClient extends TestBase
105 function __construct($base, $username, $password)
107 $this->basepath = $base;
108 $this->username = $username;
109 $this->password = $password;
111 $this->fullname = ucfirst($username) . ' Smith';
112 $this->homepage = 'http://example.org/' . $username;
113 $this->bio = 'Stub account for OStatus tests.';
114 $this->location = 'Montreal, QC';
118 * Make a low-level web hit to this site, with authentication.
119 * @param string $path URL fragment for something under the base path
120 * @param array $params POST parameters to send
121 * @param boolean $auth whether to include auth data
123 * @throws Exception on low-level error conditions
125 protected function hit($path, $params=array(), $auth=false, $cookies=array())
127 $url = $this->basepath . '/' . $path;
129 $http = new HTTP_Request2($url, 'POST');
131 $http->setAuth($this->username, $this->password, HTTP_Request2::AUTH_BASIC);
133 foreach ($cookies as $name => $val) {
134 $http->addCookie($name, $val);
136 $http->addPostParameter($params);
137 $response = $http->send();
139 $code = $response->getStatus();
140 if ($code < '200' || $code >= '400') {
141 throw new Exception("Failed API hit to $url: $code\n" . $response->getBody());
148 * Make a hit to a web form, without authentication but with a session.
149 * @param string $path URL fragment relative to site base
150 * @param string $form id of web form to pull initial parameters from
151 * @param array $params POST parameters, will be merged with defaults in form
153 protected function web($path, $form, $params=array())
155 $url = $this->basepath . '/' . $path;
156 $http = new HTTP_Request2($url, 'GET');
157 $response = $http->send();
159 $dom = $this->checkWeb($url, 'GET', $response);
161 foreach ($response->getCookies() as $cookie) {
162 // @fixme check for expirations etc
163 $cookies[$cookie['name']] = $cookie['value'];
166 $form = $dom->getElementById($form);
168 throw new Exception("Form $form not found on $url");
170 $inputs = $form->getElementsByTagName('input');
171 foreach ($inputs as $item) {
172 $type = $item->getAttribute('type');
173 if ($type != 'check') {
174 $name = $item->getAttribute('name');
175 $val = $item->getAttribute('value');
176 if ($name && $val && !isset($params[$name])) {
177 $params[$name] = $val;
182 $response = $this->hit($path, $params, false, $cookies);
183 $dom = $this->checkWeb($url, 'POST', $response);
188 protected function checkWeb($url, $method, $response)
190 $dom = new DOMDocument();
191 if (!$dom->loadHTML($response->getBody())) {
192 throw new Exception("Invalid HTML from $method to $url");
195 $xpath = new DOMXPath($dom);
196 $error = $xpath->query('//p[@class="error"]');
197 if ($error && $error->length) {
198 throw new Exception("Error on $method to $url: " .
199 $error->item(0)->textContent);
206 * Make an API hit to this site, with authentication.
207 * @param string $path URL fragment for something under 'api' folder
208 * @param string $style one of 'json', 'xml', or 'atom'
209 * @param array $params POST parameters to send
210 * @return mixed associative array for JSON, DOMDocument for XML/Atom
211 * @throws Exception on low-level error conditions
213 protected function api($path, $style, $params=array())
215 $response = $this->hit("api/$path.$style", $params, true);
216 $body = $response->getBody();
217 if ($style == 'json') {
218 $data = json_decode($body, true);
219 if ($data !== null) {
220 if (!empty($data['error'])) {
221 throw new Exception("JSON API returned error: " . $data['error']);
225 throw new Exception("Bogus JSON data from $path:\n$body");
227 } else if ($style == 'xml' || $style == 'atom') {
228 $dom = new DOMDocument();
229 if ($dom->loadXML($body)) {
232 throw new Exception("Bogus XML data from $path:\n$body");
235 throw new Exception("API needs to be JSON, XML, or Atom");
240 * Register the account.
242 * Unfortunately there's not an API method for registering, so we fake it.
246 $this->log("Registering user %s on %s",
249 $ret = $this->web('main/register', 'form_register',
250 array('nickname' => $this->username,
251 'password' => $this->password,
252 'confirm' => $this->password,
253 'fullname' => $this->fullname,
254 'homepage' => $this->homepage,
257 'submit' => 'Register'));
261 * Check that the account has been registered and can be used.
262 * On failure, throws a test failure exception.
264 function assertRegistered()
266 $this->log("Confirming %s is registered on %s",
269 $data = $this->api('account/verify_credentials', 'json');
270 $this->assertEqual($this->username, $data['screen_name']);
271 $this->assertEqual($this->fullname, $data['name']);
272 $this->assertEqual($this->homepage, $data['url']);
273 $this->assertEqual($this->bio, $data['description']);
277 * Post a given message from this account
278 * @param string $message
279 * @return string URL/URI of notice
280 * @todo reply, location options
282 function post($message)
284 $this->log("Posting notice as %s on %s: %s",
288 $data = $this->api('statuses/update', 'json',
289 array('status' => $message));
291 $url = $this->basepath . '/notice/' . $data['id'];
296 * Check that this account has received the notice.
297 * @param string $notice_uri URI for the notice to check for
299 function assertReceived($notice_uri)
304 $ok = $this->checkReceived($notice_uri);
310 $this->log("Didn't see it yet, waiting $timeout seconds");
314 throw new Exception("Message $notice_uri not received by $this->username");
318 * Pull the user's home timeline to check if a notice with the given
319 * source URL has been received recently.
320 * If we don't see it, we'll try a couple more times up to 10 seconds.
322 * @param string $notice_uri
324 function checkReceived($notice_uri)
326 $this->log("Checking if %s on %s received notice %s",
331 $dom = $this->api('statuses/home_timeline', 'atom', $params);
333 $xml = simplexml_import_dom($dom);
337 if (is_array($xml->entry)) {
338 $entries = $xml->entry;
340 $entries = array($xml->entry);
342 foreach ($entries as $entry) {
343 if ($entry->id == $notice_uri) {
344 $this->log("found it $notice_uri");
347 //$this->log("nope... " . $entry->id);
353 * Check that this account is subscribed to the given profile.
354 * @param string $profile_uri URI for the profile to check for
356 function assertHasSubscription($profile_uri)
358 throw new Exception('tbi');
362 * Check that this account is subscribed to by the given profile.
363 * @param string $profile_uri URI for the profile to check for
365 function assertHasSubscriber($profile_uri)
367 throw new Exception('tbi');
372 $args = array_slice($_SERVER['argv'], 1);
373 if (count($args) < 2) {
375 remote-tests.php <url1> <url2>
376 url1: base URL of a StatusNet instance
377 url2: base URL of another StatusNet instance
379 This will register user accounts on the two given StatusNet instances
380 and run some tests to confirm that OStatus subscription and posting
381 between the two sites works correctly.
390 $tester = new OStatusTester($a, $b);