set('dto.log.logger', NullLogger::class); }); it('uses NullLogger as fallback when logger config is null', function () { config()->set('dto.log.logger', null); $log = new Log(); expect($log->logger)->toBeInstanceOf(NullLogger::class); }); it('uses NullLogger as fallback when logger config is invalid', function () { config()->set('dto.log.logger', 'NonExistentLoggerClass'); $log = new Log(); expect($log->logger)->toBeInstanceOf(NullLogger::class); }); it('instantiates logger from class name via Laravel container', function () { config()->set('dto.log.logger', CustomLogger::class); $log = new Log(); expect($log->logger)->toBeInstanceOf(CustomLogger::class); }); it('uses logger object directly when provided', function () { $customLogger = new CustomLogger(); config()->set('dto.log.logger', $customLogger); $log = new Log(); expect($log->logger)->toBe($customLogger); }); it('invokes callable to get logger instance', function () { config()->set('dto.log.logger', function () { return new CustomLogger(); }); $log = new Log(); expect($log->logger)->toBeInstanceOf(CustomLogger::class); }); }); describe('log level configuration', function () { beforeEach(function () { $this->customLogger = new CustomLogger(); config()->set('dto.log.logger', $this->customLogger); }); afterEach(function () { config()->set('dto.log.logger', NullLogger::class); config()->set('dto.log.rules', LogLevel::DEBUG); config()->set('dto.log.input', LogLevel::DEBUG); config()->set('dto.log.raw_input', LogLevel::DEBUG); config()->set('dto.log.validation_errors', LogLevel::INFO); }); it('logs rules at configured level', function () { config()->set('dto.log.rules', LogLevel::INFO); $log = new Log(); $log->rules(['field' => ['required']]); expect($this->customLogger->hasLog(LogLevel::INFO, 'field'))->toBeTrue(); }); it('logs input at configured level', function () { config()->set('dto.log.input', LogLevel::INFO); $log = new Log(); $log->input(['field' => 'value']); expect($this->customLogger->hasLog(LogLevel::INFO, 'value'))->toBeTrue(); }); it('logs raw input at configured level', function () { config()->set('dto.log.raw_input', LogLevel::ERROR); $log = new Log(); $log->inputRaw(['field' => 'raw_value']); expect($this->customLogger->hasLog(LogLevel::ERROR, 'raw_value'))->toBeTrue(); }); it('logs validation errors at configured level', function () { config()->set('dto.log.validation_errors', LogLevel::ERROR); $log = new Log(); $log->validationErrors(['field' => ['The field is required.']]); expect($this->customLogger->hasLog(LogLevel::ERROR, 'required'))->toBeTrue(); }); it('allows different log levels for each log type', function () { config()->set('dto.log.rules', LogLevel::DEBUG); config()->set('dto.log.input', LogLevel::INFO); config()->set('dto.log.raw_input', LogLevel::INFO); $log = new Log(); $log->rules(['rules_field' => ['required']]); $log->input(['input_field' => 'value']); $log->inputRaw(['raw_field' => 'raw_value']); expect($this->customLogger->hasLog(LogLevel::DEBUG, 'rules_field'))->toBeTrue(); expect($this->customLogger->hasLog(LogLevel::INFO, 'input_field'))->toBeTrue(); expect($this->customLogger->hasLog(LogLevel::INFO, 'raw_field'))->toBeTrue(); }); it('defaults to DEBUG level when not configured', function () { config()->set('dto.log.rules', null); config()->set('dto.log.input', null); config()->set('dto.log.raw_input', null); $customLogger = new CustomLogger(); config()->set('dto.log.logger', $customLogger); $log = new Log(); $log->rules(['field' => ['required']]); $log->input(['field' => 'value']); $log->inputRaw(['field' => 'raw_value']); expect(count($customLogger->logs))->toBe(3); expect($customLogger->logs[0]['level'])->toBe(LogLevel::DEBUG); expect($customLogger->logs[1]['level'])->toBe(LogLevel::DEBUG); expect($customLogger->logs[2]['level'])->toBe(LogLevel::DEBUG); }); }); describe('integration with DataObject', function () { beforeEach(function () { $this->customLogger = new CustomLogger(); config()->set('dto.log.logger', $this->customLogger); config()->set('dto.log.rules', LogLevel::DEBUG); config()->set('dto.log.input', LogLevel::DEBUG); config()->set('dto.log.raw_input', LogLevel::DEBUG); }); afterEach(function () { config()->set('dto.log.logger', NullLogger::class); }); it('logs raw input during fromArray execution', function () { PrimitiveData::fromArray([ 'string' => 'test', 'int' => 42, 'float' => 3.14, 'bool' => true, ]); expect($this->customLogger->hasLog(LogLevel::DEBUG, 'raw_input'))->toBeFalse(); expect($this->customLogger->hasLog(LogLevel::DEBUG, 'string'))->toBeTrue(); expect($this->customLogger->hasLog(LogLevel::DEBUG, '42'))->toBeTrue(); }); it('logs rules during fromArray execution', function () { PrimitiveData::fromArray([ 'string' => 'test', 'int' => 42, 'float' => 3.14, 'bool' => true, ]); expect($this->customLogger->hasLog(LogLevel::DEBUG, 'required'))->toBeTrue(); }); it('logs processed input during fromArray execution', function () { PrimitiveData::fromArray([ 'string' => 'test', 'int' => 42, 'float' => 3.14, 'bool' => true, ]); expect($this->customLogger->hasLog(LogLevel::DEBUG, 'test'))->toBeTrue(); }); it('captures all three log types during successful fromArray', function () { PrimitiveData::fromArray([ 'string' => 'integration_test', 'int' => 123, 'float' => 9.99, 'bool' => false, ]); $rawInputLogged = false; $rulesLogged = false; $inputLogged = false; foreach ($this->customLogger->logs as $log) { if (str_contains($log['message'], 'string')) { $rawInputLogged = true; } if (str_contains($log['message'], 'required')) { $rulesLogged = true; } if (str_contains($log['message'], 'integration_test')) { $inputLogged = true; } } expect($rawInputLogged)->toBeTrue('Raw input should be logged'); expect($rulesLogged)->toBeTrue('Rules should be logged'); expect($inputLogged)->toBeTrue('Processed input should be logged'); }); it('logs even when validation fails', function () { try { PrimitiveData::fromArray([ 'int' => 42, 'float' => 3.14, 'bool' => true, ]); } catch (\Illuminate\Validation\ValidationException $e) { // Expected } expect($this->customLogger->hasLog(LogLevel::DEBUG, 'required'))->toBeTrue(); expect($this->customLogger->hasLog(LogLevel::DEBUG, '42'))->toBeTrue(); }); it('logs validation errors when validation fails', function () { config()->set('dto.log.validation_errors', LogLevel::ERROR); try { PrimitiveData::fromArray([ 'int' => 42, 'float' => 3.14, 'bool' => true, ]); } catch (\Illuminate\Validation\ValidationException $e) { // Expected } expect($this->customLogger->hasLog(LogLevel::ERROR, 'string'))->toBeTrue(); expect($this->customLogger->hasLog(LogLevel::ERROR, 'required'))->toBeTrue(); }); }); describe('logging with NullLogger', function () { it('does not throw when logging with NullLogger', function () { config()->set('dto.log.logger', NullLogger::class); $log = new Log(); expect(function () use ($log) { $log->rules(['field' => ['required']]); $log->input(['field' => 'value']); $log->inputRaw(['field' => 'raw_value']); })->not->toThrow(\Throwable::class); }); it('does not affect DataObject behavior when using NullLogger', function () { config()->set('dto.log.logger', NullLogger::class); $object = PrimitiveData::fromArray([ 'string' => 'test', 'int' => 42, 'float' => 3.14, 'bool' => true, ]); expect($object)->toBeInstanceOf(PrimitiveData::class); expect($object->string)->toBe('test'); }); });