2
# Copyright (c) 2011 Apple Inc. All rights reserved.
4
# Licensed under the Apache License, Version 2.0 (the "License");
5
# you may not use this file except in compliance with the License.
6
# You may obtain a copy of the License at
8
# http://www.apache.org/licenses/LICENSE-2.0
10
# Unless required by applicable law or agreed to in writing, software
11
# distributed under the License is distributed on an "AS IS" BASIS,
12
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
# See the License for the specific language governing permissions and
14
# limitations under the License.
17
from calendarserver.push.applepush import (
18
ApplePushNotifierService, APNProviderProtocol
20
from twistedcaldav.test.util import TestCase
21
from twisted.internet.defer import inlineCallbacks
22
from twisted.internet.task import Clock
24
from txdav.common.datastore.test.util import buildStore, CommonCommonTests
26
class ApplePushNotifierServiceTests(CommonCommonTests, TestCase):
30
yield super(ApplePushNotifierServiceTests, self).setUp()
31
self.store = yield buildStore(self, None)
34
def test_ApplePushNotifierService(self):
37
"Service" : "calendarserver.push.applepush.ApplePushNotifierService",
39
"SubscriptionURL" : "apn",
40
"DataHost" : "calendars.example.com",
41
"ProviderHost" : "gateway.push.apple.com",
42
"ProviderPort" : 2195,
43
"FeedbackHost" : "feedback.push.apple.com",
44
"FeedbackPort" : 2196,
45
"FeedbackUpdateSeconds" : 300,
47
"CertificatePath" : "caldav.cer",
48
"PrivateKeyPath" : "caldav.pem",
49
"Topic" : "caldav_topic",
52
"CertificatePath" : "carddav.cer",
53
"PrivateKeyPath" : "carddav.pem",
54
"Topic" : "carddav_topic",
60
txn = self.store.newTransaction()
61
token = "2d0d55cd7f98bcb81c6e24abcdc35168254c7846a43e2828b1ba5a8f82e219df"
62
key1 = "/CalDAV/calendars.example.com/user01/calendar/"
64
guid = "D2256BCC-48E2-42D1-BD89-CBA1E4CCDFFB"
65
yield txn.addAPNSubscription(token, key1, timestamp1, guid)
67
key2 = "/CalDAV/calendars.example.com/user02/calendar/"
69
yield txn.addAPNSubscription(token, key2, timestamp2, guid)
74
service = (yield ApplePushNotifierService.makeService(settings,
75
self.store, testConnectorClass=TestConnector, reactor=clock))
76
self.assertEquals(set(service.providers.keys()), set(["CalDAV","CardDAV"]))
77
self.assertEquals(set(service.feedbacks.keys()), set(["CalDAV","CardDAV"]))
79
# First, enqueue a notification while we have no connection, in this
80
# case by doing it prior to startService()
82
# Notification arrives from calendar server
83
yield service.enqueue("update", "CalDAV|user01/calendar")
85
# The notification should be in the queue
86
self.assertEquals(service.providers["CalDAV"].queue, [(token, key1)])
88
# Start the service, making the connection which should service the
90
service.startService()
92
# The queue should be empty
93
self.assertEquals(service.providers["CalDAV"].queue, [])
95
# Verify data sent to APN
96
connector = service.providers["CalDAV"].testConnector
97
rawData = connector.transport.data
98
self.assertEquals(len(rawData), 103)
99
data = struct.unpack("!BIIH32sH", rawData[:45])
100
self.assertEquals(data[0], 1) # command
101
self.assertEquals(data[4].encode("hex"), token.replace(" ", "")) # token
102
payloadLength = data[5]
103
payload = struct.unpack("%ds" % (payloadLength,),
105
self.assertEquals(payload[0], '{"key" : "%s"}' % (key1,))
108
errorData = struct.pack("!BBI", APNProviderProtocol.COMMAND_ERROR, 1, 1)
109
yield connector.receiveData(errorData)
112
# Prior to feedback, there are 2 subscriptions
113
txn = self.store.newTransaction()
114
subscriptions = (yield txn.apnSubscriptionsByToken(token))
116
self.assertEquals(len(subscriptions), 2)
120
connector = service.feedbacks["CalDAV"].testConnector
121
binaryToken = token.decode("hex")
122
feedbackData = struct.pack("!IH32s", timestamp, len(binaryToken),
124
yield connector.receiveData(feedbackData)
126
# The second subscription should now be gone
127
# Prior to feedback, there are 2 subscriptions
128
txn = self.store.newTransaction()
129
subscriptions = (yield txn.apnSubscriptionsByToken(token))
131
self.assertEquals(len(subscriptions), 1)
134
class TestConnector(object):
136
def connect(self, service, factory):
137
self.service = service
138
service.protocol = factory.buildProtocol(None)
139
service.connected = 1
140
self.transport = StubTransport()
141
service.protocol.makeConnection(self.transport)
143
def receiveData(self, data):
144
return self.service.protocol.dataReceived(data)
147
class StubTransport(object):
152
def write(self, data):