-
Notifications
You must be signed in to change notification settings - Fork 2
Feature/formatter unit test #479
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
Conversation
590d5f6 to
a044a1b
Compare
a044a1b to
ca920e2
Compare
9054f4b to
bf23fcd
Compare
bf23fcd to
25bc9cc
Compare
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.
Pull request overview
This pull request adds comprehensive unit tests for audit log formatters in the OpenTelemetry module. The tests validate that all formatter classes can be instantiated, handle various event types correctly, and gracefully manage error conditions.
Key changes:
- Added a
FormatterTestHelperclass with assertion methods to validate formatter implementation requirements - Added an
AuditContextBuilderclass using the builder pattern to simplify test context creation - Added comprehensive integration tests in
AllFormattersIntegrationTestthat discover and validate all formatters in the codebase - Modified
AuditLogFormatterFactoryto add exception handling when loading configuration
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 9 comments.
| File | Description |
|---|---|
| tests/OpenTelemetry/Formatters/Support/FormatterTestHelper.php | Helper class providing assertion methods for validating formatter implementations |
| tests/OpenTelemetry/Formatters/Support/AuditContextBuilder.php | Builder class for creating test audit contexts with fluent interface |
| tests/OpenTelemetry/Formatters/AllFormattersIntegrationTest.php | Comprehensive integration tests that validate all formatter classes |
| app/Audit/AuditLogFormatterFactory.php | Added exception handling around config loading in constructor |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if ($formatter !== null) { | ||
| $this->assertInstanceOf( | ||
| IAuditLogFormatter::class, | ||
| $formatter, | ||
| 'Factory must return IAuditLogFormatter instance for valid subject' | ||
| ); | ||
|
|
||
| $this->assertNotNull($formatter, 'Returned formatter must not be null'); | ||
| } |
Copilot
AI
Jan 8, 2026
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.
This assertion is redundant and unreachable. If formatter is null, the condition on line 348 would be true and the code would not reach this line. If formatter is not null, then the check on line 348 already ensured it's an instance of IAuditLogFormatter, making this assertion redundant.
| if ($formatter !== null) { | |
| $this->assertInstanceOf( | |
| IAuditLogFormatter::class, | |
| $formatter, | |
| 'Factory must return IAuditLogFormatter instance for valid subject' | |
| ); | |
| $this->assertNotNull($formatter, 'Returned formatter must not be null'); | |
| } | |
| $this->assertNotNull($formatter, 'Returned formatter must not be null'); | |
| $this->assertInstanceOf( | |
| IAuditLogFormatter::class, | |
| $formatter, | |
| 'Factory must return IAuditLogFormatter instance for valid subject' | |
| ); |
| throw new \Exception("Formatter class does not exist: {$formatterClass}"); | ||
| } | ||
|
|
||
| $reflection = new ReflectionClass($formatterClass); | ||
|
|
||
| try { | ||
| $formatter = $reflection->newInstance($eventType); | ||
| } catch (\Throwable $e) { | ||
| $formatter = $reflection->newInstance(); | ||
| } | ||
|
|
||
| if (!$formatter instanceof IAuditLogFormatter) { | ||
| throw new \Exception("Formatter must implement IAuditLogFormatter"); | ||
| } | ||
|
|
||
| return $formatter; | ||
| } catch (\ReflectionException $e) { | ||
| throw new \Exception("Failed to instantiate {$formatterClass}: " . $e->getMessage()); |
Copilot
AI
Jan 8, 2026
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.
The generic Exception class is being thrown instead of a more specific exception type. Consider using a specific exception type like InvalidArgumentException or RuntimeException to better indicate the nature of the error.
| throw new \Exception( | ||
| get_class($formatter) . " must have a setContext method" | ||
| ); | ||
| } |
Copilot
AI
Jan 8, 2026
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.
The generic Exception class is being thrown instead of a more specific exception type. Consider using a specific exception type like InvalidArgumentException or RuntimeException to better indicate the nature of the error.
| throw new \Exception( | ||
| get_class($formatter) . " must handle empty changesets gracefully: " . $e->getMessage() | ||
| ); |
Copilot
AI
Jan 8, 2026
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.
The generic Exception class is being thrown instead of a more specific exception type. Consider using a specific exception type like InvalidArgumentException or RuntimeException to better indicate the nature of the error.
| $this->config = config('audit_log', []); | ||
| try { | ||
| $this->config = config('audit_log', []); | ||
| } catch (\Exception $e) { |
Copilot
AI
Jan 8, 2026
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.
This catch block silently suppresses all exceptions when attempting to load the audit_log config. If the config system throws an exception, it could indicate a more serious configuration issue that should be logged or handled explicitly rather than silently defaulting to an empty array.
| } catch (\Exception $e) { | |
| } catch (\Exception $e) { | |
| Log::error('Failed to load audit_log configuration', ['exception' => $e]); |
| throw new \Exception("Cannot test abstract formatter: {$formatterClass}"); | ||
| } | ||
|
|
||
| $constructor = $reflection->getConstructor(); | ||
| if ($constructor === null) { | ||
| return; | ||
| } | ||
|
|
||
| try { | ||
| $reflection->newInstance(IAuditStrategy::EVENT_ENTITY_CREATION); | ||
| return; | ||
| } catch (\Throwable $e) { | ||
| try { | ||
| $reflection->newInstance(); | ||
| return; | ||
| } catch (\Throwable $e) { | ||
| $requiredParams = []; | ||
| foreach ($constructor->getParameters() as $param) { | ||
| if (!$param->isOptional() && !$param->allowsNull()) { | ||
| $requiredParams[] = $param->getName(); | ||
| } | ||
| } | ||
|
|
||
| if (!empty($requiredParams)) { | ||
| throw new \Exception( | ||
| "{$formatterClass} has required constructor parameters: " . | ||
| implode(', ', $requiredParams) . | ||
| ". These parameters must either have default values or be optionally injectable." | ||
| ); | ||
| } | ||
| throw $e; | ||
| } | ||
| } | ||
| } catch (\ReflectionException $e) { | ||
| throw new \Exception("Failed to validate constructor for {$formatterClass}: " . $e->getMessage()); | ||
| } |
Copilot
AI
Jan 8, 2026
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.
The generic Exception class is being thrown instead of a more specific exception type. Consider using a specific exception type like InvalidArgumentException or RuntimeException to better indicate the nature of the error.
| throw new \Exception( | ||
| get_class($formatter) . " must handle invalid subjects gracefully: " . $e->getMessage() | ||
| ); |
Copilot
AI
Jan 8, 2026
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.
The generic Exception class is being thrown instead of a more specific exception type. Consider using a specific exception type like InvalidArgumentException or RuntimeException to better indicate the nature of the error.
| throw new \Exception( | ||
| "{$formatterClass} must extend AbstractAuditLogFormatter" | ||
| ); | ||
| } | ||
| } catch (\ReflectionException $e) { | ||
| throw new \Exception("Failed to validate {$formatterClass}: " . $e->getMessage()); | ||
| } |
Copilot
AI
Jan 8, 2026
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.
The generic Exception class is being thrown instead of a more specific exception type. Consider using a specific exception type like InvalidArgumentException or RuntimeException to better indicate the nature of the error.
| throw new \Exception( | ||
| "{$formatterClass} must have a format() method" | ||
| ); | ||
| } | ||
|
|
||
| $method = $reflection->getMethod('format'); | ||
|
|
||
| if ($method->isAbstract()) { | ||
| throw new \Exception( | ||
| "{$formatterClass}::format() must not be abstract" | ||
| ); | ||
| } | ||
|
|
||
| $params = $method->getParameters(); | ||
| if (count($params) < 1) { | ||
| throw new \Exception( | ||
| "{$formatterClass}::format() must accept at least 1 parameter (subject)" | ||
| ); | ||
| } | ||
| } catch (\ReflectionException $e) { | ||
| throw new \Exception("Failed to validate format method for {$formatterClass}: " . $e->getMessage()); | ||
| } |
Copilot
AI
Jan 8, 2026
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.
The generic Exception class is being thrown instead of a more specific exception type. Consider using a specific exception type like InvalidArgumentException or RuntimeException to better indicate the nature of the error.
smarcet
left a comment
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.
LGTM
ref: https://app.clickup.com/t/86b80t46g