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