Passed
Push — 4 ( dace2f...ced2ba )
by Damian
09:35
created

CsvBulkLoaderTest::testLeadingTabs()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 28
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 20
nc 2
nop 0
dl 0
loc 28
rs 8.8571
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\Dev\Tests;
4
5
use SilverStripe\Dev\Tests\CsvBulkLoaderTest\CustomLoader;
6
use SilverStripe\Dev\Tests\CsvBulkLoaderTest\Player;
7
use SilverStripe\Dev\Tests\CsvBulkLoaderTest\PlayerContract;
8
use SilverStripe\Dev\Tests\CsvBulkLoaderTest\Team;
9
use SilverStripe\ORM\DataObject;
10
use SilverStripe\ORM\FieldType\DBField;
11
use SilverStripe\Core\Config\Config;
12
use SilverStripe\Dev\CsvBulkLoader;
13
use SilverStripe\Dev\SapphireTest;
14
15
class CsvBulkLoaderTest extends SapphireTest
16
{
17
18
    protected static $fixture_file = 'CsvBulkLoaderTest.yml';
19
20
    protected static $extra_dataobjects = array(
21
        Team::class,
22
        Player::class,
23
        PlayerContract::class,
24
    );
25
26
    /**
27
     * Name of csv test dir
28
     *
29
     * @var string
30
     */
31
    protected $csvPath = null;
32
33
    protected function setUp()
34
    {
35
        parent::setUp();
36
        $this->csvPath = __DIR__ . '/CsvBulkLoaderTest/csv/';
37
    }
38
39
    /**
40
     * Test plain import with column auto-detection
41
     */
42
    public function testLoad()
43
    {
44
        $loader = new CsvBulkLoader(Player::class);
45
        $filepath = $this->csvPath . 'PlayersWithHeader.csv';
46
        $file = fopen($filepath, 'r');
47
        $compareCount = $this->getLineCount($file);
0 ignored issues
show
Unused Code introduced by
The assignment to $compareCount is dead and can be removed.
Loading history...
48
        fgetcsv($file); // pop header row
0 ignored issues
show
Bug introduced by
It seems like $file can also be of type false; however, parameter $handle of fgetcsv() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

48
        fgetcsv(/** @scrutinizer ignore-type */ $file); // pop header row
Loading history...
49
        $compareRow = fgetcsv($file);
0 ignored issues
show
Unused Code introduced by
The assignment to $compareRow is dead and can be removed.
Loading history...
50
        $results = $loader->load($filepath);
51
52
        // Test that right amount of columns was imported
53
        $this->assertCount(5, $results, 'Test correct count of imported data');
54
55
        // Test that columns were correctly imported
56
        $obj = DataObject::get_one(
57
            Player::class,
58
            array(
59
            '"CsvBulkLoaderTest_Player"."FirstName"' => 'John'
60
            )
61
        );
62
        $this->assertNotNull($obj);
63
        $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...
64
        $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...
65
        $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...
66
67
        fclose($file);
0 ignored issues
show
Bug introduced by
It seems like $file can also be of type false; however, parameter $handle of fclose() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

67
        fclose(/** @scrutinizer ignore-type */ $file);
Loading history...
68
    }
69
70
    /**
71
     * Test plain import with clear_table_before_import
72
         */
73
    public function testDeleteExistingRecords()
74
    {
75
        $loader = new CsvBulkLoader(Player::class);
76
        $filepath = $this->csvPath . 'PlayersWithHeader.csv';
77
        $loader->deleteExistingRecords = true;
78
        $results1 = $loader->load($filepath);
79
        $this->assertCount(5, $results1, 'Test correct count of imported data on first load');
80
81
        //delete existing data before doing second CSV import
82
        $results2 = $loader->load($filepath);
0 ignored issues
show
Unused Code introduced by
The assignment to $results2 is dead and can be removed.
Loading history...
83
        //get all instances of the loaded DataObject from the database and count them
84
        $resultDataObject = DataObject::get(Player::class);
85
86
        $this->assertCount(
87
            5,
88
            $resultDataObject,
89
            'Test if existing data is deleted before new data is added'
90
        );
91
    }
92
93
    public function testLeadingTabs()
94
    {
95
        $loader = new CsvBulkLoader(Player::class);
96
        $loader->hasHeaderRow = false;
97
        $loader->columnMap = array(
98
            'FirstName',
99
            'Biography',
100
            null, // ignored column
101
            'Birthday',
102
            'IsRegistered'
103
        );
104
        $filepath = $this->csvPath . 'PlayersWithTabs.csv';
105
        $results = $loader->load($filepath);
106
        $this->assertCount(5, $results);
107
108
        $expectedBios = [
109
            "\tHe's a good guy",
110
            "=She is awesome.\nSo awesome that she gets multiple rows and \"escaped\" strings in her biography",
111
            "-Pretty old\, with an escaped comma",
112
            "@Unicode FTW",
113
            "+Unicode FTW",
114
        ];
115
116
        foreach (Player::get()->column('Biography') as $bio) {
117
            $this->assertContains($bio, $expectedBios);
118
        }
119
120
        $this->assertEquals(Player::get()->count(), count($expectedBios));
121
    }
122
123
    /**
124
     * Test import with manual column mapping
125
     */
126
    public function testLoadWithColumnMap()
127
    {
128
        $loader = new CsvBulkLoader(Player::class);
129
        $filepath = $this->csvPath . 'Players.csv';
130
        $file = fopen($filepath, 'r');
131
        $compareCount = $this->getLineCount($file);
0 ignored issues
show
Unused Code introduced by
The assignment to $compareCount is dead and can be removed.
Loading history...
132
        $compareRow = fgetcsv($file);
0 ignored issues
show
Unused Code introduced by
The assignment to $compareRow is dead and can be removed.
Loading history...
Bug introduced by
It seems like $file can also be of type false; however, parameter $handle of fgetcsv() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

132
        $compareRow = fgetcsv(/** @scrutinizer ignore-type */ $file);
Loading history...
133
        $loader->columnMap = array(
134
            'FirstName',
135
            'Biography',
136
            null, // ignored column
137
            'Birthday',
138
            'IsRegistered'
139
        );
140
        $loader->hasHeaderRow = false;
141
        $results = $loader->load($filepath);
142
143
        // Test that right amount of columns was imported
144
        $this->assertCount(4, $results, 'Test correct count of imported data');
145
146
        // Test that columns were correctly imported
147
        $obj = DataObject::get_one(
148
            Player::class,
149
            array(
150
            '"CsvBulkLoaderTest_Player"."FirstName"' => 'John'
151
            )
152
        );
153
        $this->assertNotNull($obj);
154
        $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...
155
        $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...
156
        $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...
157
158
        $obj2 = DataObject::get_one(
159
            Player::class,
160
            array(
161
            '"CsvBulkLoaderTest_Player"."FirstName"' => 'Jane'
162
            )
163
        );
164
        $this->assertNotNull($obj2);
165
        $this->assertEquals('0', $obj2->IsRegistered);
166
167
        fclose($file);
0 ignored issues
show
Bug introduced by
It seems like $file can also be of type false; however, parameter $handle of fclose() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

167
        fclose(/** @scrutinizer ignore-type */ $file);
Loading history...
168
    }
169
170
    /**
171
     * Test import with manual column mapping and custom column names
172
     */
173
    public function testLoadWithCustomHeaderAndRelation()
174
    {
175
        $loader = new CsvBulkLoader(Player::class);
176
        $filepath = $this->csvPath . 'PlayersWithCustomHeaderAndRelation.csv';
177
        $file = fopen($filepath, 'r');
178
        $compareCount = $this->getLineCount($file);
0 ignored issues
show
Unused Code introduced by
The assignment to $compareCount is dead and can be removed.
Loading history...
179
        fgetcsv($file); // pop header row
0 ignored issues
show
Bug introduced by
It seems like $file can also be of type false; however, parameter $handle of fgetcsv() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

179
        fgetcsv(/** @scrutinizer ignore-type */ $file); // pop header row
Loading history...
180
        $compareRow = fgetcsv($file);
181
        $loader->columnMap = array(
182
            'first name' => 'FirstName',
183
            'bio' => 'Biography',
184
            'bday' => 'Birthday',
185
            'teamtitle' => 'Team.Title', // test existing relation
186
            'teamsize' => 'Team.TeamSize', // test existing relation
187
            'salary' => 'Contract.Amount' // test relation creation
188
        );
189
        $loader->hasHeaderRow = true;
190
        $loader->relationCallbacks = array(
191
            'Team.Title' => array(
192
                'relationname' => 'Team',
193
                'callback' => 'getTeamByTitle'
194
            ),
195
            // contract should be automatically discovered
196
        );
197
        $results = $loader->load($filepath);
198
199
        // Test that right amount of columns was imported
200
        $this->assertCount(1, $results, 'Test correct count of imported data');
201
202
        // Test of augumenting existing relation (created by fixture)
203
        $testTeam = DataObject::get_one(Team::class, null, null, '"Created" DESC');
204
        $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...
205
206
        // Test of creating relation
207
        $testContract = DataObject::get_one(PlayerContract::class);
208
        $testPlayer = DataObject::get_one(
209
            Player::class,
210
            array(
211
            '"CsvBulkLoaderTest_Player"."FirstName"' => 'John'
212
            )
213
        );
214
        $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...
215
216
        // Test nested setting of relation properties
217
        $contractAmount = DBField::create_field('Currency', $compareRow[5])->RAW();
218
        $this->assertEquals(
219
            $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

219
            $testPlayer->/** @scrutinizer ignore-call */ 
220
                         Contract()->Amount,
Loading history...
220
            $contractAmount,
221
            'Setting nested values in a relation works'
222
        );
223
224
        fclose($file);
0 ignored issues
show
Bug introduced by
It seems like $file can also be of type false; however, parameter $handle of fclose() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

224
        fclose(/** @scrutinizer ignore-type */ $file);
Loading history...
225
    }
226
227
    /**
228
     * Test import with custom identifiers by importing the data.
229
     *
230
     * @todo Test duplicateCheck callbacks
231
     */
232
    public function testLoadWithIdentifiers()
233
    {
234
        // first load
235
        $loader = new CsvBulkLoader(Player::class);
236
        $filepath = $this->csvPath . 'PlayersWithId.csv';
237
        $loader->duplicateChecks = array(
238
            'ExternalIdentifier' => 'ExternalIdentifier',
239
            'NonExistantIdentifier' => 'ExternalIdentifier',
240
            'AdditionalIdentifier' => 'ExternalIdentifier'
241
        );
242
        $results = $loader->load($filepath);
243
        $createdPlayers = $results->Created();
244
245
        $player = $createdPlayers->first();
246
        $this->assertEquals($player->FirstName, 'John');
247
        $this->assertEquals(
248
            $player->Biography,
249
            'He\'s a good guy',
250
            'test updating of duplicate imports within the same import works'
251
        );
252
253
        // load with updated data
254
        $filepath = $this->csvPath . 'PlayersWithIdUpdated.csv';
255
        $results = $loader->load($filepath);
0 ignored issues
show
Unused Code introduced by
The assignment to $results is dead and can be removed.
Loading history...
256
257
        // HACK need to update the loaded record from the database
258
        $player = DataObject::get_by_id(Player::class, $player->ID);
259
        $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...
260
261
        // null values are valid imported
262
        // $this->assertEquals($player->Biography, 'He\'s a good guy',
263
        //  'Test retaining of previous information on duplicate when overwriting with blank field');
264
    }
265
266
    public function testLoadWithCustomImportMethods()
267
    {
268
        $loader = new CustomLoader(Player::class);
269
        $filepath = $this->csvPath . 'PlayersWithHeader.csv';
270
        $loader->columnMap = array(
271
            'FirstName' => '->importFirstName',
272
            'Biography' => 'Biography',
273
            'Birthday' => 'Birthday',
274
            'IsRegistered' => 'IsRegistered'
275
        );
276
        $results = $loader->load($filepath);
277
        $createdPlayers = $results->Created();
278
        $player = $createdPlayers->first();
279
        $this->assertEquals('Customized John', $player->FirstName);
280
        $this->assertEquals("He's a good guy", $player->Biography);
281
        $this->assertEquals("1", $player->IsRegistered);
282
    }
283
284
    public function testLoadWithCustomImportMethodDuplicateMap()
285
    {
286
        $loader = new CustomLoader(Player::class);
287
        $filepath = $this->csvPath . 'PlayersWithHeader.csv';
288
        $loader->columnMap = array(
289
            'FirstName' => '->updatePlayer',
290
            'Biography' => '->updatePlayer',
291
            'Birthday' => 'Birthday',
292
            'IsRegistered' => 'IsRegistered'
293
        );
294
295
        $results = $loader->load($filepath);
296
297
        $createdPlayers = $results->Created();
298
        $player = $createdPlayers->first();
299
300
        $this->assertEquals($player->FirstName, "John. He's a good guy. ");
301
    }
302
303
304
    protected function getLineCount(&$file)
305
    {
306
        $i = 0;
307
        while (fgets($file) !== false) {
308
            $i++;
309
        }
310
        rewind($file);
311
        return $i;
312
    }
313
314
    public function testLargeFileSplitIntoSmallerFiles()
315
    {
316
        Config::modify()->set(CsvBulkLoader::class, 'lines', 3);
317
318
        $loader = new CsvBulkLoader(Player::class);
319
        $path = $this->csvPath . 'LargeListOfPlayers.csv';
320
321
        $results = $loader->load($path);
322
323
        $this->assertCount(10, $results);
324
    }
325
}
326