-
Notifications
You must be signed in to change notification settings - Fork 202
feat(server): log unsafe errors #1258
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
cf5a7a1
0aed48b
5139962
9ee8313
5508a41
063ba6a
db10276
bc583de
7f830b1
93b7ec8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
<?php | ||
|
||
namespace Drupal\Tests\graphql\Kernel\Framework; | ||
|
||
use Drupal\Core\Logger\RfcLoggerTrait; | ||
use Drupal\Tests\graphql\Kernel\GraphQLTestBase; | ||
use Psr\Log\LoggerInterface; | ||
|
||
/** | ||
* Test error logging. | ||
* | ||
* @group graphql | ||
*/ | ||
class LoggerTest extends GraphQLTestBase implements LoggerInterface { | ||
|
||
use RfcLoggerTrait; | ||
|
||
/** | ||
* Loggers calls. | ||
* | ||
* @var array | ||
*/ | ||
protected $loggerCalls = []; | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
protected function setUp(): void { | ||
parent::setUp(); | ||
|
||
$schema = <<<GQL | ||
schema { | ||
query: Query | ||
} | ||
type Query { | ||
resolvesToNull: String! | ||
throwsException: String! | ||
takesIntArgument(id: Int!): String | ||
} | ||
GQL; | ||
|
||
$this->setUpSchema($schema); | ||
|
||
$this->mockResolver('Query', 'resolvesToNull', NULL); | ||
$this->mockResolver('Query', 'throwsException', function () { | ||
throw new \Exception('BOOM!'); | ||
}); | ||
$this->mockResolver('Query', 'takesIntArgument'); | ||
|
||
$this->container->get('logger.factory')->addLogger($this); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice trick, this is really convenient. I need to remember that. |
||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function log($level, $message, array $context = []) { | ||
$this->loggerCalls[] = [ | ||
'level' => $level, | ||
'message' => $message, | ||
'context' => $context, | ||
]; | ||
} | ||
|
||
/** | ||
* Test if invariant violation errors are logged. | ||
*/ | ||
public function testInvariantViolationError(): void { | ||
$result = $this->query('query { resolvesToNull }'); | ||
$this->assertSame(200, $result->getStatusCode()); | ||
// Client should not see the actual error. | ||
$this->assertSame([ | ||
'errors' => [ | ||
[ | ||
'message' => 'Internal server error', | ||
'extensions' => [ | ||
'category' => 'internal', | ||
], | ||
'locations' => [ | ||
[ | ||
'line' => 1, | ||
'column' => 9, | ||
], | ||
], | ||
'path' => [ | ||
'resolvesToNull', | ||
], | ||
], | ||
], | ||
], json_decode($result->getContent(), TRUE)); | ||
// The error should be logged. | ||
$this->assertCount(1, $this->loggerCalls); | ||
$loggerCall = reset($this->loggerCalls); | ||
$details = json_decode($loggerCall['context']['details'], TRUE); | ||
$this->assertSame($details['$operation']['query'], 'query { resolvesToNull }'); | ||
$this->assertSame($details['$operation']['variables'], []); | ||
$this->assertCount(1, $details['$result->errors']); | ||
$this->assertSame( | ||
$details['$result->errors'][0]['message'], | ||
'Cannot return null for non-nullable field "Query.resolvesToNull".' | ||
); | ||
$this->assertStringContainsString( | ||
'For error #0: GraphQL\Error\InvariantViolation: Cannot return null for non-nullable field "Query.resolvesToNull".', | ||
$loggerCall['context']['previous'] | ||
); | ||
} | ||
|
||
/** | ||
* Test if exceptions thrown from resolvers are logged. | ||
*/ | ||
public function testException(): void { | ||
$result = $this->query('query { throwsException }'); | ||
$this->assertSame(200, $result->getStatusCode()); | ||
// Client should not see the actual error. | ||
$this->assertSame([ | ||
'errors' => [ | ||
[ | ||
'message' => 'Internal server error', | ||
'extensions' => [ | ||
'category' => 'internal', | ||
], | ||
'locations' => [ | ||
[ | ||
'line' => 1, | ||
'column' => 9, | ||
], | ||
], | ||
'path' => [ | ||
'throwsException', | ||
], | ||
], | ||
], | ||
], json_decode($result->getContent(), TRUE)); | ||
// The error should be logged. | ||
$this->assertCount(1, $this->loggerCalls); | ||
$loggerCall = reset($this->loggerCalls); | ||
$details = json_decode($loggerCall['context']['details'], TRUE); | ||
$this->assertSame($details['$operation']['query'], 'query { throwsException }'); | ||
$this->assertSame($details['$operation']['variables'], []); | ||
$this->assertCount(1, $details['$result->errors']); | ||
$this->assertSame($details['$result->errors'][0]['message'], 'BOOM!'); | ||
$this->assertStringContainsString( | ||
'For error #0: Exception: BOOM!', | ||
$loggerCall['context']['previous'] | ||
); | ||
} | ||
|
||
/** | ||
* Test if client error are not logged. | ||
*/ | ||
public function testClientError(): void { | ||
$result = $this->query('query { takesIntArgument(id: "boom") }'); | ||
$this->assertSame(200, $result->getStatusCode()); | ||
// The error should be reported back to client. | ||
$this->assertSame([ | ||
'errors' => [ | ||
0 => [ | ||
'message' => 'Field "takesIntArgument" argument "id" requires type Int!, found "boom".', | ||
'extensions' => [ | ||
'category' => 'graphql', | ||
], | ||
'locations' => [ | ||
0 => [ | ||
'line' => 1, | ||
'column' => 30, | ||
], | ||
], | ||
], | ||
], | ||
], json_decode($result->getContent(), TRUE)); | ||
// The error should not be logged. | ||
$this->assertCount(0, $this->loggerCalls); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -70,6 +70,7 @@ protected function setUp(): void { | |
$this->installEntitySchema('graphql_server'); | ||
$this->installEntitySchema('configurable_language'); | ||
$this->installConfig(['language']); | ||
$this->installEntitySchema('menu_link_content'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't even want to know why we need this :-D But totally fine, thanks! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On the query run there was an error logged. Something like "DB error: menu_link_content table does not exist". I also did not want to know why it happens 😅 So I just installed There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we know in what test this was? This could indicate a hidden dependency that I don't think should exist. So in theory this could even be flagging a breaking change. (It's also not a module I'd expect should be added for all tests, so I'm not sure if There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It was happening in the new test I created. |
||
|
||
$this->setUpCurrentUser([], $this->userPermissions()); | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should inject the logger, but does not matter much in this class. Can be done in a follow-up.