fix(integration-requests): freeze votes on not-doable requests (#555)
## What Prevent adding or modifying votes on requests marked as **not-doable**. - **Backend** (`removeVote`): a vote cast before a request was marked not-doable could still be removed, mutating a frozen tally. It now returns `404`, mirroring `vote()` which already rejected not-doable requests. - **Frontend**: hide the unvote (chevron-down) button for not-doable requests; the vote button was already disabled for them. ## Test Added a feature test: vote → request becomes not-doable → `DELETE` vote returns `404` and the tally is unchanged.
This commit is contained in:
parent
0ea54fa0d7
commit
89c1ab1ca8
|
|
@ -79,6 +79,11 @@ class IntegrationRequestController extends Controller
|
|||
{
|
||||
$user = $request->user();
|
||||
|
||||
// Not-doable requests are frozen: their tally can no longer be touched.
|
||||
if ($integrationRequest->status === IntegrationRequestStatus::NotDoable) {
|
||||
abort(404);
|
||||
}
|
||||
|
||||
// Only votes cast this month can be undone, so the refund maps back to
|
||||
// the current quota while earlier months' tallies stay locked in.
|
||||
$vote = $integrationRequest->votes()
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@ export function IntegrationRequestsBoard({
|
|||
};
|
||||
|
||||
const handleRemoveVote = async (item: IntegrationRequestItem) => {
|
||||
if (busy || !item.can_unvote) {
|
||||
if (busy || !item.can_unvote || item.status === 'not_doable') {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -273,19 +273,22 @@ export function IntegrationRequestsBoard({
|
|||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
{item.can_unvote && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
disabled={busy}
|
||||
onClick={() =>
|
||||
handleRemoveVote(item)
|
||||
}
|
||||
aria-label={__('Remove one vote')}
|
||||
>
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
</Button>
|
||||
)}
|
||||
{item.can_unvote &&
|
||||
item.status !== 'not_doable' && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
disabled={busy}
|
||||
onClick={() =>
|
||||
handleRemoveVote(item)
|
||||
}
|
||||
aria-label={__(
|
||||
'Remove one vote',
|
||||
)}
|
||||
>
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant={
|
||||
item.has_voted
|
||||
|
|
|
|||
|
|
@ -323,6 +323,20 @@ test('nobody can vote on a not doable request', function () {
|
|||
->assertNotFound();
|
||||
});
|
||||
|
||||
test('a vote on a request later marked not doable cannot be removed', function () {
|
||||
$user = User::factory()->create();
|
||||
$request = IntegrationRequest::factory()->approved()->create();
|
||||
$request->votes()->create(['user_id' => $user->id]);
|
||||
|
||||
$request->update(['status' => IntegrationRequestStatus::NotDoable]);
|
||||
|
||||
$this->actingAs($user)
|
||||
->deleteJson("/integration-requests/{$request->id}/vote")
|
||||
->assertNotFound();
|
||||
|
||||
expect($request->votes()->count())->toBe(1);
|
||||
});
|
||||
|
||||
test('the review command marks a request as not doable with a comment', function () {
|
||||
$request = IntegrationRequest::factory()->create(['name' => 'Hard Bank']);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue