##
# Copyright (c) 2005-2017 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
##


from twisted.internet.defer import inlineCallbacks

from twistedcaldav.ical import Component, normalize_iCalStr

from txdav.common.datastore.test.util import CommonCommonTests, populateCalendarsFrom
from twisted.trial.unittest import TestCase
from twext.python.clsprop import classproperty
from twistedcaldav.config import config
from txdav.common.icommondatastore import ObjectResourceTooBigError, \
    InvalidObjectResourceError, InvalidComponentForStoreError, InvalidUIDError, \
    UIDExistsError, UIDExistsElsewhereError
from txdav.caldav.icalendarstore import InvalidComponentTypeError, \
    TooManyAttendeesError, InvalidCalendarAccessError, ComponentUpdateState, \
    DuplicatePrivateCommentsError
from txdav.common.datastore.sql_tables import _BIND_MODE_WRITE
from txdav.caldav.datastore.sql import CalendarObject


class ImplicitRequests (CommonCommonTests, TestCase):
    """
    Test twistedcaldav.scheduyling.implicit with a Request object.
    """

    @inlineCallbacks
    def setUp(self):
        yield super(ImplicitRequests, self).setUp()
        yield self.buildStoreAndDirectory()
        yield self.populate()

    @inlineCallbacks
    def populate(self):
        yield populateCalendarsFrom(self.requirements, self.storeUnderTest())
        self.notifierFactory.reset()

    @classproperty(cache=False)
    def requirements(cls):  # @NoSelf
        return {
            "user01": {
                "calendar_1": {
                },
                "inbox": {
                },
            },
            "user02": {
                "calendar_1": {
                },
                "inbox": {
                },
            },
        }

    def storeUnderTest(self):
        """
        Create and return a L{CalendarStore} for testing.
        """
        return self._sqlCalendarStore

    @inlineCallbacks
    def test_doCreateResource(self):
        """
        Test that resource creation works.
        """

        data1 = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890-attendee-reply
DTSTAMP:20080601T120000Z
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
ORGANIZER;CN="User 01":mailto:user01@example.com
ATTENDEE:mailto:user01@example.com
ATTENDEE:mailto:user02@example.com
END:VEVENT
END:VCALENDAR
"""

        calendar_collection = (yield self.calendarUnderTest(home="user01"))
        calendar1 = Component.fromString(data1)
        yield calendar_collection.createCalendarObjectWithName("test.ics", calendar1)
        yield self.commit()

        calendar_resource1 = (yield self.calendarObjectUnderTest(name="test.ics", home="user01",))
        calendar1 = (yield calendar_resource1.component())
        calendar1 = str(calendar1).replace("\r\n ", "")
        self.assertTrue("urn:x-uid:user01" in calendar1)
        self.assertTrue("urn:x-uid:user02" in calendar1)
        self.assertTrue("CN=" in calendar1)
        yield self.commit()

    @inlineCallbacks
    def test_validation_maxResourceSize(self):
        """
        Test that various types of invalid calendar data are rejected when creating a resource.
        """

        data1 = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890-attendee-reply
DTSTAMP:20080601T120000Z
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
END:VEVENT
END:VCALENDAR
"""

        data2 = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890-attendee-reply
DTSTAMP:20080601T120000Z
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
SUMMARY:Changed
END:VEVENT
END:VCALENDAR
"""

        self.patch(config, "MaxResourceSize", 100)
        calendar_collection = (yield self.calendarUnderTest(home="user01"))
        calendar1 = Component.fromString(data1)
        yield self.failUnlessFailure(calendar_collection.createCalendarObjectWithName("test.ics", calendar1), ObjectResourceTooBigError)
        yield self.commit()

        self.patch(config, "MaxResourceSize", 10000)
        calendar_collection = (yield self.calendarUnderTest(home="user01"))
        calendar1 = Component.fromString(data1)
        yield calendar_collection.createCalendarObjectWithName("test.ics", calendar1)
        yield self.commit()

        self.patch(config, "MaxResourceSize", 100)
        calendar_resource = (yield self.calendarObjectUnderTest(name="test.ics", home="user01",))
        calendar2 = Component.fromString(data2)
        yield self.failUnlessFailure(calendar_resource.setComponent(calendar2), ObjectResourceTooBigError)
        yield self.commit()

    @inlineCallbacks
    def test_validation_validCalendarDataCheck(self):
        """
        Test that various types of invalid calendar data are rejected when creating a resource.
        """

        data = (
            "xyz",
            Component.fromString("""BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890-attendee-reply
DTSTAMP:20080601T120000Z
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
SUMMARY:1
SUMMARY:2
END:VEVENT
END:VCALENDAR
"""),

            Component.fromString("""BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
