the browser-facing portion of osu!
at master 26 kB view raw
1<?php 2 3// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0. 4// See the LICENCE file in the repository root for full licence text. 5 6namespace Tests\Libraries\BeatmapsetDiscussion; 7 8use App\Events\NewPrivateNotificationEvent; 9use App\Exceptions\InvariantException; 10use App\Jobs\Notifications\BeatmapsetDiscussionQualifiedProblem; 11use App\Jobs\Notifications\BeatmapsetDisqualify; 12use App\Jobs\Notifications\BeatmapsetResetNominations; 13use App\Libraries\BeatmapsetDiscussion\Review; 14use App\Models\Beatmap; 15use App\Models\BeatmapDiscussion; 16use App\Models\BeatmapDiscussionPost; 17use App\Models\Beatmapset; 18use App\Models\Notification; 19use App\Models\User; 20use Faker; 21use Illuminate\Support\Facades\Event; 22use Queue; 23use Tests\TestCase; 24 25class ReviewTest extends TestCase 26{ 27 protected static $faker; 28 protected $beatmap; 29 protected $beatmapset; 30 protected $user; 31 32 public static function setUpBeforeClass(): void 33 { 34 self::$faker = Faker\Factory::create(); 35 } 36 37 //region Review::create() 38 39 //region Failure Scenarios 40 41 // empty document 42 public function testCreateDocumentEmpty() 43 { 44 $this->expectException(InvariantException::class); 45 Review::create($this->beatmapset, [], $this->user); 46 } 47 48 // missing block type 49 public function testCreateDocumentMissingBlockType() 50 { 51 $this->expectException(InvariantException::class); 52 Review::create( 53 $this->beatmapset, 54 [ 55 [ 56 'text' => 'invalid lol', 57 ], 58 ], 59 $this->user 60 ); 61 } 62 63 // invalid block type 64 public function testCreateDocumentInvalidBlockType() 65 { 66 $this->expectException(InvariantException::class); 67 Review::create( 68 $this->beatmapset, 69 [ 70 [ 71 'type' => 'invalid lol', 72 ], 73 ], 74 $this->user 75 ); 76 } 77 78 // invalid paragraph block 79 public function testCreateDocumentInvalidParagraphBlockContent() 80 { 81 $this->expectException(InvariantException::class); 82 Review::create( 83 $this->beatmapset, 84 [ 85 [ 86 'type' => 'paragraph', 87 ], 88 ], 89 $this->user 90 ); 91 } 92 93 // invalid embed block 94 public function testCreateDocumentInvalidEmbedBlockContent() 95 { 96 $this->expectException(InvariantException::class); 97 Review::create( 98 $this->beatmapset, 99 [ 100 [ 101 'type' => 'embed', 102 ], 103 ], 104 $this->user 105 ); 106 } 107 108 // valid document containing zero issue embeds 109 public function testCreateDocumentValidParagraphWithNoIssues() 110 { 111 $this->expectException(InvariantException::class); 112 Review::create( 113 $this->beatmapset, 114 [ 115 [ 116 'type' => 'paragraph', 117 'text' => 'this is a text', 118 ], 119 ], 120 $this->user 121 ); 122 } 123 124 // valid paragraph but text is JSON 125 public function testCreateDocumentValidParagraphButJSON() 126 { 127 $this->expectException(InvariantException::class); 128 Review::create( 129 $this->beatmapset, 130 [ 131 [ 132 'type' => 'paragraph', 133 'text' => ['y', 'tho'], 134 ], 135 ], 136 $this->user 137 ); 138 } 139 140 // valid review but text is JSON 141 public function testCreateDocumentValidIssueButJSON() 142 { 143 $this->expectException(InvariantException::class); 144 Review::create( 145 $this->beatmapset, 146 [ 147 [ 148 'type' => 'embed', 149 'discussion_type' => 'problem', 150 'text' => ['y', 'tho'], 151 'timestamp' => true, 152 'beatmap_id' => $this->beatmap->getKey(), 153 ], 154 [ 155 'type' => 'embed', 156 'discussion_type' => 'problem', 157 'text' => self::$faker->sentence(), 158 ], 159 ], 160 $this->user 161 ); 162 } 163 164 // document with too many blocks 165 public function testCreateDocumentValidWithTooManyBlocks() 166 { 167 $this->expectException(InvariantException::class); 168 Review::create( 169 $this->beatmapset, 170 [ 171 [ 172 'type' => 'embed', 173 'discussion_type' => 'problem', 174 'text' => self::$faker->sentence(), 175 ], 176 [ 177 'type' => 'paragraph', 178 'text' => self::$faker->sentence(), 179 ], 180 [ 181 'type' => 'paragraph', 182 'text' => self::$faker->sentence(), 183 ], 184 [ 185 'type' => 'paragraph', 186 'text' => self::$faker->sentence(), 187 ], 188 [ 189 'type' => 'paragraph', 190 'text' => self::$faker->sentence(), 191 ], 192 ], 193 $this->user 194 ); 195 } 196 197 //endregion 198 199 //region Success Scenarios 200 201 // valid document containing issue embeds 202 public function testCreateDocumentDocumentValidWithIssues() 203 { 204 $discussionCount = BeatmapDiscussion::count(); 205 $discussionPostCount = BeatmapDiscussionPost::count(); 206 $timestampedIssueText = '00:01:234 '.self::$faker->sentence(); 207 $issueText = self::$faker->sentence(); 208 209 Review::create( 210 $this->beatmapset, 211 [ 212 [ 213 'type' => 'embed', 214 'discussion_type' => 'problem', 215 'text' => $timestampedIssueText, 216 'timestamp' => true, 217 'beatmap_id' => $this->beatmap->getKey(), 218 ], 219 [ 220 'type' => 'embed', 221 'discussion_type' => 'problem', 222 'text' => $issueText, 223 ], 224 [ 225 'type' => 'paragraph', 226 'text' => 'this is some paragraph text', 227 ], 228 ], 229 $this->user 230 ); 231 232 $discussionJson = json_encode($this->beatmapset->defaultDiscussionJson()); 233 $this->assertStringContainsString("\"message\":\"{$timestampedIssueText}\"", $discussionJson); 234 $this->assertStringContainsString('"timestamp":1234', $discussionJson); 235 $this->assertStringContainsString("\"message\":\"{$issueText}\"", $discussionJson); 236 237 // ensure 3 discussions/posts are created - one for the review and one for each embedded problem 238 $this->assertSame($discussionCount + 3, BeatmapDiscussion::count()); 239 $this->assertSame($discussionPostCount + 3, BeatmapDiscussionPost::count()); 240 } 241 242 // valid document containing issue embeds should trigger disqualification (for GMT) 243 public function testCreateDocumentDocumentValidWithIssuesShouldDisqualify() 244 { 245 $gmtUser = User::factory()->withGroup('gmt')->create(); 246 $beatmapset = Beatmapset::factory()->qualified()->create(); 247 $beatmapset->beatmaps()->save(Beatmap::factory()->make()); 248 $watchingUser = User::factory()->create(); 249 $beatmapset->watches()->create(['user_id' => $watchingUser->getKey()]); 250 251 Review::create( 252 $beatmapset, 253 [ 254 [ 255 'type' => 'embed', 256 'discussion_type' => 'problem', 257 'text' => self::$faker->sentence(), 258 ], 259 [ 260 'type' => 'paragraph', 261 'text' => 'this is some paragraph text', 262 ], 263 ], 264 $gmtUser 265 ); 266 267 $beatmapset->refresh(); 268 269 // ensure qualified beatmap has been reset to pending 270 $this->assertSame($beatmapset->approved, Beatmapset::STATES['pending']); 271 272 // ensure a disqualification notification is dispatched 273 Queue::assertPushed(BeatmapsetDisqualify::class); 274 $this->runFakeQueue(); 275 Event::assertDispatched(NewPrivateNotificationEvent::class); 276 } 277 278 // valid document containing issue embeds should reset nominations (for GMT) 279 public function testCreateDocumentDocumentValidWithIssuesShouldResetNominations() 280 { 281 $beatmapset = Beatmapset::factory()->create([ 282 'approved' => Beatmapset::STATES['pending'], 283 ]); 284 $beatmapset->beatmaps()->save(Beatmap::factory()->make()); 285 286 $playmode = $beatmapset->playmodesStr()[0]; 287 $natUser = User::factory()->withGroup('nat', [$playmode])->create(); 288 $watchingUser = User::factory()->create(); 289 $beatmapset->watches()->create(['user_id' => $watchingUser->getKey()]); 290 291 // ensure beatmapset has a nomination 292 $beatmapset->nominate($natUser, [$playmode]); 293 $this->assertSame($beatmapset->currentNominationCount()[$playmode], 1); 294 295 Review::create( 296 $beatmapset, 297 [ 298 [ 299 'type' => 'embed', 300 'discussion_type' => 'problem', 301 'text' => self::$faker->sentence(), 302 ], 303 [ 304 'type' => 'paragraph', 305 'text' => 'this is some paragraph text', 306 ], 307 ], 308 $natUser 309 ); 310 311 $beatmapset->refresh(); 312 313 // ensure beatmap is still pending 314 $this->assertSame($beatmapset->approved, Beatmapset::STATES['pending']); 315 // ensure nomination count has been reset 316 $this->assertSame($beatmapset->currentNominationCount()[$playmode], 0); 317 318 // ensure a nomination reset notification is dispatched 319 Queue::assertPushed(BeatmapsetResetNominations::class); 320 $this->runFakeQueue(); 321 Event::assertDispatched(NewPrivateNotificationEvent::class); 322 } 323 324 // valid document containing issue embeds should reset nominations (for GMT) 325 /** 326 * @dataProvider dataProviderForQualifiedProblem 327 */ 328 public function testCreateDocumentDocumentValidWithNewIssuesShouldNotify($state, $shouldNotify) 329 { 330 $gmtUser = User::factory()->withGroup('gmt')->create(); 331 $beatmapset = Beatmapset::factory()->$state()->create(); 332 $beatmapset->beatmaps()->save(Beatmap::factory()->make(['playmode' => 0])); 333 334 $notificationOption = $gmtUser->notificationOptions()->firstOrCreate([ 335 'name' => Notification::BEATMAPSET_DISCUSSION_QUALIFIED_PROBLEM, 336 ]); 337 $notificationOption->update(['details' => ['modes' => ['osu']]]); 338 339 Review::create( 340 $beatmapset, 341 [ 342 [ 343 'type' => 'embed', 344 'discussion_type' => 'problem', 345 'text' => self::$faker->sentence(), 346 ], 347 [ 348 'type' => 'paragraph', 349 'text' => 'this is some paragraph text', 350 ], 351 ], 352 $this->user 353 ); 354 355 $beatmapset->refresh(); 356 357 // ensure beatmap status hasn't changed. 358 $this->assertSame($beatmapset->status(), $state); 359 360 if ($shouldNotify) { 361 // ensure a new problem notification is dispatched 362 Queue::assertPushed(BeatmapsetDiscussionQualifiedProblem::class); 363 $this->runFakeQueue(); 364 Event::assertDispatched(NewPrivateNotificationEvent::class); 365 } else { 366 Queue::assertNotPushed(BeatmapsetDiscussionQualifiedProblem::class); 367 $this->runFakeQueue(); 368 Event::assertNotDispatched(NewPrivateNotificationEvent::class); 369 } 370 } 371 372 //endregion 373 374 //endregion 375 376 //region Review::update() 377 378 //region Failure Scenarios 379 380 // empty document 381 public function testUpdateDocumentEmpty() 382 { 383 $this->expectException(InvariantException::class); 384 $this->updateReview([]); 385 } 386 387 // missing block type 388 public function testUpdateDocumentMissingBlockType() 389 { 390 $this->expectException(InvariantException::class); 391 $this->updateReview([ 392 [ 393 'text' => 'invalid lol', 394 ], 395 ]); 396 } 397 398 // invalid block type 399 public function testUpdateDocumentInvalidBlockType() 400 { 401 $this->expectException(InvariantException::class); 402 $this->updateReview([ 403 [ 404 'type' => 'invalid lol', 405 ], 406 ]); 407 } 408 409 // invalid paragraph block 410 public function testUpdateDocumentInvalidParagraphBlockContent() 411 { 412 $this->expectException(InvariantException::class); 413 $this->updateReview([ 414 [ 415 'type' => 'paragraph', 416 ], 417 ]); 418 } 419 420 // invalid embed block 421 public function testUpdateDocumentInvalidEmbedBlockContent() 422 { 423 $this->expectException(InvariantException::class); 424 $this->updateReview([ 425 [ 426 'type' => 'embed', 427 ], 428 ]); 429 } 430 431 // valid document containing zero issue embeds 432 public function testUpdateDocumentValidParagraphWithNoIssues() 433 { 434 $this->expectException(InvariantException::class); 435 $this->updateReview([ 436 [ 437 'type' => 'paragraph', 438 'text' => 'this is a text', 439 ], 440 ]); 441 } 442 443 // valid paragraph but text is JSON 444 public function testUpdateDocumentValidParagraphButJSON() 445 { 446 $this->expectException(InvariantException::class); 447 $this->updateReview([ 448 [ 449 'type' => 'paragraph', 450 'text' => ['y', 'tho'], 451 ], 452 ]); 453 } 454 455 // valid review but text is JSON 456 public function testUpdateDocumentValidIssueButJSON() 457 { 458 $this->expectException(InvariantException::class); 459 $this->updateReview([ 460 [ 461 'type' => 'embed', 462 'discussion_type' => 'problem', 463 'text' => ['y', 'tho'], 464 'timestamp' => true, 465 'beatmap_id' => $this->beatmap->getKey(), 466 ], 467 [ 468 'type' => 'embed', 469 'discussion_type' => 'problem', 470 'text' => self::$faker->sentence(), 471 ], 472 ]); 473 } 474 475 // document with too many blocks 476 public function testUpdateDocumentValidWithTooManyBlocks() 477 { 478 $this->expectException(InvariantException::class); 479 $this->updateReview([ 480 [ 481 'type' => 'embed', 482 'discussion_type' => 'problem', 483 'text' => self::$faker->sentence(), 484 ], 485 [ 486 'type' => 'paragraph', 487 'text' => self::$faker->sentence(), 488 ], 489 [ 490 'type' => 'paragraph', 491 'text' => self::$faker->sentence(), 492 ], 493 [ 494 'type' => 'paragraph', 495 'text' => self::$faker->sentence(), 496 ], 497 [ 498 'type' => 'paragraph', 499 'text' => self::$faker->sentence(), 500 ], 501 ]); 502 } 503 504 // document referencing issues belonging to another review 505 public function testUpdateDocumentValidWithExternalReference() 506 { 507 $review = $this->setUpReview(); 508 509 $differentReview = $this->setUpReview(); 510 $document = json_decode($differentReview->startingPost->message, true); 511 512 $this->expectException(InvariantException::class); 513 Review::update($review, $document, $this->user); 514 } 515 516 //endregion 517 518 //region Success Scenarios 519 520 // valid document containing issue embeds 521 public function testUpdateDocumentValidWithIssues() 522 { 523 $review = $this->setUpReview(); 524 $linkedIssue = BeatmapDiscussion::where('parent_id', $review->id)->first(); 525 526 $discussionCount = BeatmapDiscussion::count(); 527 $discussionPostCount = BeatmapDiscussionPost::count(); 528 529 $document = json_decode($review->startingPost->message, true); 530 531 Review::update($review, $document, $this->user); 532 533 // ensure number of discussions/issues hasn't changed 534 $this->assertSame($discussionCount, BeatmapDiscussion::count()); 535 $this->assertSame($discussionPostCount, BeatmapDiscussionPost::count()); 536 537 // ensure issue is still linked correctly 538 $this->assertSame($review->id, $linkedIssue->refresh()->parent_id); 539 } 540 541 // adding a new embed to an existing issue 542 public function testUpdateDocumentWithNewIssue() 543 { 544 $review = $this->setUpReview(); 545 546 $discussionCount = BeatmapDiscussion::count(); 547 $discussionPostCount = BeatmapDiscussionPost::count(); 548 $linkedIssueCount = BeatmapDiscussion::where('parent_id', $review->id)->count(); 549 550 $document = json_decode($review->startingPost->message, true); 551 $document[] = [ 552 'type' => 'embed', 553 'discussion_type' => 'problem', 554 'text' => 'whee', 555 ]; 556 557 Review::update($review, $document, $this->user); 558 559 // ensure new issue was created 560 $this->assertSame($discussionCount + 1, BeatmapDiscussion::count()); 561 $this->assertSame($discussionPostCount + 1, BeatmapDiscussionPost::count()); 562 563 // ensure new issue is linked correctly 564 $this->assertSame($linkedIssueCount + 1, BeatmapDiscussion::where('parent_id', $review->id)->count()); 565 } 566 567 public function testUpdateDocumentWithNewIssueShouldDisqualify() 568 { 569 $gmtUser = User::factory()->withGroup('gmt')->create(); 570 $beatmapset = Beatmapset::factory()->qualified()->create(); 571 $beatmapset->beatmaps()->save(Beatmap::factory()->make()); 572 $review = $this->setUpPraiseOnlyReview($beatmapset, $gmtUser); 573 574 // ensure qualified beatmap is qualified 575 $this->assertSame($beatmapset->approved, Beatmapset::STATES['qualified']); 576 577 // ensure we have a user watching, otherwise no notifications will be sent 578 $watchingUser = User::factory()->create(); 579 $beatmapset->watches()->create(['user_id' => $watchingUser->getKey()]); 580 581 $document = json_decode($review->startingPost->message, true); 582 $document[] = [ 583 'type' => 'embed', 584 'discussion_type' => 'problem', 585 'text' => 'whee', 586 ]; 587 588 Review::update($review, $document, $gmtUser); 589 590 $beatmapset->refresh(); 591 592 // ensure qualified beatmap has been reset to pending 593 $this->assertSame($beatmapset->approved, Beatmapset::STATES['pending']); 594 595 // ensure a disqualification notification is dispatched 596 Queue::assertPushed(BeatmapsetDisqualify::class); 597 $this->runFakeQueue(); 598 Event::assertDispatched(NewPrivateNotificationEvent::class); 599 } 600 601 public function testUpdateDocumentWithNewIssueShouldResetNominations() 602 { 603 $beatmapset = Beatmapset::factory()->create([ 604 'approved' => Beatmapset::STATES['pending'], 605 ]); 606 $beatmapset->beatmaps()->save(Beatmap::factory()->make()); 607 608 $playmode = $beatmapset->playmodesStr()[0]; 609 $natUser = User::factory()->withGroup('nat', [$playmode])->create(); 610 $review = $this->setUpPraiseOnlyReview($beatmapset, $natUser); 611 612 // ensure qualified beatmap is pending 613 $this->assertSame($beatmapset->approved, Beatmapset::STATES['pending']); 614 615 // ensure beatmapset has a nominationBeatmapsetCompactTransformer.php 616 $beatmapset->nominate($natUser, [$playmode]); 617 $this->assertSame($beatmapset->currentNominationCount()[$playmode], 1); 618 619 // ensure we have a user watching, otherwise no notifications will be sent 620 $watchingUser = User::factory()->create(); 621 $beatmapset->watches()->create(['user_id' => $watchingUser->getKey()]); 622 623 $document = json_decode($review->startingPost->message, true); 624 $document[] = [ 625 'type' => 'embed', 626 'discussion_type' => 'problem', 627 'text' => 'whee', 628 ]; 629 630 Review::update($review, $document, $natUser); 631 632 $beatmapset->refresh(); 633 634 // ensure beatmap is still pending 635 $this->assertSame($beatmapset->approved, Beatmapset::STATES['pending']); 636 // ensure nomination count has been reset 637 $this->assertSame($beatmapset->currentNominationCount()[$playmode], 0); 638 639 // ensure a nomination reset notification is dispatched 640 Queue::assertPushed(BeatmapsetResetNominations::class); 641 $this->runFakeQueue(); 642 Event::assertDispatched(NewPrivateNotificationEvent::class); 643 } 644 645 /** 646 * @dataProvider dataProviderForQualifiedProblem 647 */ 648 public function testUpdateDocumentWithNewIssueShouldNotifyIfQualified($state, $shouldNotify) 649 { 650 $gmtUser = User::factory()->withGroup('gmt')->create(); 651 $beatmapset = Beatmapset::factory()->$state()->create(); 652 $beatmapset->beatmaps()->save(Beatmap::factory()->make(['playmode' => 0])); 653 654 $notificationOption = $gmtUser->notificationOptions()->firstOrCreate([ 655 'name' => Notification::BEATMAPSET_DISCUSSION_QUALIFIED_PROBLEM, 656 ]); 657 $notificationOption->update(['details' => ['modes' => ['osu']]]); 658 659 $review = $this->setUpPraiseOnlyReview($beatmapset, $gmtUser); 660 661 // ensure qualified beatmap is qualified 662 $this->assertSame($beatmapset->status(), $state); 663 664 $document = json_decode($review->startingPost->message, true); 665 $document[] = [ 666 'type' => 'embed', 667 'discussion_type' => 'problem', 668 'text' => 'whee', 669 ]; 670 671 Review::update($review, $document, $this->user); 672 673 $beatmapset->refresh(); 674 675 // ensure beatmap status hasn't changed. 676 $this->assertSame($beatmapset->status(), $state); 677 678 if ($shouldNotify) { 679 // ensure a new problem notification is dispatched 680 Queue::assertPushed(BeatmapsetDiscussionQualifiedProblem::class); 681 $this->runFakeQueue(); 682 Event::assertDispatched(NewPrivateNotificationEvent::class); 683 } else { 684 Queue::assertNotPushed(BeatmapsetDiscussionQualifiedProblem::class); 685 $this->runFakeQueue(); 686 Event::assertNotDispatched(NewPrivateNotificationEvent::class); 687 } 688 } 689 690 // removing/unlinking an embed from an existing issue 691 public function testUpdateDocumentRemoveIssue() 692 { 693 $review = $this->setUpReview(); 694 695 $discussionCount = BeatmapDiscussion::count(); 696 $discussionPostCount = BeatmapDiscussionPost::count(); 697 698 $document = json_decode($review->startingPost->message, true); 699 $issue = array_shift($document); // drop the first issue 700 701 Review::update($review, $document, $this->user); 702 703 // ensure number of discussions/issues hasn't changed 704 $this->assertSame($discussionCount, BeatmapDiscussion::count()); 705 $this->assertSame($discussionPostCount, BeatmapDiscussionPost::count()); 706 707 $unlinked = BeatmapDiscussion::find($issue['discussion_id']); 708 709 // ensure embed is no longer in message 710 $this->assertStringNotContainsString((string) $unlinked->id, $review->startingPost->message); 711 712 // ensure parent_id is removed from child issue 713 $this->assertNull($unlinked->parent_id); 714 } 715 716 //endregion 717 718 //endregion 719 720 public static function dataProviderForQualifiedProblem() 721 { 722 return [ 723 ['qualified', true], 724 ['pending', false], 725 ]; 726 } 727 728 protected function setUp(): void 729 { 730 parent::setUp(); 731 732 Queue::fake(); 733 Event::fake(); 734 735 config_set('osu.beatmapset.discussion_review_max_blocks', 4); 736 737 $this->user = User::factory()->create(); 738 $this->beatmapset = Beatmapset::factory()->create([ 739 'approved' => Beatmapset::STATES['pending'], 740 ]); 741 $this->beatmap = $this->beatmapset->beatmaps()->save(Beatmap::factory()->make()); 742 } 743 744 protected function setUpReview($beatmapset = null): BeatmapDiscussion 745 { 746 $timestampedIssueText = '00:01:234 '.self::$faker->sentence(); 747 $issueText = self::$faker->sentence(); 748 749 return Review::create( 750 $beatmapset ?? $this->beatmapset, 751 [ 752 [ 753 'type' => 'embed', 754 'discussion_type' => 'problem', 755 'text' => $timestampedIssueText, 756 'timestamp' => true, 757 'beatmap_id' => $this->beatmap->getKey(), 758 ], 759 [ 760 'type' => 'embed', 761 'discussion_type' => 'problem', 762 'text' => $issueText, 763 ], 764 [ 765 'type' => 'paragraph', 766 'text' => 'this is some paragraph text', 767 ], 768 ], 769 $this->user 770 ); 771 } 772 773 protected function setUpPraiseOnlyReview($beatmapset = null, $user = null): BeatmapDiscussion 774 { 775 return Review::create( 776 $beatmapset ?? $this->beatmapset, 777 [ 778 [ 779 'type' => 'embed', 780 'discussion_type' => 'praise', 781 'text' => self::$faker->sentence(), 782 ], 783 [ 784 'type' => 'paragraph', 785 'text' => 'this is some paragraph text', 786 ], 787 ], 788 $user ?? $this->user 789 ); 790 } 791 792 protected function updateReview($document) 793 { 794 $review = $this->setUpReview(); 795 Review::update($review, $document, $this->user); 796 } 797}