256 lines
7.3 KiB
PHP
256 lines
7.3 KiB
PHP
<?php
|
|
|
|
namespace Tests\Unit\ActivityLog;
|
|
|
|
use App\ActivityLog\ChangeDetector;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Tests\TestCase;
|
|
|
|
/**
|
|
* ChangeDetector 테스트
|
|
*
|
|
* 모델의 $activityLogFields 메타데이터와 스냅샷을 비교하여
|
|
* 구조화된 변경 이력을 올바르게 생성하는지 검증합니다.
|
|
*/
|
|
class ChangeDetectorTest extends TestCase
|
|
{
|
|
/**
|
|
* 변경된 필드를 올바르게 감지하는지 확인
|
|
*/
|
|
public function test_detects_changed_fields(): void
|
|
{
|
|
$model = new ChangeDetectorTestModel;
|
|
$model->name = '변경 후';
|
|
$model->email = 'new@example.com';
|
|
|
|
$snapshot = [
|
|
'name' => '변경 전',
|
|
'email' => 'old@example.com',
|
|
];
|
|
|
|
$changes = ChangeDetector::detect($model, $snapshot);
|
|
|
|
$this->assertNotNull($changes);
|
|
$this->assertCount(2, $changes);
|
|
|
|
// name 필드 변경 확인
|
|
$nameChange = collect($changes)->firstWhere('field', 'name');
|
|
$this->assertEquals('model.name', $nameChange['label_key']);
|
|
$this->assertEquals('변경 전', $nameChange['old']);
|
|
$this->assertEquals('변경 후', $nameChange['new']);
|
|
$this->assertEquals('text', $nameChange['type']);
|
|
|
|
// email 필드 변경 확인
|
|
$emailChange = collect($changes)->firstWhere('field', 'email');
|
|
$this->assertEquals('model.email', $emailChange['label_key']);
|
|
$this->assertEquals('old@example.com', $emailChange['old']);
|
|
$this->assertEquals('new@example.com', $emailChange['new']);
|
|
}
|
|
|
|
/**
|
|
* 변경이 없으면 null을 반환하는지 확인
|
|
*/
|
|
public function test_returns_null_when_no_changes(): void
|
|
{
|
|
$model = new ChangeDetectorTestModel;
|
|
$model->name = '동일값';
|
|
$model->email = 'same@example.com';
|
|
|
|
$snapshot = [
|
|
'name' => '동일값',
|
|
'email' => 'same@example.com',
|
|
];
|
|
|
|
$changes = ChangeDetector::detect($model, $snapshot);
|
|
|
|
$this->assertNull($changes);
|
|
}
|
|
|
|
/**
|
|
* snapshot이 null이면 null을 반환하는지 확인
|
|
*/
|
|
public function test_returns_null_when_snapshot_is_null(): void
|
|
{
|
|
$model = new ChangeDetectorTestModel;
|
|
$model->name = 'test';
|
|
|
|
$changes = ChangeDetector::detect($model, null);
|
|
|
|
$this->assertNull($changes);
|
|
}
|
|
|
|
/**
|
|
* enum 타입 필드의 old_label_key와 new_label_key가 포함되는지 확인
|
|
*/
|
|
public function test_handles_enum_type_with_label_keys(): void
|
|
{
|
|
$model = new ChangeDetectorEnumTestModel;
|
|
$model->status = ChangeDetectorTestStatus::Active;
|
|
|
|
$snapshot = [
|
|
'status' => 'inactive',
|
|
];
|
|
|
|
$changes = ChangeDetector::detect($model, $snapshot);
|
|
|
|
$this->assertNotNull($changes);
|
|
$this->assertCount(1, $changes);
|
|
|
|
$statusChange = $changes[0];
|
|
$this->assertEquals('status', $statusChange['field']);
|
|
$this->assertEquals('model.status', $statusChange['label_key']);
|
|
$this->assertEquals('enum', $statusChange['type']);
|
|
$this->assertEquals('inactive', $statusChange['old']);
|
|
$this->assertEquals('active', $statusChange['new']);
|
|
$this->assertEquals('test.status.inactive', $statusChange['old_label_key']);
|
|
$this->assertEquals('test.status.active', $statusChange['new_label_key']);
|
|
}
|
|
|
|
/**
|
|
* enum 타입 필드의 old가 null이면 old_label_key도 null인지 확인
|
|
*/
|
|
public function test_enum_null_old_value_returns_null_label_key(): void
|
|
{
|
|
$model = new ChangeDetectorEnumTestModel;
|
|
$model->status = ChangeDetectorTestStatus::Active;
|
|
|
|
$snapshot = [
|
|
'status' => null,
|
|
];
|
|
|
|
$changes = ChangeDetector::detect($model, $snapshot);
|
|
|
|
$this->assertNotNull($changes);
|
|
$statusChange = $changes[0];
|
|
$this->assertNull($statusChange['old_label_key']);
|
|
$this->assertEquals('test.status.active', $statusChange['new_label_key']);
|
|
}
|
|
|
|
/**
|
|
* $activityLogFields에 없는 필드는 무시되는지 확인
|
|
*/
|
|
public function test_ignores_untracked_fields(): void
|
|
{
|
|
$model = new ChangeDetectorTestModel;
|
|
$model->name = '변경됨';
|
|
$model->email = 'same@example.com';
|
|
$model->untracked_field = '추적 안 됨';
|
|
|
|
$snapshot = [
|
|
'name' => '원본',
|
|
'email' => 'same@example.com',
|
|
'untracked_field' => '이전값',
|
|
];
|
|
|
|
$changes = ChangeDetector::detect($model, $snapshot);
|
|
|
|
$this->assertNotNull($changes);
|
|
$this->assertCount(1, $changes);
|
|
$this->assertEquals('name', $changes[0]['field']);
|
|
}
|
|
|
|
/**
|
|
* $activityLogFields가 없는 모델은 null을 반환하는지 확인
|
|
*/
|
|
public function test_returns_null_when_model_has_no_activity_log_fields(): void
|
|
{
|
|
$model = new ChangeDetectorNoFieldsModel;
|
|
$model->name = 'test';
|
|
|
|
$snapshot = ['name' => 'old'];
|
|
|
|
$changes = ChangeDetector::detect($model, $snapshot);
|
|
|
|
$this->assertNull($changes);
|
|
}
|
|
|
|
/**
|
|
* BackedEnum 객체가 스냅샷에 있을 때 값으로 변환하여 비교하는지 확인
|
|
*/
|
|
public function test_converts_backed_enum_in_snapshot_to_value(): void
|
|
{
|
|
$model = new ChangeDetectorEnumTestModel;
|
|
$model->status = ChangeDetectorTestStatus::Active;
|
|
|
|
// 스냅샷에 Enum 객체가 들어있는 경우 (toArray() 시 발생 가능)
|
|
$snapshot = [
|
|
'status' => ChangeDetectorTestStatus::Inactive,
|
|
];
|
|
|
|
$changes = ChangeDetector::detect($model, $snapshot);
|
|
|
|
$this->assertNotNull($changes);
|
|
$this->assertEquals('inactive', $changes[0]['old']);
|
|
$this->assertEquals('active', $changes[0]['new']);
|
|
}
|
|
}
|
|
|
|
// ============================================================
|
|
// 테스트용 모델 및 Enum 정의
|
|
// ============================================================
|
|
|
|
/**
|
|
* 테스트용 모델 (text 필드만)
|
|
*/
|
|
class ChangeDetectorTestModel extends Model
|
|
{
|
|
/** @var array<string, array> */
|
|
public static array $activityLogFields = [
|
|
'name' => ['label_key' => 'model.name', 'type' => 'text'],
|
|
'email' => ['label_key' => 'model.email', 'type' => 'text'],
|
|
];
|
|
|
|
protected $guarded = [];
|
|
}
|
|
|
|
/**
|
|
* 테스트용 모델 (enum 필드 포함)
|
|
*/
|
|
class ChangeDetectorEnumTestModel extends Model
|
|
{
|
|
/** @var array<string, array> */
|
|
public static array $activityLogFields = [
|
|
'status' => [
|
|
'label_key' => 'model.status',
|
|
'type' => 'enum',
|
|
'enum' => ChangeDetectorTestStatus::class,
|
|
],
|
|
];
|
|
|
|
protected $guarded = [];
|
|
|
|
protected function casts(): array
|
|
{
|
|
return [
|
|
'status' => ChangeDetectorTestStatus::class,
|
|
];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 테스트용 모델 ($activityLogFields 없음)
|
|
*/
|
|
class ChangeDetectorNoFieldsModel extends Model
|
|
{
|
|
protected $guarded = [];
|
|
}
|
|
|
|
/**
|
|
* 테스트용 Enum (labelKey() 메서드 포함)
|
|
*/
|
|
enum ChangeDetectorTestStatus: string
|
|
{
|
|
case Active = 'active';
|
|
case Inactive = 'inactive';
|
|
|
|
/**
|
|
* 다국어 라벨 키를 반환합니다.
|
|
*
|
|
* @return string
|
|
*/
|
|
public function labelKey(): string
|
|
{
|
|
return 'test.status.'.$this->value;
|
|
}
|
|
}
|