Passed
Push — 4.4 ( a2a202...24015c )
by Maxime
08:07
created

CsvBulkLoaderTest   A

Complexity

Total Complexity 14

Size/Duplication

Total Lines 323
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 180
c 0
b 0
f 0
dl 0
loc 323
rs 10
wmc 14

12 Methods

Rating   Name   Duplication   Size   Complexity  
A testLoadWithCustomHeaderAndRelation() 0 52 1
A testLoad() 0 26 1
A testLoadWithCustomImportMethodDuplicateMap() 0 17 1
A getLineCount() 0 8 2
A testLoadWithIdentifiers() 0 28 1
A setUp() 0 4 1
A testDeleteExistingRecords() 0 17 1
A testLoadWithByteOrderMark() 0 13 1
A testLoadWithCustomImportMethods() 0 16 1
A testLargeFileSplitIntoSmallerFiles() 0 10 1
A testLeadingTabs() 0 28 2
A testLoadWithColumnMap() 0 42 1
1
<?php
2
3
namespace SilverStripe\Dev\Tests;
4
5
use League\Csv\Writer;
6
use SilverStripe\Dev\Tests\CsvBulkLoaderTest\CustomLoader;
7
use SilverStripe\Dev\Tests\CsvBulkLoaderTest\Player;
8
use SilverStripe\Dev\Tests\CsvBulkLoaderTest\PlayerContract;
9
use SilverStripe\Dev\Tests\CsvBulkLoaderTest\Team;
10
use SilverStripe\ORM\DataObject;
11
use SilverStripe\ORM\FieldType\DBField;
12
use SilverStripe\Core\Config\Config;
13
use SilverStripe\Dev\CsvBulkLoader;
14
use SilverStripe\Dev\SapphireTest;
15
16
class CsvBulkLoaderTest extends SapphireTest
17
{
18
19
    protected static $fixture_file = 'CsvBulkLoaderTest.yml';
20
21
    protected static $extra_dataobjects = array(
22
        Team::class,
23
        Player::class,
24
        PlayerContract::class,
25
    );
26
27
    /**
28
     * Name of csv test dir
29
     *
30
     * @var string
31
     */
32
    protected $csvPath = null;
33
34
    protected function setUp()
35
    {
36
        parent::setUp();
37
        $this->csvPath = __DIR__ . '/CsvBulkLoaderTest/csv/';
38
    }
39
40
    /**
41
     * Test plain import with column auto-detection
42
     */
43
    public function testLoad()
44
    {
45
        $loader = new CsvBulkLoader(Player::class);
46
        $filepath = $this->csvPath . 'PlayersWithHeader.csv';
47
        $file = fopen($filepath, 'r');
48
        $compareCount = $this->getLineCount($file);
0 ignored issues
show
Unused Code introduced by
The assignment to $compareCount is dead and can be removed.
Loading history...
49
        fgetcsv($file); // pop header row
50
        $compareRow = fgetcsv($file);
0 ignored issues
show
Unused Code introduced by
The assignment to $compareRow is dead and can be removed.
Loading history...
51
        $results = $loader->load($filepath);
52
53
        // Test that right amount of columns was imported
54
        $this->assertCount(5, $results, 'Test correct count of imported data');
55
56
        // Test that columns were correctly imported
57
        $obj = DataObject::get_one(
58
            Player::class,
59
            array(
60
            '"CsvBulkLoaderTest_Player"."FirstName"' => 'John'
61
            )
62
        );
63
        $this->assertNotNull($obj);
64
        $this->assertEquals("He's a good guy", $obj->Biography);
0 ignored issues
show
Bug Best Practice introduced by
The property Biography does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
65
        $this->assertEquals("1988-01-31", $obj->Birthday);
0 ignored issues
show
Bug Best Practice introduced by
The property Birthday does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
66
        $this->assertEquals("1", $obj->IsRegistered);
0 ignored issues
show
Bug Best Practice introduced by
The property IsRegistered does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
67
68
        fclose($file);
69
    }
70
71
    /**
72
     * Test plain import with clear_table_before_import
73
         */
74
    public function testDeleteExistingRecords()
75
    {
76
        $loader = new CsvBulkLoader(Player::class);
77
        $filepath = $this->csvPath . 'PlayersWithHeader.csv';
78
        $loader->deleteExistingRecords = true;
79
        $results1 = $loader->load($filepath);
80
        $this->assertCount(5, $results1, 'Test correct count of imported data on first load');
81
82
        //delete existing data before doing second CSV import
83
        $results2 = $loader->load($filepath);
0 ignored issues
show
Unused Code introduced by
The assignment to $results2 is dead and can be removed.
Loading history...
84
        //get all instances of the loaded DataObject from the database and count them
85
        $resultDataObject = DataObject::get(Player::class);
86
87
        $this->assertCount(
88
            5,
89
            $resultDataObject,
90
            'Test if existing data is deleted before new data is added'
91
        );
92
    }
93
94
    public function testLeadingTabs()
95
    {
96
        $loader = new CsvBulkLoader(Player::class);
97
        $loader->hasHeaderRow = false;
98
        $loader->columnMap = array(
99
            'FirstName',
100
            'Biography',
101
            null, // ignored column
102
            'Birthday',
103
            'IsRegistered'
104
        );
105
        $filepath = $this->csvPath . 'PlayersWithTabs.csv';
106
        $results = $loader->load($filepath);
107
        $this->assertCount(5, $results);
108
109
        $expectedBios = [
110
            "\tHe's a good guy",
111
            "=She is awesome.\nSo awesome that she gets multiple rows and \"escaped\" strings in her biography",
112
            "-Pretty old\, with an escaped comma",
113
            "@Unicode FTW",
114
            "+Unicode FTW",
115
        ];
116
117
        foreach (Player::get()->column('Biography') as $bio) {
118
            $this->assertContains($bio, $expectedBios);
119
        }
120
121
        $this->assertEquals(Player::get()->count(), count($expectedBios));
122
    }
123
124
    /**
125
     * Test import with manual column mapping
126
     */
127
    public function testLoadWithColumnMap()
128
    {
129
        $loader = new CsvBulkLoader(Player::class);
130
        $filepath = $this->csvPath . 'Players.csv';
131
        $file = fopen($filepath, 'r');
132
        $compareCount = $this->getLineCount($file);
0 ignored issues
show
Unused Code introduced by
The assignment to $compareCount is dead and can be removed.
Loading history...
133
        $compareRow = fgetcsv($file);
0 ignored issues
show
Unused Code introduced by
The assignment to $compareRow is dead and can be removed.
Loading history...
134
        $loader->columnMap = array(
135
            'FirstName',
136
            'Biography',
137
            null, // ignored column
138
            'Birthday',
139
            'IsRegistered'
140
        );
141
        $loader->hasHeaderRow = false;
142
        $results = $loader->load($filepath);
143
144
        // Test that right amount of columns was imported
145
        $this->assertCount(4, $results, 'Test correct count of imported data');
146
147
        // Test that columns were correctly imported
148
        $obj = DataObject::get_one(
149
            Player::class,
150
            array(
151
            '"CsvBulkLoaderTest_Player"."FirstName"' => 'John'
152
            )
153
        );
154
        $this->assertNotNull($obj);
155
        $this->assertEquals("He's a good guy", $obj->Biography);
0 ignored issues
show
Bug Best Practice introduced by
The property Biography does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
156
        $this->assertEquals("1988-01-31", $obj->Birthday);
0 ignored issues
show
Bug Best Practice introduced by
The property Birthday does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
157
        $this->assertEquals("1", $obj->IsRegistered);
0 ignored issues
show
Bug Best Practice introduced by
The property IsRegistered does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
158
159
        $obj2 = DataObject::get_one(
160
            Player::class,
161
            array(
162
            '"CsvBulkLoaderTest_Player"."FirstName"' => 'Jane'
163
            )
164
        );
165
        $this->assertNotNull($obj2);
166
        $this->assertEquals('0', $obj2->IsRegistered);
167
168
        fclose($file);
169
    }
170
171
    /**
172
     * Test import with manual column mapping and custom column names
173
     */
174
    public function testLoadWithCustomHeaderAndRelation()
175
    {
176
        $loader = new CsvBulkLoader(Player::class);
177
        $filepath = $this->csvPath . 'PlayersWithCustomHeaderAndRelation.csv';
178
        $file = fopen($filepath, 'r');
179
        $compareCount = $this->getLineCount($file);
0 ignored issues
show
Unused Code introduced by
The assignment to $compareCount is dead and can be removed.
Loading history...
180
        fgetcsv($file); // pop header row
181
        $compareRow = fgetcsv($file);
182
        $loader->columnMap = array(
183
            'first name' => 'FirstName',
184
            'bio' => 'Biography',
185
            'bday' => 'Birthday',
186
            'teamtitle' => 'Team.Title', // test existing relation
187
            'teamsize' => 'Team.TeamSize', // test existing relation
188
            'salary' => 'Contract.Amount' // test relation creation
189
        );
190
        $loader->hasHeaderRow = true;
191
        $loader->relationCallbacks = array(
192
            'Team.Title' => array(
193
                'relationname' => 'Team',
194
                'callback' => 'getTeamByTitle'
195
            ),
196
            // contract should be automatically discovered
197
        );
198
        $results = $loader->load($filepath);
199
200
        // Test that right amount of columns was imported
201
        $this->assertCount(1, $results, 'Test correct count of imported data');
202
203
        // Test of augumenting existing relation (created by fixture)
204
        $testTeam = DataObject::get_one(Team::class, null, null, '"Created" DESC');
205
        $this->assertEquals('20', $testTeam->TeamSize, 'Augumenting existing has_one relation works');
0 ignored issues
show
Bug Best Practice introduced by
The property TeamSize does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
206
207
        // Test of creating relation
208
        $testContract = DataObject::get_one(PlayerContract::class);
209
        $testPlayer = DataObject::get_one(
210
            Player::class,
211
            array(
212
            '"CsvBulkLoaderTest_Player"."FirstName"' => 'John'
213
            )
214
        );
215
        $this->assertEquals($testPlayer->ContractID, $testContract->ID, 'Creating new has_one relation works');
0 ignored issues
show
Bug Best Practice introduced by
The property ContractID does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
216
217
        // Test nested setting of relation properties
218
        $contractAmount = DBField::create_field('Currency', $compareRow[5])->RAW();
219
        $this->assertEquals(
220
            $testPlayer->Contract()->Amount,
0 ignored issues
show
Bug introduced by
The method Contract() does not exist on SilverStripe\ORM\DataObject. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

220
            $testPlayer->/** @scrutinizer ignore-call */ 
221
                         Contract()->Amount,
Loading history...
221
            $contractAmount,
222
            'Setting nested values in a relation works'
223
        );
224
225
        fclose($file);
226
    }
227
228
    /**
229
     * Test import with custom identifiers by importing the data.
230
     *
231
     * @todo Test duplicateCheck callbacks
232
     */
233
    public function testLoadWithIdentifiers()
234
    {
235
        // first load
236
        $loader = new CsvBulkLoader(Player::class);
237
        $filepath = $this->csvPath . 'PlayersWithId.csv';
238
        $loader->duplicateChecks = array(
239
            'ExternalIdentifier' => 'ExternalIdentifier',
240
            'NonExistantIdentifier' => 'ExternalIdentifier',
241
            'AdditionalIdentifier' => 'ExternalIdentifier'
242
        );
243
        $results = $loader->load($filepath);
244
        $createdPlayers = $results->Created();
245
246
        $player = $createdPlayers->first();
247
        $this->assertEquals($player->FirstName, 'John');
248
        $this->assertEquals(
249
            $player->Biography,
250
            'He\'s a good guy',
251
            'test updating of duplicate imports within the same import works'
252
        );
253
254
        // load with updated data
255
        $filepath = $this->csvPath . 'PlayersWithIdUpdated.csv';
256
        $results = $loader->load($filepath);
0 ignored issues
show
Unused Code introduced by
The assignment to $results is dead and can be removed.
Loading history...
257
258
        // HACK need to update the loaded record from the database
259
        $player = DataObject::get_by_id(Player::class, $player->ID);
260
        $this->assertEquals($player->FirstName, 'JohnUpdated', 'Test updating of existing records works');
0 ignored issues
show
Bug Best Practice introduced by
The property FirstName does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
261
262
        // null values are valid imported
263
        // $this->assertEquals($player->Biography, 'He\'s a good guy',
264
        //  'Test retaining of previous information on duplicate when overwriting with blank field');
265
    }
266
267
    public function testLoadWithCustomImportMethods()
268
    {
269
        $loader = new CustomLoader(Player::class);
270
        $filepath = $this->csvPath . 'PlayersWithHeader.csv';
271
        $loader->columnMap = array(
272
            'FirstName' => '->importFirstName',
273
            'Biography' => 'Biography',
274
            'Birthday' => 'Birthday',
275
            'IsRegistered' => 'IsRegistered'
276
        );
277
        $results = $loader->load($filepath);
278
        $createdPlayers = $results->Created();
279
        $player = $createdPlayers->first();
280
        $this->assertEquals('Customized John', $player->FirstName);
281
        $this->assertEquals("He's a good guy", $player->Biography);
282
        $this->assertEquals("1", $player->IsRegistered);
283
    }
284
285
    public function testLoadWithCustomImportMethodDuplicateMap()
286
    {
287
        $loader = new CustomLoader(Player::class);
288
        $filepath = $this->csvPath . 'PlayersWithHeader.csv';
289
        $loader->columnMap = array(
290
            'FirstName' => '->updatePlayer',
291
            'Biography' => '->updatePlayer',
292
            'Birthday' => 'Birthday',
293
            'IsRegistered' => 'IsRegistered'
294
        );
295
296
        $results = $loader->load($filepath);
297
298
        $createdPlayers = $results->Created();
299
        $player = $createdPlayers->first();
300
301
        $this->assertEquals($player->FirstName, "John. He's a good guy. ");
302
    }
303
304
    public function testLoadWithByteOrderMark()
305
    {
306
        $loader = new CsvBulkLoader(Player::class);
307
        $loader->load($this->csvPath . 'PlayersWithHeaderAndBOM.csv');
308
309
        $players = Player::get();
310
311
        $this->assertCount(3, $players);
312
        $this->assertListContains([
313
            ['FirstName' => 'Jamie', 'Birthday' => '1882-01-31'],
314
            ['FirstName' => 'Järg', 'Birthday' => '1982-06-30'],
315
            ['FirstName' => 'Jacob', 'Birthday' => '2000-04-30'],
316
        ], $players);
317
    }
318
319
    protected function getLineCount(&$file)
320
    {
321
        $i = 0;
322
        while (fgets($file) !== false) {
323
            $i++;
324
        }
325
        rewind($file);
326
        return $i;
327
    }
328
329
    public function testLargeFileSplitIntoSmallerFiles()
330
    {
331
        Config::modify()->set(CsvBulkLoader::class, 'lines', 3);
332
333
        $loader = new CsvBulkLoader(Player::class);
334
        $path = $this->csvPath . 'LargeListOfPlayers.csv';
335
336
        $results = $loader->load($path);
337
338
        $this->assertCount(10, $results);
339
    }
340
}
341