METHOD:PUBLISH
BEGIN:VEVENT
UID:12345-67890-attendee-reply
DTSTAMP:20080601T120000Z
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
END:VEVENT
END:VCALENDAR
"""),
        )

        for item in data:
            calendar_collection = (yield self.calendarUnderTest(home="user01"))
            calendar = item
            yield self.failUnlessFailure(calendar_collection.createCalendarObjectWithName("test.ics", calendar), InvalidObjectResourceError, InvalidComponentForStoreError)
            yield self.commit()

    @inlineCallbacks
    def test_validation_validSupportedComponentType(self):
        """
        Test that resources are restricted by component type.
        """

        data1 = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890-attendee-reply
DTSTAMP:20080601T120000Z
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
END:VEVENT
END:VCALENDAR
"""

        calendar_collection = (yield self.calendarUnderTest(home="user01"))
        yield calendar_collection.setSupportedComponents("VTODO")
        calendar = Component.fromString(data1)
        yield self.failUnlessFailure(calendar_collection.createCalendarObjectWithName("test.ics", calendar), InvalidComponentTypeError)
        yield self.commit()

    @inlineCallbacks
    def test_validation_validAttendeeListSizeCheck(self):
        """
        Test that resource with too many attendees are rejected.
        """

        data1 = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890-attendee-reply
DTSTAMP:20080601T120000Z
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
ORGANIZER;CN="User 01":mailto:user01@example.com
ATTENDEE:mailto:user01@example.com
ATTENDEE:mailto:user02@example.com
ATTENDEE:mailto:user03@example.com
ATTENDEE:mailto:user04@example.com
ATTENDEE:mailto:user05@example.com
END:VEVENT
END:VCALENDAR
"""

        self.patch(config, "MaxAttendeesPerInstance", 2)
        calendar_collection = (yield self.calendarUnderTest(home="user01"))
        calendar = Component.fromString(data1)
        yield self.failUnlessFailure(calendar_collection.createCalendarObjectWithName("test.ics", calendar), TooManyAttendeesError)
        yield self.commit()

    @inlineCallbacks
    def test_validation_validAccess_invalidValue(self):
        """
        Test that resource access mode changes are rejected.
        """

        data1 = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
X-CALENDARSERVER-ACCESS:BOGUS
BEGIN:VEVENT
UID:12345-67890-attendee-reply
DTSTAMP:20080601T120000Z
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
END:VEVENT
END:VCALENDAR
"""

        self.patch(config, "EnablePrivateEvents", True)
        calendar_collection = (yield self.calendarUnderTest(home="user01"))
        calendar = Component.fromString(data1)
        yield self.failUnlessFailure(calendar_collection.createCalendarObjectWithName("test.ics", calendar), InvalidCalendarAccessError)
        yield self.commit()

    @inlineCallbacks
    def test_validation_validAccess_authzChangeNotAllowed(self):
        """
        Test that resource access mode changes are rejected.
        """

        data1 = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
X-CALENDARSERVER-ACCESS:PRIVATE
BEGIN:VEVENT
UID:12345-67890-attendee-reply
DTSTAMP:20080601T120000Z
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
END:VEVENT
END:VCALENDAR
"""

        self.patch(config, "EnablePrivateEvents", True)
        txn = self.transactionUnderTest()
        txn._authz_uid = "user02"
        calendar_collection = (yield self.calendarUnderTest(home="user01"))
        calendar = Component.fromString(data1)
        yield self.failUnlessFailure(calendar_collection.createCalendarObjectWithName("test.ics", calendar), InvalidCalendarAccessError)
        yield self.commit()

        # This one should be OK
        txn = self.transactionUnderTest()
        txn._authz_uid = "user01"
        calendar_collection = (yield self.calendarUnderTest(home="user01"))
        calendar = Component.fromString(data1)
        yield calendar_collection.createCalendarObjectWithName("test.ics", calendar)
        yield self.commit()

        # This one should re-insert access mode
        data2 = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890-attendee-reply
DTSTAMP:20080601T120000Z
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
SUMMARY:Changed
END:VEVENT
END:VCALENDAR
"""

        txn = self.transactionUnderTest()
        txn._authz_uid = "user01"
        calendar_resource = (yield self.calendarObjectUnderTest(name="test.ics", home="user01",))
        calendar = Component.fromString(data2)
        yield calendar_resource.setComponent(calendar)
        yield self.commit()

        calendar_resource = (yield self.calendarObjectUnderTest(name="test.ics", home="user01",))
        calendar1 = (yield calendar_resource.component())
        calendar1 = str(calendar1).replace("\r\n ", "")
        self.assertTrue("X-CALENDARSERVER-ACCESS:PRIVATE" in calendar1)
        self.assertTrue("SUMMARY:Changed" in calendar1)
        yield self.commit()

    @inlineCallbacks
    def test_validation_overwriteUID(self):
        """
        Test that a change to a resource UID is not allowed.
        """

        data1 = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890-attendee-reply
