Skip to content

Commit 98c26fd

Browse files
committed
vote own questions
1 parent 956fcba commit 98c26fd

4 files changed

Lines changed: 83 additions & 3 deletions

File tree

src/Service/QuestionService.php

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace HeimrichHannot\QnaBundle\Service;
66

7+
use Doctrine\DBAL\Connection;
78
use HeimrichHannot\QnaBundle\Dto\QnaQuestion;
89
use HeimrichHannot\QnaBundle\Dto\QnaSession;
910
use HeimrichHannot\QnaBundle\Enum\SessionState;
@@ -15,6 +16,7 @@
1516
use HeimrichHannot\QnaBundle\Exception\SessionNotPublishedException;
1617
use HeimrichHannot\QnaBundle\Gateway\QnaQuestionGateway;
1718
use HeimrichHannot\QnaBundle\Gateway\QnaSessionGateway;
19+
use HeimrichHannot\QnaBundle\Gateway\QnaVoteGateway;
1820
use Psr\Clock\ClockInterface;
1921

2022
final readonly class QuestionService
@@ -26,6 +28,8 @@ public function __construct(
2628
private ClockInterface $clock,
2729
private int $maxQuestionLength,
2830
private int $questionCooldown,
31+
private QnaVoteGateway $voteGateway,
32+
private Connection $connection,
2933
) {
3034
}
3135

@@ -53,9 +57,12 @@ public function create(int $sessionId, string $question): QnaQuestion
5357
throw new QuestionCooldownException($this->questionCooldown - ($timestamp - $latestCreatedAt));
5458
}
5559

56-
$questionId = $this->questionGateway->create($session->id, $memberId, $question, $timestamp);
60+
return $this->connection->transactional(function () use ($session, $memberId, $question, $timestamp): QnaQuestion {
61+
$questionId = $this->questionGateway->create($session->id, $memberId, $question, $timestamp);
62+
$this->voteGateway->create($questionId, $memberId, $timestamp);
5763

58-
return new QnaQuestion($questionId, $session->id, $memberId, $question, $timestamp);
64+
return new QnaQuestion($questionId, $session->id, $memberId, $question, $timestamp);
65+
});
5966
}
6067

6168
private function requireOpenSession(int $sessionId): QnaSession

tests/Integration/QuestionAnswerDatabaseTest.php

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use HeimrichHannot\QnaBundle\Gateway\QnaVoteGateway;
1414
use HeimrichHannot\QnaBundle\Service\FrontendMemberProvider;
1515
use HeimrichHannot\QnaBundle\Service\QuestionAnswerService;
16+
use HeimrichHannot\QnaBundle\Service\QuestionService;
1617
use HeimrichHannot\QnaBundle\Service\VoteService;
1718
use PHPUnit\Framework\Attributes\DataProvider;
1819
use PHPUnit\Framework\TestCase;
@@ -50,6 +51,57 @@ protected function tearDown(): void
5051
$this->connection->close();
5152
}
5253

