tests: Catch an exception
[profile/ivi/rygel.git] / tests / rygel-http-post-test.vala
1 /*
2  * Copyright (C) 2010 Nokia Corporation.
3  *
4  * Author: Zeeshan Ali (Khattak) <zeeshanak@gnome.org>
5  *                               <zeeshan.ali@nokia.com>
6  *
7  * This file is part of Rygel.
8  *
9  * Rygel is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU Lesser General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * Rygel is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public License
20  * along with this program; if not, write to the Free Software Foundation,
21  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22  */
23
24 using Soup;
25 using Gee;
26
27 public errordomain Rygel.TestError {
28     SKIP = 77,
29     TIMEOUT
30 }
31
32 public errordomain Rygel.ClientHacksError {
33     NA
34 }
35
36 public class Rygel.ClientHacks {
37     public static ClientHacks create (Message? message) throws Error {
38         throw new ClientHacksError.NA ("");
39     }
40
41     public void apply (MediaItem item) {
42     }
43 }
44
45 public class Rygel.TestRequestFactory {
46     public Soup.Message msg;
47     public Soup.KnownStatusCode? expected_code;
48     public bool cancel;
49
50     public TestRequestFactory (Soup.Message msg,
51                                Soup.KnownStatusCode? expected_code,
52                                bool cancel = false) {
53         this.msg = msg;
54         this.expected_code = expected_code;
55         this.cancel = cancel;
56     }
57
58     internal HTTPPost create_post (HTTPServer http_server,
59                                    Soup.Server server,
60                                    Soup.Message msg) {
61         HTTPPost request = new HTTPPost (http_server, server, msg);
62
63         return request;
64     }
65 }
66
67 public class Rygel.HTTPPostTest : GLib.Object {
68     protected HTTPServer server;
69     protected HTTPClient client;
70     private bool server_done;
71     private bool client_done;
72     private bool ready;
73
74     private MainLoop main_loop;
75     private Error error;
76
77     private ArrayList<TestRequestFactory> requests;
78     private TestRequestFactory current_request;
79
80     public static int main (string[] args) {
81         try {
82             var test = new HTTPPostTest ();
83
84             test.run ();
85         } catch (TestError.SKIP error) {
86             return error.code;
87         } catch (Error error) {
88             critical ("%s", error.message);
89
90             return -1;
91         }
92
93         return 0;
94     }
95
96     public HTTPPostTest () throws Error {
97         this.server = new HTTPServer ();
98         this.client = new HTTPClient (this.server.context);
99         this.main_loop = new MainLoop (null, false);
100         this.create_test_messages();
101     }
102
103     public virtual void run () throws Error {
104         // cleanup
105         var file = File.new_for_uri (MediaItem.URI);
106         FileUtils.remove (file.get_path ());
107
108         Timeout.add_seconds (3, this.on_timeout);
109         this.server.message_received.connect (this.on_message_received);
110         this.client.completed.connect (this.on_client_completed);
111
112         this.start_next_test_request ();
113
114         this.main_loop.run ();
115
116         if (this.error != null) {
117             throw this.error;
118         }
119     }
120
121     private void create_test_messages () {
122         requests = new ArrayList<TestRequestFactory> ();
123
124         var request = new Soup.Message ("POST", this.server.uri);
125         requests.add (new TestRequestFactory (request, Soup.KnownStatusCode.OK));
126
127         request = new Soup.Message ("POST", this.server.uri);
128         requests.add (new TestRequestFactory (request,
129                                              Soup.KnownStatusCode.NOT_FOUND));
130
131         request = new Soup.Message ("POST", this.server.create_uri ("NullItem"));
132         requests.add (new TestRequestFactory (request,
133                                               Soup.KnownStatusCode.BAD_REQUEST));
134
135         request = new Soup.Message ("POST",
136                                     this.server.create_uri ("ErrorItem"));
137         requests.add (new TestRequestFactory (request,
138                                               Soup.KnownStatusCode.OK));
139
140         request = new Soup.Message ("POST",
141                                     this.server.create_uri ("CancelItem"));
142         requests.add (new TestRequestFactory (request,
143                                               Soup.KnownStatusCode.OK, true));
144
145         request = new Soup.Message ("POST",
146                                     this.server.create_uri ("VanishingItem"));
147         requests.add (new TestRequestFactory (request, Soup.KnownStatusCode.OK));
148     }
149
150     private HTTPRequest create_request (Soup.Message msg) throws Error {
151         var srv = this.server.context.server;
152         var request = this.current_request.create_post (this.server, srv, msg);
153
154         return request;
155     }
156
157     private void start_next_test_request() {
158         this.current_request = requests.remove_at (0);
159         this.client.msg = this.current_request.msg;
160
161         this.client.run.begin ();
162     }
163
164     private void on_client_completed (StateMachine client) {
165         if (requests.size > 0) {
166             if (this.server_done) {
167                 this.server_done = false;
168                 this.start_next_test_request ();
169             } else {
170                 this.ready = true;
171             }
172         } else {
173             if (this.server_done) {
174                 this.main_loop.quit ();
175             }
176             this.client_done = true;
177         }
178     }
179
180     private void on_message_received (HTTPServer   server,
181                                       Soup.Message msg) {
182         this.handle_client_message.begin (msg);
183     }
184
185     private async void handle_client_message (Soup.Message msg) {
186         try {
187             var request = this.create_request (msg);
188
189             if (this.current_request.cancel) {
190                 request.cancellable.cancel ();
191             } else {
192                 yield request.run ();
193
194                 debug ("status.code: %d", (int) msg.status_code);
195                 assert (msg.status_code == this.current_request.expected_code);
196
197                 this.check_result.begin ();
198             }
199
200             if (this.client_done) {
201                 this.main_loop.quit ();
202             } else if (this.ready) {
203                 this.ready = false;
204
205                 start_next_test_request ();
206             } else {
207                 this.server_done = true;
208             }
209         } catch (Error error) {
210             this.error = error;
211             this.main_loop.quit ();
212
213             return;
214         }
215     }
216
217     private bool on_timeout () {
218         this.error = new TestError.TIMEOUT ("Timeout");
219         this.main_loop.quit ();
220
221         return false;
222     }
223
224     private async void check_result () {
225         try {
226             var file = this.server.root_container.item.file;
227             var stream = yield file.read_async (Priority.HIGH, null);
228             var buffer = new uint8[HTTPClient.LENGTH];
229             yield stream.read_async (buffer, Priority.HIGH, null);
230
231             for (var i = 0; i < HTTPClient.LENGTH; i++) {
232                 assert (buffer[i] == this.client.content[i]);
233             }
234         } catch (IOError.NOT_FOUND e) {
235             return;
236         } catch (Error error) {
237             this.error = error;
238
239             this.main_loop.quit ();
240         }
241     }
242 }
243
244 public class Rygel.HTTPServer : GLib.Object {
245     private const string SERVER_PATH = "/RygelHTTPServer/Rygel/Test";
246     public string path_root {
247         get {
248             return SERVER_PATH;
249         }
250     }
251
252     public MediaContainer root_container;
253     public GUPnP.Context context;
254
255     public string uri {
256         owned get {
257                         var item = new MediaItem (MediaContainer.ITEM_ID, this.root_container);
258                         var item_uri = new HTTPItemURI (item, this);
259             return item_uri.to_string ();
260         }
261     }
262
263     public string create_uri(string item_id) {
264         var item = new MediaItem (item_id, this.root_container);
265         var item_uri = new HTTPItemURI (item, this);
266         return item_uri.to_string ();
267     }
268
269     public signal void message_received (Soup.Message message);
270
271     public HTTPServer () throws TestError {
272         try {
273             this.context = new GUPnP.Context (null, "lo", 0);
274         } catch (Error error) {
275             throw new TestError.SKIP ("Network context not available");
276         }
277
278         assert (this.context != null);
279         assert (this.context.host_ip != null);
280         assert (this.context.port > 0);
281
282         context.server.request_started.connect (this.on_request_started);
283
284         this.root_container = new MediaContainer ();
285     }
286
287     private void on_request_started (Soup.Server        server,
288                                      Soup.Message       msg,
289                                      Soup.ClientContext client) {
290         msg.got_headers.connect (this.on_got_headers);
291     }
292
293     private void on_got_headers (Soup.Message msg) {
294         this.message_received (msg);
295     }
296
297     public Transcoder get_transcoder (string target) throws Error {
298         if (target == "MP3") {
299             return new Transcoder ("mp3");
300         }
301         throw new HTTPRequestError.NOT_FOUND (
302                             "No transcoder available for target format '%s'",
303                             target);
304     }
305 }
306
307 public class Rygel.HTTPClient : GLib.Object, StateMachine {
308     public const size_t LENGTH = 1024;
309
310     public uint8[] content;
311
312     public GUPnP.Context context;
313     public Soup.Message msg;
314
315     public Cancellable cancellable { get; set; }
316
317     public HTTPClient (GUPnP.Context context) {
318         this.context = context;
319         this.content = new uint8[1024];
320     }
321
322     public async void run () {
323         SourceFunc run_continue = run.callback;
324
325         this.msg.request_body.append (MemoryUse.COPY, content);
326
327         this.context.session.queue_message (this.msg, (session, msg) => {
328             run_continue ();
329         });
330
331         yield;
332
333         this.completed ();
334     }
335 }
336
337 public class Rygel.MediaContainer : Rygel.MediaObject {
338     public const string ITEM_ID = "TestItem";
339
340     public signal void container_updated (MediaContainer container);
341
342     public MediaItem item;
343     private bool vanish;
344     private bool error;
345
346     public File file;
347     private FileMonitor monitor;
348
349     public MediaContainer () {
350         this.file = File.new_for_uri (MediaItem.URI);
351         this.item = new MediaItem (ITEM_ID, this);
352         this.vanish = false;
353         this.error = false;
354         this.id = "TesContainer";
355
356         try {
357             this.monitor = this.file.monitor_file (FileMonitorFlags.NONE);
358         } catch (GLib.Error error) {
359             assert_not_reached ();
360         }
361
362         this.monitor.changed.connect (this.on_file_changed);
363     }
364
365     public async MediaObject? find_object (string       item_id,
366                                            Cancellable? cancellable)
367                                            throws Error {
368         SourceFunc find_object_continue = find_object.callback;
369         Idle.add (() => {
370             find_object_continue ();
371
372             return false;
373         });
374
375         yield;
376
377         if (item_id == "ErrorItem" && this.error) {
378             var msg = _("Fake error caused by %s object.");
379             this.file.delete (null);
380
381             throw new ContentDirectoryError.INVALID_ARGS (msg, item_id);
382         } else if (item_id == "ErrorItem" && !this.error) {
383             this.error = true;
384         }
385
386         if (item_id == "VanishingItem" && this.vanish) {
387             this.file.delete (null);
388
389             return null;
390         } else if (item_id == "VanishingItem" && !this.vanish) {
391             this.vanish = true;
392         }
393
394         if (item_id != this.item.id) {
395             this.item = new MediaItem (item_id, this);
396         }
397
398         return this.item;
399     }
400
401     public void on_file_changed (FileMonitor      monitor,
402                                  File             file,
403                                  File?            other_file,
404                                  FileMonitorEvent event_type) {
405         this.item.place_holder = false;
406
407         this.container_updated (this);
408     }
409
410     ~MediaContainer() {
411         try {
412             this.file.delete (null);
413         } catch (GLib.Error error) {
414             assert_not_reached ();
415         }
416     }
417 }
418
419 public class Rygel.MediaItem : Rygel.MediaObject {
420     public const string URI = "file:///tmp/rygel-upload-test.wav";
421
422     public long size = 1024;
423     public long duration = 1024;
424     public ArrayList<string> uris = new ArrayList<string> ();
425
426     public bool place_holder = true;
427
428     public File file;
429
430     public MediaItem.for_visual_item () {}
431
432     public MediaItem (string id, MediaContainer parent) {
433         this.id = id;
434         this.parent = parent;
435
436         this.file = parent.file;
437     }
438
439     public async File? get_writable (Cancellable? cancellable) throws Error {
440         SourceFunc get_writable_continue = get_writable.callback;
441
442         Idle.add (() => {
443             get_writable_continue ();
444
445             return false;
446         });
447
448         yield;
449
450         if (this.id == "NullItem") {
451             this.file.delete (null);
452
453             return null;
454         } else {
455             return this.file;
456         }
457     }
458 }
459
460 internal class Rygel.HTTPResponse : Rygel.StateMachine, GLib.Object {
461     public Cancellable cancellable { get; set; }
462
463     private Soup.Message msg;
464     private Soup.Server server;
465
466     public HTTPResponse (HTTPPost get_request) {
467         this.msg = get_request.msg;
468
469         this.server = get_request.server;
470     }
471
472     public async void run () {
473         SourceFunc run_continue = run.callback;
474
475         Idle.add (() => {
476             run_continue ();
477
478             return false;
479         });
480
481         yield;
482
483         this.msg.set_status (Soup.KnownStatusCode.OK);
484         this.server.unpause_message (msg);
485
486         this.completed ();
487     }
488 }
489
490 public class Rygel.ItemRemovalQueue: GLib.Object {
491     public static ItemRemovalQueue get_default () {
492        return new ItemRemovalQueue ();
493     }
494
495     public bool dequeue (MediaItem item) {
496         return true;
497     }
498
499     public async void remove_now (MediaItem item, Cancellable? cancellable) {
500         Idle.add (remove_now.callback);
501
502         yield;
503     }
504 }
505
506 public class Rygel.MediaObject : GLib.Object {
507     public string id;
508     public unowned MediaContainer parent;
509     public string mime_type = "";
510 }
511
512 public class Rygel.Thumbnail : GLib.Object {
513     public string file_extension;
514 }
515
516 public class Rygel.VisualItem : Rygel.MediaItem {
517     public ArrayList<Thumbnail> thumbnails = new ArrayList<Thumbnail> ();
518
519     public VisualItem () {
520         base.for_visual_item();
521     }
522 }
523
524 private class Rygel.Subtitle : GLib.Object {
525     public string caption_type;
526 }
527
528 private class Rygel.VideoItem : Rygel.VisualItem {
529     public ArrayList<Subtitle> subtitles = new ArrayList<Subtitle> ();
530 }
531
532 private class Rygel.MusicItem : MediaItem {
533     public Thumbnail album_art;
534
535     public MusicItem (string id, MediaContainer parent) {
536         base (id, parent);
537     }
538 }
539
540 public errordomain Rygel.ContentDirectoryError {
541     INVALID_ARGS = 402
542 }
543
544 public class Rygel.Transcoder : GLib.Object {
545     public string extension { get; protected set; }
546
547     public Transcoder (string extension) {
548         this.extension = extension;
549     }
550 }