1
# -*- coding: utf-8 -*-
3
# tests.eventlog.test_zg_listener - test logging ZG events
5
# Author: Alejandro J. Cura <alecu@canonical.com>
7
# Copyright 2010 Canonical Ltd.
9
# This program is free software: you can redistribute it and/or modify it
10
# under the terms of the GNU General Public License version 3, as published
11
# by the Free Software Foundation.
13
# This program is distributed in the hope that it will be useful, but
14
# WITHOUT ANY WARRANTY; without even the implied warranties of
15
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
16
# PURPOSE. See the GNU General Public License for more details.
18
# You should have received a copy of the GNU General Public License along
19
# with this program. If not, see <http://www.gnu.org/licenses/>.
20
"""Test the event logging from SyncDaemon into Zeitgeist."""
28
from twisted.internet import defer
29
from zeitgeist.datamodel import Interpretation, Manifestation
31
from contrib.testing.testcase import (
32
FakeMain, FakeMainTestCase, BaseTwistedTestCase)
33
from ubuntuone.devtools.handlers import MementoHandler
34
from ubuntuone.platform.linux import get_udf_path
35
from ubuntuone.storageprotocol import client, delta
36
from ubuntuone.storageprotocol.request import ROOT
37
from ubuntuone.storageprotocol.sharersp import NotifyShareHolder
38
from ubuntuone.syncdaemon.action_queue import (
39
RequestQueue, Upload, MakeFile, MakeDir)
40
from ubuntuone.eventlog.zg_listener import (
41
zglog, ZeitgeistListener, ACTOR_UBUNTUONE,
42
EVENT_INTERPRETATION_U1_FOLDER_SHARED,
43
EVENT_INTERPRETATION_U1_FOLDER_UNSHARED,
44
EVENT_INTERPRETATION_U1_SHARE_ACCEPTED,
45
EVENT_INTERPRETATION_U1_SHARE_UNACCEPTED,
46
EVENT_INTERPRETATION_U1_CONFLICT_RENAME,
47
EVENT_INTERPRETATION_U1_UDF_CREATED,
48
EVENT_INTERPRETATION_U1_UDF_DELETED,
49
EVENT_INTERPRETATION_U1_UDF_SUBSCRIBED,
50
EVENT_INTERPRETATION_U1_UDF_UNSUBSCRIBED,
51
MANIFESTATION_U1_CONTACT_DATA_OBJECT, DIRECTORY_MIMETYPE,
52
INTERPRETATION_U1_CONTACT, URI_PROTOCOL_U1,
53
STORAGE_DELETED, STORAGE_NETWORK, STORAGE_LOCAL)
54
from ubuntuone.syncdaemon.sync import Sync
55
from ubuntuone.syncdaemon.volume_manager import Share, Shared, UDF
56
from tests.syncdaemon.test_action_queue import (
57
ConnectedBaseTestCase,
61
VOLUME = uuid.UUID('12345678-1234-1234-1234-123456789abc')
64
class MockLogger(object):
65
"""A mock logger that stores whatever is logged into it."""
68
"""Initialize this instance."""
73
self.events.append(event)
75
def listen_for(event_q, event, callback, count=1, collect=False):
76
"""Setup a EQ listener for the specified event."""
77
class Listener(object):
78
"""A basic listener to handle the pushed event."""
84
def _handle_event(self, *args, **kwargs):
87
self.events.append((args, kwargs))
88
if self.hits == count:
89
event_q.unsubscribe(self)
93
callback((args, kwargs))
98
setattr(listener, 'handle_'+event, listener._handle_event)
99
event_q.subscribe(listener)
103
class ZeitgeistListenerTestCase(FakeMainTestCase):
104
"""Tests for ZeitgeistListener."""
107
"""Initialize this instance."""
108
super(ZeitgeistListenerTestCase, self).setUp()
109
self.patch(zglog, "ZeitgeistLogger", MockLogger)
110
self.listener = ZeitgeistListener(self.fs, self.vm)
111
self.event_q.subscribe(self.listener)
113
def _listen_for(self, *args, **kwargs):
114
return listen_for(self.main.event_q, *args, **kwargs)
117
class ZeitgeistSharesTestCase(ZeitgeistListenerTestCase):
118
"""Tests for all Share-related zeitgeist events."""
120
def test_share_created_with_username_is_logged(self):
121
"""A ShareCreated event is logged."""
122
fake_username = "fake user"
123
path = os.path.join(self.vm.root.path, 'shared_path')
124
sample_node_id = "node id"
125
self.main.fs.create(path, "")
126
self.main.fs.set_node_id(path, sample_node_id)
128
def fake_create_share(node_id, user, name, access_level, marker, path):
129
"""Fake the creation of the share on the server."""
130
self.assertIn(marker, self.vm.marker_share_map)
131
share_id = self.fs.get_by_mdid(marker).share_id
132
self.main.event_q.push('AQ_CREATE_SHARE_OK',
136
self.patch(self.main.action_q, "create_share", fake_create_share)
137
self.vm.create_share(path, fake_username, 'shared_name', 'View')
139
self.assert_folder_shared_is_logged(path, fake_username)
141
def test_share_created_with_email_is_logged(self):
142
"""A ShareCreated event is logged."""
143
fake_username = "fakeuser@somewhere.com"
144
path = os.path.join(self.vm.root.path, 'shared_path')
145
sample_node_id = "node id"
146
self.main.fs.create(path, "")
147
self.main.fs.set_node_id(path, sample_node_id)
149
def fake_create_share(node_id, user, name, access_level, marker, path):
150
"""Fake the creation of the share on the server."""
151
self.assertIn(marker, self.vm.marker_share_map)
152
self.main.event_q.push('AQ_SHARE_INVITATION_SENT',
155
self.patch(self.main.action_q, "create_share", fake_create_share)
156
self.vm.create_share(path, fake_username, 'shared_name', 'View')
158
self.assert_folder_shared_is_logged(path, fake_username)
160
def assert_folder_shared_is_logged(self, path, fake_username):
161
"""Assert that the FolderShared event was logged."""
163
self.assertEqual(len(self.listener.zg.events), 1)
164
event = self.listener.zg.events[0]
166
self.assertEqual(event.interpretation,
167
EVENT_INTERPRETATION_U1_FOLDER_SHARED)
168
self.assertEqual(event.manifestation,
169
Manifestation.USER_ACTIVITY)
170
self.assertEqual(event.actor, ACTOR_UBUNTUONE)
172
folder = event.subjects[0]
173
self.assertTrue(folder.uri.startswith(URI_PROTOCOL_U1))
174
self.assertEqual(folder.interpretation, Interpretation.FOLDER)
175
self.assertEqual(folder.manifestation,
176
Manifestation.REMOTE_DATA_OBJECT)
177
self.assertTrue(folder.origin.endswith(path))
178
self.assertEqual(folder.mimetype, DIRECTORY_MIMETYPE)
179
self.assertEqual(folder.storage, STORAGE_NETWORK)
181
other_user = event.subjects[1]
182
self.assertEqual(other_user.uri, "mailto:" + fake_username)
183
self.assertEqual(other_user.interpretation, INTERPRETATION_U1_CONTACT)
184
self.assertEqual(other_user.manifestation,
185
MANIFESTATION_U1_CONTACT_DATA_OBJECT)
188
@defer.inlineCallbacks
189
def test_share_deleted_is_logged(self):
190
"""Test VolumeManager.delete_share."""
191
sample_share_id = "share id"
192
sample_node_id = "node id"
193
fake_username = "fake user"
194
path = os.path.join(self.vm.root.path, 'shared_path')
195
self.main.fs.create(path, "")
196
self.main.fs.set_node_id(path, sample_node_id)
197
share = Shared(path=path, volume_id=sample_share_id,
198
node_id=sample_node_id, other_username=fake_username)
199
self.vm.add_shared(share)
201
def fake_delete_share(share_id):
202
"""Fake delete_share."""
203
self.assertEqual(share_id, share.volume_id)
204
self.main.event_q.push('AQ_DELETE_SHARE_OK', share_id=share_id)
206
self.patch(self.main.action_q, 'delete_share', fake_delete_share)
208
self._listen_for('VM_SHARE_DELETED', d.callback, 1, collect=True)
209
self.vm.delete_share(share.volume_id)
212
self.assertEqual(len(self.listener.zg.events), 1)
213
event = self.listener.zg.events[0]
215
self.assertEqual(event.interpretation,
216
EVENT_INTERPRETATION_U1_FOLDER_UNSHARED)
217
self.assertEqual(event.manifestation,
218
Manifestation.USER_ACTIVITY)
219
self.assertEqual(event.actor, ACTOR_UBUNTUONE)
221
folder = event.subjects[0]
222
self.assertTrue(folder.uri.startswith(URI_PROTOCOL_U1))
223
self.assertEqual(folder.interpretation, Interpretation.FOLDER)
224
self.assertEqual(folder.manifestation,
225
Manifestation.REMOTE_DATA_OBJECT)
226
self.assertTrue(folder.origin.endswith(path))
227
self.assertEqual(folder.mimetype, DIRECTORY_MIMETYPE)
228
self.assertEqual(folder.storage, STORAGE_NETWORK)
230
other_user = event.subjects[1]
231
self.assertEqual(other_user.uri, "mailto:" + fake_username)
232
self.assertEqual(other_user.interpretation, INTERPRETATION_U1_CONTACT)
233
self.assertEqual(other_user.manifestation,
234
MANIFESTATION_U1_CONTACT_DATA_OBJECT)
236
def test_share_accepted_is_logged(self):
237
"""Test that an accepted share event is logged."""
238
# initialize the the root
239
self.vm._got_root('root_uuid')
240
fake_username = "fake user"
241
path = os.path.join(self.vm.root.path, 'shared_path')
242
self.main.fs.create(path, "")
243
share_path = os.path.join(self.shares_dir, 'fake_share')
244
share = Share(path=share_path, volume_id='volume_id', node_id="node_id",
245
other_username=fake_username)
246
self.vm.add_share(share)
248
self.assertEqual(len(self.listener.zg.events), 1)
249
event = self.listener.zg.events[0]
251
self.assertEqual(event.interpretation,
252
EVENT_INTERPRETATION_U1_SHARE_ACCEPTED)
253
self.assertEqual(event.manifestation,
254
Manifestation.USER_ACTIVITY)
255
self.assertEqual(event.actor, ACTOR_UBUNTUONE)
257
folder = event.subjects[0]
258
self.assertTrue(folder.uri.startswith(URI_PROTOCOL_U1))
259
self.assertEqual(folder.interpretation, Interpretation.FOLDER)
260
self.assertEqual(folder.manifestation,
261
Manifestation.REMOTE_DATA_OBJECT)
262
self.assertTrue(folder.origin.endswith(share_path))
263
self.assertEqual(folder.mimetype, DIRECTORY_MIMETYPE)
264
self.assertEqual(folder.storage, STORAGE_NETWORK)
266
other_user = event.subjects[1]
267
self.assertEqual(other_user.uri, "mailto:" + fake_username)
268
self.assertEqual(other_user.interpretation, INTERPRETATION_U1_CONTACT)
269
self.assertEqual(other_user.manifestation,
270
MANIFESTATION_U1_CONTACT_DATA_OBJECT)
273
@defer.inlineCallbacks
274
def test_share_unaccepted_is_logged(self):
275
"""Test that an unaccepted share event is logged."""
276
fake_username = "fake user"
279
share_path = os.path.join(self.main.shares_dir, 'share')
280
holder = NotifyShareHolder.from_params(uuid.uuid4(),
284
u'visible_name', 'Read')
286
self.main.vm.add_share(Share.from_notify_holder(holder, share_path))
287
self._listen_for('VM_VOLUME_DELETED', d.callback, 1, collect=True)
288
self.main.event_q.push('SV_SHARE_DELETED', share_id=holder.share_id)
291
self.assertEqual(len(self.listener.zg.events), 2)
292
event = self.listener.zg.events[1]
294
self.assertEqual(event.interpretation,
295
EVENT_INTERPRETATION_U1_SHARE_UNACCEPTED)
296
self.assertEqual(event.manifestation,
297
Manifestation.USER_ACTIVITY)
298
self.assertEqual(event.actor, ACTOR_UBUNTUONE)
300
folder = event.subjects[0]
301
self.assertTrue(folder.uri.startswith(URI_PROTOCOL_U1))
302
self.assertEqual(folder.interpretation, Interpretation.FOLDER)
303
self.assertEqual(folder.manifestation,
304
Manifestation.REMOTE_DATA_OBJECT)
305
self.assertTrue(folder.origin.endswith(share_path))
306
self.assertEqual(folder.mimetype, DIRECTORY_MIMETYPE)
307
self.assertEqual(folder.storage, STORAGE_NETWORK)
309
other_user = event.subjects[1]
310
self.assertEqual(other_user.uri, "mailto:" + fake_username)
311
self.assertEqual(other_user.interpretation, INTERPRETATION_U1_CONTACT)
312
self.assertEqual(other_user.manifestation,
313
MANIFESTATION_U1_CONTACT_DATA_OBJECT)
316
class ZeitgeistUDFsTestCase(ZeitgeistListenerTestCase):
317
"""Tests for all UDFs-related zeitgeist events."""
320
"""Initialize this test instance."""
321
super(ZeitgeistUDFsTestCase, self).setUp()
322
self.home_dir = self.mktemp('ubuntuonehacker')
323
self._old_home = os.environ['HOME']
324
os.environ['HOME'] = self.home_dir
327
"""Finalize this test instance."""
328
self.rmtree(self.home_dir)
329
os.environ['HOME'] = self._old_home
330
super(ZeitgeistUDFsTestCase, self).tearDown()
332
def _create_udf(self, id, node_id, suggested_path, subscribed=True):
333
"""Create an UDF and returns it and the volume."""
334
path = get_udf_path(suggested_path)
335
# make sure suggested_path is unicode
336
if isinstance(suggested_path, str):
337
suggested_path = suggested_path.decode('utf-8')
338
udf = UDF(str(id), str(node_id), suggested_path, path, subscribed)
341
@defer.inlineCallbacks
342
def test_udf_create_is_logged(self):
343
"""Test for Folders.create."""
344
path = os.path.join(self.home_dir, u'ñoño'.encode('utf-8'))
346
node_id = uuid.uuid4()
348
def create_udf(path, name, marker):
349
"""Fake create_udf."""
350
# check that the marker is the full path to the udf
351
expanded_path = os.path.expanduser(path.encode('utf-8'))
352
udf_path = os.path.join(expanded_path, name.encode('utf-8'))
353
if str(marker) != udf_path:
354
d.errback(ValueError("marker != path - "
355
"marker: %r path: %r" % (marker, udf_path)))
356
self.main.event_q.push("AQ_CREATE_UDF_OK", **dict(volume_id=id,
360
self.patch(self.main.action_q, "create_udf", create_udf)
363
self._listen_for('VM_UDF_CREATED', d.callback, 1, collect=True)
364
self.vm.create_udf(path)
367
self.assertEqual(len(self.listener.zg.events), 1)
368
event = self.listener.zg.events[0]
370
self.assertEqual(event.interpretation,
371
EVENT_INTERPRETATION_U1_UDF_CREATED)
372
self.assertEqual(event.manifestation,
373
Manifestation.USER_ACTIVITY)
374
self.assertEqual(event.actor, ACTOR_UBUNTUONE)
376
folder = event.subjects[0]
377
self.assertTrue(folder.uri.startswith(URI_PROTOCOL_U1))
378
self.assertEqual(folder.interpretation, Interpretation.FOLDER)
379
self.assertEqual(folder.manifestation,
380
Manifestation.REMOTE_DATA_OBJECT)
381
self.assertTrue(folder.origin.endswith(path))
382
self.assertEqual(folder.mimetype, DIRECTORY_MIMETYPE)
383
self.assertEqual(folder.storage, STORAGE_NETWORK)
385
@defer.inlineCallbacks
386
def test_udf_delete_is_logged(self):
387
"""Test for Folders.delete."""
389
node_id = uuid.uuid4()
390
path = os.path.join(self.home_dir, u'ñoño'.encode('utf-8'))
392
def create_udf(path, name, marker):
393
"""Fake create_udf."""
394
# check that the marker is the full path to the udf
395
expanded_path = os.path.expanduser(path.encode('utf-8'))
396
udf_path = os.path.join(expanded_path, name.encode('utf-8'))
397
if str(marker) != udf_path:
398
d.errback(ValueError("marker != path - "
399
"marker: %r path: %r" % (marker, udf_path)))
400
self.main.event_q.push("AQ_CREATE_UDF_OK", **dict(volume_id=id,
404
self.patch(self.main.action_q, "create_udf", create_udf)
407
self._listen_for('VM_UDF_CREATED', d.callback, 1, collect=True)
408
self.vm.create_udf(path)
411
def delete_volume(volume_id, path):
412
"""Fake delete_volume."""
413
self.main.event_q.push("AQ_DELETE_VOLUME_OK", volume_id=id)
415
self.patch(self.main.action_q, "delete_volume", delete_volume)
417
self.vm.delete_volume(str(id))
419
self.assertEqual(len(self.listener.zg.events), 1)
420
event = self.listener.zg.events[0]
423
self._listen_for('VM_VOLUME_DELETED', d.callback, 1, collect=True)
426
self.assertEqual(len(self.listener.zg.events), 2)
427
event = self.listener.zg.events[1]
429
self.assertEqual(event.interpretation,
430
EVENT_INTERPRETATION_U1_UDF_DELETED)
431
self.assertEqual(event.manifestation,
432
Manifestation.USER_ACTIVITY)
433
self.assertEqual(event.actor, ACTOR_UBUNTUONE)
435
folder = event.subjects[0]
436
self.assertTrue(folder.uri.startswith(URI_PROTOCOL_U1))
437
self.assertEqual(folder.interpretation, Interpretation.FOLDER)
438
self.assertEqual(folder.manifestation,
439
Manifestation.DELETED_RESOURCE)
440
self.assertTrue(folder.origin.endswith(path))
441
self.assertEqual(folder.mimetype, DIRECTORY_MIMETYPE)
442
self.assertEqual(folder.storage, STORAGE_DELETED)
444
@defer.inlineCallbacks
445
def test_udf_subscribe_is_logged(self):
446
"""Test for Folders.subscribe."""
447
suggested_path = u'~/ñoño'
448
udf = self._create_udf(uuid.uuid4(), 'node_id', suggested_path,
450
yield self.main.vm.add_udf(udf)
452
self._listen_for('VM_UDF_SUBSCRIBED', d.callback, 1, collect=True)
453
self.vm.subscribe_udf(udf.volume_id)
456
self.assertEqual(len(self.listener.zg.events), 2)
457
event = self.listener.zg.events[1]
459
self.assertEqual(event.interpretation,
460
EVENT_INTERPRETATION_U1_UDF_SUBSCRIBED)
461
self.assertEqual(event.manifestation,
462
Manifestation.USER_ACTIVITY)
463
self.assertEqual(event.actor, ACTOR_UBUNTUONE)
465
folder = event.subjects[0]
466
self.assertTrue(folder.uri.endswith(udf.path))
467
self.assertEqual(folder.interpretation, Interpretation.FOLDER)
468
self.assertEqual(folder.manifestation,
469
Manifestation.FILE_DATA_OBJECT)
470
self.assertTrue(folder.origin.startswith(URI_PROTOCOL_U1))
471
self.assertEqual(folder.mimetype, DIRECTORY_MIMETYPE)
472
self.assertEqual(folder.storage, STORAGE_LOCAL)
474
@defer.inlineCallbacks
475
def test_udf_unsubscribe_is_logged(self):
476
"""Test for Folders.unsubscribe."""
477
suggested_path = u'~/ñoño'
478
udf = self._create_udf(uuid.uuid4(), 'node_id', suggested_path,
480
yield self.main.vm.add_udf(udf)
482
self._listen_for('VM_UDF_UNSUBSCRIBED', d.callback, 1, collect=True)
483
self.vm.unsubscribe_udf(udf.volume_id)
486
self.assertEqual(len(self.listener.zg.events), 2)
487
event = self.listener.zg.events[1]
489
self.assertEqual(event.interpretation,
490
EVENT_INTERPRETATION_U1_UDF_UNSUBSCRIBED)
491
self.assertEqual(event.manifestation,
492
Manifestation.USER_ACTIVITY)
493
self.assertEqual(event.actor, ACTOR_UBUNTUONE)
495
folder = event.subjects[0]
496
self.assertTrue(folder.uri.endswith(udf.path))
497
self.assertEqual(folder.interpretation, Interpretation.FOLDER)
498
self.assertEqual(folder.manifestation,
499
Manifestation.DELETED_RESOURCE)
500
self.assertTrue(folder.origin.startswith(URI_PROTOCOL_U1))
501
self.assertEqual(folder.mimetype, DIRECTORY_MIMETYPE)
502
self.assertEqual(folder.storage, STORAGE_DELETED)
505
class ZeitgeistRemoteFileSyncTestCase(ConnectedBaseTestCase):
506
"""File sync events are logged into Zeitgeist."""
509
"""Initialize this test instance."""
510
ConnectedBaseTestCase.setUp(self)
511
self.rq = request_queue = RequestQueue(action_queue=self.action_queue)
512
self.rq.transfers_semaphore = FakeSemaphore()
514
class MyUpload(Upload):
515
"""Just to allow monkeypatching."""
518
self.command = MyUpload(request_queue, share_id=self.share_id,
519
node_id='a_node_id', previous_hash='prev_hash',
520
hash='yadda', crc32=0, size=0, path='path',
521
fileobj_factory=lambda: None,
522
tempfile_factory=lambda: None)
523
self.command.pre_queue_setup() # create the logger
524
self.fsm = self.action_queue.main.fs
525
self.vm = self.action_queue.main.vm
526
self.patch(zglog, "ZeitgeistLogger", MockLogger)
527
self.listener = ZeitgeistListener(self.fsm, self.vm)
528
self.action_queue.event_queue.subscribe(self.listener)
529
self.root_id = "roootid"
530
self.sync = Sync(main=self.main)
533
"""Finalize this test instance."""
534
ConnectedBaseTestCase.tearDown(self)
536
def test_syncdaemon_creates_file_on_server_is_logged(self):
537
"""Files created by SyncDaemon on the server are logged."""
538
filename = "filename.mp3"
539
path = os.path.join(self.vm.root.path, filename)
540
self.fsm.create(path, "")
541
self.fsm.set_node_id(path, "a_node_id")
543
request = client.MakeFile(self.action_queue.client, self.share_id,
545
request.new_id = 'a_node_id'
546
request.new_generation = 13
548
# create a command and trigger it success
549
cmd = MakeFile(self.rq, self.share_id, 'parent', filename,
551
res = cmd.handle_success(request)
552
assert res is request
554
# create a request and fill it with succesful information
555
request = client.PutContent(self.action_queue.client, self.share_id,
556
'node', 'prvhash', 'newhash', 'crc32',
557
'size', 'deflated', 'fd')
558
request.new_generation = 13
560
# trigger success in the command
561
self.command.handle_success(request)
563
# check for successful event
564
kwargs = dict(share_id=self.command.share_id, node_id='a_node_id',
565
hash='yadda', new_generation=13)
567
info = dict(marker='marker', new_id='a_node_id', new_generation=13,
568
volume_id=self.share_id)
570
('AQ_FILE_NEW_OK', info),
571
('AQ_UPLOAD_FINISHED', kwargs),
573
self.assertEqual(events, self.command.action_queue.event_queue.events)
575
self.assertEqual(len(self.listener.zg.events), 1)
576
event = self.listener.zg.events[0]
578
self.assertEqual(event.interpretation,
579
Interpretation.CREATE_EVENT)
580
self.assertEqual(event.manifestation,
581
Manifestation.SCHEDULED_ACTIVITY)
582
self.assertEqual(event.actor, ACTOR_UBUNTUONE)
584
remote_file = event.subjects[0]
585
self.assertTrue(remote_file.uri.startswith(URI_PROTOCOL_U1))
586
self.assertEqual(remote_file.interpretation, Interpretation.AUDIO)
587
self.assertEqual(remote_file.manifestation,
588
Manifestation.REMOTE_DATA_OBJECT)
589
self.assertTrue(remote_file.origin.endswith(filename))
590
self.assertEqual(remote_file.mimetype, "audio/mpeg")
591
self.assertEqual(remote_file.storage, STORAGE_NETWORK)
593
def test_syncdaemon_creates_dir_on_server_is_logged(self):
594
"""Dirs created by SyncDaemon on the server are logged."""
596
path = os.path.join(self.vm.root.path, dirname)
597
self.fsm.create(path, "")
598
self.fsm.set_node_id(path, "a_node_id")
600
request = client.MakeDir(self.action_queue.client, self.share_id,
602
request.new_id = 'a_node_id'
603
request.new_generation = 13
605
# create a command and trigger it success
606
cmd = MakeDir(self.rq, self.share_id, 'parent',
607
dirname, 'marker', path)
608
res = cmd.handle_success(request)
609
assert res is request
611
# check for successful event
612
info = dict(marker='marker', new_id='a_node_id', new_generation=13,
613
volume_id=self.share_id)
614
events = [('AQ_DIR_NEW_OK', info)]
615
self.assertEqual(events, self.command.action_queue.event_queue.events)
617
self.assertEqual(len(self.listener.zg.events), 1)
618
event = self.listener.zg.events[0]
620
self.assertEqual(event.interpretation,
621
Interpretation.CREATE_EVENT)
622
self.assertEqual(event.manifestation,
623
Manifestation.SCHEDULED_ACTIVITY)
624
self.assertEqual(event.actor, ACTOR_UBUNTUONE)
626
remote_file = event.subjects[0]
627
self.assertTrue(remote_file.uri.startswith(URI_PROTOCOL_U1))
628
self.assertEqual(remote_file.interpretation, Interpretation.FOLDER)
629
self.assertEqual(remote_file.manifestation,
630
Manifestation.REMOTE_DATA_OBJECT)
631
self.assertTrue(remote_file.origin.endswith(dirname))
632
self.assertEqual(remote_file.mimetype, DIRECTORY_MIMETYPE)
633
self.assertEqual(remote_file.storage, STORAGE_NETWORK)
635
def test_syncdaemon_modifies_on_server_is_logged(self):
636
"""Files modified by SyncDaemon on the server are logged."""
637
filename = "filename.mp3"
638
path = os.path.join(self.vm.root.path, filename)
639
self.fsm.create(path, "")
640
self.fsm.set_node_id(path, "a_node_id")
642
# create a request and fill it with succesful information
643
request = client.PutContent(self.action_queue.client, self.share_id,
644
'node', 'prvhash', 'newhash', 'crc32',
645
'size', 'deflated', 'fd')
646
request.new_generation = 13
648
# trigger success in the command
649
self.command.handle_success(request)
651
# check for successful event
652
kwargs = dict(share_id=self.command.share_id, node_id='a_node_id',
653
hash='yadda', new_generation=13)
655
events = [('AQ_UPLOAD_FINISHED', kwargs)]
656
self.assertEqual(events, self.command.action_queue.event_queue.events)
658
self.assertEqual(len(self.listener.zg.events), 1)
659
event = self.listener.zg.events[0]
661
self.assertEqual(event.interpretation,
662
Interpretation.MODIFY_EVENT)
663
self.assertEqual(event.manifestation,
664
Manifestation.SCHEDULED_ACTIVITY)
665
self.assertEqual(event.actor, ACTOR_UBUNTUONE)
667
remote_file = event.subjects[0]
668
self.assertTrue(remote_file.uri.startswith(URI_PROTOCOL_U1))
669
self.assertEqual(remote_file.interpretation, Interpretation.AUDIO)
670
self.assertEqual(remote_file.manifestation,
671
Manifestation.REMOTE_DATA_OBJECT)
672
self.assertTrue(remote_file.origin.endswith(filename))
673
self.assertEqual(remote_file.mimetype, "audio/mpeg")
674
self.assertEqual(remote_file.storage, STORAGE_NETWORK)
676
@defer.inlineCallbacks
677
def test_syncdaemon_deletes_file_on_server_is_logged(self):
678
"""Files deleted by SD on the server are logged."""
680
listen_for(self.main.event_q, 'AQ_UNLINK_OK', d.callback)
682
path = os.path.join(self.main.vm.root.path, "filename.mp3")
683
self.main.fs.create(path, "")
684
self.main.fs.set_node_id(path, "node_id")
685
self.main.event_q.push("AQ_UNLINK_OK", share_id="",
686
parent_id="parent_id",
687
node_id="node_id", new_generation=13)
690
self.assertEqual(len(self.listener.zg.events), 1)
691
event = self.listener.zg.events[0]
693
self.assertEqual(event.interpretation,
694
Interpretation.DELETE_EVENT)
695
self.assertEqual(event.manifestation,
696
Manifestation.SCHEDULED_ACTIVITY)
697
self.assertEqual(event.actor, ACTOR_UBUNTUONE)
699
remote_folder = event.subjects[0]
700
self.assertTrue(remote_folder.uri.startswith(URI_PROTOCOL_U1))
701
self.assertEqual(remote_folder.interpretation, Interpretation.AUDIO)
702
self.assertEqual(remote_folder.manifestation,
703
Manifestation.DELETED_RESOURCE)
704
self.assertTrue(remote_folder.origin.endswith("filename.mp3"))
705
self.assertEqual(remote_folder.mimetype, "audio/mpeg")
706
self.assertEqual(remote_folder.storage, STORAGE_DELETED)
708
@defer.inlineCallbacks
709
def test_syncdaemon_deletes_dir_on_server_is_logged(self):
710
"""Files deleted by SD on the server are logged."""
712
listen_for(self.main.event_q, 'AQ_UNLINK_OK', d.callback)
714
path = os.path.join(self.main.vm.root.path, "folder name")
715
self.main.fs.create(path, "", is_dir=True)
716
self.main.fs.set_node_id(path, "node_id")
717
self.main.event_q.push("AQ_UNLINK_OK", share_id="",
718
parent_id="parent_id",
719
node_id="node_id", new_generation=13)
722
self.assertEqual(len(self.listener.zg.events), 1)
723
event = self.listener.zg.events[0]
725
self.assertEqual(event.interpretation,
726
Interpretation.DELETE_EVENT)
727
self.assertEqual(event.manifestation,
728
Manifestation.SCHEDULED_ACTIVITY)
729
self.assertEqual(event.actor, ACTOR_UBUNTUONE)
731
remote_folder = event.subjects[0]
732
self.assertTrue(remote_folder.uri.startswith(URI_PROTOCOL_U1))
733
self.assertEqual(remote_folder.interpretation, Interpretation.FOLDER)
734
self.assertEqual(remote_folder.manifestation,
735
Manifestation.DELETED_RESOURCE)
736
self.assertTrue(remote_folder.origin.endswith("folder name"))
737
self.assertEqual(remote_folder.mimetype, DIRECTORY_MIMETYPE)
738
self.assertEqual(remote_folder.storage, STORAGE_DELETED)
741
class ZeitgeistLocalFileSyncTestCase(BaseTwistedTestCase):
742
"""Zeitgeist events coming from the server."""
746
"""Initialize this instance."""
747
BaseTwistedTestCase.setUp(self)
748
self.root = self.mktemp('root')
749
self.shares = self.mktemp('shares')
750
self.data = self.mktemp('data')
751
self.partials_dir = self.mktemp('partials_dir')
752
self.handler = MementoHandler()
753
self.handler.setLevel(logging.ERROR)
754
FakeMain._sync_class = Sync
755
self.main = FakeMain(root_dir=self.root, shares_dir=self.shares,
757
partials_dir=self.partials_dir)
758
self._logger = logging.getLogger('ubuntuone.SyncDaemon')
759
self._logger.addHandler(self.handler)
761
self.root_id = root_id = "roootid"
762
self.main.vm._got_root(root_id)
763
self.filemp3delta = delta.FileInfoDelta(
764
generation=5, is_live=True, file_type=delta.FILE,
765
parent_id=self.root_id, share_id=ROOT, node_id=uuid.uuid4(),
766
name=u"fileñ.mp3", is_public=False, content_hash="hash",
767
crc32=1, size=10, last_modified=0)
769
self.dirdelta = delta.FileInfoDelta(
770
generation=6, is_live=True, file_type=delta.DIRECTORY,
771
parent_id=root_id, share_id=ROOT, node_id=uuid.uuid4(),
772
name=u"directory_ñ", is_public=False, content_hash="hash",
773
crc32=1, size=10, last_modified=0)
775
self.patch(zglog, "ZeitgeistLogger", MockLogger)
776
self.listener = ZeitgeistListener(self.main.fs, self.main.vm)
777
self.main.event_q.subscribe(self.listener)
780
"""Clean up this instance."""
781
self._logger.removeHandler(self.handler)
783
FakeMain._sync_class = None
784
shutil.rmtree(self.root)
785
shutil.rmtree(self.shares)
786
shutil.rmtree(self.data)
787
for record in self.handler.records:
788
exc_info = getattr(record, 'exc_info', None)
789
if exc_info is not None:
790
raise exc_info[0], exc_info[1], exc_info[2]
791
BaseTwistedTestCase.tearDown(self)
793
@defer.inlineCallbacks
794
def test_syncdaemon_creates_file_locally_is_logged(self):
795
"""Files created locally by SyncDaemon are logged."""
797
d2 = defer.Deferred()
798
listen_for(self.main.event_q, 'SV_FILE_NEW', d.callback)
799
listen_for(self.main.event_q, 'AQ_DOWNLOAD_FINISHED', d2.callback)
801
deltas = [ self.filemp3delta ]
802
kwargs = dict(volume_id=ROOT, delta_content=deltas, end_generation=11,
803
full=True, free_bytes=10)
804
self.main.sync.handle_AQ_DELTA_OK(**kwargs)
806
# check that the file is created
807
node = self.main.fs.get_by_node_id(ROOT, self.filemp3delta.node_id)
808
self.assertEqual(node.path, self.filemp3delta.name.encode('utf8'))
809
self.assertEqual(node.is_dir, False)
810
self.assertEqual(node.generation, self.filemp3delta.generation)
812
yield d # wait for SV_FILE_NEW
815
share_id=self.filemp3delta.share_id,
816
node_id=self.filemp3delta.node_id,
817
server_hash="server hash")
818
self.main.event_q.push("AQ_DOWNLOAD_FINISHED", **dlargs)
820
yield d2 # wait for AQ_DOWNLOAD_FINISHED
822
self.assertEqual(len(self.listener.zg.events), 1)
823
event = self.listener.zg.events[0]
825
self.assertEqual(event.interpretation,
826
Interpretation.CREATE_EVENT)
827
self.assertEqual(event.manifestation,
828
Manifestation.WORLD_ACTIVITY)
829
self.assertEqual(event.actor, ACTOR_UBUNTUONE)
831
local_file = event.subjects[0]
832
self.assertTrue(local_file.uri.endswith(
833
self.filemp3delta.name.encode('utf8')))
834
self.assertEqual(local_file.interpretation, Interpretation.AUDIO)
835
self.assertEqual(local_file.manifestation,
836
Manifestation.FILE_DATA_OBJECT)
837
self.assertTrue(local_file.origin.startswith(URI_PROTOCOL_U1))
838
self.assertEqual(local_file.mimetype, "audio/mpeg")
839
self.assertEqual(local_file.storage, STORAGE_LOCAL)
841
@defer.inlineCallbacks
842
def test_syncdaemon_creates_dir_locally_is_logged(self):
843
"""Dirs created locally by SyncDaemon are logged."""
845
listen_for(self.main.event_q, 'SV_DIR_NEW', d.callback)
847
deltas = [ self.dirdelta ]
848
kwargs = dict(volume_id=ROOT, delta_content=deltas, end_generation=11,
849
full=True, free_bytes=10)
850
self.main.sync.handle_AQ_DELTA_OK(**kwargs)
852
# check that the dir is created
853
node = self.main.fs.get_by_node_id(ROOT, self.dirdelta.node_id)
854
self.assertEqual(node.path, self.dirdelta.name.encode('utf8'))
855
self.assertEqual(node.is_dir, True)
856
self.assertEqual(node.generation, self.dirdelta.generation)
858
yield d # wait for SV_DIR_NEW
860
self.assertEqual(len(self.listener.zg.events), 1)
861
event = self.listener.zg.events[0]
863
self.assertEqual(event.interpretation,
864
Interpretation.CREATE_EVENT)
865
self.assertEqual(event.manifestation,
866
Manifestation.WORLD_ACTIVITY)
867
self.assertEqual(event.actor, ACTOR_UBUNTUONE)
869
local_file = event.subjects[0]
870
self.assertTrue(local_file.uri.endswith(
871
self.dirdelta.name.encode('utf8')))
872
self.assertEqual(local_file.interpretation, Interpretation.FOLDER)
873
self.assertEqual(local_file.manifestation,
874
Manifestation.FILE_DATA_OBJECT)
875
self.assertTrue(local_file.origin.startswith(URI_PROTOCOL_U1))
876
self.assertEqual(local_file.mimetype, DIRECTORY_MIMETYPE)
877
self.assertEqual(local_file.storage, STORAGE_LOCAL)
879
@defer.inlineCallbacks
880
def test_syncdaemon_modifies_locally_is_logged(self):
881
"""Files modified locally by SyncDaemon are logged."""
883
d2 = defer.Deferred()
884
listen_for(self.main.event_q, 'SV_FILE_NEW', d.callback)
885
listen_for(self.main.event_q, 'AQ_DOWNLOAD_FINISHED', d2.callback)
887
deltas = [ self.filemp3delta ]
888
kwargs = dict(volume_id=ROOT, delta_content=deltas, end_generation=11,
889
full=True, free_bytes=10)
890
self.main.sync.handle_AQ_DELTA_OK(**kwargs)
892
# check that the file is modified
893
node = self.main.fs.get_by_node_id(ROOT, self.filemp3delta.node_id)
894
self.assertEqual(node.path, self.filemp3delta.name.encode('utf8'))
895
self.assertEqual(node.is_dir, False)
896
self.assertEqual(node.generation, self.filemp3delta.generation)
898
yield d # wait for SV_FILE_NEW
900
# remove from the recent list
901
local_file_id = (self.filemp3delta.share_id, self.filemp3delta.node_id)
902
self.listener.newly_created_local_files.remove(local_file_id)
905
share_id=self.filemp3delta.share_id,
906
node_id=self.filemp3delta.node_id,
907
server_hash="server hash")
908
self.main.event_q.push("AQ_DOWNLOAD_FINISHED", **dlargs)
910
yield d2 # wait for AQ_DOWNLOAD_FINISHED
912
self.assertEqual(len(self.listener.zg.events), 1)
913
event = self.listener.zg.events[0]
915
self.assertEqual(event.interpretation,
916
Interpretation.MODIFY_EVENT)
917
self.assertEqual(event.manifestation,
918
Manifestation.WORLD_ACTIVITY)
919
self.assertEqual(event.actor, ACTOR_UBUNTUONE)
921
local_file = event.subjects[0]
922
self.assertTrue(local_file.uri.endswith(
923
self.filemp3delta.name.encode('utf8')))
924
self.assertEqual(local_file.interpretation, Interpretation.AUDIO)
925
self.assertEqual(local_file.manifestation,
926
Manifestation.FILE_DATA_OBJECT)
927
self.assertTrue(local_file.origin.startswith(URI_PROTOCOL_U1))
928
self.assertEqual(local_file.mimetype, "audio/mpeg")
929
self.assertEqual(local_file.storage, STORAGE_LOCAL)
931
@defer.inlineCallbacks
932
def test_syncdaemon_deletes_file_locally_is_logged(self):
933
"""Files deleted locally by SyncDaemon are logged."""
935
listen_for(self.main.event_q, 'SV_FILE_DELETED', d.callback)
937
filename = self.filemp3delta.name.encode("utf-8")
938
path = os.path.join(self.main.vm.root.path, filename)
939
self.main.fs.create(path, "")
940
self.main.fs.set_node_id(path, "node_id")
941
self.main.event_q.push("SV_FILE_DELETED", volume_id="",
942
node_id="node_id", is_dir=False)
946
self.assertEqual(len(self.listener.zg.events), 1)
947
event = self.listener.zg.events[0]
949
self.assertEqual(event.interpretation,
950
Interpretation.DELETE_EVENT)
951
self.assertEqual(event.manifestation,
952
Manifestation.WORLD_ACTIVITY)
953
self.assertEqual(event.actor, ACTOR_UBUNTUONE)
955
local_file = event.subjects[0]
956
self.assertTrue(local_file.uri.endswith(
957
self.filemp3delta.name.encode('utf8')))
958
self.assertEqual(local_file.interpretation, Interpretation.AUDIO)
959
self.assertEqual(local_file.manifestation,
960
Manifestation.DELETED_RESOURCE)
961
self.assertTrue(local_file.origin.startswith(URI_PROTOCOL_U1))
962
self.assertEqual(local_file.mimetype, "audio/mpeg")
963
self.assertEqual(local_file.storage, STORAGE_DELETED)
965
@defer.inlineCallbacks
966
def test_syncdaemon_deletes_dir_locally_is_logged(self):
967
"""Dirs deleted locally by SyncDaemon are logged."""
969
listen_for(self.main.event_q, 'SV_FILE_DELETED', d.callback)
971
path = os.path.join(self.main.vm.root.path, "folder name")
972
self.main.fs.create(path, "", is_dir=True)
973
self.main.fs.set_node_id(path, "node_id")
974
self.main.event_q.push("SV_FILE_DELETED", volume_id="",
975
node_id="node_id", is_dir=True)
979
self.assertEqual(len(self.listener.zg.events), 1)
980
event = self.listener.zg.events[0]
982
self.assertEqual(event.interpretation,
983
Interpretation.DELETE_EVENT)
984
self.assertEqual(event.manifestation,
985
Manifestation.WORLD_ACTIVITY)
986
self.assertEqual(event.actor, ACTOR_UBUNTUONE)
988
local_folder = event.subjects[0]
989
self.assertTrue(local_folder.uri.endswith("folder name"))
990
self.assertEqual(local_folder.interpretation, Interpretation.FOLDER)
991
self.assertEqual(local_folder.manifestation,
992
Manifestation.DELETED_RESOURCE)
993
self.assertTrue(local_folder.origin.startswith(URI_PROTOCOL_U1))
994
self.assertEqual(local_folder.mimetype, DIRECTORY_MIMETYPE)
995
self.assertEqual(local_folder.storage, STORAGE_DELETED)
997
@defer.inlineCallbacks
998
def test_file_sync_conflict_is_logged(self):
999
"""Files renamed because of conflict are logged."""
1000
d = defer.Deferred()
1001
listen_for(self.main.event_q, 'FSM_FILE_CONFLICT', d.callback)
1003
testfile = os.path.join(self.main.vm.root.path, 'sample.mp3')
1004
mdid = self.main.fs.create(testfile, "")
1005
self.main.fs.set_node_id(testfile, "uuid")
1006
with open(testfile, "w") as fh:
1007
fh.write("this is music!")
1009
self.main.fs.move_to_conflict(mdid)
1013
self.assertEqual(len(self.listener.zg.events), 1)
1014
event = self.listener.zg.events[0]
1016
self.assertEqual(event.interpretation,
1017
EVENT_INTERPRETATION_U1_CONFLICT_RENAME)
1018
self.assertEqual(event.manifestation,
1019
Manifestation.WORLD_ACTIVITY)
1020
self.assertEqual(event.actor, ACTOR_UBUNTUONE)
1022
local_file = event.subjects[0]
1023
new_name = testfile + self.main.fs.CONFLICT_SUFFIX
1024
self.assertTrue(local_file.uri.endswith(new_name))
1025
self.assertEqual(local_file.interpretation, Interpretation.AUDIO)
1026
self.assertEqual(local_file.manifestation,
1027
Manifestation.FILE_DATA_OBJECT)
1028
self.assertTrue(local_file.origin.endswith(testfile))
1029
self.assertEqual(local_file.mimetype, "audio/mpeg")
1030
self.assertEqual(local_file.storage, STORAGE_LOCAL)
1032
@defer.inlineCallbacks
1033
def test_dir_sync_conflict_is_logged(self):
1034
"""Dirs renamed because of conflict are logged."""
1035
d = defer.Deferred()
1036
listen_for(self.main.event_q, 'FSM_DIR_CONFLICT', d.callback)
1038
testdir = os.path.join(self.main.vm.root.path, 'sampledir')
1039
mdid = self.main.fs.create(testdir, "", is_dir=True)
1040
self.main.fs.set_node_id(testdir, "uuid")
1043
self.main.fs.move_to_conflict(mdid)
1047
self.assertEqual(len(self.listener.zg.events), 1)
1048
event = self.listener.zg.events[0]
1050
self.assertEqual(event.interpretation,
1051
EVENT_INTERPRETATION_U1_CONFLICT_RENAME)
1052
self.assertEqual(event.manifestation,
1053
Manifestation.WORLD_ACTIVITY)
1054
self.assertEqual(event.actor, ACTOR_UBUNTUONE)
1056
local_file = event.subjects[0]
1057
new_name = testdir + self.main.fs.CONFLICT_SUFFIX
1058
self.assertTrue(local_file.uri.endswith(new_name))
1059
self.assertEqual(local_file.interpretation, Interpretation.FOLDER)
1060
self.assertEqual(local_file.manifestation,
1061
Manifestation.FILE_DATA_OBJECT)
1062
self.assertTrue(local_file.origin.endswith(testdir))
1063
self.assertEqual(local_file.mimetype, DIRECTORY_MIMETYPE)
1064
self.assertEqual(local_file.storage, STORAGE_LOCAL)
1066
class ZeitgeistPublicFilesTestCase(ZeitgeistListenerTestCase):
1067
"""Public files events are logged into Zeitgeist."""
1069
@defer.inlineCallbacks
1070
def test_publish_url_is_logged(self):
1071
"""Publishing a file with a url is logged."""
1075
public_url = 'http://example.com/foo.mp3'
1077
share_path = os.path.join(self.shares_dir, 'share')
1078
self.main.vm.add_share(Share(path=share_path, volume_id='share',
1079
other_username='other username'))
1080
path = os.path.join(share_path, "foo.mp3")
1081
self.main.fs.create(path, str(share_id))
1082
self.main.fs.set_node_id(path, str(node_id))
1084
d = defer.Deferred()
1085
self._listen_for('AQ_CHANGE_PUBLIC_ACCESS_OK', d.callback)
1086
self.main.event_q.push('AQ_CHANGE_PUBLIC_ACCESS_OK',
1087
share_id=share_id, node_id=node_id,
1088
is_public=is_public, public_url=public_url)
1091
self.assertEqual(len(self.listener.zg.events), 2)
1092
event = self.listener.zg.events[1]
1094
self.assertEqual(event.interpretation,
1095
Interpretation.CREATE_EVENT)
1096
self.assertEqual(event.manifestation,
1097
Manifestation.USER_ACTIVITY)
1098
self.assertEqual(event.actor, ACTOR_UBUNTUONE)
1100
public_file = event.subjects[0]
1101
self.assertEqual(public_file.uri, public_url)
1102
self.assertEqual(public_file.interpretation, Interpretation.AUDIO)
1103
self.assertEqual(public_file.manifestation,
1104
Manifestation.REMOTE_DATA_OBJECT)
1105
self.assertTrue(public_file.origin.endswith(node_id))
1106
self.assertEqual(public_file.mimetype, "audio/mpeg")
1107
self.assertEqual(public_file.storage, STORAGE_NETWORK)
1109
@defer.inlineCallbacks
1110
def test_unpublish_url_is_logged(self):
1111
"""Unpublishing a file with a url is logged."""
1115
public_url = 'http://example.com/foo.mp3'
1117
share_path = os.path.join(self.shares_dir, 'share')
1118
self.main.vm.add_share(Share(path=share_path, volume_id='share',
1119
other_username='other username'))
1120
path = os.path.join(share_path, "foo.mp3")
1121
self.main.fs.create(path, str(share_id))
1122
self.main.fs.set_node_id(path, str(node_id))
1124
d = defer.Deferred()
1125
self._listen_for('AQ_CHANGE_PUBLIC_ACCESS_OK', d.callback)
1126
self.main.event_q.push('AQ_CHANGE_PUBLIC_ACCESS_OK',
1127
share_id=share_id, node_id=node_id,
1128
is_public=is_public, public_url=public_url)
1131
self.assertEqual(len(self.listener.zg.events), 2)
1132
event = self.listener.zg.events[1]
1134
self.assertEqual(event.interpretation,
1135
Interpretation.DELETE_EVENT)
1136
self.assertEqual(event.manifestation,
1137
Manifestation.USER_ACTIVITY)
1138
self.assertEqual(event.actor, ACTOR_UBUNTUONE)
1140
public_file = event.subjects[0]
1141
self.assertEqual(public_file.uri, public_url)
1142
self.assertEqual(public_file.interpretation, Interpretation.AUDIO)
1143
self.assertEqual(public_file.manifestation,
1144
Manifestation.DELETED_RESOURCE)
1145
self.assertTrue(public_file.origin.endswith(node_id))
1146
self.assertEqual(public_file.mimetype, "audio/mpeg")
1147
self.assertEqual(public_file.storage, STORAGE_DELETED)
1151
"""Collect these tests only on linux."""
1153
if sys.platform == 'linux2':
1154
tests = unittest.TestLoader().loadTestsFromName(__name__)