DTSTAMP:20080601T120000Z
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
END:VEVENT
END:VCALENDAR
"""

        calendar_collection = (yield self.calendarUnderTest(home="user01"))
        calendar = Component.fromString(data1)
        yield calendar_collection.createCalendarObjectWithName("test.ics", calendar)
        yield self.commit()

        # This one should fail
        data2 = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890-attendee-reply-1
DTSTAMP:20080601T120000Z
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
END:VEVENT
END:VCALENDAR
"""

        calendar_resource = (yield self.calendarObjectUnderTest(name="test.ics", home="user01",))
        calendar = Component.fromString(data2)
        yield self.failUnlessFailure(calendar_resource.setComponent(calendar), InvalidUIDError)
        yield self.commit()

    @inlineCallbacks
    def test_validation_duplicateUIDSameCalendar(self):
        """
        Test that a resource with a duplicate UID in the same calendar is not allowed.
        """

        data1 = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890-attendee-reply
DTSTAMP:20080601T120000Z
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
END:VEVENT
END:VCALENDAR
"""

        calendar_collection = (yield self.calendarUnderTest(home="user01"))
        calendar = Component.fromString(data1)
        yield calendar_collection.createCalendarObjectWithName("test.ics", calendar)
        yield self.commit()

        # This one should fail
        data2 = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890-attendee-reply
DTSTAMP:20080601T120000Z
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
SUMMARY:Changed
END:VEVENT
END:VCALENDAR
"""

        calendar_collection = (yield self.calendarUnderTest(home="user01"))
        calendar = Component.fromString(data2)
        yield self.failUnlessFailure(calendar_collection.createCalendarObjectWithName("test2.ics", calendar), UIDExistsError)
        yield self.commit()

    @inlineCallbacks
    def test_validation_duplicateUIDDifferentCalendar(self):
        """
        Test that a resource with a duplicate UID in a different calendar is not allowed.
        """

        data1 = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890-attendee-reply
DTSTAMP:20080601T120000Z
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
END:VEVENT
END:VCALENDAR
"""

        calendar_collection = (yield self.calendarUnderTest(home="user01"))
        calendar = Component.fromString(data1)
        yield calendar_collection.createCalendarObjectWithName("test.ics", calendar)
        yield self.commit()

        # This one should fail
        data2 = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890-attendee-reply
DTSTAMP:20080601T120000Z
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
SUMMARY:Changed
END:VEVENT
END:VCALENDAR
"""

        home_collection = (yield self.homeUnderTest(name="user01"))
        calendar_collection_2 = (yield home_collection.createCalendarWithName("calendar_2"))
        calendar = Component.fromString(data2)
        yield self.failUnlessFailure(calendar_collection_2.createCalendarObjectWithName("test2.ics", calendar), UIDExistsElsewhereError)
        yield self.commit()

    @inlineCallbacks
    def test_validation_noPreservePrivateComments(self):
        """
        Test that attendee private comments are no longer restored.
        """

        data1 = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890-attendee-reply
DTSTAMP:20080601T120000Z
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
X-CALENDARSERVER-PRIVATE-COMMENT:My Comment
END:VEVENT
END:VCALENDAR
"""

        calendar_collection = (yield self.calendarUnderTest(home="user01"))
        calendar = Component.fromString(data1)
        yield calendar_collection.createCalendarObjectWithName("test.ics", calendar)
        yield self.commit()

        data2 = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890-attendee-reply
DTSTAMP:20080601T120000Z
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
SUMMARY:Changed
END:VEVENT
END:VCALENDAR
"""

        txn = self.transactionUnderTest()
        txn._authz_uid = "user01"
        calendar_resource = (yield self.calendarObjectUnderTest(name="test.ics", home="user01",))
        calendar = Component.fromString(data2)
        yield calendar_resource.setComponent(calendar)
        yield self.commit()

        calendar_resource = (yield self.calendarObjectUnderTest(name="test.ics", home="user01",))
        calendar1 = (yield calendar_resource.component())
        calendar1 = str(calendar1).replace("\r\n ", "")
        self.assertFalse("X-CALENDARSERVER-PRIVATE-COMMENT:My Comment" in calendar1)
        self.assertTrue("SUMMARY:Changed" in calendar1)
        yield self.commit()

    @inlineCallbacks
    def test_validation_preserveOrganizerPrivateComments(self):
        """
        Test that organizer private comments are restored.
        """

        data1 = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890-organizer
DTSTAMP:20080601T120000Z
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
X-CALENDARSERVER-ATTENDEE-COMMENT;X-CALENDARSERVER-ATTENDEE-REF="urn:x-uid:user01";
 X-CALENDARSERVER-DTSTAMP=20131101T100000Z:Someone else's comment
