88from caldav .compatibility_hints import FeatureSet
99from caldav .lib .error import NotFoundError , AuthorizationError , ReportError , DAVError
1010from caldav .calendarobjectresource import Event , Todo , Journal
11+ from caldav .search import CalDAVSearcher
1112
1213from .checks_base import Check
1314
@@ -105,7 +106,10 @@ def _try_make_calendar(self, cal_id, **kwargs):
105106 ## calendar creation must have gone OK.
106107 calmade = True
107108 self .checker .principal .calendar (cal_id = cal_id ).events ()
108- self .set_feature ("create-calendar" )
109+ ## the caller takes care of setting quirk flag if mkcol
110+ ## (todo - does this make sense? Actually the whole _try_make_calendar looks messy to me and should probably be refactored)
111+ if kwargs .get ('method' , 'mkcalendar' ) != 'mkcol' :
112+ self .set_feature ("create-calendar" )
109113 if kwargs .get ("name" ):
110114 try :
111115 name = "A calendar with this name should not exist"
@@ -344,7 +348,7 @@ def add_if_not_existing(*largs, **kwargs):
344348
345349 simple_event = add_if_not_existing (
346350 Event ,
347- summary = "simple event with a start time and an end time" ,
351+ summary = "Simple event with a start time and an end time" ,
348352 uid = "csc_simple_event1" ,
349353 dtstart = datetime (2000 , 1 , 1 , 12 , 0 , 0 , tzinfo = utc ),
350354 dtend = datetime (2000 , 1 , 1 , 13 , 0 , 0 , tzinfo = utc ),
@@ -486,71 +490,129 @@ def add_if_not_existing(*largs, **kwargs):
486490END:VCALENDAR""" ,
487491 )
488492
493+ simple_event = add_if_not_existing (
494+ Event ,
495+ description = "Simple event without a summary" ,
496+ uid = "csc_simple_event_no_summary" ,
497+ dtstart = datetime (2000 , 3 , 1 , 12 , 0 , 0 , tzinfo = utc ),
498+ dtend = datetime (2000 , 3 , 1 , 13 , 0 , 0 , tzinfo = utc ),
499+ )
500+
489501 ## No more existing IDs in the calendar from 2000 ... otherwise,
490502 ## more work is needed to ensure those won't pollute the tests nor be
491503 ## deleted by accident
492504 assert not object_by_uid
493505 assert self .checker .calendar .events ()
494506 assert self .checker .tasklist .todos ()
495507
508+ class SearchMixIn :
509+ ## Boilerplate
510+ def search_find_set (self , cal_or_searcher , feature , num_expected = None , ** search_args ):
511+ try :
512+ results = cal_or_searcher .search (** search_args , post_filter = False )
513+ cnt = len (results )
514+ if num_expected is None :
515+ is_good = cnt > 0
516+ else :
517+ is_good = cnt == num_expected
518+ self .set_feature (feature , is_good )
519+ except ReportError :
520+ self .set_feature (feature , "ungraceful" )
521+
496522
497- class CheckSearch (Check ):
523+ class CheckSearch (Check , SearchMixIn ):
498524 depends_on = {PrepareCalendar }
499525 features_to_be_checked = {
500526 "search.time-range.event" ,
501- "search.category" ,
502- "search.category.fullstring" ,
503- "search.category.fullstring.smart" ,
504527 "search.time-range.todo" ,
528+ "search.text" ,
529+ "search.text.case-sensitive" ,
530+ "search.text.case-insensitive" ,
531+ "search.text.substring" ,
532+ "search.is-not-defined" ,
533+ "search.text.category" ,
534+ "search.text.category.substring" ,
505535 "search.comp-type-optional" ,
506536 "search.combined-is-logical-and" ,
507537 } ## TODO: we can do so much better than this
508538
509539 def _run_check (self ):
510540 cal = self .checker .calendar
511541 tasklist = self .checker .tasklist
512- events = cal .search (
542+ self .search_find_set (
543+ cal , "search.time-range.event" , 1 ,
513544 start = datetime (2000 , 1 , 1 , tzinfo = utc ),
514545 end = datetime (2000 , 1 , 2 , tzinfo = utc ),
515546 event = True ,
516547 )
517- self .set_feature ( "search.time-range.event" , len ( events ) == 1 )
518- tasks = tasklist . search (
548+ self .search_find_set (
549+ tasklist , " search.time-range.todo" , 1 ,
519550 start = datetime (2000 , 1 , 9 , tzinfo = utc ),
520551 end = datetime (2000 , 1 , 10 , tzinfo = utc ),
521552 todo = True ,
522553 include_completed = True ,
523554 )
524- self .set_feature ("search.time-range.todo" , len (tasks ) == 1 )
525555
526- ## search.category
527- try :
528- events = cal .search (category = "hands" , event = True )
529- self .set_feature ("search.category" , len (events ) == 1 )
530- except ReportError :
531- self .set_feature ("search.category" , "ungraceful" )
532- if self .feature_checked ("search.category" , str ) != 'ungraceful' :
533- events = cal .search (category = "hands,feet,head" , event = True )
534- self .set_feature ("search.category.fullstring" , len (events ) == 1 )
535- if len (events ) == 1 :
536- events = cal .search (category = "feet,head,hands" , event = True )
537- self .set_feature ("search.category.fullstring.smart" , len (events ) == 1 )
556+ ## summary search
557+ self .search_find_set (
558+ cal , "search.text" , 1 ,
559+ summary = "Simple event with a start time and an end time" ,
560+ event = True )
561+
562+ ## summary search is by default case sensitive
563+ self .search_find_set (
564+ cal , "search.text.case-sensitive" , 0 ,
565+ summary = "simple event with a start time and an end time" ,
566+ event = True )
567+
568+ ## summary search, case insensitive
569+ searcher = CalDAVSearcher (event = True )
570+ searcher .add_property_filter ('summary' , "simple event with a start time and an end time" , case_sensitive = False )
571+ self .search_find_set (
572+ searcher , "search.text.case-insensitive" , 1 , calendar = cal )
573+
574+ ## is not defined search
575+ searcher = CalDAVSearcher (event = True )
576+ searcher .add_property_filter ('summary' , None , operator = "undef" )
577+ self .search_find_set (
578+ searcher , "search.is-not-defined" , 1 , calendar = cal )
579+
580+ ## summary search, substring
581+ ## The RFC says that TextMatch is a subetext search
582+ self .search_find_set (
583+ cal , "search.text.substring" , 1 ,
584+ summary = "Simple event with a start time and" ,
585+ event = True )
586+
587+ ## search.text.category
588+ self .search_find_set (
589+ cal , "search.text.category" , 1 ,
590+ category = "hands" , event = True )
538591
539592 ## search.combined
540- if self .feature_checked ("search.category" ):
541- events1 = cal .search (category = "hands" , event = True , start = datetime (2000 , 1 , 1 , 11 , 0 , 0 ), end = datetime (2000 , 1 , 13 , 14 , 0 , 0 ))
542- events2 = cal .search (category = "hands" , event = True , start = datetime (2000 , 1 , 1 , 9 , 0 , 0 ), end = datetime (2000 , 1 , 6 , 14 , 0 , 0 ))
593+ if self .feature_checked ("search.text. category" ):
594+ events1 = cal .search (category = "hands" , event = True , start = datetime (2000 , 1 , 1 , 11 , 0 , 0 ), end = datetime (2000 , 1 , 13 , 14 , 0 , 0 ), post_filter = False )
595+ events2 = cal .search (category = "hands" , event = True , start = datetime (2000 , 1 , 1 , 9 , 0 , 0 ), end = datetime (2000 , 1 , 6 , 14 , 0 , 0 ), post_filter = False )
543596 self .set_feature ("search.combined-is-logical-and" , len (events1 ) == 1 and len (events2 ) == 0 )
544-
597+ self .search_find_set (
598+ cal , "search.text.category.substring" , 1 ,
599+ category = "eet" ,
600+ event = True )
545601 try :
602+ summary = "Simple event with a start time and"
603+ ## Text search with and without comptype
604+ tswc = cal .search (summary = summary , event = True , post_filter = False )
605+ tswoc = cal .search (summary = summary , post_filter = False )
606+ ## Testing if search without comp-type filter returns both events and tasks
546607 if self .feature_checked ("search.time-range.todo" ):
547608 objects = cal .search (
548609 start = datetime (2000 , 1 , 1 , tzinfo = utc ),
549610 end = datetime (2001 , 1 , 1 , tzinfo = utc ),
611+ post_filter = False ,
550612 )
551613 else :
552- objects = _filter_2000 (cal .search ())
553- if len (objects ) == 0 :
614+ objects = _filter_2000 (cal .search (post_filter = False ))
615+ if len (objects ) == 0 and not tswoc :
554616 self .set_feature (
555617 "search.comp-type-optional" ,
556618 {
@@ -570,12 +632,15 @@ def _run_check(self):
570632 cal != tasklist
571633 and len (objects )
572634 + len (
635+ ## Also search tasklist without comp-type to see if we get all objects
573636 tasklist .search (
574637 start = datetime (2000 , 1 , 1 , tzinfo = utc ),
575638 end = datetime (2001 , 1 , 1 , tzinfo = utc ),
639+ post_filter = False ,
576640 )
577641 )
578- == self .checker .cnt
642+ == self .checker .cnt and
643+ (tswoc or not tswc )
579644 ):
580645 self .set_feature (
581646 "search.comp-type-optional" ,
@@ -584,7 +649,7 @@ def _run_check(self):
584649 "description" : "comp-filter is redundant in search as a calendar can only hold one kind of components" ,
585650 },
586651 )
587- elif len (objects ) == self .checker .cnt :
652+ elif len (objects ) == self .checker .cnt and ( tswoc or not tswc ) :
588653 self .set_feature ("search.comp-type-optional" )
589654 else :
590655 ## TODO ... we need to do more testing on search to conclude certainly on this one. But at least we get something out.
@@ -599,7 +664,7 @@ def _run_check(self):
599664 self .set_feature ("search.comp-type-optional" , {"support" : "ungraceful" })
600665
601666
602- class CheckRecurrenceSearch (Check ):
667+ class CheckRecurrenceSearch (Check , SearchMixIn ):
603668 depends_on = {CheckSearch }
604669 features_to_be_checked = {
605670 "search.recurrences.includes-implicit.todo" ,
@@ -661,6 +726,10 @@ def _run_check(self):
661726 event = True ,
662727 post_filter = False ,
663728 )
729+ ## Xandikos version 0.2.12 breaks here for me.
730+ ## It didn't break earlier.
731+ ## Everything is exactly the same here. Same data on the server, same query
732+ ## There must be some local state in xandikos causing some bug to happen
664733 assert len (exception ) == 1
665734 far_future_recurrence = cal .search (
666735 start = datetime (2045 , 3 , 12 , tzinfo = utc ),
0 commit comments