[CalendarServer-changes] [11057] CalendarServer/branches/users/cdaboo/store-scheduling
source_changes at macosforge.org
source_changes at macosforge.org
Thu Apr 18 11:21:54 PDT 2013
Revision: 11057
http://trac.calendarserver.org//changeset/11057
Author: cdaboo at apple.com
Date: 2013-04-18 11:21:54 -0700 (Thu, 18 Apr 2013)
Log Message:
-----------
Checkpoint - getting unit tests to pass (currently ignored the cli tools). Moved CalDAV scheduling resource back into twistedcaldav.
Modified Paths:
--------------
CalendarServer/branches/users/cdaboo/store-scheduling/calendarserver/push/test/test_amppush.py
CalendarServer/branches/users/cdaboo/store-scheduling/calendarserver/push/test/test_applepush.py
CalendarServer/branches/users/cdaboo/store-scheduling/calendarserver/push/test/test_notifier.py
CalendarServer/branches/users/cdaboo/store-scheduling/calendarserver/tap/test/test_caldav.py
CalendarServer/branches/users/cdaboo/store-scheduling/calendarserver/tools/test/test_purge.py
CalendarServer/branches/users/cdaboo/store-scheduling/twext/web2/dav/test/util.py
CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/directory/directory.py
CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/directory/principal.py
CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/directory/test/test_calendar.py
CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/directory/test/test_principal.py
CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/resource.py
CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/storebridge.py
CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/test/test_calendarquery.py
CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/test/test_collectioncontents.py
CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/test/test_multiget.py
CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/test/test_wrapping.py
CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/test/util.py
CalendarServer/branches/users/cdaboo/store-scheduling/txdav/base/propertystore/sql.py
CalendarServer/branches/users/cdaboo/store-scheduling/txdav/base/propertystore/test/test_sql.py
CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/caldav/scheduler.py
CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/scheduler.py
CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/sql.py
CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/test/common.py
CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/test/test_sql.py
CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/icalendarstore.py
CalendarServer/branches/users/cdaboo/store-scheduling/txdav/common/datastore/test/util.py
Added Paths:
-----------
CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/scheduling_store/
CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/scheduling_store/__init__.py
CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/scheduling_store/caldav/
CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/scheduling_store/caldav/__init__.py
CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/scheduling_store/caldav/resource.py
CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/scheduling_store/caldav/test/
CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/scheduling_store/caldav/test/__init__.py
CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/scheduling_store/caldav/test/test_resource.py
Removed Paths:
-------------
CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/test/test_validation.py
CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/caldav/resource.py
CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/caldav/test/test_resource.py
Modified: CalendarServer/branches/users/cdaboo/store-scheduling/calendarserver/push/test/test_amppush.py
===================================================================
--- CalendarServer/branches/users/cdaboo/store-scheduling/calendarserver/push/test/test_amppush.py 2013-04-17 23:02:28 UTC (rev 11056)
+++ CalendarServer/branches/users/cdaboo/store-scheduling/calendarserver/push/test/test_amppush.py 2013-04-18 18:21:54 UTC (rev 11057)
@@ -16,10 +16,10 @@
from calendarserver.push.amppush import AMPPushMaster, AMPPushNotifierProtocol
from calendarserver.push.amppush import NotificationForID
-from twistedcaldav.test.util import TestCase
+from twistedcaldav.test.util import StoreTestCase
from twisted.internet.task import Clock
-class AMPPushMasterTests(TestCase):
+class AMPPushMasterTests(StoreTestCase):
def test_AMPPushMaster(self):
Modified: CalendarServer/branches/users/cdaboo/store-scheduling/calendarserver/push/test/test_applepush.py
===================================================================
--- CalendarServer/branches/users/cdaboo/store-scheduling/calendarserver/push/test/test_applepush.py 2013-04-17 23:02:28 UTC (rev 11056)
+++ CalendarServer/branches/users/cdaboo/store-scheduling/calendarserver/push/test/test_applepush.py 2013-04-18 18:21:54 UTC (rev 11057)
@@ -21,21 +21,14 @@
ApplePushNotifierService, APNProviderProtocol
)
from calendarserver.push.util import validToken, TokenHistory
-from twistedcaldav.test.util import TestCase
+from twistedcaldav.test.util import StoreTestCase
from twisted.internet.defer import inlineCallbacks, succeed
from twisted.internet.task import Clock
-from txdav.common.datastore.test.util import buildStore, CommonCommonTests
from txdav.common.icommondatastore import InvalidSubscriptionValues
-class ApplePushNotifierServiceTests(CommonCommonTests, TestCase):
+class ApplePushNotifierServiceTests(StoreTestCase):
@inlineCallbacks
- def setUp(self):
- yield super(ApplePushNotifierServiceTests, self).setUp()
- self.store = yield buildStore(self, None)
-
-
- @inlineCallbacks
def test_ApplePushNotifierService(self):
settings = {
@@ -67,7 +60,7 @@
}
# Add subscriptions
- txn = self.store.newTransaction()
+ txn = self._sqlCalendarStore.newTransaction()
# Ensure empty values don't get through
try:
@@ -116,7 +109,7 @@
# Set up the service
clock = Clock()
service = (yield ApplePushNotifierService.makeService(settings,
- self.store, testConnectorClass=TestConnector, reactor=clock))
+ self._sqlCalendarStore, testConnectorClass=TestConnector, reactor=clock))
self.assertEquals(set(service.providers.keys()), set(["CalDAV", "CardDAV"]))
self.assertEquals(set(service.feedbacks.keys()), set(["CalDAV", "CardDAV"]))
@@ -125,7 +118,7 @@
# Notification arrives from calendar server
dataChangedTimestamp = 1354815999
- txn = self.store.newTransaction()
+ txn = self._sqlCalendarStore.newTransaction()
yield service.enqueue(txn, "/CalDAV/calendars.example.com/user01/calendar/",
dataChangedTimestamp=dataChangedTimestamp)
yield txn.commit()
@@ -166,7 +159,7 @@
# Reset sent data
providerConnector.transport.data = None
# Send notification while service is connected
- txn = self.store.newTransaction()
+ txn = self._sqlCalendarStore.newTransaction()
yield service.enqueue(txn, "/CalDAV/calendars.example.com/user01/calendar/")
yield txn.commit()
clock.advance(1) # so that first push is sent
@@ -208,7 +201,7 @@
self.assertEquals(len(providerConnector.service.protocol.buffer), 1)
# Prior to feedback, there are 2 subscriptions
- txn = self.store.newTransaction()
+ txn = self._sqlCalendarStore.newTransaction()
subscriptions = (yield txn.apnSubscriptionsByToken(token))
yield txn.commit()
self.assertEquals(len(subscriptions), 2)
@@ -247,7 +240,7 @@
self.assertEquals(len(feedbackConnector.service.protocol.buffer), 1)
# The second subscription should now be gone
- txn = self.store.newTransaction()
+ txn = self._sqlCalendarStore.newTransaction()
subscriptions = (yield txn.apnSubscriptionsByToken(token))
yield txn.commit()
self.assertEquals(subscriptions,
@@ -265,7 +258,7 @@
self.assertTrue((id, token2) not in providerConnector.service.protocol.history.history)
# All subscriptions for this token should now be gone
- txn = self.store.newTransaction()
+ txn = self._sqlCalendarStore.newTransaction()
subscriptions = (yield txn.apnSubscriptionsByToken(token2))
yield txn.commit()
self.assertEquals(subscriptions, [])
@@ -275,19 +268,19 @@
#
# Create two subscriptions, one old and one new
- txn = self.store.newTransaction()
+ txn = self._sqlCalendarStore.newTransaction()
now = int(time.time())
yield txn.addAPNSubscription(token2, key1, now - 2 * 24 * 60 * 60, uid, userAgent, ipAddr) # old
yield txn.addAPNSubscription(token2, key2, now, uid, userAgent, ipAddr) # recent
yield txn.commit()
# Purge old subscriptions
- txn = self.store.newTransaction()
+ txn = self._sqlCalendarStore.newTransaction()
yield txn.purgeOldAPNSubscriptions(now - 60 * 60)
yield txn.commit()
# Check that only the recent subscription remains
- txn = self.store.newTransaction()
+ txn = self._sqlCalendarStore.newTransaction()
subscriptions = (yield txn.apnSubscriptionsByToken(token2))
yield txn.commit()
self.assertEquals(len(subscriptions), 1)
Modified: CalendarServer/branches/users/cdaboo/store-scheduling/calendarserver/push/test/test_notifier.py
===================================================================
--- CalendarServer/branches/users/cdaboo/store-scheduling/calendarserver/push/test/test_notifier.py 2013-04-17 23:02:28 UTC (rev 11056)
+++ CalendarServer/branches/users/cdaboo/store-scheduling/calendarserver/push/test/test_notifier.py 2013-04-18 18:21:54 UTC (rev 11057)
@@ -14,13 +14,12 @@
# limitations under the License.
##
-from twistedcaldav.test.util import TestCase
+from twistedcaldav.test.util import StoreTestCase
from calendarserver.push.notifier import PushDistributor
from calendarserver.push.notifier import getPubSubAPSConfiguration
from calendarserver.push.notifier import PushNotificationWork
from twisted.internet.defer import inlineCallbacks, succeed
from twistedcaldav.config import ConfigDict
-from txdav.common.datastore.test.util import buildStore
class StubService(object):
@@ -38,7 +37,7 @@
-class PushDistributorTests(TestCase):
+class PushDistributorTests(StoreTestCase):
@inlineCallbacks
def test_enqueue(self):
@@ -95,20 +94,19 @@
-class PushNotificationWorkTests(TestCase):
+class PushNotificationWorkTests(StoreTestCase):
@inlineCallbacks
def test_work(self):
- self.store = yield buildStore(self, None)
pushDistributor = StubDistributor()
def decorateTransaction(txn):
txn._pushDistributor = pushDistributor
- self.store.callWithNewTransactions(decorateTransaction)
+ self._sqlCalendarStore.callWithNewTransactions(decorateTransaction)
- txn = self.store.newTransaction()
+ txn = self._sqlCalendarStore.newTransaction()
wp = (yield txn.enqueue(PushNotificationWork,
pushID="/CalDAV/localhost/foo/",
))
@@ -117,7 +115,7 @@
self.assertEquals(pushDistributor.history, ["/CalDAV/localhost/foo/"])
pushDistributor.reset()
- txn = self.store.newTransaction()
+ txn = self._sqlCalendarStore.newTransaction()
wp = (yield txn.enqueue(PushNotificationWork,
pushID="/CalDAV/localhost/bar/",
))
Modified: CalendarServer/branches/users/cdaboo/store-scheduling/calendarserver/tap/test/test_caldav.py
===================================================================
--- CalendarServer/branches/users/cdaboo/store-scheduling/calendarserver/tap/test/test_caldav.py 2013-04-17 23:02:28 UTC (rev 11056)
+++ CalendarServer/branches/users/cdaboo/store-scheduling/calendarserver/tap/test/test_caldav.py 2013-04-18 18:21:54 UTC (rev 11057)
@@ -55,7 +55,7 @@
from twistedcaldav.directory.calendar import DirectoryCalendarHomeProvisioningResource
from twistedcaldav.directory.principal import DirectoryPrincipalProvisioningResource
-from twistedcaldav.test.util import TestCase, CapturingProcessProtocol
+from twistedcaldav.test.util import StoreTestCase, CapturingProcessProtocol
from calendarserver.tap.caldav import (
CalDAVOptions, CalDAVServiceMaker, CalDAVService, GroupOwnedUNIXServer,
@@ -93,6 +93,7 @@
self.childFDs = childFDs
+
class InMemoryProcessSpawner(Clock):
"""
Stub out L{IReactorProcess} and L{IReactorClock} so that we can examine the
@@ -149,12 +150,15 @@
def checkDirectory(self, *args, **kwargs):
pass
+
def checkFile(self, *args, **kwargs):
pass
+
def checkDirectories(self, *args, **kwargs):
pass
+
def loadConfiguration(self):
"""
Simple wrapper to avoid printing during test runs.
@@ -170,26 +174,30 @@
)
-class CalDAVOptionsTest (TestCase):
+
+class CalDAVOptionsTest (StoreTestCase):
"""
Test various parameters of our usage.Options subclass
"""
+ @inlineCallbacks
def setUp(self):
"""
Set up our options object, giving it a parent, and forcing the
global config to be loaded from defaults.
"""
- TestCase.setUp(self)
+ yield super(CalDAVOptionsTest, self).setUp()
self.config = TestCalDAVOptions()
self.config.parent = Options()
self.config.parent["uid"] = 0
self.config.parent["gid"] = 0
self.config.parent["nodaemon"] = False
+
def tearDown(self):
config.setDefaults(DEFAULT_CONFIG)
config.reload()
+
def test_overridesConfig(self):
"""
Test that values on the command line's -o and --option options
@@ -223,6 +231,7 @@
self.assertRaises(UsageError, self.config.parseOptions, argv)
+
def test_setsParent(self):
"""
Test that certain values are set on the parent (i.e. twistd's
@@ -271,6 +280,7 @@
myConfig.Authentication.Basic.Enabled
)
+
def test_specifyDictPath(self):
"""
Test that we can specify command line overrides to leafs using
@@ -289,14 +299,17 @@
self.assertEquals(config.MultiProcess["ProcessCount"], 102)
-class BaseServiceMakerTests(TestCase):
+
+
+class BaseServiceMakerTests(StoreTestCase):
"""
Utility class for ServiceMaker tests.
"""
configOptions = None
+ @inlineCallbacks
def setUp(self):
- TestCase.setUp(self)
+ yield super(BaseServiceMakerTests, self).setUp()
self.options = TestCalDAVOptions()
self.options.parent = Options()
self.options.parent["gid"] = None
@@ -324,13 +337,13 @@
"type": "twistedcaldav.directory.augment.AugmentXMLDB"
}
- self.config.UseDatabase = False
- self.config.ServerRoot = self.mktemp()
- self.config.ConfigRoot = "config"
- self.config.ProcessType = "Single"
- self.config.SSLPrivateKey = pemFile
+ self.config.UseDatabase = False
+ self.config.ServerRoot = self.mktemp()
+ self.config.ConfigRoot = "config"
+ self.config.ProcessType = "Single"
+ self.config.SSLPrivateKey = pemFile
self.config.SSLCertificate = pemFile
- self.config.EnableSSL = True
+ self.config.EnableSSL = True
self.config.Memcached.Pools.Default.ClientEnabled = False
self.config.Memcached.Pools.Default.ServerEnabled = False
self.config.DirectoryAddressBook.Enabled = False
@@ -415,7 +428,7 @@
-class SocketGroupOwnership(TestCase):
+class SocketGroupOwnership(StoreTestCase):
"""
Tests for L{GroupOwnedUNIXServer}.
"""
@@ -523,11 +536,15 @@
self.writeConfig()
class NotAStore(object):
queuer = LocalQueuer(None)
+ def __init__(self, directory):
+ self.directory = directory
def newTransaction(self):
return None
def callWithNewTransactions(self, x):
pass
- store = NotAStore()
+ def directoryService(self):
+ return self.directory
+ store = NotAStore(self.directory)
def something(proposal):
pass
store.queuer.callWithNewProposals(something)
@@ -578,6 +595,7 @@
"%s is not a CalDAVService" % (service,)
)
+
def test_defaultListeners(self):
"""
Test that the Slave service has sub services with the
@@ -631,6 +649,7 @@
context.certificateFileName,
)
+
def test_noSSL(self):
"""
Test the single service to make sure there is no SSL Service when SSL
@@ -646,6 +665,7 @@
[s.__class__ for s in service.services]
)
+
def test_noHTTP(self):
"""
Test the single service to make sure there is no TCPServer when
@@ -661,6 +681,7 @@
[s.__class__ for s in service.services]
)
+
def test_singleBindAddresses(self):
"""
Test that the TCPServer and SSLServers are bound to the proper address
@@ -674,6 +695,7 @@
if isinstance(s, (internet.TCPServer, internet.SSLServer)):
self.assertEquals(s.kwargs["interface"], "127.0.0.1")
+
def test_multipleBindAddresses(self):
"""
Test that the TCPServer and SSLServers are bound to the proper
@@ -712,6 +734,7 @@
self.assertEquals(len(tcpServers), 0)
self.assertEquals(len(sslServers), 0)
+
def test_listenBacklog(self):
"""
Test that the backlog arguments is set in TCPServer and SSLServers
@@ -725,6 +748,7 @@
self.assertEquals(s.kwargs["backlog"], 1024)
+
class ServiceHTTPFactoryTests(BaseServiceMakerTests):
"""
Test the configuration of the initial resource hierarchy of the
@@ -759,6 +783,7 @@
self.assertEquals(len(expectedSchemes),
len(authWrapper.credentialFactories))
+
def test_servicePrincipalNone(self):
"""
Test that the Kerberos principal look is attempted if the principal is empty.
@@ -770,8 +795,9 @@
authWrapper = site.resource.resource
- self.assertFalse(authWrapper.credentialFactories.has_key("negotiate"))
+ self.assertFalse("negotiate" in authWrapper.credentialFactories)
+
def test_servicePrincipal(self):
"""
Test that the kerberos realm is the realm portion of a principal
@@ -788,6 +814,7 @@
self.assertEquals(ncf.service, "http at HELLO")
self.assertEquals(ncf.realm, "bob")
+
def test_AuthWrapperPartialEnabled(self):
"""
Test that the expected credential factories exist when
@@ -795,7 +822,7 @@
enabled.
"""
- self.config.Authentication.Basic.Enabled = False
+ self.config.Authentication.Basic.Enabled = False
self.config.Authentication.Kerberos.Enabled = False
self.writeConfig()
@@ -813,6 +840,7 @@
len(authWrapper.credentialFactories)
)
+
def test_LogWrapper(self):
"""
Test the configuration of the log wrapper
@@ -823,6 +851,7 @@
site.resource,
LogWrapperResource))
+
def test_rootResource(self):
"""
Test the root resource
@@ -832,6 +861,7 @@
self.failUnless(isinstance(root, RootResource))
+
def test_principalResource(self):
"""
Test the principal resource
@@ -844,6 +874,7 @@
DirectoryPrincipalProvisioningResource
))
+
def test_calendarResource(self):
"""
Test the calendar resource
@@ -863,7 +894,7 @@
<dict>
<key>users</key>
<array>
- <dict>
+ <dict>
<key>password</key>
<string>superuser</string>
<key>username</key>
@@ -892,6 +923,7 @@
self.assertEquals(principals.directory, calendars.directory)
+
def test_aggregateDirectory(self):
"""
Assert that the base directory service is actually
@@ -955,6 +987,7 @@
return 'Dummy'
+
class ScriptProcessObject(DummyProcessObject):
"""
Simple stub for the Process Object API that will run a test script.
@@ -971,9 +1004,7 @@
-
-
-class DelayedStartupProcessMonitorTests(TestCase):
+class DelayedStartupProcessMonitorTests(StoreTestCase):
"""
Test cases for L{DelayedStartupProcessMonitor}.
"""
@@ -1068,13 +1099,13 @@
# Most arguments here will be ignored, so these are bogus values.
slave = TwistdSlaveProcess(
- twistd = "bleh",
- tapname = "caldav",
- configFile = "/does/not/exist",
- id = 10,
- interfaces = '127.0.0.1',
- inheritFDs = [3, 7],
- inheritSSLFDs = [19, 25],
+ twistd="bleh",
+ tapname="caldav",
+ configFile="/does/not/exist",
+ id=10,
+ interfaces='127.0.0.1',
+ inheritFDs=[3, 7],
+ inheritSSLFDs=[19, 25],
)
dspm.addProcessObject(slave, {})
@@ -1121,12 +1152,12 @@
dspm = DelayedStartupProcessMonitor(imps)
# Most arguments here will be ignored, so these are bogus values.
slave = TwistdSlaveProcess(
- twistd = "bleh",
- tapname = "caldav",
- configFile = "/does/not/exist",
- id = 10,
- interfaces = '127.0.0.1',
- metaSocket = FakeDispatcher().addSocket()
+ twistd="bleh",
+ tapname="caldav",
+ configFile="/does/not/exist",
+ id=10,
+ interfaces='127.0.0.1',
+ metaSocket=FakeDispatcher().addSocket()
)
dspm.addProcessObject(slave, {})
@@ -1134,7 +1165,7 @@
oneProcessTransport = imps.waitForOneProcess()
self.assertIn("MetaFD=4", oneProcessTransport.args)
self.assertEquals(
- oneProcessTransport.args[oneProcessTransport.args.index("MetaFD=4")-1],
+ oneProcessTransport.args[oneProcessTransport.args.index("MetaFD=4") - 1],
'-o',
"MetaFD argument was not passed as an option"
)
@@ -1155,12 +1186,12 @@
sampleCounter = range(0, 5)
for counter in sampleCounter:
slave = TwistdSlaveProcess(
- twistd = "bleh",
- tapname = "caldav",
- configFile = "/does/not/exist",
- id = counter * 10,
- interfaces = '127.0.0.1',
- metaSocket = FakeDispatcher().addSocket()
+ twistd="bleh",
+ tapname="caldav",
+ configFile="/does/not/exist",
+ id=counter * 10,
+ interfaces='127.0.0.1',
+ metaSocket=FakeDispatcher().addSocket()
)
dspm.addProcessObject(slave, {"SAMPLE_ENV_COUNTER": str(counter)})
dspm.startService()
@@ -1179,7 +1210,7 @@
def __init__(self, n):
self.fd = n
-
+
def fileno(self):
return self.fd
@@ -1194,7 +1225,7 @@
-class TwistdSlaveProcessTests(TestCase):
+class TwistdSlaveProcessTests(StoreTestCase):
"""
Tests for L{TwistdSlaveProcess}.
"""
@@ -1212,11 +1243,8 @@
+class ReExecServiceTests(StoreTestCase):
-
-
-class ReExecServiceTests(TestCase):
-
@inlineCallbacks
def test_reExecService(self):
"""
@@ -1239,7 +1267,8 @@
self.assertEquals(output.count("STOP"), 2)
-class SystemIDsTests(TestCase):
+
+class SystemIDsTests(StoreTestCase):
"""
Verifies the behavior of calendarserver.tap.caldav.getSystemIDs
"""
@@ -1281,6 +1310,7 @@
}
)
+
def test_getSystemIDs_UserNameNotFound(self):
"""
If userName is passed in but is not found on the system, raise a
@@ -1289,6 +1319,7 @@
self.assertRaises(ConfigurationError, self._wrappedFunction(),
"nonexistent", "exists")
+
def test_getSystemIDs_GroupNameNotFound(self):
"""
If groupName is passed in but is not found on the system, raise a
@@ -1297,12 +1328,14 @@
self.assertRaises(ConfigurationError, self._wrappedFunction(),
"exists", "nonexistent")
+
def test_getSystemIDs_NamesNotSpecified(self):
"""
If names are not provided, use the IDs of the process
"""
self.assertEquals(self._wrappedFunction()("", ""), (44, 45))
+
def test_getSystemIDs_NamesSpecified(self):
"""
If names are provided, use the IDs corresponding to those names
Modified: CalendarServer/branches/users/cdaboo/store-scheduling/calendarserver/tools/test/test_purge.py
===================================================================
--- CalendarServer/branches/users/cdaboo/store-scheduling/calendarserver/tools/test/test_purge.py 2013-04-17 23:02:28 UTC (rev 11056)
+++ CalendarServer/branches/users/cdaboo/store-scheduling/calendarserver/tools/test/test_purge.py 2013-04-18 18:21:54 UTC (rev 11057)
@@ -15,19 +15,17 @@
##
-from calendarserver.tap.util import getRootResource
from calendarserver.tools.purge import PurgePrincipalService
from twistedcaldav.config import config
from twistedcaldav.ical import Component
-from twistedcaldav.test.util import TestCase
+from twistedcaldav.test.util import StoreTestCase
from pycalendar.datetime import PyCalendarDateTime
from pycalendar.timezone import PyCalendarTimezone
-from twisted.trial import unittest
from twisted.internet.defer import inlineCallbacks
-from txdav.common.datastore.test.util import buildStore, populateCalendarsFrom, CommonCommonTests
+from txdav.common.datastore.test.util import populateCalendarsFrom
from txdav.common.datastore.sql_tables import _BIND_MODE_WRITE
from twext.web2.http_headers import MimeType
@@ -227,7 +225,7 @@
-class CancelEventTestCase(TestCase):
+class CancelEventTestCase(StoreTestCase):
def test_cancelRepeating(self):
# A repeating event where purged CUA is organizer
@@ -779,7 +777,7 @@
-class PurgePrincipalTests(CommonCommonTests, unittest.TestCase):
+class PurgePrincipalTests(StoreTestCase):
"""
Tests for purging the data belonging to a given principal
"""
@@ -808,10 +806,6 @@
@inlineCallbacks
def setUp(self):
- yield super(PurgePrincipalTests, self).setUp()
- self._sqlCalendarStore = yield buildStore(self, self.notifierFactory)
- yield self.populate()
-
self.patch(config.DirectoryService.params, "xmlFile",
os.path.join(
os.path.dirname(__file__), "purge", "accounts.xml"
@@ -822,8 +816,7 @@
os.path.dirname(__file__), "purge", "resources.xml"
)
)
- self.rootResource = getRootResource(config, self._sqlCalendarStore)
- self.directory = self.rootResource.getDirectory()
+ yield super(PurgePrincipalTests, self).setUp()
txn = self._sqlCalendarStore.newTransaction()
@@ -863,13 +856,6 @@
self.notifierFactory.reset()
- def storeUnderTest(self):
- """
- Create and return a L{CalendarStore} for testing.
- """
- return self._sqlCalendarStore
-
-
@inlineCallbacks
def test_purgeUIDs(self):
"""
Modified: CalendarServer/branches/users/cdaboo/store-scheduling/twext/web2/dav/test/util.py
===================================================================
--- CalendarServer/branches/users/cdaboo/store-scheduling/twext/web2/dav/test/util.py 2013-04-17 23:02:28 UTC (rev 11056)
+++ CalendarServer/branches/users/cdaboo/store-scheduling/twext/web2/dav/test/util.py 2013-04-18 18:21:54 UTC (rev 11057)
@@ -7,10 +7,10 @@
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
-#
+#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
-#
+#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -58,7 +58,7 @@
associated logic with the C{chanRequest} attribute).
"""
- clientproto = (1,1)
+ clientproto = (1, 1)
def __init__(self, site, method, uri, headers=None, content=None):
if not headers:
@@ -80,7 +80,10 @@
self.host = 'localhost'
self.port = 8080
+
def writeResponse(self, response):
+ if self.chanRequest:
+ self.chanRequest.writeHeaders(response.code, response.headers)
return response
@@ -94,6 +97,7 @@
def __init__(self, resource):
self._dict = {}
+
def get(self, qname):
try:
property = self._dict[qname]
@@ -106,21 +110,27 @@
doc = element.WebDAVDocument.fromString(property)
return doc.root_element
+
def set(self, property):
self._dict[property.qname()] = property.toxml()
+
def delete(self, qname):
try:
del(self._dict[qname])
except KeyError:
pass
+
def contains(self, qname):
return qname in self._dict
+
def list(self):
return self._dict.keys()
+
+
class TestFile (DAVFile):
_cachedPropertyStores = {}
@@ -135,9 +145,12 @@
return self._dead_properties
+
def parent(self):
return TestFile(self.fp.parent())
+
+
class TestCase (unittest.TestCase):
resource_class = TestFile
@@ -193,7 +206,7 @@
if os.path.isfile(os.path.join(src, f))
]
- for dirname in (docroot,) + dirnames[3:8+1]:
+ for dirname in (docroot,) + dirnames[3:8 + 1]:
for filename in filenames[:5]:
copy(filename, dirname)
return docroot
@@ -207,6 +220,7 @@
return self._docroot
+
def _setDocumentRoot(self, value):
self._docroot = value
@@ -219,6 +233,7 @@
self._site = Site(rootresource)
return self._site
+
def _setSite(self, site):
self._site = site
@@ -228,6 +243,7 @@
unittest.TestCase.setUp(self)
TestFile._cachedPropertyStores = {}
+
def tearDown(self):
unittest.TestCase.tearDown(self)
@@ -238,7 +254,7 @@
URI.
"""
path = mkdtemp(prefix=prefix + "_", dir=self.docroot)
- uri = joinURL("/", url_quote(os.path.basename(path))) + "/"
+ uri = joinURL("/", url_quote(os.path.basename(path))) + "/"
return (os.path.abspath(path), uri)
@@ -319,7 +335,6 @@
-
class Site:
# FIXME: There is no ISite interface; there should be.
# implements(ISite)
@@ -327,6 +342,8 @@
def __init__(self, resource):
self.resource = resource
+
+
def dircmp(dir1, dir2):
dc = DirCompare(dir1, dir2)
return bool(
@@ -335,12 +352,15 @@
dc.common_funny or dc.funny_files
)
+
+
def serialize(f, work):
d = Deferred()
def oops(error):
d.errback(error)
+
def do_serialize(_):
try:
args = work.next()
Modified: CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/directory/directory.py
===================================================================
--- CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/directory/directory.py 2013-04-17 23:02:28 UTC (rev 11056)
+++ CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/directory/directory.py 2013-04-18 18:21:54 UTC (rev 11057)
@@ -114,6 +114,14 @@
self.realmName = realmName
+ def getPrincipalPath(self):
+ return getattr(self, "principalPath", None)
+
+
+ def setPrincipalPath(self, principalPath):
+ self.principalPath = principalPath
+
+
def available(self):
"""
By default, the directory is available. This may return a boolean or a
@@ -1078,8 +1086,14 @@
["mailto:%s" % (emailAddress,)
for emailAddress in self.emailAddresses]
)
+ path = self.service.getPrincipalPath()
if self.guid:
cuas.add("urn:uuid:%s" % (self.guid,))
+ if path:
+ cuas.add("/%s/__uids__/%s/" % (path, self.guid,))
+ if path:
+ for shortName in self.shortNames:
+ cuas.add("/%s/%s/%s/" % (path, self.recordType, shortName,))
return frozenset(cuas)
@@ -1171,7 +1185,7 @@
def displayName(self):
- return self.record.fullName if self.record.fullName else self.record.shortNames[0]
+ return self.fullName if self.fullName else self.shortNames[0]
def isLoginEnabled(self):
@@ -1320,7 +1334,7 @@
def canAutoSchedule(self, organizer):
if config.Scheduling.Options.AutoSchedule.Enabled:
if (config.Scheduling.Options.AutoSchedule.Always or
- self.getAutoSchedule() or
+ self.autoSchedule or
self.autoAcceptFromOrganizer(organizer)):
if (self.getCUType() != "INDIVIDUAL" or
config.Scheduling.Options.AutoSchedule.AllowUsers):
Modified: CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/directory/principal.py
===================================================================
--- CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/directory/principal.py 2013-04-17 23:02:28 UTC (rev 11056)
+++ CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/directory/principal.py 2013-04-18 18:21:54 UTC (rev 11057)
@@ -1203,7 +1203,7 @@
def calendarUserAddresses(self):
- return self.record.calendarUserAddresses()
+ return self.record.calendarUserAddresses
def htmlElement(self):
Modified: CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/directory/test/test_calendar.py
===================================================================
--- CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/directory/test/test_calendar.py 2013-04-17 23:02:28 UTC (rev 11056)
+++ CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/directory/test/test_calendar.py 2013-04-18 18:21:54 UTC (rev 11057)
@@ -19,41 +19,18 @@
from twistedcaldav import caldavxml
-from twistedcaldav.test.util import TestCase
-from twext.web2.test.test_server import SimpleRequest
-from twistedcaldav.directory.util import transactionFromRequest, NotFoundResource
+from twistedcaldav.test.util import StoreTestCase, SimpleStoreRequest
+from twistedcaldav.directory.util import NotFoundResource
-class ProvisionedCalendars (TestCase):
+class ProvisionedCalendars (StoreTestCase):
"""
Directory service provisioned principals.
"""
- @inlineCallbacks
- def setUp(self):
- yield super(ProvisionedCalendars, self).setUp()
-
- self.createStockDirectoryService()
- self.setupCalendars()
-
-
def oneRequest(self, uri):
- req = self._cleanupRequest = SimpleRequest(self.site, "GET", uri)
- return req
+ return SimpleStoreRequest(self, "GET", uri)
- def tearDown(self):
- """
- If the request started by this test has a transaction, commit it.
- Otherwise, don't bother.
- """
- class JustForCleanup(object):
- def newTransaction(self, *whatever):
- return self
- def commit(self):
- return
- return transactionFromRequest(self._cleanupRequest, JustForCleanup()).commit()
-
-
def test_NonExistentCalendarHome(self):
"""
Requests for missing homes and principals should return
@@ -67,7 +44,9 @@
request = self.oneRequest("/calendars/users/12345/")
d = request.locateResource(request.uri)
d.addCallback(_response)
+ return d
+
def test_ExistentCalendarHome(self):
def _response(resource):
@@ -77,7 +56,9 @@
request = self.oneRequest("/calendars/users/wsanchez/")
d = request.locateResource(request.uri)
d.addCallback(_response)
+ return d
+
def test_ExistentCalendar(self):
def _response(resource):
@@ -87,7 +68,9 @@
request = self.oneRequest("/calendars/users/wsanchez/calendar/")
d = request.locateResource(request.uri)
d.addCallback(_response)
+ return d
+
def test_ExistentInbox(self):
def _response(resource):
@@ -97,7 +80,9 @@
request = self.oneRequest("/calendars/users/wsanchez/inbox/")
d = request.locateResource(request.uri)
d.addCallback(_response)
+ return d
+
@inlineCallbacks
def test_CalendarTranspProperty(self):
@@ -111,7 +96,7 @@
inbox = (yield request.locateResource("/calendars/users/wsanchez/inbox/"))
if inbox is None:
self.fail("Incorrect response to GET on existent inbox.")
-
+
# Provisioned calendar has default opaque property
transp = (yield calendar.hasProperty(caldavxml.ScheduleCalendarTransp, request))
self.assertTrue(transp)
@@ -173,4 +158,3 @@
transp = (yield calendar.readProperty(caldavxml.ScheduleCalendarTransp, request))
self.assertEqual(transp, caldavxml.ScheduleCalendarTransp(caldavxml.Opaque()))
-
Modified: CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/directory/test/test_principal.py
===================================================================
--- CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/directory/test/test_principal.py 2013-04-17 23:02:28 UTC (rev 11056)
+++ CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/directory/test/test_principal.py 2013-04-18 18:21:54 UTC (rev 11057)
@@ -69,6 +69,7 @@
for directory in self.directoryServices:
name = directory.__class__.__name__
url = "/" + name + "/"
+ directory.setPrincipalPath(name)
provisioningResource = DirectoryPrincipalProvisioningResource(url, directory)
@@ -138,14 +139,16 @@
principalCollections = recordResource.principalCollections()
self.assertEquals(set((provisioningURL,)), set(pc.principalCollectionURL() for pc in principalCollections))
+
def test_allRecords(self):
"""
Test of a test routine...
"""
- for provisioningResource, recordType, recordResource, record in self._allRecords():
+ for _ignore_provisioningResource, _ignore_recordType, recordResource, record in self._allRecords():
if record.enabled:
self.assertEquals(recordResource.record, record)
+
##
# DirectoryPrincipalProvisioningResource
##
@@ -154,7 +157,7 @@
"""
DirectoryPrincipalProvisioningResource.principalForShortName()
"""
- for provisioningResource, recordType, recordResource, record in self._allRecords():
+ for provisioningResource, recordType, _ignore_recordResource, record in self._allRecords():
principal = provisioningResource.principalForShortName(recordType, record.shortNames[0])
if record.enabled:
self.failIf(principal is None)
@@ -162,6 +165,7 @@
else:
self.failIf(principal is not None)
+
def test_principalForUser(self):
"""
DirectoryPrincipalProvisioningResource.principalForUser()
@@ -177,6 +181,7 @@
else:
self.failIf(userResource is not None)
+
def test_principalForAuthID(self):
"""
DirectoryPrincipalProvisioningResource.principalForAuthID()
@@ -193,11 +198,12 @@
else:
self.failIf(userResource is not None)
+
def test_principalForUID(self):
"""
DirectoryPrincipalProvisioningResource.principalForUID()
"""
- for provisioningResource, recordType, recordResource, record in self._allRecords():
+ for provisioningResource, _ignore_recordType, _ignore_recordResource, record in self._allRecords():
principal = provisioningResource.principalForUID(record.uid)
if record.enabled:
self.failIf(principal is None)
@@ -205,11 +211,12 @@
else:
self.failIf(principal is not None)
+
def test_principalForRecord(self):
"""
DirectoryPrincipalProvisioningResource.principalForRecord()
"""
- for provisioningResource, recordType, recordResource, record in self._allRecords():
+ for provisioningResource, _ignore_recordType, _ignore_recordResource, record in self._allRecords():
principal = provisioningResource.principalForRecord(record)
if record.enabled:
self.failIf(principal is None)
@@ -217,14 +224,15 @@
else:
self.failIf(principal is not None)
+
def test_principalForCalendarUserAddress(self):
"""
DirectoryPrincipalProvisioningResource.principalForCalendarUserAddress()
"""
for (
- provisioningResource, recordType, recordResource, record
+ provisioningResource, _ignore_recordType, recordResource, record
) in self._allRecords():
-
+
test_items = tuple(record.calendarUserAddresses)
if recordResource:
principalURL = recordResource.principalURL()
@@ -270,12 +278,13 @@
None
)
+
@inlineCallbacks
def test_enabledForCalendaring(self):
"""
DirectoryPrincipalProvisioningResource.principalForCalendarUserAddress()
"""
- for provisioningResource, recordType, recordResource, record in self._allRecords():
+ for provisioningResource, _ignore_recordType, _ignore_recordResource, record in self._allRecords():
principal = provisioningResource.principalForRecord(record)
if record.enabled:
self.failIf(principal is None)
@@ -295,7 +304,7 @@
def hasProperty(property):
self.assertTrue(property in principal.liveProperties())
yield principal.readProperty(property, None)
-
+
@inlineCallbacks
def doesNotHaveProperty(property):
self.assertTrue(property not in principal.liveProperties())
@@ -307,7 +316,7 @@
self.fail("Wrong exception type")
else:
self.fail("No exception principal: %s, property %s" % (principal, property,))
-
+
if record.enabledForCalendaring:
yield hasProperty((caldav_namespace, "calendar-home-set"))
yield hasProperty((caldav_namespace, "calendar-user-address-set"))
@@ -332,16 +341,17 @@
else:
yield doesNotHaveProperty(carddavxml.AddressBookHomeSet.qname())
+
def test_enabledAsOrganizer(self):
"""
DirectoryPrincipalProvisioningResource.principalForCalendarUserAddress()
"""
-
+
ok_types = (
DirectoryService.recordType_users,
)
- for provisioningResource, recordType, recordResource, record in self._allRecords():
-
+ for provisioningResource, recordType, _ignore_recordResource, record in self._allRecords():
+
if record.enabledForCalendaring:
principal = provisioningResource.principalForRecord(record)
self.failIf(principal is None)
@@ -356,13 +366,14 @@
DirectoryService.recordType_locations,
DirectoryService.recordType_resources,
)
- for provisioningResource, recordType, recordResource, record in self._allRecords():
-
+ for provisioningResource, recordType, _ignore_recordResource, record in self._allRecords():
+
if record.enabledForCalendaring:
principal = provisioningResource.principalForRecord(record)
self.failIf(principal is None)
self.assertEqual(principal.enabledAsOrganizer(), recordType in ok_types)
+
# FIXME: Run DirectoryPrincipalProvisioningResource tests on DirectoryPrincipalTypeProvisioningResource also
##
@@ -374,52 +385,57 @@
Each DirectoryPrincipalResource should have a cacheNotifier attribute
that is an instance of DisabledCacheNotifier
"""
- for provisioningResource, recordType, recordResource, record in self._allRecords():
+ for _ignore_provisioningResource, _ignore_recordType, recordResource, record in self._allRecords():
if record.enabled:
self.failUnless(isinstance(recordResource.cacheNotifier,
DisabledCacheNotifier))
+
def test_displayName(self):
"""
DirectoryPrincipalResource.displayName()
"""
- for provisioningResource, recordType, recordResource, record in self._allRecords():
+ for _ignore_provisioningResource, _ignore_recordType, recordResource, record in self._allRecords():
if record.enabled:
self.failUnless(recordResource.displayName())
+
@inlineCallbacks
def test_groupMembers(self):
"""
DirectoryPrincipalResource.groupMembers()
"""
- for provisioningResource, recordType, recordResource, record in self._allRecords():
+ for _ignore_provisioningResource, _ignore_recordType, recordResource, record in self._allRecords():
if record.enabled:
members = yield recordResource.groupMembers()
self.failUnless(set(record.members()).issubset(set(r.record for r in members)))
+
@inlineCallbacks
def test_groupMemberships(self):
"""
DirectoryPrincipalResource.groupMemberships()
"""
- for provisioningResource, recordType, recordResource, record in self._allRecords():
+ for _ignore_provisioningResource, _ignore_recordType, recordResource, record in self._allRecords():
if record.enabled:
memberships = yield recordResource.groupMemberships()
self.failUnless(set(record.groups()).issubset(set(r.record for r in memberships if hasattr(r, "record"))))
+
def test_principalUID(self):
"""
DirectoryPrincipalResource.principalUID()
"""
- for provisioningResource, recordType, recordResource, record in self._allRecords():
+ for _ignore_provisioningResource, _ignore_recordType, recordResource, record in self._allRecords():
if record.enabled:
self.assertEquals(record.guid, recordResource.principalUID())
+
def test_calendarUserAddresses(self):
"""
DirectoryPrincipalResource.calendarUserAddresses()
"""
- for provisioningResource, recordType, recordResource, record in self._allRecords():
+ for _ignore_provisioningResource, _ignore_recordType, recordResource, record in self._allRecords():
if record.enabledForCalendaring:
self.failUnless(
(
@@ -432,20 +448,22 @@
record.enabledForCalendaring = False
self.failIf(recordResource.calendarUserAddresses())
+
def test_canonicalCalendarUserAddress(self):
"""
DirectoryPrincipalResource.canonicalCalendarUserAddress()
"""
- for provisioningResource, recordType, recordResource, record in self._allRecords():
+ for _ignore_provisioningResource, _ignore_recordType, recordResource, record in self._allRecords():
if record.enabledForCalendaring:
self.failUnless(recordResource.canonicalCalendarUserAddress().startswith("urn:uuid:"))
+
def test_addressBookHomeURLs(self):
"""
DirectoryPrincipalResource.addressBookHomeURLs(),
"""
# No addressbook home provisioner should result in no addressbook homes.
- for provisioningResource, recordType, recordResource, record in self._allRecords():
+ for provisioningResource, _ignore_recordType, recordResource, record in self._allRecords():
if record.enabledForAddressBooks:
self.failIf(tuple(recordResource.addressBookHomeURLs()))
@@ -460,7 +478,7 @@
os.mkdir(path)
# Need a data store
- _newStore = CommonDataStore(path, None, True, False)
+ _newStore = CommonDataStore(path, None, None, True, False)
provisioningResource = DirectoryAddressBookHomeProvisioningResource(
directory,
@@ -471,7 +489,7 @@
addressBookRootResources[directory.__class__.__name__] = provisioningResource
# AddressBook home provisioners should result in addressBook homes.
- for provisioningResource, recordType, recordResource, record in self._allRecords():
+ for provisioningResource, _ignore_recordType, recordResource, record in self._allRecords():
if record.enabledForAddressBooks:
homeURLs = tuple(recordResource.addressBookHomeURLs())
self.failUnless(homeURLs)
@@ -487,6 +505,7 @@
for homeURL in homeURLs:
self.failUnless(homeURL.startswith(addressBookRootURL))
+
def test_calendarHomeURLs(self):
"""
DirectoryPrincipalResource.calendarHomeURLs(),
@@ -494,7 +513,7 @@
DirectoryPrincipalResource.scheduleOutboxURL()
"""
# No calendar home provisioner should result in no calendar homes.
- for provisioningResource, recordType, recordResource, record in self._allRecords():
+ for provisioningResource, _ignore_recordType, recordResource, record in self._allRecords():
if record.enabledForCalendaring:
self.failIf(tuple(recordResource.calendarHomeURLs()))
self.failIf(recordResource.scheduleInboxURL())
@@ -511,7 +530,7 @@
os.mkdir(path)
# Need a data store
- _newStore = CommonDataStore(path, None, True, False)
+ _newStore = CommonDataStore(path, None, None, True, False)
provisioningResource = DirectoryCalendarHomeProvisioningResource(
directory,
@@ -522,7 +541,7 @@
calendarRootResources[directory.__class__.__name__] = provisioningResource
# Calendar home provisioners should result in calendar homes.
- for provisioningResource, recordType, recordResource, record in self._allRecords():
+ for provisioningResource, _ignore_recordType, recordResource, record in self._allRecords():
if record.enabledForCalendaring:
homeURLs = tuple(recordResource.calendarHomeURLs())
self.failUnless(homeURLs)
@@ -557,19 +576,20 @@
self.failIf(inboxURL)
self.failIf(outboxURL)
+
def test_canAutoSchedule(self):
"""
DirectoryPrincipalResource.canAutoSchedule()
"""
# Set all resources and locations to auto-schedule, plus one user
- for provisioningResource, recordType, recordResource, record in self._allRecords():
+ for _ignore_provisioningResource, recordType, recordResource, record in self._allRecords():
if record.enabledForCalendaring:
if recordType in ("locations", "resources") or record.uid == "cdaboo":
recordResource.record.autoSchedule = True
# Default state - resources and locations, enabled, others not
- for provisioningResource, recordType, recordResource, record in self._allRecords():
+ for _ignore_provisioningResource, recordType, recordResource, record in self._allRecords():
if record.enabledForCalendaring:
if recordType in ("locations", "resources"):
self.assertTrue(recordResource.canAutoSchedule())
@@ -578,7 +598,7 @@
# Set config to allow users
self.patch(config.Scheduling.Options.AutoSchedule, "AllowUsers", True)
- for provisioningResource, recordType, recordResource, record in self._allRecords():
+ for _ignore_provisioningResource, recordType, recordResource, record in self._allRecords():
if record.enabledForCalendaring:
if recordType in ("locations", "resources") or record.uid == "cdaboo":
self.assertTrue(recordResource.canAutoSchedule())
@@ -587,7 +607,7 @@
# Set config to disallow all
self.patch(config.Scheduling.Options.AutoSchedule, "Enabled", False)
- for provisioningResource, recordType, recordResource, record in self._allRecords():
+ for _ignore_provisioningResource, recordType, recordResource, record in self._allRecords():
if record.enabledForCalendaring:
self.assertFalse(recordResource.canAutoSchedule())
@@ -600,7 +620,7 @@
# Location "apollo" has an auto-accept group ("both_coasts") set in augments.xml,
# therefore any organizer in that group should be able to auto schedule
- for provisioningResource, recordType, recordResource, record in self._allRecords():
+ for _ignore_provisioningResource, _ignore_recordType, recordResource, record in self._allRecords():
if record.uid == "apollo":
# No organizer
@@ -617,11 +637,12 @@
"""
Default access controls for principals.
"""
- for provisioningResource, recordType, recordResource, record in self._allRecords():
+ for _ignore_provisioningResource, _ignore_recordType, recordResource, record in self._allRecords():
if record.enabled:
for args in _authReadOnlyPrivileges(self, recordResource, recordResource.principalURL()):
yield self._checkPrivileges(*args)
+
@inlineCallbacks
def test_defaultAccessControlList_provisioners(self):
"""
@@ -641,6 +662,7 @@
for args in _authReadOnlyPrivileges(self, typeResource, typeResource.principalCollectionURL()):
yield self._checkPrivileges(*args)
+
def test_propertyToField(self):
class stubElement(object):
@@ -677,6 +699,7 @@
(field, converted)
)
+
def _allRecords(self):
"""
@return: an iterable of tuples
@@ -696,6 +719,7 @@
recordResource = provisioningResource.principalForRecord(record)
yield provisioningResource, recordType, recordResource, record
+
def _checkPrivileges(self, resource, url, principal, privilege, allowed):
request = SimpleRequest(self.site, "GET", "/")
@@ -720,14 +744,16 @@
d.addCallback(gotResource)
return d
+
+
def _authReadOnlyPrivileges(self, resource, url):
items = []
- for provisioningResource, recordType, recordResource, record in self._allRecords():
+ for _ignore_provisioningResource, _ignore_recordType, recordResource, record in self._allRecords():
if record.enabled:
- items.append(( davxml.HRef().fromString(recordResource.principalURL()), davxml.Read() , True ))
- items.append(( davxml.HRef().fromString(recordResource.principalURL()), davxml.Write() , False ))
- items.append(( davxml.Unauthenticated() , davxml.Read() , False ))
- items.append(( davxml.Unauthenticated() , davxml.Write() , False ))
-
+ items.append((davxml.HRef().fromString(recordResource.principalURL()), davxml.Read() , True))
+ items.append((davxml.HRef().fromString(recordResource.principalURL()), davxml.Write() , False))
+ items.append((davxml.Unauthenticated() , davxml.Read() , False))
+ items.append((davxml.Unauthenticated() , davxml.Write() , False))
+
for principal, privilege, allowed in items:
yield resource, url, principal, privilege, allowed
Modified: CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/resource.py
===================================================================
--- CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/resource.py 2013-04-17 23:02:28 UTC (rev 11056)
+++ CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/resource.py 2013-04-18 18:21:54 UTC (rev 11057)
@@ -1090,7 +1090,7 @@
@inlineCallbacks
- def movedCalendar(self, request, defaultCalendarType, destination, destination_uri):
+ def movedCalendar(self, request, destination, destination_uri):
"""
Calendar has been moved. Need to do some extra clean-up.
"""
@@ -1099,17 +1099,11 @@
principal = (yield self.resourceOwnerPrincipal(request))
inboxURL = principal.scheduleInboxURL()
if inboxURL:
- (_ignore_scheme, _ignore_host, destination_path, _ignore_query, _ignore_fragment) = urlsplit(normalizeURL(destination_uri))
-
inbox = (yield request.locateResource(inboxURL))
inbox.processFreeBusyCalendar(request.path, False)
inbox.processFreeBusyCalendar(destination_uri, destination.isCalendarOpaque())
- # Adjust the default calendar setting if necessary
- if defaultCalendarType is not None:
- yield inbox.writeProperty(defaultCalendarType(element.HRef(destination_path)), request)
-
def isCalendarOpaque(self):
assert self.isCalendarCollection()
@@ -1121,22 +1115,13 @@
return False
- @inlineCallbacks
def isDefaultCalendar(self, request):
assert self.isCalendarCollection()
- # Not allowed to delete the default calendar
- principal = (yield self.resourceOwnerPrincipal(request))
- inboxURL = principal.scheduleInboxURL()
- if inboxURL:
- inbox = (yield request.locateResource(inboxURL))
- result = (yield inbox.isDefaultCalendar(request, self))
- returnValue(result)
+ return self._newStoreParentHome.isDefaultCalendar(self._newStoreObject)
- returnValue(None)
-
@inlineCallbacks
def iCalendarForUser(self, request):
@@ -2350,8 +2335,8 @@
)
)
-
returnValue(customxml.PubSubPushTransportsProperty(*children))
+
returnValue(None)
elif qname == (customxml.calendarserver_namespace, "pushkey"):
@@ -2546,7 +2531,7 @@
from twistedcaldav.storebridge import StoreScheduleInboxResource
self._provisionedChildren["inbox"] = StoreScheduleInboxResource.maybeCreateInbox
- from twistedcaldav.scheduling.caldav.resource import ScheduleOutboxResource
+ from twistedcaldav.scheduling_store.caldav.resource import ScheduleOutboxResource
self._provisionedChildren["outbox"] = ScheduleOutboxResource
if config.EnableDropBox and not config.EnableManagedAttachments:
Added: CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/scheduling_store/__init__.py
===================================================================
--- CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/scheduling_store/__init__.py (rev 0)
+++ CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/scheduling_store/__init__.py 2013-04-18 18:21:54 UTC (rev 11057)
@@ -0,0 +1,15 @@
+##
+# Copyright (c) 2005-2013 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.
+##
Added: CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/scheduling_store/caldav/__init__.py
===================================================================
--- CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/scheduling_store/caldav/__init__.py (rev 0)
+++ CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/scheduling_store/caldav/__init__.py 2013-04-18 18:21:54 UTC (rev 11057)
@@ -0,0 +1,15 @@
+##
+# Copyright (c) 2005-2013 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.
+##
Added: CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/scheduling_store/caldav/resource.py
===================================================================
--- CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/scheduling_store/caldav/resource.py (rev 0)
+++ CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/scheduling_store/caldav/resource.py 2013-04-18 18:21:54 UTC (rev 11057)
@@ -0,0 +1,489 @@
+# -*- test-case-name: twistedcaldav.directory.test.test_calendar -*-
+##
+# Copyright (c) 2005-2013 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 txdav.caldav.icalendarstore import InvalidDefaultCalendar
+from twisted.python.failure import Failure
+
+"""
+CalDAV scheduling resources.
+"""
+
+__all__ = [
+ "ScheduleInboxResource",
+ "ScheduleOutboxResource",
+ "deliverSchedulePrivilegeSet",
+]
+
+
+from twistedcaldav.config import config
+# _schedulePrivilegeSet implicitly depends on config being initialized. The
+# following line is wrong because _schedulePrivilegeSet won't actually use the
+# config file, it will pick up stdconfig whenever it is imported, so this works
+# around that for now.
+__import__("twistedcaldav.stdconfig") # FIXME
+
+from twext.web2 import responsecode
+from twext.web2.dav.http import ErrorResponse, MultiStatusResponse
+from twext.web2.dav.resource import davPrivilegeSet
+from twext.web2.dav.util import joinURL, normalizeURL
+from twext.web2.http import HTTPError
+
+from twisted.internet.defer import inlineCallbacks, returnValue, succeed
+
+from twistedcaldav import caldavxml, customxml
+from twistedcaldav.caldavxml import caldav_namespace, Opaque, \
+ CalendarFreeBusySet, ScheduleCalendarTransp
+from twistedcaldav.customxml import calendarserver_namespace
+from twistedcaldav.ical import allowedComponents, Component
+from twistedcaldav.resource import CalDAVResource
+from twistedcaldav.resource import isCalendarCollectionResource
+
+from txdav.base.propertystore.base import PropertyName
+from txdav.caldav.datastore.scheduling.caldav.scheduler import CalDAVScheduler
+from txdav.xml import element as davxml
+from txdav.xml.rfc2518 import HRef
+
+def _schedulePrivilegeSet(deliver):
+ edited = False
+
+ top_supported_privileges = []
+
+ for supported_privilege in davPrivilegeSet.childrenOfType(davxml.SupportedPrivilege):
+ all_privilege = supported_privilege.childOfType(davxml.Privilege)
+ if isinstance(all_privilege.children[0], davxml.All):
+ all_description = supported_privilege.childOfType(davxml.Description)
+ all_supported_privileges = list(supported_privilege.childrenOfType(davxml.SupportedPrivilege))
+ all_supported_privileges.append(
+ davxml.SupportedPrivilege(
+ davxml.Privilege(caldavxml.ScheduleDeliver() if deliver else caldavxml.ScheduleSend()),
+ davxml.Description("schedule privileges for current principal", **{"xml:lang": "en"}),
+ ),
+ )
+ if config.Scheduling.CalDAV.OldDraftCompatibility:
+ all_supported_privileges.append(
+ davxml.SupportedPrivilege(
+ davxml.Privilege(caldavxml.Schedule()),
+ davxml.Description("old-style schedule privileges for current principal", **{"xml:lang": "en"}),
+ ),
+ )
+ top_supported_privileges.append(
+ davxml.SupportedPrivilege(all_privilege, all_description, *all_supported_privileges)
+ )
+ edited = True
+ else:
+ top_supported_privileges.append(supported_privilege)
+
+ assert edited, "Structure of davPrivilegeSet changed in a way that I don't know how to extend for schedulePrivilegeSet"
+
+ return davxml.SupportedPrivilegeSet(*top_supported_privileges)
+
+deliverSchedulePrivilegeSet = _schedulePrivilegeSet(True)
+sendSchedulePrivilegeSet = _schedulePrivilegeSet(False)
+
+class CalendarSchedulingCollectionResource (CalDAVResource):
+ """
+ CalDAV principal resource.
+
+ Extends L{DAVResource} to provide CalDAV scheduling collection
+ functionality.
+ """
+ def __init__(self, parent):
+ """
+ @param parent: the parent resource of this one.
+ """
+ assert parent is not None
+
+ super(CalendarSchedulingCollectionResource, self).__init__(principalCollections=parent.principalCollections())
+
+ self.parent = parent
+
+
+ def isCollection(self):
+ return True
+
+
+ def isCalendarCollection(self):
+ return False
+
+
+ def isPseudoCalendarCollection(self):
+ return True
+
+
+ def supportedReports(self):
+ result = super(CalDAVResource, self).supportedReports()
+ result.append(davxml.Report(caldavxml.CalendarQuery(),))
+ result.append(davxml.Report(caldavxml.CalendarMultiGet(),))
+ # free-busy report not allowed
+ if config.EnableSyncReport:
+ # Only allowed on calendar/inbox/addressbook collections
+ result.append(davxml.Report(davxml.SyncCollection(),))
+ return result
+
+
+
+class ScheduleInboxResource (CalendarSchedulingCollectionResource):
+ """
+ CalDAV schedule Inbox resource.
+
+ Extends L{DAVResource} to provide CalDAV functionality.
+ """
+
+ def liveProperties(self):
+
+ return super(ScheduleInboxResource, self).liveProperties() + (
+ caldavxml.CalendarFreeBusySet.qname(),
+ caldavxml.ScheduleDefaultCalendarURL.qname(),
+ customxml.ScheduleDefaultTasksURL.qname(),
+ )
+
+
+ def resourceType(self):
+ return davxml.ResourceType.scheduleInbox
+
+
+ @inlineCallbacks
+ def readProperty(self, property, request):
+ if type(property) is tuple:
+ qname = property
+ else:
+ qname = property.qname()
+
+ if qname == caldavxml.CalendarFreeBusySet.qname():
+ # Always return at least an empty list
+ if not self.hasDeadProperty(property):
+ top = self.parent.url()
+ values = []
+ for cal in (yield self.parent._newStoreHome.calendars()):
+ prop = cal.properties().get(PropertyName.fromString(ScheduleCalendarTransp.sname()))
+ if prop == ScheduleCalendarTransp(Opaque()):
+ values.append(HRef(joinURL(top, cal.name())))
+ returnValue(CalendarFreeBusySet(*values))
+ elif qname in (caldavxml.ScheduleDefaultCalendarURL.qname(), customxml.ScheduleDefaultTasksURL.qname()):
+ result = (yield self.readDefaultCalendarProperty(request, qname))
+ returnValue(result)
+
+ result = (yield super(ScheduleInboxResource, self).readProperty(property, request))
+ returnValue(result)
+
+
+ @inlineCallbacks
+ def writeProperty(self, property, request):
+ assert isinstance(property, davxml.WebDAVElement)
+
+ # Strictly speaking CS:calendar-availability is a live property in the sense that the
+ # server enforces what can be stored, however it need not actually
+ # exist so we cannot list it in liveProperties on this resource, since its
+ # its presence there means that hasProperty will always return True for it.
+ if property.qname() == customxml.CalendarAvailability.qname():
+ if not property.valid():
+ raise HTTPError(ErrorResponse(
+ responsecode.CONFLICT,
+ (caldav_namespace, "valid-calendar-data"),
+ description="Invalid property"
+ ))
+
+ elif property.qname() == caldavxml.CalendarFreeBusySet.qname():
+ # Verify that the calendars added in the PROPPATCH are valid. We do not check
+ # whether existing items in the property are still valid - only new ones.
+ property.children = [davxml.HRef(normalizeURL(str(href))) for href in property.children]
+ new_calendars = set([str(href) for href in property.children])
+ if not self.hasDeadProperty(property):
+ old_calendars = set()
+ else:
+ old_calendars = set([normalizeURL(str(href)) for href in self.readDeadProperty(property).children])
+ added_calendars = new_calendars.difference(old_calendars)
+ for href in added_calendars:
+ cal = (yield request.locateResource(str(href)))
+ if cal is None or not cal.exists() or not isCalendarCollectionResource(cal):
+ # Validate that href's point to a valid calendar.
+ raise HTTPError(ErrorResponse(
+ responsecode.CONFLICT,
+ (caldav_namespace, "valid-calendar-url"),
+ "Invalid URI",
+ ))
+ for href in tuple(new_calendars):
+ cal = (yield request.locateResource(str(href)))
+ if cal is None or not cal.exists() or not isCalendarCollectionResource(cal):
+ new_calendars.remove(href)
+ property.children = [davxml.HRef(href) for href in new_calendars]
+
+ elif property.qname() in (caldavxml.ScheduleDefaultCalendarURL.qname(), customxml.ScheduleDefaultTasksURL.qname()):
+ yield self.writeDefaultCalendarProperty(request, property)
+ returnValue(None)
+
+ yield super(ScheduleInboxResource, self).writeProperty(property, request)
+
+
+ def processFreeBusyCalendar(self, uri, addit):
+ uri = normalizeURL(uri)
+
+ if not self.hasDeadProperty(caldavxml.CalendarFreeBusySet.qname()):
+ fbset = set()
+ else:
+ fbset = set([normalizeURL(str(href)) for href in self.readDeadProperty(caldavxml.CalendarFreeBusySet.qname()).children])
+ if addit:
+ if uri not in fbset:
+ fbset.add(uri)
+ self.writeDeadProperty(caldavxml.CalendarFreeBusySet(*[davxml.HRef(url) for url in fbset]))
+ else:
+ if uri in fbset:
+ fbset.remove(uri)
+ self.writeDeadProperty(caldavxml.CalendarFreeBusySet(*[davxml.HRef(url) for url in fbset]))
+
+
+ @inlineCallbacks
+ def readDefaultCalendarProperty(self, request, qname):
+ """
+ Read either the default VEVENT or VTODO calendar property. Try to pick one if not present.
+ """
+
+ tasks = qname == customxml.ScheduleDefaultTasksURL.qname()
+ componentType = "VTODO" if tasks else "VEVENT"
+ prop_to_set = customxml.ScheduleDefaultTasksURL if tasks else caldavxml.ScheduleDefaultCalendarURL
+
+ # This property now comes direct from the calendar home new store object
+ default = (yield self.parent._newStoreHome.defaultCalendar(componentType))
+ defaultURL = joinURL(self.parent.url(), default.name())
+ returnValue(prop_to_set(davxml.HRef(defaultURL)))
+
+
+ @inlineCallbacks
+ def writeDefaultCalendarProperty(self, request, property):
+ """
+ Write either the default VEVENT or VTODO calendar property, validating and canonicalizing the value
+ """
+ tasks = property.qname() == customxml.ScheduleDefaultTasksURL
+ error_element = (calendarserver_namespace, "valid-schedule-default-tasks-URL") if tasks else (caldav_namespace, "valid-schedule-default-calendar-URL")
+
+ # Verify that the calendar added in the PROPPATCH is valid.
+ property.children = [davxml.HRef(normalizeURL(str(href))) for href in property.children]
+ new_calendar = [str(href) for href in property.children]
+ cal = None
+ if len(new_calendar) == 1:
+ cal = (yield request.locateResource(str(new_calendar[0])))
+ else:
+ raise HTTPError(ErrorResponse(
+ responsecode.BAD_REQUEST,
+ error_element,
+ "Invalid HRef in property",
+ ))
+
+ try:
+ # Now set it on the new store object
+ yield self.parent._newStoreHome.setDefaultCalendar(cal._newStoreObject, tasks)
+ except InvalidDefaultCalendar as e:
+ raise HTTPError(ErrorResponse(
+ responsecode.CONFLICT,
+ error_element,
+ str(e),
+ ))
+
+
+ @inlineCallbacks
+ def defaultCalendar(self, request, componentType):
+ """
+ Find the default calendar for the supplied iCalendar component type. If one does
+ not exist, automatically provision it.
+ """
+
+ # This property now comes direct from the calendar home new store object
+ default = (yield self.parent._newStoreHome.defaultCalendar(componentType))
+
+ # Need L{DAVResource} object to return not new store object
+ default = (yield request.locateResource(joinURL(self.parent.url(), default.name())))
+
+ returnValue(default)
+
+
+ ##
+ # ACL
+ ##
+
+ def supportedPrivileges(self, request):
+ return succeed(deliverSchedulePrivilegeSet)
+
+
+ def defaultAccessControlList(self):
+
+ privs = (
+ davxml.Privilege(caldavxml.ScheduleDeliver()),
+ )
+ if config.Scheduling.CalDAV.OldDraftCompatibility:
+ privs += (davxml.Privilege(caldavxml.Schedule()),)
+
+ return davxml.ACL(
+ # CalDAV:schedule-deliver for any authenticated user
+ davxml.ACE(
+ davxml.Principal(davxml.Authenticated()),
+ davxml.Grant(*privs),
+ ),
+ )
+
+
+
+class ScheduleOutboxResource (CalendarSchedulingCollectionResource):
+ """
+ CalDAV schedule Outbox resource.
+
+ Extends L{DAVResource} to provide CalDAV functionality.
+ """
+
+ def resourceType(self):
+ return davxml.ResourceType.scheduleOutbox
+
+
+ def getSupportedComponentSet(self):
+ return caldavxml.SupportedCalendarComponentSet(
+ *[caldavxml.CalendarComponent(name=item) for item in allowedComponents]
+ )
+
+
+ @inlineCallbacks
+ def http_POST(self, request):
+ """
+ The CalDAV POST method.
+
+ This uses a generator function yielding either L{waitForDeferred} objects or L{Response} objects.
+ This allows for code that follows a 'linear' execution pattern rather than having to use nested
+ L{Deferred} callbacks. The logic is easier to follow this way plus we don't run into deep nesting
+ issues which the other approach would have with large numbers of recipients.
+ """
+ # Check authentication and access controls
+ yield self.authorize(request, (caldavxml.ScheduleSend(),))
+
+ calendar = (yield self.loadCalendarFromRequest(request))
+ originator = (yield self.loadOriginatorFromRequestDetails(request))
+ recipients = self.loadRecipientsFromCalendarData()
+
+ # This is a local CALDAV scheduling operation.
+ scheduler = CalDAVScheduler(self._associatedTransaction, self.parent._newStoreHome.uid())
+
+ # Do the POST processing treating
+ result = (yield scheduler.doSchedulingViaPOST(originator, recipients, calendar))
+ returnValue(result.response())
+
+
+ @inlineCallbacks
+ def loadCalendarFromRequest(self, request):
+ # Must be content-type text/calendar
+ contentType = request.headers.getHeader("content-type")
+ if contentType is not None and (contentType.mediaType, contentType.mediaSubtype) != ("text", "calendar"):
+ self.log_error("MIME type %s not allowed in calendar collection" % (contentType,))
+ raise HTTPError(ErrorResponse(
+ responsecode.FORBIDDEN,
+ (caldav_namespace, "supported-calendar-data"),
+ "Data is not calendar data",
+ ))
+
+ # Parse the calendar object from the HTTP request stream
+ try:
+ calendar = (yield Component.fromIStream(request.stream))
+ except:
+ # FIXME: Bare except
+ self.log_error("Error while handling POST: %s" % (Failure(),))
+ raise HTTPError(ErrorResponse(
+ responsecode.FORBIDDEN,
+ (caldav_namespace, "valid-calendar-data"),
+ description="Can't parse calendar data"
+ ))
+
+ returnValue(calendar)
+
+
+ @inlineCallbacks
+ def loadOriginatorFromRequestDetails(self, request):
+ # Get the originator who is the authenticated user
+ originatorPrincipal = None
+ originator = ""
+ authz_principal = self.currentPrincipal(request).children[0]
+ if isinstance(authz_principal, davxml.HRef):
+ originatorPrincipalURL = str(authz_principal)
+ if originatorPrincipalURL:
+ originatorPrincipal = (yield request.locateResource(originatorPrincipalURL))
+ if originatorPrincipal:
+ # Pick the canonical CUA:
+ originator = originatorPrincipal.canonicalCalendarUserAddress()
+
+ if not originator:
+ self.log_err("%s request must have Originator" % (self.method,))
+ raise HTTPError(ErrorResponse(
+ responsecode.FORBIDDEN,
+ (caldav_namespace, "originator-specified"),
+ "Missing originator",
+ ))
+ else:
+ returnValue(originator)
+
+
+ def loadRecipientsFromCalendarData(self, calendar):
+
+ # Get the ATTENDEEs
+ attendees = list()
+ unique_set = set()
+ for attendee, _ignore in calendar.getAttendeesByInstance():
+ if attendee not in unique_set:
+ attendees.append(attendee)
+ unique_set.add(attendee)
+
+ if not attendees:
+ self.log_err("POST request must have at least one ATTENDEE")
+ raise HTTPError(ErrorResponse(
+ responsecode.FORBIDDEN,
+ (caldav_namespace, "recipient-specified"),
+ "Must have recipients",
+ ))
+ else:
+ return(list(attendees))
+
+
+ ##
+ # ACL
+ ##
+
+ def supportedPrivileges(self, request):
+ return succeed(sendSchedulePrivilegeSet)
+
+
+ def defaultAccessControlList(self):
+ if config.EnableProxyPrincipals:
+ myPrincipal = self.parent.principalForRecord()
+
+ privs = (
+ davxml.Privilege(caldavxml.ScheduleSend()),
+ )
+ if config.Scheduling.CalDAV.OldDraftCompatibility:
+ privs += (davxml.Privilege(caldavxml.Schedule()),)
+
+ return davxml.ACL(
+ # CalDAV:schedule for associated write proxies
+ davxml.ACE(
+ davxml.Principal(davxml.HRef(joinURL(myPrincipal.principalURL(), "calendar-proxy-write"))),
+ davxml.Grant(*privs),
+ davxml.Protected(),
+ ),
+ )
+ else:
+ return super(ScheduleOutboxResource, self).defaultAccessControlList()
+
+
+ def report_urn_ietf_params_xml_ns_caldav_calendar_query(self, request, calendar_query):
+ return succeed(MultiStatusResponse(()))
+
+
+ def report_urn_ietf_params_xml_ns_caldav_calendar_multiget(self, request, multiget):
+ responses = [davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.NOT_FOUND)) for href in multiget.resources]
+ return succeed(MultiStatusResponse((responses)))
Added: CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/scheduling_store/caldav/test/__init__.py
===================================================================
--- CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/scheduling_store/caldav/test/__init__.py (rev 0)
+++ CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/scheduling_store/caldav/test/__init__.py 2013-04-18 18:21:54 UTC (rev 11057)
@@ -0,0 +1,15 @@
+##
+# Copyright (c) 2005-2013 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.
+##
Added: CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/scheduling_store/caldav/test/test_resource.py
===================================================================
--- CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/scheduling_store/caldav/test/test_resource.py (rev 0)
+++ CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/scheduling_store/caldav/test/test_resource.py 2013-04-18 18:21:54 UTC (rev 11057)
@@ -0,0 +1,344 @@
+##
+# Copyright (c) 2005-2013 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 twext.web2 import responsecode, http_headers
+from twext.web2.dav.util import davXMLFromStream
+from twext.web2.iweb import IResponse
+from twext.web2.stream import MemoryStream
+from twext.web2.test.test_server import SimpleRequest
+
+from twisted.internet.defer import inlineCallbacks
+
+from twistedcaldav import caldavxml, customxml
+from twistedcaldav.test.util import HomeTestCase, StoreTestCase, \
+ SimpleStoreRequest
+
+from txdav.xml import element as davxml
+
+class Properties (HomeTestCase):
+ """
+ CalDAV properties
+ """
+ def test_free_busy_set_prop(self):
+ """
+ Test for PROPFIND on Inbox with missing calendar-free-busy-set property.
+ """
+
+ inbox_uri = "/inbox/"
+
+ def propfind_cb(response):
+ response = IResponse(response)
+
+ if response.code != responsecode.MULTI_STATUS:
+ self.fail("Incorrect response to PROPFIND: %s" % (response.code,))
+
+ def got_xml(doc):
+ if not isinstance(doc.root_element, davxml.MultiStatus):
+ self.fail("PROPFIND response XML root element is not multistatus: %r" % (doc.root_element,))
+
+ response = doc.root_element.childOfType(davxml.Response)
+ href = response.childOfType(davxml.HRef)
+ self.failUnless(str(href) == inbox_uri)
+
+ for propstat in response.childrenOfType(davxml.PropertyStatus):
+ status = propstat.childOfType(davxml.Status)
+ if status.code != responsecode.OK:
+ self.fail("Unable to read requested properties (%s): %r"
+ % (status, propstat.childOfType(davxml.PropertyContainer).toxml()))
+
+ container = propstat.childOfType(davxml.PropertyContainer)
+
+ #
+ # Check CalDAV:calendar-free-busy-set
+ #
+
+ free_busy_set = container.childOfType(caldavxml.CalendarFreeBusySet)
+ if not free_busy_set:
+ self.fail("Expected CalDAV:calendar-free-busy-set element; but got none.")
+
+ if not free_busy_set.children:
+ self.fail("Expected non-empty CalDAV:calendar-free-busy-set element.")
+
+ return davXMLFromStream(response.stream).addCallback(got_xml)
+
+ query = davxml.PropertyFind(
+ davxml.PropertyContainer(
+ caldavxml.CalendarFreeBusySet(),
+ ),
+ )
+
+ request = SimpleRequest(
+ self.site,
+ "PROPFIND",
+ inbox_uri,
+ headers=http_headers.Headers({"Depth": "0"}),
+ )
+ request.stream = MemoryStream(query.toxml())
+ return self.send(request, propfind_cb)
+
+
+ @inlineCallbacks
+ def test_free_busy_set_remove_broken(self):
+ """
+ ???
+ """
+
+ request = SimpleRequest(self.site, "GET", "/inbox/")
+ inbox = yield request.locateResource("/inbox/")
+ self.assertTrue(inbox.hasDeadProperty(caldavxml.CalendarFreeBusySet))
+ oldfbset = set(("/calendar",))
+ oldset = caldavxml.CalendarFreeBusySet(*[davxml.HRef(url) for url in oldfbset])
+
+ newfbset = set()
+ newfbset.update(oldfbset)
+ newfbset.add("/calendar-broken")
+ newset = caldavxml.CalendarFreeBusySet(*[davxml.HRef(url) for url in newfbset])
+
+ inbox.writeDeadProperty(newset)
+ changedset = inbox.readDeadProperty(caldavxml.CalendarFreeBusySet)
+ self.assertEqual(tuple(changedset.children), tuple(newset.children))
+
+ yield inbox.writeProperty(newset, request)
+
+ changedset = inbox.readDeadProperty(caldavxml.CalendarFreeBusySet)
+ self.assertEqual(tuple(changedset.children), tuple(oldset.children))
+
+
+ @inlineCallbacks
+ def test_free_busy_set_strip_slash(self):
+ """
+ ???
+ """
+
+ request = SimpleRequest(self.site, "GET", "/inbox/")
+ inbox = yield request.locateResource("/inbox/")
+ self.assertTrue(inbox.hasDeadProperty(caldavxml.CalendarFreeBusySet))
+
+ oldfbset = set(("/calendar/",))
+ oldset = caldavxml.CalendarFreeBusySet(*[davxml.HRef(url) for url in oldfbset])
+ inbox.writeDeadProperty(oldset)
+
+ writefbset = set(("/calendar/",))
+ writeset = caldavxml.CalendarFreeBusySet(*[davxml.HRef(url) for url in writefbset])
+ yield inbox.writeProperty(writeset, request)
+
+ correctfbset = set(("/calendar",))
+ correctset = caldavxml.CalendarFreeBusySet(*[davxml.HRef(url) for url in correctfbset])
+ changedset = inbox.readDeadProperty(caldavxml.CalendarFreeBusySet)
+ self.assertEqual(tuple(changedset.children), tuple(correctset.children))
+
+
+ @inlineCallbacks
+ def test_free_busy_set_strip_slash_remove(self):
+ """
+ ???
+ """
+
+ request = SimpleRequest(self.site, "GET", "/inbox/")
+ inbox = yield request.locateResource("/inbox/")
+ self.assertTrue(inbox.hasDeadProperty(caldavxml.CalendarFreeBusySet))
+
+ oldfbset = set(("/calendar/", "/broken/"))
+ oldset = caldavxml.CalendarFreeBusySet(*[davxml.HRef(url) for url in oldfbset])
+ inbox.writeDeadProperty(oldset)
+
+ writefbset = set(("/calendar/", "/broken/"))
+ writeset = caldavxml.CalendarFreeBusySet(*[davxml.HRef(url) for url in writefbset])
+ yield inbox.writeProperty(writeset, request)
+
+ correctfbset = set(("/calendar",))
+ correctset = caldavxml.CalendarFreeBusySet(*[davxml.HRef(url) for url in correctfbset])
+ changedset = inbox.readDeadProperty(caldavxml.CalendarFreeBusySet)
+ self.assertEqual(tuple(changedset.children), tuple(correctset.children))
+
+
+
+class DefaultCalendar (StoreTestCase):
+
+ @inlineCallbacks
+ def test_pick_default_vevent_calendar(self):
+ """
+ Test that pickNewDefaultCalendar will choose the correct calendar.
+ """
+
+ request = SimpleStoreRequest(self, "GET", "/calendars/users/wsanchez/")
+ inbox = yield request.locateResource("/calendars/users/wsanchez/inbox")
+
+ # default property initially present
+ prop = yield inbox.readProperty(caldavxml.ScheduleDefaultCalendarURL, request)
+ self.assertEqual(str(prop.children[0]), "/calendars/__uids__/6423F94A-6B76-4A3A-815B-D52CFD77935D/calendar")
+
+ yield self.abort()
+
+
+ @inlineCallbacks
+ def test_pick_default_vtodo_calendar(self):
+ """
+ Test that pickNewDefaultCalendar will choose the correct tasks calendar.
+ """
+
+ request = SimpleStoreRequest(self, "GET", "/calendars/users/wsanchez/")
+ inbox = yield request.locateResource("/calendars/users/wsanchez/inbox")
+
+ default = yield inbox.readProperty(customxml.ScheduleDefaultTasksURL, request)
+ self.assertEqual(str(default.children[0]), "/calendars/__uids__/6423F94A-6B76-4A3A-815B-D52CFD77935D/tasks")
+
+ yield self.abort()
+
+
+ @inlineCallbacks
+ def test_missing_default_vevent_calendar(self):
+ """
+ Test that pickNewDefaultCalendar will create a missing default calendar.
+ """
+
+ request = SimpleStoreRequest(self, "GET", "/calendars/users/wsanchez/")
+ home = yield request.locateResource("/calendars/users/wsanchez/")
+ inbox = yield request.locateResource("/calendars/users/wsanchez/inbox")
+
+ # default property initially not present
+ default = yield inbox.readProperty(caldavxml.ScheduleDefaultCalendarURL, request)
+ self.assertEqual(str(default.children[0]), "/calendars/__uids__/6423F94A-6B76-4A3A-815B-D52CFD77935D/calendar")
+
+ # Forcibly remove the one we need
+ yield home._newStoreHome.removeChildWithName("calendar")
+ names = [calendarName for calendarName in (yield home._newStoreHome.listCalendars())]
+ self.assertTrue("calendar" not in names)
+
+ default = yield inbox.readProperty(caldavxml.ScheduleDefaultCalendarURL, request)
+ self.assertEqual(str(default.children[0]), "/calendars/__uids__/6423F94A-6B76-4A3A-815B-D52CFD77935D/calendar")
+
+ yield self.abort()
+
+
+ @inlineCallbacks
+ def test_missing_default_vtodo_calendar(self):
+ """
+ Test that pickNewDefaultCalendar will create a missing default tasks calendar.
+ """
+
+ request = SimpleStoreRequest(self, "GET", "/calendars/users/wsanchez/")
+ home = yield request.locateResource("/calendars/users/wsanchez/")
+ inbox = yield request.locateResource("/calendars/users/wsanchez/inbox")
+
+ # default property present
+ default = yield inbox.readProperty(customxml.ScheduleDefaultTasksURL, request)
+ self.assertEqual(str(default.children[0]), "/calendars/__uids__/6423F94A-6B76-4A3A-815B-D52CFD77935D/tasks")
+
+ # Forcibly remove the one we need
+ yield home._newStoreHome.removeChildWithName("tasks")
+ names = [calendarName for calendarName in (yield home._newStoreHome.listCalendars())]
+ self.assertTrue("tasks" not in names)
+
+ default = yield inbox.readProperty(customxml.ScheduleDefaultTasksURL, request)
+ self.assertEqual(str(default.children[0]), "/calendars/__uids__/6423F94A-6B76-4A3A-815B-D52CFD77935D/tasks")
+
+ yield self.abort()
+
+
+ @inlineCallbacks
+ def test_pick_default_other(self):
+ """
+ Make calendar
+ """
+
+ request = SimpleStoreRequest(self, "GET", "/calendars/users/wsanchez/")
+ inbox = yield request.locateResource("/calendars/users/wsanchez/inbox")
+
+ # default property present
+ default = yield inbox.readProperty(caldavxml.ScheduleDefaultCalendarURL, request)
+ self.assertEqual(str(default.children[0]), "/calendars/__uids__/6423F94A-6B76-4A3A-815B-D52CFD77935D/calendar")
+
+ # Create a new default calendar
+ newcalendar = yield request.locateResource("/calendars/users/wsanchez/newcalendar")
+ yield newcalendar.createCalendarCollection()
+ yield inbox.writeProperty(caldavxml.ScheduleDefaultCalendarURL(davxml.HRef("/calendars/__uids__/6423F94A-6B76-4A3A-815B-D52CFD77935D/newcalendar")), request)
+
+ # Delete the normal calendar
+ calendar = yield request.locateResource("/calendars/users/wsanchez/calendar")
+ yield calendar.storeRemove(request)
+ yield self.commit()
+
+ request = SimpleStoreRequest(self, "GET", "/calendars/users/wsanchez/")
+ inbox = yield request.locateResource("/calendars/users/wsanchez/inbox")
+
+ default = yield inbox.readProperty(caldavxml.ScheduleDefaultCalendarURL, request)
+ self.assertEqual(str(default.children[0]), "/calendars/__uids__/6423F94A-6B76-4A3A-815B-D52CFD77935D/newcalendar")
+
+ yield self.abort()
+
+
+ @inlineCallbacks
+ def test_set_default_vevent_other(self):
+ """
+ Test that the default URL can be set to another VEVENT calendar
+ """
+
+ request = SimpleStoreRequest(self, "GET", "/calendars/users/wsanchez/")
+ inbox = yield request.locateResource("/calendars/users/wsanchez/inbox")
+
+ # default property is present
+ default = yield inbox.readProperty(caldavxml.ScheduleDefaultCalendarURL, request)
+ self.assertEqual(str(default.children[0]), "/calendars/__uids__/6423F94A-6B76-4A3A-815B-D52CFD77935D/calendar")
+
+ # Create a new default calendar
+ newcalendar = yield request.locateResource("/calendars/users/wsanchez/newcalendar")
+ yield newcalendar.createCalendarCollection()
+ yield newcalendar.setSupportedComponents(("VEVENT",))
+ yield self.commit()
+
+ request = SimpleStoreRequest(self, "GET", "/calendars/users/wsanchez/")
+ inbox = yield request.locateResource("/calendars/users/wsanchez/inbox")
+ yield inbox.writeProperty(caldavxml.ScheduleDefaultCalendarURL(davxml.HRef("/calendars/__uids__/6423F94A-6B76-4A3A-815B-D52CFD77935D/newcalendar")), request)
+
+ default = yield inbox.readProperty(caldavxml.ScheduleDefaultCalendarURL, request)
+ self.assertEqual(str(default.children[0]), "/calendars/__uids__/6423F94A-6B76-4A3A-815B-D52CFD77935D/newcalendar")
+
+ yield self.commit()
+
+
+ @inlineCallbacks
+ def test_is_default_calendar(self):
+ """
+ Test .isDefaultCalendar() returns the proper class or None.
+ """
+
+ # Create a new non-default calendar
+ request = SimpleStoreRequest(self, "GET", "/calendars/users/wsanchez/")
+ newcalendar = yield request.locateResource("/calendars/users/wsanchez/newcalendar")
+ yield newcalendar.createCalendarCollection()
+ yield newcalendar.setSupportedComponents(("VEVENT",))
+ inbox = yield request.locateResource("/calendars/users/wsanchez/inbox")
+ yield inbox.defaultCalendar(request, "VEVENT")
+ yield inbox.defaultCalendar(request, "VTODO")
+ yield self.commit()
+
+ request = SimpleStoreRequest(self, "GET", "/calendars/users/wsanchez/")
+ inbox = yield request.locateResource("/calendars/users/wsanchez/inbox")
+ calendar = yield request.locateResource("/calendars/users/wsanchez/calendar")
+ newcalendar = yield request.locateResource("/calendars/users/wsanchez/newcalendar")
+ tasks = yield request.locateResource("/calendars/users/wsanchez/tasks")
+
+ result = yield calendar.isDefaultCalendar(request)
+ self.assertTrue(result)
+
+ result = yield newcalendar.isDefaultCalendar(request)
+ self.assertFalse(result)
+
+ result = yield tasks.isDefaultCalendar(request)
+ self.assertTrue(result)
+
+ yield self.commit()
Modified: CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/storebridge.py
===================================================================
--- CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/storebridge.py 2013-04-17 23:02:28 UTC (rev 11056)
+++ CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/storebridge.py 2013-04-18 18:21:54 UTC (rev 11057)
@@ -48,11 +48,10 @@
from twistedcaldav.ical import Component as VCalendar, Property as VProperty, \
InvalidICalendarDataError, iCalendarProductID, allowedComponents, Component
from twistedcaldav.memcachelock import MemcacheLockTimeoutError
-from twistedcaldav.method.put_addressbook_common import StoreAddressObjectResource
from twistedcaldav.notifications import NotificationCollectionResource, NotificationResource
from twistedcaldav.resource import CalDAVResource, GlobalAddressBookResource, \
DefaultAlarmPropertyMixin
-from twistedcaldav.scheduling.caldav.resource import ScheduleInboxResource
+from twistedcaldav.scheduling_store.caldav.resource import ScheduleInboxResource
from twistedcaldav.scheduling.implicit import ImplicitScheduler
from twistedcaldav.vcard import Component as VCard, InvalidVCardDataError
@@ -1271,14 +1270,11 @@
Moving a calendar collection is allowed for the purposes of changing
that calendar's name.
"""
- defaultCalendarType = (yield self.isDefaultCalendar(request))
-
result = (yield super(CalendarCollectionResource, self).http_MOVE(request))
if result == NO_CONTENT:
destinationURI = urlsplit(request.headers.getHeader("destination"))[2]
destination = yield request.locateResource(destinationURI)
- yield self.movedCalendar(request, defaultCalendarType,
- destination, destinationURI)
+ yield self.movedCalendar(request, destination, destinationURI)
returnValue(result)
Modified: CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/test/test_calendarquery.py
===================================================================
--- CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/test/test_calendarquery.py 2013-04-17 23:02:28 UTC (rev 11056)
+++ CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/test/test_calendarquery.py 2013-04-18 18:21:54 UTC (rev 11057)
@@ -18,25 +18,24 @@
from twisted.trial.unittest import SkipTest
-from twext.python.filepath import CachingFilePath as FilePath
-
from twext.web2 import responsecode
from twext.web2.iweb import IResponse
from twext.web2.stream import MemoryStream
from txdav.xml import element as davxml
from twext.web2.dav.util import davXMLFromStream
-from twext.web2.test.test_server import SimpleRequest
from twistedcaldav import caldavxml
from twistedcaldav import ical
from twistedcaldav.query import calendarqueryfilter
from twistedcaldav.config import config
-from twistedcaldav.test.util import HomeTestCase
+from twistedcaldav.test.util import StoreTestCase, SimpleStoreRequest
from twisted.internet.defer import inlineCallbacks, returnValue
-from txdav.common.datastore.test.util import buildStore, StubNotifierFactory
from pycalendar.datetime import PyCalendarDateTime
+from twistedcaldav.ical import Component
+from txdav.caldav.icalendarstore import ComponentUpdateState
+from twistedcaldav.directory.directory import DirectoryService
@inlineCallbacks
@@ -61,21 +60,35 @@
count += 1
if child.basename().split(".")[-1] != "ics":
continue
- request = SimpleRequest(testCase.site, "PUT",
- uri + "/" + child.basename())
+ request = SimpleStoreRequest(testCase, "PUT", uri + "/" + child.basename())
request.stream = MemoryStream(child.getContent())
yield testCase.send(request)
returnValue(count)
-class CalendarQuery (HomeTestCase):
+class CalendarQuery (StoreTestCase):
"""
calendar-query REPORT
"""
data_dir = os.path.join(os.path.dirname(__file__), "data")
holidays_dir = os.path.join(data_dir, "Holidays")
+ @inlineCallbacks
+ def populate(self):
+ """
+ Put the contents of the Holidays directory into the store.
+ """
+ record = self.directory.recordWithShortName(DirectoryService.recordType_users, "wsanchez")
+ yield self.transactionUnderTest().calendarHomeWithUID(record.uid, create=True)
+ calendar = yield self.calendarUnderTest(name="calendar", home=record.uid)
+ for f in os.listdir(self.holidays_dir):
+ if f.endswith(".ics"):
+ component = Component.fromString(open(os.path.join(self.holidays_dir, f)).read())
+ yield calendar._createCalendarObjectWithNameInternal(f, component, internal_state=ComponentUpdateState.RAW)
+ yield self.commit()
+
+
def test_calendar_query_time_range(self):
"""
Partial retrieval of events by time range.
@@ -158,8 +171,9 @@
self.fail("REPORT property %r returned calendar %s outside of request time range %r"
% (property, property.calendar, query_timerange))
- return self.calendar_query("/calendar_query_time_range/", query, got_xml)
+ return self.calendar_query(query, got_xml)
+
def test_calendar_query_partial_recurring(self):
"""
Partial retrieval of recurring events.
@@ -167,6 +181,7 @@
"""
raise SkipTest("test unimplemented")
+
def test_calendar_query_expanded_recurring(self):
"""
Expanded retrieval of recurring events.
@@ -174,6 +189,7 @@
"""
raise SkipTest("test unimplemented")
+
def test_calendar_query_partial_freebusy(self):
"""
Partial retrieval of stored free busy components.
@@ -181,6 +197,7 @@
"""
raise SkipTest("test unimplemented")
+
def test_calendar_query_todo_alarm(self):
"""
Retrieval of to-dos by alarm time range.
@@ -188,6 +205,7 @@
"""
raise SkipTest("test unimplemented")
+
def test_calendar_query_by_uid(self):
"""
Event by UID.
@@ -196,7 +214,6 @@
uid = "C3189A88-1ED0-11D9-A5E0-000A958A3252"
return self.simple_event_query(
- "/calendar_query_uid/",
caldavxml.PropertyFilter(
caldavxml.TextMatch.fromString(uid, False),
name="UID",
@@ -204,6 +221,7 @@
[uid]
)
+
def test_calendar_query_partstat(self):
"""
Retrieval of events by participation status.
@@ -211,6 +229,7 @@
"""
raise SkipTest("test unimplemented")
+
def test_calendar_query_all_events(self):
"""
All events.
@@ -219,53 +238,47 @@
uids = [r[0] for r in (os.path.splitext(f) for f in
os.listdir(self.holidays_dir)) if r[1] == ".ics"]
- return self.simple_event_query("/calendar_query_events/", None, uids)
+ return self.simple_event_query(None, uids)
+
def test_calendar_query_limited_with_data(self):
"""
All events.
(CalDAV-access-09, section 7.6.8)
"""
-
- oldValue = config.MaxQueryWithDataResults
- config.MaxQueryWithDataResults = 1
+
+ self.patch(config, "MaxQueryWithDataResults", 1)
def _restoreValueOK(f):
- config.MaxQueryWithDataResults = oldValue
self.fail("REPORT must fail with 403")
def _restoreValueError(f):
- config.MaxQueryWithDataResults = oldValue
return None
uids = [r[0] for r in (os.path.splitext(f) for f in os.listdir(self.holidays_dir)) if r[1] == ".ics"]
- d = self.simple_event_query("/calendar_query_events/", None, uids)
+ d = self.simple_event_query(None, uids)
d.addCallbacks(_restoreValueOK, _restoreValueError)
return d
+
def test_calendar_query_limited_without_data(self):
"""
All events.
(CalDAV-access-09, section 7.6.8)
"""
-
- oldValue = config.MaxQueryWithDataResults
- config.MaxQueryWithDataResults = 1
- def _restoreValueOK(f):
- config.MaxQueryWithDataResults = oldValue
- return None
+ self.patch(config, "MaxQueryWithDataResults", 1)
def _restoreValueError(f):
- config.MaxQueryWithDataResults = oldValue
self.fail("REPORT must not fail with 403")
uids = [r[0] for r in (os.path.splitext(f) for f in os.listdir(self.holidays_dir)) if r[1] == ".ics"]
- d = self.simple_event_query("/calendar_query_events/", None, uids, withData=False)
- d.addCallbacks(_restoreValueOK, _restoreValueError)
+ d = self.simple_event_query(None, uids, withData=False)
+ d.addErrback(_restoreValueError)
return d
- def simple_event_query(self, cal_uri, event_filter, uids, withData=True):
+
+ def simple_event_query(self, event_filter, uids, withData=True):
props = (
davxml.GETETag(),
)
@@ -302,7 +315,8 @@
for property in properties:
qname = property.qname()
- if qname == (davxml.dav_namespace, "getetag"): continue
+ if qname == (davxml.dav_namespace, "getetag"):
+ continue
if qname != (caldavxml.caldav_namespace, "calendar-data"):
self.fail("Response included unexpected property %r" % (property,))
@@ -323,22 +337,13 @@
self.assertEqual(result_calendar, original_calendar)
- return self.calendar_query(cal_uri, query, got_xml)
+ return self.calendar_query(query, got_xml)
@inlineCallbacks
- def calendar_query(self, calendar_uri, query, got_xml):
+ def calendar_query(self, query, got_xml):
- response = yield self.send(SimpleRequest(self.site, "MKCALENDAR", calendar_uri))
- response = IResponse(response)
-
- if response.code != responsecode.CREATED:
- self.fail("MKCALENDAR failed: %s" % (response.code,))
-
- # Add holiday events to calendar
- yield addEventsDir(self, FilePath(self.holidays_dir), calendar_uri)
-
- request = SimpleRequest(self.site, "REPORT", calendar_uri)
+ request = SimpleStoreRequest(self, "REPORT", "/calendars/users/wsanchez/calendar/", authid="wsanchez")
request.stream = MemoryStream(query.toxml())
response = yield self.send(request)
@@ -350,17 +355,3 @@
returnValue(
(yield davXMLFromStream(response.stream).addCallback(got_xml))
)
-
-
-class DatabaseQueryTests(CalendarQuery):
-
- @inlineCallbacks
- def setUp(self):
- self.calendarStore = yield buildStore(self, StubNotifierFactory())
- yield super(DatabaseQueryTests, self).setUp()
-
-
- def createDataStore(self):
- return self.calendarStore
-
-
Modified: CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/test/test_collectioncontents.py
===================================================================
--- CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/test/test_collectioncontents.py 2013-04-17 23:02:28 UTC (rev 11056)
+++ CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/test/test_collectioncontents.py 2013-04-18 18:21:54 UTC (rev 11057)
@@ -20,7 +20,6 @@
from twext.web2.iweb import IResponse
from twext.web2.stream import MemoryStream, FileStream
from twext.web2.http_headers import MimeType
-from twext.web2.test.test_server import SimpleRequest
from twistedcaldav.ical import Component
from twistedcaldav.memcachelock import MemcacheLock
@@ -28,9 +27,10 @@
from twistedcaldav.method.put_common import StoreCalendarObjectResource
-from twistedcaldav.test.util import HomeTestCase
+from twistedcaldav.test.util import StoreTestCase, SimpleStoreRequest
+from twext.web2.dav.util import joinURL
-class CollectionContents(HomeTestCase):
+class CollectionContents(StoreTestCase):
"""
PUT request
"""
@@ -65,7 +65,7 @@
"""
Make (regular) collection in calendar
"""
- calendar_uri = "/collection_in_calendar/"
+ calendar_uri = "/calendars/users/wsanchez/collection_in_calendar/"
def mkcalendar_cb(response):
response = IResponse(response)
@@ -79,26 +79,28 @@
if response.code != responsecode.FORBIDDEN:
self.fail("Incorrect response to nested MKCOL: %s" % (response.code,))
- nested_uri = "/".join([calendar_uri, "nested"])
+ nested_uri = joinURL(calendar_uri, "nested")
- request = SimpleRequest(self.site, "MKCOL", nested_uri)
- self.send(request, mkcol_cb)
+ request = SimpleStoreRequest(self, "MKCOL", nested_uri, authid="wsanchez")
+ return self.send(request, mkcol_cb)
- request = SimpleRequest(self.site, "MKCALENDAR", calendar_uri)
+ request = SimpleStoreRequest(self, "MKCALENDAR", calendar_uri, authid="wsanchez")
return self.send(request, mkcalendar_cb)
+
def test_bogus_file(self):
"""
Bogus file in calendar collection
"""
+
# FIXME: Should FileStream be OK here?
+ # FIXME: Should FileStream be OK here?
dst_file = file(__file__)
- try:
- stream = FileStream(dst_file)
- return self._test_file_in_calendar("bogus file in calendar", (stream, responsecode.FORBIDDEN))
- finally:
- dst_file.close()
+ self.addCleanup(dst_file.close)
+ stream = FileStream(dst_file)
+ return self._test_file_in_calendar("bogus file in calendar", (stream, responsecode.FORBIDDEN))
+
def openHolidays(self):
"""
Open the 'Holidays.ics' calendar.
@@ -135,7 +137,8 @@
if subcomponent.name() == "VEVENT":
subcalendar = Component("VCALENDAR")
subcalendar.addComponent(subcomponent)
- for property in calendar.properties(): subcalendar.addProperty(property)
+ for property in calendar.properties():
+ subcalendar.addProperty(property)
work.append((MemoryStream(str(subcalendar)), responsecode.CREATED))
return self._test_file_in_calendar("single event in calendar", *work)
@@ -148,8 +151,10 @@
stream = self.dataPath.child(
"Holidays").child(
"C318AA54-1ED0-11D9-A5E0-000A958A3252.ics").open()
- try: calendar = str(Component.fromStream(stream))
- finally: stream.close()
+ try:
+ calendar = str(Component.fromStream(stream))
+ finally:
+ stream.close()
return self._test_file_in_calendar(
"mutiple resources with the same UID",
@@ -164,7 +169,7 @@
with the data from given stream and verifies that the response code from the
PUT request matches the given response_code.
"""
- calendar_uri = "/testing_calendar/"
+ calendar_uri = "/calendars/users/wsanchez/testing_calendar/"
@inlineCallbacks
@@ -178,8 +183,8 @@
for stream, response_code in work:
- dst_uri = "/".join([calendar_uri, "dst%d.ics" % (c,)])
- request = SimpleRequest(self.site, "PUT", dst_uri)
+ dst_uri = joinURL(calendar_uri, "dst%d.ics" % (c,))
+ request = SimpleStoreRequest(self, "PUT", dst_uri, authid="wsanchez")
request.headers.setHeader("if-none-match", "*")
request.headers.setHeader("content-type", MimeType("text", "calendar"))
request.stream = stream
@@ -191,7 +196,7 @@
c += 1
- request = SimpleRequest(self.site, "MKCALENDAR", calendar_uri)
+ request = SimpleStoreRequest(self, "MKCALENDAR", calendar_uri, authid="wsanchez")
return self.send(request, mkcalendar_cb)
@@ -199,7 +204,7 @@
"""
Make (regular) collection in calendar
"""
- calendar_uri = "/dot_file_in_calendar/"
+ calendar_uri = "/calendars/users/wsanchez/dot_file_in_calendar/"
def mkcalendar_cb(response):
response = IResponse(response)
@@ -217,16 +222,17 @@
"Holidays").child(
"C318AA54-1ED0-11D9-A5E0-000A958A3252.ics"
).open()
- try: calendar = str(Component.fromStream(stream))
- finally: stream.close()
+ try:
+ calendar = str(Component.fromStream(stream))
+ finally:
+ stream.close()
event_uri = "/".join([calendar_uri, ".event.ics"])
- request = SimpleRequest(self.site, "PUT", event_uri)
+ request = SimpleStoreRequest(self, "PUT", event_uri, authid="wsanchez")
request.headers.setHeader("content-type", MimeType("text", "calendar"))
request.stream = MemoryStream(calendar)
return self.send(request, put_cb)
- request = SimpleRequest(self.site, "MKCALENDAR", calendar_uri)
+ request = SimpleStoreRequest(self, "MKCALENDAR", calendar_uri, authid="wsanchez")
return self.send(request, mkcalendar_cb)
-
Modified: CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/test/test_multiget.py
===================================================================
--- CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/test/test_multiget.py 2013-04-17 23:02:28 UTC (rev 11056)
+++ CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/test/test_multiget.py 2013-04-18 18:21:54 UTC (rev 11057)
@@ -13,23 +13,24 @@
# limitations under the License.
##
-import os
-
-from twisted.internet.defer import inlineCallbacks, returnValue
from twext.python.filepath import CachingFilePath as FilePath
from twext.web2 import responsecode
+from twext.web2.dav.util import davXMLFromStream, joinURL
from twext.web2.iweb import IResponse
from twext.web2.stream import MemoryStream
-from txdav.xml import element as davxml
-from twext.web2.dav.util import davXMLFromStream
-from twext.web2.test.test_server import SimpleRequest
+from twisted.internet.defer import inlineCallbacks, returnValue
+
from twistedcaldav import caldavxml
from twistedcaldav import ical
-from twistedcaldav.test.util import HomeTestCase, todo
from twistedcaldav.config import config
+from twistedcaldav.test.util import todo, StoreTestCase, SimpleStoreRequest
-class CalendarMultiget (HomeTestCase):
+from txdav.xml import element as davxml
+
+import os
+
+class CalendarMultiget (StoreTestCase):
"""
calendar-multiget REPORT
"""
@@ -48,6 +49,7 @@
return self.simple_event_multiget("/calendar_multiget_events/", okuids, baduids)
+
def test_multiget_all_events(self):
"""
All events.
@@ -59,6 +61,7 @@
return self.simple_event_multiget("/calendar_multiget_events/", okuids, baduids)
+
def test_multiget_limited_with_data(self):
"""
All events.
@@ -82,6 +85,7 @@
d.addCallbacks(_restoreValueOK, _restoreValueError)
return d
+
def test_multiget_limited_no_data(self):
"""
All events.
@@ -103,6 +107,7 @@
return self.simple_event_multiget("/calendar_multiget_events/", okuids, baduids, withData=False)
+
@todo("Remove: Does not work with new store")
@inlineCallbacks
def test_multiget_one_broken_event(self):
@@ -110,10 +115,10 @@
All events.
(CalDAV-access-09, section 7.6.8)
"""
- okuids = ["good", "bad",]
+ okuids = ["good", "bad", ]
baduids = []
data = {
- "good":"""BEGIN:VCALENDAR
+ "good": """BEGIN:VCALENDAR
CALSCALE:GREGORIAN
PRODID:-//Apple Computer\, Inc//iCal 2.0//EN
VERSION:2.0
@@ -127,7 +132,7 @@
END:VEVENT
END:VCALENDAR
""".replace("\n", "\r\n"),
- "bad":"""BEGIN:VCALENDAR
+ "bad": """BEGIN:VCALENDAR
CALSCALE:GREGORIAN
PRODID:-//Apple Computer\, Inc//iCal 2.0//EN
VERSION:2.0
@@ -144,7 +149,7 @@
}
yield self.simple_event_multiget("/calendar_multiget_events/", okuids, baduids, data)
-
+
# Now forcibly corrupt one piece of calendar data
calendar_path = os.path.join(self.docroot, "calendar_multiget_events/", "bad.ics")
f = open(calendar_path, "w")
@@ -165,7 +170,10 @@
baduids = ["bad", ]
yield self.simple_event_multiget("/calendar_multiget_events/", okuids, baduids, data, no_init=True)
+
def simple_event_multiget(self, cal_uri, okuids, baduids, data=None, no_init=False, withData=True):
+
+ cal_uri = joinURL("/calendars/users/wsanchez", cal_uri)
props = (
davxml.GETETag(),
)
@@ -175,14 +183,14 @@
)
children = []
children.append(davxml.PropertyContainer(*props))
-
- okhrefs = [cal_uri + x + ".ics" for x in okuids]
- badhrefs = [cal_uri + x + ".ics" for x in baduids]
+
+ okhrefs = [joinURL(cal_uri, x + ".ics") for x in okuids]
+ badhrefs = [joinURL(cal_uri, x + ".ics") for x in baduids]
for href in okhrefs + badhrefs:
children.append(davxml.HRef.fromString(href))
-
+
query = caldavxml.CalendarMultiGet(*children)
-
+
def got_xml(doc):
if not isinstance(doc.root_element, davxml.MultiStatus):
self.fail("REPORT response XML root element is not multistatus: %r" % (doc.root_element,))
@@ -200,7 +208,8 @@
for property in properties:
qname = property.qname()
- if qname == (davxml.dav_namespace, "getetag"): continue
+ if qname == (davxml.dav_namespace, "getetag"):
+ continue
if qname != (caldavxml.caldav_namespace, "calendar-data"):
self.fail("Response included unexpected property %r" % (property,))
@@ -223,7 +232,7 @@
original_calendar = ical.Component.fromStream(original_filename)
self.assertEqual(result_calendar, original_calendar)
-
+
for response in doc.root_element.childrenOfType(davxml.StatusResponse):
href = str(response.childOfType(davxml.HRef))
propstatus = response.childOfType(davxml.PropertyStatus)
@@ -241,38 +250,37 @@
continue
else:
self.fail("Got unexpected href %r" % (href,))
-
+
if withData and (len(okuids) + len(badhrefs)):
self.fail("Some components were not returned: %r, %r" % (okuids, badhrefs))
return self.calendar_query(cal_uri, query, got_xml, data, no_init)
+
@inlineCallbacks
def calendar_query(self, calendar_uri, query, got_xml, data, no_init):
if not no_init:
- response = yield self.send(SimpleRequest(self.site, "MKCALENDAR",
- calendar_uri))
+ response = yield self.send(SimpleStoreRequest(self, "MKCALENDAR", calendar_uri, authid="wsanchez"))
response = IResponse(response)
if response.code != responsecode.CREATED:
self.fail("MKCALENDAR failed: %s" % (response.code,))
if data:
for filename, icaldata in data.iteritems():
- request = SimpleRequest(self.site, "PUT",
- calendar_uri + "/" + filename + ".ics")
+ request = SimpleStoreRequest(self, "PUT", joinURL(calendar_uri, filename + ".ics"), authid="wsanchez")
request.stream = MemoryStream(icaldata)
yield self.send(request)
else:
# Add holiday events to calendar
for child in FilePath(self.holidays_dir).children():
- if os.path.splitext(child.basename())[1] != ".ics": continue
- request = SimpleRequest(self.site, "PUT",
- calendar_uri + "/" + child.basename())
+ if os.path.splitext(child.basename())[1] != ".ics":
+ continue
+ request = SimpleStoreRequest(self, "PUT", joinURL(calendar_uri, child.basename()), authid="wsanchez")
request.stream = MemoryStream(child.getContent())
yield self.send(request)
- request = SimpleRequest(self.site, "REPORT", calendar_uri)
+ request = SimpleStoreRequest(self, "REPORT", calendar_uri, authid="wsanchez")
request.stream = MemoryStream(query.toxml())
response = yield self.send(request)
Deleted: CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/test/test_validation.py
===================================================================
--- CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/test/test_validation.py 2013-04-17 23:02:28 UTC (rev 11056)
+++ CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/test/test_validation.py 2013-04-18 18:21:54 UTC (rev 11057)
@@ -1,229 +0,0 @@
-##
-# Copyright (c) 2009-2013 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 xml.etree.cElementTree import XML
-
-from twisted.internet.defer import inlineCallbacks
-from twisted.trial.unittest import TestCase
-
-# XXX this should be public, but it isn't, since it's in a test_* module. Need
-# to address this to use system twisted.
-from twext.web2.test.test_server import SimpleRequest
-from twext.web2.http import HTTPError
-from twext.web2.resource import Resource
-
-from twistedcaldav.config import config
-from twistedcaldav.ical import Component, Property
-from twistedcaldav.method.put_common import StoreCalendarObjectResource
-from twistedcaldav.caldavxml import MaxAttendeesPerInstance
-from twistedcaldav.resource import CalDAVResource
-
-class InMemoryCalendarObjectResource(CalDAVResource):
-
- def exists(self):
- return hasattr(self, "_data") and self._data is not None
-
-
- def iCalendarForUser(self, user):
- return self._data
-
-
- def setData(self, data):
- self._data = data
-
-
-
-class TestCopyMoveValidation(TestCase):
- """
- Tests for the validation code in L{twistedcaldav.method.put_common}.
- """
-
- def setUp(self):
- """
- Set up some CalDAV stuff.
- """
-
- self.destination = InMemoryCalendarObjectResource()
- self.destination.name = lambda : 'bar'
- self.destinationParent = CalDAVResource()
- self.destinationParent.name = lambda : 'foo'
- self.destinationParent.isSupportedComponent = lambda x: True
-
-
- def _getSampleCalendar(self):
- return Component.fromString("""BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//Apple Computer\, Inc//iCal 2.0//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTAMP:20071114T000000Z
-DTSTART:20071114T000000Z
-ORGANIZER:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-RRULE:FREQ=YEARLY
-END:VEVENT
-END:VCALENDAR
-""")
-
-
- def _getStorer(self, calendar):
- self.sampleCalendar = calendar
- req = SimpleRequest(None, "COPY", "http://example.com/foo/bar")
- req._rememberResource(self.destination, "/foo/bar")
- req._rememberResource(self.destinationParent, "/foo/")
- req._rememberResource(Resource(), "/")
- self.storer = StoreCalendarObjectResource(
- req,
- destination=self.destination,
- destinationparent=self.destinationParent,
- destination_uri="http://example.com/foo/baz",
- calendar=self.sampleCalendar
- )
- return self.storer
-
-
- @inlineCallbacks
- def test_simpleValidRequest(self):
- """
- For a simple valid request,
- L{StoreCalendarObjectResource.fullValidation} results in a L{Deferred}
- which fires with C{None} (and raises no exception).
- """
- self.assertEquals((yield self._getStorer(self._getSampleCalendar()).fullValidation()), None)
-
-
- @inlineCallbacks
- def test_exceedMaximumAttendeesIfNew(self):
- """
- If too many attendees are specified (more than the configured maximum
- for the server), the storer raises an exception containing a
- L{MaxAttendeesPerInstance} element that reports the maximum value, as
- per U{RFC4791 section 5.2.9
- <http://www.webdav.org/specs/rfc4791.html#max-attendees-per-instance>}.
- This test is for new resources.
- """
-
- # Get the event, and add too many attendees to it.
- self.sampleCalendar = self._getSampleCalendar()
- eventComponent = list(self.sampleCalendar.subcomponents())[0]
- for x in xrange(config.MaxAttendeesPerInstance):
- eventComponent.addProperty(
- Property("ATTENDEE", "mailto:user%d at example.com" % (x + 3,)))
-
- try:
- yield self._getStorer(self.sampleCalendar).fullValidation()
- except HTTPError, err:
- element = XML(err.response.stream.mem)[0]
- self.assertEquals(
- element.tag,
- MaxAttendeesPerInstance.sname()
- )
- self.assertEquals(int(element.text), config.MaxAttendeesPerInstance)
- else:
- self.fail("No error; validation should have failed!")
-
-
- @inlineCallbacks
- def test_exceedMaximumAttendeesWhenIncreasing(self):
- """
- If too many attendees are specified (more than the configured maximum
- for the server), the storer raises an exception containing a
- L{MaxAttendeesPerInstance} element that reports the maximum value, as
- per U{RFC4791 section 5.2.9
- <http://www.webdav.org/specs/rfc4791.html#max-attendees-per-instance>}.
- This test is for an increase to an already over-sized resource.
- """
-
- self.patch(config, "MaxAttendeesPerInstance", config.MaxAttendeesPerInstance + 10)
-
- # Get the event, and add many attendees to it - but not enough to fail.
- self.sampleCalendar = self._getSampleCalendar()
- eventComponent = list(self.sampleCalendar.subcomponents())[0]
- for x in xrange(config.MaxAttendeesPerInstance - 5):
- eventComponent.addProperty(
- Property("ATTENDEE", "mailto:user%d at example.com" % (x + 3,)))
-
- try:
- yield self._getStorer(self.sampleCalendar).fullValidation()
- except HTTPError:
- self.fail("Validation should not have failed!")
- self.destination.setData(self.sampleCalendar.duplicate())
-
- # Now reduce the limit and try to add an attendee.
- config.MaxAttendeesPerInstance -= 10
- eventComponent.addProperty(
- Property("ATTENDEE", "mailto:user-extra at example.com"))
-
- try:
- yield self._getStorer(self.sampleCalendar).fullValidation()
- except HTTPError, err:
- element = XML(err.response.stream.mem)[0]
- self.assertEquals(
- element.tag,
- MaxAttendeesPerInstance.sname()
- )
- self.assertEquals(int(element.text), config.MaxAttendeesPerInstance)
- else:
- self.fail("No error; validation should have failed!")
-
-
- @inlineCallbacks
- def test_doNotExceedMaximumAttendeesIfAlreadyPresent(self):
- """
- If too many attendees are specified (more than the configured maximum
- for the server), the storer raises an exception containing a
- L{MaxAttendeesPerInstance} element that reports the maximum value, as
- per U{RFC4791 section 5.2.9
- <http://www.webdav.org/specs/rfc4791.html#max-attendees-per-instance>}.
- This test is for no change to an already over-sized resource.
- """
-
- self.patch(config, "MaxAttendeesPerInstance", config.MaxAttendeesPerInstance + 10)
-
- # Get the event, and add many attendees to it - but not enough to fail.
- self.sampleCalendar = self._getSampleCalendar()
- eventComponent = list(self.sampleCalendar.subcomponents())[0]
- for x in xrange(config.MaxAttendeesPerInstance - 5):
- eventComponent.addProperty(
- Property("ATTENDEE", "mailto:user%d at example.com" % (x + 3,)))
-
- try:
- yield self._getStorer(self.sampleCalendar).fullValidation()
- except HTTPError:
- self.fail("Validation should not have failed!")
- self.destination.setData(self.sampleCalendar.duplicate())
-
- # Now reduce the limit and try to store without any additional attendees.
- config.MaxAttendeesPerInstance -= 10
-
- try:
- yield self._getStorer(self.sampleCalendar).fullValidation()
- except HTTPError:
- self.fail("Validation should not have failed!")
- self.destination.setData(self.sampleCalendar.duplicate())
-
- # Now try to store with fewer attendees.
- self.sampleCalendar = self._getSampleCalendar()
- eventComponent = list(self.sampleCalendar.subcomponents())[0]
- for x in xrange(config.MaxAttendeesPerInstance + 2):
- eventComponent.addProperty(
- Property("ATTENDEE", "mailto:user%d at example.com" % (x + 3,)))
-
- try:
- yield self._getStorer(self.sampleCalendar).fullValidation()
- except HTTPError:
- self.fail("Validation should not have failed!")
- self.destination.setData(self.sampleCalendar.duplicate())
Modified: CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/test/test_wrapping.py
===================================================================
--- CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/test/test_wrapping.py 2013-04-17 23:02:28 UTC (rev 11056)
+++ CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/test/test_wrapping.py 2013-04-18 18:21:54 UTC (rev 11057)
@@ -19,7 +19,6 @@
"""
-from twext.web2.server import Request
from twext.web2.responsecode import UNAUTHORIZED
from twext.web2.http_headers import Headers
from twext.enterprise.ienterprise import AlreadyFinishedError
@@ -35,15 +34,14 @@
from twistedcaldav.storebridge import DropboxCollection, \
CalendarCollectionResource, AddressBookCollectionResource
-from twistedcaldav.test.util import TestCase
+from twistedcaldav.test.util import StoreTestCase, SimpleStoreRequest
from txdav.idav import IDataStore
from txdav.caldav.datastore.test.test_file import test_event_text
from txdav.carddav.datastore.test.test_file import vcard4_text
-from txdav.common.datastore.test.util import buildStore, assertProvides, \
- StubNotifierFactory
+from txdav.common.datastore.test.util import assertProvides
from twext.web2.http import HTTPError
@@ -98,25 +96,13 @@
-class WrappingTests(TestCase):
+class WrappingTests(StoreTestCase):
"""
Tests for L{twistedcaldav.static.CalDAVResource} creating the appropriate type
of txdav.caldav.datastore.file underlying object when it can determine what
type it really represents.
"""
- def setUp(self):
- # Mostly copied from
- # twistedcaldav.directory.test.test_calendar.ProvisionedCalendars; this
- # should probably be refactored, perhaps even to call (some part of) the
- # actual root-resource construction logic?
- super(WrappingTests, self).setUp()
-
- # Setup the initial directory
- self.createStockDirectoryService()
- self.setupCalendars()
-
-
@inlineCallbacks
def populateOneObject(self, objectName, objectText):
"""
@@ -128,21 +114,13 @@
@param objectText: Some iCalendar text to populate it with.
@type objectText: str
"""
- record = self.directoryService.recordWithShortName("users", "wsanchez")
+ record = self.directory.recordWithShortName("users", "wsanchez")
uid = record.uid
- # XXX there should be a more test-friendly way to ensure the directory
- # actually exists
- try:
- self.calendarCollection._newStore._path.createDirectory()
- except:
- pass
- txn = self.calendarCollection._newStore.newTransaction()
+ txn = self.transactionUnderTest()
home = yield txn.calendarHomeWithUID(uid, True)
cal = yield home.calendarWithName("calendar")
- yield cal.createCalendarObjectWithName(
- objectName, VComponent.fromString(objectText)
- )
- yield txn.commit()
+ yield cal.createCalendarObjectWithName(objectName, VComponent.fromString(objectText))
+ yield self.commit()
@inlineCallbacks
@@ -155,24 +133,13 @@
@param objectText: Some iVcard text to populate it with.
@type objectText: str
"""
- record = self.directoryService.recordWithShortName("users", "wsanchez")
+ record = self.directory.recordWithShortName("users", "wsanchez")
uid = record.uid
- # XXX there should be a more test-friendly way to ensure the directory
- # actually exists
- try:
- self.addressbookCollection._newStore._path.createDirectory()
- except:
- pass
- txn = self.addressbookCollection._newStore.newTransaction()
+ txn = self.transactionUnderTest()
home = yield txn.addressbookHomeWithUID(uid, True)
adbk = yield home.addressbookWithName("addressbook")
- if adbk is None:
- yield home.createAddressBookWithName("addressbook")
- adbk = yield home.addressbookWithName("addressbook")
- yield adbk.createAddressBookObjectWithName(
- objectName, VCComponent.fromString(objectText)
- )
- yield txn.commit()
+ yield adbk.createAddressBookObjectWithName(objectName, VCComponent.fromString(objectText))
+ yield self.commit()
requestUnderTest = None
@@ -212,36 +179,18 @@
returnValue(aResource)
- def commit(self):
- """
- Since C{getResource} treats this test case as a resource, it will have
- an associated transaction. Commit that transaction to bring the
- filesystem into a consistent state.
- """
- return self.requestUnderTest._newStoreTransaction.commit()
-
-
def requestForPath(self, path, method='GET'):
"""
Get a L{Request} with a L{FakeChanRequest} for a given path and method.
"""
headers = Headers()
headers.addRawHeader("Host", "localhost:8008")
- chanReq = FakeChanRequest()
- req = Request(
- site=self.site,
- chanRequest=chanReq,
- command=method,
- path=path,
- version=('1', '1'),
- contentLength=0,
- headers=headers
- )
+ req = SimpleStoreRequest(self, method, path, headers)
# 'process()' normally sets these. Shame on web2, having so much
# partially-initialized stuff floating around.
req.remoteAddr = '127.0.0.1'
- req.path = path
+ req.chanRequest = FakeChanRequest()
req.credentialFactories = {}
return req
@@ -254,7 +203,7 @@
L{Resource} is accurately set.
"""
self.assertEquals(resource._principalCollections,
- frozenset([self.directoryFixture.principalsResource]))
+ frozenset([self.principalsResource]))
@inlineCallbacks
@@ -268,10 +217,11 @@
for pathType in self.pathTypes:
req = self.requestForPath('/%ss/users/wsanchez/%s/forget/it'
% (pathType, pathType))
+ txn = self.transactionUnderTest()
yield req.process()
self.assertEquals(req.chanRequest.code, 404)
yield self.failUnlessFailure(
- maybeDeferred(req._newStoreTransaction.commit),
+ maybeDeferred(txn.commit),
AlreadyFinishedError
)
@@ -294,7 +244,7 @@
Creating a DirectoryCalendarHomeProvisioningResource will create a
paired CalendarStore.
"""
- assertProvides(self, IDataStore, self.calendarCollection._newStore)
+ assertProvides(self, IDataStore, self._sqlCalendarStore)
@inlineCallbacks
@@ -424,9 +374,9 @@
Exceeding quota on an attachment returns an HTTP error code.
"""
self.patch(config, "EnableDropBox", True)
- if not hasattr(self.calendarCollection._newStore, "_dropbox_ok"):
- self.calendarCollection._newStore._dropbox_ok = False
- self.patch(self.calendarCollection._newStore, "_dropbox_ok", True)
+ if not hasattr(self._sqlCalendarStore, "_dropbox_ok"):
+ self._sqlCalendarStore._dropbox_ok = False
+ self.patch(self._sqlCalendarStore, "_dropbox_ok", True)
self.patch(Calendar, "asShared", lambda self: [])
yield self.populateOneObject("1.ics", test_event_text)
@@ -545,8 +495,7 @@
"""
Assert that a user's calendar is empty (their default calendar by default).
"""
- txn = self.calendarStore.newTransaction()
- self.addCleanup(txn.commit)
+ txn = self.transactionUnderTest()
home = yield txn.calendarHomeWithUID(user, create=True)
cal = yield home.calendarWithName(calendarName)
objects = yield cal.calendarObjects()
@@ -557,16 +506,6 @@
class DatabaseWrappingTests(WrappingTests):
@inlineCallbacks
- def setUp(self):
- self.calendarStore = yield buildStore(self, StubNotifierFactory())
- super(DatabaseWrappingTests, self).setUp()
-
-
- def createDataStore(self):
- return self.calendarStore
-
-
- @inlineCallbacks
def test_invalidCalendarPUT(self):
"""
Exceeding quota on an attachment returns an HTTP error code.
@@ -583,6 +522,7 @@
((yield calendarObject.renderHTTP(self.requestUnderTest)),
self.requestUnderTest)
)
+
# see twistedcaldav/directory/test/accounts.xml
wsanchez = '6423F94A-6B76-4A3A-815B-D52CFD77935D'
cdaboo = '5A985493-EE2C-4665-94CF-4DFEA3A89500'
@@ -628,9 +568,10 @@
DURATION:PT1H
SUMMARY:Test
END:VEVENT""".format(wsanchez=wsanchez, cdaboo=cdaboo)
- #txn = self.requestUnderTest._newStoreTransaction
+
invalidEvent = eventTemplate.format(invalidInstance, wsanchez=wsanchez, cdaboo=cdaboo).replace(CR, CRLF)
yield putEvt(invalidEvent)
+ self.lastTransaction = None
self.requestUnderTest = None
yield self.assertCalendarEmpty(wsanchez)
yield self.assertCalendarEmpty(cdaboo)
Modified: CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/test/util.py
===================================================================
--- CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/test/util.py 2013-04-17 23:02:28 UTC (rev 11056)
+++ CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/test/util.py 2013-04-18 18:21:54 UTC (rev 11057)
@@ -29,7 +29,7 @@
from twext.python.memcacheclient import ClientFactory
from twext.python.filepath import CachingFilePath as FilePath
import twext.web2.dav.test.util
-from txdav.xml import element as davxml
+from txdav.xml import element as davxml, element
from twext.web2.http import HTTPError, StatusResponse
from twistedcaldav import memcacher
@@ -44,12 +44,17 @@
from twistedcaldav.directory.aggregate import AggregateDirectoryService
from twistedcaldav.directory.xmlfile import XMLDirectoryService
-from txdav.common.datastore.test.util import deriveQuota
+from txdav.common.datastore.test.util import deriveQuota, CommonCommonTests
from txdav.common.datastore.file import CommonDataStore
from calendarserver.provision.root import RootResource
from twext.python.log import Logger
+from txdav.caldav.datastore.test.util import buildCalendarStore
+from calendarserver.tap.util import getRootResource, directoryFromConfig
+from twext.web2.dav.test.util import SimpleRequest
+from twistedcaldav.directory.util import transactionFromRequest
+from twistedcaldav.directory.directory import DirectoryService
log = Logger()
@@ -95,7 +100,6 @@
)
self._directoryChangeHooks = [_setUpPrincipals]
-
directoryService = None
principalsResource = None
@@ -150,6 +154,119 @@
+class SimpleStoreRequest(SimpleRequest):
+ """
+ A SimpleRequest that automatically grabs the proper transaction for a test.
+ """
+ def __init__(self, test, method, uri, headers=None, content=None, authid=None):
+ super(SimpleStoreRequest, self).__init__(test.site, method, uri, headers, content)
+ self._test = test
+ self._newStoreTransaction = test.transactionUnderTest(txn=transactionFromRequest(self, test.storeUnderTest()))
+ self.credentialFactories = {}
+
+ # Fake credentials if auth needed
+ if authid is not None:
+ record = self._test.directory.recordWithShortName(DirectoryService.recordType_users, authid)
+ if record:
+ self.authzUser = self.authnUser = element.Principal(element.HRef("/principals/__uids__/%s/" % (record.uid,)))
+
+
+ @inlineCallbacks
+ def process(self):
+ """
+ Process will commit the transaction in the test so we need to clear it out.
+ """
+ result = yield super(SimpleStoreRequest, self).process()
+ self._test.lastTransaction = None
+ returnValue(result)
+
+
+ def _cbFinishRender(self, result):
+ self._test.lastTransaction = None
+ return super(SimpleStoreRequest, self)._cbFinishRender(result)
+
+
+
+class StoreTestCase(CommonCommonTests, twext.web2.dav.test.util.TestCase):
+ """
+ A base class for tests that use the SQL store.
+ """
+
+ @inlineCallbacks
+ def setUp(self):
+ yield super(StoreTestCase, self).setUp()
+
+ self.configure()
+
+ self._sqlCalendarStore = yield buildCalendarStore(self, self.notifierFactory, directoryFromConfig(config))
+ self.rootResource = getRootResource(config, self._sqlCalendarStore)
+ self.directory = self._sqlCalendarStore.directoryService()
+
+ self.principalsResource = DirectoryPrincipalProvisioningResource("/principals/", self.directory)
+ self.site.resource.putChild("principals", self.principalsResource)
+ self.calendarCollection = DirectoryCalendarHomeProvisioningResource(self.directory, "/calendars/", self._sqlCalendarStore)
+ self.site.resource.putChild("calendars", self.calendarCollection)
+ self.addressbookCollection = DirectoryAddressBookHomeProvisioningResource(self.directory, "/addressbooks/", self._sqlCalendarStore)
+ self.site.resource.putChild("addressbooks", self.addressbookCollection)
+
+ yield self.populate()
+
+
+ def populate(self):
+ return succeed(None)
+
+
+ def storeUnderTest(self):
+ """
+ Return a store for testing.
+ """
+ return self._sqlCalendarStore
+
+
+ def configure(self):
+ """
+ Adjust the global configuration for this test.
+ """
+ self.serverRoot = self.mktemp()
+ os.mkdir(self.serverRoot)
+
+ config.reset()
+
+ config.ServerRoot = os.path.abspath(self.serverRoot)
+ config.ConfigRoot = "config"
+ config.LogRoot = "logs"
+ config.RunRoot = "logs"
+
+ if not os.path.exists(config.DataRoot):
+ os.makedirs(config.DataRoot)
+ if not os.path.exists(config.DocumentRoot):
+ os.makedirs(config.DocumentRoot)
+ if not os.path.exists(config.ConfigRoot):
+ os.makedirs(config.ConfigRoot)
+ if not os.path.exists(config.LogRoot):
+ os.makedirs(config.LogRoot)
+
+ config.Memcached.Pools.Default.ClientEnabled = False
+ config.Memcached.Pools.Default.ServerEnabled = False
+ ClientFactory.allowTestCache = True
+ memcacher.Memcacher.allowTestCache = True
+ memcacher.Memcacher.memoryCacheInstance = None
+ config.DirectoryAddressBook.Enabled = False
+
+ accounts = FilePath(config.DataRoot).child("accounts.xml")
+ accounts.setContent(xmlFile.getContent())
+
+
+ @property
+ def directoryService(self):
+ """
+ Read-only alias for L{DirectoryFixture.directoryService} for
+ compatibility with older tests. TODO: remove this.
+ """
+ return self.directory
+
+
+
class TestCase(twext.web2.dav.test.util.TestCase):
resource_class = RootResource
@@ -159,7 +276,7 @@
addressbooks.) By default returns a L{CommonDataStore}, but this is a
hook for subclasses to override to provide different data stores.
"""
- return CommonDataStore(FilePath(config.DocumentRoot), None, True, False,
+ return CommonDataStore(FilePath(config.DocumentRoot), None, None, True, False,
quota=deriveQuota(self))
@@ -267,7 +384,7 @@
continue
childPath = os.path.join(parent, childName)
- if childStructure.has_key("@contents"):
+ if "@contents" in childStructure:
# This is a file
with open(childPath, "w") as child:
child.write(childStructure["@contents"])
@@ -276,7 +393,7 @@
os.mkdir(childPath)
createChildren(childPath, childStructure)
- if childStructure.has_key("@xattrs"):
+ if "@xattrs" in childStructure:
xattrs = childStructure["@xattrs"]
for attr, value in xattrs.iteritems():
try:
@@ -285,7 +402,7 @@
pass
# Set access and modified times
- if childStructure.has_key("@timestamp"):
+ if "@timestamp" in childStructure:
timestamp = childStructure["@timestamp"]
os.utime(childPath, (timestamp, timestamp))
@@ -338,13 +455,13 @@
childPath = os.path.join(parent, childName)
if not os.path.exists(childPath):
- if childStructure.has_key("@optional"):
+ if "@optional" in childStructure:
return True
else:
print("Missing:", childPath)
return False
- if childStructure.has_key("@contents"):
+ if "@contents" in childStructure:
# This is a file
expectedContents = childStructure["@contents"]
if expectedContents is None:
@@ -371,7 +488,7 @@
if not verifyChildren(childPath, childStructure):
return False
- if childStructure.has_key("@xattrs"):
+ if "@xattrs" in childStructure:
try:
# See if we have xattr support; IOError if not
try:
@@ -406,11 +523,14 @@
return verifyChildren(root, structure)
+
+
class norequest(object):
def addResponseFilter(self, filter):
"stub; ignore me"
+
class HomeTestCase(TestCase):
"""
Utility class for tests which wish to interact with a calendar home rather
@@ -421,7 +541,7 @@
# FIXME: AddressBookHomeTestCase needs the same treatment.
fp = FilePath(self.mktemp())
fp.createDirectory()
- return CommonDataStore(fp, None, True, False)
+ return CommonDataStore(fp, None, None, True, False)
def setUp(self):
@@ -449,7 +569,6 @@
return self._refreshRoot().addCallback(_defer)
-
committed = True
def noRenderCommit(self):
@@ -577,6 +696,7 @@
self._properties = {}
self.resource = _FauxResource()
+
def get(self, qname, uid=None):
qnameuid = qname + (uid,)
data = self._properties.get(qnameuid)
@@ -584,10 +704,12 @@
raise HTTPError(StatusResponse(404, "No such property"))
return data
+
def set(self, property, uid=None):
qnameuid = property.qname() + (uid,)
self._properties[qnameuid] = property
+
def delete(self, qname, uid=None):
try:
qnameuid = qname + (uid,)
@@ -595,14 +717,16 @@
except KeyError:
pass
+
def contains(self, qname, uid=None):
qnameuid = qname + (uid,)
return qnameuid in self._properties
+
def list(self, uid=None, filterByUID=True):
results = self._properties.iterkeys()
if filterByUID:
- return [
+ return [
(namespace, name)
for namespace, name, propuid in results
if propuid == uid
@@ -611,6 +735,7 @@
return results
+
class StubCacheChangeNotifier(object):
def __init__(self, *args, **kwargs):
pass
@@ -634,6 +759,7 @@
self._timeouts = {}
+
def get(self, key):
if key not in self._cache:
return succeed((0, None))
@@ -649,7 +775,6 @@
if key in self._timeouts:
self._timeouts[key].cancel()
-
self._timeouts[key] = self._reactor.callLater(
expireTime,
_removeKey)
@@ -693,6 +818,7 @@
"""
+
class CapturingProcessProtocol(ProcessProtocol):
"""
A L{ProcessProtocol} that captures its output and error.
Modified: CalendarServer/branches/users/cdaboo/store-scheduling/txdav/base/propertystore/sql.py
===================================================================
--- CalendarServer/branches/users/cdaboo/store-scheduling/txdav/base/propertystore/sql.py 2013-04-17 23:02:28 UTC (rev 11056)
+++ CalendarServer/branches/users/cdaboo/store-scheduling/txdav/base/propertystore/sql.py 2013-04-18 18:21:54 UTC (rev 11057)
@@ -50,7 +50,6 @@
"do not construct directly, call PropertyStore.load()"
)
-
_allWithID = Select([prop.NAME, prop.VIEWER_UID, prop.VALUE],
From=prop,
Where=prop.RESOURCE_ID == Parameter("resourceID"))
@@ -213,14 +212,12 @@
return WebDAVDocument.fromString(value).root_element
-
_updateQuery = Update({prop.VALUE: Parameter("value")},
Where=(
prop.RESOURCE_ID == Parameter("resourceID")).And(
prop.NAME == Parameter("name")).And(
prop.VIEWER_UID == Parameter("uid")))
-
_insertQuery = Insert({prop.VALUE: Parameter("value"),
prop.RESOURCE_ID: Parameter("resourceID"),
prop.NAME: Parameter("name"),
@@ -262,8 +259,6 @@
self.log_error("setting a property failed; probably nothing.")
self._txn.subtransaction(trySetItem).addErrback(justLogIt)
-
-
_deleteQuery = Delete(
prop, Where=(prop.RESOURCE_ID == Parameter("resourceID")).And(
prop.NAME == Parameter("name")).And(
@@ -276,11 +271,17 @@
key_str = key.toString()
del self._cached[(key_str, uid)]
- self._deleteQuery.on(self._txn, lambda:KeyError(key),
- resourceID=self._resourceID,
- name=key_str, uid=uid
- )
- self._cacher.delete(str(self._resourceID))
+ @inlineCallbacks
+ def doIt(txn):
+ yield self._deleteQuery.on(txn, lambda: KeyError(key),
+ resourceID=self._resourceID,
+ name=key_str, uid=uid
+ )
+ self._cacher.delete(str(self._resourceID))
+ def justLogIt(f):
+ f.trap(AllRetriesFailed)
+ self.log_error("setting a property failed; probably nothing.")
+ self._txn.subtransaction(doIt).addErrback(justLogIt)
def _keys_uid(self, uid):
@@ -298,6 +299,7 @@
self._deleteResourceQuery.on(self._txn, resourceID=self._resourceID)
self._cacher.delete(str(self._resourceID))
+
@inlineCallbacks
def copyAllProperties(self, other):
"""
@@ -316,7 +318,6 @@
yield self._insertQuery.on(
self._txn, resourceID=self._resourceID, value=value_str,
name=key_str, uid=uid)
-
# Reload from the DB
self._cached = {}
Modified: CalendarServer/branches/users/cdaboo/store-scheduling/txdav/base/propertystore/test/test_sql.py
===================================================================
--- CalendarServer/branches/users/cdaboo/store-scheduling/txdav/base/propertystore/test/test_sql.py 2013-04-17 23:02:28 UTC (rev 11056)
+++ CalendarServer/branches/users/cdaboo/store-scheduling/txdav/base/propertystore/test/test_sql.py 2013-04-18 18:21:54 UTC (rev 11057)
@@ -148,6 +148,7 @@
'b': pval2}[race[-1]]
self.assertEquals(self.propertyStore[pname], winner)
+
@inlineCallbacks
def test_copy(self):
@@ -182,7 +183,7 @@
# Do copy and check results
yield store2_user1.copyAllProperties(store1_user1)
-
+
self.assertEqual(store1_user1.keys(), store2_user1.keys())
store1_user2 = yield PropertyStore.load("user01", "user02", self._txn, 2)
@@ -190,7 +191,23 @@
self.assertEqual(store1_user2.keys(), store2_user2.keys())
+ @inlineCallbacks
+ def test_insert_delete(self):
+
+ # Existing store
+ store1_user1 = yield PropertyStore.load("user01", None, self._txn, 2)
+
+ pname = propertyName("dummy1")
+ pvalue = propertyValue("value1-user1")
+
+ yield store1_user1.__setitem__(pname, pvalue)
+ self.assertEqual(store1_user1[pname], pvalue)
+
+ yield store1_user1.__delitem__(pname)
+ self.assertTrue(pname not in store1_user1)
+
+ yield store1_user1.__setitem__(pname, pvalue)
+ self.assertEqual(store1_user1[pname], pvalue)
+
if PropertyStore is None:
PropertyStoreTest.skip = importErrorMessage
-
-
Deleted: CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/caldav/resource.py
===================================================================
--- CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/caldav/resource.py 2013-04-17 23:02:28 UTC (rev 11056)
+++ CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/caldav/resource.py 2013-04-18 18:21:54 UTC (rev 11057)
@@ -1,516 +0,0 @@
-# -*- test-case-name: twistedcaldav.directory.test.test_calendar -*-
-##
-# Copyright (c) 2005-2013 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.
-##
-
-"""
-CalDAV scheduling resources.
-"""
-
-__all__ = [
- "ScheduleInboxResource",
- "ScheduleOutboxResource",
- "deliverSchedulePrivilegeSet",
-]
-
-
-from twext.web2 import responsecode
-from txdav.xml import element as davxml
-from txdav.xml.rfc2518 import HRef
-from twext.web2.dav.http import ErrorResponse, MultiStatusResponse
-from twext.web2.dav.resource import davPrivilegeSet
-from twext.web2.dav.util import joinURL, normalizeURL
-from twext.web2.http import HTTPError
-
-from twisted.internet.defer import inlineCallbacks, returnValue, succeed
-
-from twistedcaldav import caldavxml, customxml
-from twistedcaldav.caldavxml import caldav_namespace, Opaque, \
- CalendarFreeBusySet, ScheduleCalendarTransp
-from twistedcaldav.config import config
-# _schedulePrivilegeSet implicitly depends on config being initialized. The
-# following line is wrong because _schedulePrivilegeSet won't actually use the
-# config file, it will pick up stdconfig whenever it is imported, so this works
-# around that for now.
-__import__("twistedcaldav.stdconfig") # FIXME
-from twistedcaldav.customxml import calendarserver_namespace
-from twistedcaldav.ical import allowedComponents
-from twistedcaldav.resource import CalDAVResource
-from twistedcaldav.resource import isCalendarCollectionResource
-from txdav.caldav.datastore.scheduling.caldav.scheduler import CalDAVScheduler
-
-from txdav.base.propertystore.base import PropertyName
-
-def _schedulePrivilegeSet(deliver):
- edited = False
-
- top_supported_privileges = []
-
- for supported_privilege in davPrivilegeSet.childrenOfType(davxml.SupportedPrivilege):
- all_privilege = supported_privilege.childOfType(davxml.Privilege)
- if isinstance(all_privilege.children[0], davxml.All):
- all_description = supported_privilege.childOfType(davxml.Description)
- all_supported_privileges = list(supported_privilege.childrenOfType(davxml.SupportedPrivilege))
- all_supported_privileges.append(
- davxml.SupportedPrivilege(
- davxml.Privilege(caldavxml.ScheduleDeliver() if deliver else caldavxml.ScheduleSend()),
- davxml.Description("schedule privileges for current principal", **{"xml:lang": "en"}),
- ),
- )
- if config.Scheduling.CalDAV.OldDraftCompatibility:
- all_supported_privileges.append(
- davxml.SupportedPrivilege(
- davxml.Privilege(caldavxml.Schedule()),
- davxml.Description("old-style schedule privileges for current principal", **{"xml:lang": "en"}),
- ),
- )
- top_supported_privileges.append(
- davxml.SupportedPrivilege(all_privilege, all_description, *all_supported_privileges)
- )
- edited = True
- else:
- top_supported_privileges.append(supported_privilege)
-
- assert edited, "Structure of davPrivilegeSet changed in a way that I don't know how to extend for schedulePrivilegeSet"
-
- return davxml.SupportedPrivilegeSet(*top_supported_privileges)
-
-deliverSchedulePrivilegeSet = _schedulePrivilegeSet(True)
-sendSchedulePrivilegeSet = _schedulePrivilegeSet(False)
-
-class CalendarSchedulingCollectionResource (CalDAVResource):
- """
- CalDAV principal resource.
-
- Extends L{DAVResource} to provide CalDAV scheduling collection
- functionality.
- """
- def __init__(self, parent):
- """
- @param parent: the parent resource of this one.
- """
- assert parent is not None
-
- super(CalendarSchedulingCollectionResource, self).__init__(principalCollections=parent.principalCollections())
-
- self.parent = parent
-
-
- def isCollection(self):
- return True
-
-
- def isCalendarCollection(self):
- return False
-
-
- def isPseudoCalendarCollection(self):
- return True
-
-
- def supportedReports(self):
- result = super(CalDAVResource, self).supportedReports()
- result.append(davxml.Report(caldavxml.CalendarQuery(),))
- result.append(davxml.Report(caldavxml.CalendarMultiGet(),))
- # free-busy report not allowed
- if config.EnableSyncReport:
- # Only allowed on calendar/inbox/addressbook collections
- result.append(davxml.Report(davxml.SyncCollection(),))
- return result
-
-
-
-class ScheduleInboxResource (CalendarSchedulingCollectionResource):
- """
- CalDAV schedule Inbox resource.
-
- Extends L{DAVResource} to provide CalDAV functionality.
- """
-
- def liveProperties(self):
-
- return super(ScheduleInboxResource, self).liveProperties() + (
- caldavxml.CalendarFreeBusySet.qname(),
- caldavxml.ScheduleDefaultCalendarURL.qname(),
- customxml.ScheduleDefaultTasksURL.qname(),
- )
-
-
- def resourceType(self):
- return davxml.ResourceType.scheduleInbox
-
-
- @inlineCallbacks
- def readProperty(self, property, request):
- if type(property) is tuple:
- qname = property
- else:
- qname = property.qname()
-
- if qname == caldavxml.CalendarFreeBusySet.qname():
- # Always return at least an empty list
- if not self.hasDeadProperty(property):
- top = self.parent.url()
- values = []
- for cal in (yield self.parent._newStoreHome.calendars()):
- prop = cal.properties().get(PropertyName.fromString(ScheduleCalendarTransp.sname()))
- if prop == ScheduleCalendarTransp(Opaque()):
- values.append(HRef(joinURL(top, cal.name())))
- returnValue(CalendarFreeBusySet(*values))
- elif qname in (caldavxml.ScheduleDefaultCalendarURL.qname(), customxml.ScheduleDefaultTasksURL.qname()):
- result = (yield self.readDefaultCalendarProperty(request, qname))
- returnValue(result)
-
- result = (yield super(ScheduleInboxResource, self).readProperty(property, request))
- returnValue(result)
-
-
- @inlineCallbacks
- def writeProperty(self, property, request):
- assert isinstance(property, davxml.WebDAVElement)
-
- # Strictly speaking CS:calendar-availability is a live property in the sense that the
- # server enforces what can be stored, however it need not actually
- # exist so we cannot list it in liveProperties on this resource, since its
- # its presence there means that hasProperty will always return True for it.
- if property.qname() == customxml.CalendarAvailability.qname():
- if not property.valid():
- raise HTTPError(ErrorResponse(
- responsecode.CONFLICT,
- (caldav_namespace, "valid-calendar-data"),
- description="Invalid property"
- ))
-
- elif property.qname() == caldavxml.CalendarFreeBusySet.qname():
- # Verify that the calendars added in the PROPPATCH are valid. We do not check
- # whether existing items in the property are still valid - only new ones.
- property.children = [davxml.HRef(normalizeURL(str(href))) for href in property.children]
- new_calendars = set([str(href) for href in property.children])
- if not self.hasDeadProperty(property):
- old_calendars = set()
- else:
- old_calendars = set([normalizeURL(str(href)) for href in self.readDeadProperty(property).children])
- added_calendars = new_calendars.difference(old_calendars)
- for href in added_calendars:
- cal = (yield request.locateResource(str(href)))
- if cal is None or not cal.exists() or not isCalendarCollectionResource(cal):
- # Validate that href's point to a valid calendar.
- raise HTTPError(ErrorResponse(
- responsecode.CONFLICT,
- (caldav_namespace, "valid-calendar-url"),
- "Invalid URI",
- ))
- for href in tuple(new_calendars):
- cal = (yield request.locateResource(str(href)))
- if cal is None or not cal.exists() or not isCalendarCollectionResource(cal):
- new_calendars.remove(href)
- property.children = [davxml.HRef(href) for href in new_calendars]
-
- elif property.qname() in (caldavxml.ScheduleDefaultCalendarURL.qname(), customxml.ScheduleDefaultTasksURL.qname()):
- property = (yield self.writeDefaultCalendarProperty(request, property))
-
- yield super(ScheduleInboxResource, self).writeProperty(property, request)
-
-
- def processFreeBusyCalendar(self, uri, addit):
- uri = normalizeURL(uri)
-
- if not self.hasDeadProperty(caldavxml.CalendarFreeBusySet.qname()):
- fbset = set()
- else:
- fbset = set([normalizeURL(str(href)) for href in self.readDeadProperty(caldavxml.CalendarFreeBusySet.qname()).children])
- if addit:
- if uri not in fbset:
- fbset.add(uri)
- self.writeDeadProperty(caldavxml.CalendarFreeBusySet(*[davxml.HRef(url) for url in fbset]))
- else:
- if uri in fbset:
- fbset.remove(uri)
- self.writeDeadProperty(caldavxml.CalendarFreeBusySet(*[davxml.HRef(url) for url in fbset]))
-
-
- @inlineCallbacks
- def readDefaultCalendarProperty(self, request, qname):
- """
- Read either the default VEVENT or VTODO calendar property. Try to pick one if not present.
- """
-
- tasks = qname == customxml.ScheduleDefaultTasksURL.qname()
-
- # Must have a valid default
- try:
- defaultCalendarProperty = self.readDeadProperty(qname)
- except HTTPError:
- defaultCalendarProperty = None
- if defaultCalendarProperty and len(defaultCalendarProperty.children) == 1:
- defaultCalendar = str(defaultCalendarProperty.children[0])
- cal = (yield request.locateResource(str(defaultCalendar)))
- if cal is not None and isCalendarCollectionResource(cal) and cal.exists() and not cal.isShareeCollection():
- returnValue(defaultCalendarProperty)
-
- # Default is not valid - we have to try to pick one
- defaultCalendarProperty = (yield self.pickNewDefaultCalendar(request, tasks=tasks))
- returnValue(defaultCalendarProperty)
-
-
- @inlineCallbacks
- def writeDefaultCalendarProperty(self, request, property):
- """
- Write either the default VEVENT or VTODO calendar property, validating and canonicalizing the value
- """
- tasks = property.qname() == customxml.ScheduleDefaultTasksURL
- componentType = "VTODO" if tasks else "VEVENT"
- prop_to_set = customxml.ScheduleDefaultTasksURL if tasks else caldavxml.ScheduleDefaultCalendarURL
- error_element = (calendarserver_namespace, "valid-schedule-default-tasks-URL") if tasks else (caldav_namespace, "valid-schedule-default-calendar-URL")
-
- # Verify that the calendar added in the PROPPATCH is valid.
- property.children = [davxml.HRef(normalizeURL(str(href))) for href in property.children]
- new_calendar = [str(href) for href in property.children]
- cal = None
- if len(new_calendar) == 1:
- calURI = str(new_calendar[0])
- cal = (yield request.locateResource(str(new_calendar[0])))
-
- # TODO: check that owner of the new calendar is the same as owner of this inbox
- if cal is None or not cal.exists() or not isCalendarCollectionResource(cal) or \
- cal.isShareeCollection() or not cal.isSupportedComponent(componentType):
- # Validate that href's point to a valid calendar.
- raise HTTPError(ErrorResponse(
- responsecode.CONFLICT,
- error_element,
- "Invalid URI",
- ))
- else:
- # Canonicalize the URL to __uids__ form
- calURI = (yield cal.canonicalURL(request))
- property = prop_to_set(davxml.HRef(calURI))
- returnValue(property)
-
-
- @inlineCallbacks
- def pickNewDefaultCalendar(self, request, tasks=False):
- """
- First see if default provisioned calendar exists in the calendar home and pick that. Otherwise
- pick another from the calendar home.
- """
-
- componentType = "VTODO" if tasks else "VEVENT"
- test_name = "tasks" if tasks else "calendar"
- prop_to_set = customxml.ScheduleDefaultTasksURL if tasks else caldavxml.ScheduleDefaultCalendarURL
-
- calendarHomeURL = self.parent.url()
- defaultCalendarURL = joinURL(calendarHomeURL, test_name)
- defaultCalendar = (yield request.locateResource(defaultCalendarURL))
- if defaultCalendar is None or not defaultCalendar.exists():
- # Really, the dead property shouldn't be necessary, and this should
- # be entirely computed by a back-end method like 'defaultCalendar()'
-
- @inlineCallbacks
- def _findDefault():
- for calendarName in (yield self.parent._newStoreHome.listCalendars()): # These are only unshared children
- if calendarName == "inbox":
- continue
- calendar = (yield self.parent._newStoreHome.calendarWithName(calendarName))
- if not calendar.owned():
- continue
- if not calendar.isSupportedComponent(componentType):
- continue
- break
- else:
- calendarName = None
- returnValue(calendarName)
-
- foundName = yield _findDefault()
- if foundName is None:
- # Create a default and try and get its name again
- yield self.parent._newStoreHome.ensureDefaultCalendarsExist()
- foundName = yield _findDefault()
- if foundName is None:
- # Failed to even create a default - bad news...
- raise RuntimeError("No valid calendars to use as a default %s calendar." % (componentType,))
-
- defaultCalendarURL = joinURL(calendarHomeURL, foundName)
-
- prop = prop_to_set(davxml.HRef(defaultCalendarURL))
- self.writeDeadProperty(prop)
- returnValue(prop)
-
-
- @inlineCallbacks
- def defaultCalendar(self, request, componentType):
- """
- Find the default calendar for the supplied iCalendar component type. If one does
- not exist, automatically provision it.
- """
-
- # Check any default calendar property first - this will create if none exists
- default = (yield self.readProperty(caldavxml.ScheduleDefaultCalendarURL.qname(), request))
- if len(default.children) == 1:
- defaultURL = str(default.children[0])
- default = (yield request.locateResource(defaultURL))
- else:
- default = None
-
- # Check that default handles the component type
- if default is not None:
- if not default.isSupportedComponent(componentType):
- default = None
-
- # Must have a default - provision one if not
- if default is None:
-
- # Try to find a calendar supporting the required component type. If there are multiple, pick
- # the one with the oldest created timestamp as that will likely be the initial provision.
- for calendarName in (yield self.parent._newStoreHome.listCalendars()): # These are only unshared children
- if calendarName == "inbox":
- continue
- calendar = (yield self.parent._newStoreHome.calendarWithName(calendarName))
- if not calendar.isSupportedComponent(componentType):
- continue
- if default is None or calendar.created() < default.created():
- default = calendar
-
- # If none can be found, provision one
- if default is None:
- new_name = "%ss" % (componentType.lower()[1:],)
- default = yield self.parent._newStoreHome.createCalendarWithName(new_name)
- yield default.setSupportedComponents(componentType.upper())
-
- # Need L{DAVResource} object to return not new store object
- default = (yield request.locateResource(joinURL(self.parent.url(), default.name())))
-
- returnValue(default)
-
-
- @inlineCallbacks
- def isDefaultCalendar(self, request, calendar):
- """
- Is the supplied calendar one of the possible default calendars.
- """
- assert calendar.isCalendarCollection()
-
- # Not allowed to delete the default calendar
- for default_prop in (caldavxml.ScheduleDefaultCalendarURL, customxml.ScheduleDefaultTasksURL,):
- default = (yield self.readProperty(default_prop.qname(), request))
- if default and len(default.children) == 1:
- defaultURL = normalizeURL(str(default.children[0]))
- myURL = (yield calendar.canonicalURL(request))
- if defaultURL == myURL:
- returnValue(default_prop)
-
- returnValue(None)
-
-
- ##
- # ACL
- ##
-
- def supportedPrivileges(self, request):
- return succeed(deliverSchedulePrivilegeSet)
-
-
- def defaultAccessControlList(self):
-
- privs = (
- davxml.Privilege(caldavxml.ScheduleDeliver()),
- )
- if config.Scheduling.CalDAV.OldDraftCompatibility:
- privs += (davxml.Privilege(caldavxml.Schedule()),)
-
- return davxml.ACL(
- # CalDAV:schedule-deliver for any authenticated user
- davxml.ACE(
- davxml.Principal(davxml.Authenticated()),
- davxml.Grant(*privs),
- ),
- )
-
-
-
-class ScheduleOutboxResource (CalendarSchedulingCollectionResource):
- """
- CalDAV schedule Outbox resource.
-
- Extends L{DAVResource} to provide CalDAV functionality.
- """
-
- def resourceType(self):
- return davxml.ResourceType.scheduleOutbox
-
-
- def getSupportedComponentSet(self):
- return caldavxml.SupportedCalendarComponentSet(
- *[caldavxml.CalendarComponent(name=item) for item in allowedComponents]
- )
-
-
- @inlineCallbacks
- def http_POST(self, request):
- """
- The CalDAV POST method.
-
- This uses a generator function yielding either L{waitForDeferred} objects or L{Response} objects.
- This allows for code that follows a 'linear' execution pattern rather than having to use nested
- L{Deferred} callbacks. The logic is easier to follow this way plus we don't run into deep nesting
- issues which the other approach would have with large numbers of recipients.
- """
- # Check authentication and access controls
- yield self.authorize(request, (caldavxml.ScheduleSend(),))
-
- # This is a local CALDAV scheduling operation.
- scheduler = CalDAVScheduler(request, self)
-
- # Do the POST processing treating
- result = (yield scheduler.doSchedulingViaPOST())
- returnValue(result.response())
-
-
- ##
- # ACL
- ##
-
- def supportedPrivileges(self, request):
- return succeed(sendSchedulePrivilegeSet)
-
-
- def defaultAccessControlList(self):
- if config.EnableProxyPrincipals:
- myPrincipal = self.parent.principalForRecord()
-
- privs = (
- davxml.Privilege(caldavxml.ScheduleSend()),
- )
- if config.Scheduling.CalDAV.OldDraftCompatibility:
- privs += (davxml.Privilege(caldavxml.Schedule()),)
-
- return davxml.ACL(
- # CalDAV:schedule for associated write proxies
- davxml.ACE(
- davxml.Principal(davxml.HRef(joinURL(myPrincipal.principalURL(), "calendar-proxy-write"))),
- davxml.Grant(*privs),
- davxml.Protected(),
- ),
- )
- else:
- return super(ScheduleOutboxResource, self).defaultAccessControlList()
-
-
- def report_urn_ietf_params_xml_ns_caldav_calendar_query(self, request, calendar_query):
- return succeed(MultiStatusResponse(()))
-
-
- def report_urn_ietf_params_xml_ns_caldav_calendar_multiget(self, request, multiget):
- responses = [davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.NOT_FOUND)) for href in multiget.resources]
- return succeed(MultiStatusResponse((responses)))
Modified: CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/caldav/scheduler.py
===================================================================
--- CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/caldav/scheduler.py 2013-04-17 23:02:28 UTC (rev 11056)
+++ CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/caldav/scheduler.py 2013-04-18 18:21:54 UTC (rev 11057)
@@ -66,12 +66,12 @@
self.doingPOST = False
- def doSchedulingViaPOST(self, transaction):
+ def doSchedulingViaPOST(self):
"""
The Scheduling POST operation on an Outbox.
"""
self.doingPOST = True
- return super(CalDAVScheduler, self).doSchedulingViaPOST(transaction)
+ return super(CalDAVScheduler, self).doSchedulingViaPOST()
def checkAuthorization(self):
@@ -127,9 +127,9 @@
else:
# Map recipient to their inbox
inbox = None
- if principal.thisServer():
+ if principal.calendarsEnabled() and principal.thisServer():
if principal.locallyHosted():
- recipient_home = yield self.txn.calendarHomeWithUID(principal.uid)
+ recipient_home = yield self.txn.calendarHomeWithUID(principal.uid, create=True)
if recipient_home:
inbox = (yield recipient_home.calendarWithName("inbox"))
else:
Deleted: CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/caldav/test/test_resource.py
===================================================================
--- CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/caldav/test/test_resource.py 2013-04-17 23:02:28 UTC (rev 11056)
+++ CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/caldav/test/test_resource.py 2013-04-18 18:21:54 UTC (rev 11057)
@@ -1,457 +0,0 @@
-##
-# Copyright (c) 2005-2013 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 twext.web2 import responsecode, http_headers
-from twext.web2.dav.util import davXMLFromStream
-from twext.web2.http import HTTPError
-from twext.web2.iweb import IResponse
-from twext.web2.stream import MemoryStream
-from twext.web2.test.test_server import SimpleRequest
-from twisted.internet.defer import inlineCallbacks
-from twistedcaldav import caldavxml, customxml
-from twistedcaldav.test.util import HomeTestCase, TestCase
-from txdav.xml import element as davxml
-
-class Properties (HomeTestCase):
- """
- CalDAV properties
- """
- def test_free_busy_set_prop(self):
- """
- Test for PROPFIND on Inbox with missing calendar-free-busy-set property.
- """
-
- inbox_uri = "/inbox/"
-
- def propfind_cb(response):
- response = IResponse(response)
-
- if response.code != responsecode.MULTI_STATUS:
- self.fail("Incorrect response to PROPFIND: %s" % (response.code,))
-
- def got_xml(doc):
- if not isinstance(doc.root_element, davxml.MultiStatus):
- self.fail("PROPFIND response XML root element is not multistatus: %r" % (doc.root_element,))
-
- response = doc.root_element.childOfType(davxml.Response)
- href = response.childOfType(davxml.HRef)
- self.failUnless(str(href) == inbox_uri)
-
- for propstat in response.childrenOfType(davxml.PropertyStatus):
- status = propstat.childOfType(davxml.Status)
- if status.code != responsecode.OK:
- self.fail("Unable to read requested properties (%s): %r"
- % (status, propstat.childOfType(davxml.PropertyContainer).toxml()))
-
- container = propstat.childOfType(davxml.PropertyContainer)
-
- #
- # Check CalDAV:calendar-free-busy-set
- #
-
- free_busy_set = container.childOfType(caldavxml.CalendarFreeBusySet)
- if not free_busy_set:
- self.fail("Expected CalDAV:calendar-free-busy-set element; but got none.")
-
- if not free_busy_set.children:
- self.fail("Expected non-empty CalDAV:calendar-free-busy-set element.")
-
- return davXMLFromStream(response.stream).addCallback(got_xml)
-
- query = davxml.PropertyFind(
- davxml.PropertyContainer(
- caldavxml.CalendarFreeBusySet(),
- ),
- )
-
- request = SimpleRequest(
- self.site,
- "PROPFIND",
- inbox_uri,
- headers=http_headers.Headers({"Depth": "0"}),
- )
- request.stream = MemoryStream(query.toxml())
- return self.send(request, propfind_cb)
-
-
- @inlineCallbacks
- def test_free_busy_set_remove_broken(self):
- """
- ???
- """
-
- request = SimpleRequest(self.site, "GET", "/inbox/")
- inbox = yield request.locateResource("/inbox/")
- self.assertTrue(inbox.hasDeadProperty(caldavxml.CalendarFreeBusySet))
- oldfbset = set(("/calendar",))
- oldset = caldavxml.CalendarFreeBusySet(*[davxml.HRef(url) for url in oldfbset])
-
- newfbset = set()
- newfbset.update(oldfbset)
- newfbset.add("/calendar-broken")
- newset = caldavxml.CalendarFreeBusySet(*[davxml.HRef(url) for url in newfbset])
-
- inbox.writeDeadProperty(newset)
- changedset = inbox.readDeadProperty(caldavxml.CalendarFreeBusySet)
- self.assertEqual(tuple(changedset.children), tuple(newset.children))
-
- yield inbox.writeProperty(newset, request)
-
- changedset = inbox.readDeadProperty(caldavxml.CalendarFreeBusySet)
- self.assertEqual(tuple(changedset.children), tuple(oldset.children))
-
-
- @inlineCallbacks
- def test_free_busy_set_strip_slash(self):
- """
- ???
- """
-
- request = SimpleRequest(self.site, "GET", "/inbox/")
- inbox = yield request.locateResource("/inbox/")
- self.assertTrue(inbox.hasDeadProperty(caldavxml.CalendarFreeBusySet))
-
- oldfbset = set(("/calendar/",))
- oldset = caldavxml.CalendarFreeBusySet(*[davxml.HRef(url) for url in oldfbset])
- inbox.writeDeadProperty(oldset)
-
- writefbset = set(("/calendar/",))
- writeset = caldavxml.CalendarFreeBusySet(*[davxml.HRef(url) for url in writefbset])
- yield inbox.writeProperty(writeset, request)
-
- correctfbset = set(("/calendar",))
- correctset = caldavxml.CalendarFreeBusySet(*[davxml.HRef(url) for url in correctfbset])
- changedset = inbox.readDeadProperty(caldavxml.CalendarFreeBusySet)
- self.assertEqual(tuple(changedset.children), tuple(correctset.children))
-
-
- @inlineCallbacks
- def test_free_busy_set_strip_slash_remove(self):
- """
- ???
- """
-
- request = SimpleRequest(self.site, "GET", "/inbox/")
- inbox = yield request.locateResource("/inbox/")
- self.assertTrue(inbox.hasDeadProperty(caldavxml.CalendarFreeBusySet))
-
- oldfbset = set(("/calendar/", "/broken/"))
- oldset = caldavxml.CalendarFreeBusySet(*[davxml.HRef(url) for url in oldfbset])
- inbox.writeDeadProperty(oldset)
-
- writefbset = set(("/calendar/", "/broken/"))
- writeset = caldavxml.CalendarFreeBusySet(*[davxml.HRef(url) for url in writefbset])
- yield inbox.writeProperty(writeset, request)
-
- correctfbset = set(("/calendar",))
- correctset = caldavxml.CalendarFreeBusySet(*[davxml.HRef(url) for url in correctfbset])
- changedset = inbox.readDeadProperty(caldavxml.CalendarFreeBusySet)
- self.assertEqual(tuple(changedset.children), tuple(correctset.children))
-
-
-
-class DefaultCalendar (TestCase):
-
- def setUp(self):
- super(DefaultCalendar, self).setUp()
- self.createStockDirectoryService()
- self.setupCalendars()
-
-
- @inlineCallbacks
- def test_pick_default_vevent_calendar(self):
- """
- Test that pickNewDefaultCalendar will choose the correct calendar.
- """
-
- request = SimpleRequest(self.site, "GET", "/calendars/users/wsanchez/")
- inbox = yield request.locateResource("/calendars/users/wsanchez/inbox")
-
- # default property initially not present
- try:
- inbox.readDeadProperty(caldavxml.ScheduleDefaultCalendarURL)
- except HTTPError:
- pass
- else:
- self.fail("caldavxml.ScheduleDefaultCalendarURL is not empty")
-
- yield inbox.pickNewDefaultCalendar(request)
-
- try:
- default = inbox.readDeadProperty(caldavxml.ScheduleDefaultCalendarURL)
- except HTTPError:
- self.fail("caldavxml.ScheduleDefaultCalendarURL is not present")
- else:
- self.assertEqual(str(default.children[0]), "/calendars/__uids__/6423F94A-6B76-4A3A-815B-D52CFD77935D/calendar")
-
- request._newStoreTransaction.abort()
-
-
- @inlineCallbacks
- def test_pick_default_vtodo_calendar(self):
- """
- Test that pickNewDefaultCalendar will choose the correct tasks calendar.
- """
-
- request = SimpleRequest(self.site, "GET", "/calendars/users/wsanchez/")
- inbox = yield request.locateResource("/calendars/users/wsanchez/inbox")
-
- # default property initially not present
- try:
- inbox.readDeadProperty(customxml.ScheduleDefaultTasksURL)
- except HTTPError:
- pass
- else:
- self.fail("customxml.ScheduleDefaultTasksURL is not empty")
-
- yield inbox.pickNewDefaultCalendar(request, tasks=True)
-
- try:
- default = inbox.readDeadProperty(customxml.ScheduleDefaultTasksURL)
- except HTTPError:
- self.fail("customxml.ScheduleDefaultTasksURL is not present")
- else:
- self.assertEqual(str(default.children[0]), "/calendars/__uids__/6423F94A-6B76-4A3A-815B-D52CFD77935D/tasks")
-
- request._newStoreTransaction.abort()
-
-
- @inlineCallbacks
- def test_missing_default_vevent_calendar(self):
- """
- Test that pickNewDefaultCalendar will create a missing default calendar.
- """
-
- request = SimpleRequest(self.site, "GET", "/calendars/users/wsanchez/")
- home = yield request.locateResource("/calendars/users/wsanchez/")
- inbox = yield request.locateResource("/calendars/users/wsanchez/inbox")
-
- # default property initially not present
- try:
- inbox.readDeadProperty(caldavxml.ScheduleDefaultCalendarURL)
- except HTTPError:
- pass
- else:
- self.fail("caldavxml.ScheduleDefaultCalendarURL is not empty")
-
- # Forcibly remove the one we need
- yield home._newStoreHome.removeChildWithName("calendar")
- names = [calendarName for calendarName in (yield home._newStoreHome.listCalendars())]
- self.assertTrue("calendar" not in names)
-
- yield inbox.pickNewDefaultCalendar(request)
-
- try:
- default = inbox.readDeadProperty(caldavxml.ScheduleDefaultCalendarURL)
- except HTTPError:
- self.fail("caldavxml.ScheduleDefaultCalendarURL is not present")
- else:
- self.assertEqual(str(default.children[0]), "/calendars/__uids__/6423F94A-6B76-4A3A-815B-D52CFD77935D/calendar")
-
- request._newStoreTransaction.abort()
-
-
- @inlineCallbacks
- def test_missing_default_vtodo_calendar(self):
- """
- Test that pickNewDefaultCalendar will create a missing default tasks calendar.
- """
-
- request = SimpleRequest(self.site, "GET", "/calendars/users/wsanchez/")
- home = yield request.locateResource("/calendars/users/wsanchez/")
- inbox = yield request.locateResource("/calendars/users/wsanchez/inbox")
-
- # default property initially not present
- try:
- inbox.readDeadProperty(customxml.ScheduleDefaultTasksURL)
- except HTTPError:
- pass
- else:
- self.fail("caldavxml.ScheduleDefaultTasksURL is not empty")
-
- # Forcibly remove the one we need
- yield home._newStoreHome.removeChildWithName("tasks")
- names = [calendarName for calendarName in (yield home._newStoreHome.listCalendars())]
- self.assertTrue("tasks" not in names)
-
- yield inbox.pickNewDefaultCalendar(request, tasks=True)
-
- try:
- default = inbox.readDeadProperty(customxml.ScheduleDefaultTasksURL)
- except HTTPError:
- self.fail("caldavxml.ScheduleDefaultTasksURL is not present")
- else:
- self.assertEqual(str(default.children[0]), "/calendars/__uids__/6423F94A-6B76-4A3A-815B-D52CFD77935D/tasks")
-
- request._newStoreTransaction.abort()
-
-
- @inlineCallbacks
- def test_pick_default_other(self):
- """
- Make calendar
- """
-
- request = SimpleRequest(self.site, "GET", "/calendars/users/wsanchez/")
- inbox = yield request.locateResource("/calendars/users/wsanchez/inbox")
-
- # default property not present
- try:
- inbox.readDeadProperty(caldavxml.ScheduleDefaultCalendarURL)
- except HTTPError:
- pass
- else:
- self.fail("caldavxml.ScheduleDefaultCalendarURL is not empty")
-
- # Create a new default calendar
- newcalendar = yield request.locateResource("/calendars/users/wsanchez/newcalendar")
- yield newcalendar.createCalendarCollection()
- inbox.writeDeadProperty(caldavxml.ScheduleDefaultCalendarURL(
- davxml.HRef("/calendars/__uids__/6423F94A-6B76-4A3A-815B-D52CFD77935D/newcalendar")
- ))
-
- # Delete the normal calendar
- calendar = yield request.locateResource("/calendars/users/wsanchez/calendar")
- yield calendar.storeRemove(request, False, "/calendars/users/wsanchez/calendar")
-
- inbox.removeDeadProperty(caldavxml.ScheduleDefaultCalendarURL)
-
- # default property not present
- try:
- inbox.readDeadProperty(caldavxml.ScheduleDefaultCalendarURL)
- except HTTPError:
- pass
- else:
- self.fail("caldavxml.ScheduleDefaultCalendarURL is not empty")
- request._newStoreTransaction.commit()
-
- request = SimpleRequest(self.site, "GET", "/calendars/users/wsanchez/")
- inbox = yield request.locateResource("/calendars/users/wsanchez/inbox")
- yield inbox.pickNewDefaultCalendar(request)
-
- try:
- default = inbox.readDeadProperty(caldavxml.ScheduleDefaultCalendarURL)
- except HTTPError:
- self.fail("caldavxml.ScheduleDefaultCalendarURL is not present")
- else:
- self.assertEqual(str(default.children[0]), "/calendars/__uids__/6423F94A-6B76-4A3A-815B-D52CFD77935D/newcalendar")
-
- request._newStoreTransaction.abort()
-
-
- @inlineCallbacks
- def test_fix_shared_default(self):
- """
- Make calendar
- """
-
- request = SimpleRequest(self.site, "GET", "/calendars/users/wsanchez/")
- inbox = yield request.locateResource("/calendars/users/wsanchez/inbox")
-
- # Create a new default calendar
- newcalendar = yield request.locateResource("/calendars/__uids__/6423F94A-6B76-4A3A-815B-D52CFD77935D/newcalendar")
- yield newcalendar.createCalendarCollection()
- inbox.writeDeadProperty(caldavxml.ScheduleDefaultCalendarURL(
- davxml.HRef("/calendars/__uids__/6423F94A-6B76-4A3A-815B-D52CFD77935D/newcalendar")
- ))
- try:
- default = yield inbox.readProperty(caldavxml.ScheduleDefaultCalendarURL, request)
- except HTTPError:
- self.fail("caldavxml.ScheduleDefaultCalendarURL is not present")
- else:
- self.assertEqual(str(default.children[0]), "/calendars/__uids__/6423F94A-6B76-4A3A-815B-D52CFD77935D/newcalendar")
-
- # Force the new calendar to think it is a virtual share
- newcalendar._isShareeCollection = True
-
- try:
- default = yield inbox.readProperty(caldavxml.ScheduleDefaultCalendarURL, request)
- except HTTPError:
- self.fail("caldavxml.ScheduleDefaultCalendarURL is not present")
- else:
- self.assertEqual(str(default.children[0]), "/calendars/__uids__/6423F94A-6B76-4A3A-815B-D52CFD77935D/calendar")
-
- request._newStoreTransaction.abort()
-
-
- @inlineCallbacks
- def test_set_default_vevent_other(self):
- """
- Test that the default URL can be set to another VEVENT calendar
- """
-
- request = SimpleRequest(self.site, "GET", "/calendars/users/wsanchez/")
- inbox = yield request.locateResource("/calendars/users/wsanchez/inbox")
-
- # default property not present
- try:
- inbox.readDeadProperty(caldavxml.ScheduleDefaultCalendarURL)
- except HTTPError:
- pass
- else:
- self.fail("caldavxml.ScheduleDefaultCalendarURL is not empty")
-
- # Create a new default calendar
- newcalendar = yield request.locateResource("/calendars/users/wsanchez/newcalendar")
- yield newcalendar.createCalendarCollection()
- yield newcalendar.setSupportedComponents(("VEVENT",))
- request._newStoreTransaction.commit()
-
- request = SimpleRequest(self.site, "GET", "/calendars/users/wsanchez/")
- inbox = yield request.locateResource("/calendars/users/wsanchez/inbox")
- yield inbox.writeProperty(caldavxml.ScheduleDefaultCalendarURL(davxml.HRef("/calendars/__uids__/6423F94A-6B76-4A3A-815B-D52CFD77935D/newcalendar")), request)
-
- try:
- default = inbox.readDeadProperty(caldavxml.ScheduleDefaultCalendarURL)
- except HTTPError:
- self.fail("caldavxml.ScheduleDefaultCalendarURL is not present")
- else:
- self.assertEqual(str(default.children[0]), "/calendars/__uids__/6423F94A-6B76-4A3A-815B-D52CFD77935D/newcalendar")
-
- request._newStoreTransaction.commit()
-
-
- @inlineCallbacks
- def test_is_default_calendar(self):
- """
- Test .isDefaultCalendar() returns the proper class or None.
- """
-
- # Create a new non-default calendar
- request = SimpleRequest(self.site, "GET", "/calendars/users/wsanchez/")
- newcalendar = yield request.locateResource("/calendars/users/wsanchez/newcalendar")
- yield newcalendar.createCalendarCollection()
- yield newcalendar.setSupportedComponents(("VEVENT",))
- inbox = yield request.locateResource("/calendars/users/wsanchez/inbox")
- yield inbox.pickNewDefaultCalendar(request)
- request._newStoreTransaction.commit()
-
- request = SimpleRequest(self.site, "GET", "/calendars/users/wsanchez/")
- inbox = yield request.locateResource("/calendars/users/wsanchez/inbox")
- calendar = yield request.locateResource("/calendars/users/wsanchez/calendar")
- newcalendar = yield request.locateResource("/calendars/users/wsanchez/newcalendar")
- tasks = yield request.locateResource("/calendars/users/wsanchez/tasks")
-
- result = yield inbox.isDefaultCalendar(request, calendar)
- self.assertEqual(result, caldavxml.ScheduleDefaultCalendarURL)
-
- result = yield inbox.isDefaultCalendar(request, newcalendar)
- self.assertEqual(result, None)
-
- result = yield inbox.isDefaultCalendar(request, tasks)
- self.assertEqual(result, customxml.ScheduleDefaultTasksURL)
-
- request._newStoreTransaction.commit()
Modified: CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/scheduler.py
===================================================================
--- CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/scheduler.py 2013-04-17 23:02:28 UTC (rev 11056)
+++ CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/scheduler.py 2013-04-18 18:21:54 UTC (rev 11057)
@@ -17,9 +17,10 @@
from twisted.internet.defer import inlineCallbacks, returnValue
from twisted.python.failure import Failure
+from twext.enterprise.locking import NamedLock
from twext.python.log import Logger, LoggingMixIn
from twext.web2 import responsecode
-from twext.web2.http import HTTPError, Response, StatusResponse
+from twext.web2.http import HTTPError, Response
from twext.web2.http_headers import MimeType
from txdav.xml import element as davxml
from twext.web2.dav.http import messageForFailure, statusForFailure, \
@@ -30,7 +31,6 @@
from twistedcaldav.accounting import accountingEnabled, emitAccounting
from twistedcaldav.config import config
from twistedcaldav.ical import Component
-from twistedcaldav.memcachelock import MemcacheLock, MemcacheLockTimeoutError
from txdav.caldav.datastore.scheduling import addressmapping
from txdav.caldav.datastore.scheduling.caldav.delivery import ScheduleViaCalDAV
from txdav.caldav.datastore.scheduling.cuaddress import InvalidCalendarUser, \
@@ -42,6 +42,7 @@
from txdav.caldav.datastore.scheduling.imip.delivery import ScheduleViaIMip
from txdav.caldav.datastore.scheduling.ischedule.delivery import ScheduleViaISchedule
from txdav.caldav.datastore.scheduling.itip import iTIPRequestStatus
+import hashlib
"""
CalDAV/Server-to-Server scheduling behavior.
@@ -153,49 +154,25 @@
@inlineCallbacks
- def doSchedulingViaPOST(self, use_request_headers=False):
+ def doSchedulingViaPOST(self, originator, recipients, calendar):
"""
The Scheduling POST operation on an Outbox.
"""
- self.method = "POST"
+ self.calendar = calendar
+ self.preProcessCalendarData()
- # Load various useful bits doing some basic checks on those
- yield self.loadCalendarFromRequest()
-
- if use_request_headers:
- self.loadFromRequestHeaders()
- else:
- yield self.loadFromRequestData()
-
if self.logItems is not None:
- self.logItems["recipients"] = len(self.recipients)
- self.logItems["cl"] = str(len(str(self.calendar)))
+ self.logItems["recipients"] = len(recipients)
+ self.logItems["cl"] = str(len(str(calendar)))
- # Do some extra authorization checks
- self.checkAuthorization()
-
# We might trigger an implicit scheduling operation here that will require consistency
# of data for all events with the same UID. So detect this and use a lock
- if self.calendar.resourceType() != "VFREEBUSY":
- uid = self.calendar.resourceUID()
- lock = MemcacheLock(
- "ImplicitUIDLock",
- uid,
- timeout=config.Scheduling.Options.UIDLockTimeoutSeconds,
- expire_time=config.Scheduling.Options.UIDLockExpirySeconds,
- )
+ if calendar.resourceType() != "VFREEBUSY":
+ uid = calendar.resourceUID()
+ yield NamedLock.acquire(self._txn, "ImplicitUIDLock:%s" % (hashlib.md5(uid).hexdigest(),))
- try:
- yield lock.acquire()
- except MemcacheLockTimeoutError:
- raise HTTPError(StatusResponse(responsecode.CONFLICT, "UID: %s currently in use on the server." % (uid,)))
- else:
- # Release lock after commit or abort
- self.txn.postCommit(lock.clean)
- self.txn.postAbort(lock.clean)
-
- result = (yield self.doScheduling())
+ result = (yield self.doSchedulingDirectly("POST", originator, recipients, calendar))
returnValue(result)
@@ -705,9 +682,9 @@
else:
# Map recipient to their inbox
inbox = None
- if principal.thisServer():
+ if principal.calendarsEnabled() and principal.thisServer():
if principal.locallyHosted():
- recipient_home = yield self.txn.calendarHomeWithUID(principal.uid)
+ recipient_home = yield self.txn.calendarHomeWithUID(principal.uid, create=True)
if recipient_home:
inbox = (yield recipient_home.calendarWithName("inbox"))
else:
Modified: CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/sql.py
===================================================================
--- CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/sql.py 2013-04-17 23:02:28 UTC (rev 11056)
+++ CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/sql.py 2013-04-18 18:21:54 UTC (rev 11057)
@@ -72,7 +72,7 @@
InvalidUIDError, UIDExistsError, ResourceDeletedError, \
AttendeeAllowedError, InvalidPerUserDataMerge, ComponentUpdateState, \
ValidOrganizerError, ShareeAllowedError, ComponentRemoveState, \
- InvalidComponentForStoreError, InvalidResourceMove
+ InvalidComponentForStoreError, InvalidResourceMove, InvalidDefaultCalendar
from txdav.caldav.icalendarstore import QuotaExceeded
from txdav.common.datastore.sql import CommonHome, CommonHomeChild, \
CommonObjectResource, ECALENDARTYPE
@@ -727,6 +727,39 @@
@inlineCallbacks
+ def setDefaultCalendar(self, calendar, tasks=False):
+ """
+ Set the default calendar for a particular type of component.
+
+ @param calendar: the calendar being set as the default
+ @type calendar: L{CalendarObject}
+ @param tasks: C{True} for VTODO, C{False} for VEVENT
+ @type componentType: C{bool}
+ """
+ chm = self._homeMetaDataSchema
+ componentType = "VTODO" if tasks else "VEVENT"
+ attribute_to_test = "_default_tasks" if tasks else "_default_events"
+ column_to_set = chm.DEFAULT_TASKS if tasks else chm.DEFAULT_EVENTS
+
+ # Check validity of the default
+ if calendar.isInbox():
+ raise InvalidDefaultCalendar("Cannot set inbox as a default calendar")
+ elif not calendar.owned():
+ raise InvalidDefaultCalendar("Cannot set shared calendar as a default calendar")
+ elif not calendar.isSupportedComponent(componentType):
+ raise InvalidDefaultCalendar("Cannot set default calendar to unsupported component type")
+ elif calendar.ownerHome().uid() != self.uid():
+ raise InvalidDefaultCalendar("Cannot set default calendar to someone else's calendar")
+
+ setattr(self, attribute_to_test, calendar._resourceID)
+ yield Update(
+ {column_to_set: calendar._resourceID},
+ Where=chm.RESOURCE_ID == self._resourceID,
+ ).on(self._txn)
+ yield self.invalidateQueryCache()
+
+
+ @inlineCallbacks
def defaultCalendar(self, componentType):
"""
Find the default calendar for the supplied iCalendar component type. If one does
@@ -741,10 +774,14 @@
else:
default = None
- # Check that default handles the component type
+ # Check that default meets requirements
if default is not None:
- if not default.isSupportedComponent(componentType):
+ if default.isInbox():
default = None
+ elif not default.owned():
+ default = None
+ elif not default.isSupportedComponent(componentType):
+ default = None
# Must have a default - provision one if not
if default is None:
@@ -755,9 +792,9 @@
calendar = (yield self.calendarWithName(calendarName))
if calendar.isInbox():
continue
- if not calendar.owned():
+ elif not calendar.owned():
continue
- if not calendar.isSupportedComponent(componentType):
+ elif not calendar.isSupportedComponent(componentType):
continue
if default is None or calendar.created() < default.created():
default = calendar
Modified: CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/test/common.py
===================================================================
--- CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/test/common.py 2013-04-17 23:02:28 UTC (rev 11056)
+++ CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/test/common.py 2013-04-18 18:21:54 UTC (rev 11057)
@@ -275,9 +275,10 @@
},
"home_defaults": {
"calendar_1": {
- "1.ics": (cal1NoSplitsRoot.child("1.ics").getContent(), metadata1),
- "3.ics": (cal1NoSplitsRoot.child("3.ics").getContent(), metadata3),
+ "1.ics": (cal1DefaultsRoot.child("1.ics").getContent(), metadata1),
+ "3.ics": (cal1DefaultsRoot.child("3.ics").getContent(), metadata3),
},
+ "inbox" : {},
},
}
md5s = {
Modified: CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/test/test_sql.py
===================================================================
--- CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/test/test_sql.py 2013-04-17 23:02:28 UTC (rev 11056)
+++ CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/test/test_sql.py 2013-04-18 18:21:54 UTC (rev 11057)
@@ -14,7 +14,8 @@
# limitations under the License.
##
from txdav.caldav.datastore.test.util import buildCalendarStore
-from txdav.caldav.icalendarstore import ComponentUpdateState
+from txdav.caldav.icalendarstore import ComponentUpdateState, \
+ InvalidDefaultCalendar
"""
Tests for txdav.caldav.datastore.postgres, mostly based on
@@ -1163,6 +1164,54 @@
@inlineCallbacks
+ def test_setDefaultCalendar(self):
+ """
+ Make sure a default_events calendar is assigned.
+ """
+
+ home = yield self.homeUnderTest(name="home_defaults")
+ calendar1 = yield home.calendarWithName("calendar_1")
+ yield calendar1.splitCollectionByComponentTypes()
+ yield self.commit()
+
+ home = yield self.homeUnderTest(name="home_defaults")
+ self.assertEqual(home._default_events, None)
+ self.assertEqual(home._default_tasks, None)
+ calendar1 = yield home.calendarWithName("calendar_1")
+ yield home.setDefaultCalendar(calendar1, False)
+ self.assertEqual(home._default_events, calendar1._resourceID)
+ self.assertEqual(home._default_tasks, None)
+ yield self.commit()
+
+ home = yield self.homeUnderTest(name="home_defaults")
+ calendar1 = yield home.calendarWithName("calendar_1")
+ calendar2 = yield home.calendarWithName("calendar_1-vtodo")
+ yield self.failUnlessFailure(home.setDefaultCalendar(calendar2, False), InvalidDefaultCalendar)
+ self.assertEqual(home._default_events, calendar1._resourceID)
+ self.assertEqual(home._default_tasks, None)
+ yield self.commit()
+
+ home = yield self.homeUnderTest(name="home_defaults")
+ calendar1 = yield home.calendarWithName("calendar_1")
+ calendar2 = yield home.calendarWithName("calendar_1-vtodo")
+ yield home.setDefaultCalendar(calendar2, True)
+ self.assertEqual(home._default_events, calendar1._resourceID)
+ self.assertEqual(home._default_tasks, calendar2._resourceID)
+ yield self.commit()
+
+ home = yield self.homeUnderTest(name="home_defaults")
+ calendar1 = yield home.calendarWithName("inbox")
+ yield self.failUnlessFailure(home.setDefaultCalendar(calendar1, False), InvalidDefaultCalendar)
+ yield self.commit()
+
+ home = yield self.homeUnderTest(name="home_defaults")
+ home_other = yield self.homeUnderTest(name="home_splits")
+ calendar1 = yield home_other.calendarWithName("calendar_1")
+ yield self.failUnlessFailure(home.setDefaultCalendar(calendar1, False), InvalidDefaultCalendar)
+ yield self.commit()
+
+
+ @inlineCallbacks
def test_resourceLock(self):
"""
Test CommonObjectResource.lock to make sure it locks, raises on missing resource,
Modified: CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/icalendarstore.py
===================================================================
--- CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/icalendarstore.py 2013-04-17 23:02:28 UTC (rev 11056)
+++ CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/icalendarstore.py 2013-04-18 18:21:54 UTC (rev 11057)
@@ -206,6 +206,13 @@
+class InvalidDefaultCalendar(CommonStoreError):
+ """
+ Setting a default calendar failed.
+ """
+
+
+
class AttachmentStoreFailed(Exception):
"""
Unable to store an attachment.
Modified: CalendarServer/branches/users/cdaboo/store-scheduling/txdav/common/datastore/test/util.py
===================================================================
--- CalendarServer/branches/users/cdaboo/store-scheduling/txdav/common/datastore/test/util.py 2013-04-17 23:02:28 UTC (rev 11056)
+++ CalendarServer/branches/users/cdaboo/store-scheduling/txdav/common/datastore/test/util.py 2013-04-18 18:21:54 UTC (rev 11057)
@@ -598,18 +598,18 @@
savedStore = None
assertProvides = assertProvides
- def transactionUnderTest(self):
+ def transactionUnderTest(self, txn=None):
"""
Create a transaction from C{storeUnderTest} and save it as
C{lastTransaction}. Also makes sure to use the same store, saving the
value from C{storeUnderTest}.
"""
if self.lastTransaction is None:
- self.lastTransaction = self.concurrentTransaction()
+ self.lastTransaction = self.concurrentTransaction(txn)
return self.lastTransaction
- def concurrentTransaction(self):
+ def concurrentTransaction(self, txn=None):
"""
Create a transaction from C{storeUnderTest} and save it for later
clean-up.
@@ -617,9 +617,12 @@
if self.savedStore is None:
self.savedStore = self.storeUnderTest()
self.counter += 1
- txn = self.savedStore.newTransaction(
- self.id() + " #" + str(self.counter)
- )
+ if txn is None:
+ txn = self.savedStore.newTransaction(
+ self.id() + " #" + str(self.counter)
+ )
+ else:
+ txn._label = self.id() + " #" + str(self.counter)
@inlineCallbacks
def maybeCommitThis():
try:
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20130418/67051836/attachment-0001.html>
More information about the calendarserver-changes
mailing list