test: Silence warnings in ItemCreator test
[profile/ivi/rygel.git] / tests / rygel-item-creator-test.vala
1 /*
2  * Copyright (C) 2012 Nokia Corporation.
3  * Copyright (C) 2012 Intel Corporation.
4  *
5  * Author: Jens Georg <jensg@openismus.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 [CCode (cname = "uuid_generate", cheader_filename = "uuid/uuid.h")]
25 internal extern static void uuid_generate ([CCode (array_length = false)]
26                                            uchar[] uuid);
27 [CCode (cname = "uuid_unparse", cheader_filename = "uuid/uuid.h")]
28 internal extern static void uuid_unparse ([CCode (array_length = false)]
29                                           uchar[] uuid,
30                                           [CCode (array_length = false)]
31                                           uchar[] output);
32
33 public const string DIDL_ITEM = """<?xml version="1.0" encoding="UTF-8"?>
34 <DIDL-Lite
35     xmlns:dc="http://purl.org/dc/elements/1.1/"
36     xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"
37     xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/"
38     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
39     xsi:schemaLocation="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/
40         http://www.upnp.org/schemas/av/didl-lite-v2-20060531.xsd
41       urn:schemas-upnp-org:metadata-1-0/upnp/
42         http://www.upnp.org/schemas/av/upnp-v2-20060531.xsd">
43     <item id="" parentID="0" restricted="0">
44         <dc:title>New Song</dc:title>
45         <upnp:class>object.item.audioItem</upnp:class>
46         <res protocolInfo="*:*:*:*" />
47     </item>
48 </DIDL-Lite>""";
49
50 public class Rygel.ServiceAction : GLib.Object {
51     public int error_code;
52     public string error_message;
53     public string id;
54     public string elements;
55
56     public ServiceAction (string? container_id,
57                           string? elements) {
58         this.id = container_id;
59         this.elements = elements;
60     }
61
62     public void @return() {}
63     public void return_error (int code, string message) {
64         this.error_code = code;
65         this.error_message = message;
66     }
67
68     public new void @get (string arg1_name,
69                           Type arg1_type,
70                           out string arg1_val,
71                           string arg2_name,
72                           Type arg2_type,
73                           out string arg2_val) {
74         assert (arg1_name == "ContainerID");
75         assert (arg1_type == typeof (string));
76         arg1_val = id;
77
78         assert (arg2_name == "Elements");
79         assert (arg2_type == typeof (string));
80         arg2_val = elements;
81     }
82
83     public new void @set (string arg1_name,
84                           Type arg1_type,
85                           string arg1_val,
86                           string arg2_name,
87                           Type arg2_type,
88                           string arg2_val) {
89         assert (arg1_name == "ObjectID");
90         assert (arg1_type == typeof (string));
91
92         assert (arg2_name == "Result");
93         assert (arg2_type == typeof (string));
94     }
95 }
96
97 public class Rygel.HTTPServer : GLib.Object {
98 }
99
100 public class Rygel.ItemRemovalQueue : GLib.Object {
101     public static ItemRemovalQueue get_default () {
102         return new ItemRemovalQueue ();
103     }
104
105     public void queue (MediaItem item, Cancellable? cancellable) {
106     }
107 }
108
109 public class Rygel.MediaObject : GLib.Object {
110     public string id;
111     public string ref_id;
112     public unowned MediaContainer parent;
113     public string upnp_class;
114     public string title;
115     public GUPnP.OCMFlags ocm_flags;
116     public Gee.ArrayList<string> uris;
117
118     public void add_uri (string uri) {
119         this.uris.add (uri);
120     }
121 }
122
123 public class Rygel.MediaItem : Rygel.MediaObject {
124     public string dlna_profile;
125     public string mime_type;
126     public long size;
127     public bool place_holder;
128     public string date;
129
130     public MediaItem (string id, MediaContainer parent, string title) {
131         this.id = id;
132         this.parent = parent;
133         this.title = title;
134     }
135
136     public void serialize (GUPnP.DIDLLiteWriter writer, HTTPServer server) {
137     }
138 }
139
140 public class Rygel.MusicItem : Rygel.AudioItem {
141     public const string UPNP_CLASS = "object.item.audioItem.musicTrack";
142
143     public MusicItem (string id, MediaContainer parent, string title) {
144         base (id, parent, title);
145     }
146 }
147
148 public class Rygel.AudioItem : Rygel.MediaItem {
149     public const string UPNP_CLASS = "object.item.audioItem";
150     public string artist;
151     public string album;
152
153     public AudioItem (string id, MediaContainer parent, string title) {
154         base (id, parent, title);
155     }
156 }
157 public class Rygel.ImageItem : Rygel.MediaItem {
158     public const string UPNP_CLASS = "object.item.imageItem";
159     public ImageItem (string id, MediaContainer parent, string title) {
160         base (id, parent, title);
161     }
162 }
163
164 public class Rygel.VideoItem : Rygel.MediaItem {
165     public const string UPNP_CLASS = "object.item.videoItem";
166     public VideoItem (string id, MediaContainer parent, string title) {
167         base (id, parent, title);
168     }
169 }
170
171 public class Rygel.PhotoItem : Rygel.MediaItem {
172     public const string UPNP_CLASS = "object.item.imageItem.photo";
173     public string creator;
174
175     public PhotoItem (string id, MediaContainer parent, string title) {
176         base (id, parent, title);
177     }
178 }
179 public class Rygel.ContentDirectory : GLib.Object {
180     public Cancellable cancellable;
181     public MediaContainer root_container;
182     public HTTPServer http_server;
183 }
184
185 public class Rygel.MediaContainer : Rygel.MediaObject {
186     public Gee.ArrayList<string> create_classes = new Gee.ArrayList<string> ();
187     public int child_count;
188     public string sort_criteria = "+dc:title";
189
190     // mockable elements
191     public MediaObject found_object = null;
192
193     public async MediaObject? find_object (string       id,
194                                            Cancellable? cancellable = null) {
195         Idle.add (() => { find_object.callback (); return false; });
196         yield;
197
198         return found_object;
199     }
200
201     public signal void container_updated (MediaContainer container);
202 }
203
204 public class Rygel.MediaObjects : Gee.ArrayList<MediaObject> {
205 }
206
207 public class Rygel.WritableContainer : Rygel.MediaContainer {
208     public bool can_create (string upnp_class) {
209         return this.create_classes.contains (upnp_class);
210     }
211
212     public async File? get_writable (Cancellable? cancellable = null) {
213         return File.new_for_commandline_arg ("/tmp");
214     }
215
216     public async void add_item (MediaItem    item,
217                                 Cancellable? cancellable = null) {
218     }
219 }
220
221 public class Rygel.SearchableContainer : Rygel.MediaContainer {
222     public MediaObjects result = new MediaObjects ();
223
224     public async MediaObjects search (SearchExpression expression,
225                                       int              offset,
226                                       int              count,
227                                       out int          total_matches,
228                                       string           soer_criteria,
229                                       Cancellable?     cancellable = null) {
230         Idle.add (() => { search.callback (); return false; });
231         yield;
232
233         return result;
234     }
235 }
236
237 public errordomain Rygel.ContentDirectoryError {
238     BAD_METADATA,
239     NO_SUCH_OBJECT,
240     INVALID_ARGS,
241     RESTRICTED_PARENT,
242     ERROR
243 }
244
245 public class Rygel.Transcoder {
246 }
247
248 public class Rygel.TestMediaEngine : Rygel.MediaEngine {
249     public override unowned GLib.List<DLNAProfile> get_dlna_profiles () {
250         return null;
251     }
252
253     public override unowned GLib.List<Transcoder>? get_transcoders () {
254         return null;
255     }
256
257     public override DataSource create_data_source (string uri) {
258         return null;
259     }
260 }
261
262 public class Rygel.EngineLoader {
263     public EngineLoader () { }
264
265     public MediaEngine load_engine () {
266         return new TestMediaEngine ();
267     }
268 }
269
270 public static void log_func (string? domain,
271                              LogLevelFlags flags,
272                              string message) {
273
274     // Ignore critical of gee 0.6 and recent glib
275     if (message.has_prefix ("Read-only property 'read-only-view' on class")) {
276         Log.default_handler (domain, flags, message);
277
278         return;
279     }
280
281     if (LogLevelFlags.LEVEL_CRITICAL in flags ||
282         LogLevelFlags.LEVEL_ERROR in flags ||
283         LogLevelFlags.FLAG_FATAL in flags) {
284         print ("======> FAILED: %s: %s\n", domain ?? "", message);
285         assert_not_reached ();
286     }
287 }
288
289 public class Rygel.HTTPItemCreatorTest : GLib.Object {
290
291     public static int main (string[] args) {
292         Log.set_default_handler (log_func);
293         var test = new HTTPItemCreatorTest ();
294         test.test_parse_args ();
295         test.test_didl_parsing ();
296         test.test_fetch_container ();
297
298         return 0;
299     }
300
301     // expected errors
302     Error no_such_object;
303     Error restricted_parent;
304     Error bad_metadata;
305     Error invalid_args;
306
307     public HTTPItemCreatorTest () {
308         this.no_such_object = new ContentDirectoryError.NO_SUCH_OBJECT("");
309         this.restricted_parent = new ContentDirectoryError.RESTRICTED_PARENT("");
310         this.bad_metadata = new ContentDirectoryError.BAD_METADATA("");
311         this.invalid_args = new ContentDirectoryError.INVALID_ARGS("");
312     }
313
314     private void test_parse_args () {
315         // check null container id
316         var content_directory = new ContentDirectory ();
317
318         var action = new ServiceAction (null, "");
319         var creator = new ItemCreator (content_directory, action);
320         creator.run ();
321         assert (action.error_code == no_such_object.code);
322
323         // check elements containing a comment
324         action = new ServiceAction ("0", "<!-- This is an XML comment -->");
325         creator = new ItemCreator (content_directory, action);
326         creator.run ();
327         assert (action.error_code == bad_metadata.code);
328
329         // check null elements
330         action = new ServiceAction ("0", null);
331         creator = new ItemCreator (content_directory, action);
332         creator.run ();
333         assert (action.error_code == bad_metadata.code);
334     }
335
336     private void test_didl_parsing_step (Xml.Doc *doc, int expected_code) {
337         string xml;
338
339         doc->dump_memory_enc (out xml);
340         var action = new ServiceAction ("0", xml);
341         var content_directory = new ContentDirectory ();
342         var creator = new ItemCreator (content_directory, action);
343         creator.run ();
344         assert (action.error_code == expected_code);
345     }
346
347     private void test_didl_parsing () {
348         var xml = Xml.Parser.read_memory (DIDL_ITEM,
349                                           DIDL_ITEM.length,
350                                           null,
351                                           null,
352                                           Xml.ParserOption.RECOVER |
353                                           Xml.ParserOption.NOBLANKS);
354         var didl_node = xml->children;
355         var item_node = didl_node->children;
356         var content_directory = new ContentDirectory ();
357
358         // test no DIDL
359         var action = new ServiceAction ("0", "");
360         var creator = new ItemCreator (content_directory, action);
361         creator.run ();
362         assert (action.error_code == bad_metadata.code);
363         assert (action.error_message == "Bad metadata");
364
365         // test empty DIDL
366         item_node->unlink ();
367         didl_node->set_content ("  ");
368         this.test_didl_parsing_step (xml, bad_metadata.code);
369
370         // test item node with missing restricted attribute
371         var tmp = item_node->copy (1);
372         tmp->unset_prop ("restricted");
373         didl_node->add_child (tmp);
374         this.test_didl_parsing_step (xml, bad_metadata.code);
375
376         // test item node with restricted=1
377         tmp->set_prop ("restricted", "1");
378         this.test_didl_parsing_step (xml, invalid_args.code);
379
380         // test item node with invalid id
381         tmp->unlink ();
382         tmp = item_node->copy (1);
383         tmp->set_prop ("id", "InvalidItemId");
384         didl_node->add_child (tmp);
385         this.test_didl_parsing_step (xml, bad_metadata.code);
386
387         // test item node with missing id
388         tmp->unset_prop ("id");
389         this.test_didl_parsing_step (xml, bad_metadata.code);
390
391         // test item node with missing title
392         tmp->unlink ();
393         tmp = item_node->copy (1);
394         var title_node = tmp->children;
395         title_node->unlink ();
396         didl_node->add_child (tmp);
397         this.test_didl_parsing_step (xml, bad_metadata.code);
398
399         // test missing, empty or non-item upnp class
400         tmp->unlink ();
401         tmp = item_node->copy (1);
402         var class_node = tmp->children->next;
403         class_node->set_content ("object.container");
404         didl_node->add_child (tmp);
405         this.test_didl_parsing_step (xml, bad_metadata.code);
406
407         class_node->set_content ("");
408         this.test_didl_parsing_step (xml, bad_metadata.code);
409
410         class_node->unlink ();
411         this.test_didl_parsing_step (xml, bad_metadata.code);
412     }
413
414     private void test_fetch_container_run (ItemCreator creator) {
415         var main_loop = new MainLoop (null, false);
416         creator.run.begin ( () => { main_loop.quit (); });
417         main_loop.run ();
418     }
419
420     private void test_fetch_container () {
421         // check case when object is not found
422         var content_directory = new ContentDirectory ();
423         var root_container = new SearchableContainer ();
424         content_directory.root_container = root_container;
425         var action = new ServiceAction ("0", DIDL_ITEM);
426         var creator = new ItemCreator (content_directory, action);
427         this.test_fetch_container_run (creator);
428         assert (action.error_code == no_such_object.code);
429
430         // check case when found object is not a container → Error 710
431         // cf. ContentDirectory:2 spec, Table 2-22
432         root_container.found_object = new MediaObject ();
433         this.test_fetch_container_run (creator);
434         assert (action.error_code == no_such_object.code);
435
436         // check case when found container does not have OCMUpload set
437         root_container.found_object = new MediaContainer ();
438         this.test_fetch_container_run (creator);
439         assert (action.error_code == restricted_parent.code);
440
441         // check case when found container is not a writable container
442         root_container.found_object.ocm_flags |= GUPnP.OCMFlags.UPLOAD;
443         this.test_fetch_container_run (creator);
444         assert (action.error_code == restricted_parent.code);
445
446         // check when found container does not have the correct create class
447         var container = new WritableContainer ();
448         container.create_classes.add ("object.item.imageItem.musicTrack");
449         container.ocm_flags |= GUPnP.OCMFlags.UPLOAD;
450         root_container.found_object = container;
451         this.test_fetch_container_run (creator);
452         assert (action.error_code == bad_metadata.code);
453
454         // check DLNA.ORG_AnyContainer when root container is not searchable
455         content_directory.root_container = new MediaContainer ();
456         action.id = "DLNA.ORG_AnyContainer";
457         this.test_fetch_container_run (creator);
458         assert (action.error_code == no_such_object.code);
459
460         // check DLNA.ORG_AnyContainer when no writable container is found
461         content_directory.root_container = new SearchableContainer ();
462         this.test_fetch_container_run (creator);
463         // We cannot distinguish this case from the "create-class doesn't match"
464         // case
465         assert (action.error_code == bad_metadata.code);
466     }
467 }