-
Notifications
You must be signed in to change notification settings - Fork 14
Expand file tree
/
Copy pathVirtualColumnTest.php
More file actions
265 lines (219 loc) · 8.08 KB
/
VirtualColumnTest.php
File metadata and controls
265 lines (219 loc) · 8.08 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
<?php
namespace Stancl\VirtualColumn\Tests;
use Orchestra\Testbench\TestCase;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Database\Eloquent\Model;
use Stancl\VirtualColumn\VirtualColumn;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use PHPUnit\Framework\Attributes\Test;
class VirtualColumnTest extends TestCase
{
public function setUp(): void
{
parent::setUp();
$this->loadMigrationsFrom(__DIR__ . '/etc/migrations');
}
#[Test]
public function keys_which_dont_have_their_own_column_go_into_data_json_column()
{
$model = MyModel::create([
'foo' => 'bar',
]);
// Test that model works correctly
$this->assertSame('bar', $model->foo);
$this->assertSame(null, $model->data);
// Low level test to assert database structure
$this->assertSame(['foo' => 'bar'], json_decode(DB::table('my_models')->where('id', $model->id)->first()->data, true));
$this->assertSame(null, DB::table('my_models')->where('id', $model->id)->first()->foo ?? null);
// Model has the correct structure when retrieved
$model = MyModel::first();
$this->assertSame('bar', $model->foo);
$this->assertSame('bar', $model->getOriginal('foo'));
$this->assertSame(null, $model->data);
// Model can be updated
$model->update([
'foo' => 'baz',
'abc' => 'xyz',
]);
$this->assertSame('baz', $model->foo);
$this->assertSame('baz', $model->getOriginal('foo'));
$this->assertSame('xyz', $model->abc);
$this->assertSame('xyz', $model->getOriginal('abc'));
$this->assertSame(null, $model->data);
// Model can be retrieved after update & is structured correctly
$model = MyModel::first();
$this->assertSame('baz', $model->foo);
$this->assertSame('xyz', $model->abc);
$this->assertSame(null, $model->data);
}
#[Test]
public function model_is_always_decoded_when_accessed_by_user_event()
{
MyModel::retrieved(function (MyModel $model) {
$this->assertFalse($model->dataEncoded);
});
MyModel::saving(function (MyModel $model) {
$this->assertFalse($model->dataEncoded);
});
MyModel::updating(function (MyModel $model) {
$this->assertFalse($model->dataEncoded);
});
MyModel::creating(function (MyModel $model) {
$this->assertFalse($model->dataEncoded);
});
MyModel::saved(function (MyModel $model) {
$this->assertFalse($model->dataEncoded);
});
MyModel::updated(function (MyModel $model) {
$this->assertFalse($model->dataEncoded);
});
MyModel::created(function (MyModel $model) {
$this->assertFalse($model->dataEncoded);
});
$model = MyModel::create(['foo' => 'bar']);
$model->update(['foo' => 'baz']);
MyModel::first();
}
#[Test]
public function column_names_are_generated_correctly()
{
// FooModel's virtual data column name is 'virtual'
$virtualColumnName = 'virtual->foo';
$customColumnName = 'custom1';
/** @var FooModel $model */
$model = FooModel::create([
'custom1' => $customColumnName,
'foo' => $virtualColumnName
]);
$this->assertSame($customColumnName, $model->getColumnForQuery('custom1'));
$this->assertSame($virtualColumnName, $model->getColumnForQuery('foo'));
}
#[Test]
public function models_extending_a_parent_model_using_virtualcolumn_get_encoded_correctly()
{
// Create a model that extends a parent model using VirtualColumn
// 'foo' is a custom column, 'data' is the virtual column
FooChild::create(['foo' => 'foo']);
$encodedFoo = DB::select('select * from foo_childs limit 1')[0];
// Assert that the model was encoded correctly
$this->assertSame($encodedFoo->data, '[]');
$this->assertSame($encodedFoo->foo, 'foo');
// Create another child model of the same parent
// 'bar' is a custom column, 'data' is the virtual column
BarChild::create(['bar' => 'bar']);
$encodedBar = DB::select('select * from bar_childs limit 1')[0];
$this->assertSame($encodedBar->data, '[]');
$this->assertSame($encodedBar->bar, 'bar');
}
// maybe add an explicit test that the saving() and updating() listeners don't run twice?
#[Test]
public function encrypted_casts_work_with_virtual_column() {
// Custom encrypted castables have to be specified in the $customEncryptedCastables static property
MyModel::$customEncryptedCastables = [EncryptedCast::class];
/** @var MyModel $model */
$model = MyModel::create($encryptedAttributes = [
'password' => 'foo', // 'encrypted'
'array' => ['foo', 'bar'], // 'encrypted:array'
'collection' => collect(['foo', 'bar']), // 'encrypted:collection'
'json' => json_encode(['foo', 'bar']), // 'encrypted:json'
'object' => (object) json_encode(['foo', 'bar']), // 'encrypted:object'
'custom' => 'foo', // Custom castable – 'EncryptedCast::class'
'null_value' => null, // 'encrypted'
]);
foreach($encryptedAttributes as $key => $expectedValue) {
$savedValue = $model->getAttributes()[$key]; // Encrypted
if ($savedValue !== null) {
$this->assertTrue($model->valueEncrypted($savedValue));
$this->assertNotEquals($expectedValue, $savedValue);
}
$retrievedValue = $model->$key; // Decrypted
$this->assertEquals($expectedValue, $retrievedValue);
}
// Reset static property
MyModel::$customEncryptedCastables = [];
}
#[Test]
public function updating_model_does_not_store_timestamps_in_the_virtual_column() {
/** @var TimestampModel $model */
$model = TimestampModel::create();
$dbRecordDataColumn = fn () => DB::selectOne('select * from timestamp_models where id = ?', [$model->id])->data;
$this->assertStringNotContainsString('created_at', $dbRecordDataColumn());
$this->assertStringNotContainsString('updated_at', $dbRecordDataColumn());
$model->update(['data' => ['virtual' => 'bar']]);
$this->assertStringNotContainsString('created_at', $dbRecordDataColumn());
$this->assertStringNotContainsString('updated_at', $dbRecordDataColumn());
}
}
class TimestampModel extends Model
{
use VirtualColumn;
public $timestamps = true;
protected $guarded = [];
}
class ParentModel extends Model
{
use VirtualColumn;
public $timestamps = false;
protected $guarded = [];
}
class MyModel extends ParentModel
{
public $casts = [
'password' => 'encrypted',
'array' => 'encrypted:array',
'collection' => 'encrypted:collection',
'json' => 'encrypted:json',
'object' => 'encrypted:object',
'custom' => EncryptedCast::class,
'null_value' => 'encrypted',
];
}
class FooModel extends ParentModel
{
public static function getCustomColumns(): array
{
return [
'id',
'custom1',
'custom2',
];
}
public static function getDataColumn(): string
{
return 'virtual';
}
}
class EncryptedCast implements CastsAttributes
{
public function get($model, $key, $value, $attributes)
{
return Crypt::decryptString($value);
}
public function set($model, $key, $value, $attributes)
{
return Crypt::encryptString($value);
}
}
class FooChild extends ParentModel
{
public $table = 'foo_childs';
public static function getCustomColumns(): array
{
return [
'id',
'foo',
];
}
}
class BarChild extends ParentModel
{
public $table = 'bar_childs';
public static function getCustomColumns(): array
{
return [
'id',
'bar',
];
}
}