Skip to content

Commit f974920

Browse files
authored
More tests (#469)
1 parent 3fb3f2a commit f974920

12 files changed

Lines changed: 674 additions & 3 deletions

File tree

composer.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,11 @@
3636
"ext-pdo": "*",
3737
"maglnet/composer-require-checker": "^4.7.1",
3838
"phpunit/phpunit": "^10.5.58",
39-
"rector/rector": "^2.1.7",
39+
"rector/rector": "^2.2.3",
4040
"roave/infection-static-analysis-plugin": "^1.35",
4141
"spatie/phpunit-watcher": "^1.24",
4242
"vimeo/psalm": "^5.26.1 || ^6.8.8",
43-
"vlucas/phpdotenv": "^5.6",
43+
"vlucas/phpdotenv": "^5.6.2",
4444
"yiisoft/aliases": "^2.0",
4545
"yiisoft/arrays": "^3.2",
4646
"yiisoft/cache": "^3.1",
@@ -53,7 +53,7 @@
5353
"yiisoft/event-dispatcher": "^1.1",
5454
"yiisoft/factory": "^1.3",
5555
"yiisoft/middleware-dispatcher": "^5.3",
56-
"yiisoft/test-support": "^3.0"
56+
"yiisoft/test-support": "^3.0.2"
5757
},
5858
"suggest": {
5959
"yiisoft/arrays": "For \\Yiisoft\\Arrays\\ArrayableInterface support",

tests/ActiveRecordTest.php

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,11 @@
3434
use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\Promotion;
3535
use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\Profile;
3636
use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\SetValueOnUpdateAr;
37+
use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\Article;
38+
use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\ArticleComment;
3739
use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\Type;
40+
use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\User;
41+
use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\UserProfile;
3842
use Yiisoft\ActiveRecord\Tests\Support\DbHelper;
3943
use Yiisoft\ActiveRecord\Tests\Support\ModelFactory;
4044
use Yiisoft\Db\Exception\Exception;
@@ -1511,4 +1515,99 @@ static function(object $event) {
15111515

15121516
$this->assertTrue($category->isDeleted);
15131517
}
1518+
1519+
public function testLinkViaRelationWithNewRecord(): void
1520+
{
1521+
$customer = new Customer();
1522+
$item = new Item();
1523+
1524+
$this->expectException(InvalidCallException::class);
1525+
$this->expectExceptionMessage(
1526+
'Unable to link models: the models being linked cannot be newly created.'
1527+
);
1528+
$customer->link('items2', $item);
1529+
}
1530+
1531+
public function testLinkViaTable(): void
1532+
{
1533+
$this->reloadFixtureAfterTest();
1534+
1535+
$customer = Customer::query()->findByPk(1);
1536+
$this->assertNotNull($customer);
1537+
1538+
$item = Item::query()->findByPk(3);
1539+
$this->assertNotNull($item);
1540+
1541+
$customer->link('orderItems', $item, ['quantity' => 5, 'subtotal' => 50.00]);
1542+
1543+
$orderItems = $customer->relation('orderItems');
1544+
$itemIds = ArArrayHelper::getColumn($orderItems, 'id');
1545+
1546+
$this->assertContains(3, $itemIds);
1547+
}
1548+
1549+
public function testLinkBothNewRecordsWithSharedPrimaryKey(): void
1550+
{
1551+
$user = new User();
1552+
$user->setId(100);
1553+
$user->setUsername('testuser');
1554+
1555+
$profile = new UserProfile();
1556+
$profile->setId(100);
1557+
$profile->setBio('Test bio');
1558+
1559+
$this->expectException(InvalidCallException::class);
1560+
$this->expectExceptionMessage(
1561+
'Unable to link models: at most one model can be newly created.'
1562+
);
1563+
1564+
$user->link('profile', $profile);
1565+
}
1566+
1567+
public function testLinkNewRecordToExistingWithSharedPrimaryKey(): void
1568+
{
1569+
$this->reloadFixtureAfterTest();
1570+
1571+
$user = User::query()->findByPk(1);
1572+
1573+
$profile = new UserProfile();
1574+
$profile->setBio('Bio for existing user');
1575+
1576+
$profile->link('user', $user);
1577+
1578+
$this->assertEquals(1, $profile->getId());
1579+
$this->assertFalse($profile->isNewRecord());
1580+
}
1581+
1582+
public function testLinkExistingRecordToNewWithSharedPrimaryKey(): void
1583+
{
1584+
$this->reloadFixtureAfterTest();
1585+
1586+
$user = User::query()->findByPk(1);
1587+
1588+
$profile = new UserProfile();
1589+
$profile->setBio('New profile for existing user');
1590+
1591+
$user->link('profile', $profile);
1592+
1593+
$this->assertEquals(1, $profile->getId());
1594+
$this->assertFalse($profile->isNewRecord());
1595+
}
1596+
1597+
public function testLinkWithNonPrimaryKeyFields(): void
1598+
{
1599+
$this->reloadFixtureAfterTest();
1600+
1601+
$article = Article::query()->findByPk(1);
1602+
$this->assertNotNull($article);
1603+
1604+
$comment = new ArticleComment();
1605+
$comment->setCommentText('Test comment');
1606+
1607+
$this->expectException(InvalidCallException::class);
1608+
$this->expectExceptionMessage(
1609+
'Unable to link models: the link defining the relation does not involve any primary key.'
1610+
);
1611+
$article->link('comments', $comment);
1612+
}
15141613
}

