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