InvitationResponseControllerTest.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
  5. * SPDX-License-Identifier: AGPL-3.0-or-later
  6. */
  7. namespace OCA\DAV\Tests\Unit\DAV\Controller;
  8. use OCA\DAV\CalDAV\InvitationResponse\InvitationResponseServer;
  9. use OCA\DAV\Controller\InvitationResponseController;
  10. use OCP\AppFramework\Http\TemplateResponse;
  11. use OCP\AppFramework\Utility\ITimeFactory;
  12. use OCP\DB\IResult;
  13. use OCP\DB\QueryBuilder\IExpressionBuilder;
  14. use OCP\DB\QueryBuilder\IQueryBuilder;
  15. use OCP\IDBConnection;
  16. use OCP\IRequest;
  17. use Sabre\VObject\ITip\Message;
  18. use Test\TestCase;
  19. class InvitationResponseControllerTest extends TestCase {
  20. /** @var InvitationResponseController */
  21. private $controller;
  22. /** @var IDBConnection|\PHPUnit\Framework\MockObject\MockObject */
  23. private $dbConnection;
  24. /** @var IRequest|\PHPUnit\Framework\MockObject\MockObject */
  25. private $request;
  26. /** @var ITimeFactory|\PHPUnit\Framework\MockObject\MockObject */
  27. private $timeFactory;
  28. /** @var InvitationResponseServer|\PHPUnit\Framework\MockObject\MockObject */
  29. private $responseServer;
  30. protected function setUp(): void {
  31. parent::setUp();
  32. $this->dbConnection = $this->createMock(IDBConnection::class);
  33. $this->request = $this->createMock(IRequest::class);
  34. $this->timeFactory = $this->createMock(ITimeFactory::class);
  35. $this->responseServer = $this->getMockBuilder(InvitationResponseServer::class)
  36. ->disableOriginalConstructor()
  37. ->getMock();
  38. $this->controller = new InvitationResponseController(
  39. 'appName',
  40. $this->request,
  41. $this->dbConnection,
  42. $this->timeFactory,
  43. $this->responseServer
  44. );
  45. }
  46. public function attendeeProvider(): array {
  47. return [
  48. 'local attendee' => [false],
  49. 'external attendee' => [true]
  50. ];
  51. }
  52. /**
  53. * @dataProvider attendeeProvider
  54. */
  55. public function testAccept(bool $isExternalAttendee): void {
  56. $this->buildQueryExpects('TOKEN123', [
  57. 'id' => 0,
  58. 'uid' => 'this-is-the-events-uid',
  59. 'recurrenceid' => null,
  60. 'attendee' => 'mailto:attendee@foo.bar',
  61. 'organizer' => 'mailto:organizer@foo.bar',
  62. 'sequence' => null,
  63. 'token' => 'TOKEN123',
  64. 'expiration' => 420000,
  65. ], 1337);
  66. $expected = <<<EOF
  67. BEGIN:VCALENDAR
  68. VERSION:2.0
  69. PRODID:-//Nextcloud/Nextcloud CalDAV Server//EN
  70. METHOD:REPLY
  71. BEGIN:VEVENT
  72. ATTENDEE;PARTSTAT=ACCEPTED:mailto:attendee@foo.bar
  73. ORGANIZER:mailto:organizer@foo.bar
  74. UID:this-is-the-events-uid
  75. SEQUENCE:0
  76. REQUEST-STATUS:2.0;Success
  77. DTSTAMP:19700101T002217Z
  78. END:VEVENT
  79. END:VCALENDAR
  80. EOF;
  81. $expected = preg_replace('~\R~u', "\r\n", $expected);
  82. $called = false;
  83. $this->responseServer->expects($this->once())
  84. ->method('handleITipMessage')
  85. ->willReturnCallback(function (Message $iTipMessage) use (&$called, $isExternalAttendee, $expected): void {
  86. $called = true;
  87. $this->assertEquals('this-is-the-events-uid', $iTipMessage->uid);
  88. $this->assertEquals('VEVENT', $iTipMessage->component);
  89. $this->assertEquals('REPLY', $iTipMessage->method);
  90. $this->assertEquals(null, $iTipMessage->sequence);
  91. $this->assertEquals('mailto:attendee@foo.bar', $iTipMessage->sender);
  92. if ($isExternalAttendee) {
  93. $this->assertEquals('mailto:organizer@foo.bar', $iTipMessage->recipient);
  94. } else {
  95. $this->assertEquals('mailto:attendee@foo.bar', $iTipMessage->recipient);
  96. }
  97. $iTipMessage->scheduleStatus = '1.2;Message delivered locally';
  98. $this->assertEquals($expected, $iTipMessage->message->serialize());
  99. });
  100. $this->responseServer->expects($this->once())
  101. ->method('isExternalAttendee')
  102. ->willReturn($isExternalAttendee);
  103. $response = $this->controller->accept('TOKEN123');
  104. $this->assertInstanceOf(TemplateResponse::class, $response);
  105. $this->assertEquals('schedule-response-success', $response->getTemplateName());
  106. $this->assertEquals([], $response->getParams());
  107. $this->assertTrue($called);
  108. }
  109. /**
  110. * @dataProvider attendeeProvider
  111. */
  112. public function testAcceptSequence(bool $isExternalAttendee): void {
  113. $this->buildQueryExpects('TOKEN123', [
  114. 'id' => 0,
  115. 'uid' => 'this-is-the-events-uid',
  116. 'recurrenceid' => null,
  117. 'attendee' => 'mailto:attendee@foo.bar',
  118. 'organizer' => 'mailto:organizer@foo.bar',
  119. 'sequence' => 1337,
  120. 'token' => 'TOKEN123',
  121. 'expiration' => 420000,
  122. ], 1337);
  123. $expected = <<<EOF
  124. BEGIN:VCALENDAR
  125. VERSION:2.0
  126. PRODID:-//Nextcloud/Nextcloud CalDAV Server//EN
  127. METHOD:REPLY
  128. BEGIN:VEVENT
  129. ATTENDEE;PARTSTAT=ACCEPTED:mailto:attendee@foo.bar
  130. ORGANIZER:mailto:organizer@foo.bar
  131. UID:this-is-the-events-uid
  132. SEQUENCE:1337
  133. REQUEST-STATUS:2.0;Success
  134. DTSTAMP:19700101T002217Z
  135. END:VEVENT
  136. END:VCALENDAR
  137. EOF;
  138. $expected = preg_replace('~\R~u', "\r\n", $expected);
  139. $called = false;
  140. $this->responseServer->expects($this->once())
  141. ->method('handleITipMessage')
  142. ->willReturnCallback(function (Message $iTipMessage) use (&$called, $isExternalAttendee, $expected): void {
  143. $called = true;
  144. $this->assertEquals('this-is-the-events-uid', $iTipMessage->uid);
  145. $this->assertEquals('VEVENT', $iTipMessage->component);
  146. $this->assertEquals('REPLY', $iTipMessage->method);
  147. $this->assertEquals(1337, $iTipMessage->sequence);
  148. $this->assertEquals('mailto:attendee@foo.bar', $iTipMessage->sender);
  149. if ($isExternalAttendee) {
  150. $this->assertEquals('mailto:organizer@foo.bar', $iTipMessage->recipient);
  151. } else {
  152. $this->assertEquals('mailto:attendee@foo.bar', $iTipMessage->recipient);
  153. }
  154. $iTipMessage->scheduleStatus = '1.2;Message delivered locally';
  155. $this->assertEquals($expected, $iTipMessage->message->serialize());
  156. });
  157. $this->responseServer->expects($this->once())
  158. ->method('isExternalAttendee')
  159. ->willReturn($isExternalAttendee);
  160. $response = $this->controller->accept('TOKEN123');
  161. $this->assertInstanceOf(TemplateResponse::class, $response);
  162. $this->assertEquals('schedule-response-success', $response->getTemplateName());
  163. $this->assertEquals([], $response->getParams());
  164. $this->assertTrue($called);
  165. }
  166. /**
  167. * @dataProvider attendeeProvider
  168. */
  169. public function testAcceptRecurrenceId(bool $isExternalAttendee): void {
  170. $this->buildQueryExpects('TOKEN123', [
  171. 'id' => 0,
  172. 'uid' => 'this-is-the-events-uid',
  173. 'recurrenceid' => "RECURRENCE-ID;TZID=Europe/Berlin:20180726T150000\n",
  174. 'attendee' => 'mailto:attendee@foo.bar',
  175. 'organizer' => 'mailto:organizer@foo.bar',
  176. 'sequence' => null,
  177. 'token' => 'TOKEN123',
  178. 'expiration' => 420000,
  179. ], 1337);
  180. $expected = <<<EOF
  181. BEGIN:VCALENDAR
  182. VERSION:2.0
  183. PRODID:-//Nextcloud/Nextcloud CalDAV Server//EN
  184. METHOD:REPLY
  185. BEGIN:VEVENT
  186. ATTENDEE;PARTSTAT=ACCEPTED:mailto:attendee@foo.bar
  187. ORGANIZER:mailto:organizer@foo.bar
  188. UID:this-is-the-events-uid
  189. SEQUENCE:0
  190. REQUEST-STATUS:2.0;Success
  191. RECURRENCE-ID;TZID=Europe/Berlin:20180726T150000
  192. DTSTAMP:19700101T002217Z
  193. END:VEVENT
  194. END:VCALENDAR
  195. EOF;
  196. $expected = preg_replace('~\R~u', "\r\n", $expected);
  197. $called = false;
  198. $this->responseServer->expects($this->once())
  199. ->method('handleITipMessage')
  200. ->willReturnCallback(function (Message $iTipMessage) use (&$called, $isExternalAttendee, $expected): void {
  201. $called = true;
  202. $this->assertEquals('this-is-the-events-uid', $iTipMessage->uid);
  203. $this->assertEquals('VEVENT', $iTipMessage->component);
  204. $this->assertEquals('REPLY', $iTipMessage->method);
  205. $this->assertEquals(0, $iTipMessage->sequence);
  206. $this->assertEquals('mailto:attendee@foo.bar', $iTipMessage->sender);
  207. if ($isExternalAttendee) {
  208. $this->assertEquals('mailto:organizer@foo.bar', $iTipMessage->recipient);
  209. } else {
  210. $this->assertEquals('mailto:attendee@foo.bar', $iTipMessage->recipient);
  211. }
  212. $iTipMessage->scheduleStatus = '1.2;Message delivered locally';
  213. $this->assertEquals($expected, $iTipMessage->message->serialize());
  214. });
  215. $this->responseServer->expects($this->once())
  216. ->method('isExternalAttendee')
  217. ->willReturn($isExternalAttendee);
  218. $response = $this->controller->accept('TOKEN123');
  219. $this->assertInstanceOf(TemplateResponse::class, $response);
  220. $this->assertEquals('schedule-response-success', $response->getTemplateName());
  221. $this->assertEquals([], $response->getParams());
  222. $this->assertTrue($called);
  223. }
  224. public function testAcceptTokenNotFound(): void {
  225. $this->buildQueryExpects('TOKEN123', null, 1337);
  226. $response = $this->controller->accept('TOKEN123');
  227. $this->assertInstanceOf(TemplateResponse::class, $response);
  228. $this->assertEquals('schedule-response-error', $response->getTemplateName());
  229. $this->assertEquals([], $response->getParams());
  230. }
  231. public function testAcceptExpiredToken(): void {
  232. $this->buildQueryExpects('TOKEN123', [
  233. 'id' => 0,
  234. 'uid' => 'this-is-the-events-uid',
  235. 'recurrenceid' => null,
  236. 'attendee' => 'mailto:attendee@foo.bar',
  237. 'organizer' => 'mailto:organizer@foo.bar',
  238. 'sequence' => null,
  239. 'token' => 'TOKEN123',
  240. 'expiration' => 42,
  241. ], 1337);
  242. $response = $this->controller->accept('TOKEN123');
  243. $this->assertInstanceOf(TemplateResponse::class, $response);
  244. $this->assertEquals('schedule-response-error', $response->getTemplateName());
  245. $this->assertEquals([], $response->getParams());
  246. }
  247. /**
  248. * @dataProvider attendeeProvider
  249. */
  250. public function testDecline(bool $isExternalAttendee): void {
  251. $this->buildQueryExpects('TOKEN123', [
  252. 'id' => 0,
  253. 'uid' => 'this-is-the-events-uid',
  254. 'recurrenceid' => null,
  255. 'attendee' => 'mailto:attendee@foo.bar',
  256. 'organizer' => 'mailto:organizer@foo.bar',
  257. 'sequence' => null,
  258. 'token' => 'TOKEN123',
  259. 'expiration' => 420000,
  260. ], 1337);
  261. $expected = <<<EOF
  262. BEGIN:VCALENDAR
  263. VERSION:2.0
  264. PRODID:-//Nextcloud/Nextcloud CalDAV Server//EN
  265. METHOD:REPLY
  266. BEGIN:VEVENT
  267. ATTENDEE;PARTSTAT=DECLINED:mailto:attendee@foo.bar
  268. ORGANIZER:mailto:organizer@foo.bar
  269. UID:this-is-the-events-uid
  270. SEQUENCE:0
  271. REQUEST-STATUS:2.0;Success
  272. DTSTAMP:19700101T002217Z
  273. END:VEVENT
  274. END:VCALENDAR
  275. EOF;
  276. $expected = preg_replace('~\R~u', "\r\n", $expected);
  277. $called = false;
  278. $this->responseServer->expects($this->once())
  279. ->method('handleITipMessage')
  280. ->willReturnCallback(function (Message $iTipMessage) use (&$called, $isExternalAttendee, $expected): void {
  281. $called = true;
  282. $this->assertEquals('this-is-the-events-uid', $iTipMessage->uid);
  283. $this->assertEquals('VEVENT', $iTipMessage->component);
  284. $this->assertEquals('REPLY', $iTipMessage->method);
  285. $this->assertEquals(null, $iTipMessage->sequence);
  286. $this->assertEquals('mailto:attendee@foo.bar', $iTipMessage->sender);
  287. if ($isExternalAttendee) {
  288. $this->assertEquals('mailto:organizer@foo.bar', $iTipMessage->recipient);
  289. } else {
  290. $this->assertEquals('mailto:attendee@foo.bar', $iTipMessage->recipient);
  291. }
  292. $iTipMessage->scheduleStatus = '1.2;Message delivered locally';
  293. $this->assertEquals($expected, $iTipMessage->message->serialize());
  294. });
  295. $this->responseServer->expects($this->once())
  296. ->method('isExternalAttendee')
  297. ->willReturn($isExternalAttendee);
  298. $response = $this->controller->decline('TOKEN123');
  299. $this->assertInstanceOf(TemplateResponse::class, $response);
  300. $this->assertEquals('schedule-response-success', $response->getTemplateName());
  301. $this->assertEquals([], $response->getParams());
  302. $this->assertTrue($called);
  303. }
  304. public function testOptions(): void {
  305. $response = $this->controller->options('TOKEN123');
  306. $this->assertInstanceOf(TemplateResponse::class, $response);
  307. $this->assertEquals('schedule-response-options', $response->getTemplateName());
  308. $this->assertEquals(['token' => 'TOKEN123'], $response->getParams());
  309. }
  310. /**
  311. * @dataProvider attendeeProvider
  312. */
  313. public function testProcessMoreOptionsResult(bool $isExternalAttendee): void {
  314. $this->request->expects($this->once())
  315. ->method('getParam')
  316. ->with('partStat')
  317. ->willReturn('TENTATIVE');
  318. $this->buildQueryExpects('TOKEN123', [
  319. 'id' => 0,
  320. 'uid' => 'this-is-the-events-uid',
  321. 'recurrenceid' => null,
  322. 'attendee' => 'mailto:attendee@foo.bar',
  323. 'organizer' => 'mailto:organizer@foo.bar',
  324. 'sequence' => null,
  325. 'token' => 'TOKEN123',
  326. 'expiration' => 420000,
  327. ], 1337);
  328. $expected = <<<EOF
  329. BEGIN:VCALENDAR
  330. VERSION:2.0
  331. PRODID:-//Nextcloud/Nextcloud CalDAV Server//EN
  332. METHOD:REPLY
  333. BEGIN:VEVENT
  334. ATTENDEE;PARTSTAT=TENTATIVE:mailto:attendee@foo.bar
  335. ORGANIZER:mailto:organizer@foo.bar
  336. UID:this-is-the-events-uid
  337. SEQUENCE:0
  338. REQUEST-STATUS:2.0;Success
  339. DTSTAMP:19700101T002217Z
  340. END:VEVENT
  341. END:VCALENDAR
  342. EOF;
  343. $expected = preg_replace('~\R~u', "\r\n", $expected);
  344. $called = false;
  345. $this->responseServer->expects($this->once())
  346. ->method('handleITipMessage')
  347. ->willReturnCallback(function (Message $iTipMessage) use (&$called, $isExternalAttendee, $expected): void {
  348. $called = true;
  349. $this->assertEquals('this-is-the-events-uid', $iTipMessage->uid);
  350. $this->assertEquals('VEVENT', $iTipMessage->component);
  351. $this->assertEquals('REPLY', $iTipMessage->method);
  352. $this->assertEquals(null, $iTipMessage->sequence);
  353. $this->assertEquals('mailto:attendee@foo.bar', $iTipMessage->sender);
  354. if ($isExternalAttendee) {
  355. $this->assertEquals('mailto:organizer@foo.bar', $iTipMessage->recipient);
  356. } else {
  357. $this->assertEquals('mailto:attendee@foo.bar', $iTipMessage->recipient);
  358. }
  359. $iTipMessage->scheduleStatus = '1.2;Message delivered locally';
  360. $this->assertEquals($expected, $iTipMessage->message->serialize());
  361. });
  362. $this->responseServer->expects($this->once())
  363. ->method('isExternalAttendee')
  364. ->willReturn($isExternalAttendee);
  365. $response = $this->controller->processMoreOptionsResult('TOKEN123');
  366. $this->assertInstanceOf(TemplateResponse::class, $response);
  367. $this->assertEquals('schedule-response-success', $response->getTemplateName());
  368. $this->assertEquals([], $response->getParams());
  369. $this->assertTrue($called);
  370. }
  371. private function buildQueryExpects($token, $return, $time): void {
  372. $queryBuilder = $this->createMock(IQueryBuilder::class);
  373. $stmt = $this->createMock(IResult::class);
  374. $expr = $this->createMock(IExpressionBuilder::class);
  375. $this->dbConnection->expects($this->once())
  376. ->method('getQueryBuilder')
  377. ->with()
  378. ->willReturn($queryBuilder);
  379. $queryBuilder->method('expr')
  380. ->willReturn($expr);
  381. $queryBuilder->method('createNamedParameter')
  382. ->willReturnMap([
  383. [$token, \PDO::PARAM_STR, null, 'namedParameterToken']
  384. ]);
  385. $stmt->expects($this->once())
  386. ->method('fetch')
  387. ->with(\PDO::FETCH_ASSOC)
  388. ->willReturn($return);
  389. $stmt->expects($this->once())
  390. ->method('closeCursor');
  391. $function = 'functionToken';
  392. $expr->expects($this->once())
  393. ->method('eq')
  394. ->with('token', 'namedParameterToken')
  395. ->willReturn((string)$function);
  396. $this->dbConnection->expects($this->once())
  397. ->method('getQueryBuilder')
  398. ->with()
  399. ->willReturn($queryBuilder);
  400. $queryBuilder->expects($this->once())
  401. ->method('select')
  402. ->with('*')
  403. ->willReturn($queryBuilder);
  404. $queryBuilder->expects($this->once())
  405. ->method('from')
  406. ->with('calendar_invitations')
  407. ->willReturn($queryBuilder);
  408. $queryBuilder->expects($this->once())
  409. ->method('where')
  410. ->with($function)
  411. ->willReturn($queryBuilder);
  412. $queryBuilder->expects($this->once())
  413. ->method('executeQuery')
  414. ->with()
  415. ->willReturn($stmt);
  416. $this->timeFactory->method('getTime')
  417. ->willReturn($time);
  418. }
  419. }