tests/MagicActiveRecordTest.php

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
use DateTimeImmutable;
88
use DateTimeZone;
99
use DivisionByZeroError;
10+
use PHPUnit\Framework\Attributes\DataProvider;
11+
use Yiisoft\ActiveRecord\ActiveQueryInterface;
1012
use Yiisoft\ActiveRecord\Tests\Stubs\MagicActiveRecord\Alpha;
1113
use Yiisoft\ActiveRecord\Tests\Stubs\MagicActiveRecord\Animal;
1214
use Yiisoft\ActiveRecord\Tests\Stubs\MagicActiveRecord\Cat;
@@ -849,4 +851,138 @@ public function testIsChanged(): void
849851

850852
$this->assertTrue($newItem->isChanged());
851853
}
854+
855+
public function testGettingWriteOnlyProperty(): void
856+
{
857+
$customer = new Customer();
858+
859+
$this->expectException(InvalidCallException::class);
860+
$this->expectExceptionMessage(
861+
'Getting write-only property: ' . Customer::class . '::ordersReadOnly'
862+
);
863+
$customer->ordersReadOnly;
864+
}
865+
866+
public function testUnsetPropertyWithDependentRelations(): void
867+
{
868+
$orderItem = new OrderItem();
869+
$orderItem->order_id = 1;
870+
$orderItem->item_id = 2;
871+
872+
$order = $orderItem->order;
873+
$this->assertNotNull($order);
874+
$this->assertEquals(1, $order->id);
875+
$this->assertTrue($orderItem->isRelationPopulated('order'));
876+
877+
unset($orderItem->order_id);
878+
879+
$this->assertFalse($orderItem->isRelationPopulated('order'));
880+
$this->assertNull($orderItem->order_id);
881+
}
882+
883+
public function testUnsetPopulatedRelation(): void
884+
{
885+
$customerQuery = Customer::query();
886+
$customer = $customerQuery->findByPk(1);
887+
888+
$orders = $customer->orders;
889+
$this->assertNotEmpty($orders);
890+
$this->assertTrue($customer->isRelationPopulated('orders'));
891+
892+
unset($customer->orders);
893+
894+
$this->assertFalse($customer->isRelationPopulated('orders'));
895+
}
896+
897+
public function testSettingUnknownProperty(): void
898+
{
899+
$customer = new Customer();
900+
901+
$this->expectException(UnknownPropertyException::class);
902+
$this->expectExceptionMessage(
903+
'Setting unknown property: ' . Customer::class . '::nonExistentProperty'
904+
);
905+
$customer->nonExistentProperty = 'value';
906+
}
907+
908+
public static function dataIsProperty(): array
909+
{
910+
return [
911+
'table column property' => [true, 'name'],
912+
'another table column' => [true, 'email'],
913+
'relation query' => [true, 'profile'],
914+
'setter only' => [true, 'ordersReadOnly'],
915+
'public class property' => [true, 'status2'],
916+
'another public class property' => [true, 'sumTotal'],
917+
'non-existent property' => [false, 'nonExistent'],
918+
];
919+
}
920+
921+
#[DataProvider('dataIsProperty')]
922+
public function testIsProperty(bool $expected, string $name): void
923+
{
924+
$customer = new Customer();
925+
926+
$this->assertSame($expected, $customer->isProperty($name));
927+
}
928+
929+
public static function dataIsPropertyWithoutCheckVars(): array
930+
{
931+
return [
932+
'table column property' => [true, 'name'],
933+
'another table column' => [true, 'email'],
934+
'relation query' => [true, 'profile'],
935+
'setter only' => [true, 'ordersReadOnly'],
936+
'public class property' => [false, 'status2'],
937+
'another public class property' => [false, 'sumTotal'],
938+
'non-existent property' => [false, 'nonExistent'],
939+
];
940+
}
941+
942+
#[DataProvider('dataIsPropertyWithoutCheckVars')]
943+
public function testIsPropertyWithoutCheckVars(bool $expected, string $name): void
944+
{
945+
$customer = new Customer();
946+
947+
$this->assertSame($expected, $customer->isProperty($name, false));
948+
}
949+
950+
public function testRelationQueryNonExistentRelation(): void
951+
{
952+
$customer = new Customer();
953+
954+
$this->expectException(InvalidArgumentException::class);
955+
$this->expectExceptionMessage(
956+
Customer::class . ' has no relation named "nonExistentRelation".'
957+
);
958+
$customer->relationQuery('nonExistentRelation');
959+
}
960+
961+
public function testRelationQueryInvalidReturnType(): void
962+
{
963+
$customer = new Customer();
964+
965+
$this->expectException(InvalidArgumentException::class);
966+
$this->expectExceptionMessage(
967+
'Relation query method "'
968+
. Customer::class
969+
. '::getItemQuery()" should return type "'
970+
. ActiveQueryInterface::class
971+
. '", but returns "void" type.'
972+
);
973+
$customer->relationQuery('item');
974+
}
975+
976+
public function testRelationQueryCaseSensitive(): void
977+
{
978+
$customer = new Customer();
979+
980+
$this->expectException(InvalidArgumentException::class);
981+
$this->expectExceptionMessage(
982+
'Relation names are case sensitive. '
983+
. Customer::class
984+
. ' has a relation named "profile" instead of "Profile".'
985+
);
986+
$customer->relationQuery('Profile');
987+
}
852988
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord;
6+
7+
use Yiisoft\ActiveRecord\ActiveQuery;
8+
use Yiisoft\ActiveRecord\ActiveQueryInterface;
9+
use Yiisoft\ActiveRecord\ActiveRecord;
10+
11+
final class Article extends ActiveRecord
12+
{
13+
protected int $id;
14+
protected string $title;
15+
protected string $slug;
16+
17+
public function tableName(): string
18+
{
19+
return 'article';
20+
}
21+
22+
public function getId(): int
23+
{
24+
return $this->id;
25+
}
26+
27+
public function setId(int $id): void
28+
{
29+
$this->id = $id;
30+
}
31+
32+
public function getTitle(): string
33+
{
34+
return $this->title;
35+
}
36+
37+
public function setTitle(string $title): void
38+
{
39+
$this->title = $title;
40+
}
41+
42+
public function getSlug(): string
43+
{
44+
return $this->slug;
45+
}
46+
47+
public function setSlug(string $slug): void
48+
{
49+
$this->slug = $slug;
50+
}
51+
52+
public function relationQuery(string $name): ActiveQueryInterface
53+
{
54+
return match ($name) {
55+
'comments' => $this->getCommentsQuery(),
56+
default => parent::relationQuery($name),
57+
};
58+
}
59+
60+
public function getComments(): array
61+
{
62+
return $this->relation('comments');
63+
}
64+
65+
public function getCommentsQuery(): ActiveQuery
66+
{
67+
return $this->hasMany(ArticleComment::class, ['article_slug' => 'slug']);
68+
}
69+
}

0 commit comments

Comments
 (0)