END:VEVENT
END:VCALENDAR
"""

        calendar_collection = (yield self.calendarUnderTest(home="user01"))
        calendar = Component.fromString(data1)
        yield calendar_collection.createCalendarObjectWithName("test.ics", calendar)
        yield self.commit()

        data2 = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890-organizer
DTSTAMP:20080601T120000Z
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
SUMMARY:Changed
END:VEVENT
END:VCALENDAR
"""

        txn = self.transactionUnderTest()
        txn._authz_uid = "user01"
        calendar_resource = (yield self.calendarObjectUnderTest(name="test.ics", home="user01",))
        calendar = Component.fromString(data2)
        yield calendar_resource.setComponent(calendar)
        yield self.commit()

        calendar_resource = (yield self.calendarObjectUnderTest(name="test.ics", home="user01",))
        calendar1 = (yield calendar_resource.component())
        calendar1 = str(calendar1).replace("\r\n ", "")
        self.assertTrue("X-CALENDARSERVER-ATTENDEE-COMMENT;X-CALENDARSERVER-ATTENDEE-REF=\"urn:x-uid:user01\";X-CALENDARSERVER-DTSTAMP=20131101T100000Z:Someone else's comment" in calendar1)
        self.assertTrue("SUMMARY:Changed" in calendar1)
        yield self.commit()

    @inlineCallbacks
    def test_validation_replaceMissingToDoProperties_OrganizerAttendee(self):
        """
        Test that missing scheduling properties in VTODOs are recovered.
        """

        data1 = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VTODO
UID:12345-67890-attendee-reply
DTSTAMP:20080601T120000Z
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
ORGANIZER;CN="User 01":mailto:user01@example.com
ATTENDEE:mailto:user01@example.com
ATTENDEE:mailto:user02@example.com
END:VTODO
END:VCALENDAR
"""

        calendar_collection = (yield self.calendarUnderTest(home="user01"))
        calendar = Component.fromString(data1)
        yield calendar_collection.createCalendarObjectWithName("test.ics", calendar)
        yield self.commit()

        data2 = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VTODO
UID:12345-67890-attendee-reply
DTSTAMP:20080601T120000Z
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
SUMMARY:Changed
END:VTODO
END:VCALENDAR
"""

        txn = self.transactionUnderTest()
        txn._authz_uid = "user01"
        calendar_resource = (yield self.calendarObjectUnderTest(name="test.ics", home="user01",))
        calendar = Component.fromString(data2)
        yield calendar_resource.setComponent(calendar)
        yield self.commit()

        calendar_resource = (yield self.calendarObjectUnderTest(name="test.ics", home="user01",))
        calendar1 = (yield calendar_resource.component())
        calendar1 = str(calendar1).replace("\r\n ", "")
        self.assertTrue("ORGANIZER" in calendar1)
        self.assertTrue("ATTENDEE" in calendar1)
        self.assertTrue("SUMMARY:Changed" in calendar1)
        yield self.commit()

    @inlineCallbacks
    def test_validation_replaceMissingToDoProperties_Completed(self):
        """
        Test that VTODO completed status is fixed.
        """

        data1 = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VTODO
UID:12345-67890-attendee-reply
DTSTAMP:20080601T120000Z
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
ORGANIZER;CN="User 01":mailto:user01@example.com
ATTENDEE:mailto:user01@example.com
ATTENDEE:mailto:user02@example.com
END:VTODO
END:VCALENDAR
"""

        calendar_collection = (yield self.calendarUnderTest(home="user01"))
        calendar = Component.fromString(data1)
        yield calendar_collection.createCalendarObjectWithName("test.ics", calendar)
        yield self.commit()

        data2 = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VTODO
UID:12345-67890-attendee-reply
DTSTAMP:20080601T120000Z
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
SUMMARY:Changed
COMPLETED:20080601T140000Z
ORGANIZER;CN="User 01":mailto:user01@example.com
ATTENDEE:mailto:user01@example.com
ATTENDEE:mailto:user02@example.com
END:VTODO
END:VCALENDAR
"""

        txn = self.transactionUnderTest()
        txn._authz_uid = "user01"
        calendar_resource = (yield self.calendarObjectUnderTest(name="test.ics", home="user01",))
        calendar = Component.fromString(data2)
        yield calendar_resource.setComponent(calendar)
        yield self.commit()

        calendar_resource = (yield self.calendarObjectUnderTest(name="test.ics", home="user01",))
        calendar1 = (yield calendar_resource.component())
        calendar1 = str(calendar1).replace("\r\n ", "")
        self.assertTrue("ORGANIZER" in calendar1)
        self.assertTrue("ATTENDEE" in calendar1)
        self.assertTrue("SUMMARY:Changed" in calendar1)
        self.assertTrue("PARTSTAT=COMPLETED" in calendar1)
        yield self.commit()

    @inlineCallbacks
    def test_validation_dropboxPathNormalization(self):
        """
        Test that dropbox paths are normalized.
        """

        calendar_collection = (yield self.calendarUnderTest(home="user01"))
        sharee_home = (yield self.homeUnderTest(name="user02"))
        shared_name = (yield calendar_collection.shareWith(sharee_home, _BIND_MODE_WRITE,))
        yield self.commit()

        data1 = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VTODO
