tests: Fix broken tests after d6f83bbe
[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 (this.root_container.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 string id = "TesContainer";
343     public MediaItem item;
344     private bool vanish;
345     private bool error;
346
347     public File file;
348     private FileMonitor monitor;
349
350     public MediaContainer () {
351         this.file = File.new_for_uri (MediaItem.URI);
352         this.item = new MediaItem (ITEM_ID, this);
353         this.vanish = false;
354         this.error = false;
355
356         this.monitor = this.file.monitor_file (FileMonitorFlags.NONE);
357         this.monitor.changed.connect (this.on_file_changed);
358     }
359
360     public async MediaObject? find_object (string       item_id,
361                                            Cancellable? cancellable)
362                                            throws Error {
363         SourceFunc find_object_continue = find_object.callback;
364         Idle.add (() => {
365             find_object_continue ();
366
367             return false;
368         });
369
370         yield;
371
372         if (item_id == "ErrorItem" && this.error) {
373             var msg = _("Fake error caused by %s object.");
374             this.file.delete (null);
375
376             throw new ContentDirectoryError.INVALID_ARGS (msg, item_id);
377         } else if (item_id == "ErrorItem" && !this.error) {
378             this.error = true;
379         }
380
381         if (item_id == "VanishingItem" && this.vanish) {
382             this.file.delete (null);
383
384             return null;
385         } else if (item_id == "VanishingItem" && !this.vanish) {
386             this.vanish = true;
387         }
388
389         if (item_id != this.item.id) {
390             this.item = new MediaItem (item_id, this);
391         }
392
393         return this.item;
394     }
395
396     public void on_file_changed (FileMonitor      monitor,
397                                  File             file,
398                                  File?            other_file,
399                                  FileMonitorEvent event_type) {
400         this.item.place_holder = false;
401
402         this.container_updated (this);
403     }
404
405     ~MediaContainer() {
406         try {
407             this.file.delete (null);
408         } catch (GLib.Error error) {
409             assert_not_reached ();
410         }
411     }
412 }
413
414 public class Rygel.MediaItem : Rygel.MediaObject {
415     public const string URI = "file:///tmp/rygel-upload-test.wav";
416
417     public weak MediaContainer parent;
418
419     public string id;
420     public long size = 1024;
421     public long duration = 1024;
422     public ArrayList<string> uris = new ArrayList<string> ();
423
424     public bool place_holder = true;
425
426     public File file;
427
428     public MediaItem.for_visual_item () {}
429
430     public MediaItem (string id, MediaContainer parent) {
431         this.id = id;
432         this.parent = parent;
433
434         this.file = parent.file;
435     }
436
437     public async File? get_writable (Cancellable? cancellable) throws Error {
438         SourceFunc get_writable_continue = get_writable.callback;
439
440         Idle.add (() => {
441             get_writable_continue ();
442
443             return false;
444         });
445
446         yield;
447
448         if (this.id == "NullItem") {
449             this.file.delete (null);
450
451             return null;
452         } else {
453             return this.file;
454         }
455     }
456 }
457
458 internal class Rygel.HTTPResponse : Rygel.StateMachine, GLib.Object {
459     public Cancellable cancellable { get; set; }
460
461     private Soup.Message msg;
462     private Soup.Server server;
463
464     public HTTPResponse (HTTPPost get_request) {
465         this.msg = get_request.msg;
466
467         this.server = get_request.server;
468     }
469
470     public async void run () {
471         SourceFunc run_continue = run.callback;
472
473         Idle.add (() => {
474             run_continue ();
475
476             return false;
477         });
478
479         yield;
480
481         this.msg.set_status (Soup.KnownStatusCode.OK);
482         this.server.unpause_message (msg);
483
484         this.completed ();
485     }
486 }
487
488 public class Rygel.ItemRemovalQueue: GLib.Object {
489     public static ItemRemovalQueue get_default () {
490        return new ItemRemovalQueue ();
491     }
492
493     public bool dequeue (MediaItem item) {
494         return true;
495     }
496
497     public async void remove_now (MediaItem item, Cancellable? cancellable) {
498         Idle.add (remove_now.callback);
499
500         yield;
501     }
502 }
503
504 public class Rygel.MediaObject : GLib.Object {
505     public string mime_type = "";
506 }
507
508 public class Rygel.Thumbnail : GLib.Object {
509     public string file_extension;
510 }
511
512 public class Rygel.VisualItem : Rygel.MediaItem {
513     public ArrayList<Thumbnail> thumbnails = new ArrayList<Thumbnail> ();
514
515     public VisualItem () {
516         base.for_visual_item();
517     }
518 }
519
520 private class Rygel.Subtitle : GLib.Object {
521     public string caption_type;
522 }
523
524 private class Rygel.VideoItem : Rygel.VisualItem {
525     public ArrayList<Subtitle> subtitles = new ArrayList<Subtitle> ();
526 }
527
528 private class Rygel.MusicItem : MediaItem {
529     public Thumbnail album_art;
530
531     public MusicItem (string id, MediaContainer parent) {
532         base (id, parent);
533     }
534 }
535
536 public errordomain Rygel.ContentDirectoryError {
537     INVALID_ARGS = 402
538 }
539
540 public class Rygel.Transcoder : GLib.Object {
541     public string extension { get; protected set; }
542
543     public Transcoder (string extension) {
544         this.extension = extension;
545     }
546 }