]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - tests/atompub/atompub_test.php
ebe948572246e7e79754fbb8385b2ef7a0fd833d
[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, 'GET');
69         // basic auth, whee
70         $client->setAuth($this->user, $this->password);
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());
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());
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());
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());
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);
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 // $atom = '';
215
216 // post!
217 echo "Posting a new message... ";
218 $noticeUrl = $collection->post($atom);
219 echo "ok, got $noticeUrl\n";
220
221 echo "Fetching the new notice... ";
222 $notice = new AtomPubClient($noticeUrl, $user, $pass);
223 $body = $notice->get();
224 AtomPubClient::validateAtomEntry($body);
225 echo "ok\n";
226
227 echo "Confirming new entry looks right... ";
228 // confirm that it actually is what we expected
229 // confirm it has an edit URL that matches $target
230 echo "NYI\n";
231
232 echo "Refetching the collection... ";
233 $feed = $collection->get();
234 echo "ok\n";
235
236 echo "Confirming new entry is in the feed... ";
237 // make sure the new entry is in there
238 //  edit URL should match
239 echo "NYI\n";
240
241 echo "Editing notice (should fail)... ";
242 try {
243     $notice->put($target, $atom2);
244     die("ERROR: editing a notice should have failed.\n");
245 } catch (Exception $e) {
246     echo "ok (failed as expected)\n";
247 }
248
249 echo "Deleting notice... ";
250 $notice->delete();
251 echo "ok\n";
252
253 echo "Refetching deleted notice to confirm it's gone... ";
254 try {
255     $body = $notice->get();
256     die("ERROR: notice should be gone now.\n");
257 } catch (Exception $e) {
258     echo "ok\n";
259 }
260
261 echo "Refetching the collection.. ";
262 $feed = $collection->get();
263 echo "ok\n";
264
265 echo "Confirming deleted notice is no longer in the feed... ";
266 echo "NYI\n";
267
268 // make subscriptions
269 // make some posts
270 // make sure the posts go through or not depending on the subs
271 // remove subscriptions
272 // test that they don't go through now
273
274 // group memberships too
275
276
277
278
279 // make sure we can't post to someone else's feed!
280 // make sure we can't delete someone else's messages
281 // make sure we can't create/delete someone else's subscriptions
282 // make sure we can't create/delete someone else's group memberships
283