UID:12345-67890-attendee-reply
DTSTAMP:20080601T120000Z
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
X-APPLE-DROPBOX:https://example.com/calendars/users/user02/dropbox/123.dropbox
ATTACH;VALUE=URI:https://example.com/calendars/users/user02/dropbox/123.dropbox/1.txt
ATTACH;VALUE=URI:https://example.org/attachments/2.txt
END:VTODO
END:VCALENDAR
"""

        calendar_collection = (yield self.calendarUnderTest(name=shared_name, home="user02"))
        calendar = Component.fromString(data1)
        yield calendar_collection.createCalendarObjectWithName("test.ics", calendar)
        yield self.commit()

        calendar_resource = (yield self.calendarObjectUnderTest(name="test.ics", calendar_name=shared_name, home="user02",))
        calendar1 = (yield calendar_resource.component())
        calendar1 = str(calendar1).replace("\r\n ", "")
        self.assertTrue("X-APPLE-DROPBOX:https://example.com/calendars/__uids__/user01/dropbox/123.dropbox" in calendar1)
        self.assertTrue("ATTACH:https://example.com/calendars/__uids__/user01/dropbox/123.dropbox/1.txt" in calendar1)
        self.assertTrue("ATTACH:https://example.org/attachments/2.txt" in calendar1)
        yield self.commit()

    @inlineCallbacks
    def test_validation_processAlarms_DuplicateRemoval(self):
        """
        Test that duplicate alarms are removed.
        """

        data1 = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890-attendee-reply
DTSTAMP:20080601T120000Z
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
END:VEVENT
END:VCALENDAR
"""

        calendar_collection = (yield self.calendarUnderTest(home="user01"))
        calendar = Component.fromString(data1)
        yield calendar_collection.createCalendarObjectWithName("test.ics", calendar)
        yield self.commit()

        data2 = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890-attendee-reply
DTSTAMP:20080601T120000Z
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
SUMMARY:Changed
BEGIN:VALARM
X-WR-ALARMUID:D9D1AC84-F629-4B9D-9B6B-4A6CA9A11FEF
UID:D9D1AC84-F629-4B9D-9B6B-4A6CA9A11FEF
DESCRIPTION:Event reminder
TRIGGER:-PT8M
ACTION:DISPLAY
END:VALARM
BEGIN:VALARM
X-WR-ALARMUID:D9D1AC84-F629-4B9D-9B6B-4A6CA9A11FEF
UID:D9D1AC84-F629-4B9D-9B6B-4A6CA9A11FEF
DESCRIPTION:Event reminder
TRIGGER:-PT8M
ACTION:DISPLAY
END:VALARM
END:VEVENT
END:VCALENDAR
"""

        txn = self.transactionUnderTest()
        txn._authz_uid = "user01"
        calendar_resource = (yield self.calendarObjectUnderTest(name="test.ics", home="user01",))
        calendar = Component.fromString(data2)
        result = (yield calendar_resource.setComponent(calendar))
        yield self.commit()
        self.assertTrue(result)

        calendar_resource = (yield self.calendarObjectUnderTest(name="test.ics", home="user01",))
        calendar1 = (yield calendar_resource.component())
        calendar1 = str(calendar1).replace("\r\n ", "")
        self.assertEqual(calendar1.count("BEGIN:VALARM"), 1)
        self.assertTrue("SUMMARY:Changed" in calendar1)
        yield self.commit()

    @inlineCallbacks
    def test_validation_processAlarms_AddDefault(self):
        """
        Test that default alarms are added.
        """

        alarm = """BEGIN:VALARM
X-WR-ALARMUID:D9D1AC84-F629-4B9D-9B6B-4A6CA9A11FEF
UID:D9D1AC84-F629-4B9D-9B6B-4A6CA9A11FEF
DESCRIPTION:Event reminder
TRIGGER:-PT8M
ACTION:DISPLAY
END:VALARM
"""

        home = (yield self.homeUnderTest(name="user01"))
        yield home.setDefaultAlarm(alarm, True, True)
        yield self.commit()

        data1 = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890-attendee-reply
DTSTAMP:20080601T120000Z
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
END:VEVENT
END:VCALENDAR
"""

        calendar_collection = (yield self.calendarUnderTest(home="user01"))
        calendar = Component.fromString(data1)
        yield calendar_collection.createCalendarObjectWithName("test.ics", calendar)
        yield self.commit()

        calendar_resource = (yield self.calendarObjectUnderTest(name="test.ics", home="user01",))
        calendar1 = (yield calendar_resource.component())
        calendar1 = str(calendar1).replace("\r\n ", "")
        self.assertEqual(calendar1.count("BEGIN:VALARM"), 1)
        yield self.commit()

    @inlineCallbacks
    def test_validation_processAlarms_NoDefaultShared(self):
        """
        Test that default alarms are not added to shared resources.
        """

        # Set default alarm for user02
        alarm = """BEGIN:VALARM
X-WR-ALARMUID:D9D1AC84-F629-4B9D-9B6B-4A6CA9A11FEF
UID:D9D1AC84-F629-4B9D-9B6B-4A6CA9A11FEF
DESCRIPTION:Event reminder
TRIGGER:-PT8M
ACTION:DISPLAY
END:VALARM
"""

        home = (yield self.homeUnderTest(name="user02"))
        yield home.setDefaultAlarm(alarm, True, True)
        yield self.commit()

        # user01 shares calendar with user02
        calendar_collection = (yield self.calendarUnderTest(home="user01"))
        sharee_home = (yield self.homeUnderTest(name="user02"))
        shared_name = (yield calendar_collection.shareWith(sharee_home, _BIND_MODE_WRITE,))
        yield self.commit()

        # user02 writes event to shared calendar
        data1 = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890-attendee-reply
