tests: Add missing fix from previous commit
[profile/ivi/rygel.git] / tests / rygel-item-creator-test.vala
1 /*
2  * Copyright (C) 2012 Nokia Corporation.
3  *
4  * Author: Jens Georg <jensg@openismus.com>
5  *
6  * This file is part of Rygel.
7  *
8  * Rygel is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU Lesser General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * Rygel is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public License
19  * along with this program; if not, write to the Free Software Foundation,
20  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21  */
22
23 [CCode (cname = "uuid_generate", cheader_filename = "uuid/uuid.h")]
24 internal extern static void uuid_generate ([CCode (array_length = false)]
25                                            uchar[] uuid);
26 [CCode (cname = "uuid_unparse", cheader_filename = "uuid/uuid.h")]
27 internal extern static void uuid_unparse ([CCode (array_length = false)]
28                                           uchar[] uuid,
29                                           [CCode (array_length = false)]
30                                           uchar[] output);
31
32 public const string DIDL_ITEM = """<?xml version="1.0" encoding="UTF-8"?>
33 <DIDL-Lite
34     xmlns:dc="http://purl.org/dc/elements/1.1/"
35     xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"
36     xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/"
37     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
38     xsi:schemaLocation="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/
39         http://www.upnp.org/schemas/av/didl-lite-v2-20060531.xsd
40       urn:schemas-upnp-org:metadata-1-0/upnp/
41         http://www.upnp.org/schemas/av/upnp-v2-20060531.xsd">
42     <item id="" parentID="0" restricted="0">
43         <dc:title>New Song</dc:title>
44         <upnp:class>object.item.audioItem</upnp:class>
45         <res protocolInfo="*:*:*:*" />
46     </item>
47 </DIDL-Lite>""";
48
49 public class Rygel.ServiceAction : GLib.Object {
50     public int error_code;
51     public string error_message;
52     public string id;
53     public string elements;
54
55     public ServiceAction (string? container_id,
56                           string? elements) {
57         this.id = container_id;
58         this.elements = elements;
59     }
60
61     public void @return() {}
62     public void return_error (int code, string message) {
63         this.error_code = code;
64         this.error_message = message;
65     }
66
67     public new void @get (string arg1_name,
68                           Type arg1_type,
69                           out string arg1_val,
70                           string arg2_name,
71                           Type arg2_type,
72                           out string arg2_val) {
73         assert (arg1_name == "ContainerID");
74         assert (arg1_type == typeof (string));
75         arg1_val = id;
76
77         assert (arg2_name == "Elements");
78         assert (arg2_type == typeof (string));
79         arg2_val = elements;
80     }
81
82     public new void @set (string arg1_name,
83                           Type arg1_type,
84                           string arg1_val,
85                           string arg2_name,
86                           Type arg2_type,
87                           string arg2_val) {
88         assert (arg1_name == "ObjectID");
89         assert (arg1_type == typeof (string));
90
91         assert (arg2_name == "Result");
92         assert (arg2_type == typeof (string));
93     }
94 }
95
96 public class Rygel.HTTPServer : GLib.Object {
97 }
98
99 public class Rygel.ItemRemovalQueue : GLib.Object {
100     public static ItemRemovalQueue get_default () {
101         return new ItemRemovalQueue ();
102     }
103
104     public void queue (MediaItem item, Cancellable? cancellable) {
105     }
106 }
107
108 public class Rygel.MediaObject : GLib.Object {
109     public string id;
110     public string ref_id;
111     public unowned MediaContainer parent;
112     public string upnp_class;
113     public string title;
114     public GUPnP.OCMFlags ocm_flags;
115     public Gee.ArrayList<string> uris;
116
117     public void add_uri (string uri) {
118         this.uris.add (uri);
119     }
120 }
121
122 public class Rygel.MediaItem : Rygel.MediaObject {
123     public string dlna_profile;
124     public string mime_type;
125     public long size;
126     public bool place_holder;
127     public string date;
128
129     public MediaItem (string id, MediaContainer parent, string title) {
130         this.id = id;
131         this.parent = parent;
132         this.title = title;
133     }
134
135     public void serialize (GUPnP.DIDLLiteWriter writer, HTTPServer server) {
136     }
137 }
138
139 public class Rygel.MusicItem : Rygel.AudioItem {
140     public const string UPNP_CLASS = "object.item.audioItem.musicTrack";
141
142     public MusicItem (string id, MediaContainer parent, string title) {
143         base (id, parent, title);
144     }
145 }
146
147 public class Rygel.AudioItem : Rygel.MediaItem {
148     public const string UPNP_CLASS = "object.item.audioItem";
149     public string artist;
150     public string album;
151
152     public AudioItem (string id, MediaContainer parent, string title) {
153         base (id, parent, title);
154     }
155 }
156 public class Rygel.ImageItem : Rygel.MediaItem {
157     public const string UPNP_CLASS = "object.item.imageItem";
158     public ImageItem (string id, MediaContainer parent, string title) {
159         base (id, parent, title);
160     }
161 }
162
163 public class Rygel.VideoItem : Rygel.MediaItem {
164     public const string UPNP_CLASS = "object.item.videoItem";
165     public VideoItem (string id, MediaContainer parent, string title) {
166         base (id, parent, title);
167     }
168 }
169
170 public class Rygel.PhotoItem : Rygel.MediaItem {
171     public const string UPNP_CLASS = "object.item.imageItem.photo";
172     public string creator;
173
174     public PhotoItem (string id, MediaContainer parent, string title) {
175         base (id, parent, title);
176     }
177 }
178 public class Rygel.ContentDirectory : GLib.Object {
179     public Cancellable cancellable;
180     public MediaContainer root_container;
181     public HTTPServer http_server;
182 }
183
184 public class Rygel.MediaContainer : Rygel.MediaObject {
185     public Gee.ArrayList<string> create_classes = new Gee.ArrayList<string> ();
186     public int child_count;
187     public string sort_criteria = "+dc:title";
188
189     // mockable elements
190     public MediaObject found_object = null;
191
192     public async MediaObject? find_object (string       id,
193                                            Cancellable? cancellable = null) {
194         Idle.add (() => { find_object.callback (); return false; });
195         yield;
196
197         return found_object;
198     }
199
200     public signal void container_updated (MediaContainer container);
201 }
202
203 public class Rygel.MediaObjects : Gee.ArrayList<MediaObject> {
204 }
205
206 public class Rygel.WritableContainer : Rygel.MediaContainer {
207     public bool can_create (string upnp_class) {
208         return this.create_classes.contains (upnp_class);
209     }
210
211     public async File? get_writable (Cancellable? cancellable = null) {
212         return File.new_for_commandline_arg ("/tmp");
213     }
214
215     public async void add_item (MediaItem    item,
216                                 Cancellable? cancellable = null) {
217     }
218 }
219
220 public class Rygel.SearchableContainer : Rygel.MediaContainer {
221     public MediaObjects result = new MediaObjects ();
222
223     public async MediaObjects search (SearchExpression expression,
224                                       int              offset,
225                                       int              count,
226                                       out int          total_matches,
227                                       string           soer_criteria,
228                                       Cancellable?     cancellable = null) {
229         Idle.add (() => { search.callback (); return false; });
230         yield;
231
232         return result;
233     }
234 }
235
236 public errordomain Rygel.ContentDirectoryError {
237     BAD_METADATA,
238     NO_SUCH_OBJECT,
239     INVALID_ARGS,
240     RESTRICTED_PARENT,
241     ERROR
242 }
243
244 public class Rygel.HTTPItemCreatorTest : GLib.Object {
245
246     public static int main (string[] args) {
247         var test = new HTTPItemCreatorTest ();
248         test.test_parse_args ();
249         test.test_didl_parsing ();
250         test.test_fetch_container ();
251
252         return 0;
253     }
254
255     // expected errors
256     Error no_such_object;
257     Error restricted_parent;
258     Error bad_metadata;
259     Error invalid_args;
260
261     public HTTPItemCreatorTest () {
262         this.no_such_object = new ContentDirectoryError.NO_SUCH_OBJECT("");
263         this.restricted_parent = new ContentDirectoryError.RESTRICTED_PARENT("");
264         this.bad_metadata = new ContentDirectoryError.BAD_METADATA("");
265         this.invalid_args = new ContentDirectoryError.INVALID_ARGS("");
266     }
267
268     private void test_parse_args () {
269         // check null container id
270         var content_directory = new ContentDirectory ();
271
272         var action = new ServiceAction (null, "");
273         var creator = new ItemCreator (content_directory, action);
274         creator.run ();
275         assert (action.error_code == no_such_object.code);
276
277         // check elements containing a comment
278         action = new ServiceAction ("0", "<!-- This is an XML comment -->");
279         creator = new ItemCreator (content_directory, action);
280         creator.run ();
281         assert (action.error_code == bad_metadata.code);
282
283         // check null elements
284         action = new ServiceAction ("0", null);
285         creator = new ItemCreator (content_directory, action);
286         creator.run ();
287         assert (action.error_code == bad_metadata.code);
288     }
289
290     private void test_didl_parsing_step (Xml.Doc *doc, int expected_code) {
291         string xml;
292
293         doc->dump_memory_enc (out xml);
294         var action = new ServiceAction ("0", xml);
295         var content_directory = new ContentDirectory ();
296         var creator = new ItemCreator (content_directory, action);
297         creator.run ();
298         assert (action.error_code == expected_code);
299     }
300
301     private void test_didl_parsing () {
302         var xml = Xml.Parser.read_memory (DIDL_ITEM,
303                                           DIDL_ITEM.length,
304                                           null,
305                                           null,
306                                           Xml.ParserOption.RECOVER |
307                                           Xml.ParserOption.NOBLANKS);
308         var didl_node = xml->children;
309         var item_node = didl_node->children;
310         var content_directory = new ContentDirectory ();
311
312         // test no DIDL
313         var action = new ServiceAction ("0", "");
314         var creator = new ItemCreator (content_directory, action);
315         creator.run ();
316         assert (action.error_code == bad_metadata.code);
317         assert (action.error_message == "Bad metadata");
318
319         // test empty DIDL
320         item_node->unlink ();
321         didl_node->set_content ("  ");
322         this.test_didl_parsing_step (xml, bad_metadata.code);
323
324         // test item node with missing restricted attribute
325         var tmp = item_node->copy (1);
326         tmp->unset_prop ("restricted");
327         didl_node->add_child (tmp);
328         this.test_didl_parsing_step (xml, bad_metadata.code);
329
330         // test item node with restricted=1
331         tmp->set_prop ("restricted", "1");
332         this.test_didl_parsing_step (xml, invalid_args.code);
333
334         // test item node with invalid id
335         tmp->unlink ();
336         tmp = item_node->copy (1);
337         tmp->set_prop ("id", "InvalidItemId");
338         didl_node->add_child (tmp);
339         this.test_didl_parsing_step (xml, bad_metadata.code);
340
341         // test item node with missing id
342         tmp->unset_prop ("id");
343         this.test_didl_parsing_step (xml, bad_metadata.code);
344
345         // test item node with missing title
346         tmp->unlink ();
347         tmp = item_node->copy (1);
348         var title_node = tmp->children;
349         title_node->unlink ();
350         didl_node->add_child (tmp);
351         this.test_didl_parsing_step (xml, bad_metadata.code);
352
353         // test missing, empty or non-item upnp class
354         tmp->unlink ();
355         tmp = item_node->copy (1);
356         var class_node = tmp->children->next;
357         class_node->set_content ("object.container");
358         didl_node->add_child (tmp);
359         this.test_didl_parsing_step (xml, bad_metadata.code);
360
361         class_node->set_content ("");
362         this.test_didl_parsing_step (xml, bad_metadata.code);
363
364         class_node->unlink ();
365         this.test_didl_parsing_step (xml, bad_metadata.code);
366     }
367
368     private void test_fetch_container_run (ItemCreator creator) {
369         var main_loop = new MainLoop (null, false);
370         creator.run.begin ( () => { main_loop.quit (); });
371         main_loop.run ();
372     }
373
374     private void test_fetch_container () {
375         // check case when object is not found
376         var content_directory = new ContentDirectory ();
377         var root_container = new SearchableContainer ();
378         content_directory.root_container = root_container;
379         var action = new ServiceAction ("0", DIDL_ITEM);
380         var creator = new ItemCreator (content_directory, action);
381         this.test_fetch_container_run (creator);
382         assert (action.error_code == no_such_object.code);
383
384         // check case when found object is not a container → Error 710
385         // cf. ContentDirectory:2 spec, Table 2-22
386         root_container.found_object = new MediaObject ();
387         this.test_fetch_container_run (creator);
388         assert (action.error_code == no_such_object.code);
389
390         // check case when found container does not have OCMUpload set
391         root_container.found_object = new MediaContainer ();
392         this.test_fetch_container_run (creator);
393         assert (action.error_code == restricted_parent.code);
394
395         // check case when found container is not a writable container
396         root_container.found_object.ocm_flags |= GUPnP.OCMFlags.UPLOAD;
397         this.test_fetch_container_run (creator);
398         assert (action.error_code == restricted_parent.code);
399
400         // check when found container does not have the correct create class
401         var container = new WritableContainer ();
402         container.create_classes.add ("object.item.imageItem.musicTrack");
403         container.ocm_flags |= GUPnP.OCMFlags.UPLOAD;
404         root_container.found_object = container;
405         this.test_fetch_container_run (creator);
406         assert (action.error_code == bad_metadata.code);
407
408         // check DLNA.ORG_AnyContainer when root container is not searchable
409         content_directory.root_container = new MediaContainer ();
410         action.id = "DLNA.ORG_AnyContainer";
411         this.test_fetch_container_run (creator);
412         assert (action.error_code == no_such_object.code);
413
414         // check DLNA.ORG_AnyContainer when no writable container is found
415         content_directory.root_container = new SearchableContainer ();
416         this.test_fetch_container_run (creator);
417         // We cannot distinguish this case from the "create-class doesn't match"
418         // case
419         assert (action.error_code == bad_metadata.code);
420     }
421 }