]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - tests/atompub/atompub_test.php
AtomPub tests: fix for atom post check
[quix0rs-gnu-social.git] / tests / atompub / atompub_test.php
1 #!/usr/bin/env php
2 <?php
3 /*
4  * StatusNet - the distributed open-source microblogging tool
5  * Copyright (C) 2010, StatusNet, Inc.
6  *
7  * This program is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU Affero General Public License as published by
9  * the Free Software Foundation, either version 3 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU Affero General Public License for more details.
16  *
17  * You should have received a copy of the GNU Affero General Public License
18  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
19  */
20
21 define('INSTALLDIR', realpath(dirname(__FILE__) . '/../..'));
22
23 $shortoptions = 'n:p:';
24 $longoptions = array('nickname=', 'password=', 'dry-run');
25
26 $helptext = <<<END_OF_HELP
27 USAGE: atompub_test.php [options]
28
29 Runs some tests on the AtomPub interface for the site. You must provide
30 a user account to authenticate as; it will be used to make some test
31 posts on the site.
32
33 Options:
34   -n<user>  --nickname=<user>  Nickname of account to post as
35   -p<pass>  --password=<pass>  Password for account
36   --dry-run                    Skip tests that modify the site (post, delete)
37
38 END_OF_HELP;
39
40 require_once INSTALLDIR.'/scripts/commandline.inc';
41
42 class AtomPubClient
43 {
44     public $url;
45     private $user, $pass;
46
47     /**
48      *
49      * @param string $url collection feed URL
50      * @param string $user auth username
51      * @param string $pass auth password
52      */
53     function __construct($url, $user, $pass)
54     {
55         $this->url = $url;
56         $this->user = $user;
57         $this->pass = $pass;
58     }
59
60     /**
61      * Set up an HTTPClient with auth for our resource.
62      *
63      * @param string $method
64      * @return HTTPClient
65      */
66     private function httpClient($method='GET')
67     {
68         $client = new HTTPClient($this->url);
69         $client->setMethod($method);
70         $client->setAuth($this->user, $this->pass);
71         return $client;
72     }
73
74     function get()
75     {
76         $client = $this->httpClient('GET');
77         $response = $client->send();
78         if ($response->isOk()) {
79             return $response->getBody();
80         } else {
81             throw new Exception("Bogus return code: " . $response->getStatus() . ': ' . $response->getBody());
82         }
83     }
84
85     /**
86      * Create a new resource by POSTing it to the collection.
87      * If successful, will return the URL representing the
88      * canonical location of the new resource. Neat!
89      *
90      * @param string $data
91      * @param string $type defaults to Atom entry
92      * @return string URL to the created resource
93      *
94      * @throws exceptions on failure
95      */
96     function post($data, $type='application/atom+xml;type=entry')
97     {
98         $client = $this->httpClient('POST');
99         $client->setHeader('Content-Type', $type);
100         // optional Slug header not used in this case
101         $client->setBody($data);
102         $response = $client->send();
103
104         if ($response->getStatus() != '201') {
105             throw new Exception("Expected HTTP 201 on POST, got " . $response->getStatus() . ': ' . $response->getBody());
106         }
107         $loc = $response->getHeader('Location');
108         $contentLoc = $response->getHeader('Content-Location');
109
110         if (empty($loc)) {
111             throw new Exception("AtomPub POST response missing Location header.");
112         }
113         if (!empty($contentLoc)) {
114             if ($loc != $contentLoc) {
115                 throw new Exception("AtomPub POST response Location and Content-Location headers do not match.");
116             }
117
118             // If Content-Location and Location match, that means the response
119             // body is safe to interpret as the resource itself.
120             if ($type == 'application/atom+xml;type=entry') {
121                 self::validateAtomEntry($response->getBody());
122             }
123         }
124
125         return $loc;
126     }
127
128     /**
129      * Note that StatusNet currently doesn't allow PUT editing on notices.
130      *
131      * @param string $data
132      * @param string $type defaults to Atom entry
133      * @return true on success
134      *
135      * @throws exceptions on failure
136      */
137     function put($data, $type='application/atom+xml;type=entry')
138     {
139         $client = $this->httpClient('PUT');
140         $client->setHeader('Content-Type', $type);
141         $client->setBody($data);
142         $response = $client->send();
143
144         if ($response->getStatus() != '200' && $response->getStatus() != '204') {
145             throw new Exception("Expected HTTP 200 or 204 on PUT, got " . $response->getStatus() . ': ' . $response->getBody());
146         }
147
148         return true;
149     }
150
151     /**
152      * Delete the resource.
153      *
154      * @return true on success
155      *
156      * @throws exceptions on failure
157      */
158     function delete()
159     {
160         $client = $this->httpClient('GET');
161         $client->setBody($data);
162         $response = $client->send();
163
164         if ($response->getStatus() != '200' && $response->getStatus() != '204') {
165             throw new Exception("Expected HTTP 200 or 204 on DELETE, got " . $response->getStatus() . ': ' . $response->getBody());
166         }
167
168         return true;
169     }
170
171     /**
172      * Ensure that the given string is a parseable Atom entry.
173      *
174      * @param string $str
175      * @return boolean
176      * @throws Exception on invalid input
177      */
178     static function validateAtomEntry($str)
179     {
180         if (empty($str)) {
181             throw new Exception('Bad Atom entry: empty');
182         }
183         $dom = new DOMDocument;
184         if (!$dom->loadXML($str)) {
185             throw new Exception('Bad Atom entry: XML is not well formed.');
186         }
187
188         $activity = new Activity($dom->documentRoot);
189         return true;
190     }
191 }
192
193
194 $user = get_option_value('n', 'nickname');
195 $pass = get_option_value('p', 'password');
196
197 if (!$user) {
198     die("Must set a user: --nickname=<username>\n");
199 }
200 if (!$pass) {
201     die("Must set a password: --password=<username>\n");
202 }
203
204 // discover the feed...
205 // @fixme will this actually work?
206 $url = common_local_url('ApiTimelineUser', array('format' => 'atom', 'id' => $user));
207
208 echo "Collection URL is: $url\n";
209
210 $collection = new AtomPubClient($url, $user, $pass);
211
212 // confirm the feed has edit links ..... ?
213
214 echo "Posting an empty message (should fail)... ";
215 try {
216     $noticeUrl = $collection->post('');
217     die("FAILED, succeeded!\n");
218 } catch (Exception $e) {
219     echo "ok\n";
220 }
221
222 echo "Posting an invalid XML message (should fail)... ";
223 try {
224     $noticeUrl = $collection->post('<feed<entry>barf</yomomma>');
225     die("FAILED, succeeded!\n");
226 } catch (Exception $e) {
227     echo "ok\n";
228 }
229
230 echo "Posting a valid XML but non-Atom message (should fail)... ";
231 try {
232     $noticeUrl = $collection->post('<feed xmlns="http://notatom.com"><id>arf</id><entry><id>barf</id></entry></feed>');
233     die("FAILED, succeeded!\n");
234 } catch (Exception $e) {
235     echo "ok\n";
236 }
237
238 // post!
239 $rand = mt_rand(0, 99999);
240 $atom = <<<END_ATOM
241 <entry xmlns="http://www.w3.org/2005/Atom">
242     <title>This is an AtomPub test post title ($rand)</title>
243     <content>This is an AtomPub test post content ($rand)</content>
244 </entry>
245 END_ATOM;
246
247 echo "Posting a new message... ";
248 $noticeUrl = $collection->post($atom);
249 echo "ok, got $noticeUrl\n";
250
251 echo "Fetching the new notice... ";
252 $notice = new AtomPubClient($noticeUrl, $user, $pass);
253 $body = $notice->get();
254 AtomPubClient::validateAtomEntry($body);
255 echo "ok\n";
256
257 echo "Confirming new entry looks right... ";
258 // confirm that it actually is what we expected
259 // confirm it has an edit URL that matches $target
260 echo "NYI\n";
261
262 echo "Refetching the collection... ";
263 $feed = $collection->get();
264 echo "ok\n";
265
266 echo "Confirming new entry is in the feed... ";
267 // make sure the new entry is in there
268 //  edit URL should match
269 echo "NYI\n";
270
271 echo "Editing notice (should fail)... ";
272 try {
273     $notice->put($target, $atom2);
274     die("ERROR: editing a notice should have failed.\n");
275 } catch (Exception $e) {
276     echo "ok (failed as expected)\n";
277 }
278
279 echo "Deleting notice... ";
280 $notice->delete();
281 echo "ok\n";
282
283 echo "Refetching deleted notice to confirm it's gone... ";
284 try {
285     $body = $notice->get();
286     var_dump($body);
287     die("ERROR: notice should be gone now.\n");
288 } catch (Exception $e) {
289     echo "ok\n";
290 }
291
292 echo "Refetching the collection.. ";
293 $feed = $collection->get();
294 echo "ok\n";
295
296 echo "Confirming deleted notice is no longer in the feed... ";
297 echo "NYI\n";
298
299 // make subscriptions
300 // make some posts
301 // make sure the posts go through or not depending on the subs
302 // remove subscriptions
303 // test that they don't go through now
304
305 // group memberships too
306
307
308
309
310 // make sure we can't post to someone else's feed!
311 // make sure we can't delete someone else's messages
312 // make sure we can't create/delete someone else's subscriptions
313 // make sure we can't create/delete someone else's group memberships
314