]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - plugins/OStatus/tests/remote-tests.php
OStatus remote sending test cases. Doesn't actually run within PHPUnit right now...
[quix0rs-gnu-social.git] / plugins / OStatus / tests / remote-tests.php
1 <?php
2
3 if (php_sapi_name() != 'cli') {
4     die('not for web');
5 }
6
7 define('INSTALLDIR', dirname(dirname(dirname(dirname(__FILE__)))));
8 set_include_path(INSTALLDIR . '/extlib' . PATH_SEPARATOR . get_include_path());
9
10 require_once 'PEAR.php';
11 require_once 'Net/URL2.php';
12 require_once 'HTTP/Request2.php';
13
14
15 // ostatus test script, client-side :)
16
17 class TestBase
18 {
19     function log($str)
20     {
21         $args = func_get_args();
22         array_shift($args);
23
24         $msg = vsprintf($str, $args);
25         print $msg . "\n";
26     }
27
28     function assertEqual($a, $b)
29     {
30         if ($a != $b) {
31             throw new Exception("Failed to assert equality: expected $a, got $b");
32         }
33         return true;
34     }
35
36     function assertNotEqual($a, $b)
37     {
38         if ($a == $b) {
39             throw new Exception("Failed to assert inequality: expected not $a, got $b");
40         }
41         return true;
42     }
43 }
44
45 class OStatusTester extends TestBase
46 {
47     /**
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)
50      */
51     function __construct($a, $b) {
52         $this->a = $a;
53         $this->b = $b;
54
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));
58     }
59
60     function run()
61     {
62         $this->setup();
63         $this->testLocalPost();
64         $this->testMentionUrl();
65         $this->log("DONE!");
66     }
67
68     function setup()
69     {
70         $this->pub->register();
71         $this->pub->assertRegistered();
72
73         $this->sub->register();
74         $this->sub->assertRegistered();
75     }
76
77     function testLocalPost()
78     {
79         $post = $this->pub->post("Local post, no subscribers yet.");
80         $this->assertNotEqual('', $post);
81
82         $post = $this->sub->post("Local post, no subscriptions yet.");
83         $this->assertNotEqual('', $post);
84     }
85
86     /**
87      * pub posts: @b/sub
88      */
89     function testMentionUrl()
90     {
91         $bits = parse_url($this->b);
92         $base = $bits['host'];
93         if (isset($bits['path'])) {
94             $base .= $bits['path'];
95         }
96         $name = $this->sub->username;
97
98         $post = $this->pub->post("@$base/$name should have this in home and replies");
99         $this->sub->assertReceived($post);
100     }
101 }
102
103 class SNTestClient extends TestBase
104 {
105     function __construct($base, $username, $password)
106     {
107         $this->basepath = $base;
108         $this->username = $username;
109         $this->password = $password;
110
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';
115     }
116
117     /**
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
122      * @return string
123      * @throws Exception on low-level error conditions
124      */
125     protected function hit($path, $params=array(), $auth=false, $cookies=array())
126     {
127         $url = $this->basepath . '/' . $path;
128
129         $http = new HTTP_Request2($url, 'POST');
130         if ($auth) {
131             $http->setAuth($this->username, $this->password, HTTP_Request2::AUTH_BASIC);
132         }
133         foreach ($cookies as $name => $val) {
134             $http->addCookie($name, $val);
135         }
136         $http->addPostParameter($params);
137         $response = $http->send();
138
139         $code = $response->getStatus();
140         if ($code < '200' || $code >= '400') {
141             throw new Exception("Failed API hit to $url: $code\n" . $response->getBody());
142         }
143
144         return $response;
145     }
146
147     /**
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
152      */
153     protected function web($path, $form, $params=array())
154     {
155         $url = $this->basepath . '/' . $path;
156         $http = new HTTP_Request2($url, 'GET');
157         $response = $http->send();
158
159         $dom = $this->checkWeb($url, 'GET', $response);
160         $cookies = array();
161         foreach ($response->getCookies() as $cookie) {
162             // @fixme check for expirations etc
163             $cookies[$cookie['name']] = $cookie['value'];
164         }
165
166         $form = $dom->getElementById($form);
167         if (!$form) {
168             throw new Exception("Form $form not found on $url");
169         }
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;
178                 }
179             }
180         }
181
182         $response = $this->hit($path, $params, false, $cookies);
183         $dom = $this->checkWeb($url, 'POST', $response);
184
185         return $dom;
186     }
187
188     protected function checkWeb($url, $method, $response)
189     {
190         $dom = new DOMDocument();
191         if (!$dom->loadHTML($response->getBody())) {
192             throw new Exception("Invalid HTML from $method to $url");
193         }
194
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);
200         }
201
202         return $dom;
203     }
204
205     /**
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
212      */
213     protected function api($path, $style, $params=array())
214     {
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']);
222                 }
223                 return $data;
224             } else {
225                 throw new Exception("Bogus JSON data from $path:\n$body");
226             }
227         } else if ($style == 'xml' || $style == 'atom') {
228             $dom = new DOMDocument();
229             if ($dom->loadXML($body)) {
230                 return $dom;
231             } else {
232                 throw new Exception("Bogus XML data from $path:\n$body");
233             }
234         } else {
235             throw new Exception("API needs to be JSON, XML, or Atom");
236         }
237     }
238
239     /**
240      * Register the account.
241      *
242      * Unfortunately there's not an API method for registering, so we fake it.
243      */
244     function register()
245     {
246         $this->log("Registering user %s on %s",
247                    $this->username,
248                    $this->basepath);
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,
255                   'bio' => $this->bio,
256                   'license' => 1,
257                   'submit' => 'Register'));
258     }
259
260     /**
261      * Check that the account has been registered and can be used.
262      * On failure, throws a test failure exception.
263      */
264     function assertRegistered()
265     {
266         $this->log("Confirming %s is registered on %s",
267                    $this->username,
268                    $this->basepath);
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']);
274     }
275
276     /**
277      * Post a given message from this account
278      * @param string $message
279      * @return string URL/URI of notice
280      * @todo reply, location options
281      */
282     function post($message)
283     {
284         $this->log("Posting notice as %s on %s: %s",
285                    $this->username,
286                    $this->basepath,
287                    $message);
288         $data = $this->api('statuses/update', 'json',
289             array('status' => $message));
290
291         $url = $this->basepath . '/notice/' . $data['id'];
292         return $url;
293     }
294
295     /**
296      * Check that this account has received the notice.
297      * @param string $notice_uri URI for the notice to check for
298      */
299     function assertReceived($notice_uri)
300     {
301         $timeout = 5;
302         $tries = 6;
303         while ($tries) {
304             $ok = $this->checkReceived($notice_uri);
305             if ($ok) {
306                 return true;
307             }
308             $tries--;
309             if ($tries) {
310                 $this->log("Didn't see it yet, waiting $timeout seconds");
311                 sleep($timeout);
312             }
313         }
314         throw new Exception("Message $notice_uri not received by $this->username");
315     }
316
317     /**
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.
321      *
322      * @param string $notice_uri
323      */
324     function checkReceived($notice_uri)
325     {
326         $this->log("Checking if %s on %s received notice %s",
327                    $this->username,
328                    $this->basepath,
329                    $notice_uri);
330         $params = array();
331         $dom = $this->api('statuses/home_timeline', 'atom', $params);
332
333         $xml = simplexml_import_dom($dom);
334         if (!$xml->entry) {
335             return false;
336         }
337         if (is_array($xml->entry)) {
338             $entries = $xml->entry;
339         } else {
340             $entries = array($xml->entry);
341         }
342         foreach ($entries as $entry) {
343             if ($entry->id == $notice_uri) {
344                 $this->log("found it $notice_uri");
345                 return true;
346             }
347             //$this->log("nope... " . $entry->id);
348         }
349         return false;
350     }
351
352     /**
353      * Check that this account is subscribed to the given profile.
354      * @param string $profile_uri URI for the profile to check for
355      */
356     function assertHasSubscription($profile_uri)
357     {
358         throw new Exception('tbi');
359     }
360
361     /**
362      * Check that this account is subscribed to by the given profile.
363      * @param string $profile_uri URI for the profile to check for
364      */
365     function assertHasSubscriber($profile_uri)
366     {
367         throw new Exception('tbi');
368     }
369
370 }
371
372 $args = array_slice($_SERVER['argv'], 1);
373 if (count($args) < 2) {
374     print <<<END_HELP
375 remote-tests.php <url1> <url2>
376   url1: base URL of a StatusNet instance
377   url2: base URL of another StatusNet instance
378
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.
382
383 END_HELP;
384 exit(1);
385 }
386
387 $a = $args[0];
388 $b = $args[1];
389
390 $tester = new OStatusTester($a, $b);
391 $tester->run();
392