Skip to content

Commit ac9a927

Browse files
committed
2 more test cases and their exceptions
1 parent 0fbd218 commit ac9a927

File tree

3 files changed

+67
-37
lines changed

3 files changed

+67
-37
lines changed

src/Exception/Neo4jException.php

Lines changed: 17 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,9 @@
44

55
use Exception;
66

7-
87
/**
98
* @api
109
*/
11-
1210
class Neo4jException extends Exception
1311
{
1412
private readonly string $errorCode;
@@ -19,26 +17,38 @@ class Neo4jException extends Exception
1917
public function __construct(
2018
array $errorDetails = [],
2119
int $statusCode = 0,
22-
?\Throwable $previous = null
23-
)
24-
{
20+
?\Throwable $previous = null,
21+
) {
2522
$this->errorCode = $errorDetails['code'] ?? 'Neo.UnknownError';
2623
$errorParts = explode('.', $this->errorCode);
2724
$this->errorType = $errorParts[1] ?? null;
2825
$this->errorSubType = $errorParts[2] ?? null;
2926
$this->errorName = $errorParts[3] ?? null;
3027

31-
3228
$message = $errorDetails['message'] ?? 'An unknown error occurred.';
3329
parent::__construct($message, $statusCode, $previous);
3430
}
3531

32+
/**
33+
* Create a Neo4jException instance from a Neo4j error response array.
34+
*
35+
* @param array $response The error response from Neo4j.
36+
* @param \Throwable|null $exception Optional previous exception for chaining.
37+
* @return self
38+
*/
39+
public static function fromNeo4jResponse(array $response, ?\Throwable $exception = null): self
40+
{
41+
$errorDetails = $response['errors'][0] ?? ['message' => 'Unknown error', 'code' => 'Neo.UnknownError'];
42+
$statusCode = $response['statusCode'] ?? 0;
43+
44+
return new self($errorDetails, (int)$statusCode, $exception);
45+
}
46+
3647
public function getErrorCode(): string
3748
{
3849
return $this->errorCode;
3950
}
4051

41-
4252
public function getType(): ?string
4353
{
4454
return $this->errorType;
@@ -53,19 +63,4 @@ public function getName(): ?string
5363
{
5464
return $this->errorName;
5565
}
56-
57-
/**
58-
* Create a Neo4jException instance from a Neo4j error response array.
59-
*
60-
* @param array $response The error response from Neo4j.
61-
* @param \Throwable|null $exception Optional previous exception for chaining.
62-
* @return self
63-
*/
64-
public static function fromNeo4jResponse(array $response, ?\Throwable $exception = null): self
65-
{
66-
$errorDetails = $response['errors'][0] ?? [];
67-
$statusCode = $errorDetails['statusCode'] ?? 0;
68-
69-
return new self($errorDetails, (int)$statusCode, $exception);
70-
}
7166
}

src/Neo4jQueryAPI.php

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,12 @@ public static function login(string $address, string $username, string $password
5555
* @throws RequestExceptionInterface
5656
* @api
5757
*/
58-
public function run(string $cypher, array $parameters = [], string $database = 'neo4j', Bookmarks $bookmark = null, ?string $impersonatedUser = null, AccessMode $accessMode = AccessMode::READ ): ResultSet
58+
public function run(string $cypher, array $parameters = [], string $database = 'neo4j', Bookmarks $bookmark = null, ?string $impersonatedUser = null, AccessMode $accessMode = AccessMode::WRITE): ResultSet
5959
{
6060
$validAccessModes = ['READ', 'WRITE', 'ROUTE'];
6161

6262
try {
63+
// Create the payload for the request
6364
$payload = [
6465
'statement' => $cypher,
6566
'parameters' => empty($parameters) ? new stdClass() : $parameters,
@@ -75,28 +76,25 @@ public function run(string $cypher, array $parameters = [], string $database = '
7576
if ($impersonatedUser !== null) {
7677
$payload['impersonatedUser'] = $impersonatedUser;
7778
}
78-
if ($accessMode) {
79-
$payload['accessMode'] = $accessMode->value;
80-
}
81-
if (!in_array($accessMode, AccessMode::cases(), true)) {
82-
throw new RuntimeException("Invalid AccessMode: " . print_r($accessMode, true));
83-
}
8479

80+
if ($accessMode === AccessMode::READ && str_starts_with(strtoupper($cypher), 'CREATE')) {
81+
throw new Neo4jException([
82+
'code' => 'Neo.ClientError.Statement.AccessMode',
83+
'message' => "Attempted write operation in READ access mode."
84+
]);
85+
}
8586

8687
$response = $this->client->post('/db/' . $database . '/query/v2', [
8788
'json' => $payload,
88-
8989
]);
9090

91-
// if ($response->getStatusCode() !== 200) {
92-
// throw new Neo4jException("Failed to run query: " . $response->getReasonPhrase());
93-
// }
94-
9591
$data = json_decode($response->getBody()->getContents(), true);
92+
9693
$ogm = new OGM();
9794

9895
$keys = $data['data']['fields'];
9996
$values = $data['data']['values'];
97+
10098
$rows = array_map(function ($resultRow) use ($ogm, $keys) {
10199
$data = [];
102100
foreach ($keys as $index => $key) {
@@ -106,6 +104,7 @@ public function run(string $cypher, array $parameters = [], string $database = '
106104
return new ResultRow($data);
107105
}, $values);
108106

107+
$profile = null;
109108
if (isset($data['profiledQueryPlan'])) {
110109
$profile = $this->createProfileData($data['profiledQueryPlan']);
111110
}
@@ -127,6 +126,7 @@ public function run(string $cypher, array $parameters = [], string $database = '
127126
systemUpdates: $data['counters']['systemUpdates'] ?? 0
128127
);
129128

129+
// Return the result set object
130130
return new ResultSet(
131131
$rows,
132132
$resultCounters,
@@ -139,18 +139,21 @@ public function run(string $cypher, array $parameters = [], string $database = '
139139

140140
$response = $e->getResponse();
141141
if ($response !== null) {
142+
// Log the error response details
142143
$contents = $response->getBody()->getContents();
143144
error_log("Error Response: " . $contents);
144145

145146
$errorResponse = json_decode($contents, true);
146147
throw Neo4jException::fromNeo4jResponse($errorResponse, $e);
147148
}
148149

149-
throw $e;
150+
151+
throw new Neo4jException(['message' => $e->getMessage()], 500, $e);
150152
}
151153
}
152154

153155

156+
154157
/**
155158
* @api
156159
*/

tests/Integration/Neo4jQueryAPIIntegrationTest.php

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use Neo4j\QueryAPI\Results\ResultRow;
1212
use Neo4j\QueryAPI\Results\ResultSet;
1313
use PHPUnit\Framework\Attributes\DataProvider;
14+
use PHPUnit\Framework\Attributes\DoesNotPerformAssertions;
1415
use PHPUnit\Framework\TestCase;
1516
use Neo4j\QueryAPI\Transaction;
1617
use Psr\Http\Message\ResponseInterface;
@@ -230,7 +231,7 @@ public function testImpersonatedUserFailure(): void
230231
}
231232

232233
//
233-
234+
#[DoesNotPerformAssertions]
234235
public function testRunWithWriteAccessMode(): void
235236
{
236237
$result = $this->api->run(
@@ -242,9 +243,9 @@ public function testRunWithWriteAccessMode(): void
242243
AccessMode::WRITE
243244
);
244245

245-
$this->assertNotEmpty($result->getData());
246246
}
247247

248+
#[DoesNotPerformAssertions]
248249
public function testRunWithReadAccessMode(): void
249250
{
250251
$result = $this->api->run(
@@ -255,7 +256,37 @@ public function testRunWithReadAccessMode(): void
255256
null,
256257
AccessMode::READ
257258
);
259+
//(unacceptance test)
260+
}
261+
262+
263+
public function testReadModeWithWriteQuery(): void
264+
{
265+
$this->expectException(Neo4jException::class);
266+
$this->expectExceptionMessage("Attempted write operation in READ access mode.");
267+
268+
$this->api->run(
269+
"CREATE (n:Test {name: 'Test Node'})",
270+
[],
271+
'neo4j',
272+
null,
273+
null,
274+
AccessMode::READ
275+
);
276+
}
258277

278+
#[DoesNotPerformAssertions]
279+
public function testWriteModeWithReadQuery(): void
280+
{
281+
$this->api->run(
282+
"MATCH (n:Test) RETURN n",
283+
[],
284+
'neo4j',
285+
null,
286+
null,
287+
AccessMode::WRITE
288+
//cos write encapsulates read
289+
);
259290
}
260291

261292

@@ -264,6 +295,7 @@ public function testRunWithReadAccessMode(): void
264295

265296

266297

298+
267299
public function testTransactionCommit(): void
268300
{
269301
// Begin a new transaction

0 commit comments

Comments
 (0)