test node controller ownership checks
add failing tests asserting 403 when a non-owner tries to read or write nodes on another user's text, plus admin bypass. existing tests now attach a session user to mirror the new controller signature.
This commit is contained in:
parent
051e44033f
commit
e56cb56ce7
2 changed files with 222 additions and 38 deletions
|
|
@ -7,6 +7,7 @@ use App\Node\NodeController;
|
|||
use App\Node\UseCases\BulkCreateNodes;
|
||||
use App\Text\CreateTextDto;
|
||||
use App\User\UseCases\CreateUserDto;
|
||||
use App\User\User;
|
||||
use App\ValueObjects\EmailAddress;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
|
@ -23,19 +24,32 @@ class BulkCreateNodesControllerTest extends TestCase
|
|||
private FakeNodeRepository $nodeRepo;
|
||||
private BulkCreateNodes $useCase;
|
||||
private NodeController $controller;
|
||||
private User $user;
|
||||
private User $otherUser;
|
||||
private User $admin;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$userRepo = new FakeUserRepository();
|
||||
$user = $userRepo->create(new CreateUserDto(
|
||||
$this->user = $userRepo->create(new CreateUserDto(
|
||||
email: new EmailAddress('a@b.com'),
|
||||
passwordHash: '',
|
||||
isAdmin: false,
|
||||
));
|
||||
$this->otherUser = $userRepo->create(new CreateUserDto(
|
||||
email: new EmailAddress('other@b.com'),
|
||||
passwordHash: '',
|
||||
isAdmin: false,
|
||||
));
|
||||
$this->admin = $userRepo->create(new CreateUserDto(
|
||||
email: new EmailAddress('admin@b.com'),
|
||||
passwordHash: '',
|
||||
isAdmin: true,
|
||||
));
|
||||
$this->textRepo = new FakeTextRepository();
|
||||
$text = $this->textRepo->create(new CreateTextDto(
|
||||
name: 'test text',
|
||||
user: $user,
|
||||
user: $this->user,
|
||||
));
|
||||
|
||||
$this->nodeRepo = new FakeNodeRepository();
|
||||
|
|
@ -54,13 +68,17 @@ class BulkCreateNodesControllerTest extends TestCase
|
|||
);
|
||||
}
|
||||
|
||||
private function makeRequest(array $data): ServerRequestInterface
|
||||
{
|
||||
private function makeRequest(
|
||||
array $data,
|
||||
?User $user = null,
|
||||
): ServerRequestInterface {
|
||||
$body = new StreamFactory()->createStream(json_encode($data));
|
||||
return new ServerRequestFactory()
|
||||
$request = new ServerRequestFactory()
|
||||
->createServerRequest('POST', 'http://localhost/api/nodes/bulk')
|
||||
->withHeader('Content-Type', 'application/json')
|
||||
->withBody($body);
|
||||
$sessionUser = $user ?? $this->user;
|
||||
return $request->withAttribute('user', $sessionUser);
|
||||
}
|
||||
|
||||
public function test_bulk_create_nodes_returns_201_with_created_nodes(): void
|
||||
|
|
@ -227,4 +245,42 @@ class BulkCreateNodesControllerTest extends TestCase
|
|||
|
||||
$this->assertEquals(404, $response->getStatusCode());
|
||||
}
|
||||
|
||||
public function test_bulk_create_nodes_returns_403_when_not_owner(): void
|
||||
{
|
||||
$response = $this->controller->bulkCreateNodes(
|
||||
$this->makeRequest(
|
||||
[
|
||||
'textId' => 0,
|
||||
'parentNodeId' => 0,
|
||||
'titlePrefix' => 'Page',
|
||||
'count' => 3,
|
||||
],
|
||||
$this->otherUser,
|
||||
),
|
||||
new Response(),
|
||||
$this->useCase,
|
||||
);
|
||||
|
||||
$this->assertEquals(403, $response->getStatusCode());
|
||||
}
|
||||
|
||||
public function test_bulk_create_nodes_allows_admin_on_any_text(): void
|
||||
{
|
||||
$response = $this->controller->bulkCreateNodes(
|
||||
$this->makeRequest(
|
||||
[
|
||||
'textId' => 0,
|
||||
'parentNodeId' => 0,
|
||||
'titlePrefix' => 'Page',
|
||||
'count' => 2,
|
||||
],
|
||||
$this->admin,
|
||||
),
|
||||
new Response(),
|
||||
$this->useCase,
|
||||
);
|
||||
|
||||
$this->assertEquals(201, $response->getStatusCode());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,8 +7,10 @@ use App\Node\NodeController;
|
|||
use App\Node\UseCases\CreateNode;
|
||||
use App\Text\CreateTextDto;
|
||||
use App\User\UseCases\CreateUserDto;
|
||||
use App\User\User;
|
||||
use App\ValueObjects\EmailAddress;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Slim\Psr7\Factory\ServerRequestFactory;
|
||||
use Slim\Psr7\Factory\StreamFactory;
|
||||
use Slim\Psr7\Response;
|
||||
|
|
@ -21,21 +23,65 @@ class NodeControllerTest extends TestCase
|
|||
private FakeTextRepository $textRepo;
|
||||
private FakeNodeRepository $nodeRepo;
|
||||
private NodeController $controller;
|
||||
private User $user;
|
||||
private User $otherUser;
|
||||
private User $admin;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$userRepo = new FakeUserRepository();
|
||||
$user = $userRepo->create(new CreateUserDto(
|
||||
$this->user = $userRepo->create(new CreateUserDto(
|
||||
email: new EmailAddress('a@b.com'),
|
||||
passwordHash: '',
|
||||
isAdmin: false,
|
||||
));
|
||||
$this->otherUser = $userRepo->create(new CreateUserDto(
|
||||
email: new EmailAddress('other@b.com'),
|
||||
passwordHash: '',
|
||||
isAdmin: false,
|
||||
));
|
||||
$this->admin = $userRepo->create(new CreateUserDto(
|
||||
email: new EmailAddress('admin@b.com'),
|
||||
passwordHash: '',
|
||||
isAdmin: true,
|
||||
));
|
||||
$this->textRepo = new FakeTextRepository();
|
||||
$this->textRepo->create(new CreateTextDto(name: 'test text', user: $user));
|
||||
$this->textRepo->create(new CreateTextDto(
|
||||
name: 'test text',
|
||||
user: $this->user,
|
||||
));
|
||||
|
||||
$this->nodeRepo = new FakeNodeRepository();
|
||||
|
||||
$this->controller = new NodeController($this->nodeRepo, $this->textRepo);
|
||||
$this->controller = new NodeController(
|
||||
$this->nodeRepo,
|
||||
$this->textRepo,
|
||||
);
|
||||
}
|
||||
|
||||
private function makeRequest(
|
||||
array $body,
|
||||
?User $user,
|
||||
): ServerRequestInterface {
|
||||
$stream = new StreamFactory()->createStream(json_encode($body));
|
||||
$request = new ServerRequestFactory()
|
||||
->createServerRequest('POST', 'http://localhost/api/nodes')
|
||||
->withHeader('Content-Type', 'application/json')
|
||||
->withBody($stream);
|
||||
if ($user !== null) {
|
||||
$request = $request->withAttribute('user', $user);
|
||||
}
|
||||
return $request;
|
||||
}
|
||||
|
||||
private function makeGetRequest(?User $user): ServerRequestInterface
|
||||
{
|
||||
$request = new ServerRequestFactory()
|
||||
->createServerRequest('GET', 'http://localhost/api/nodes/0');
|
||||
if ($user !== null) {
|
||||
$request = $request->withAttribute('user', $user);
|
||||
}
|
||||
return $request;
|
||||
}
|
||||
|
||||
public function test_get_nodes_of_text_returns_flat_array(): void
|
||||
|
|
@ -47,7 +93,11 @@ class NodeControllerTest extends TestCase
|
|||
parentNode: null,
|
||||
));
|
||||
|
||||
$response = $this->controller->getNodesOfText(new Response(), 0);
|
||||
$response = $this->controller->getNodesOfText(
|
||||
$this->makeGetRequest($this->user),
|
||||
new Response(),
|
||||
0,
|
||||
);
|
||||
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
$this->assertEquals(
|
||||
|
|
@ -60,7 +110,11 @@ class NodeControllerTest extends TestCase
|
|||
|
||||
public function test_get_nodes_of_text_returns_empty_array_when_no_nodes(): void
|
||||
{
|
||||
$response = $this->controller->getNodesOfText(new Response(), 0);
|
||||
$response = $this->controller->getNodesOfText(
|
||||
$this->makeGetRequest($this->user),
|
||||
new Response(),
|
||||
0,
|
||||
);
|
||||
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
$this->assertEquals(json_encode([]), $response->getBody());
|
||||
|
|
@ -68,11 +122,44 @@ class NodeControllerTest extends TestCase
|
|||
|
||||
public function test_get_nodes_of_text_returns_404_for_unknown_text(): void
|
||||
{
|
||||
$response = $this->controller->getNodesOfText(new Response(), 99);
|
||||
$response = $this->controller->getNodesOfText(
|
||||
$this->makeGetRequest($this->user),
|
||||
new Response(),
|
||||
99,
|
||||
);
|
||||
|
||||
$this->assertEquals(404, $response->getStatusCode());
|
||||
}
|
||||
|
||||
public function test_get_nodes_of_text_returns_403_when_not_owner(): void
|
||||
{
|
||||
$response = $this->controller->getNodesOfText(
|
||||
$this->makeGetRequest($this->otherUser),
|
||||
new Response(),
|
||||
0,
|
||||
);
|
||||
|
||||
$this->assertEquals(403, $response->getStatusCode());
|
||||
}
|
||||
|
||||
public function test_get_nodes_of_text_allows_admin(): void
|
||||
{
|
||||
$text = $this->textRepo->find(0);
|
||||
$this->nodeRepo->create(new CreateNodeDto(
|
||||
text: $text,
|
||||
title: 'Root Node',
|
||||
parentNode: null,
|
||||
));
|
||||
|
||||
$response = $this->controller->getNodesOfText(
|
||||
$this->makeGetRequest($this->admin),
|
||||
new Response(),
|
||||
0,
|
||||
);
|
||||
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
}
|
||||
|
||||
public function test_get_nodes_includes_parent_node_id(): void
|
||||
{
|
||||
$text = $this->textRepo->find(0);
|
||||
|
|
@ -87,7 +174,11 @@ class NodeControllerTest extends TestCase
|
|||
parentNode: $rootNode,
|
||||
));
|
||||
|
||||
$response = $this->controller->getNodesOfText(new Response(), 0);
|
||||
$response = $this->controller->getNodesOfText(
|
||||
$this->makeGetRequest($this->user),
|
||||
new Response(),
|
||||
0,
|
||||
);
|
||||
$body = json_decode($response->getBody(), true);
|
||||
|
||||
$this->assertEquals(0, $body[1]['parentNodeId']);
|
||||
|
|
@ -102,15 +193,14 @@ class NodeControllerTest extends TestCase
|
|||
parentNode: null,
|
||||
));
|
||||
|
||||
$body = new StreamFactory()->createStream(json_encode([
|
||||
$request = $this->makeRequest(
|
||||
[
|
||||
'textId' => 0,
|
||||
'title' => 'Child Node',
|
||||
'parentNodeId' => $rootNode->getId(),
|
||||
]));
|
||||
$request = new ServerRequestFactory()
|
||||
->createServerRequest('POST', 'http://localhost/api/nodes')
|
||||
->withHeader('Content-Type', 'application/json')
|
||||
->withBody($body);
|
||||
],
|
||||
$this->user,
|
||||
);
|
||||
|
||||
$response = $this->controller->createNode(
|
||||
$request,
|
||||
|
|
@ -127,14 +217,13 @@ class NodeControllerTest extends TestCase
|
|||
|
||||
public function test_create_node_returns_400_when_title_missing(): void
|
||||
{
|
||||
$body = new StreamFactory()->createStream(json_encode([
|
||||
$request = $this->makeRequest(
|
||||
[
|
||||
'textId' => 0,
|
||||
'parentNodeId' => null,
|
||||
]));
|
||||
$request = new ServerRequestFactory()
|
||||
->createServerRequest('POST', 'http://localhost/api/nodes')
|
||||
->withHeader('Content-Type', 'application/json')
|
||||
->withBody($body);
|
||||
],
|
||||
$this->user,
|
||||
);
|
||||
|
||||
$response = $this->controller->createNode(
|
||||
$request,
|
||||
|
|
@ -147,15 +236,14 @@ class NodeControllerTest extends TestCase
|
|||
|
||||
public function test_create_node_returns_404_when_text_not_found(): void
|
||||
{
|
||||
$body = new StreamFactory()->createStream(json_encode([
|
||||
$request = $this->makeRequest(
|
||||
[
|
||||
'textId' => 99,
|
||||
'title' => 'Some Node',
|
||||
'parentNodeId' => null,
|
||||
]));
|
||||
$request = new ServerRequestFactory()
|
||||
->createServerRequest('POST', 'http://localhost/api/nodes')
|
||||
->withHeader('Content-Type', 'application/json')
|
||||
->withBody($body);
|
||||
],
|
||||
$this->user,
|
||||
);
|
||||
|
||||
$response = $this->controller->createNode(
|
||||
$request,
|
||||
|
|
@ -165,4 +253,44 @@ class NodeControllerTest extends TestCase
|
|||
|
||||
$this->assertEquals(404, $response->getStatusCode());
|
||||
}
|
||||
|
||||
public function test_create_node_returns_403_when_not_owner(): void
|
||||
{
|
||||
$request = $this->makeRequest(
|
||||
[
|
||||
'textId' => 0,
|
||||
'title' => 'Hijack',
|
||||
'parentNodeId' => null,
|
||||
],
|
||||
$this->otherUser,
|
||||
);
|
||||
|
||||
$response = $this->controller->createNode(
|
||||
$request,
|
||||
new Response(),
|
||||
new CreateNode($this->nodeRepo, $this->textRepo),
|
||||
);
|
||||
|
||||
$this->assertEquals(403, $response->getStatusCode());
|
||||
}
|
||||
|
||||
public function test_create_node_allows_admin_on_any_text(): void
|
||||
{
|
||||
$request = $this->makeRequest(
|
||||
[
|
||||
'textId' => 0,
|
||||
'title' => 'Admin Root',
|
||||
'parentNodeId' => null,
|
||||
],
|
||||
$this->admin,
|
||||
);
|
||||
|
||||
$response = $this->controller->createNode(
|
||||
$request,
|
||||
new Response(),
|
||||
new CreateNode($this->nodeRepo, $this->textRepo),
|
||||
);
|
||||
|
||||
$this->assertEquals(201, $response->getStatusCode());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue