Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion src/Testing/TableTestState.php
Original file line number Diff line number Diff line change
Expand Up @@ -89,14 +89,24 @@ protected function prepareChanges(array $changes): array
return array_map(function ($item) use ($jsonFields) {
foreach ($jsonFields as $jsonField) {
if (Arr::has($item, $jsonField)) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (Arr::has($item, $jsonField)) {
if (Arr::has($item, $jsonField) && !is_null($item[$jsonField])) {

$item[$jsonField] = json_decode($item[$jsonField], true);
$value = $item[$jsonField];

$item[$jsonField] = $this->isBinary($value)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$item[$jsonField] = $this->isBinary($value)
$item[$jsonField] = ($this->isBinary($value))

? bin2hex($value)
: json_decode($value, true);
}
}

return $item;
}, $changes);
}

protected function isBinary(?string $data): bool
{
return !is_null($data)
&& !mb_check_encoding($data, 'UTF-8');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return !is_null($data)
&& !mb_check_encoding($data, 'UTF-8');
return !mb_check_encoding($data, 'UTF-8');

}

protected function getFixturePath(string $fixtureName): string
{
$testClassTrace = Arr::first(debug_backtrace(), fn ($trace) => str_ends_with($trace['file'], 'Test.php'));
Expand Down
4 changes: 2 additions & 2 deletions tests/BaseRequestTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public function testGetOrderableFields()
{
$result = $this->callEncapsulatedMethod(new BaseRequest(), 'getOrderableFields', TestModel::class);

$expectedResult = 'id,name,json_field,castable_field,*,created_at,updated_at';
$expectedResult = 'id,name,json_field,castable_field,binary_field,*,created_at,updated_at';

$this->assertEquals($expectedResult, $result);
}
Expand All @@ -28,7 +28,7 @@ public function testGetOrderableFieldsWithAdditionalFields()

$result = $this->callEncapsulatedMethod(new BaseRequest(), 'getOrderableFields', ...$args);

$expectedResult = 'id,name,json_field,castable_field,*,created_at,updated_at,additional_field_1,additional_field_2';
$expectedResult = 'id,name,json_field,castable_field,binary_field,*,created_at,updated_at,additional_field_1,additional_field_2';

$this->assertEquals($expectedResult, $result);
}
Expand Down
36 changes: 35 additions & 1 deletion tests/ModelTestStateTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public function testInitialization()
$jsonFields = $this->getProtectedProperty($reflectionClass, 'jsonFields', $modelTestState);
$state = $this->getProtectedProperty($reflectionClass, 'state', $modelTestState);

$this->assertEquals(['json_field', 'castable_field'], $jsonFields);
$this->assertEquals(['json_field', 'castable_field', 'binary_field'], $jsonFields);
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current implementation treats ALL fields with custom CastsAttributes implementations as JSON fields (including BinaryCast). This is problematic because not all custom casts represent JSON data. The fix in line 93 of TableTestState.php works around this by adding json_validate() checks, but this is a band-aid solution.

A more robust approach would be to:

  1. Introduce a marker interface (e.g., JsonCast) that JSON-based custom casts can implement
  2. Update isJsonCast() to only return true for casts that implement this interface or are the 'array' type
  3. This would prevent binary fields from being incorrectly treated as JSON fields in the first place

The current workaround works but may cause confusion and could lead to similar issues with other non-JSON custom casts in the future.

Suggested change
$this->assertEquals(['json_field', 'castable_field', 'binary_field'], $jsonFields);
$this->assertEquals(['json_field', 'castable_field'], $jsonFields);

Copilot uses AI. Check for mistakes.
$this->assertEquals($originRecords, $state);
}

Expand Down Expand Up @@ -88,6 +88,40 @@ public function testAssertChangesWithoutJsonFields()
$modelTestState->assertChangesEqualsFixture('assertion_fixture_without_json_fields.json');
}

public function testAssertChangesBinaryString()
{
$initialDatasetMock = collect([[
'id' => 1,
'binary_field' => null,
]]);
$changedDatasetMock = collect([[
'id' => 1,
'binary_field' => md5('some_string', true),
]]);

$this->mockGettingDatasetForChanges($changedDatasetMock, $initialDatasetMock, 'test_models');

$modelTestState = new ModelTestState(TestModel::class);
$modelTestState->assertChangesEqualsFixture('assert_changes_binary_string.json');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$modelTestState->assertChangesEqualsFixture('assert_changes_binary_string.json');
$modelTestState->assertChangesEqualsFixture('null_to_binary_string_changes');

}

public function testAssertChangesBinaryNullable()
{
$initialDatasetMock = collect([[
'id' => 1,
'binary_field' => md5('some_string', true),
]]);
$changedDatasetMock = collect([[
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$changedDatasetMock = collect([[
$changedDatasetMock = collect([[

'id' => 1,
'binary_field' => null,
]]);

$this->mockGettingDatasetForChanges($changedDatasetMock, $initialDatasetMock, 'test_models');

$modelTestState = new ModelTestState(TestModel::class);
$modelTestState->assertChangesEqualsFixture('assert_changes_binary_string_to_null.json');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$modelTestState->assertChangesEqualsFixture('assert_changes_binary_string_to_null.json');
$modelTestState->assertChangesEqualsFixture('binary_string_to_null_changes');

}

public function testAssertNoChanges()
{
$datasetMock = collect($this->getJsonFixture('get_without_changes/dataset.json'));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"updated": [
{
"id": 1,
"binary_field": "31ee76261d87fed8cb9d4c465c48158c"
}
],
"created": [],
"deleted": []
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"updated": [
{
"id": 1,
"binary_field": null
}
],
"created": [],
"deleted": []
}
18 changes: 18 additions & 0 deletions tests/support/Mock/Casts/BinaryCast.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace RonasIT\Support\Tests\Support\Mock\Casts;

use Illuminate\Contracts\Database\Eloquent\CastsAttributes;

class BinaryCast implements CastsAttributes
{
public function get($model, $key, $value, $attributes)
{
return is_null($value) ? $value : bin2hex($value);
}

public function set($model, $key, $value, $attributes)
{
return is_null($value) ? $value : md5($value, true);
}
}
3 changes: 3 additions & 0 deletions tests/support/Mock/Models/TestModel.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use RonasIT\Support\Tests\Support\Mock\Casts\BinaryCast;
use RonasIT\Support\Tests\Support\Mock\Casts\JSONCustomCast;
use RonasIT\Support\Traits\ModelTrait;

Expand All @@ -17,11 +18,13 @@ class TestModel extends Model
'name',
'json_field',
'castable_field',
'binary_field',
];

protected $casts = [
'json_field' => 'array',
'castable_field' => JSONCustomCast::class,
'binary_field' => BinaryCast::class,
];

public function relation(): HasMany
Expand Down