File: test_attachments.py

package info (click to toggle)
python-exchangelib 5.5.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 12,084 kB
  • sloc: python: 25,351; sh: 6; makefile: 5
file content (325 lines) | stat: -rw-r--r-- 15,618 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
from exchangelib.attachments import AttachmentId, FileAttachment, ItemAttachment
from exchangelib.errors import ErrorInvalidAttachmentId, ErrorInvalidIdMalformed
from exchangelib.fields import FieldPath
from exchangelib.folders import Inbox
from exchangelib.items import Item, Message
from exchangelib.properties import HTMLBody
from exchangelib.services import GetAttachment
from exchangelib.util import chunkify

from .common import get_random_string
from .test_items.test_basics import BaseItemTest


class AttachmentsTest(BaseItemTest):
    TEST_FOLDER = "inbox"
    FOLDER_CLASS = Inbox
    ITEM_CLASS = Message

    def test_magic(self):
        for item in (FileAttachment(name="XXX"), ItemAttachment(name="XXX")):
            self.assertIn("name=", str(item))
            self.assertIn(item.__class__.__name__, repr(item))

    def test_attachment_failure(self):
        att1 = FileAttachment(name="my_file_1.txt", content="Hello from unicode æøå".encode("utf-8"))
        att1.attachment_id = "XXX"
        with self.assertRaises(ValueError) as e:
            att1.attach()
        self.assertEqual(e.exception.args[0], "This attachment has already been created")
        att1.attachment_id = None
        with self.assertRaises(ValueError) as e:
            att1.attach()
        self.assertEqual(e.exception.args[0], "Parent item None must have an account")
        att1.parent_item = Item()
        with self.assertRaises(ValueError) as e:
            att1.attach()
        self.assertEqual(e.exception.args[0], "Parent item Item(attachments=[]) must have an account")
        att1.parent_item = None
        with self.assertRaises(ValueError) as e:
            att1.detach()
        self.assertEqual(e.exception.args[0], "This attachment has not been created")
        att1.attachment_id = "XXX"
        with self.assertRaises(ValueError) as e:
            att1.detach()
        self.assertEqual(e.exception.args[0], "Parent item None must have an account")
        att1.parent_item = Item()
        with self.assertRaises(ValueError) as e:
            att1.detach()
        self.assertEqual(e.exception.args[0], "Parent item Item(attachments=[]) must have an account")
        att1.parent_item = "XXX"
        with self.assertRaises(TypeError) as e:
            att1.clean()
        self.assertEqual(
            e.exception.args[0], "'parent_item' 'XXX' must be of type <class 'exchangelib.items.item.Item'>"
        )
        with self.assertRaises(ValueError) as e:
            Message(attachments=[att1])
        self.assertIn("must point to this item", e.exception.args[0])
        att1.parent_item = None
        att1.attachment_id = None

    def test_file_attachment_properties(self):
        binary_file_content = "Hello from unicode æøå".encode("utf-8")
        att1 = FileAttachment(name="my_file_1.txt", content=binary_file_content)
        self.assertIn("name='my_file_1.txt'", str(att1))
        att1.content = binary_file_content  # Test property setter
        with self.assertRaises(TypeError) as e:
            att1.content = "XXX"
        self.assertEqual(e.exception.args[0], "'value' 'XXX' must be of type <class 'bytes'>")
        self.assertEqual(att1.content, binary_file_content)  # Test property getter
        att1.attachment_id = "xxx"
        self.assertEqual(att1.content, binary_file_content)  # Test property getter when attachment_id is set
        att1._content = None
        with self.assertRaises(ValueError):
            print(att1.content)  # Test property getter when we need to fetch the content

    def test_item_attachment_properties(self):
        attached_item1 = self.get_test_item(folder=self.test_folder)
        att1 = ItemAttachment(name="attachment1", item=attached_item1)
        self.assertIn("name='attachment1'", str(att1))
        att1.item = attached_item1  # Test property setter
        with self.assertRaises(TypeError) as e:
            att1.item = "XXX"
        self.assertEqual(e.exception.args[0], "'value' 'XXX' must be of type <class 'exchangelib.items.item.Item'>")
        self.assertEqual(att1.item, attached_item1)  # Test property getter
        self.assertEqual(att1.item, attached_item1)  # Test property getter
        att1.attachment_id = "xxx"
        self.assertEqual(att1.item, attached_item1)  # Test property getter when attachment_id is set
        att1._item = None
        with self.assertRaises(ValueError):
            print(att1.item)  # Test property getter when we need to fetch the item

    def test_item_attachments(self):
        item = self.get_test_item(folder=self.test_folder)
        attached_item1 = self.get_test_item(folder=self.test_folder)
        att1 = ItemAttachment(name="attachment1", item=attached_item1)

        # Test __init__(attachments=...) and attach() on new item
        self.assertEqual(len(item.attachments), 0)
        item.attach(att1)
        self.assertEqual(len(item.attachments), 1)
        item.save()
        fresh_item = self.get_item_by_id(item)
        self.assertEqual(len(fresh_item.attachments), 1)
        fresh_attachments = sorted(fresh_item.attachments, key=lambda a: a.name)
        self.assertEqual(fresh_attachments[0].name, "attachment1")
        self.assertEqual(fresh_attachments[0].item.subject, attached_item1.subject)
        self.assertEqual(fresh_attachments[0].item.body, attached_item1.body)
        # Same as 'body' because 'body' doesn't contain HTML
        self.assertEqual(fresh_attachments[0].item.text_body, attached_item1.body)

        # Test attach on saved object
        att2 = ItemAttachment(name="attachment2", item=attached_item1)
        self.assertEqual(len(item.attachments), 1)
        item.attach(att2)
        self.assertEqual(len(item.attachments), 2)
        fresh_item = self.get_item_by_id(item)
        self.assertEqual(len(fresh_item.attachments), 2)
        fresh_attachments = sorted(fresh_item.attachments, key=lambda a: a.name)
        self.assertEqual(fresh_attachments[0].name, "attachment1")
        self.assertEqual(fresh_attachments[0].item.subject, attached_item1.subject)
        self.assertEqual(fresh_attachments[0].item.body, attached_item1.body)
        self.assertEqual(fresh_attachments[1].name, "attachment2")
        self.assertEqual(fresh_attachments[1].item.subject, attached_item1.subject)
        self.assertEqual(fresh_attachments[1].item.body, attached_item1.body)

        # Test detach
        item.detach(att1)
        self.assertTrue(att1.attachment_id is None)
        self.assertTrue(att1.parent_item is None)
        fresh_item = self.get_item_by_id(item)
        self.assertEqual(len(fresh_item.attachments), 1)
        fresh_attachments = sorted(fresh_item.attachments, key=lambda a: a.name)
        self.assertEqual(fresh_attachments[0].name, "attachment2")
        self.assertEqual(fresh_attachments[0].item.subject, attached_item1.subject)
        self.assertEqual(fresh_attachments[0].item.body, attached_item1.body)

    def test_raw_service_call(self):
        item = self.get_test_item(folder=self.test_folder)
        attached_item1 = self.get_test_item(folder=self.test_folder)
        attached_item1.body = HTMLBody("<html><body>Hello HTML</body></html>")
        att1 = ItemAttachment(name="attachment1", item=attached_item1)
        item.attach(att1)
        item.save()
        with self.assertRaises(ValueError):
            # Bad body_type
            GetAttachment(account=att1.parent_item.account).get(
                items=[att1.attachment_id],
                include_mime_content=True,
                body_type="XXX",
                filter_html_content=None,
                additional_fields=[],
            )
        # Test body_type
        attachment = GetAttachment(account=att1.parent_item.account).get(
            items=[att1.attachment_id],
            include_mime_content=True,
            body_type="Text",
            filter_html_content=None,
            additional_fields=[FieldPath(field=self.ITEM_CLASS.get_field_by_fieldname("body"))],
        )
        self.assertEqual(attachment.item.body, "Hello HTML\r\n")
        # Test filter_html_content. I wonder what unsafe HTML is.
        attachment = GetAttachment(account=att1.parent_item.account).get(
            items=[att1.attachment_id],
            include_mime_content=False,
            body_type="HTML",
            filter_html_content=True,
            additional_fields=[FieldPath(field=self.ITEM_CLASS.get_field_by_fieldname("body"))],
        )
        self.assertEqual(
            attachment.item.body.replace("\r\n", ""),
            '<html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8">'
            "</head><body>Hello HTML </body></html>",
        )

    def test_file_attachments(self):
        item = self.get_test_item(folder=self.test_folder)

        # Test __init__(attachments=...) and attach() on new item
        binary_file_content = "Hello from unicode æøå".encode("utf-8")
        att1 = FileAttachment(name="my_file_1.txt", content=binary_file_content)
        self.assertEqual(len(item.attachments), 0)
        item.attach(att1)
        self.assertEqual(len(item.attachments), 1)
        item.save()
        fresh_item = self.get_item_by_id(item)
        self.assertEqual(len(fresh_item.attachments), 1)
        fresh_attachments = sorted(fresh_item.attachments, key=lambda a: a.name)
        self.assertEqual(fresh_attachments[0].name, "my_file_1.txt")
        self.assertEqual(fresh_attachments[0].content, binary_file_content)

        # Test attach on saved object
        att2 = FileAttachment(name="my_file_2.txt", content=binary_file_content)
        self.assertEqual(len(item.attachments), 1)
        item.attach(att2)
        self.assertEqual(len(item.attachments), 2)
        fresh_item = self.get_item_by_id(item)
        self.assertEqual(len(fresh_item.attachments), 2)
        fresh_attachments = sorted(fresh_item.attachments, key=lambda a: a.name)
        self.assertEqual(fresh_attachments[0].name, "my_file_1.txt")
        self.assertEqual(fresh_attachments[0].content, binary_file_content)
        self.assertEqual(fresh_attachments[1].name, "my_file_2.txt")
        self.assertEqual(fresh_attachments[1].content, binary_file_content)

        # Test detach
        item.detach(att1)
        self.assertTrue(att1.attachment_id is None)
        self.assertTrue(att1.parent_item is None)
        fresh_item = self.get_item_by_id(item)
        self.assertEqual(len(fresh_item.attachments), 1)
        fresh_attachments = sorted(fresh_item.attachments, key=lambda a: a.name)
        self.assertEqual(fresh_attachments[0].name, "my_file_2.txt")
        self.assertEqual(fresh_attachments[0].content, binary_file_content)

    def test_streaming_file_attachments(self):
        item = self.get_test_item(folder=self.test_folder)
        large_binary_file_content = get_random_string(2**10).encode("utf-8")
        large_att = FileAttachment(name="my_large_file.txt", content=large_binary_file_content)
        item.attach(large_att)
        item.save()

        # Test streaming file content
        fresh_item = self.get_item_by_id(item)
        with fresh_item.attachments[0].fp as fp:
            self.assertEqual(fp.read(), large_binary_file_content)

        # Test partial reads of streaming file content
        fresh_item = self.get_item_by_id(item)
        with fresh_item.attachments[0].fp as fp:
            chunked_reads = []
            buffer = fp.read(7)
            while buffer:
                chunked_reads.append(buffer)
                buffer = fp.read(7)
            self.assertListEqual(chunked_reads, list(chunkify(large_binary_file_content, 7)))

    def test_streaming_file_attachment_error(self):
        # Test that we can parse XML error responses in streaming mode.

        # Try to stream an attachment with malformed ID
        item = self.get_test_item(folder=self.test_folder).save()
        att = FileAttachment(
            parent_item=item,
            attachment_id=AttachmentId(id="AAMk="),
            name="dummy.txt",
            content=b"",
        )
        with self.assertRaises(ErrorInvalidIdMalformed):
            with att.fp as fp:
                fp.read()

        # Try to stream a non-existent attachment
        att.attachment_id = None
        att.attach()
        att_id = att.attachment_id
        att.detach()
        att.parent_item = item
        att.attachment_id = att_id
        with self.assertRaises(ErrorInvalidAttachmentId):
            with att.fp as fp:
                fp.read()

    def test_empty_file_attachment(self):
        item = self.get_test_item(folder=self.test_folder)
        att1 = FileAttachment(name="empty_file.txt", content=b"")
        item.attach(att1)
        item.save()
        fresh_item = self.get_item_by_id(item)
        self.assertEqual(fresh_item.attachments[0].content, b"")

    def test_both_attachment_types(self):
        item = self.get_test_item(folder=self.test_folder)
        attached_item = self.get_test_item(folder=self.test_folder).save()
        item_attachment = ItemAttachment(name="item_attachment", item=attached_item)
        file_attachment = FileAttachment(name="file_attachment", content=b"file_attachment")
        item.attach(item_attachment)
        item.attach(file_attachment)
        item.save()

        fresh_item = self.get_item_by_id(item)
        self.assertSetEqual({a.name for a in fresh_item.attachments}, {"item_attachment", "file_attachment"})

    def test_recursive_attachments(self):
        # Test that we can handle an item which has an attached item, which has an attached item...
        item = self.get_test_item(folder=self.test_folder)
        attached_item_level_1 = self.get_test_item(folder=self.test_folder)
        attached_item_level_2 = self.get_test_item(folder=self.test_folder)
        attached_item_level_3 = self.get_test_item(folder=self.test_folder)

        attached_item_level_3.save()
        attachment_level_3 = ItemAttachment(name="attached_item_level_3", item=attached_item_level_3)
        attached_item_level_2.attach(attachment_level_3)
        attached_item_level_2.save()
        attachment_level_2 = ItemAttachment(name="attached_item_level_2", item=attached_item_level_2)
        attached_item_level_1.attach(attachment_level_2)
        attached_item_level_1.save()
        attachment_level_1 = ItemAttachment(name="attached_item_level_1", item=attached_item_level_1)
        item.attach(attachment_level_1)
        item.save()

        self.assertEqual(
            item.attachments[0].item.attachments[0].item.attachments[0].item.subject, attached_item_level_3.subject
        )

        # Also test a fresh item
        new_item = self.test_folder.get(id=item.id, changekey=item.changekey)
        self.assertEqual(
            new_item.attachments[0].item.attachments[0].item.attachments[0].item.subject, attached_item_level_3.subject
        )

    def test_detach_all(self):
        # Make sure that we can detach all by passing item.attachments
        item = self.get_test_item(folder=self.test_folder).save()
        item.attach([FileAttachment(name="empty_file.txt", content=b"") for _ in range(6)])
        self.assertEqual(len(item.attachments), 6)
        item.detach(item.attachments)
        self.assertEqual(len(item.attachments), 0)

    def test_detach_with_refresh(self):
        # Make sure that we can detach after refresh
        item = self.get_test_item(folder=self.test_folder).save()
        item.attach(FileAttachment(name="empty_file.txt", content=b""))
        item.refresh()
        item.detach(item.attachments)