Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
7 changes: 2 additions & 5 deletions .github/workflows/tests-for-databases.yml
Original file line number Diff line number Diff line change
Expand Up @@ -204,9 +204,6 @@ jobs:
ports:
- 1433:1433

strategy:
fail-fast: true

name: SQL Server 2019

steps:
Expand All @@ -227,7 +224,7 @@ jobs:
curl https://packages.microsoft.com/config/ubuntu/22.04/prod.list | sudo tee /etc/apt/sources.list.d/msprod.list
sudo apt-get update
sudo ACCEPT_EULA=Y apt-get install -y msodbcsql18 mssql-tools18 unixodbc-dev
echo 'export PATH="$PATH:/opt/mssql-tools18/bin"' >> $GITHUB_ENV
echo "/opt/mssql-tools18/bin" >> $GITHUB_PATH

- name: Set Framework version
run: composer config version "12.x-dev"
Expand All @@ -239,7 +236,7 @@ jobs:
run: |
echo "Waiting for SQL Server to start..."
for i in {1..30}; do
/opt/mssql-tools18/bin/sqlcmd -S localhost -U SA -P Forge123 -Q "SELECT 1" && break
sqlcmd -S localhost -U SA -P Forge123 -Q "SELECT 1" -C && break
echo "SQL Server is starting up..."
sleep 2
done
Expand Down
28 changes: 28 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,34 @@

All notable changes to `model-required-fields` will be documented in this file.

## 3.1.5 - 2025-09-20

### What's Changed

* refactor tests and cache name

**Full Changelog**: https://github.com/WatheqAlshowaiter/model-fields/compare/3.1.4...3.1.5

## 3.1.4 - 2025-09-20

### What's Changed

* chore: fix SQL server cert issue in CI

**Full Changelog**: https://github.com/WatheqAlshowaiter/model-fields/compare/3.1.3...3.1.4

## 3.1.3 - 2025-09-20

Improve the test

**Full Changelog**: https://github.com/WatheqAlshowaiter/model-fields/compare/3.1.2...3.1.3

## 3.1.2 - 2025-09-20

* Add more tests

**Full Changelog**: https://github.com/WatheqAlshowaiter/model-fields/compare/3.1.1...3.1.2

## 3.1.1 - 2025-09-20

### What's Changed
Expand Down
4 changes: 3 additions & 1 deletion src/Console/ModelFieldsCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

class ModelFieldsCommand extends Command
{
const STAR_PROMPT_CACHE_KEY = 'model-fields.github_star_prompted';

protected $signature = 'model:fields
{model : The model class name (e.g., User, App\\Models\\Post)}
{--a|all : Get all fields}
Expand Down Expand Up @@ -208,7 +210,7 @@ private function askToStarRepository()
return;
}

$cacheKey = 'model-fields.banner_shown';
$cacheKey = self::STAR_PROMPT_CACHE_KEY;
$repo = 'https://github.com/WatheqAlshowaiter/model-fields';

if (Cache::get($cacheKey)) {
Expand Down
85 changes: 65 additions & 20 deletions tests/FieldsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,26 +60,6 @@ public static function requiredFields()

// but other methods works fine
$this->assertEquals(['created_at', 'updated_at'], $testModelClass::nullableFields());

}

/**
* @throws ReflectionException
*/
private function removeMacro(string $class, string $macro): void
{
if (! method_exists($class, 'hasMacro')) {
return;
}

$reflection = new ReflectionClass($class);
$property = $reflection->getProperty('macros');
$property->setAccessible(true);

$macros = $property->getValue();
unset($macros[$macro]);
$property->setValue($macros);
$property->setAccessible(false);
}

public function test_throw_exception_if_model_is_not_extends_of_eloquent_model()
Expand Down Expand Up @@ -111,6 +91,18 @@ public function test_throw_exception_if_use_get_older_versions_methods_before_us
Fields::primaryField();
}

public function test_facade_accepts_dynamic_string_class_names()
{
$modelClasses = [
"WatheqAlshowaiter\ModelFields\Tests\Models\Father", // with leading slash
"\WatheqAlshowaiter\ModelFields\Tests\Models\Mother", // without leading slash
];

foreach ($modelClasses as $modelClass) {
$this->assertEquals(['id'], Fields::model($modelClass)->primaryField());
}
}

