tests: Fix rygel-http-post-test
[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 class Rygel.HTTPPostTest : GLib.Object {
33     protected HTTPServer server;
34     protected HTTPClient client;
35
36     private bool server_done;
37     private bool client_done;
38
39     private MainLoop main_loop;
40
41     private Error error;
42
43     public static int main (string[] args) {
44         try {
45             var test = new HTTPPostTest ();
46
47             test.run ();
48         } catch (TestError.SKIP error) {
49             return error.code;
50         } catch (Error error) {
51             critical ("%s", error.message);
52
53             return -1;
54         }
55
56         return 0;
57     }
58
59     public HTTPPostTest () throws Error {
60         this.server = new HTTPServer ();
61         this.client = new HTTPClient (this.server.context,
62                                       this.server.uri);
63         this.main_loop = new MainLoop (null, false);
64     }
65
66     public virtual void run () throws Error {
67         Timeout.add_seconds (3, this.on_timeout);
68         this.server.message_received.connect (this.on_message_received);
69         this.client.completed.connect (this.on_client_completed);
70
71         this.client.run.begin ();
72
73         this.main_loop.run ();
74
75         if (this.error != null) {
76             throw this.error;
77         }
78     }
79
80     private HTTPRequest create_request (Soup.Message msg) throws Error {
81         return new HTTPPost (this.server,
82                             this.server.context.server,
83                             msg);
84     }
85
86     private void on_client_completed (StateMachine client) {
87         this.client_done = true;
88         this.check_and_exit.begin ();
89     }
90
91     private void on_message_received (HTTPServer   server,
92                                       Soup.Message msg) {
93         this.handle_client_message.begin (msg);
94     }
95
96     private async void handle_client_message (Soup.Message msg) {
97         try {
98             var request = this.create_request (msg);
99
100             yield request.run ();
101
102             assert ((request as HTTPPost).item != null);
103
104             this.server_done = true;
105             this.check_and_exit.begin ();
106         } catch (Error error) {
107             this.error = error;
108             this.main_loop.quit ();
109
110             return;
111         }
112     }
113
114     private bool on_timeout () {
115         this.error = new TestError.TIMEOUT ("Timeout");
116         this.main_loop.quit ();
117
118         return false;
119     }
120
121     private async void check_and_exit () {
122         if (!(this.server_done && this.client_done)) {
123             return;
124         }
125
126         try {
127             var file = this.server.root_container.item.file;
128             var stream = yield file.read_async (Priority.HIGH, null);
129             var buffer = new uint8[HTTPClient.LENGTH];
130
131             yield stream.read_async (buffer, Priority.HIGH, null);
132
133             for (var i = 0; i < HTTPClient.LENGTH; i++) {
134                 assert (buffer[i] == this.client.content[i]);
135             }
136         } catch (Error error) {
137             this.error = error;
138         }
139
140         this.main_loop.quit ();
141     }
142 }
143
144 public class Rygel.HTTPServer : GLib.Object {
145     private const string SERVER_PATH = "/RygelHTTPServer/Rygel/Test";
146     public string path_root {
147         get {
148             return SERVER_PATH;
149         }
150     }
151
152     public MediaContainer root_container;
153     public GUPnP.Context context;
154
155     public string uri {
156         owned get {
157             var item_uri = new HTTPItemURI (this.root_container.ITEM_ID,
158                                             this);
159
160             return item_uri.to_string ();
161         }
162     }
163
164     public signal void message_received (Soup.Message message);
165
166     public HTTPServer () throws TestError {
167         try {
168             this.context = new GUPnP.Context (null, "lo", 0);
169         } catch (Error error) {
170             throw new TestError.SKIP ("Network context not available");
171         }
172
173         assert (this.context != null);
174         assert (this.context.host_ip != null);
175         assert (this.context.port > 0);
176
177         context.server.request_started.connect (this.on_request_started);
178
179         this.root_container = new MediaContainer ();
180     }
181
182     private void on_request_started (Soup.Server        server,
183                                      Soup.Message       msg,
184                                      Soup.ClientContext client) {
185         msg.got_headers.connect (this.on_got_headers);
186     }
187
188     private void on_got_headers (Soup.Message msg) {
189         this.message_received (msg);
190     }
191 }
192
193 public class Rygel.HTTPClient : GLib.Object, StateMachine {
194     public const size_t LENGTH = 1024;
195
196     public uint8[] content;
197
198     public GUPnP.Context context;
199     public Soup.Message msg;
200
201     public Cancellable cancellable { get; set; }
202
203     public HTTPClient (GUPnP.Context context,
204                        string        uri) {
205         this.context = context;
206         this.content = new uint8[1024];
207
208         this.msg = new Soup.Message ("POST",  uri);
209         assert (this.msg != null);
210     }
211
212     public async void run () {
213         SourceFunc run_continue = run.callback;
214
215         this.msg.request_body.append (MemoryUse.COPY, content);
216
217         this.context.session.queue_message (this.msg, (session, msg) => {
218             run_continue ();
219         });
220
221         yield;
222
223         this.completed ();
224     }
225 }
226
227 public class Rygel.MediaContainer : Rygel.MediaObject {
228     public const string ITEM_ID = "TestItem";
229
230     public signal void container_updated (MediaContainer container);
231
232     public string id = "TesContainer";
233     public MediaItem item;
234
235     private FileMonitor monitor;
236
237     public MediaContainer () {
238         this.item = new MediaItem (ITEM_ID, this);
239
240         this.monitor = this.item.file.monitor_file (FileMonitorFlags.NONE);
241         this.monitor.changed.connect (this.on_file_changed);
242     }
243
244     public async MediaObject? find_object (string       item_id,
245                                            Cancellable? cancellable)
246                                            throws Error {
247         SourceFunc find_object_continue = find_object.callback;
248         Idle.add (() => {
249             find_object_continue ();
250
251             return false;
252         });
253
254         yield;
255
256         if (item_id == ITEM_ID) {
257             return this.item;
258         } else {
259             return null;
260         }
261     }
262
263     public void on_file_changed (FileMonitor      monitor,
264                                  File             file,
265                                  File?            other_file,
266                                  FileMonitorEvent event_type) {
267         this.container_updated (this);
268     }
269 }
270
271 public class Rygel.MediaItem : Rygel.MediaObject {
272     public const string URI = "file:///tmp/rygel-upload-test.wav";
273
274     public weak MediaContainer parent;
275
276     public string id;
277     public long size = 1024;
278     public long duration = 1024;
279
280     public bool place_holder = true;
281
282     public File file;
283
284     public MediaItem (string id, MediaContainer parent) {
285         this.id = id;
286         this.parent = parent;
287
288         this.file = File.new_for_uri (URI);
289     }
290
291     ~MediaItem() {
292         try {
293             this.file.delete (null);
294         } catch (GLib.Error error) {
295             assert_not_reached ();
296         }
297     }
298
299     public async File? get_writable (Cancellable? cancellable) throws Error {
300         SourceFunc get_writable_continue = get_writable.callback;
301
302         Idle.add (() => {
303             get_writable_continue ();
304
305             return false;
306         });
307
308         yield;
309
310         return this.file;
311     }
312 }
313
314 internal class Rygel.HTTPResponse : Rygel.StateMachine, GLib.Object {
315     public Cancellable cancellable { get; set; }
316
317     private Soup.Message msg;
318     private Soup.Server server;
319
320     public HTTPResponse (HTTPPost get_request) {
321         this.msg = get_request.msg;
322         this.server = get_request.server;
323     }
324
325     public async void run () {
326         SourceFunc run_continue = run.callback;
327
328         Idle.add (() => {
329             run_continue ();
330
331             return false;
332         });
333
334         yield;
335
336         this.msg.set_status (Soup.KnownStatusCode.OK);
337         this.server.unpause_message (msg);
338
339         this.completed ();
340     }
341 }
342
343 public class Rygel.ItemRemovalQueue: GLib.Object {
344     public static ItemRemovalQueue get_default () {
345        return new ItemRemovalQueue ();
346     }
347
348     public bool dequeue (MediaItem item) {
349         return true;
350     }
351
352     public async void remove_now (MediaItem item, Cancellable? cancellable) {
353         Idle.add (remove_now.callback);
354
355         yield;
356     }
357 }
358
359 public class Rygel.MediaObject : GLib.Object {}
360
361 public errordomain Rygel.ContentDirectoryError {
362     INVALID_ARGS = 402
363 }