DTSTAMP:20080601T120000Z
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
END:VEVENT
END:VCALENDAR
"""

        calendar_collection = (yield self.calendarUnderTest(name=shared_name, home="user02"))
        calendar = Component.fromString(data1)
        yield calendar_collection.createCalendarObjectWithName("test.ics", calendar)
        yield self.commit()

        calendar_resource = (yield self.calendarObjectUnderTest(name="test.ics", calendar_name=shared_name, home="user02",))
        calendar1 = (yield calendar_resource.component())
        calendar1 = str(calendar1).replace("\r\n ", "")
        self.assertEqual(calendar1.count("BEGIN:VALARM"), 0)
        yield self.commit()

    @inlineCallbacks
    def test_validation_mergePerUserData(self):
        """
        Test that per-user data is correctly stored and retrieved.
        """

        calendar_collection = (yield self.calendarUnderTest(home="user01"))
        sharee_home = (yield self.homeUnderTest(name="user02"))
        shared_name = (yield calendar_collection.shareWith(sharee_home, _BIND_MODE_WRITE,))
        yield self.commit()

        data1 = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890-attendee-reply
DTSTAMP:20080601T120000Z
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
BEGIN:VALARM
X-WR-ALARMUID:D9D1AC84-F629-4B9D-9B6B-4A6CA9A11FEF
UID:D9D1AC84-F629-4B9D-9B6B-4A6CA9A11FEF
DESCRIPTION:Event reminder
TRIGGER:-PT5M
ACTION:DISPLAY
END:VALARM
END:VEVENT
END:VCALENDAR
"""

        calendar_collection = (yield self.calendarUnderTest(home="user01"))
        calendar = Component.fromString(data1)
        yield calendar_collection.createCalendarObjectWithName("test.ics", calendar)
        yield self.commit()

        data2 = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890-attendee-reply
DTSTAMP:20080601T120000Z
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
BEGIN:VALARM
X-WR-ALARMUID:D9D1AC84-F629-4B9D-9B6B-4A6CA9A11FEF
UID:D9D1AC84-F629-4B9D-9B6B-4A6CA9A11FEF
DESCRIPTION:Event reminder
TRIGGER:-PT10M
ACTION:DISPLAY
END:VALARM
END:VEVENT
END:VCALENDAR
"""

        calendar_resource = (yield self.calendarObjectUnderTest(name="test.ics", calendar_name=shared_name, home="user02",))
        calendar = Component.fromString(data2)
        yield calendar_resource.setComponent(calendar)
        yield self.commit()

        calendar_resource = (yield self.calendarObjectUnderTest(name="test.ics", home="user01",))

        # Unfiltered view of event
        calendar1 = (yield calendar_resource.component())
        calendar1 = str(calendar1).replace("\r\n ", "")
        self.assertTrue("TRIGGER:-PT5M" in calendar1)
        self.assertTrue("TRIGGER:-PT10M" in calendar1)
        self.assertEqual(calendar1.count("BEGIN:VALARM"), 2)

        # user01 view of event
        calendar1 = (yield calendar_resource.componentForUser("user01"))
        calendar1 = str(calendar1).replace("\r\n ", "")
        self.assertTrue("TRIGGER:-PT5M" in calendar1)
        self.assertFalse("TRIGGER:-PT10M" in calendar1)
        self.assertEqual(calendar1.count("BEGIN:VALARM"), 1)

        # user02 view of event
        calendar1 = (yield calendar_resource.componentForUser("user02"))
        calendar1 = str(calendar1).replace("\r\n ", "")
        self.assertFalse("TRIGGER:-PT5M" in calendar1)
        self.assertTrue("TRIGGER:-PT10M" in calendar1)
        self.assertEqual(calendar1.count("BEGIN:VALARM"), 1)

        yield self.commit()

        calendar_resource = (yield self.calendarObjectUnderTest(name="test.ics", calendar_name=shared_name, home="user02",))

        # Unfiltered view of event
        calendar1 = (yield calendar_resource.component())
        calendar1 = str(calendar1).replace("\r\n ", "")
        self.assertTrue("TRIGGER:-PT5M" in calendar1)
        self.assertTrue("TRIGGER:-PT10M" in calendar1)
        self.assertEqual(calendar1.count("BEGIN:VALARM"), 2)

        # user01 view of event
        calendar1 = (yield calendar_resource.componentForUser("user01"))
        calendar1 = str(calendar1).replace("\r\n ", "")
        self.assertTrue("TRIGGER:-PT5M" in calendar1)
        self.assertFalse("TRIGGER:-PT10M" in calendar1)
        self.assertEqual(calendar1.count("BEGIN:VALARM"), 1)

        # user02 view of event
        calendar1 = (yield calendar_resource.componentForUser("user02"))
        calendar1 = str(calendar1).replace("\r\n ", "")
        self.assertFalse("TRIGGER:-PT5M" in calendar1)
        self.assertTrue("TRIGGER:-PT10M" in calendar1)
        self.assertEqual(calendar1.count("BEGIN:VALARM"), 1)

        yield self.commit()

    @inlineCallbacks
    def test_validation_processScheduleTags(self):
        """
        Test that schedule tags are correctly updated.
        """

        data1 = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890-attendee-reply
DTSTAMP:20080601T120000Z
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
END:VEVENT
END:VCALENDAR
"""

        calendar_collection = (yield self.calendarUnderTest(home="user01"))
        calendar = Component.fromString(data1)
        yield calendar_collection.createCalendarObjectWithName("test.ics", calendar)
        yield self.commit()

        data2 = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890-attendee-reply
