diff --git a/tmail-backend/integration-tests/jmap/jmap-integration-tests-common/src/main/resources/emailWithAliceInviteBobIcsAttachment.eml b/tmail-backend/integration-tests/jmap/jmap-integration-tests-common/src/main/resources/emailWithAliceInviteBobIcsAttachment.eml new file mode 100644 index 0000000000..91a6348011 --- /dev/null +++ b/tmail-backend/integration-tests/jmap/jmap-integration-tests-common/src/main/resources/emailWithAliceInviteBobIcsAttachment.eml @@ -0,0 +1,66 @@ +To: Bob +From: Alice +Subject: Event Invitation from Alice +Message-ID: +Date: Tue, 5 Sep 2017 09:54:16 +0200 +MIME-Version: 1.0 +Content-Type: multipart/mixed; + boundary="------------D24E361990BDBA143D4D8794" +Content-Language: en-US + +This is a multi-part message in MIME format. +--------------D24E361990BDBA143D4D8794 +Content-Type: text/plain; charset=utf-8; format=flowed +Content-Transfer-Encoding: 7bit + +The message has a text attachment. + +--------------D24E361990BDBA143D4D8794 +Content-Type: text/calendar; charset=UTF-8; method=REQUEST +Content-Disposition: attachment; +Content-Transfer-Encoding: base64 + +QkVHSU46VkNBTEVOREFSClBST0RJRDotLy9BbGlhc291cmNlIEdyb3VwZSBMSU5BR09SQS8vT0JN +IENhbGVuZGFyIDMuMi4xLXJjMi8vRlIKQ0FMU0NBTEU6R1JFR09SSUFOClgtT0JNLVRJTUU6MTQ4 +MzcwMzQzNgpWRVJTSU9OOjIuMApNRVRIT0Q6UkVRVUVTVApCRUdJTjpWRVZFTlQKQ1JFQVRFRDoy +MDE3MDEwNlQxMTUwMzVaCkxBU1QtTU9ESUZJRUQ6MjAxNzAxMDZUMTE1MDM2WgpEVFNUQU1QOjIw +MTcwMTA2VDExNTAzNloKRFRTVEFSVDoyMDE3MDExMVQwOTAwMDBaCkRVUkFUSU9OOlBUMUgzME0K +VFpJRDpBc2lhL0hvX0NoaV9NaW5oClRSQU5TUDpPUEFRVUUKU0VRVUVOQ0U6MApTVU1NQVJZOlNw +cmludCBwbGFubmluZyAjMjMKREVTQ1JJUFRJT046IGRlc2NyaXB0aW9uIDEyMwpDTEFTUzpQVUJM +SUMKUFJJT1JJVFk6NQpPUkdBTklaRVI7WC1PQk0tSUQ9MTI4O0NOPVJhcGhhZWwgT1VBWkFOQTpN +QUlMVE86YWxpY2VAZG8ubWFpbi50bGQKWC1PUEVOUEFBUy1WSURFT0NPTkZFUkVOQ0U6aHR0cHM6 +Ly9qaXRzaS5saW5hZ29yYS5jb20vYWJjZApMT0NBVElPTjpIYW5nb3V0ClJSVUxFOkZSRVE9WUVB +UkxZO0JZTU9OVEg9MTA7QllEQVk9TU87QllTRVRQT1M9MSwyO1VOVElMPTIwMjQwMTExVDA5MDAw +MFoKRVhSVUxFOkZSRVE9WUVBUkxZO0JZTU9OVEg9MTA7QllEQVk9TU87QllTRVRQT1M9MTtVTlRJ +TD0yMDIzMDExMVQwOTAwMDBaCkNBVEVHT1JJRVM6ClVJRDplYTEyNzY5MC0wNDQwLTQwNGItYWY5 +OC05ODIzYzg1NWEyODMKQVRURU5ERUU7Q1VUWVBFPUlORElWSURVQUw7Uk9MRT1BRE1JTjtSU1ZQ +PVRSVUU7Q049TWF0dGhpZXUgRVhUX0JBRUNITEVSO1BBUlRTVEFUPU5FRQogRFMtQUNUSU9OO1gt +T0JNLUlEPTMwMjpNQUlMVE86YW5vdGhlckBkb21haW4udGxkCkFUVEVOREVFO0NVVFlQRT1JTkRJ +VklEVUFMO1JTVlA9VFJVRTtDTj1MYXVyYSBST1lFVDtQQVJUU1RBVD1ORUVEUy1BQ1RJT047CiBY +LU9CTS1JRD03MjM6TUFJTFRPOmJvYkBkb21haW4udGxkClNUQVRVUzpDT05GSVJNRUQKRU5EOlZF +VkVOVApFTkQ6VkNBTEVOREFSCg== +--------------D24E361990BDBA143D4D8794 +Content-Type: application/ics; name=meeting.ics +Content-Disposition: attachment; filename=meeting.ics +Content-Transfer-Encoding: base64 + +QkVHSU46VkNBTEVOREFSClBST0RJRDotLy9BbGlhc291cmNlIEdyb3VwZSBMSU5BR09SQS8vT0JN +IENhbGVuZGFyIDMuMi4xLXJjMi8vRlIKQ0FMU0NBTEU6R1JFR09SSUFOClgtT0JNLVRJTUU6MTQ4 +MzcwMzQzNgpWRVJTSU9OOjIuMApNRVRIT0Q6UkVRVUVTVApCRUdJTjpWRVZFTlQKQ1JFQVRFRDoy +MDE3MDEwNlQxMTUwMzVaCkxBU1QtTU9ESUZJRUQ6MjAxNzAxMDZUMTE1MDM2WgpEVFNUQU1QOjIw +MTcwMTA2VDExNTAzNloKRFRTVEFSVDoyMDE3MDExMVQwOTAwMDBaCkRVUkFUSU9OOlBUMUgzME0K +VFpJRDpBc2lhL0hvX0NoaV9NaW5oClRSQU5TUDpPUEFRVUUKU0VRVUVOQ0U6MApTVU1NQVJZOlNw +cmludCBwbGFubmluZyAjMjMKREVTQ1JJUFRJT046IGRlc2NyaXB0aW9uIDEyMwpDTEFTUzpQVUJM +SUMKUFJJT1JJVFk6NQpPUkdBTklaRVI7WC1PQk0tSUQ9MTI4O0NOPVJhcGhhZWwgT1VBWkFOQTpN +QUlMVE86YWxpY2VAZG8ubWFpbi50bGQKWC1PUEVOUEFBUy1WSURFT0NPTkZFUkVOQ0U6aHR0cHM6 +Ly9qaXRzaS5saW5hZ29yYS5jb20vYWJjZApMT0NBVElPTjpIYW5nb3V0ClJSVUxFOkZSRVE9WUVB +UkxZO0JZTU9OVEg9MTA7QllEQVk9TU87QllTRVRQT1M9MSwyO1VOVElMPTIwMjQwMTExVDA5MDAw +MFoKRVhSVUxFOkZSRVE9WUVBUkxZO0JZTU9OVEg9MTA7QllEQVk9TU87QllTRVRQT1M9MTtVTlRJ +TD0yMDIzMDExMVQwOTAwMDBaCkNBVEVHT1JJRVM6ClVJRDplYTEyNzY5MC0wNDQwLTQwNGItYWY5 +OC05ODIzYzg1NWEyODMKQVRURU5ERUU7Q1VUWVBFPUlORElWSURVQUw7Uk9MRT1BRE1JTjtSU1ZQ +PVRSVUU7Q049TWF0dGhpZXUgRVhUX0JBRUNITEVSO1BBUlRTVEFUPU5FRQogRFMtQUNUSU9OO1gt +T0JNLUlEPTMwMjpNQUlMVE86YW5vdGhlckBkb21haW4udGxkCkFUVEVOREVFO0NVVFlQRT1JTkRJ +VklEVUFMO1JTVlA9VFJVRTtDTj1MYXVyYSBST1lFVDtQQVJUU1RBVD1ORUVEUy1BQ1RJT047CiBY +LU9CTS1JRD03MjM6TUFJTFRPOmJvYkBkb21haW4udGxkClNUQVRVUzpDT05GSVJNRUQKRU5EOlZF +VkVOVApFTkQ6VkNBTEVOREFSCg== +--------------D24E361990BDBA143D4D8794-- diff --git a/tmail-backend/integration-tests/jmap/jmap-integration-tests-common/src/main/resources/emailWithAndreInviteBobIcsAttachment.eml b/tmail-backend/integration-tests/jmap/jmap-integration-tests-common/src/main/resources/emailWithAndreInviteBobIcsAttachment.eml new file mode 100644 index 0000000000..9d5a418340 --- /dev/null +++ b/tmail-backend/integration-tests/jmap/jmap-integration-tests-common/src/main/resources/emailWithAndreInviteBobIcsAttachment.eml @@ -0,0 +1,66 @@ +To: Bob +From: Andre +Subject: Event Invitation from Alice +Message-ID: +Date: Tue, 5 Sep 2017 09:54:16 +0200 +MIME-Version: 1.0 +Content-Type: multipart/mixed; + boundary="------------D24E361990BDBA143D4D8794" +Content-Language: en-US + +This is a multi-part message in MIME format. +--------------D24E361990BDBA143D4D8794 +Content-Type: text/plain; charset=utf-8; format=flowed +Content-Transfer-Encoding: 7bit + +The message has a text attachment. + +--------------D24E361990BDBA143D4D8794 +Content-Type: text/calendar; method=REQUEST +Content-Disposition: attachment; +Content-Transfer-Encoding: base64 + +QkVHSU46VkNBTEVOREFSClBST0RJRDotLy9BbGlhc291cmNlIEdyb3VwZSBMSU5BR09SQS8vT0JN +IENhbGVuZGFyIDMuMi4xLXJjMi8vRlIKQ0FMU0NBTEU6R1JFR09SSUFOClgtT0JNLVRJTUU6MTQ4 +MzcwMzQzNgpWRVJTSU9OOjIuMApNRVRIT0Q6UkVRVUVTVApCRUdJTjpWRVZFTlQKQ1JFQVRFRDoy +MDE3MDEwNlQxMTUwMzVaCkxBU1QtTU9ESUZJRUQ6MjAxNzAxMDZUMTE1MDM2WgpEVFNUQU1QOjIw +MTcwMTA2VDExNTAzNloKRFRTVEFSVDoyMDE3MDExMVQwOTAwMDBaCkRVUkFUSU9OOlBUMUgzME0K +VFpJRDpBc2lhL0hvX0NoaV9NaW5oClRSQU5TUDpPUEFRVUUKU0VRVUVOQ0U6MApTVU1NQVJZOlNw +cmludCBwbGFubmluZyAjMjMKREVTQ1JJUFRJT046IGRlc2NyaXB0aW9uIDEyMwpDTEFTUzpQVUJM +SUMKUFJJT1JJVFk6NQpPUkdBTklaRVI7WC1PQk0tSUQ9MTI4O0NOPVJhcGhhZWwgT1VBWkFOQTpN +QUlMVE86YW5kcmVAZG9tYWluLnRsZApYLU9QRU5QQUFTLVZJREVPQ09ORkVSRU5DRTpodHRwczov +L2ppdHNpLmxpbmFnb3JhLmNvbS9hYmNkCkxPQ0FUSU9OOkhhbmdvdXQKUlJVTEU6RlJFUT1ZRUFS +TFk7QllNT05USD0xMDtCWURBWT1NTztCWVNFVFBPUz0xLDI7VU5USUw9MjAyNDAxMTFUMDkwMDAw +WgpFWFJVTEU6RlJFUT1ZRUFSTFk7QllNT05USD0xMDtCWURBWT1NTztCWVNFVFBPUz0xO1VOVElM +PTIwMjMwMTExVDA5MDAwMFoKQ0FURUdPUklFUzoKVUlEOmVhMTI3NjkwLTA0NDAtNDA0Yi1hZjk4 +LTk4MjNjODU1YTI4MwpBVFRFTkRFRTtDVVRZUEU9SU5ESVZJRFVBTDtST0xFPUFETUlOO1JTVlA9 +VFJVRTtDTj1NYXR0aGlldSBFWFRfQkFFQ0hMRVI7UEFSVFNUQVQ9TkVFCiBEUy1BQ1RJT047WC1P +Qk0tSUQ9MzAyOk1BSUxUTzphbm90aGVyQGRvbWFpbi50bGQKQVRURU5ERUU7Q1VUWVBFPUlORElW +SURVQUw7UlNWUD1UUlVFO0NOPUJPQjtQQVJUU1RBVD1ORUVEUy1BQ1RJT047CiBYLU9CTS1JRD03 +MjM6TUFJTFRPOmJvYkBkb21haW4udGxkClNUQVRVUzpDT05GSVJNRUQKRU5EOlZFVkVOVApFTkQ6 +VkNBTEVOREFSCg== +--------------D24E361990BDBA143D4D8794 +Content-Type: application/ics; name=meeting.ics +Content-Disposition: attachment; filename=meeting.ics +Content-Transfer-Encoding: base64 + +QkVHSU46VkNBTEVOREFSClBST0RJRDotLy9BbGlhc291cmNlIEdyb3VwZSBMSU5BR09SQS8vT0JN +IENhbGVuZGFyIDMuMi4xLXJjMi8vRlIKQ0FMU0NBTEU6R1JFR09SSUFOClgtT0JNLVRJTUU6MTQ4 +MzcwMzQzNgpWRVJTSU9OOjIuMApNRVRIT0Q6UkVRVUVTVApCRUdJTjpWRVZFTlQKQ1JFQVRFRDoy +MDE3MDEwNlQxMTUwMzVaCkxBU1QtTU9ESUZJRUQ6MjAxNzAxMDZUMTE1MDM2WgpEVFNUQU1QOjIw +MTcwMTA2VDExNTAzNloKRFRTVEFSVDoyMDE3MDExMVQwOTAwMDBaCkRVUkFUSU9OOlBUMUgzME0K +VFpJRDpBc2lhL0hvX0NoaV9NaW5oClRSQU5TUDpPUEFRVUUKU0VRVUVOQ0U6MApTVU1NQVJZOlNw +cmludCBwbGFubmluZyAjMjMKREVTQ1JJUFRJT046IGRlc2NyaXB0aW9uIDEyMwpDTEFTUzpQVUJM +SUMKUFJJT1JJVFk6NQpPUkdBTklaRVI7WC1PQk0tSUQ9MTI4O0NOPVJhcGhhZWwgT1VBWkFOQTpN +QUlMVE86YW5kcmVAZG9tYWluLnRsZApYLU9QRU5QQUFTLVZJREVPQ09ORkVSRU5DRTpodHRwczov +L2ppdHNpLmxpbmFnb3JhLmNvbS9hYmNkCkxPQ0FUSU9OOkhhbmdvdXQKUlJVTEU6RlJFUT1ZRUFS +TFk7QllNT05USD0xMDtCWURBWT1NTztCWVNFVFBPUz0xLDI7VU5USUw9MjAyNDAxMTFUMDkwMDAw +WgpFWFJVTEU6RlJFUT1ZRUFSTFk7QllNT05USD0xMDtCWURBWT1NTztCWVNFVFBPUz0xO1VOVElM +PTIwMjMwMTExVDA5MDAwMFoKQ0FURUdPUklFUzoKVUlEOmVhMTI3NjkwLTA0NDAtNDA0Yi1hZjk4 +LTk4MjNjODU1YTI4MwpBVFRFTkRFRTtDVVRZUEU9SU5ESVZJRFVBTDtST0xFPUFETUlOO1JTVlA9 +VFJVRTtDTj1NYXR0aGlldSBFWFRfQkFFQ0hMRVI7UEFSVFNUQVQ9TkVFCiBEUy1BQ1RJT047WC1P +Qk0tSUQ9MzAyOk1BSUxUTzphbm90aGVyQGRvbWFpbi50bGQKQVRURU5ERUU7Q1VUWVBFPUlORElW +SURVQUw7UlNWUD1UUlVFO0NOPUJPQjtQQVJUU1RBVD1ORUVEUy1BQ1RJT047CiBYLU9CTS1JRD03 +MjM6TUFJTFRPOmJvYkBkb21haW4udGxkClNUQVRVUzpDT05GSVJNRUQKRU5EOlZFVkVOVApFTkQ6 +VkNBTEVOREFSCg== +--------------D24E361990BDBA143D4D8794-- diff --git a/tmail-backend/integration-tests/jmap/jmap-integration-tests-common/src/main/resources/emailWithIcsMissingAttendee.eml b/tmail-backend/integration-tests/jmap/jmap-integration-tests-common/src/main/resources/emailWithIcsMissingAttendee.eml new file mode 100644 index 0000000000..66257edc32 --- /dev/null +++ b/tmail-backend/integration-tests/jmap/jmap-integration-tests-common/src/main/resources/emailWithIcsMissingAttendee.eml @@ -0,0 +1,48 @@ +To: Bob +From: Alice +Subject: Event Invitation from Alice +Message-ID: +Date: Tue, 5 Sep 2017 09:54:16 +0200 +MIME-Version: 1.0 +Content-Type: multipart/mixed; + boundary="------------D24E361990BDBA143D4D8794" +Content-Language: en-US + +This is a multi-part message in MIME format. +--------------D24E361990BDBA143D4D8794 +Content-Type: text/plain; charset=utf-8; format=flowed +Content-Transfer-Encoding: 7bit + +The message has a text attachment. + +--------------D24E361990BDBA143D4D8794 +Content-Type: text/calendar; charset=UTF-8; method=REQUEST +Content-Disposition: attachment; +Content-Transfer-Encoding: base64 + +QkVHSU46VkNBTEVOREFSClZFUlNJT046Mi4wClBST0RJRDotLy9TYWJyZS8vU2FicmUgVk9iamVj +dCA0LjEuMy8vRU4KQ0FMU0NBTEU6R1JFR09SSUFOCk1FVEhPRDpSRVFVRVNUCkJFR0lOOlZUSU1F +Wk9ORQpUWklEOkFzaWEvSG9fQ2hpX01pbmgKQkVHSU46U1RBTkRBUkQKVFpPRkZTRVRGUk9NOisw +NzAwClRaT0ZGU0VUVE86KzA3MDAKVFpOQU1FOklDVApEVFNUQVJUOjE5NzAwMTAxVDAwMDAwMApF +TkQ6U1RBTkRBUkQKRU5EOlZUSU1FWk9ORQpCRUdJTjpWRVZFTlQKVUlEOjAzN2FhYWQzLTE3Yzkt +NDdjOC1iZDZiLWYyY2JmZTkyNWVmNwpUUkFOU1A6T1BBUVVFCkRUU1RBUlQ7VFpJRD1Bc2lhL0hv +X0NoaV9NaW5oOjIwMjMwMzA5VDE0MDAwMApDTEFTUzpDT05GSURFTlRJQUwKWC1PUEVOUEFBUy1W +SURFT0NPTkZFUkVOQ0U6ClNVTU1BUlk6TU9COiBpbnRlZ3JhdGlvbiB0ZXN0cwpPUkdBTklaRVI7 +Q049QmVub8OudCBURUxMSUVSOm1haWx0bzpidGVsbGllckBkb21haW4udGxkCkRUU1RBTVA6MjAy +MzAzMDZUMDc0MTMzWgpTRVFVRU5DRTowCkVORDpWRVZFTlQKRU5EOlZDQUxFTkRBUgo= +--------------D24E361990BDBA143D4D8794 +Content-Type: application/ics; name=meeting.ics +Content-Disposition: attachment; filename=meeting.ics +Content-Transfer-Encoding: base64 + +QkVHSU46VkNBTEVOREFSClZFUlNJT046Mi4wClBST0RJRDotLy9TYWJyZS8vU2FicmUgVk9iamVj +dCA0LjEuMy8vRU4KQ0FMU0NBTEU6R1JFR09SSUFOCk1FVEhPRDpSRVFVRVNUCkJFR0lOOlZUSU1F +Wk9ORQpUWklEOkFzaWEvSG9fQ2hpX01pbmgKQkVHSU46U1RBTkRBUkQKVFpPRkZTRVRGUk9NOisw +NzAwClRaT0ZGU0VUVE86KzA3MDAKVFpOQU1FOklDVApEVFNUQVJUOjE5NzAwMTAxVDAwMDAwMApF +TkQ6U1RBTkRBUkQKRU5EOlZUSU1FWk9ORQpCRUdJTjpWRVZFTlQKVUlEOjAzN2FhYWQzLTE3Yzkt +NDdjOC1iZDZiLWYyY2JmZTkyNWVmNwpUUkFOU1A6T1BBUVVFCkRUU1RBUlQ7VFpJRD1Bc2lhL0hv +X0NoaV9NaW5oOjIwMjMwMzA5VDE0MDAwMApDTEFTUzpDT05GSURFTlRJQUwKWC1PUEVOUEFBUy1W +SURFT0NPTkZFUkVOQ0U6ClNVTU1BUlk6TU9COiBpbnRlZ3JhdGlvbiB0ZXN0cwpPUkdBTklaRVI7 +Q049QmVub8OudCBURUxMSUVSOm1haWx0bzpidGVsbGllckBkb21haW4udGxkCkRUU1RBTVA6MjAy +MzAzMDZUMDc0MTMzWgpTRVFVRU5DRTowCkVORDpWRVZFTlQKRU5EOlZDQUxFTkRBUgo= +--------------D24E361990BDBA143D4D8794-- diff --git a/tmail-backend/integration-tests/jmap/jmap-integration-tests-common/src/main/resources/emailWithIcsMissingMethod.eml b/tmail-backend/integration-tests/jmap/jmap-integration-tests-common/src/main/resources/emailWithIcsMissingMethod.eml new file mode 100644 index 0000000000..617b944bb5 --- /dev/null +++ b/tmail-backend/integration-tests/jmap/jmap-integration-tests-common/src/main/resources/emailWithIcsMissingMethod.eml @@ -0,0 +1,66 @@ +To: Bob +From: Alice +Subject: Event Invitation from Alice +Message-ID: +Date: Tue, 5 Sep 2017 09:54:16 +0200 +MIME-Version: 1.0 +Content-Type: multipart/mixed; + boundary="------------D24E361990BDBA143D4D8794" +Content-Language: en-US + +This is a multi-part message in MIME format. +--------------D24E361990BDBA143D4D8794 +Content-Type: text/plain; charset=utf-8; format=flowed +Content-Transfer-Encoding: 7bit + +The message has a text attachment. + +--------------D24E361990BDBA143D4D8794 +Content-Type: text/calendar; charset=UTF-8; method=REQUEST +Content-Disposition: attachment; +Content-Transfer-Encoding: base64 + +QkVHSU46VkNBTEVOREFSClBST0RJRDotLy9BbGlhc291cmNlIEdyb3VwZSBMSU5BR09SQS8vT0JN +IENhbGVuZGFyIDMuMi4xLXJjMi8vRlIKQ0FMU0NBTEU6R1JFR09SSUFOClgtT0JNLVRJTUU6MTQ4 +MzcwMzQzNgpWRVJTSU9OOjIuMApCRUdJTjpWRVZFTlQKQ1JFQVRFRDoyMDE3MDEwNlQxMTUwMzVa +CkxBU1QtTU9ESUZJRUQ6MjAxNzAxMDZUMTE1MDM2WgpEVFNUQU1QOjIwMTcwMTA2VDExNTAzNloK +RFRTVEFSVDoyMDE3MDExMVQwOTAwMDBaCkRVUkFUSU9OOlBUMUgzME0KVFpJRDpBc2lhL0hvX0No +aV9NaW5oClRSQU5TUDpPUEFRVUUKU0VRVUVOQ0U6MApTVU1NQVJZOlNwcmludCBwbGFubmluZyAj +MjMKREVTQ1JJUFRJT046IGRlc2NyaXB0aW9uIDEyMwpDTEFTUzpQVUJMSUMKUFJJT1JJVFk6NQpP +UkdBTklaRVI7WC1PQk0tSUQ9MTI4O0NOPVJhcGhhZWwgT1VBWkFOQTpNQUlMVE86YWxpY2VAZG8u +bWFpbi50bGQKWC1PUEVOUEFBUy1WSURFT0NPTkZFUkVOQ0U6aHR0cHM6Ly9qaXRzaS5saW5hZ29y +YS5jb20vYWJjZApMT0NBVElPTjpIYW5nb3V0ClJSVUxFOkZSRVE9WUVBUkxZO0JZTU9OVEg9MTA7 +QllEQVk9TU87QllTRVRQT1M9MSwyO1VOVElMPTIwMjQwMTExVDA5MDAwMFoKRVhSVUxFOkZSRVE9 +WUVBUkxZO0JZTU9OVEg9MTA7QllEQVk9TU87QllTRVRQT1M9MTtVTlRJTD0yMDIzMDExMVQwOTAw +MDBaCkNBVEVHT1JJRVM6ClVJRDplYTEyNzY5MC0wNDQwLTQwNGItYWY5OC05ODIzYzg1NWEyODMK +QVRURU5ERUU7Q1VUWVBFPUlORElWSURVQUw7Uk9MRT1BRE1JTjtSU1ZQPVRSVUU7Q049TWF0dGhp +ZXUgRVhUX0JBRUNITEVSO1BBUlRTVEFUPU5FRQogRFMtQUNUSU9OO1gtT0JNLUlEPTMwMjpNQUlM +VE86YW5vdGhlckBkb21haW4udGxkCkFUVEVOREVFO0NVVFlQRT1JTkRJVklEVUFMO1JTVlA9VFJV +RTtDTj1MYXVyYSBST1lFVDtQQVJUU1RBVD1ORUVEUy1BQ1RJT047CiBYLU9CTS1JRD03MjM6TUFJ +TFRPOmJvYkBkb21haW4udGxkClNUQVRVUzpDT05GSVJNRUQKRU5EOlZFVkVOVApFTkQ6VkNBTEVO +REFSCg== +--------------D24E361990BDBA143D4D8794 +Content-Type: application/ics; name=meeting.ics +Content-Disposition: attachment; filename=meeting.ics +Content-Transfer-Encoding: base64 + +QkVHSU46VkNBTEVOREFSClBST0RJRDotLy9BbGlhc291cmNlIEdyb3VwZSBMSU5BR09SQS8vT0JN +IENhbGVuZGFyIDMuMi4xLXJjMi8vRlIKQ0FMU0NBTEU6R1JFR09SSUFOClgtT0JNLVRJTUU6MTQ4 +MzcwMzQzNgpWRVJTSU9OOjIuMApCRUdJTjpWRVZFTlQKQ1JFQVRFRDoyMDE3MDEwNlQxMTUwMzVa +CkxBU1QtTU9ESUZJRUQ6MjAxNzAxMDZUMTE1MDM2WgpEVFNUQU1QOjIwMTcwMTA2VDExNTAzNloK +RFRTVEFSVDoyMDE3MDExMVQwOTAwMDBaCkRVUkFUSU9OOlBUMUgzME0KVFpJRDpBc2lhL0hvX0No +aV9NaW5oClRSQU5TUDpPUEFRVUUKU0VRVUVOQ0U6MApTVU1NQVJZOlNwcmludCBwbGFubmluZyAj +MjMKREVTQ1JJUFRJT046IGRlc2NyaXB0aW9uIDEyMwpDTEFTUzpQVUJMSUMKUFJJT1JJVFk6NQpP +UkdBTklaRVI7WC1PQk0tSUQ9MTI4O0NOPVJhcGhhZWwgT1VBWkFOQTpNQUlMVE86YWxpY2VAZG8u +bWFpbi50bGQKWC1PUEVOUEFBUy1WSURFT0NPTkZFUkVOQ0U6aHR0cHM6Ly9qaXRzaS5saW5hZ29y +YS5jb20vYWJjZApMT0NBVElPTjpIYW5nb3V0ClJSVUxFOkZSRVE9WUVBUkxZO0JZTU9OVEg9MTA7 +QllEQVk9TU87QllTRVRQT1M9MSwyO1VOVElMPTIwMjQwMTExVDA5MDAwMFoKRVhSVUxFOkZSRVE9 +WUVBUkxZO0JZTU9OVEg9MTA7QllEQVk9TU87QllTRVRQT1M9MTtVTlRJTD0yMDIzMDExMVQwOTAw +MDBaCkNBVEVHT1JJRVM6ClVJRDplYTEyNzY5MC0wNDQwLTQwNGItYWY5OC05ODIzYzg1NWEyODMK +QVRURU5ERUU7Q1VUWVBFPUlORElWSURVQUw7Uk9MRT1BRE1JTjtSU1ZQPVRSVUU7Q049TWF0dGhp +ZXUgRVhUX0JBRUNITEVSO1BBUlRTVEFUPU5FRQogRFMtQUNUSU9OO1gtT0JNLUlEPTMwMjpNQUlM +VE86YW5vdGhlckBkb21haW4udGxkCkFUVEVOREVFO0NVVFlQRT1JTkRJVklEVUFMO1JTVlA9VFJV +RTtDTj1MYXVyYSBST1lFVDtQQVJUU1RBVD1ORUVEUy1BQ1RJT047CiBYLU9CTS1JRD03MjM6TUFJ +TFRPOmJvYkBkb21haW4udGxkClNUQVRVUzpDT05GSVJNRUQKRU5EOlZFVkVOVApFTkQ6VkNBTEVO +REFSCg== +--------------D24E361990BDBA143D4D8794-- diff --git a/tmail-backend/integration-tests/jmap/jmap-integration-tests-common/src/main/resources/emailWithIcsMissingOrginizer.eml b/tmail-backend/integration-tests/jmap/jmap-integration-tests-common/src/main/resources/emailWithIcsMissingOrginizer.eml new file mode 100644 index 0000000000..b8f9a604a5 --- /dev/null +++ b/tmail-backend/integration-tests/jmap/jmap-integration-tests-common/src/main/resources/emailWithIcsMissingOrginizer.eml @@ -0,0 +1,52 @@ +To: Bob +From: Alice +Subject: Event Invitation from Alice +Message-ID: +Date: Tue, 5 Sep 2017 09:54:16 +0200 +MIME-Version: 1.0 +Content-Type: multipart/mixed; + boundary="------------D24E361990BDBA143D4D8794" +Content-Language: en-US + +This is a multi-part message in MIME format. +--------------D24E361990BDBA143D4D8794 +Content-Type: text/plain; charset=utf-8; format=flowed +Content-Transfer-Encoding: 7bit + +The message has a text attachment. + +--------------D24E361990BDBA143D4D8794 +Content-Type: text/calendar; charset=UTF-8; method=REQUEST +Content-Disposition: attachment; +Content-Transfer-Encoding: base64 + +QkVHSU46VkNBTEVOREFSClZFUlNJT046Mi4wClBST0RJRDotLy9TYWJyZS8vU2FicmUgVk9iamVj +dCA0LjEuMy8vRU4KQ0FMU0NBTEU6R1JFR09SSUFOCk1FVEhPRDpSRVFVRVNUCkJFR0lOOlZUSU1F +Wk9ORQpUWklEOkFzaWEvSG9fQ2hpX01pbmgKQkVHSU46U1RBTkRBUkQKVFpPRkZTRVRGUk9NOisw +NzAwClRaT0ZGU0VUVE86KzA3MDAKVFpOQU1FOklDVApEVFNUQVJUOjE5NzAwMTAxVDAwMDAwMApF +TkQ6U1RBTkRBUkQKRU5EOlZUSU1FWk9ORQpCRUdJTjpWRVZFTlQKVUlEOjAzN2FhYWQzLTE3Yzkt +NDdjOC1iZDZiLWYyY2JmZTkyNWVmNwpUUkFOU1A6T1BBUVVFCkRUU1RBUlQ7VFpJRD1Bc2lhL0hv +X0NoaV9NaW5oOjIwMjMwMzA5VDE0MDAwMApDTEFTUzpDT05GSURFTlRJQUwKWC1PUEVOUEFBUy1W +SURFT0NPTkZFUkVOQ0U6ClNVTU1BUlk6TU9COiBpbnRlZ3JhdGlvbiB0ZXN0cwpBVFRFTkRFRTtQ +QVJUU1RBVD1ORUVEUy1BQ1RJT047UlNWUD1UUlVFO1JPTEU9UkVRLVBBUlRJQ0lQQU5UO0NVVFlQ +RT1JTkRJVkkKIERVQUw7Q049VmFuIFR1bmcgVFJBTjptYWlsdG86dnR0cmFuQGRvbWFpbi50bGQK +RFRTVEFNUDoyMDIzMDMwNlQwNzQxMzNaClNFUVVFTkNFOjAKRU5EOlZFVkVOVApFTkQ6VkNBTEVO +REFSCg== +--------------D24E361990BDBA143D4D8794 +Content-Type: application/ics; name=meeting.ics +Content-Disposition: attachment; filename=meeting.ics +Content-Transfer-Encoding: base64 + +QkVHSU46VkNBTEVOREFSClZFUlNJT046Mi4wClBST0RJRDotLy9TYWJyZS8vU2FicmUgVk9iamVj +dCA0LjEuMy8vRU4KQ0FMU0NBTEU6R1JFR09SSUFOCk1FVEhPRDpSRVFVRVNUCkJFR0lOOlZUSU1F +Wk9ORQpUWklEOkFzaWEvSG9fQ2hpX01pbmgKQkVHSU46U1RBTkRBUkQKVFpPRkZTRVRGUk9NOisw +NzAwClRaT0ZGU0VUVE86KzA3MDAKVFpOQU1FOklDVApEVFNUQVJUOjE5NzAwMTAxVDAwMDAwMApF +TkQ6U1RBTkRBUkQKRU5EOlZUSU1FWk9ORQpCRUdJTjpWRVZFTlQKVUlEOjAzN2FhYWQzLTE3Yzkt +NDdjOC1iZDZiLWYyY2JmZTkyNWVmNwpUUkFOU1A6T1BBUVVFCkRUU1RBUlQ7VFpJRD1Bc2lhL0hv +X0NoaV9NaW5oOjIwMjMwMzA5VDE0MDAwMApDTEFTUzpDT05GSURFTlRJQUwKWC1PUEVOUEFBUy1W +SURFT0NPTkZFUkVOQ0U6ClNVTU1BUlk6TU9COiBpbnRlZ3JhdGlvbiB0ZXN0cwpBVFRFTkRFRTtQ +QVJUU1RBVD1ORUVEUy1BQ1RJT047UlNWUD1UUlVFO1JPTEU9UkVRLVBBUlRJQ0lQQU5UO0NVVFlQ +RT1JTkRJVkkKIERVQUw7Q049VmFuIFR1bmcgVFJBTjptYWlsdG86dnR0cmFuQGRvbWFpbi50bGQK +RFRTVEFNUDoyMDIzMDMwNlQwNzQxMzNaClNFUVVFTkNFOjAKRU5EOlZFVkVOVApFTkQ6VkNBTEVO +REFSCg== +--------------D24E361990BDBA143D4D8794-- diff --git a/tmail-backend/integration-tests/jmap/jmap-integration-tests-common/src/main/resources/emailWithIcsMissingVEVENT.eml b/tmail-backend/integration-tests/jmap/jmap-integration-tests-common/src/main/resources/emailWithIcsMissingVEVENT.eml new file mode 100644 index 0000000000..60dfee908c --- /dev/null +++ b/tmail-backend/integration-tests/jmap/jmap-integration-tests-common/src/main/resources/emailWithIcsMissingVEVENT.eml @@ -0,0 +1,38 @@ +To: Bob +From: Alice +Subject: Event Invitation from Alice +Message-ID: +Date: Tue, 5 Sep 2017 09:54:16 +0200 +MIME-Version: 1.0 +Content-Type: multipart/mixed; + boundary="------------D24E361990BDBA143D4D8794" +Content-Language: en-US + +This is a multi-part message in MIME format. +--------------D24E361990BDBA143D4D8794 +Content-Type: text/plain; charset=utf-8; format=flowed +Content-Transfer-Encoding: 7bit + +The message has a text attachment. + +--------------D24E361990BDBA143D4D8794 +Content-Type: text/calendar; charset=UTF-8; method=REQUEST +Content-Disposition: attachment; +Content-Transfer-Encoding: base64 + +QkVHSU46VkNBTEVOREFSClZFUlNJT046Mi4wClBST0RJRDotLy9TYWJyZS8vU2FicmUgVk9iamVj +dCA0LjEuMy8vRU4KQ0FMU0NBTEU6R1JFR09SSUFOCk1FVEhPRDpSRVFVRVNUCkJFR0lOOlZUSU1F +Wk9ORQpUWklEOkFzaWEvSG9fQ2hpX01pbmgKQkVHSU46U1RBTkRBUkQKVFpPRkZTRVRGUk9NOisw +NzAwClRaT0ZGU0VUVE86KzA3MDAKVFpOQU1FOklDVApEVFNUQVJUOjE5NzAwMTAxVDAwMDAwMApF +TkQ6U1RBTkRBUkQKRU5EOlZUSU1FWk9ORQpFTkQ6VkNBTEVOREFSCg== +--------------D24E361990BDBA143D4D8794 +Content-Type: application/ics; name=meeting.ics +Content-Disposition: attachment; filename=meeting.ics +Content-Transfer-Encoding: base64 + +QkVHSU46VkNBTEVOREFSClZFUlNJT046Mi4wClBST0RJRDotLy9TYWJyZS8vU2FicmUgVk9iamVj +dCA0LjEuMy8vRU4KQ0FMU0NBTEU6R1JFR09SSUFOCk1FVEhPRDpSRVFVRVNUCkJFR0lOOlZUSU1F +Wk9ORQpUWklEOkFzaWEvSG9fQ2hpX01pbmgKQkVHSU46U1RBTkRBUkQKVFpPRkZTRVRGUk9NOisw +NzAwClRaT0ZGU0VUVE86KzA3MDAKVFpOQU1FOklDVApEVFNUQVJUOjE5NzAwMTAxVDAwMDAwMApF +TkQ6U1RBTkRBUkQKRU5EOlZUSU1FWk9ORQpFTkQ6VkNBTEVOREFSCg== +--------------D24E361990BDBA143D4D8794-- diff --git a/tmail-backend/integration-tests/jmap/jmap-integration-tests-common/src/main/resources/emailWithTwoInvalidIcsAttachments.eml b/tmail-backend/integration-tests/jmap/jmap-integration-tests-common/src/main/resources/emailWithTwoInvalidIcsAttachments.eml new file mode 100644 index 0000000000..8c156033f8 --- /dev/null +++ b/tmail-backend/integration-tests/jmap/jmap-integration-tests-common/src/main/resources/emailWithTwoInvalidIcsAttachments.eml @@ -0,0 +1,114 @@ +To: Bob +From: Alice +Subject: Event Invitation from Alice +Message-ID: +Date: Tue, 5 Sep 2017 09:54:16 +0200 +MIME-Version: 1.0 +Content-Type: multipart/mixed; + boundary="------------D24E361990BDBA143D4D8794" +Content-Language: en-US + +This is a multi-part message in MIME format. +--------------D24E361990BDBA143D4D8794 +Content-Type: text/plain; charset=utf-8; format=flowed +Content-Transfer-Encoding: 7bit + +The message has a text attachment. + +--------------D24E361990BDBA143D4D8794 +Content-Type: text/calendar; charset=UTF-8; method=REQUEST +Content-Disposition: attachment; +Content-Transfer-Encoding: base64 + +QkVHSU46VkNBTEVOREFSClBST0RJRDotLy9BbGlhc291cmNlIEdyb3VwZSBMSU5BR09SQS8vT0JN +IENhbGVuZGFyIDMuMi4xLXJjMi8vRlIKQ0FMU0NBTEU6R1JFR09SSUFOClgtT0JNLVRJTUU6MTQ4 +MzcwMzQzNgpWRVJTSU9OOjIuMApNRVRIT0Q6UkVRVUVTVApCRUdJTjpWRVZFTlQKQ1JFQVRFRDoy +MDE3MDEwNlQxMTUwMzVaCkxBU1QtTU9ESUZJRUQ6MjAxNzAxMDZUMTE1MDM2WgpEVFNUQU1QOjIw +MTcwMTA2VDExNTAzNloKRFRTVEFSVDoyMDE3MDExMVQwOTAwMDBaCkRVUkFUSU9OOlBUMUgzME0K +VFpJRDpBc2lhL0hvX0NoaV9NaW5oClNFUVVFTkNFOjAKU1VNTUFSWTpTcHJpbnQgcGxhbm5pbmcg +IzIzCkRFU0NSSVBUSU9OOiBkZXNjcmlwdGlvbiAxMjMKQ0xBU1M6UFVCTElDClBSSU9SSVRZOjUK +T1JHQU5JWkVSO1gtT0JNLUlEPTEyODtDTj1SYXBoYWVsIE9VQVpBTkE6TUFJTFRPOm91YXphbmFA +ZG9tYWluLnRsZApYLU9QRU5QQUFTLVZJREVPQ09ORkVSRU5DRTpodHRwczovL2ppdHNpLmxpbmFn +b3JhLmNvbS9hYmNkCkxPQ0FUSU9OOkhhbmdvdXQKUlJVTEU6RlJFUT1ZRUFSTFk7QllNT05USD0x +MDtCWURBWT1NTztCWVNFVFBPUz0xLDI7VU5USUw9MjAyNDAxMTFUMDkwMDAwWgpFWFJVTEU6RlJF +UT1ZRUFSTFk7QllNT05USD0xMDtCWURBWT1NTztCWVNFVFBPUz0xO1VOVElMPTIwMjMwMTExVDA5 +MDAwMFoKQ0FURUdPUklFUzoKVUlEOmVhMTI3NjkwLTA0NDAtNDA0Yi1hZjk4LTk4MjNjODU1YTI4 +MwpBVFRFTkRFRTtDVVRZUEU9SU5ESVZJRFVBTDtST0xFPUFETUlOO1JTVlA9VFJVRTtDTj1NYXR0 +aGlldSBFWFRfQkFFQ0hMRVI7UEFSVFNUQVQ9TkVFCiBEUy1BQ1RJT047WC1PQk0tSUQ9MzAyOk1B +SUxUTzpiYWVjaGxlckBkb21haW4udGxkCkFUVEVOREVFO0NVVFlQRT1JTkRJVklEVUFMO1JTVlA9 +VFJVRTtDTj1MYXVyYSBST1lFVDtQQVJUU1RBVD1ORUVEUy1BQ1RJT047CiBYLU9CTS1JRD03MjM6 +TUFJTFRPOnJveWV0QGRvbWFpbi50bGQKU1RBVFVTOklOVkFMSURfVkFMVUUKRU5EOlZFVkVOVApF +TkQ6VkNBTEVOREFSCg== +--------------D24E361990BDBA143D4D8794 +Content-Type: application/ics; name=meeting.ics +Content-Disposition: attachment; filename=meeting.ics +Content-Transfer-Encoding: base64 + +QkVHSU46VkNBTEVOREFSClBST0RJRDotLy9BbGlhc291cmNlIEdyb3VwZSBMSU5BR09SQS8vT0JN +IENhbGVuZGFyIDMuMi4xLXJjMi8vRlIKQ0FMU0NBTEU6R1JFR09SSUFOClgtT0JNLVRJTUU6MTQ4 +MzcwMzQzNgpWRVJTSU9OOjIuMApNRVRIT0Q6UkVRVUVTVApCRUdJTjpWRVZFTlQKQ1JFQVRFRDoy +MDE3MDEwNlQxMTUwMzVaCkxBU1QtTU9ESUZJRUQ6MjAxNzAxMDZUMTE1MDM2WgpEVFNUQU1QOjIw +MTcwMTA2VDExNTAzNloKRFRTVEFSVDoyMDE3MDExMVQwOTAwMDBaCkRVUkFUSU9OOlBUMUgzME0K +VFpJRDpBc2lhL0hvX0NoaV9NaW5oClNFUVVFTkNFOjAKU1VNTUFSWTpTcHJpbnQgcGxhbm5pbmcg +IzIzCkRFU0NSSVBUSU9OOiBkZXNjcmlwdGlvbiAxMjMKQ0xBU1M6UFVCTElDClBSSU9SSVRZOjUK +T1JHQU5JWkVSO1gtT0JNLUlEPTEyODtDTj1SYXBoYWVsIE9VQVpBTkE6TUFJTFRPOm91YXphbmFA +ZG9tYWluLnRsZApYLU9QRU5QQUFTLVZJREVPQ09ORkVSRU5DRTpodHRwczovL2ppdHNpLmxpbmFn +b3JhLmNvbS9hYmNkCkxPQ0FUSU9OOkhhbmdvdXQKUlJVTEU6RlJFUT1ZRUFSTFk7QllNT05USD0x +MDtCWURBWT1NTztCWVNFVFBPUz0xLDI7VU5USUw9MjAyNDAxMTFUMDkwMDAwWgpFWFJVTEU6RlJF +UT1ZRUFSTFk7QllNT05USD0xMDtCWURBWT1NTztCWVNFVFBPUz0xO1VOVElMPTIwMjMwMTExVDA5 +MDAwMFoKQ0FURUdPUklFUzoKVUlEOmVhMTI3NjkwLTA0NDAtNDA0Yi1hZjk4LTk4MjNjODU1YTI4 +MwpBVFRFTkRFRTtDVVRZUEU9SU5ESVZJRFVBTDtST0xFPUFETUlOO1JTVlA9VFJVRTtDTj1NYXR0 +aGlldSBFWFRfQkFFQ0hMRVI7UEFSVFNUQVQ9TkVFCiBEUy1BQ1RJT047WC1PQk0tSUQ9MzAyOk1B +SUxUTzpiYWVjaGxlckBkb21haW4udGxkCkFUVEVOREVFO0NVVFlQRT1JTkRJVklEVUFMO1JTVlA9 +VFJVRTtDTj1MYXVyYSBST1lFVDtQQVJUU1RBVD1ORUVEUy1BQ1RJT047CiBYLU9CTS1JRD03MjM6 +TUFJTFRPOnJveWV0QGRvbWFpbi50bGQKU1RBVFVTOklOVkFMSURfVkFMVUUKRU5EOlZFVkVOVApF +TkQ6VkNBTEVOREFSCg== +--------------D24E361990BDBA143D4D8794 +Content-Type: text/calendar; charset=UTF-8; method=REQUEST +Content-Disposition: attachment; +Content-Transfer-Encoding: base64 + +QkVHSU46VkNBTEVOREFSClBST0RJRDotLy9BbGlhc291cmNlIEdyb3VwZSBMSU5BR09SQS8vT0JN +IENhbGVuZGFyIDMuMi4xLXJjMi8vRlIKQ0FMU0NBTEU6R1JFR09SSUFOClgtT0JNLVRJTUU6MTQ4 +MzcwMzQzNgpWRVJTSU9OOjIuMApNRVRIT0Q6UkVRVUVTVApCRUdJTjpWRVZFTlQKQ1JFQVRFRDoy +MDE3MDEwNlQxMTUwMzVaCkxBU1QtTU9ESUZJRUQ6MjAxNzAxMDZUMTE1MDM2WgpEVFNUQU1QOjIw +MTcwMTA2VDExNTAzNloKRFRTVEFSVDoyMDE3MDExMVQwOTAwMDBaCkRVUkFUSU9OOlBUMUgzME0K +VFpJRDpBc2lhL0hvX0NoaV9NaW5oClNFUVVFTkNFOjAKU1VNTUFSWTpTcHJpbnQgcGxhbm5pbmcg +IzIzCkRFU0NSSVBUSU9OOiBkZXNjcmlwdGlvbiAxMjMKQ0xBU1M6UFVCTElDClBSSU9SSVRZOjUK +T1JHQU5JWkVSO1gtT0JNLUlEPTEyODtDTj1SYXBoYWVsIE9VQVpBTkE6TUFJTFRPOm91YXphbmFA +ZG9tYWluLnRsZApYLU9QRU5QQUFTLVZJREVPQ09ORkVSRU5DRTpodHRwczovL2ppdHNpLmxpbmFn +b3JhLmNvbS9hYmNkCkxPQ0FUSU9OOkhhbmdvdXQKUlJVTEU6RlJFUT1ZRUFSTFk7QllNT05USD0x +MDtCWURBWT1NTztCWVNFVFBPUz0xLDI7VU5USUw9MjAyNDAxMTFUMDkwMDAwWgpFWFJVTEU6RlJF +UT1ZRUFSTFk7QllNT05USD0xMDtCWURBWT1NTztCWVNFVFBPUz0xO1VOVElMPTIwMjMwMTExVDA5 +MDAwMFoKQ0FURUdPUklFUzoKVUlEOmVhMTI3NjkwLTA0NDAtNDA0Yi1hZjk4LTk4MjNjODU1YTI4 +MwpBVFRFTkRFRTtDVVRZUEU9SU5ESVZJRFVBTDtST0xFPUFETUlOO1JTVlA9VFJVRTtDTj1NYXR0 +aGlldSBFWFRfQkFFQ0hMRVI7UEFSVFNUQVQ9TkVFCiBEUy1BQ1RJT047WC1PQk0tSUQ9MzAyOk1B +SUxUTzpiYWVjaGxlckBkb21haW4udGxkCkFUVEVOREVFO0NVVFlQRT1JTkRJVklEVUFMO1JTVlA9 +VFJVRTtDTj1MYXVyYSBST1lFVDtQQVJUU1RBVD1ORUVEUy1BQ1RJT047CiBYLU9CTS1JRD03MjM6 +TUFJTFRPOnJveWV0QGRvbWFpbi50bGQKU1RBVFVTOkNPTkZJUk1FRApUUkFOU1A6SU5WQUxJRF9W +QUxVRQpFTkQ6VkVWRU5UCkVORDpWQ0FMRU5EQVIK +--------------D24E361990BDBA143D4D8794 +Content-Type: application/ics; name=meeting.ics +Content-Disposition: attachment; filename=meeting.ics +Content-Transfer-Encoding: base64 + +QkVHSU46VkNBTEVOREFSClBST0RJRDotLy9BbGlhc291cmNlIEdyb3VwZSBMSU5BR09SQS8vT0JN +IENhbGVuZGFyIDMuMi4xLXJjMi8vRlIKQ0FMU0NBTEU6R1JFR09SSUFOClgtT0JNLVRJTUU6MTQ4 +MzcwMzQzNgpWRVJTSU9OOjIuMApNRVRIT0Q6UkVRVUVTVApCRUdJTjpWRVZFTlQKQ1JFQVRFRDoy +MDE3MDEwNlQxMTUwMzVaCkxBU1QtTU9ESUZJRUQ6MjAxNzAxMDZUMTE1MDM2WgpEVFNUQU1QOjIw +MTcwMTA2VDExNTAzNloKRFRTVEFSVDoyMDE3MDExMVQwOTAwMDBaCkRVUkFUSU9OOlBUMUgzME0K +VFpJRDpBc2lhL0hvX0NoaV9NaW5oClNFUVVFTkNFOjAKU1VNTUFSWTpTcHJpbnQgcGxhbm5pbmcg +IzIzCkRFU0NSSVBUSU9OOiBkZXNjcmlwdGlvbiAxMjMKQ0xBU1M6UFVCTElDClBSSU9SSVRZOjUK +T1JHQU5JWkVSO1gtT0JNLUlEPTEyODtDTj1SYXBoYWVsIE9VQVpBTkE6TUFJTFRPOm91YXphbmFA +ZG9tYWluLnRsZApYLU9QRU5QQUFTLVZJREVPQ09ORkVSRU5DRTpodHRwczovL2ppdHNpLmxpbmFn +b3JhLmNvbS9hYmNkCkxPQ0FUSU9OOkhhbmdvdXQKUlJVTEU6RlJFUT1ZRUFSTFk7QllNT05USD0x +MDtCWURBWT1NTztCWVNFVFBPUz0xLDI7VU5USUw9MjAyNDAxMTFUMDkwMDAwWgpFWFJVTEU6RlJF +UT1ZRUFSTFk7QllNT05USD0xMDtCWURBWT1NTztCWVNFVFBPUz0xO1VOVElMPTIwMjMwMTExVDA5 +MDAwMFoKQ0FURUdPUklFUzoKVUlEOmVhMTI3NjkwLTA0NDAtNDA0Yi1hZjk4LTk4MjNjODU1YTI4 +MwpBVFRFTkRFRTtDVVRZUEU9SU5ESVZJRFVBTDtST0xFPUFETUlOO1JTVlA9VFJVRTtDTj1NYXR0 +aGlldSBFWFRfQkFFQ0hMRVI7UEFSVFNUQVQ9TkVFCiBEUy1BQ1RJT047WC1PQk0tSUQ9MzAyOk1B +SUxUTzpiYWVjaGxlckBkb21haW4udGxkCkFUVEVOREVFO0NVVFlQRT1JTkRJVklEVUFMO1JTVlA9 +VFJVRTtDTj1MYXVyYSBST1lFVDtQQVJUU1RBVD1ORUVEUy1BQ1RJT047CiBYLU9CTS1JRD03MjM6 +TUFJTFRPOnJveWV0QGRvbWFpbi50bGQKU1RBVFVTOkNPTkZJUk1FRApUUkFOU1A6SU5WQUxJRF9W +QUxVRQpFTkQ6VkVWRU5UCkVORDpWQ0FMRU5EQVIK +--------------D24E361990BDBA143D4D8794-- \ No newline at end of file diff --git a/tmail-backend/integration-tests/jmap/jmap-integration-tests-common/src/main/resources/ics/aliceInviteBob.ics b/tmail-backend/integration-tests/jmap/jmap-integration-tests-common/src/main/resources/ics/aliceInviteBob.ics index e6a77c3b73..e6519e2ca7 100644 --- a/tmail-backend/integration-tests/jmap/jmap-integration-tests-common/src/main/resources/ics/aliceInviteBob.ics +++ b/tmail-backend/integration-tests/jmap/jmap-integration-tests-common/src/main/resources/ics/aliceInviteBob.ics @@ -17,7 +17,7 @@ SUMMARY:Sprint planning #23 DESCRIPTION: description 123 CLASS:PUBLIC PRIORITY:5 -ORGANIZER;X-OBM-ID=128;CN=Raphael OUAZANA:MAILTO:alice@domain.tld +ORGANIZER;X-OBM-ID=128;CN=Raphael OUAZANA:MAILTO:alice@do.main.tld X-OPENPAAS-VIDEOCONFERENCE:https://jitsi.linagora.com/abcd LOCATION:Hangout RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=MO;BYSETPOS=1,2;UNTIL=20240111T090000Z diff --git a/tmail-backend/integration-tests/jmap/jmap-integration-tests-common/src/main/scala/com/linagora/tmail/james/common/LinagoraCalendarEventAcceptMethodContract.scala b/tmail-backend/integration-tests/jmap/jmap-integration-tests-common/src/main/scala/com/linagora/tmail/james/common/LinagoraCalendarEventAcceptMethodContract.scala index d3df0ea2a6..84b9f3cb5d 100644 --- a/tmail-backend/integration-tests/jmap/jmap-integration-tests-common/src/main/scala/com/linagora/tmail/james/common/LinagoraCalendarEventAcceptMethodContract.scala +++ b/tmail-backend/integration-tests/jmap/jmap-integration-tests-common/src/main/scala/com/linagora/tmail/james/common/LinagoraCalendarEventAcceptMethodContract.scala @@ -1,8 +1,8 @@ package com.linagora.tmail.james.common -import java.io.{ByteArrayInputStream, InputStream} import java.util.concurrent.TimeUnit +import com.linagora.tmail.james.common.LinagoraCalendarEventMethodContractUtilities.sendInvitationEmailToBobAndGetIcsBlobIds import io.netty.handler.codec.http.HttpHeaderNames.ACCEPT import io.restassured.RestAssured.{`given`, requestSpecification} import io.restassured.http.ContentType.JSON @@ -11,11 +11,11 @@ import net.javacrumbs.jsonunit.JsonMatchers.jsonEquals import net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson import net.javacrumbs.jsonunit.core.Option.IGNORING_ARRAY_ORDER import org.apache.http.HttpStatus -import org.apache.http.HttpStatus.{SC_CREATED, SC_OK} +import org.apache.http.HttpStatus.SC_OK import org.apache.james.GuiceJamesServer import org.apache.james.jmap.core.ResponseObject.SESSION_STATE import org.apache.james.jmap.http.UserCredential -import org.apache.james.jmap.rfc8621.contract.Fixture.{ACCEPT_RFC8621_VERSION_HEADER, ACCOUNT_ID, ANDRE, ANDRE_ACCOUNT_ID, ANDRE_PASSWORD, BOB, BOB_PASSWORD, DOMAIN, authScheme, baseRequestSpecBuilder} +import org.apache.james.jmap.rfc8621.contract.Fixture._ import org.apache.james.jmap.rfc8621.contract.probe.DelegationProbe import org.apache.james.jmap.rfc8621.contract.tags.CategoryTags import org.apache.james.mailbox.model.MailboxPath @@ -32,6 +32,7 @@ trait LinagoraCalendarEventAcceptMethodContract { server.getProbe(classOf[DataProbeImpl]) .fluent .addDomain(DOMAIN.asString) + .addDomain(_2_DOT_DOMAIN.asString()) // Alice domain .addUser(BOB.asString, BOB_PASSWORD) .addUser(ANDRE.asString, ANDRE_PASSWORD) @@ -39,13 +40,16 @@ trait LinagoraCalendarEventAcceptMethodContract { .setAuth(authScheme(UserCredential(BOB, BOB_PASSWORD))) .addHeader(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER) .build + + server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB)) } def randomBlobId: String @Test - def acceptShouldSucceed(): Unit = { - val blobId: String = uploadAndGetBlobId(ClassLoader.getSystemResourceAsStream("ics/aliceInviteBob.ics")) + def acceptShouldSucceed(server: GuiceJamesServer): Unit = { + val blobId: String = + sendInvitationEmailToBobAndGetIcsBlobIds(server, "emailWithAliceInviteBobIcsAttachment.eml", icsPartId = "3") val request: String = s"""{ @@ -87,8 +91,9 @@ trait LinagoraCalendarEventAcceptMethodContract { } @Test - def acceptAMissingMethodIcsShouldReturnNotAccept(): Unit = { - val missingMethodIcsBlobId: String = uploadAndGetBlobId(ClassLoader.getSystemResourceAsStream("ics/missingMethod.ics")) + def acceptAMissingMethodIcsShouldReturnNotAccept(server: GuiceJamesServer): Unit = { + val missingMethodIcsBlobId: String = + sendInvitationEmailToBobAndGetIcsBlobIds(server, "emailWithIcsMissingMethod.eml", icsPartId = "3") val request: String = s"""{ @@ -135,8 +140,9 @@ trait LinagoraCalendarEventAcceptMethodContract { } @Test - def acceptAMissingOrganizerIcsShouldReturnNotAccept(): Unit = { - val missingOrganizerIcsBlobId: String = uploadAndGetBlobId(ClassLoader.getSystemResourceAsStream("ics/missing_organizer.ics")) + def acceptAMissingOrganizerIcsShouldReturnNotAccept(server: GuiceJamesServer): Unit = { + val missingOrganizerIcsBlobId: String = + sendInvitationEmailToBobAndGetIcsBlobIds(server, "emailWithIcsMissingOrginizer.eml", icsPartId = "3") val request: String = s"""{ @@ -183,8 +189,9 @@ trait LinagoraCalendarEventAcceptMethodContract { } @Test - def acceptAMissingAttendeeIcsShouldReturnAccepted(): Unit = { - val missingAttendeeIcsBlobId: String = uploadAndGetBlobId(ClassLoader.getSystemResourceAsStream("ics/missing_attendee.ics")) + def acceptAMissingAttendeeIcsShouldReturnAccepted(server: GuiceJamesServer): Unit = { + val missingAttendeeIcsBlobId: String = + sendInvitationEmailToBobAndGetIcsBlobIds(server, "emailWithIcsMissingAttendee.eml", icsPartId = "3") val request: String = s"""{ @@ -226,8 +233,9 @@ trait LinagoraCalendarEventAcceptMethodContract { } @Test - def acceptAMissingVEventIcsShouldReturnNotAccept(): Unit = { - val missingVEventIcsBlobId: String = uploadAndGetBlobId(ClassLoader.getSystemResourceAsStream("ics/missing_vevent.ics")) + def acceptAMissingVEventIcsShouldReturnNotAccept(server: GuiceJamesServer): Unit = { + val missingVEventIcsBlobId: String = + sendInvitationEmailToBobAndGetIcsBlobIds(server, "emailWithIcsMissingVEVENT.eml", icsPartId = "3") val request: String = s"""{ @@ -274,8 +282,9 @@ trait LinagoraCalendarEventAcceptMethodContract { } @Test - def shouldReturnNotFoundResultWhenBlobIdDoesNotExist(): Unit = { - val notFoundBlobId: String = randomBlobId + def shouldReturnNotFoundResultWhenBlobIdDoesNotExist(server: GuiceJamesServer): Unit = { + val notFoundBlobId: String = + sendInvitationEmailToBobAndGetIcsBlobIds(server, "emailWithIcsMissingMethod.eml", icsPartId = "88888") val request: String = s"""{ @@ -315,8 +324,9 @@ trait LinagoraCalendarEventAcceptMethodContract { } @Test - def shouldReturnNotCreatedWhenNotAnICS(): Unit = { - val notParsableBlobId: String = uploadAndGetBlobId(new ByteArrayInputStream("notIcsFileFormat".getBytes)) + def shouldReturnNotAcceptedWhenNotAnICS(server: GuiceJamesServer): Unit = { + val notParsableBlobId: String = + sendInvitationEmailToBobAndGetIcsBlobIds(server, "emailWithAliceInviteBobIcsAttachment.eml", icsPartId = "2") val request: String = s"""{ @@ -353,7 +363,7 @@ trait LinagoraCalendarEventAcceptMethodContract { | "notAccepted": { | "$notParsableBlobId": { | "type": "invalidPatch", - | "description": "Error at line 1:Expected [BEGIN], read [notIcsFileFormat]" + | "description": "Error at line 1:Expected [BEGIN], read [The message has a text attachment.]" | } | } | }, @@ -362,10 +372,10 @@ trait LinagoraCalendarEventAcceptMethodContract { } @Test - def shouldSucceedWhenMixSeveralCases(): Unit = { - val notAcceptedId: String = uploadAndGetBlobId(new ByteArrayInputStream("notIcsFileFormat".getBytes)) - val notFoundBlobId: String = randomBlobId - val blobId: String = uploadAndGetBlobId(ClassLoader.getSystemResourceAsStream("ics/aliceInviteBob.ics")) + def shouldSucceedWhenMixSeveralCases(server: GuiceJamesServer): Unit = { + val (notAcceptedId, notFoundBlobId, blobId) = + sendInvitationEmailToBobAndGetIcsBlobIds(server, "emailWithAliceInviteBobIcsAttachment.eml", icsPartIds = ("2", "999999", "3")) + val request: String = s"""{ | "using": [ @@ -404,7 +414,7 @@ trait LinagoraCalendarEventAcceptMethodContract { | "notAccepted": { | "$notAcceptedId": { | "type": "invalidPatch", - | "description": "Error at line 1:Expected [BEGIN], read [notIcsFileFormat]" + | "description": "Error at line 1:Expected [BEGIN], read [The message has a text attachment.]" | } | } | }, @@ -527,7 +537,8 @@ trait LinagoraCalendarEventAcceptMethodContract { @Test def shouldNotFoundWhenDoesNotHavePermission(server: GuiceJamesServer): Unit = { - val blobId: String = uploadAndGetBlobId(ClassLoader.getSystemResourceAsStream("ics/aliceInviteBob.ics")) + val blobId: String = + sendInvitationEmailToBobAndGetIcsBlobIds(server, "emailWithAliceInviteBobIcsAttachment.eml", icsPartId = "3") val request: String = s"""{ @@ -569,7 +580,9 @@ trait LinagoraCalendarEventAcceptMethodContract { @Test def shouldSucceedWhenDelegated(server: GuiceJamesServer): Unit = { - val blobId: String = uploadAndGetBlobId(ClassLoader.getSystemResourceAsStream("ics/aliceInviteBob.ics")) + val blobId: String = + sendInvitationEmailToBobAndGetIcsBlobIds(server, "emailWithAliceInviteBobIcsAttachment.eml", icsPartId = "3") + server.getProbe(classOf[DelegationProbe]).addAuthorizedUser(BOB, ANDRE) val bobAccountId = ACCOUNT_ID @@ -657,9 +670,9 @@ trait LinagoraCalendarEventAcceptMethodContract { } @Test - def shouldNotCreatedWhenInvalidIcsPayload(): Unit = { - val blobId1: String = uploadAndGetBlobId(ClassLoader.getSystemResourceAsStream("ics/invalid_TRANSP.ics")) - val blobId2: String = uploadAndGetBlobId(ClassLoader.getSystemResourceAsStream("ics/invalid_STATUS.ics")) + def shouldNotCreatedWhenInvalidIcsPayload(server: GuiceJamesServer): Unit = { + val (blobId1, blobId2) = + sendInvitationEmailToBobAndGetIcsBlobIds(server, "emailWithTwoInvalidIcsAttachments.eml", icsPartIds = ("5", "3")) val request: String = s"""{ @@ -710,8 +723,9 @@ trait LinagoraCalendarEventAcceptMethodContract { } @Test - def shouldFailWhenInvalidLanguage(): Unit = { - val blobId: String = uploadAndGetBlobId(ClassLoader.getSystemResourceAsStream("ics/aliceInviteBob.ics")) + def shouldFailWhenInvalidLanguage(server: GuiceJamesServer): Unit = { + val blobId: String = + sendInvitationEmailToBobAndGetIcsBlobIds(server, "emailWithAliceInviteBobIcsAttachment.eml", icsPartId = "3") `given` .body(s"""{ @@ -735,8 +749,9 @@ trait LinagoraCalendarEventAcceptMethodContract { } @Test - def shouldFailWhenUnsupportedLanguage(): Unit = { - val blobId: String = uploadAndGetBlobId(ClassLoader.getSystemResourceAsStream("ics/aliceInviteBob.ics")) + def shouldFailWhenUnsupportedLanguage(server: GuiceJamesServer): Unit = { + val blobId: String = + sendInvitationEmailToBobAndGetIcsBlobIds(server, "emailWithAliceInviteBobIcsAttachment.eml", icsPartId = "3") val response = `given` .body(s"""{ @@ -776,8 +791,9 @@ trait LinagoraCalendarEventAcceptMethodContract { } @Test - def shouldSupportSpecialValidLanguages(): Unit = { - val blobId: String = uploadAndGetBlobId(ClassLoader.getSystemResourceAsStream("ics/aliceInviteBob.ics")) + def shouldSupportSpecialValidLanguages(server: GuiceJamesServer): Unit = { + val blobId: String = + sendInvitationEmailToBobAndGetIcsBlobIds(server, "emailWithAliceInviteBobIcsAttachment.eml", icsPartId = "3") val request: String = s"""{ @@ -823,7 +839,8 @@ trait LinagoraCalendarEventAcceptMethodContract { @Tag(CategoryTags.BASIC_FEATURE) def shouldSendReplyMailToInvitor(server: GuiceJamesServer): Unit = { val andreInboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(ANDRE)) - val blobId: String = uploadAndGetBlobId(new ByteArrayInputStream(generateInviteIcs(BOB.asString(), ANDRE.asString()).getBytes)) + val blobId: String = + sendInvitationEmailToBobAndGetIcsBlobIds(server, "emailWithAndreInviteBobIcsAttachment.eml", icsPartId = "3") `given` .body( s"""{ @@ -904,14 +921,14 @@ trait LinagoraCalendarEventAcceptMethodContract { .inPath("methodResponses[1][1].list[0]") .isEqualTo( s"""{ - | "subject": "ACCEPTED: Simple event @ Fri Feb 23, 2024 (BOB )", + | "subject": "ACCEPTED: Sprint planning #23 @ Wed Jan 11, 2017 (BOB )", | "preview": "BOB has accepted this invitation.", | "id": "$${json-unit.ignore}", | "hasAttachment": true, | "attachments": [ | { | "charset": "UTF-8", - | "size": 572, + | "size": 883, | "partId": "3", | "blobId": "$${json-unit.ignore}", | "type": "text/calendar" @@ -919,7 +936,7 @@ trait LinagoraCalendarEventAcceptMethodContract { | { | "charset": "us-ascii", | "disposition": "attachment", - | "size": 572, + | "size": 883, | "partId": "4", | "blobId": "$${json-unit.ignore}", | "name": "invite.ics", @@ -933,7 +950,8 @@ trait LinagoraCalendarEventAcceptMethodContract { @Test def mailReplyShouldSupportI18nWhenLanguageRequest(server: GuiceJamesServer): Unit = { val andreInboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(ANDRE)) - val blobId: String = uploadAndGetBlobId(new ByteArrayInputStream(generateInviteIcs(BOB.asString(), ANDRE.asString()).getBytes)) + val blobId: String = + sendInvitationEmailToBobAndGetIcsBlobIds(server, "emailWithAndreInviteBobIcsAttachment.eml", icsPartId = "3") `given` .body( s"""{ @@ -1013,14 +1031,14 @@ trait LinagoraCalendarEventAcceptMethodContract { .inPath("methodResponses[1][1].list[0]") .isEqualTo( s"""{ - | "subject": "ACCEPTÉ: Simple event @ Fri Feb 23, 2024 (BOB )", + | "subject": "ACCEPTÉ: Sprint planning #23 @ Wed Jan 11, 2017 (BOB )", | "preview": "BOB a accepté cette invitation.", | "id": "$${json-unit.ignore}", | "hasAttachment": true, | "attachments": [ | { | "charset": "UTF-8", - | "size": 572, + | "size": 883, | "partId": "3", | "blobId": "$${json-unit.ignore}", | "type": "text/calendar" @@ -1028,7 +1046,7 @@ trait LinagoraCalendarEventAcceptMethodContract { | { | "charset": "us-ascii", | "disposition": "attachment", - | "size": 572, + | "size": 883, | "partId": "4", | "blobId": "$${json-unit.ignore}", | "name": "invite.ics", @@ -1039,44 +1057,100 @@ trait LinagoraCalendarEventAcceptMethodContract { } } + @Test + def shouldFailWhenBlobIdIsNotPrefixedByMessageId(): Unit = { + val request: String = + s"""{ + | "using": [ + | "urn:ietf:params:jmap:core", + | "com:linagora:params:calendar:event"], + | "methodCalls": [[ + | "CalendarEvent/accept", + | { + | "accountId": "$ACCOUNT_ID", + | "blobIds": [ "abcd123" ] + | }, + | "c1"]] + |}""".stripMargin + val response = + `given` + .body(request) + .when + .post + .`then` + .statusCode(SC_OK) + .contentType(JSON) + .extract + .body + .asString + + println(response) + + assertThatJson(response) + .inPath("methodResponses[0]") + .isEqualTo( + s"""[ + | "error", + | { + | "type": "invalidArguments", + | "description": "Invalid calendar event blobId: abcd123. The blobId should be in the form {messageId}_{partId}." + | }, + | "c1" + |]""".stripMargin) + } + + @Test + def shouldFailWhenCalendarBlobsBelongToDifferentMessages(server: GuiceJamesServer): Unit = { + val blobIdFromMessage1: String = + sendInvitationEmailToBobAndGetIcsBlobIds(server, "emailWithAliceInviteBobIcsAttachment.eml", icsPartId = "3") + val blobIdFromMessage2: String = + sendInvitationEmailToBobAndGetIcsBlobIds(server, "emailWithAliceInviteBobIcsAttachment.eml", icsPartId = "3") + + val message1Id = blobIdFromMessage1.split("_")(0) + val message2Id = blobIdFromMessage2.split("_")(0) + + val request: String = + s"""{ + | "using": [ + | "urn:ietf:params:jmap:core", + | "com:linagora:params:calendar:event"], + | "methodCalls": [[ + | "CalendarEvent/accept", + | { + | "accountId": "$ACCOUNT_ID", + | "blobIds": [ "$blobIdFromMessage1", "$blobIdFromMessage2" ] + | }, + | "c1"]] + |}""".stripMargin + + val response = + `given` + .body(request) + .when + .post + .`then` + .statusCode(SC_OK) + .contentType(JSON) + .extract + .body + .asString + + assertThatJson(response) + .inPath("methodResponses[0]") + .isEqualTo( + s"""[ + | "error", + | { + | "type": "serverFail", + | "description": "Expected a single enclosing message for all calendar event blobIds, got: [InMemoryMessageId{value=$message1Id}, InMemoryMessageId{value=$message2Id}]" + | }, + | "c1" + |]""".stripMargin) + } + private def buildAndreRequestSpecification(server: GuiceJamesServer): RequestSpecification = baseRequestSpecBuilder(server) .setAuth(authScheme(UserCredential(ANDRE, ANDRE_PASSWORD))) .addHeader(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER) .build - - private def uploadAndGetBlobId(payload: InputStream): String = - `given` - .basePath("") - .body(payload) - .when - .post(s"/upload/$ACCOUNT_ID") - .`then` - .statusCode(SC_CREATED) - .extract - .jsonPath() - .get("blobId") - - private def generateInviteIcs(invitee: String, organizer: String): String = - s"""BEGIN:VCALENDAR - |CALSCALE:GREGORIAN - |VERSION:2.0 - |PRODID:-//Linagora//TMail Calendar//EN - |METHOD:REQUEST - |CALSCALE:GREGORIAN - |BEGIN:VEVENT - |UID:8eae5147-f2df-4853-8fe0-c88678bc8b9f - |TRANSP:OPAQUE - |DTSTART;TZID=Europe/Paris:20240223T160000 - |DTEND;TZID=Europe/Paris:20240223T163000 - |CLASS:PUBLIC - |SUMMARY:Simple event - |ORGANIZER;CN=comptetest15.linagora@domain.tld:mailto:${organizer} - |DTSTAMP:20240222T204008Z - |SEQUENCE:0 - |ATTENDEE;CUTYPE=INDIVIDUAL;RSVP=TRUE;CN=BOB;PARTSTAT=NEEDS-ACTION;X-OBM-ID=348:mailto:${invitee} - |END:VEVENT - |END:VCALENDAR - |""".stripMargin - } diff --git a/tmail-backend/integration-tests/jmap/jmap-integration-tests-common/src/main/scala/com/linagora/tmail/james/common/LinagoraCalendarEventMaybeMethodContract.scala b/tmail-backend/integration-tests/jmap/jmap-integration-tests-common/src/main/scala/com/linagora/tmail/james/common/LinagoraCalendarEventMaybeMethodContract.scala index 4be47d91b5..1ea202a814 100644 --- a/tmail-backend/integration-tests/jmap/jmap-integration-tests-common/src/main/scala/com/linagora/tmail/james/common/LinagoraCalendarEventMaybeMethodContract.scala +++ b/tmail-backend/integration-tests/jmap/jmap-integration-tests-common/src/main/scala/com/linagora/tmail/james/common/LinagoraCalendarEventMaybeMethodContract.scala @@ -1,8 +1,8 @@ package com.linagora.tmail.james.common -import java.io.{ByteArrayInputStream, InputStream} import java.util.concurrent.TimeUnit +import com.linagora.tmail.james.common.LinagoraCalendarEventMethodContractUtilities.sendInvitationEmailToBobAndGetIcsBlobIds import io.netty.handler.codec.http.HttpHeaderNames.ACCEPT import io.restassured.RestAssured.{`given`, requestSpecification} import io.restassured.http.ContentType.JSON @@ -11,17 +11,18 @@ import net.javacrumbs.jsonunit.JsonMatchers.jsonEquals import net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson import net.javacrumbs.jsonunit.core.Option.IGNORING_ARRAY_ORDER import org.apache.http.HttpStatus -import org.apache.http.HttpStatus.{SC_CREATED, SC_OK} +import org.apache.http.HttpStatus.SC_OK import org.apache.james.GuiceJamesServer import org.apache.james.jmap.core.ResponseObject.SESSION_STATE import org.apache.james.jmap.http.UserCredential -import org.apache.james.jmap.rfc8621.contract.Fixture.{ACCEPT_RFC8621_VERSION_HEADER, ACCOUNT_ID, ANDRE, ANDRE_ACCOUNT_ID, ANDRE_PASSWORD, BOB, BOB_PASSWORD, DOMAIN, authScheme, baseRequestSpecBuilder} +import org.apache.james.jmap.rfc8621.contract.Fixture._ import org.apache.james.jmap.rfc8621.contract.probe.DelegationProbe +import org.apache.james.jmap.rfc8621.contract.tags.CategoryTags import org.apache.james.mailbox.model.MailboxPath import org.apache.james.modules.MailboxProbeImpl import org.apache.james.utils.DataProbeImpl import org.hamcrest.Matchers -import org.junit.jupiter.api.{BeforeEach, Test} +import org.junit.jupiter.api.{BeforeEach, Tag, Test} import play.api.libs.json.Json trait LinagoraCalendarEventMaybeMethodContract { @@ -31,6 +32,7 @@ trait LinagoraCalendarEventMaybeMethodContract { server.getProbe(classOf[DataProbeImpl]) .fluent .addDomain(DOMAIN.asString) + .addDomain(_2_DOT_DOMAIN.asString) .addUser(BOB.asString, BOB_PASSWORD) .addUser(ANDRE.asString, ANDRE_PASSWORD) @@ -38,13 +40,16 @@ trait LinagoraCalendarEventMaybeMethodContract { .setAuth(authScheme(UserCredential(BOB, BOB_PASSWORD))) .addHeader(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER) .build + + server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB)) } def randomBlobId: String @Test - def maybeShouldSucceed(): Unit = { - val blobId: String = uploadAndGetBlobId(ClassLoader.getSystemResourceAsStream("ics/aliceInviteBob.ics")) + def maybeShouldSucceed(server: GuiceJamesServer): Unit = { + val blobId: String = + sendInvitationEmailToBobAndGetIcsBlobIds(server, "emailWithAliceInviteBobIcsAttachment.eml", icsPartId = "3") val request: String = s"""{ @@ -86,8 +91,9 @@ trait LinagoraCalendarEventMaybeMethodContract { } @Test - def shouldReturnNotFoundResultWhenBlobIdDoesNotExist(): Unit = { - val notFoundBlobId: String = randomBlobId + def shouldReturnNotFoundResultWhenBlobIdDoesNotExist(server: GuiceJamesServer): Unit = { + val notFoundBlobId: String = + sendInvitationEmailToBobAndGetIcsBlobIds(server, "emailWithIcsMissingMethod.eml", icsPartId = "88888") val request: String = s"""{ @@ -127,8 +133,9 @@ trait LinagoraCalendarEventMaybeMethodContract { } @Test - def shouldReturnNotMaybeWhenNotAnICS(): Unit = { - val notParsableBlobId: String = uploadAndGetBlobId(new ByteArrayInputStream("notIcsFileFormat".getBytes)) + def shouldReturnNotMaybeWhenNotAnICS(server: GuiceJamesServer): Unit = { + val notParsableBlobId: String = + sendInvitationEmailToBobAndGetIcsBlobIds(server, "emailWithAliceInviteBobIcsAttachment.eml", icsPartId = "2") val request: String = s"""{ @@ -165,7 +172,7 @@ trait LinagoraCalendarEventMaybeMethodContract { | "notMaybe": { | "$notParsableBlobId": { | "type": "invalidPatch", - | "description": "Error at line 1:Expected [BEGIN], read [notIcsFileFormat]" + | "description": "Error at line 1:Expected [BEGIN], read [The message has a text attachment.]" | } | } | }, @@ -174,10 +181,10 @@ trait LinagoraCalendarEventMaybeMethodContract { } @Test - def shouldSucceedWhenMixSeveralCases(): Unit = { - val notAcceptedId: String = uploadAndGetBlobId(new ByteArrayInputStream("notIcsFileFormat".getBytes)) - val notFoundBlobId: String = randomBlobId - val blobId: String = uploadAndGetBlobId(ClassLoader.getSystemResourceAsStream("ics/aliceInviteBob.ics")) + def shouldSucceedWhenMixSeveralCases(server: GuiceJamesServer): Unit = { + val (notAcceptedId, notFoundBlobId, blobId) = + sendInvitationEmailToBobAndGetIcsBlobIds(server, "emailWithAliceInviteBobIcsAttachment.eml", icsPartIds = ("2", "999999", "3")) + val request: String = s"""{ | "using": [ @@ -220,7 +227,7 @@ trait LinagoraCalendarEventMaybeMethodContract { | "notMaybe": { | "$notAcceptedId": { | "type": "invalidPatch", - | "description": "Error at line 1:Expected [BEGIN], read [notIcsFileFormat]" + | "description": "Error at line 1:Expected [BEGIN], read [The message has a text attachment.]" | } | } | }, @@ -343,7 +350,8 @@ trait LinagoraCalendarEventMaybeMethodContract { @Test def shouldNotFoundWhenDoesNotHavePermission(server: GuiceJamesServer): Unit = { - val blobId: String = uploadAndGetBlobId(ClassLoader.getSystemResourceAsStream("ics/aliceInviteBob.ics")) + val blobId: String = + sendInvitationEmailToBobAndGetIcsBlobIds(server, "emailWithAliceInviteBobIcsAttachment.eml", icsPartId = "3") val request: String = s"""{ @@ -385,7 +393,9 @@ trait LinagoraCalendarEventMaybeMethodContract { @Test def shouldSucceedWhenDelegated(server: GuiceJamesServer): Unit = { - val blobId: String = uploadAndGetBlobId(ClassLoader.getSystemResourceAsStream("ics/aliceInviteBob.ics")) + val blobId: String = + sendInvitationEmailToBobAndGetIcsBlobIds(server, "emailWithAliceInviteBobIcsAttachment.eml", icsPartId = "3") + server.getProbe(classOf[DelegationProbe]).addAuthorizedUser(BOB, ANDRE) val bobAccountId = ACCOUNT_ID @@ -473,9 +483,9 @@ trait LinagoraCalendarEventMaybeMethodContract { } @Test - def shouldNotMaybeWhenInvalidIcsPayload(): Unit = { - val blobId1: String = uploadAndGetBlobId(ClassLoader.getSystemResourceAsStream("ics/invalid_TRANSP.ics")) - val blobId2: String = uploadAndGetBlobId(ClassLoader.getSystemResourceAsStream("ics/invalid_STATUS.ics")) + def shouldNotMaybeWhenInvalidIcsPayload(server: GuiceJamesServer): Unit = { + val (blobId1, blobId2) = + sendInvitationEmailToBobAndGetIcsBlobIds(server, "emailWithTwoInvalidIcsAttachments.eml", icsPartIds = ("5", "3")) val request: String = s"""{ @@ -526,8 +536,9 @@ trait LinagoraCalendarEventMaybeMethodContract { } @Test - def shouldFailWhenInvalidLanguage(): Unit = { - val blobId: String = uploadAndGetBlobId(ClassLoader.getSystemResourceAsStream("ics/aliceInviteBob.ics")) + def shouldFailWhenInvalidLanguage(server: GuiceJamesServer): Unit = { + val blobId: String = + sendInvitationEmailToBobAndGetIcsBlobIds(server, "emailWithAliceInviteBobIcsAttachment.eml", icsPartId = "3") `given` .body(s"""{ @@ -551,8 +562,9 @@ trait LinagoraCalendarEventMaybeMethodContract { } @Test - def shouldFailWhenUnsupportedLanguage(): Unit = { - val blobId: String = uploadAndGetBlobId(ClassLoader.getSystemResourceAsStream("ics/aliceInviteBob.ics")) + def shouldFailWhenUnsupportedLanguage(server: GuiceJamesServer): Unit = { + val blobId: String = + sendInvitationEmailToBobAndGetIcsBlobIds(server, "emailWithAliceInviteBobIcsAttachment.eml", icsPartId = "3") val response = `given` .body(s"""{ @@ -592,8 +604,9 @@ trait LinagoraCalendarEventMaybeMethodContract { } @Test - def shouldSupportSpecialValidLanguages(): Unit = { - val blobId: String = uploadAndGetBlobId(ClassLoader.getSystemResourceAsStream("ics/aliceInviteBob.ics")) + def shouldSupportSpecialValidLanguages(server: GuiceJamesServer): Unit = { + val blobId: String = + sendInvitationEmailToBobAndGetIcsBlobIds(server, "emailWithAliceInviteBobIcsAttachment.eml", icsPartId = "3") val request: String = s"""{ @@ -636,9 +649,11 @@ trait LinagoraCalendarEventMaybeMethodContract { } @Test + @Tag(CategoryTags.BASIC_FEATURE) def shouldSendReplyMailToInvitor(server: GuiceJamesServer): Unit = { val andreInboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(ANDRE)) - val blobId: String = uploadAndGetBlobId(new ByteArrayInputStream(generateInviteIcs(BOB.asString(), ANDRE.asString()).getBytes)) + val blobId: String = + sendInvitationEmailToBobAndGetIcsBlobIds(server, "emailWithAndreInviteBobIcsAttachment.eml", icsPartId = "3") `given` .body( s"""{ @@ -719,14 +734,14 @@ trait LinagoraCalendarEventMaybeMethodContract { .inPath("methodResponses[1][1].list[0]") .isEqualTo( s"""{ - | "subject": "Tentatively Accepted: Simple event @ Fri Feb 23, 2024 (BOB )", + | "subject": "Tentatively Accepted: Sprint planning #23 @ Wed Jan 11, 2017 (BOB )", | "preview": "BOB has replied Maybe to this invitation.", | "id": "$${json-unit.ignore}", | "hasAttachment": true, | "attachments": [ | { | "charset": "UTF-8", - | "size": 573, + | "size": 884, | "partId": "3", | "blobId": "$${json-unit.ignore}", | "type": "text/calendar" @@ -734,7 +749,7 @@ trait LinagoraCalendarEventMaybeMethodContract { | { | "charset": "us-ascii", | "disposition": "attachment", - | "size": 573, + | "size": 884, | "partId": "4", | "blobId": "$${json-unit.ignore}", | "name": "invite.ics", @@ -748,7 +763,8 @@ trait LinagoraCalendarEventMaybeMethodContract { @Test def mailReplyShouldSupportI18nWhenLanguageRequest(server: GuiceJamesServer): Unit = { val andreInboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(ANDRE)) - val blobId: String = uploadAndGetBlobId(new ByteArrayInputStream(generateInviteIcs(BOB.asString(), ANDRE.asString()).getBytes)) + val blobId: String = + sendInvitationEmailToBobAndGetIcsBlobIds(server, "emailWithAndreInviteBobIcsAttachment.eml", icsPartId = "3") `given` .body( s"""{ @@ -828,14 +844,14 @@ trait LinagoraCalendarEventMaybeMethodContract { .inPath("methodResponses[1][1].list[0]") .isEqualTo( s"""{ - | "subject": "Accepté provisoirement: Simple event @ Fri Feb 23, 2024 (BOB )", + | "subject": "Accepté provisoirement: Sprint planning #23 @ Wed Jan 11, 2017 (BOB )", | "preview": "BOB a répondu Peut-être à cette invitation.", | "id": "$${json-unit.ignore}", | "hasAttachment": true, | "attachments": [ | { | "charset": "UTF-8", - | "size": 573, + | "size": 884, | "partId": "3", | "blobId": "$${json-unit.ignore}", | "type": "text/calendar" @@ -843,7 +859,7 @@ trait LinagoraCalendarEventMaybeMethodContract { | { | "charset": "us-ascii", | "disposition": "attachment", - | "size": 573, + | "size": 884, | "partId": "4", | "blobId": "$${json-unit.ignore}", | "name": "invite.ics", @@ -854,44 +870,99 @@ trait LinagoraCalendarEventMaybeMethodContract { } } + @Test + def shouldFailWhenBlobIdIsNotPrefixedByMessageId(): Unit = { + val request: String = + s"""{ + | "using": [ + | "urn:ietf:params:jmap:core", + | "com:linagora:params:calendar:event"], + | "methodCalls": [[ + | "CalendarEvent/maybe", + | { + | "accountId": "$ACCOUNT_ID", + | "blobIds": [ "abcd123" ] + | }, + | "c1"]] + |}""".stripMargin + + val response = + `given` + .body(request) + .when + .post + .`then` + .statusCode(SC_OK) + .contentType(JSON) + .extract + .body + .asString + + assertThatJson(response) + .inPath("methodResponses[0]") + .isEqualTo( + s"""[ + | "error", + | { + | "type": "invalidArguments", + | "description": "Invalid calendar event blobId: abcd123. The blobId should be in the form {messageId}_{partId}." + | }, + | "c1" + |]""".stripMargin) + } + + @Test + def shouldFailWhenCalendarBlobsBelongToDifferentMessages(server: GuiceJamesServer): Unit = { + val blobIdFromMessage1: String = + sendInvitationEmailToBobAndGetIcsBlobIds(server, "emailWithAliceInviteBobIcsAttachment.eml", icsPartId = "3") + val blobIdFromMessage2: String = + sendInvitationEmailToBobAndGetIcsBlobIds(server, "emailWithAliceInviteBobIcsAttachment.eml", icsPartId = "3") + + val message1Id = blobIdFromMessage1.split("_")(0) + val message2Id = blobIdFromMessage2.split("_")(0) + + val request: String = + s"""{ + | "using": [ + | "urn:ietf:params:jmap:core", + | "com:linagora:params:calendar:event"], + | "methodCalls": [[ + | "CalendarEvent/maybe", + | { + | "accountId": "$ACCOUNT_ID", + | "blobIds": [ "$blobIdFromMessage1", "$blobIdFromMessage2" ] + | }, + | "c1"]] + |}""".stripMargin + + val response = + `given` + .body(request) + .when + .post + .`then` + .statusCode(SC_OK) + .contentType(JSON) + .extract + .body + .asString + + assertThatJson(response) + .inPath("methodResponses[0]") + .isEqualTo( + s"""[ + | "error", + | { + | "type": "serverFail", + | "description": "Expected a single enclosing message for all calendar event blobIds, got: [InMemoryMessageId{value=$message1Id}, InMemoryMessageId{value=$message2Id}]" + | }, + | "c1" + |]""".stripMargin) + } + private def buildAndreRequestSpecification(server: GuiceJamesServer): RequestSpecification = baseRequestSpecBuilder(server) .setAuth(authScheme(UserCredential(ANDRE, ANDRE_PASSWORD))) .addHeader(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER) .build - - private def uploadAndGetBlobId(payload: InputStream): String = - `given` - .basePath("") - .body(payload) - .when - .post(s"/upload/$ACCOUNT_ID") - .`then` - .statusCode(SC_CREATED) - .extract - .jsonPath() - .get("blobId") - - private def generateInviteIcs(invitee: String, organizer: String): String = - s"""BEGIN:VCALENDAR - |CALSCALE:GREGORIAN - |VERSION:2.0 - |PRODID:-//Linagora//TMail Calendar//EN - |METHOD:REQUEST - |CALSCALE:GREGORIAN - |BEGIN:VEVENT - |UID:8eae5147-f2df-4853-8fe0-c88678bc8b9f - |TRANSP:OPAQUE - |DTSTART;TZID=Europe/Paris:20240223T160000 - |DTEND;TZID=Europe/Paris:20240223T163000 - |CLASS:PUBLIC - |SUMMARY:Simple event - |ORGANIZER;CN=comptetest15.linagora@domain.tld:mailto:${organizer} - |DTSTAMP:20240222T204008Z - |SEQUENCE:0 - |ATTENDEE;CUTYPE=INDIVIDUAL;RSVP=TRUE;CN=BOB;PARTSTAT=NEEDS-ACTION;X-OBM-ID=348:mailto:${invitee} - |END:VEVENT - |END:VCALENDAR - |""".stripMargin - } diff --git a/tmail-backend/integration-tests/jmap/jmap-integration-tests-common/src/main/scala/com/linagora/tmail/james/common/LinagoraCalendarEventMethodContractUtilities.scala b/tmail-backend/integration-tests/jmap/jmap-integration-tests-common/src/main/scala/com/linagora/tmail/james/common/LinagoraCalendarEventMethodContractUtilities.scala new file mode 100644 index 0000000000..f12fddfeaa --- /dev/null +++ b/tmail-backend/integration-tests/jmap/jmap-integration-tests-common/src/main/scala/com/linagora/tmail/james/common/LinagoraCalendarEventMethodContractUtilities.scala @@ -0,0 +1,50 @@ +package com.linagora.tmail.james.common + +import org.apache.james.GuiceJamesServer +import org.apache.james.jmap.rfc8621.contract.Fixture.BOB +import org.apache.james.mailbox.MessageManager.AppendCommand +import org.apache.james.mailbox.model.MailboxPath +import org.apache.james.modules.MailboxProbeImpl +import org.apache.james.util.ClassLoaderUtils + +import scala.util.Using + +object LinagoraCalendarEventMethodContractUtilities { + private def _sendInvitationEmailToBobAndGetIcsBlobIds(server: GuiceJamesServer, invitationEml: String, + icsPartIds: String*): Seq[String] = { + + Using(ClassLoaderUtils.getSystemResourceAsSharedStream(invitationEml))(stream => { + val appendResult = server.getProbe(classOf[MailboxProbeImpl]) + .appendMessageAndGetAppendResult( + BOB.asString(), + MailboxPath.inbox(BOB), + AppendCommand.from(stream)) + + icsPartIds.map(partId => s"${appendResult.getId.getMessageId.serialize()}_$partId") + }).get + } + + def sendInvitationEmailToBobAndGetIcsBlobIds(server: GuiceJamesServer, invitationEml: String, + icsPartId: String): String = { + + _sendInvitationEmailToBobAndGetIcsBlobIds(server, invitationEml, icsPartId) match { + case Seq(a) => (a) + } + } + + def sendInvitationEmailToBobAndGetIcsBlobIds(server: GuiceJamesServer, invitationEml: String, + icsPartIds: (String, String)): (String, String) = { + + _sendInvitationEmailToBobAndGetIcsBlobIds(server, invitationEml, icsPartIds._1, icsPartIds._2) match { + case Seq(a, b) => (a, b) + } + } + + def sendInvitationEmailToBobAndGetIcsBlobIds(server: GuiceJamesServer, invitationEml: String, + icsPartIds: (String, String, String)): (String, String, String) = { + + _sendInvitationEmailToBobAndGetIcsBlobIds(server, invitationEml, icsPartIds._1, icsPartIds._2, icsPartIds._3) match { + case Seq(a, b, c) => (a, b, c) + } + } +} diff --git a/tmail-backend/integration-tests/jmap/jmap-integration-tests-common/src/main/scala/com/linagora/tmail/james/common/LinagoraCalendarEventRejectMethodContract.scala b/tmail-backend/integration-tests/jmap/jmap-integration-tests-common/src/main/scala/com/linagora/tmail/james/common/LinagoraCalendarEventRejectMethodContract.scala index 1d4155f453..6f8e08fcd3 100644 --- a/tmail-backend/integration-tests/jmap/jmap-integration-tests-common/src/main/scala/com/linagora/tmail/james/common/LinagoraCalendarEventRejectMethodContract.scala +++ b/tmail-backend/integration-tests/jmap/jmap-integration-tests-common/src/main/scala/com/linagora/tmail/james/common/LinagoraCalendarEventRejectMethodContract.scala @@ -1,8 +1,8 @@ package com.linagora.tmail.james.common -import java.io.{ByteArrayInputStream, InputStream} import java.util.concurrent.TimeUnit +import com.linagora.tmail.james.common.LinagoraCalendarEventMethodContractUtilities.sendInvitationEmailToBobAndGetIcsBlobIds import io.netty.handler.codec.http.HttpHeaderNames.ACCEPT import io.restassured.RestAssured.{`given`, requestSpecification} import io.restassured.http.ContentType.JSON @@ -11,17 +11,18 @@ import net.javacrumbs.jsonunit.JsonMatchers.jsonEquals import net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson import net.javacrumbs.jsonunit.core.Option.IGNORING_ARRAY_ORDER import org.apache.http.HttpStatus -import org.apache.http.HttpStatus.{SC_CREATED, SC_OK} +import org.apache.http.HttpStatus.SC_OK import org.apache.james.GuiceJamesServer import org.apache.james.jmap.core.ResponseObject.SESSION_STATE import org.apache.james.jmap.http.UserCredential -import org.apache.james.jmap.rfc8621.contract.Fixture.{ACCEPT_RFC8621_VERSION_HEADER, ACCOUNT_ID, ANDRE, ANDRE_ACCOUNT_ID, ANDRE_PASSWORD, BOB, BOB_PASSWORD, DOMAIN, authScheme, baseRequestSpecBuilder} +import org.apache.james.jmap.rfc8621.contract.Fixture._ import org.apache.james.jmap.rfc8621.contract.probe.DelegationProbe +import org.apache.james.jmap.rfc8621.contract.tags.CategoryTags import org.apache.james.mailbox.model.MailboxPath import org.apache.james.modules.MailboxProbeImpl import org.apache.james.utils.DataProbeImpl import org.hamcrest.Matchers -import org.junit.jupiter.api.{BeforeEach, Test} +import org.junit.jupiter.api.{BeforeEach, Tag, Test} import play.api.libs.json.Json trait LinagoraCalendarEventRejectMethodContract { @@ -30,6 +31,7 @@ trait LinagoraCalendarEventRejectMethodContract { server.getProbe(classOf[DataProbeImpl]) .fluent .addDomain(DOMAIN.asString) + .addDomain(_2_DOT_DOMAIN.asString)// Alice domain .addUser(BOB.asString, BOB_PASSWORD) .addUser(ANDRE.asString, ANDRE_PASSWORD) @@ -37,13 +39,16 @@ trait LinagoraCalendarEventRejectMethodContract { .setAuth(authScheme(UserCredential(BOB, BOB_PASSWORD))) .addHeader(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER) .build + + server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB)) } def randomBlobId: String @Test - def rejectShouldSucceed(): Unit = { - val blobId: String = uploadAndGetBlobId(ClassLoader.getSystemResourceAsStream("ics/aliceInviteBob.ics")) + def rejectShouldSucceed(server: GuiceJamesServer): Unit = { + val blobId = + sendInvitationEmailToBobAndGetIcsBlobIds(server, "emailWithAliceInviteBobIcsAttachment.eml", "3") val request: String = s"""{ @@ -85,8 +90,9 @@ trait LinagoraCalendarEventRejectMethodContract { } @Test - def shouldReturnNotFoundResultWhenBlobIdDoesNotExist(): Unit = { - val notFoundBlobId: String = randomBlobId + def shouldReturnNotFoundResultWhenBlobIdDoesNotExist(server: GuiceJamesServer): Unit = { + val notFoundBlobId = + sendInvitationEmailToBobAndGetIcsBlobIds(server, "emailWithAliceInviteBobIcsAttachment.eml", "999999") val request: String = s"""{ @@ -126,8 +132,9 @@ trait LinagoraCalendarEventRejectMethodContract { } @Test - def shouldReturnNotCreatedWhenNotAnICS(): Unit = { - val notParsableBlobId: String = uploadAndGetBlobId(new ByteArrayInputStream("notIcsFileFormat".getBytes)) + def shouldReturnNotCreatedWhenNotAnICS(server: GuiceJamesServer): Unit = { + val notParsableBlobId: String = + sendInvitationEmailToBobAndGetIcsBlobIds(server, "emailWithAliceInviteBobIcsAttachment.eml", icsPartId = "2") val request: String = s"""{ @@ -164,7 +171,7 @@ trait LinagoraCalendarEventRejectMethodContract { | "notRejected": { | "$notParsableBlobId": { | "type": "invalidPatch", - | "description": "Error at line 1:Expected [BEGIN], read [notIcsFileFormat]" + | "description": "Error at line 1:Expected [BEGIN], read [The message has a text attachment.]" | } | } | }, @@ -173,10 +180,10 @@ trait LinagoraCalendarEventRejectMethodContract { } @Test - def shouldSucceedWhenMixSeveralCases(): Unit = { - val notAcceptedId: String = uploadAndGetBlobId(new ByteArrayInputStream("notIcsFileFormat".getBytes)) - val notFoundBlobId: String = randomBlobId - val blobId: String = uploadAndGetBlobId(ClassLoader.getSystemResourceAsStream("ics/aliceInviteBob.ics")) + def shouldSucceedWhenMixSeveralCases(server: GuiceJamesServer): Unit = { + val (notAcceptedId, notFoundBlobId, blobId) = + sendInvitationEmailToBobAndGetIcsBlobIds(server, "emailWithAliceInviteBobIcsAttachment.eml", icsPartIds = ("2", "999999", "3")) + val request: String = s"""{ | "using": [ @@ -219,7 +226,7 @@ trait LinagoraCalendarEventRejectMethodContract { | "notRejected": { | "$notAcceptedId": { | "type": "invalidPatch", - | "description": "Error at line 1:Expected [BEGIN], read [notIcsFileFormat]" + | "description": "Error at line 1:Expected [BEGIN], read [The message has a text attachment.]" | } | } | }, @@ -342,7 +349,8 @@ trait LinagoraCalendarEventRejectMethodContract { @Test def shouldNotFoundWhenDoesNotHavePermission(server: GuiceJamesServer): Unit = { - val blobId: String = uploadAndGetBlobId(ClassLoader.getSystemResourceAsStream("ics/aliceInviteBob.ics")) + val blobId: String = + sendInvitationEmailToBobAndGetIcsBlobIds(server, "emailWithAliceInviteBobIcsAttachment.eml", icsPartId = "3") val request: String = s"""{ @@ -384,7 +392,9 @@ trait LinagoraCalendarEventRejectMethodContract { @Test def shouldSucceedWhenDelegated(server: GuiceJamesServer): Unit = { - val blobId: String = uploadAndGetBlobId(ClassLoader.getSystemResourceAsStream("ics/aliceInviteBob.ics")) + val blobId: String = + sendInvitationEmailToBobAndGetIcsBlobIds(server, "emailWithAliceInviteBobIcsAttachment.eml", icsPartId = "3") + server.getProbe(classOf[DelegationProbe]).addAuthorizedUser(BOB, ANDRE) val bobAccountId = ACCOUNT_ID @@ -472,9 +482,9 @@ trait LinagoraCalendarEventRejectMethodContract { } @Test - def shouldNotCreatedWhenInvalidIcsPayload(): Unit = { - val blobId1: String = uploadAndGetBlobId(ClassLoader.getSystemResourceAsStream("ics/invalid_TRANSP.ics")) - val blobId2: String = uploadAndGetBlobId(ClassLoader.getSystemResourceAsStream("ics/invalid_STATUS.ics")) + def shouldNotCreatedWhenInvalidIcsPayload(server: GuiceJamesServer): Unit = { + val (blobId1, blobId2) = + sendInvitationEmailToBobAndGetIcsBlobIds(server, "emailWithTwoInvalidIcsAttachments.eml", icsPartIds = ("5", "3")) val request: String = s"""{ @@ -525,8 +535,9 @@ trait LinagoraCalendarEventRejectMethodContract { } @Test - def shouldFailWhenInvalidLanguage(): Unit = { - val blobId: String = uploadAndGetBlobId(ClassLoader.getSystemResourceAsStream("ics/aliceInviteBob.ics")) + def shouldFailWhenInvalidLanguage(server: GuiceJamesServer): Unit = { + val blobId: String = + sendInvitationEmailToBobAndGetIcsBlobIds(server, "emailWithAliceInviteBobIcsAttachment.eml", icsPartId = "3") `given` .body(s"""{ @@ -550,8 +561,9 @@ trait LinagoraCalendarEventRejectMethodContract { } @Test - def shouldFailWhenUnsupportedLanguage(): Unit = { - val blobId: String = uploadAndGetBlobId(ClassLoader.getSystemResourceAsStream("ics/aliceInviteBob.ics")) + def shouldFailWhenUnsupportedLanguage(server: GuiceJamesServer): Unit = { + val blobId: String = + sendInvitationEmailToBobAndGetIcsBlobIds(server, "emailWithAliceInviteBobIcsAttachment.eml", icsPartId = "3") val response = `given` .body(s"""{ @@ -591,8 +603,9 @@ trait LinagoraCalendarEventRejectMethodContract { } @Test - def shouldSupportSpecialValidLanguages(): Unit = { - val blobId: String = uploadAndGetBlobId(ClassLoader.getSystemResourceAsStream("ics/aliceInviteBob.ics")) + def shouldSupportSpecialValidLanguages(server: GuiceJamesServer): Unit = { + val blobId: String = + sendInvitationEmailToBobAndGetIcsBlobIds(server, "emailWithAliceInviteBobIcsAttachment.eml", icsPartId = "3") val request: String = s"""{ @@ -635,9 +648,11 @@ trait LinagoraCalendarEventRejectMethodContract { } @Test + @Tag(CategoryTags.BASIC_FEATURE) def shouldSendReplyMailToInvitor(server: GuiceJamesServer): Unit = { val andreInboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(ANDRE)) - val blobId: String = uploadAndGetBlobId(new ByteArrayInputStream(generateInviteIcs(BOB.asString(), ANDRE.asString()).getBytes)) + val blobId: String = + sendInvitationEmailToBobAndGetIcsBlobIds(server, "emailWithAndreInviteBobIcsAttachment.eml", icsPartId = "3") `given` .body( s"""{ @@ -718,14 +733,14 @@ trait LinagoraCalendarEventRejectMethodContract { .inPath("methodResponses[1][1].list[0]") .isEqualTo( s"""{ - | "subject": "Declined: Simple event @ Fri Feb 23, 2024 (BOB )", + | "subject": "Declined: Sprint planning #23 @ Wed Jan 11, 2017 (BOB )", | "preview": "BOB has declined this invitation.", | "id": "$${json-unit.ignore}", | "hasAttachment": true, | "attachments": [ | { | "charset": "UTF-8", - | "size": 572, + | "size": 883, | "partId": "3", | "blobId": "$${json-unit.ignore}", | "type": "text/calendar" @@ -733,7 +748,7 @@ trait LinagoraCalendarEventRejectMethodContract { | { | "charset": "us-ascii", | "disposition": "attachment", - | "size": 572, + | "size": 883, | "partId": "4", | "blobId": "$${json-unit.ignore}", | "name": "invite.ics", @@ -747,7 +762,8 @@ trait LinagoraCalendarEventRejectMethodContract { @Test def mailReplyShouldSupportI18nWhenLanguageRequest(server: GuiceJamesServer): Unit = { val andreInboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(ANDRE)) - val blobId: String = uploadAndGetBlobId(new ByteArrayInputStream(generateInviteIcs(BOB.asString(), ANDRE.asString()).getBytes)) + val blobId: String = + sendInvitationEmailToBobAndGetIcsBlobIds(server, "emailWithAndreInviteBobIcsAttachment.eml", icsPartId = "3") `given` .body( s"""{ @@ -829,14 +845,14 @@ trait LinagoraCalendarEventRejectMethodContract { .inPath("methodResponses[1][1].list[0]") .isEqualTo( s"""{ - | "subject": "Décliné: Simple event @ Fri Feb 23, 2024 (BOB )", + | "subject": "Décliné: Sprint planning #23 @ Wed Jan 11, 2017 (BOB )", | "preview": "BOB a décliné cette invitation.", | "id": "$${json-unit.ignore}", | "hasAttachment": true, | "attachments": [ | { | "charset": "UTF-8", - | "size": 572, + | "size": 883, | "partId": "3", | "blobId": "$${json-unit.ignore}", | "type": "text/calendar" @@ -844,7 +860,7 @@ trait LinagoraCalendarEventRejectMethodContract { | { | "charset": "us-ascii", | "disposition": "attachment", - | "size": 572, + | "size": 883, | "partId": "4", | "blobId": "$${json-unit.ignore}", | "name": "invite.ics", @@ -855,43 +871,99 @@ trait LinagoraCalendarEventRejectMethodContract { } } + @Test + def shouldFailWhenBlobIdIsNotPrefixedByMessageId(): Unit = { + val request: String = + s"""{ + | "using": [ + | "urn:ietf:params:jmap:core", + | "com:linagora:params:calendar:event"], + | "methodCalls": [[ + | "CalendarEvent/maybe", + | { + | "accountId": "$ACCOUNT_ID", + | "blobIds": [ "abcd123" ] + | }, + | "c1"]] + |}""".stripMargin + + val response = + `given` + .body(request) + .when + .post + .`then` + .statusCode(SC_OK) + .contentType(JSON) + .extract + .body + .asString + + assertThatJson(response) + .inPath("methodResponses[0]") + .isEqualTo( + s"""[ + | "error", + | { + | "type": "invalidArguments", + | "description": "Invalid calendar event blobId: abcd123. The blobId should be in the form {messageId}_{partId}." + | }, + | "c1" + |]""".stripMargin) + } + + @Test + def shouldFailWhenCalendarBlobsBelongToDifferentMessages(server: GuiceJamesServer): Unit = { + val blobIdFromMessage1: String = + sendInvitationEmailToBobAndGetIcsBlobIds(server, "emailWithAliceInviteBobIcsAttachment.eml", icsPartId = "3") + val blobIdFromMessage2: String = + sendInvitationEmailToBobAndGetIcsBlobIds(server, "emailWithAliceInviteBobIcsAttachment.eml", icsPartId = "3") + + val message1Id = blobIdFromMessage1.split("_")(0) + val message2Id = blobIdFromMessage2.split("_")(0) + + val request: String = + s"""{ + | "using": [ + | "urn:ietf:params:jmap:core", + | "com:linagora:params:calendar:event"], + | "methodCalls": [[ + | "CalendarEvent/reject", + | { + | "accountId": "$ACCOUNT_ID", + | "blobIds": [ "$blobIdFromMessage1", "$blobIdFromMessage2" ] + | }, + | "c1"]] + |}""".stripMargin + + val response = + `given` + .body(request) + .when + .post + .`then` + .statusCode(SC_OK) + .contentType(JSON) + .extract + .body + .asString + + assertThatJson(response) + .inPath("methodResponses[0]") + .isEqualTo( + s"""[ + | "error", + | { + | "type": "serverFail", + | "description": "Expected a single enclosing message for all calendar event blobIds, got: [InMemoryMessageId{value=$message1Id}, InMemoryMessageId{value=$message2Id}]" + | }, + | "c1" + |]""".stripMargin) + } + private def buildAndreRequestSpecification(server: GuiceJamesServer): RequestSpecification = baseRequestSpecBuilder(server) .setAuth(authScheme(UserCredential(ANDRE, ANDRE_PASSWORD))) .addHeader(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER) .build - - private def uploadAndGetBlobId(payload: InputStream): String = - `given` - .basePath("") - .body(payload) - .when - .post(s"/upload/$ACCOUNT_ID") - .`then` - .statusCode(SC_CREATED) - .extract - .jsonPath() - .get("blobId") - - private def generateInviteIcs(invitee: String, organizer: String): String = - s"""BEGIN:VCALENDAR - |CALSCALE:GREGORIAN - |VERSION:2.0 - |PRODID:-//Linagora//TMail Calendar//EN - |METHOD:REQUEST - |CALSCALE:GREGORIAN - |BEGIN:VEVENT - |UID:8eae5147-f2df-4853-8fe0-c88678bc8b9f - |TRANSP:OPAQUE - |DTSTART;TZID=Europe/Paris:20240223T160000 - |DTEND;TZID=Europe/Paris:20240223T163000 - |CLASS:PUBLIC - |SUMMARY:Simple event - |ORGANIZER;CN=comptetest15.linagora@domain.tld:mailto:${organizer} - |DTSTAMP:20240222T204008Z - |SEQUENCE:0 - |ATTENDEE;CUTYPE=INDIVIDUAL;RSVP=TRUE;CN=BOB;PARTSTAT=NEEDS-ACTION;X-OBM-ID=348:mailto:${invitee} - |END:VEVENT - |END:VCALENDAR - |""".stripMargin } diff --git a/tmail-backend/integration-tests/jmap/jmap-integration-tests-common/src/main/scala/com/linagora/tmail/james/common/LinagoraCalendarEventReplyWithAMQPWorkflowContract.scala b/tmail-backend/integration-tests/jmap/jmap-integration-tests-common/src/main/scala/com/linagora/tmail/james/common/LinagoraCalendarEventReplyWithAMQPWorkflowContract.scala index d33b212258..7c39621356 100644 --- a/tmail-backend/integration-tests/jmap/jmap-integration-tests-common/src/main/scala/com/linagora/tmail/james/common/LinagoraCalendarEventReplyWithAMQPWorkflowContract.scala +++ b/tmail-backend/integration-tests/jmap/jmap-integration-tests-common/src/main/scala/com/linagora/tmail/james/common/LinagoraCalendarEventReplyWithAMQPWorkflowContract.scala @@ -1,9 +1,10 @@ package com.linagora.tmail.james.common -import java.io.{ByteArrayInputStream, InputStream} +import java.io.InputStream import java.util.Optional import java.util.concurrent.TimeUnit +import com.linagora.tmail.james.common.LinagoraCalendarEventMethodContractUtilities.sendInvitationEmailToBobAndGetIcsBlobIds import io.netty.handler.codec.http.HttpHeaderNames.ACCEPT import io.restassured.RestAssured.{`given`, requestSpecification} import io.restassured.http.ContentType.JSON @@ -15,8 +16,8 @@ import org.apache.http.HttpStatus.{SC_CREATED, SC_OK} import org.apache.james.GuiceJamesServer import org.apache.james.jmap.core.ResponseObject.SESSION_STATE import org.apache.james.jmap.http.UserCredential -import org.apache.james.jmap.rfc8621.contract.Fixture.{ACCEPT_RFC8621_VERSION_HEADER, ACCOUNT_ID, ANDRE, ANDRE_ACCOUNT_ID, ANDRE_PASSWORD, BOB, BOB_PASSWORD, DOMAIN, authScheme, baseRequestSpecBuilder} -import org.apache.james.mailbox.model.{MailboxId, MailboxPath} +import org.apache.james.jmap.rfc8621.contract.Fixture._ +import org.apache.james.mailbox.model.MailboxPath import org.apache.james.modules.MailboxProbeImpl import org.apache.james.utils.DataProbeImpl import org.assertj.core.api.Assertions.assertThat @@ -37,6 +38,8 @@ trait LinagoraCalendarEventReplyWithAMQPWorkflowContract { .setAuth(authScheme(UserCredential(BOB, BOB_PASSWORD))) .addHeader(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER) .build + + server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB)) } def randomBlobId: String @@ -46,8 +49,9 @@ trait LinagoraCalendarEventReplyWithAMQPWorkflowContract { @Test def shouldPublishAMQPMessageWhenReplyAcceptSuccess(server: GuiceJamesServer): Unit = { // Given an invitation file - val andreInboxId: MailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(ANDRE)) - val blobId: String = uploadAndGetBlobId(new ByteArrayInputStream(generateInviteIcs(BOB.asString(), ANDRE.asString()).getBytes)) + val andreInboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(ANDRE)) + val blobId: String = + sendInvitationEmailToBobAndGetIcsBlobIds(server, "emailWithAndreInviteBobIcsAttachment.eml", icsPartId = "3") // When Bob accepts the invitation `given` diff --git a/tmail-backend/jmap/extensions/pom.xml b/tmail-backend/jmap/extensions/pom.xml index cb608bcbcc..a7a2d4bcf9 100644 --- a/tmail-backend/jmap/extensions/pom.xml +++ b/tmail-backend/jmap/extensions/pom.xml @@ -167,6 +167,11 @@ uuid-creator ${uuid-creator.version} + + org.apache.james + james-server-queue-memory + test + diff --git a/tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/AttendanceStatus.java b/tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/AttendanceStatus.java index 4165f7d6c7..9ce0ed6b8c 100644 --- a/tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/AttendanceStatus.java +++ b/tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/AttendanceStatus.java @@ -14,6 +14,8 @@ import com.google.common.base.Preconditions; +import net.fortuna.ical4j.model.parameter.PartStat; + public enum AttendanceStatus { Accepted("$accepted"), Declined("$rejected"), @@ -56,6 +58,15 @@ public static Optional fromMessageFlags(Flags flags) { return eventAttendanceFlags.stream().findAny(); } + public Optional toPartStat() { + return switch (this) { + case Accepted -> Optional.of(PartStat.ACCEPTED); + case Declined -> Optional.of(PartStat.DECLINED); + case Tentative -> Optional.of(PartStat.TENTATIVE); + case NeedsAction -> Optional.of(PartStat.NEEDS_ACTION); + }; + } + public static Flags getEventAttendanceFlags() { return FlagsBuilder.builder() .add(EVENT_ATTENDANCE_FLAGS.toArray(new String[0])) diff --git a/tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/EventAttendanceRepository.java b/tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/EventAttendanceRepository.java index 4e25708e42..f21a4b4f78 100644 --- a/tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/EventAttendanceRepository.java +++ b/tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/EventAttendanceRepository.java @@ -1,11 +1,19 @@ package com.linagora.tmail.james.jmap; +import java.util.Optional; + import org.apache.james.core.Username; +import org.apache.james.jmap.mail.BlobIds; import org.apache.james.mailbox.model.MessageId; import org.reactivestreams.Publisher; -interface EventAttendanceRepository { +import com.linagora.tmail.james.jmap.model.CalendarEventReplyResults; +import com.linagora.tmail.james.jmap.model.LanguageLocation; + +public interface EventAttendanceRepository { Publisher getAttendanceStatus(Username username, MessageId messageId); - Publisher setAttendanceStatus(Username username, MessageId messageId, AttendanceStatus attendanceStatus); + Publisher setAttendanceStatus(Username username, AttendanceStatus attendanceStatus, + BlobIds eventBlobIds, + Optional maybePreferredLanguage); } \ No newline at end of file diff --git a/tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/StandaloneEventAttendanceRepository.java b/tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/StandaloneEventAttendanceRepository.java index c2f578ba42..3c2b863f49 100644 --- a/tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/StandaloneEventAttendanceRepository.java +++ b/tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/StandaloneEventAttendanceRepository.java @@ -1,11 +1,14 @@ package com.linagora.tmail.james.jmap; import java.util.List; +import java.util.Optional; import jakarta.inject.Inject; import jakarta.mail.Flags; import org.apache.james.core.Username; +import org.apache.james.jmap.core.AccountId; +import org.apache.james.jmap.mail.BlobIds; import org.apache.james.mailbox.MailboxSession; import org.apache.james.mailbox.MessageIdManager; import org.apache.james.mailbox.MessageManager; @@ -18,19 +21,31 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.linagora.tmail.james.jmap.method.CalendarEventReplyPerformer; +import com.linagora.tmail.james.jmap.model.CalendarEventReplyRequest; +import com.linagora.tmail.james.jmap.model.CalendarEventReplyResults; +import com.linagora.tmail.james.jmap.model.LanguageLocation; + import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import scala.collection.JavaConverters; +import scala.compat.java8.OptionConverters; public class StandaloneEventAttendanceRepository implements EventAttendanceRepository { private static final Logger LOGGER = LoggerFactory.getLogger(StandaloneEventAttendanceRepository.class); private final MessageIdManager messageIdManager; private final SessionProvider sessionProvider; + private final CalendarEventReplyPerformer calendarEventReplyPerformer; + private final MessageId.Factory messageIdFactory; @Inject - public StandaloneEventAttendanceRepository(MessageIdManager messageIdManager, SessionProvider sessionProvider) { + public StandaloneEventAttendanceRepository(MessageIdManager messageIdManager, SessionProvider sessionProvider, + CalendarEventReplyPerformer calendarEventReplyPerformer, MessageId.Factory messageIdFactory) { this.messageIdManager = messageIdManager; this.sessionProvider = sessionProvider; + this.calendarEventReplyPerformer = calendarEventReplyPerformer; + this.messageIdFactory = messageIdFactory; } @Override @@ -52,15 +67,79 @@ private Mono handleMissingEventAttendanceFlag(MessageId messag } @Override - public Publisher setAttendanceStatus(Username username, MessageId messageId, - AttendanceStatus attendanceStatus) { + public Publisher setAttendanceStatus(Username username, AttendanceStatus attendanceStatus, + BlobIds eventBlobIds, + Optional maybePreferredLanguage) { MailboxSession systemMailboxSession = sessionProvider.createSystemSession(username); - return Flux.from(messageIdManager.getMessagesReactive(List.of(messageId), FetchGroup.MINIMAL, systemMailboxSession)) - .map(MessageResult::getMailboxId) + return getEnclosingMessageId(eventBlobIds).flatMap(messageId -> + Flux.from(messageIdManager.getMessagesReactive(List.of(messageId), FetchGroup.MINIMAL, systemMailboxSession)) + .map(MessageResult::getMailboxId) + .collectList() + .flatMap(mailboxIds -> + updateEventAttendanceFlagsInMailboxes( + messageId, + attendanceStatus, + systemMailboxSession, + mailboxIds))) + .then(tryToSendReplyEmail(username, eventBlobIds, maybePreferredLanguage, systemMailboxSession, attendanceStatus)); + } + + private Mono tryToSendReplyEmail(Username username, + BlobIds eventBlobIds, + Optional maybePreferredLanguage, + MailboxSession systemMailboxSession, + AttendanceStatus attendanceStatus) { + return Mono.just(AccountId.from(username)) + .flatMap(accountIdEither -> + accountIdEither.fold( + exception -> + Mono.error(new IllegalArgumentException("Failed to get account id from username: " + username, exception)), + accountId -> + doSendReplyEmail(accountId, systemMailboxSession, eventBlobIds, maybePreferredLanguage, attendanceStatus))); + } + + private Mono getEnclosingMessageId(BlobIds blobIds) { + return Flux.fromIterable(JavaConverters.seqAsJavaList(blobIds.value())) + .flatMap(blobId -> extractMessageId(blobId.value())) + .distinct() .collectList() - .flatMap(mailboxIds -> - updateEventAttendanceFlagsInMailboxes(messageId, attendanceStatus, systemMailboxSession, mailboxIds)); + .handle((enclosingMessageIds, sink) -> { + if (enclosingMessageIds.size() != 1) { + sink.error(new IllegalStateException("Expected a single enclosing message for all calendar event blobIds, got: " + enclosingMessageIds)); + } else { + sink.next(enclosingMessageIds.getFirst()); + } + }); + } + + private Mono extractMessageId(String blobId) { + return Mono.just(blobId) + .handle((blobIdParam, sink) -> { + int underscoreIndex = blobId.indexOf('_'); + if (underscoreIndex == -1) { + sink.error(new IllegalArgumentException(""" + Invalid calendar event blobId: %s. The blobId should be in the form {messageId}_{partId}.\ + """.formatted(blobId))); + } else { + String messageId = blobId.substring(0, underscoreIndex); + sink.next(messageIdFactory.fromString(messageId)); + } + }); + } + + private Mono doSendReplyEmail(AccountId accountId, + MailboxSession session, + BlobIds eventBlobIds, + Optional maybePreferredLanguage, + AttendanceStatus attendanceStatus) { + return Mono.justOrEmpty(attendanceStatus.toPartStat()) + .switchIfEmpty(Mono.error(new IllegalArgumentException("Invalid attendance status: " + attendanceStatus))) + .flatMap(partStat -> Mono.from( + calendarEventReplyPerformer.process( + new CalendarEventReplyRequest(accountId, eventBlobIds, + OptionConverters.toScala(maybePreferredLanguage)), + session, partStat))); } private Mono updateEventAttendanceFlagsInMailboxes(MessageId messageId, AttendanceStatus attendanceStatus, diff --git a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/TMailJMAPModule.scala b/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/TMailJMAPModule.scala index a393b1fe65..09c26aad30 100644 --- a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/TMailJMAPModule.scala +++ b/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/TMailJMAPModule.scala @@ -4,7 +4,10 @@ import java.io.FileNotFoundException import com.google.inject.name.Named import com.google.inject.{AbstractModule, Provides, Singleton} +import com.linagora.tmail.james.jmap.method.CalendarEventReplyPerformer import org.apache.commons.configuration2.{Configuration, PropertiesConfiguration} +import org.apache.james.mailbox.model.MessageId +import org.apache.james.mailbox.{MessageIdManager, SessionProvider} import org.apache.james.utils.PropertiesProvider import scala.util.Try @@ -27,4 +30,8 @@ class TMailJMAPModule extends AbstractModule { @Provides def providePublicAssetTotalSizeLimit(jmapExtensionConfiguration: JMAPExtensionConfiguration): PublicAssetTotalSizeLimit = jmapExtensionConfiguration.publicAssetTotalSizeLimit + + @Provides + def provideEventAttendanceRepository(messageIdManager: MessageIdManager, sessionProvider: SessionProvider, calendarEventReplyPerformer: CalendarEventReplyPerformer, messageIdFactory: MessageId.Factory): EventAttendanceRepository = + new StandaloneEventAttendanceRepository(messageIdManager, sessionProvider, calendarEventReplyPerformer, messageIdFactory) } diff --git a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/method/CalendarEventAcceptMethod.scala b/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/method/CalendarEventAcceptMethod.scala index 71131a7ddf..571589367d 100644 --- a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/method/CalendarEventAcceptMethod.scala +++ b/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/method/CalendarEventAcceptMethod.scala @@ -3,9 +3,9 @@ package com.linagora.tmail.james.jmap.method import com.linagora.tmail.james.jmap.json.CalendarEventReplySerializer import com.linagora.tmail.james.jmap.method.CapabilityIdentifier.LINAGORA_CALENDAR import com.linagora.tmail.james.jmap.model.{CalendarEventReplyAcceptedResponse, CalendarEventReplyRequest} +import com.linagora.tmail.james.jmap.{AttendanceStatus, EventAttendanceRepository} import eu.timepit.refined.auto._ import jakarta.inject.Inject -import net.fortuna.ical4j.model.parameter.PartStat import org.apache.james.jmap.core.CapabilityIdentifier.{CapabilityIdentifier, JMAP_CORE} import org.apache.james.jmap.core.Invocation.{Arguments, MethodName} import org.apache.james.jmap.core.{Invocation, SessionTranslator} @@ -16,8 +16,11 @@ import org.apache.james.mailbox.MailboxSession import org.apache.james.metrics.api.MetricFactory import org.reactivestreams.Publisher import play.api.libs.json.JsObject +import reactor.core.publisher.Mono -class CalendarEventAcceptMethod @Inject()(val calendarEventReplyPerformer: CalendarEventReplyPerformer, +import scala.compat.java8.OptionConverters + +class CalendarEventAcceptMethod @Inject()(val eventAttendanceRepository: EventAttendanceRepository, val metricFactory: MetricFactory, val sessionTranslator: SessionTranslator, val sessionSupplier: SessionSupplier, @@ -37,12 +40,12 @@ class CalendarEventAcceptMethod @Inject()(val calendarEventReplyPerformer: Calen invocation: InvocationWithContext, mailboxSession: MailboxSession, request: CalendarEventReplyRequest): Publisher[InvocationWithContext] = { - calendarEventReplyPerformer.process(request, mailboxSession, PartStat.ACCEPTED) + Mono.from(eventAttendanceRepository.setAttendanceStatus(mailboxSession.getUser, AttendanceStatus.Accepted, request.blobIds, OptionConverters.toJava(request.language))) .map(result => CalendarEventReplyAcceptedResponse.from(request.accountId, result)) - .map(response => Invocation( - methodName, - Arguments(CalendarEventReplySerializer.serialize(response).as[JsObject]), - invocation.invocation.methodCallId)) - .map(InvocationWithContext(_, invocation.processingContext)) + .map(response => Invocation( + methodName, + Arguments(CalendarEventReplySerializer.serialize(response).as[JsObject]), + invocation.invocation.methodCallId)) + .map(InvocationWithContext(_, invocation.processingContext)) } } diff --git a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/method/CalendarEventMaybeMethod.scala b/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/method/CalendarEventMaybeMethod.scala index 742dbad86b..7ca049e38c 100644 --- a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/method/CalendarEventMaybeMethod.scala +++ b/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/method/CalendarEventMaybeMethod.scala @@ -3,9 +3,9 @@ package com.linagora.tmail.james.jmap.method import com.linagora.tmail.james.jmap.json.CalendarEventReplySerializer import com.linagora.tmail.james.jmap.method.CapabilityIdentifier.LINAGORA_CALENDAR import com.linagora.tmail.james.jmap.model.{CalendarEventReplyMaybeResponse, CalendarEventReplyRequest} +import com.linagora.tmail.james.jmap.{AttendanceStatus, EventAttendanceRepository} import eu.timepit.refined.auto._ import jakarta.inject.Inject -import net.fortuna.ical4j.model.parameter.PartStat import org.apache.james.jmap.core.CapabilityIdentifier.{CapabilityIdentifier, JMAP_CORE} import org.apache.james.jmap.core.Invocation.{Arguments, MethodName} import org.apache.james.jmap.core.{Invocation, SessionTranslator} @@ -16,8 +16,11 @@ import org.apache.james.mailbox.MailboxSession import org.apache.james.metrics.api.MetricFactory import org.reactivestreams.Publisher import play.api.libs.json.JsObject +import reactor.core.publisher.Mono -class CalendarEventMaybeMethod @Inject()(val calendarEventReplyPerformer: CalendarEventReplyPerformer, +import scala.compat.java8.OptionConverters + +class CalendarEventMaybeMethod @Inject()(val eventAttendanceRepository: EventAttendanceRepository, val metricFactory: MetricFactory, val sessionTranslator: SessionTranslator, val sessionSupplier: SessionSupplier, @@ -30,7 +33,7 @@ class CalendarEventMaybeMethod @Inject()(val calendarEventReplyPerformer: Calend override def doProcess(capabilities: Set[CapabilityIdentifier], invocation: InvocationWithContext, mailboxSession: MailboxSession, request: CalendarEventReplyRequest): Publisher[InvocationWithContext] = { - calendarEventReplyPerformer.process(request, mailboxSession, PartStat.TENTATIVE) + Mono.from(eventAttendanceRepository.setAttendanceStatus(mailboxSession.getUser, AttendanceStatus.Tentative, request.blobIds, OptionConverters.toJava(request.language))) .map(result => CalendarEventReplyMaybeResponse.from(request.accountId, result)) .map(response => Invocation( methodName, diff --git a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/method/CalendarEventRejectMethod.scala b/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/method/CalendarEventRejectMethod.scala index e841bfb01e..1c337969c0 100644 --- a/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/method/CalendarEventRejectMethod.scala +++ b/tmail-backend/jmap/extensions/src/main/scala/com/linagora/tmail/james/jmap/method/CalendarEventRejectMethod.scala @@ -2,10 +2,10 @@ package com.linagora.tmail.james.jmap.method import com.linagora.tmail.james.jmap.json.CalendarEventReplySerializer import com.linagora.tmail.james.jmap.method.CapabilityIdentifier.LINAGORA_CALENDAR -import com.linagora.tmail.james.jmap.model.{CalendarEventReplyRejectedResponse, CalendarEventReplyRequest} +import com.linagora.tmail.james.jmap.model.{CalendarEventReplyAcceptedResponse, CalendarEventReplyRejectedResponse, CalendarEventReplyRequest} +import com.linagora.tmail.james.jmap.{AttendanceStatus, EventAttendanceRepository} import eu.timepit.refined.auto._ import jakarta.inject.Inject -import net.fortuna.ical4j.model.parameter.PartStat import org.apache.james.jmap.core.CapabilityIdentifier.{CapabilityIdentifier, JMAP_CORE} import org.apache.james.jmap.core.Invocation.{Arguments, MethodName} import org.apache.james.jmap.core.{Invocation, SessionTranslator} @@ -16,8 +16,11 @@ import org.apache.james.mailbox.MailboxSession import org.apache.james.metrics.api.MetricFactory import org.reactivestreams.Publisher import play.api.libs.json.JsObject +import reactor.core.publisher.Mono -class CalendarEventRejectMethod @Inject()(val calendarEventReplyPerformer: CalendarEventReplyPerformer, +import scala.compat.java8.OptionConverters + +class CalendarEventRejectMethod @Inject()(val eventAttendanceRepository: EventAttendanceRepository, val metricFactory: MetricFactory, val sessionTranslator: SessionTranslator, val sessionSupplier: SessionSupplier, @@ -35,7 +38,7 @@ class CalendarEventRejectMethod @Inject()(val calendarEventReplyPerformer: Calen invocation: InvocationWithContext, mailboxSession: MailboxSession, request: CalendarEventReplyRequest): Publisher[InvocationWithContext] = - calendarEventReplyPerformer.process(request, mailboxSession, PartStat.DECLINED) + Mono.from(eventAttendanceRepository.setAttendanceStatus(mailboxSession.getUser, AttendanceStatus.Declined, request.blobIds, OptionConverters.toJava(request.language))) .map(result => CalendarEventReplyRejectedResponse.from(request.accountId, result)) .map(response => Invocation( methodName, diff --git a/tmail-backend/jmap/extensions/src/test/java/com/linagora/tmail/james/jmap/StandaloneEventAttendanceRepositoryTest.java b/tmail-backend/jmap/extensions/src/test/java/com/linagora/tmail/james/jmap/StandaloneEventAttendanceRepositoryTest.java deleted file mode 100644 index 4abac0c0aa..0000000000 --- a/tmail-backend/jmap/extensions/src/test/java/com/linagora/tmail/james/jmap/StandaloneEventAttendanceRepositoryTest.java +++ /dev/null @@ -1,205 +0,0 @@ -package com.linagora.tmail.james.jmap; - -import java.util.List; -import reactor.core.publisher.Mono; - -import static org.apache.james.mailbox.fixture.MailboxFixture.ALICE; -import static org.assertj.core.api.Assertions.assertThat; - -import jakarta.mail.Flags; - -import org.apache.james.mailbox.MailboxSession; -import org.apache.james.mailbox.MailboxSessionUtil; -import org.apache.james.mailbox.MessageUid; -import org.apache.james.mailbox.exception.MailboxException; -import org.apache.james.mailbox.fixture.MailboxFixture; -import org.apache.james.mailbox.inmemory.InMemoryMessageId; -import org.apache.james.mailbox.inmemory.manager.InMemoryIntegrationResources; -import org.apache.james.mailbox.model.FetchGroup; -import org.apache.james.mailbox.model.Mailbox; -import org.apache.james.mailbox.model.MessageId; -import org.apache.james.mailbox.model.MessageResult; -import org.apache.james.mailbox.store.MessageIdManagerTestSystem; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -public class StandaloneEventAttendanceRepositoryTest { - MailboxSession session; - StandaloneEventAttendanceRepository testee; - MessageIdManagerTestSystem messageIdManagerTestSystem; - Mailbox mailbox; - - @BeforeEach - void setUp() throws MailboxException { - messageIdManagerTestSystem = createTestSystem(); - - testee = new StandaloneEventAttendanceRepository(messageIdManagerTestSystem.getMessageIdManager(), - messageIdManagerTestSystem.getMailboxManager().getSessionProvider()); - - session = MailboxSessionUtil.create(ALICE); - mailbox = messageIdManagerTestSystem.createMailbox(MailboxFixture.INBOX_ALICE, session); - } - - @Test - void givenAcceptedFlagIsLinkedToMailGetAttendanceStatusShouldReturnAccepted() { - Flags flags = new Flags("$accepted"); - MessageId messageId = createMessage(flags); - assertThat(Mono.from(testee.getAttendanceStatus(mailbox.getUser(), messageId)).block()) - .isEqualTo(AttendanceStatus.Accepted); - } - - @Test - void givenRejectedFlagIsLinkedToMailGetAttendanceStatusShouldReturnDeclined() { - Flags flags = new Flags("$rejected"); - MessageId messageId = createMessage(flags); - assertThat(Mono.from(testee.getAttendanceStatus(mailbox.getUser(), messageId)).block()) - .isEqualTo(AttendanceStatus.Declined); - } - - @Test - void givenTentativelyAcceptedFlagIsLinkedToMailGetAttendanceStatusShouldReturnTentative() { - Flags flags = new Flags("$tentativelyaccepted"); - MessageId messageId = createMessage(flags); - assertThat(Mono.from(testee.getAttendanceStatus(mailbox.getUser(), messageId)).block()) - .isEqualTo(AttendanceStatus.Tentative); - } - - // It should also print a warning message - @Test - void givenMoreThanEventAttendanceFlagIsLinkedToMailGetAttendanceStatusShouldReturnAny() { - Flags flags = new Flags("$rejected"); - flags.add("$accepted"); - - MessageId messageId = createMessage(flags); - assertThat(Mono.from(testee.getAttendanceStatus(mailbox.getUser(), messageId)).block()) - .satisfiesAnyOf( - status -> assertThat(status).isEqualTo(AttendanceStatus.Accepted), - status -> assertThat(status).isEqualTo(AttendanceStatus.Declined)); - } - - @Test - void getAttendanceStatusShouldFallbackToNeedsActionWhenNoFlagIsLinkedToMail() { - Flags flags = new Flags(); - MessageId messageId = createMessage(flags); - - assertThat(Mono.from(testee.getAttendanceStatus(mailbox.getUser(), messageId)).block()) - .isEqualTo(AttendanceStatus.NeedsAction); - } - - @Test - void getAttendanceStatusShouldFallbackToNeedsActionWhenNoEventAttendanceFlagIsLinkedToMail() { - Flags flags = new Flags(Flags.Flag.RECENT); - MessageId messageId = createMessage(flags); - - assertThat(Mono.from(testee.getAttendanceStatus(mailbox.getUser(), messageId)).block()) - .isEqualTo(AttendanceStatus.NeedsAction); - } - - @Test - void setAttendanceStatusShouldSetAcceptedFlag() { - Flags flags = new Flags(); - MessageId messageId = createMessage(flags); - - Mono.from(testee.setAttendanceStatus(mailbox.getUser(), messageId, AttendanceStatus.Accepted)).block(); - - Flags updatedFlags = getFlags(messageId); - - assertThat(updatedFlags.contains("$accepted")).isTrue(); - } - - @Test - void setAttendanceStatusShouldSetDeclinedFlag() { - Flags flags = new Flags(); - MessageId messageId = createMessage(flags); - - Mono.from(testee.setAttendanceStatus(mailbox.getUser(), messageId, AttendanceStatus.Declined)).block(); - - Flags updatedFlags = getFlags(messageId); - assertThat(updatedFlags.contains("$rejected")).isTrue(); - } - - @Test - void setAttendanceStatusShouldSetTentativeFlag() { - Flags flags = new Flags(); - MessageId messageId = createMessage(flags); - - Mono.from(testee.setAttendanceStatus(mailbox.getUser(), messageId, AttendanceStatus.Tentative)).block(); - - Flags updatedFlags = getFlags(messageId); - assertThat(updatedFlags.contains("$tentativelyaccepted")).isTrue(); - } - - @Test - void setAttendanceStatusShouldRemoveExistingEventAttendanceFlags() { - Flags flags = new Flags("$accepted"); - MessageId messageId = createMessage(flags); - - Mono.from(testee.setAttendanceStatus(mailbox.getUser(), messageId, AttendanceStatus.Declined)).block(); - - Flags updatedFlags = getFlags(messageId); - assertThat(updatedFlags.contains("$accepted")).isFalse(); - assertThat(updatedFlags.contains("$rejected")).isTrue(); - } - - @Test - void setAttendanceStatusShouldRemoveExistingEventAttendanceFlagsWhenNeedsActionSet() { - Flags flags = new Flags("$rejected"); - MessageId messageId = createMessage(flags); - - Mono.from(testee.setAttendanceStatus(mailbox.getUser(), messageId, AttendanceStatus.NeedsAction)).block(); - - Flags updatedFlags = getFlags(messageId); - assertThat(updatedFlags.contains("$rejected")).isFalse(); - assertThat(updatedFlags.contains("$needs-action")).isTrue(); - } - - @Test - void setAttendanceStatusShouldBeIdempotent() { - Flags flags = new Flags(); - MessageId messageId = createMessage(flags); - - Mono.from(testee.setAttendanceStatus(mailbox.getUser(), messageId, AttendanceStatus.Accepted)).block(); - Mono.from(testee.setAttendanceStatus(mailbox.getUser(), messageId, AttendanceStatus.Accepted)).block(); - - Flags updatedFlags = getFlags(messageId); - - assertThat(updatedFlags.contains("$accepted")).isTrue(); - assertThat(updatedFlags.getUserFlags().length).isEqualTo(1); - } - - private Flags getFlags(MessageId messageId) { - return Mono.from(messageIdManagerTestSystem.getMessageIdManager() - .getMessagesReactive(List.of(messageId), FetchGroup.MINIMAL, session)) - .map(MessageResult::getFlags) - .block(); - } - - private MessageId createMessage(Flags flags) { - MessageId messageId = messageIdManagerTestSystem.persist( - mailbox.getMailboxId(), - MessageUid.of(111), - flags, - session); - return messageId; - } - - protected MessageIdManagerTestSystem createTestSystem() { - InMemoryMessageId.Factory messageIdFactory = new InMemoryMessageId.Factory(); - - InMemoryIntegrationResources resources = InMemoryIntegrationResources.builder() - .preProvisionnedFakeAuthenticator() - .fakeAuthorizator() - .inVmEventBus() - .defaultAnnotationLimits() - .defaultMessageParser() - .scanningSearchIndex() - .noPreDeletionHooks() - .storeQuotaManager() - .build(); - - return new MessageIdManagerTestSystem(resources.getMessageIdManager(), - messageIdFactory, - resources.getMailboxManager().getMapperFactory(), - resources.getMailboxManager()); - } -} diff --git a/tmail-backend/jmap/extensions/src/test/scala/com/linagora/tmail/james/jmap/StandaloneEventAttendanceRepositoryTest.scala b/tmail-backend/jmap/extensions/src/test/scala/com/linagora/tmail/james/jmap/StandaloneEventAttendanceRepositoryTest.scala new file mode 100644 index 0000000000..7cfbc0c704 --- /dev/null +++ b/tmail-backend/jmap/extensions/src/test/scala/com/linagora/tmail/james/jmap/StandaloneEventAttendanceRepositoryTest.scala @@ -0,0 +1,202 @@ +package com.linagora.tmail.james.jmap + +import java.util +import java.util.Optional + +import com.linagora.tmail.james.jmap.method.CalendarEventReplyPerformer +import com.linagora.tmail.james.jmap.model.CalendarEventReplyRequest +import jakarta.mail.Flags +import net.fortuna.ical4j.model.parameter.PartStat +import org.apache.james.jmap.mail.{BlobId, BlobIds, PartId} +import org.apache.james.mailbox.exception.MailboxException +import org.apache.james.mailbox.fixture.MailboxFixture +import org.apache.james.mailbox.fixture.MailboxFixture.ALICE +import org.apache.james.mailbox.inmemory.manager.InMemoryIntegrationResources +import org.apache.james.mailbox.model.{FetchGroup, Mailbox, MessageId, TestMessageId} +import org.apache.james.mailbox.store.MessageIdManagerTestSystem +import org.apache.james.mailbox.{MailboxSession, MailboxSessionUtil, MessageUid} +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.{BeforeEach, Test} +import org.mockito.ArgumentMatchers.any +import org.mockito.Mockito.{mock, when} +import reactor.core.publisher.Mono +import reactor.core.scala.publisher.SMono + +import scala.util.Random + +class StandaloneEventAttendanceRepositoryTest { + var calendarEventReplyPerformer: CalendarEventReplyPerformer = _ + var session: MailboxSession = _ + var testee: StandaloneEventAttendanceRepository = _ + var messageIdManagerTestSystem: MessageIdManagerTestSystem = _ + var mailbox: Mailbox = _ + + @BeforeEach + @throws[MailboxException] + def setUp(): Unit = { + messageIdManagerTestSystem = createTestSystem + calendarEventReplyPerformer = mock(classOf[CalendarEventReplyPerformer]) + when(calendarEventReplyPerformer.process( + any(classOf[CalendarEventReplyRequest]), + any(classOf[MailboxSession]), + any(classOf[PartStat]))) + .thenReturn(SMono.empty) + + testee = new StandaloneEventAttendanceRepository(messageIdManagerTestSystem.getMessageIdManager, messageIdManagerTestSystem.getMailboxManager.getSessionProvider, calendarEventReplyPerformer, new TestMessageId.Factory) + session = MailboxSessionUtil.create(ALICE) + mailbox = messageIdManagerTestSystem.createMailbox(MailboxFixture.INBOX_ALICE, session) + } + + @Test + def givenAcceptedFlagIsLinkedToMailGetAttendanceStatusShouldReturnAccepted(): Unit = { + val flags: Flags = new Flags("$accepted") + val messageId: MessageId = createMessage(flags) + assertThat(Mono.from(testee.getAttendanceStatus(mailbox.getUser, messageId)).block) + .isEqualTo(AttendanceStatus.Accepted) + } + + @Test + def givenRejectedFlagIsLinkedToMailGetAttendanceStatusShouldReturnDeclined(): Unit = { + val flags: Flags = new Flags("$rejected") + val messageId: MessageId = createMessage(flags) + assertThat(Mono.from(testee.getAttendanceStatus(mailbox.getUser, messageId)).block).isEqualTo(AttendanceStatus.Declined) + } + + @Test + def givenTentativelyAcceptedFlagIsLinkedToMailGetAttendanceStatusShouldReturnTentative(): Unit = { + val flags: Flags = new Flags("$tentativelyaccepted") + val messageId: MessageId = createMessage(flags) + assertThat(Mono.from(testee.getAttendanceStatus(mailbox.getUser, messageId)).block).isEqualTo(AttendanceStatus.Tentative) + } + + // It should also print a warning message + @Test + def givenMoreThanEventAttendanceFlagIsLinkedToMailGetAttendanceStatusShouldReturnAny(): Unit = { + val flags: Flags = new Flags("$rejected") + flags.add("$accepted") + val messageId: MessageId = createMessage(flags) + + + assertThat(util.List.of(AttendanceStatus.Accepted, AttendanceStatus.Declined)) + .contains(Mono.from(testee.getAttendanceStatus(mailbox.getUser, messageId)).block) + } + + @Test + def getAttendanceStatusShouldFallbackToNeedsActionWhenNoFlagIsLinkedToMail(): Unit = { + val flags: Flags = new Flags + val messageId: MessageId = createMessage(flags) + assertThat(Mono.from(testee.getAttendanceStatus(mailbox.getUser, messageId)).block).isEqualTo(AttendanceStatus.NeedsAction) + } + + @Test + def getAttendanceStatusShouldFallbackToNeedsActionWhenNoEventAttendanceFlagIsLinkedToMail(): Unit = { + val flags: Flags = new Flags(Flags.Flag.RECENT) + val messageId: MessageId = createMessage(flags) + assertThat(Mono.from(testee.getAttendanceStatus(mailbox.getUser, messageId)).block).isEqualTo(AttendanceStatus.NeedsAction) + } + + @Test + def setAttendanceStatusShouldSetAcceptedFlag(): Unit = { + val flags: Flags = new Flags + val messageId: MessageId = createMessage(flags) + val calendaerEventBlobId: BlobId = createFakeCalendaerEventBlobId(messageId) + val blobIds: BlobIds = BlobIds(Seq(calendaerEventBlobId.value)) + + Mono.from(testee.setAttendanceStatus(mailbox.getUser, AttendanceStatus.Accepted, blobIds, Optional.empty)).block + + val updatedFlags: Flags = getFlags(messageId) + + assertThat(updatedFlags.contains("$accepted")).isTrue + } + + @Test + def setAttendanceStatusShouldSetDeclinedFlag(): Unit = { + val flags: Flags = new Flags + val messageId: MessageId = createMessage(flags) + val calendaerEventBlobId: BlobId = createFakeCalendaerEventBlobId(messageId) + val blobIds: BlobIds = BlobIds(Seq(calendaerEventBlobId.value)) + + Mono.from(testee.setAttendanceStatus(mailbox.getUser, AttendanceStatus.Declined, blobIds, Optional.empty)).block + val updatedFlags: Flags = getFlags(messageId) + assertThat(updatedFlags.contains("$rejected")).isTrue + } + + @Test + def setAttendanceStatusShouldSetTentativeFlag(): Unit = { + val flags: Flags = new Flags + val messageId: MessageId = createMessage(flags) + val calendaerEventBlobId: BlobId = createFakeCalendaerEventBlobId(messageId) + val blobIds: BlobIds = BlobIds(Seq(calendaerEventBlobId.value)) + + Mono.from(testee.setAttendanceStatus(mailbox.getUser, AttendanceStatus.Tentative, blobIds, Optional.empty)).block + val updatedFlags: Flags = getFlags(messageId) + assertThat(updatedFlags.contains("$tentativelyaccepted")).isTrue + } + + @Test + def setAttendanceStatusShouldRemoveExistingEventAttendanceFlags(): Unit = { + val flags: Flags = new Flags("$accepted") + val messageId: MessageId = createMessage(flags) + val calendaerEventBlobId: BlobId = createFakeCalendaerEventBlobId(messageId) + val blobIds: BlobIds = BlobIds(Seq(calendaerEventBlobId.value)) + + Mono.from(testee.setAttendanceStatus(mailbox.getUser, AttendanceStatus.Declined, blobIds, Optional.empty)).block + val updatedFlags: Flags = getFlags(messageId) + assertThat(updatedFlags.contains("$accepted")).isFalse + assertThat(updatedFlags.contains("$rejected")).isTrue + } + + @Test + def setAttendanceStatusShouldRemoveExistingEventAttendanceFlagsWhenNeedsActionSet(): Unit = { + val flags: Flags = new Flags("$rejected") + val messageId: MessageId = createMessage(flags) + val calendaerEventBlobId: BlobId = createFakeCalendaerEventBlobId(messageId) + val blobIds: BlobIds = BlobIds(Seq(calendaerEventBlobId.value)) + + Mono.from(testee.setAttendanceStatus(mailbox.getUser, AttendanceStatus.NeedsAction, blobIds, Optional.empty)).block + val updatedFlags: Flags = getFlags(messageId) + assertThat(updatedFlags.contains("$rejected")).isFalse + assertThat(updatedFlags.contains("$needs-action")).isTrue + } + + @Test + def setAttendanceStatusShouldBeIdempotent(): Unit = { + val flags: Flags = new Flags + val messageId: MessageId = createMessage(flags) + val calendaerEventBlobId: BlobId = createFakeCalendaerEventBlobId(messageId) + val blobIds: BlobIds = BlobIds(Seq(calendaerEventBlobId.value)) + + Mono.from(testee.setAttendanceStatus(mailbox.getUser, AttendanceStatus.Accepted, blobIds, Optional.empty)).block + Mono.from(testee.setAttendanceStatus(mailbox.getUser, AttendanceStatus.Accepted, blobIds, Optional.empty)).block + val updatedFlags: Flags = getFlags(messageId) + assertThat(updatedFlags.contains("$accepted")).isTrue + assertThat(updatedFlags.getUserFlags.length).isEqualTo(1) + } + + private def getFlags(messageId: MessageId): Flags = + Mono.from(messageIdManagerTestSystem.getMessageIdManager.getMessagesReactive(util.List.of(messageId), FetchGroup.MINIMAL, session)) + .map(_.getFlags) + .block + + private def createMessage(flags: Flags): MessageId = { + val messageId: MessageId = messageIdManagerTestSystem.persist(mailbox.getMailboxId, MessageUid.of(111), flags, session) + messageId + } + + private def createFakeCalendaerEventBlobId(messageId: MessageId): BlobId = + BlobId.of(BlobId.of(messageId).get, PartId.parse(Random.nextInt(1_000_000).toString).get).get + + protected def createTestSystem: MessageIdManagerTestSystem = { + val messageIdFactory = new TestMessageId.Factory + val resources = InMemoryIntegrationResources.builder() + .preProvisionnedFakeAuthenticator() + .fakeAuthorizator().inVmEventBus + .defaultAnnotationLimits() + .defaultMessageParser() + .scanningSearchIndex() + .noPreDeletionHooks() + .storeQuotaManager() + .build() + new MessageIdManagerTestSystem(resources.getMessageIdManager, messageIdFactory, resources.getMailboxManager.getMapperFactory, resources.getMailboxManager) + } +}