public function test_all_fields_for_father_model()
{
$expected = [
Expand Down Expand Up @@ -193,6 +185,40 @@ public function test_required_fields_in_order()
], Father::requiredFieldsForOlderVersions());
}

public function test_required_fields_for_mother_model()
{
$expected = [
'uuid',
'ulid',
];
$this->assertEquals($expected, Fields::model(Mother::class)->requiredFields());
$this->assertEquals($expected, Fields::model(Mother::class)->requiredFieldsForOlderVersions());
$this->assertEquals($expected, Mother::requiredFields());
$this->assertEquals($expected, Mother::requiredFieldsForOlderVersions());
}

public function test_required_fields_for_son_model()
{
$expected = [
'father_id',
];
$this->assertEquals($expected, Fields::model(Son::class)->requiredFields());
$this->assertEquals($expected, Fields::model(Son::class)->requiredFieldsForOlderVersions());
$this->assertEquals($expected, Son::requiredFields());
$this->assertEquals($expected, Son::requiredFieldsForOlderVersions());
}

public function test_required_fields_for_brother_model()
{
$expected = [
'email',
];
$this->assertEquals($expected, Fields::model(Brother::class)->requiredFields());
$this->assertEquals($expected, Fields::model(Brother::class)->requiredFieldsForOlderVersions());
$this->assertEquals($expected, Brother::requiredFields());
$this->assertEquals($expected, Brother::requiredFieldsForOlderVersions());
}

public function test_nullable_fields_for_father_model()
{
$expected = [
Expand Down Expand Up @@ -347,4 +373,23 @@ public function test_default_fields_for_brother_model()
$this->assertEquals($expected, Fields::model(Brother::class)->defaultFields());
$this->assertEquals($expected, Brother::defaultFields());
}

/**
* @throws ReflectionException
*/
private function removeMacro(string $class, string $macro): void
{
if (! method_exists($class, 'hasMacro')) {
return;
}

$reflection = new ReflectionClass($class);
$property = $reflection->getProperty('macros');
$property->setAccessible(true);

$macros = $property->getValue();
unset($macros[$macro]);
$property->setValue($macros);
$property->setAccessible(false);
}
}
63 changes: 18 additions & 45 deletions tests/ModelFieldsCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
use Illuminate\Support\Facades\Cache;
use InvalidArgumentException;
use RuntimeException;
use Symfony\Component\Console\Command\Command;
use WatheqAlshowaiter\ModelFields\Console\ModelFieldsCommand;
use WatheqAlshowaiter\ModelFields\Tests\Models\Father;

Expand All @@ -18,6 +17,18 @@ class ModelFieldsCommandTest extends TestCase

public const FAILURE_EXIT_CODE = 1;

protected function setUp(): void
{
parent::setUp();
Cache::forever(ModelFieldsCommand::STAR_PROMPT_CACHE_KEY, true);
}

protected function tearDown(): void
{
Cache::forget(ModelFieldsCommand::STAR_PROMPT_CACHE_KEY);
parent::tearDown();
}