DTSTAMP:20080601T120000Z
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
ORGANIZER;CN="User 01":mailto:user01@example.com
ATTENDEE:mailto:user01@example.com
ATTENDEE:mailto:user02@example.com
SUMMARY:Changed #1
END:VEVENT
END:VCALENDAR
"""

        calendar_resource = (yield self.calendarObjectUnderTest(name="test.ics", home="user01",))
        calendar = Component.fromString(data2)
        yield calendar_resource.setComponent(calendar)
        schedule_tag = calendar_resource.scheduleTag
        yield self.commit()

        data3 = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890-attendee-reply
DTSTAMP:20080601T120000Z
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
ORGANIZER;CN="User 01":mailto:user01@example.com
ATTENDEE:mailto:user01@example.com
ATTENDEE:mailto:user02@example.com
SUMMARY:Changed #2
END:VEVENT
END:VCALENDAR
"""

        calendar_resource = (yield self.calendarObjectUnderTest(name="test.ics", home="user01",))
        calendar = Component.fromString(data3)
        yield calendar_resource.setComponent(calendar)
        self.assertNotEqual(calendar_resource.scheduleTag, schedule_tag)
        schedule_tag = calendar_resource.scheduleTag
        yield self.commit()

        data4 = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890-attendee-reply
DTSTAMP:20080601T120000Z
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
ORGANIZER;CN="User 01":mailto:user01@example.com
ATTENDEE:mailto:user01@example.com
ATTENDEE;PARTSTAT=ACCEPTED:mailto:user02@example.com
SUMMARY:Changed #2
END:VEVENT
END:VCALENDAR
"""

        calendar_resource = (yield self.calendarObjectUnderTest(name="test.ics", home="user01",))
        calendar = Component.fromString(data4)
        yield calendar_resource._setComponentInternal(calendar, internal_state=ComponentUpdateState.ORGANIZER_ITIP_UPDATE)
        self.assertEqual(calendar_resource.scheduleTag, schedule_tag)
        yield self.commit()

    @inlineCallbacks
    def test_validation_duplicatePrivateCommentsOKWIthiTIP(self):
        """
        Test that an iTIP update to an organizer event with duplicate private comments
        does not fail.
        """

        data1 = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890-attendee-reply
DTSTAMP:20080601T120000Z
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
ORGANIZER;CN="User 01":mailto:user01@example.com
ATTENDEE:mailto:user01@example.com
ATTENDEE:mailto:user02@example.com
END:VEVENT
END:VCALENDAR
"""

        data2 = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890-attendee-reply
DTSTAMP:20080601T120000Z
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
ORGANIZER;CN="User 01":mailto:user01@example.com
ATTENDEE:mailto:user01@example.com
ATTENDEE:mailto:user02@example.com
X-CALENDARSERVER-ATTENDEE-COMMENT;X-CALENDARSERVER-ATTENDEE-REF="urn:uuid:
 user02";X-CALENDARSERVER-DTSTAMP=20140224T181133Z:Comment
X-CALENDARSERVER-ATTENDEE-COMMENT;X-CALENDARSERVER-ATTENDEE-REF="urn:uuid:
 user02";X-CALENDARSERVER-DTSTAMP=20140224T181133Z:Comment
