1
0

CommentsPluginTest.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755
  1. <?php
  2. /**
  3. * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
  4. * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
  5. * SPDX-License-Identifier: AGPL-3.0-only
  6. */
  7. namespace OCA\DAV\Tests\unit\Comments;
  8. use OC\Comments\Comment;
  9. use OCA\DAV\Comments\CommentsPlugin as CommentsPluginImplementation;
  10. use OCA\DAV\Comments\EntityCollection;
  11. use OCP\Comments\IComment;
  12. use OCP\Comments\ICommentsManager;
  13. use OCP\IUser;
  14. use OCP\IUserSession;
  15. use Sabre\DAV\INode;
  16. use Sabre\DAV\Tree;
  17. use Sabre\HTTP\RequestInterface;
  18. use Sabre\HTTP\ResponseInterface;
  19. class CommentsPluginTest extends \Test\TestCase {
  20. /** @var \Sabre\DAV\Server */
  21. private $server;
  22. /** @var Tree */
  23. private $tree;
  24. /** @var ICommentsManager */
  25. private $commentsManager;
  26. /** @var IUserSession */
  27. private $userSession;
  28. /** @var CommentsPluginImplementation */
  29. private $plugin;
  30. protected function setUp(): void {
  31. parent::setUp();
  32. $this->tree = $this->getMockBuilder(Tree::class)
  33. ->disableOriginalConstructor()
  34. ->getMock();
  35. $this->server = $this->getMockBuilder('\Sabre\DAV\Server')
  36. ->setConstructorArgs([$this->tree])
  37. ->setMethods(['getRequestUri'])
  38. ->getMock();
  39. $this->commentsManager = $this->getMockBuilder(ICommentsManager::class)
  40. ->disableOriginalConstructor()
  41. ->getMock();
  42. $this->userSession = $this->getMockBuilder(IUserSession::class)
  43. ->disableOriginalConstructor()
  44. ->getMock();
  45. $this->plugin = new CommentsPluginImplementation($this->commentsManager, $this->userSession);
  46. }
  47. public function testCreateComment(): void {
  48. $commentData = [
  49. 'actorType' => 'users',
  50. 'verb' => 'comment',
  51. 'message' => 'my first comment',
  52. ];
  53. $comment = new Comment([
  54. 'objectType' => 'files',
  55. 'objectId' => '42',
  56. 'actorType' => 'users',
  57. 'actorId' => 'alice'
  58. ] + $commentData);
  59. $comment->setId('23');
  60. $path = 'comments/files/42';
  61. $requestData = json_encode($commentData);
  62. $user = $this->getMockBuilder(IUser::class)
  63. ->disableOriginalConstructor()
  64. ->getMock();
  65. $user->expects($this->once())
  66. ->method('getUID')
  67. ->willReturn('alice');
  68. $node = $this->getMockBuilder(EntityCollection::class)
  69. ->disableOriginalConstructor()
  70. ->getMock();
  71. $node->expects($this->once())
  72. ->method('getName')
  73. ->willReturn('files');
  74. $node->expects($this->once())
  75. ->method('getId')
  76. ->willReturn('42');
  77. $node->expects($this->once())
  78. ->method('setReadMarker')
  79. ->with(null);
  80. $this->commentsManager->expects($this->once())
  81. ->method('create')
  82. ->with('users', 'alice', 'files', '42')
  83. ->willReturn($comment);
  84. $this->userSession->expects($this->once())
  85. ->method('getUser')
  86. ->willReturn($user);
  87. // technically, this is a shortcut. Inbetween EntityTypeCollection would
  88. // be returned, but doing it exactly right would not be really
  89. // unit-testing like, as it would require to haul in a lot of other
  90. // things.
  91. $this->tree->expects($this->any())
  92. ->method('getNodeForPath')
  93. ->with('/' . $path)
  94. ->willReturn($node);
  95. $request = $this->getMockBuilder(RequestInterface::class)
  96. ->disableOriginalConstructor()
  97. ->getMock();
  98. $response = $this->getMockBuilder(ResponseInterface::class)
  99. ->disableOriginalConstructor()
  100. ->getMock();
  101. $request->expects($this->once())
  102. ->method('getPath')
  103. ->willReturn('/' . $path);
  104. $request->expects($this->once())
  105. ->method('getBodyAsString')
  106. ->willReturn($requestData);
  107. $request->expects($this->once())
  108. ->method('getHeader')
  109. ->with('Content-Type')
  110. ->willReturn('application/json');
  111. $request->expects($this->once())
  112. ->method('getUrl')
  113. ->willReturn('http://example.com/dav/' . $path);
  114. $response->expects($this->once())
  115. ->method('setHeader')
  116. ->with('Content-Location', 'http://example.com/dav/' . $path . '/23');
  117. $this->server->expects($this->any())
  118. ->method('getRequestUri')
  119. ->willReturn($path);
  120. $this->plugin->initialize($this->server);
  121. $this->plugin->httpPost($request, $response);
  122. }
  123. public function testCreateCommentInvalidObject(): void {
  124. $this->expectException(\Sabre\DAV\Exception\NotFound::class);
  125. $commentData = [
  126. 'actorType' => 'users',
  127. 'verb' => 'comment',
  128. 'message' => 'my first comment',
  129. ];
  130. $comment = new Comment([
  131. 'objectType' => 'files',
  132. 'objectId' => '666',
  133. 'actorType' => 'users',
  134. 'actorId' => 'alice'
  135. ] + $commentData);
  136. $comment->setId('23');
  137. $path = 'comments/files/666';
  138. $user = $this->getMockBuilder(IUser::class)
  139. ->disableOriginalConstructor()
  140. ->getMock();
  141. $user->expects($this->never())
  142. ->method('getUID');
  143. $node = $this->getMockBuilder(EntityCollection::class)
  144. ->disableOriginalConstructor()
  145. ->getMock();
  146. $node->expects($this->never())
  147. ->method('getName');
  148. $node->expects($this->never())
  149. ->method('getId');
  150. $this->commentsManager->expects($this->never())
  151. ->method('create');
  152. $this->userSession->expects($this->never())
  153. ->method('getUser');
  154. // technically, this is a shortcut. Inbetween EntityTypeCollection would
  155. // be returned, but doing it exactly right would not be really
  156. // unit-testing like, as it would require to haul in a lot of other
  157. // things.
  158. $this->tree->expects($this->any())
  159. ->method('getNodeForPath')
  160. ->with('/' . $path)
  161. ->will($this->throwException(new \Sabre\DAV\Exception\NotFound()));
  162. $request = $this->getMockBuilder(RequestInterface::class)
  163. ->disableOriginalConstructor()
  164. ->getMock();
  165. $response = $this->getMockBuilder(ResponseInterface::class)
  166. ->disableOriginalConstructor()
  167. ->getMock();
  168. $request->expects($this->once())
  169. ->method('getPath')
  170. ->willReturn('/' . $path);
  171. $request->expects($this->never())
  172. ->method('getBodyAsString');
  173. $request->expects($this->never())
  174. ->method('getHeader')
  175. ->with('Content-Type');
  176. $request->expects($this->never())
  177. ->method('getUrl');
  178. $response->expects($this->never())
  179. ->method('setHeader');
  180. $this->server->expects($this->any())
  181. ->method('getRequestUri')
  182. ->willReturn($path);
  183. $this->plugin->initialize($this->server);
  184. $this->plugin->httpPost($request, $response);
  185. }
  186. public function testCreateCommentInvalidActor(): void {
  187. $this->expectException(\Sabre\DAV\Exception\BadRequest::class);
  188. $commentData = [
  189. 'actorType' => 'robots',
  190. 'verb' => 'comment',
  191. 'message' => 'my first comment',
  192. ];
  193. $comment = new Comment([
  194. 'objectType' => 'files',
  195. 'objectId' => '42',
  196. 'actorType' => 'users',
  197. 'actorId' => 'alice'
  198. ] + $commentData);
  199. $comment->setId('23');
  200. $path = 'comments/files/42';
  201. $requestData = json_encode($commentData);
  202. $user = $this->getMockBuilder(IUser::class)
  203. ->disableOriginalConstructor()
  204. ->getMock();
  205. $user->expects($this->never())
  206. ->method('getUID');
  207. $node = $this->getMockBuilder(EntityCollection::class)
  208. ->disableOriginalConstructor()
  209. ->getMock();
  210. $node->expects($this->once())
  211. ->method('getName')
  212. ->willReturn('files');
  213. $node->expects($this->once())
  214. ->method('getId')
  215. ->willReturn('42');
  216. $this->commentsManager->expects($this->never())
  217. ->method('create');
  218. $this->userSession->expects($this->never())
  219. ->method('getUser');
  220. // technically, this is a shortcut. Inbetween EntityTypeCollection would
  221. // be returned, but doing it exactly right would not be really
  222. // unit-testing like, as it would require to haul in a lot of other
  223. // things.
  224. $this->tree->expects($this->any())
  225. ->method('getNodeForPath')
  226. ->with('/' . $path)
  227. ->willReturn($node);
  228. $request = $this->getMockBuilder(RequestInterface::class)
  229. ->disableOriginalConstructor()
  230. ->getMock();
  231. $response = $this->getMockBuilder(ResponseInterface::class)
  232. ->disableOriginalConstructor()
  233. ->getMock();
  234. $request->expects($this->once())
  235. ->method('getPath')
  236. ->willReturn('/' . $path);
  237. $request->expects($this->once())
  238. ->method('getBodyAsString')
  239. ->willReturn($requestData);
  240. $request->expects($this->once())
  241. ->method('getHeader')
  242. ->with('Content-Type')
  243. ->willReturn('application/json');
  244. $request->expects($this->never())
  245. ->method('getUrl');
  246. $response->expects($this->never())
  247. ->method('setHeader');
  248. $this->server->expects($this->any())
  249. ->method('getRequestUri')
  250. ->willReturn($path);
  251. $this->plugin->initialize($this->server);
  252. $this->plugin->httpPost($request, $response);
  253. }
  254. public function testCreateCommentUnsupportedMediaType(): void {
  255. $this->expectException(\Sabre\DAV\Exception\UnsupportedMediaType::class);
  256. $commentData = [
  257. 'actorType' => 'users',
  258. 'verb' => 'comment',
  259. 'message' => 'my first comment',
  260. ];
  261. $comment = new Comment([
  262. 'objectType' => 'files',
  263. 'objectId' => '42',
  264. 'actorType' => 'users',
  265. 'actorId' => 'alice'
  266. ] + $commentData);
  267. $comment->setId('23');
  268. $path = 'comments/files/42';
  269. $requestData = json_encode($commentData);
  270. $user = $this->getMockBuilder(IUser::class)
  271. ->disableOriginalConstructor()
  272. ->getMock();
  273. $user->expects($this->never())
  274. ->method('getUID');
  275. $node = $this->getMockBuilder(EntityCollection::class)
  276. ->disableOriginalConstructor()
  277. ->getMock();
  278. $node->expects($this->once())
  279. ->method('getName')
  280. ->willReturn('files');
  281. $node->expects($this->once())
  282. ->method('getId')
  283. ->willReturn('42');
  284. $this->commentsManager->expects($this->never())
  285. ->method('create');
  286. $this->userSession->expects($this->never())
  287. ->method('getUser');
  288. // technically, this is a shortcut. Inbetween EntityTypeCollection would
  289. // be returned, but doing it exactly right would not be really
  290. // unit-testing like, as it would require to haul in a lot of other
  291. // things.
  292. $this->tree->expects($this->any())
  293. ->method('getNodeForPath')
  294. ->with('/' . $path)
  295. ->willReturn($node);
  296. $request = $this->getMockBuilder(RequestInterface::class)
  297. ->disableOriginalConstructor()
  298. ->getMock();
  299. $response = $this->getMockBuilder(ResponseInterface::class)
  300. ->disableOriginalConstructor()
  301. ->getMock();
  302. $request->expects($this->once())
  303. ->method('getPath')
  304. ->willReturn('/' . $path);
  305. $request->expects($this->once())
  306. ->method('getBodyAsString')
  307. ->willReturn($requestData);
  308. $request->expects($this->once())
  309. ->method('getHeader')
  310. ->with('Content-Type')
  311. ->willReturn('application/trumpscript');
  312. $request->expects($this->never())
  313. ->method('getUrl');
  314. $response->expects($this->never())
  315. ->method('setHeader');
  316. $this->server->expects($this->any())
  317. ->method('getRequestUri')
  318. ->willReturn($path);
  319. $this->plugin->initialize($this->server);
  320. $this->plugin->httpPost($request, $response);
  321. }
  322. public function testCreateCommentInvalidPayload(): void {
  323. $this->expectException(\Sabre\DAV\Exception\BadRequest::class);
  324. $commentData = [
  325. 'actorType' => 'users',
  326. 'verb' => '',
  327. 'message' => '',
  328. ];
  329. $comment = new Comment([
  330. 'objectType' => 'files',
  331. 'objectId' => '42',
  332. 'actorType' => 'users',
  333. 'actorId' => 'alice',
  334. 'message' => 'dummy',
  335. 'verb' => 'dummy'
  336. ]);
  337. $comment->setId('23');
  338. $path = 'comments/files/42';
  339. $requestData = json_encode($commentData);
  340. $user = $this->getMockBuilder(IUser::class)
  341. ->disableOriginalConstructor()
  342. ->getMock();
  343. $user->expects($this->once())
  344. ->method('getUID')
  345. ->willReturn('alice');
  346. $node = $this->getMockBuilder(EntityCollection::class)
  347. ->disableOriginalConstructor()
  348. ->getMock();
  349. $node->expects($this->once())
  350. ->method('getName')
  351. ->willReturn('files');
  352. $node->expects($this->once())
  353. ->method('getId')
  354. ->willReturn('42');
  355. $this->commentsManager->expects($this->once())
  356. ->method('create')
  357. ->with('users', 'alice', 'files', '42')
  358. ->willReturn($comment);
  359. $this->userSession->expects($this->once())
  360. ->method('getUser')
  361. ->willReturn($user);
  362. // technically, this is a shortcut. Inbetween EntityTypeCollection would
  363. // be returned, but doing it exactly right would not be really
  364. // unit-testing like, as it would require to haul in a lot of other
  365. // things.
  366. $this->tree->expects($this->any())
  367. ->method('getNodeForPath')
  368. ->with('/' . $path)
  369. ->willReturn($node);
  370. $request = $this->getMockBuilder(RequestInterface::class)
  371. ->disableOriginalConstructor()
  372. ->getMock();
  373. $response = $this->getMockBuilder(ResponseInterface::class)
  374. ->disableOriginalConstructor()
  375. ->getMock();
  376. $request->expects($this->once())
  377. ->method('getPath')
  378. ->willReturn('/' . $path);
  379. $request->expects($this->once())
  380. ->method('getBodyAsString')
  381. ->willReturn($requestData);
  382. $request->expects($this->once())
  383. ->method('getHeader')
  384. ->with('Content-Type')
  385. ->willReturn('application/json');
  386. $request->expects($this->never())
  387. ->method('getUrl');
  388. $response->expects($this->never())
  389. ->method('setHeader');
  390. $this->server->expects($this->any())
  391. ->method('getRequestUri')
  392. ->willReturn($path);
  393. $this->plugin->initialize($this->server);
  394. $this->plugin->httpPost($request, $response);
  395. }
  396. public function testCreateCommentMessageTooLong(): void {
  397. $this->expectException(\Sabre\DAV\Exception\BadRequest::class);
  398. $this->expectExceptionMessage('Message exceeds allowed character limit of');
  399. $commentData = [
  400. 'actorType' => 'users',
  401. 'verb' => 'comment',
  402. 'message' => str_pad('', IComment::MAX_MESSAGE_LENGTH + 1, 'x'),
  403. ];
  404. $comment = new Comment([
  405. 'objectType' => 'files',
  406. 'objectId' => '42',
  407. 'actorType' => 'users',
  408. 'actorId' => 'alice',
  409. 'verb' => 'comment',
  410. ]);
  411. $comment->setId('23');
  412. $path = 'comments/files/42';
  413. $requestData = json_encode($commentData);
  414. $user = $this->getMockBuilder(IUser::class)
  415. ->disableOriginalConstructor()
  416. ->getMock();
  417. $user->expects($this->once())
  418. ->method('getUID')
  419. ->willReturn('alice');
  420. $node = $this->getMockBuilder(EntityCollection::class)
  421. ->disableOriginalConstructor()
  422. ->getMock();
  423. $node->expects($this->once())
  424. ->method('getName')
  425. ->willReturn('files');
  426. $node->expects($this->once())
  427. ->method('getId')
  428. ->willReturn('42');
  429. $node->expects($this->never())
  430. ->method('setReadMarker');
  431. $this->commentsManager->expects($this->once())
  432. ->method('create')
  433. ->with('users', 'alice', 'files', '42')
  434. ->willReturn($comment);
  435. $this->userSession->expects($this->once())
  436. ->method('getUser')
  437. ->willReturn($user);
  438. // technically, this is a shortcut. Inbetween EntityTypeCollection would
  439. // be returned, but doing it exactly right would not be really
  440. // unit-testing like, as it would require to haul in a lot of other
  441. // things.
  442. $this->tree->expects($this->any())
  443. ->method('getNodeForPath')
  444. ->with('/' . $path)
  445. ->willReturn($node);
  446. $request = $this->getMockBuilder(RequestInterface::class)
  447. ->disableOriginalConstructor()
  448. ->getMock();
  449. $response = $this->getMockBuilder(ResponseInterface::class)
  450. ->disableOriginalConstructor()
  451. ->getMock();
  452. $request->expects($this->once())
  453. ->method('getPath')
  454. ->willReturn('/' . $path);
  455. $request->expects($this->once())
  456. ->method('getBodyAsString')
  457. ->willReturn($requestData);
  458. $request->expects($this->once())
  459. ->method('getHeader')
  460. ->with('Content-Type')
  461. ->willReturn('application/json');
  462. $response->expects($this->never())
  463. ->method('setHeader');
  464. $this->server->expects($this->any())
  465. ->method('getRequestUri')
  466. ->willReturn($path);
  467. $this->plugin->initialize($this->server);
  468. $this->plugin->httpPost($request, $response);
  469. }
  470. public function testOnReportInvalidNode(): void {
  471. $this->expectException(\Sabre\DAV\Exception\ReportNotSupported::class);
  472. $path = 'totally/unrelated/13';
  473. $this->tree->expects($this->any())
  474. ->method('getNodeForPath')
  475. ->with('/' . $path)
  476. ->willReturn(
  477. $this->getMockBuilder(INode::class)
  478. ->disableOriginalConstructor()
  479. ->getMock()
  480. );
  481. $this->server->expects($this->any())
  482. ->method('getRequestUri')
  483. ->willReturn($path);
  484. $this->plugin->initialize($this->server);
  485. $this->plugin->onReport(CommentsPluginImplementation::REPORT_NAME, [], '/' . $path);
  486. }
  487. public function testOnReportInvalidReportName(): void {
  488. $this->expectException(\Sabre\DAV\Exception\ReportNotSupported::class);
  489. $path = 'comments/files/42';
  490. $this->tree->expects($this->any())
  491. ->method('getNodeForPath')
  492. ->with('/' . $path)
  493. ->willReturn(
  494. $this->getMockBuilder(INode::class)
  495. ->disableOriginalConstructor()
  496. ->getMock()
  497. );
  498. $this->server->expects($this->any())
  499. ->method('getRequestUri')
  500. ->willReturn($path);
  501. $this->plugin->initialize($this->server);
  502. $this->plugin->onReport('{whoever}whatever', [], '/' . $path);
  503. }
  504. public function testOnReportDateTimeEmpty(): void {
  505. $path = 'comments/files/42';
  506. $parameters = [
  507. [
  508. 'name' => '{http://owncloud.org/ns}limit',
  509. 'value' => 5,
  510. ],
  511. [
  512. 'name' => '{http://owncloud.org/ns}offset',
  513. 'value' => 10,
  514. ],
  515. [
  516. 'name' => '{http://owncloud.org/ns}datetime',
  517. 'value' => '',
  518. ]
  519. ];
  520. $node = $this->getMockBuilder(EntityCollection::class)
  521. ->disableOriginalConstructor()
  522. ->getMock();
  523. $node->expects($this->once())
  524. ->method('findChildren')
  525. ->with(5, 10, null)
  526. ->willReturn([]);
  527. $response = $this->getMockBuilder(ResponseInterface::class)
  528. ->disableOriginalConstructor()
  529. ->getMock();
  530. $response->expects($this->once())
  531. ->method('setHeader')
  532. ->with('Content-Type', 'application/xml; charset=utf-8');
  533. $response->expects($this->once())
  534. ->method('setStatus')
  535. ->with(207);
  536. $response->expects($this->once())
  537. ->method('setBody');
  538. $this->tree->expects($this->any())
  539. ->method('getNodeForPath')
  540. ->with('/' . $path)
  541. ->willReturn($node);
  542. $this->server->expects($this->any())
  543. ->method('getRequestUri')
  544. ->willReturn($path);
  545. $this->server->httpResponse = $response;
  546. $this->plugin->initialize($this->server);
  547. $this->plugin->onReport(CommentsPluginImplementation::REPORT_NAME, $parameters, '/' . $path);
  548. }
  549. public function testOnReport(): void {
  550. $path = 'comments/files/42';
  551. $parameters = [
  552. [
  553. 'name' => '{http://owncloud.org/ns}limit',
  554. 'value' => 5,
  555. ],
  556. [
  557. 'name' => '{http://owncloud.org/ns}offset',
  558. 'value' => 10,
  559. ],
  560. [
  561. 'name' => '{http://owncloud.org/ns}datetime',
  562. 'value' => '2016-01-10 18:48:00',
  563. ]
  564. ];
  565. $node = $this->getMockBuilder(EntityCollection::class)
  566. ->disableOriginalConstructor()
  567. ->getMock();
  568. $node->expects($this->once())
  569. ->method('findChildren')
  570. ->with(5, 10, new \DateTime($parameters[2]['value']))
  571. ->willReturn([]);
  572. $response = $this->getMockBuilder(ResponseInterface::class)
  573. ->disableOriginalConstructor()
  574. ->getMock();
  575. $response->expects($this->once())
  576. ->method('setHeader')
  577. ->with('Content-Type', 'application/xml; charset=utf-8');
  578. $response->expects($this->once())
  579. ->method('setStatus')
  580. ->with(207);
  581. $response->expects($this->once())
  582. ->method('setBody');
  583. $this->tree->expects($this->any())
  584. ->method('getNodeForPath')
  585. ->with('/' . $path)
  586. ->willReturn($node);
  587. $this->server->expects($this->any())
  588. ->method('getRequestUri')
  589. ->willReturn($path);
  590. $this->server->httpResponse = $response;
  591. $this->plugin->initialize($this->server);
  592. $this->plugin->onReport(CommentsPluginImplementation::REPORT_NAME, $parameters, '/' . $path);
  593. }
  594. }