public function test_error_when_no_model_provided()
{
$this->expectException(RuntimeException::class);
Expand Down Expand Up @@ -55,12 +66,11 @@ public function test_failed_when_format_value_is_not_valid()

public function test_use_list_format_when_not_provided()
{
Cache::forever('model-fields.banner_shown', true);
Cache::forever(ModelFieldsCommand::STAR_PROMPT_CACHE_KEY, true);

$this->artisan('model:fields', ['model' => Father::class])
->assertExitCode(self::SUCCESS_EXIT_CODE);

Cache::forget('model-fields.banner_shown');
}

public function test_fail_when_provided_more_than_one_field_type()
Expand All @@ -76,8 +86,6 @@ public function test_fail_when_provided_more_than_one_field_type()

public function test_default_to_all_fields_when_no_type_specified()
{
Cache::forever('model-fields.banner_shown', true);

$this->artisan('model:fields', [
'model' => Father::class,
])
Expand All @@ -90,44 +98,32 @@ public function test_default_to_all_fields_when_no_type_specified()
->expectsOutput(' - created_at')
->expectsOutput(' - updated_at')
->expectsOutput(' - deleted_at');

Cache::forget('model-fields.banner_shown');
}

public function test_uses_type_when_specified()
{
Cache::forever('model-fields.banner_shown', true);

$this->artisan('model:fields', [
'model' => Father::class,
'--required' => true,
])
->expectsOutput('Father required fields:')
->expectsOutput(' - name')
->expectsOutput(' - email');

Cache::forget('model-fields.banner_shown');
}

public function test_uses_type_shortname_when_specified()
{
Cache::forever('model-fields.banner_shown', true);

$this->artisan('model:fields', [
'model' => Father::class,
'-r' => true,
])
->expectsOutput('Father required fields:')
->expectsOutput(' - name')
->expectsOutput(' - email');

Cache::forget('model-fields.banner_shown');
}

public function test_json_format()
{
Cache::forever('model-fields.banner_shown', true);

$this->artisan('model:fields', [
'model' => Father::class,
'--nullable' => true,
Expand All @@ -139,14 +135,10 @@ public function test_json_format()
"updated_at",
"deleted_at"
]');

Cache::forget('model-fields.banner_shown');
}

public function test_table_format()
{
Cache::forever('model-fields.banner_shown', true);

$this->artisan('model:fields', [
'model' => Father::class,
'--primary' => true,
Expand All @@ -157,78 +149,61 @@ public function test_table_format()
->expectsOutput('+-----------------------+')
->expectsOutput('| id |')
->expectsOutput('+-----------------------+');

Cache::forget('model-fields.banner_shown');
}

public function test_json_format_with_empty_results()
{
Cache::forever('model-fields.banner_shown', true);

$this->artisan('model:fields', [
'model' => Father::class,
'-A' => true,
'--format' => 'json',
])->expectsOutput('No app-default fields found for Father model.');

Cache::forget('model-fields.banner_shown');
}

public function test_table_format_with_empty_results()
{
Cache::forever('model-fields.banner_shown', true);

$this->artisan('model:fields', [
'model' => Father::class,
'-A' => true,
'--format' => 'table',
])->expectsOutput('No app-default fields found for Father model.');

Cache::forget('model-fields.banner_shown');
}

public function test_list_format_with_empty_results()
{
Cache::forever('model-fields.banner_shown', true);

$this->artisan('model:fields', [
'model' => Father::class,
'-A' => true,
])->expectsOutput('No app-default fields found for Father model.');

Cache::forget('model-fields.banner_shown');
}

public function test_does_not_ask_if_already_cached()
{
Cache::forever('model-fields.banner_shown', true);
Cache::forever(ModelFieldsCommand::STAR_PROMPT_CACHE_KEY, true); // make it clear here

$this->artisan('model:fields', [
'model' => Father::class,
])->assertExitCode(0);

$this->assertTrue(Cache::get('model-fields.banner_shown'));

Cache::forget('model-fields.banner_shown');
$this->assertTrue(Cache::get(ModelFieldsCommand::STAR_PROMPT_CACHE_KEY));
}

public function test_asks_and_user_declines()
{
Cache::forget('model-fields.banner_shown');
Cache::forget(ModelFieldsCommand::STAR_PROMPT_CACHE_KEY);

$this->artisan('model:fields', [
'model' => Father::class,
])
->expectsQuestion('🌟 Help other developers find this package by starring it on GitHub?', false)
->assertExitCode(self::SUCCESS_EXIT_CODE);

$this->assertTrue(Cache::get('model-fields.banner_shown'));
Cache::forget('model-fields.banner_shown');
$this->assertTrue(Cache::get(ModelFieldsCommand::STAR_PROMPT_CACHE_KEY));
}

public function test_asks_and_user_accepts()
{
Cache::forget('model-fields.banner_shown');
Cache::forget(ModelFieldsCommand::STAR_PROMPT_CACHE_KEY);

// Create a subclass that overrides openUrl()
$stubCommand = new class extends ModelFieldsCommand
Expand All @@ -254,7 +229,5 @@ protected function openUrl(string $url): void
->assertExitCode(self::SUCCESS_EXIT_CODE);

$this->assertStringContainsString('github.com/WatheqAlshowaiter/model-fields', $stubCommand->calledWith);

Cache::forget('model-fields.banner_shown');
}
}