END:VEVENT
END:VCALENDAR
"""

        data3 = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890-attendee-reply
DTSTAMP:20080601T120000Z
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
ORGANIZER;CN="User 01":mailto:user01@example.com
ATTENDEE:mailto:user01@example.com
ATTENDEE;PARTSTAT=ACCEPTED:mailto:user02@example.com
X-CALENDARSERVER-ATTENDEE-COMMENT;X-CALENDARSERVER-ATTENDEE-REF="urn:uuid:
 user02";X-CALENDARSERVER-DTSTAMP=20140224T181133Z:Comment
X-CALENDARSERVER-ATTENDEE-COMMENT;X-CALENDARSERVER-ATTENDEE-REF="urn:uuid:
 user02";X-CALENDARSERVER-DTSTAMP=20140224T181133Z:Comment
END:VEVENT
END:VCALENDAR
"""

        calendar_collection = (yield self.calendarUnderTest(home="user01"))
        calendar = Component.fromString(data1)
        yield calendar_collection.createCalendarObjectWithName("test.ics", calendar)
        yield self.commit()

        calendar_resource = (yield self.calendarObjectUnderTest(name="test.ics", home="user01",))
        calendar = Component.fromString(data2)
        yield calendar_resource._setComponentInternal(calendar, internal_state=ComponentUpdateState.RAW)
        yield self.commit()

        calendar_resource = (yield self.calendarObjectUnderTest(name="test.ics", home="user01",))
        calendar = Component.fromString(data3)
        yield calendar_resource._setComponentInternal(calendar, internal_state=ComponentUpdateState.ORGANIZER_ITIP_UPDATE)
        yield self.commit()

        calendar_resource = (yield self.calendarObjectUnderTest(name="test.ics", home="user01",))
        calendar = Component.fromString(data3)
        yield self.failUnlessFailure(calendar_resource.setComponent(calendar), DuplicatePrivateCommentsError)
        yield self.commit()

    @inlineCallbacks
    def test_validation_deleteWithDuplicatePrivateComments(self):
        """
        Test that attendee private comments are no longer restored.
        """

        data1 = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890-attendee-reply
DTSTAMP:20080601T120000Z
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
ORGANIZER;CN="User 01":mailto:user01@example.com
ATTENDEE:mailto:user01@example.com
ATTENDEE:mailto:user02@example.com
END:VEVENT
END:VCALENDAR
"""

        data2 = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890-attendee-reply
DTSTAMP:20080601T120000Z
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
ORGANIZER;CN="User 01":mailto:user01@example.com
ATTENDEE:mailto:user01@example.com
ATTENDEE:mailto:user02@example.com
X-CALENDARSERVER-ATTENDEE-COMMENT;X-CALENDARSERVER-ATTENDEE-REF="urn:uuid:
 user02";X-CALENDARSERVER-DTSTAMP=20140224T181133Z:Comment
X-CALENDARSERVER-ATTENDEE-COMMENT;X-CALENDARSERVER-ATTENDEE-REF="urn:uuid:
 user02";X-CALENDARSERVER-DTSTAMP=20140224T181133Z:Comment
END:VEVENT
END:VCALENDAR
"""

        data3 = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890-attendee-reply
DTSTAMP:20080601T120000Z
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
ORGANIZER;CN="User 01":mailto:user01@example.com
ATTENDEE:mailto:user01@example.com
ATTENDEE;PARTSTAT=ACCEPTED:mailto:user02@example.com
END:VEVENT
END:VCALENDAR
"""

        calendar_collection = (yield self.calendarUnderTest(home="user01"))
        calendar = Component.fromString(data1)
        yield calendar_collection.createCalendarObjectWithName("test.ics", calendar)
        yield self.commit()

        calendar_resource = (yield self.calendarObjectUnderTest(name="test.ics", home="user01",))
        calendar = Component.fromString(data2)
        yield calendar_resource._setComponentInternal(calendar, internal_state=ComponentUpdateState.RAW)
        yield self.commit()

        def raiseHere(otherself, component, inserting, internal_state):
            if component.hasDuplicatePrivateComments(doFix=False):
                raise ValueError

        self.patch(CalendarObject, "preservePrivateComments", raiseHere)

        calendar2 = (yield self.calendarUnderTest(name="calendar_1", home="user02"))
        cobjs = (yield calendar2.calendarObjects())
        self.assertTrue(len(cobjs) == 1)
        yield cobjs[0].setComponent(Component.fromString(data3))
        yield self.commit()

        calendar2 = (yield self.calendarUnderTest(name="calendar_1", home="user02"))
        cobjs = (yield calendar2.calendarObjects())
        calendar = yield cobjs[0].component()
        self.assertTrue('SCHEDULE-STATUS=5.0' in normalize_iCalStr(calendar))
        yield self.commit()

        calendar2 = (yield self.calendarUnderTest(name="calendar_1", home="user02"))
        cobjs = (yield calendar2.calendarObjects())
        self.assertTrue(len(cobjs) == 1)
        yield cobjs[0].remove()
        yield self.commit()

        calendar2 = (yield self.calendarUnderTest(name="calendar_1", home="user02"))
        cobjs = (yield calendar2.calendarObjects())
        self.assertTrue(len(cobjs) == 0)
        yield self.commit()

        self.flushLoggedErrors(ValueError)