54+
public function testNewQuestionIncludesAuthorVoteAndCannotBeVotedTwice(): void
55+
{
56+
$question = $this->questionService(new QnaVoteGateway($this->connection))->create($this->sessionId, 'Automatically voted question');
57+
58+
try {
59+
$votes = new QnaVoteGateway($this->connection);
60+
self::assertSame(1, $votes->getState($question->id, 2147483647)->voteCount);
61+
self::assertTrue($votes->getState($question->id, 2147483647)->hasVoted);
62+
self::assertFalse($votes->getState($question->id, 2147483646)->hasVoted);
63+
self::assertSame(1, $this->voteService()->vote($question->id, $this->sessionId)->voteCount);
64+
self::assertSame(2, $this->voteService(2147483646)->vote($question->id, $this->sessionId)->voteCount);
65+
} finally {
66+
$this->connection->delete('tl_qna_vote', ['pid' => $question->id]);
67+
}
68+
}
69+
70+
public function testFailedAuthorVoteRollsBackQuestion(): void
71+
{
72+
$votes = $this->createMock(QnaVoteGateway::class);
73+
$votes->expects(self::once())->method('create')->willThrowException(new \RuntimeException('Vote insert failed'));
74+
75+
try {
76+
$this->questionService($votes)->create($this->sessionId, 'Must be rolled back');
77+
self::fail('Expected vote failure.');
78+
} catch (\RuntimeException $exception) {
79+
self::assertSame('Vote insert failed', $exception->getMessage());
80+
}
81+
82+
self::assertFalse($this->connection->isTransactionActive());
83+
self::assertCount(1, (new QnaQuestionGateway($this->connection))->findForStage($this->sessionId));
84+
}
85+
86+
private function questionService(QnaVoteGateway $votes): QuestionService
87+
{
88+
$member = $this->createStub(FrontendUser::class);
89+
$member->method('__get')->willReturn(2147483647);
90+
$security = $this->createStub(Security::class);
91+
$security->method('getUser')->willReturn($member);
92+
93+
return new QuestionService(
94+
new QnaSessionGateway($this->connection),
95+
new QnaQuestionGateway($this->connection),
96+
new FrontendMemberProvider($security),
97+
new MockClock('@150'),
98+
500,
99+
20,
100+
$votes,
101+
$this->connection,
102+
);
103+
}
104+
53105
public function testMarkUndoPreservesVotesAndRestoresVoting(): void
54106
{
55107
$questions = new QnaQuestionGateway($this->connection);

tests/Unit/QnaActionControllerTest.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,8 @@ public function testCreatedQuestionRedirectsToAListUpdateThatResetsTheForm(): vo
201201
new MockClock('@150'),
202202
500,
203203
20,
204+
$this->createStub(QnaVoteGateway::class),
205+
$this->transactionConnection(),
204206
);
205207
$urlGenerator = $this->createMock(UrlGeneratorInterface::class);
206208
$urlGenerator->expects(self::once())
@@ -240,6 +242,8 @@ public function testRejectedQuestionReturnsUnprocessableReaderFrame(): void
240242
new MockClock('@150'),
241243
500,
242244
20,
245+
$this->createStub(QnaVoteGateway::class),
246+
$this->transactionConnection(),
243247
);
244248
$controller = new QnaActionController(
245249
$questionService,
@@ -311,6 +315,8 @@ public function testMissingAuthenticationKeepsUnauthorizedStatus(): void
311315
new MockClock('@150'),
312316
500,
313317
20,
318+
$this->createStub(QnaVoteGateway::class),
319+
$this->transactionConnection(),
314320
);
315321
$controller = new QnaActionController(
316322
$questionService,

tests/Unit/QuestionServiceTest.php

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace HeimrichHannot\QnaBundle\Tests\Unit;
66

77
use Contao\FrontendUser;
8+
use Doctrine\DBAL\Connection;
89
use HeimrichHannot\QnaBundle\Dto\QnaSession;
910
use HeimrichHannot\QnaBundle\Enum\SessionState;
1011
use HeimrichHannot\QnaBundle\Exception\AuthenticationRequiredException;
@@ -15,6 +16,7 @@
1516
use HeimrichHannot\QnaBundle\Exception\SessionNotPublishedException;
1617
use HeimrichHannot\QnaBundle\Gateway\QnaQuestionGateway;
1718
use HeimrichHannot\QnaBundle\Gateway\QnaSessionGateway;
19+
use HeimrichHannot\QnaBundle\Gateway\QnaVoteGateway;
1820
use HeimrichHannot\QnaBundle\Service\FrontendMemberProvider;
1921
use HeimrichHannot\QnaBundle\Service\QuestionService;
2022
use PHPUnit\Framework\Attributes\DataProvider;
@@ -36,7 +38,10 @@ public function testCreatesTrimmedQuestionForMemberFromSecurityContext(): void
3638
->with(12, 42, 'How does this work?', 1_700_000_000)
3739
->willReturn(99);
3840

39-
$question = $this->service($sessionGateway, $questionGateway)->create(12, ' How does this work? ');
41+
$votes = $this->createMock(QnaVoteGateway::class);
42+
$votes->expects(self::once())->method('create')->with(99, 42, 1_700_000_000);
43+
44+
$question = $this->service($sessionGateway, $questionGateway, voteGateway: $votes)->create(12, ' How does this work? ');
4045

4146
self::assertSame(99, $question->id);
4247
self::assertSame(42, $question->memberId);
@@ -131,6 +136,7 @@ private function service(
131136
QnaQuestionGateway $questionGateway,
132137
?int $memberId = 42,
133138
int $maxQuestionLength = 500,
139+
?QnaVoteGateway $voteGateway = null,
134140
): QuestionService {
135141
$security = $this->createStub(Security::class);
136142

@@ -142,13 +148,22 @@ private function service(
142148
$security->method('getUser')->willReturn($member);
143149
}
144150

151+
$connection = $this->createStub(Connection::class);
152+
$connection->method('transactional')->willReturnCallback(static fn (callable $callback): mixed => $callback());
153+
if (null === $voteGateway) {
154+
$voteGateway = $this->createMock(QnaVoteGateway::class);
155+
$voteGateway->expects(self::never())->method('create');
156+
}
157+
145158
return new QuestionService(
146159
$sessionGateway,
147160
$questionGateway,
148161
new FrontendMemberProvider($security),
149162
$this->clock(),
150163
$maxQuestionLength,
151164
20,
165+
$voteGateway,
166+
$connection,
152167
);
153168
}
154169

0 commit comments

Comments
 (0)