|
9 | 9 | use DateTimeImmutable; |
10 | 10 | use Tempest\Database\BelongsTo; |
11 | 11 | use Tempest\Database\DatabaseMigration; |
| 12 | +use Tempest\Database\Exceptions\DeleteStatementWasInvalid; |
12 | 13 | use Tempest\Database\Exceptions\RelationWasMissing; |
13 | 14 | use Tempest\Database\Exceptions\ValueWasMissing; |
14 | 15 | use Tempest\Database\HasMany; |
@@ -499,196 +500,115 @@ public function test_delete(): void |
499 | 500 | $this->assertNotNull(Foo::get($bar->id)); |
500 | 501 | } |
501 | 502 |
|
502 | | - public function test_property_with_carbon_type(): void |
| 503 | + public function test_delete_via_model_class_with_where_conditions(): void |
503 | 504 | { |
504 | 505 | $this->migrate( |
505 | 506 | CreateMigrationsTable::class, |
506 | | - CreateCarbonModelTable::class, |
| 507 | + FooDatabaseMigration::class, |
507 | 508 | ); |
508 | 509 |
|
509 | | - $this->container->get(CasterFactory::class)->addCaster(Carbon::class, CarbonCaster::class); |
510 | | - $this->container->get(SerializerFactory::class)->addSerializer(Carbon::class, CarbonSerializer::class); |
511 | | - |
512 | | - new CarbonModel(createdAt: new Carbon('2024-01-01'))->save(); |
| 510 | + $foo1 = Foo::create(bar: 'delete_me'); |
| 511 | + $foo2 = Foo::create(bar: 'keep_me'); |
| 512 | + $foo3 = Foo::create(bar: 'delete_me'); |
513 | 513 |
|
514 | | - $model = CarbonModel::select()->first(); |
| 514 | + query(Foo::class) |
| 515 | + ->delete() |
| 516 | + ->where('bar', 'delete_me') |
| 517 | + ->execute(); |
515 | 518 |
|
516 | | - $this->assertTrue($model->createdAt->equalTo(new Carbon('2024-01-01'))); |
| 519 | + $this->assertNull(Foo::get($foo1->id)); |
| 520 | + $this->assertNotNull(Foo::get($foo2->id)); |
| 521 | + $this->assertNull(Foo::get($foo3->id)); |
517 | 522 | } |
518 | 523 |
|
519 | | - public function test_two_way_casters_on_models(): void |
| 524 | + public function test_delete_via_model_instance_with_primary_key(): void |
520 | 525 | { |
521 | 526 | $this->migrate( |
522 | 527 | CreateMigrationsTable::class, |
523 | | - CreateCasterModelTable::class, |
| 528 | + FooDatabaseMigration::class, |
524 | 529 | ); |
525 | 530 |
|
526 | | - new CasterModel( |
527 | | - date: new DateTimeImmutable('2025-01-01 00:00:00'), |
528 | | - array_prop: ['a', 'b', 'c'], |
529 | | - enum_prop: CasterEnum::BAR, |
530 | | - )->save(); |
| 531 | + $foo1 = Foo::create(bar: 'first'); |
| 532 | + $foo2 = Foo::create(bar: 'second'); |
| 533 | + $foo1->delete(); |
531 | 534 |
|
532 | | - $model = CasterModel::select()->first(); |
533 | | - |
534 | | - $this->assertSame(new DateTimeImmutable('2025-01-01 00:00:00')->format('c'), $model->date->format('c')); |
535 | | - $this->assertSame(['a', 'b', 'c'], $model->array_prop); |
536 | | - $this->assertSame(CasterEnum::BAR, $model->enum_prop); |
| 535 | + $this->assertNull(Foo::get($foo1->id)); |
| 536 | + $this->assertNotNull(Foo::get($foo2->id)); |
| 537 | + $this->assertSame('second', Foo::get($foo2->id)->bar); |
537 | 538 | } |
538 | 539 |
|
539 | | - public function test_find(): void |
| 540 | + public function test_delete_via_model_instance_without_primary_key(): void |
540 | 541 | { |
541 | 542 | $this->migrate( |
542 | 543 | CreateMigrationsTable::class, |
543 | | - CreateATable::class, |
544 | | - CreateBTable::class, |
545 | | - CreateCTable::class, |
546 | | - ); |
547 | | - |
548 | | - new C(name: 'one')->save(); |
549 | | - new C(name: 'two')->save(); |
550 | | - |
551 | | - /** @var C[] */ |
552 | | - $valid = C::find(name: 'one')->all(); |
553 | | - |
554 | | - $this->assertCount(1, $valid); |
555 | | - $this->assertSame($valid[0]->name, 'one'); |
556 | | - |
557 | | - $invalid = C::find(name: 'three')->all(); |
558 | | - |
559 | | - $this->assertCount(0, $invalid); |
560 | | - } |
561 | | - |
562 | | - public function test_table_name_overrides(): void |
563 | | - { |
564 | | - $this->assertEquals('base_models', inspect(BaseModel::class)->getTableDefinition()->name); |
565 | | - $this->assertEquals('custom_attribute_table_name', inspect(AttributeTableNameModel::class)->getTableDefinition()->name); |
566 | | - $this->assertEquals('custom_static_method_table_name', inspect(StaticMethodTableNameModel::class)->getTableDefinition()->name); |
567 | | - } |
568 | | - |
569 | | - public function test_validation_on_create(): void |
570 | | - { |
571 | | - $this->expectException(ValidationFailed::class); |
572 | | - |
573 | | - ModelWithValidation::create( |
574 | | - index: -1, |
575 | | - ); |
576 | | - } |
577 | | - |
578 | | - public function test_validation_on_update(): void |
579 | | - { |
580 | | - $model = ModelWithValidation::new( |
581 | | - id: new PrimaryKey(1), |
582 | | - index: 1, |
583 | | - ); |
584 | | - |
585 | | - $this->expectException(ValidationFailed::class); |
586 | | - |
587 | | - $model->update( |
588 | | - index: -1, |
| 544 | + CreateModelWithoutPrimaryKeyMigration::class, |
589 | 545 | ); |
590 | | - } |
591 | | - |
592 | | - public function test_validation_on_new(): void |
593 | | - { |
594 | | - $model = ModelWithValidation::new( |
595 | | - index: 1, |
596 | | - ); |
597 | | - |
598 | | - $model->index = -1; |
599 | | - |
600 | | - $this->expectException(ValidationFailed::class); |
601 | 546 |
|
| 547 | + $model = new ModelWithoutPrimaryKey(name: 'Frieren', description: 'Elf mage'); |
602 | 548 | $model->save(); |
603 | | - } |
604 | 549 |
|
605 | | - public function test_skipped_validation(): void |
606 | | - { |
607 | | - try { |
608 | | - inspect(ModelWithValidation::class)->validate( |
609 | | - index: -1, |
610 | | - skip: -1, |
611 | | - ); |
612 | | - } catch (ValidationFailed $validationFailed) { |
613 | | - $this->assertStringContainsString(ModelWithValidation::class, $validationFailed->getMessage()); |
614 | | - $this->assertStringContainsString(ModelWithValidation::class, $validationFailed->subject); |
615 | | - $this->assertStringContainsString('index', array_key_first($validationFailed->failingRules)); |
616 | | - $this->assertInstanceOf(IsBetween::class, Arr\first($validationFailed->failingRules)[0]); |
617 | | - } |
| 550 | + $this->expectException(DeleteStatementWasInvalid::class); |
| 551 | + $model->delete(); |
618 | 552 | } |
619 | 553 |
|
620 | | - public function test_date_field(): void |
| 554 | + public function test_delete_via_model_class_without_primary_key(): void |
621 | 555 | { |
622 | 556 | $this->migrate( |
623 | 557 | CreateMigrationsTable::class, |
624 | | - CreateDateTimeModelTable::class, |
| 558 | + CreateModelWithoutPrimaryKeyMigration::class, |
625 | 559 | ); |
626 | 560 |
|
627 | | - $id = query(DateTimeModel::class) |
628 | | - ->insert([ |
629 | | - 'phpDateTime' => new NativeDateTime('2024-01-01 00:00:00'), |
630 | | - 'tempestDateTime' => DateTime::parse('2024-01-01 00:00:00'), |
631 | | - ]) |
| 561 | + query(ModelWithoutPrimaryKey::class)->create(name: 'Himmel', description: 'Hero'); |
| 562 | + query(ModelWithoutPrimaryKey::class)->create(name: 'Heiter', description: 'Priest'); |
| 563 | + query(ModelWithoutPrimaryKey::class)->create(name: 'Eisen', description: 'Warrior'); |
| 564 | + |
| 565 | + $this->assertCount(3, query(ModelWithoutPrimaryKey::class)->select()->all()); |
| 566 | + |
| 567 | + query(ModelWithoutPrimaryKey::class) |
| 568 | + ->delete() |
| 569 | + ->where('name', 'Himmel') |
632 | 570 | ->execute(); |
633 | 571 |
|
634 | | - /** @var DateTimeModel $model */ |
635 | | - $model = query(DateTimeModel::class)->select()->where('id', $id)->first(); |
| 572 | + $remaining = query(ModelWithoutPrimaryKey::class)->select()->all(); |
| 573 | + $this->assertCount(2, $remaining); |
636 | 574 |
|
637 | | - $this->assertSame('2024-01-01 00:00:00', $model->phpDateTime->format('Y-m-d H:i:s')); |
638 | | - $this->assertSame('2024-01-01 00:00:00', $model->tempestDateTime->format('yyyy-MM-dd HH:mm:ss')); |
| 575 | + $names = array_map(fn (ModelWithoutPrimaryKey $model) => $model->name, $remaining); |
| 576 | + $this->assertContains('Heiter', $names); |
| 577 | + $this->assertContains('Eisen', $names); |
| 578 | + $this->assertNotContains('Himmel', $names); |
639 | 579 | } |
640 | 580 |
|
641 | | - public function test_model_create_with_has_many_relations(): void |
| 581 | + public function test_delete_with_uninitialized_primary_key(): void |
642 | 582 | { |
643 | 583 | $this->migrate( |
644 | 584 | CreateMigrationsTable::class, |
645 | | - CreateTestUserMigration::class, |
646 | | - CreateTestPostMigration::class, |
647 | | - ); |
648 | | - |
649 | | - $user = TestUser::create( |
650 | | - name: 'Jon', |
651 | | - posts: [ |
652 | | - new TestPost('hello', 'world'), |
653 | | - new TestPost('foo', 'bar'), |
654 | | - ], |
| 585 | + FooDatabaseMigration::class, |
655 | 586 | ); |
656 | 587 |
|
657 | | - $this->assertSame('Jon', $user->name); |
658 | | - $this->assertInstanceOf(PrimaryKey::class, $user->id); |
659 | | - |
660 | | - $posts = TestPost::select() |
661 | | - ->where('test_user_id', $user->id->value) |
662 | | - ->all(); |
| 588 | + $foo = new Foo(); |
| 589 | + $foo->bar = 'unsaved'; |
663 | 590 |
|
664 | | - $this->assertCount(2, $posts); |
665 | | - $this->assertSame('hello', $posts[0]->title); |
666 | | - $this->assertSame('world', $posts[0]->body); |
667 | | - $this->assertSame('foo', $posts[1]->title); |
668 | | - $this->assertSame('bar', $posts[1]->body); |
| 591 | + $this->expectException(DeleteStatementWasInvalid::class); |
| 592 | + $foo->delete(); |
669 | 593 | } |
670 | 594 |
|
671 | | - public function test_model_update_with_only_relations(): void |
| 595 | + public function test_delete_nonexistent_record(): void |
672 | 596 | { |
673 | 597 | $this->migrate( |
674 | 598 | CreateMigrationsTable::class, |
675 | | - CreateTestUserMigration::class, |
676 | | - CreateTestPostMigration::class, |
| 599 | + FooDatabaseMigration::class, |
677 | 600 | ); |
678 | 601 |
|
679 | | - $user = TestUser::create(name: 'Frieren'); |
680 | | - $user->update(posts: [ |
681 | | - new TestPost('hello', 'world'), |
682 | | - ]); |
| 602 | + $foo = Foo::create(bar: 'test'); |
| 603 | + $fooId = $foo->id; |
683 | 604 |
|
684 | | - $posts = TestPost::select() |
685 | | - ->where('test_user_id', $user->id->value) |
686 | | - ->all(); |
| 605 | + // Delete the record |
| 606 | + $foo->delete(); |
687 | 607 |
|
688 | | - $this->assertCount(1, $posts); |
689 | | - $this->assertSame('hello', $posts[0]->title); |
690 | | - $this->assertSame('world', $posts[0]->body); |
691 | | - $this->assertSame('Frieren', $user->name); // Ensure name wasn't changed |
| 608 | + // Delete again |
| 609 | + $foo->delete(); |
| 610 | + |
| 611 | + $this->assertNull(Foo::get($fooId)); |
692 | 612 | } |
693 | 613 | } |
694 | 614 |
|
@@ -1100,3 +1020,30 @@ public function down(): ?QueryStatement |
1100 | 1020 | return null; |
1101 | 1021 | } |
1102 | 1022 | } |
| 1023 | + |
| 1024 | +final class ModelWithoutPrimaryKey |
| 1025 | +{ |
| 1026 | + use IsDatabaseModel; |
| 1027 | + |
| 1028 | + public function __construct( |
| 1029 | + public string $name, |
| 1030 | + public string $description, |
| 1031 | + ) {} |
| 1032 | +} |
| 1033 | + |
| 1034 | +final class CreateModelWithoutPrimaryKeyMigration implements DatabaseMigration |
| 1035 | +{ |
| 1036 | + private(set) string $name = '100-create-model-without-primary-key'; |
| 1037 | + |
| 1038 | + public function up(): QueryStatement |
| 1039 | + { |
| 1040 | + return new CreateTableStatement('model_without_primary_keys') |
| 1041 | + ->text('name') |
| 1042 | + ->text('description'); |
| 1043 | + } |
| 1044 | + |
| 1045 | + public function down(): ?QueryStatement |
| 1046 | + { |
| 1047 | + return null; |
| 1048 | + } |
| 1049 | +} |
0 commit comments