Schneespur — Open-source winter service documentation software (PWA + Admin). GPS tracking via OwnTracks, weather data, photo evidence, and legally compliant service records for winter maintenance operators. License: AGPL-3.0-or-later
281 lines
7.9 KiB
PHP
281 lines
7.9 KiB
PHP
<?php
|
|
|
|
/**
|
|
* League.Csv (https://csv.thephpleague.com)
|
|
*
|
|
* (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
|
|
*
|
|
* For the full copyright and license information, please view the LICENSE
|
|
* file that was distributed with this source code.
|
|
*/
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace League\Csv;
|
|
|
|
use League\Csv\Query\Constraint\Comparison;
|
|
use League\Csv\Query\QueryException;
|
|
use OutOfBoundsException;
|
|
use PHPUnit\Framework\Attributes\DataProvider;
|
|
use PHPUnit\Framework\Attributes\Group;
|
|
use PHPUnit\Framework\TestCase;
|
|
use SplTempFileObject;
|
|
|
|
use function array_reverse;
|
|
use function in_array;
|
|
use function strcmp;
|
|
use function strlen;
|
|
|
|
#[Group('reader')]
|
|
final class StatementTest extends TestCase
|
|
{
|
|
private Reader $csv;
|
|
private Statement $stmt;
|
|
private array $expected = [
|
|
['john', 'doe', 'john.doe@example.com'],
|
|
['jane', 'doe', 'jane.doe@example.com'],
|
|
];
|
|
|
|
protected function setUp(): void
|
|
{
|
|
$tmp = new SplTempFileObject();
|
|
$tmp->setCsvControl(escape: '\\');
|
|
foreach ($this->expected as $row) {
|
|
$tmp->fputcsv($row, escape: '\\');
|
|
}
|
|
|
|
$this->csv = Reader::from($tmp);
|
|
$this->stmt = (new Statement());
|
|
}
|
|
|
|
protected function tearDown(): void
|
|
{
|
|
unset($this->csv, $this->stmt);
|
|
}
|
|
|
|
public function testSetLimit(): void
|
|
{
|
|
self::assertCount(1, $this->stmt->limit(1)->process($this->csv));
|
|
}
|
|
|
|
public function testSetLimitThrowException(): void
|
|
{
|
|
$this->expectException(InvalidArgument::class);
|
|
|
|
$this->stmt->limit(-2);
|
|
}
|
|
|
|
public function testSetOffset(): void
|
|
{
|
|
self::assertContains(
|
|
['jane', 'doe', 'jane.doe@example.com'],
|
|
[...$this->stmt->offset(1)->process($this->csv)]
|
|
);
|
|
}
|
|
|
|
public function testSetOffsetThrowsException(): void
|
|
{
|
|
$this->expectException(InvalidArgument::class);
|
|
|
|
$this->stmt->offset(-1);
|
|
}
|
|
|
|
public function testStatementSameInstance(): void
|
|
{
|
|
self::assertSame($this->stmt, $this->stmt->limit(-1)->offset(0));
|
|
}
|
|
|
|
#[DataProvider('intervalTest')]
|
|
public function testInterval(int $offset, int $limit): void
|
|
{
|
|
self::assertContains(
|
|
['jane', 'doe', 'jane.doe@example.com'],
|
|
[...$this->stmt
|
|
->offset($offset)
|
|
->limit($limit)
|
|
->where(fn (array $record): bool => true)
|
|
->where(fn (array $record): bool => [] !== $record)
|
|
->process($this->csv)]
|
|
);
|
|
}
|
|
|
|
public static function intervalTest(): array
|
|
{
|
|
return [
|
|
'tooHigh' => [1, 10],
|
|
'normal' => [1, 1],
|
|
];
|
|
}
|
|
|
|
public function testIntervalThrowException(): void
|
|
{
|
|
$this->expectException(OutOfBoundsException::class);
|
|
|
|
[...$this->stmt->offset(1)->limit(0)->process($this->csv)];
|
|
}
|
|
|
|
public function testFilter(): void
|
|
{
|
|
$func2 = fn (array $row): bool => !in_array('john', $row, true);
|
|
|
|
$stmt = (new Statement())->where(fn (array $row): bool => !in_array('jane', $row, true));
|
|
$result1 = $stmt->process($this->csv);
|
|
|
|
$result2 = $stmt->where($func2)->process($result1, ['foo', 'bar']);
|
|
$result3 = $stmt->where($func2)->process($result2, ['foo', 'bar']);
|
|
|
|
self::assertNotContains(['jane', 'doe', 'jane.doe@example.com'], [...$result1]);
|
|
|
|
self::assertCount(0, $result2);
|
|
self::assertEquals($result3, $result2);
|
|
}
|
|
|
|
public function testAddWhere(): void
|
|
{
|
|
$stmt1 = (new Statement())->andWhere(0, '=', 'jane');
|
|
$result1 = $stmt1->process($this->csv);
|
|
|
|
self::assertCount(1, $result1);
|
|
self::assertEquals('jane', $result1->first()[0]);
|
|
|
|
$stmt2 = (new Statement())->andWhere(0, 'starts_with', 'j');
|
|
$result2 = $stmt2->process($this->csv);
|
|
|
|
self::assertCount(2, $result2);
|
|
self::assertEquals('jane', $result2->nth(1)[0]);
|
|
|
|
$stmt3 = (new Statement())->andWhere(2, 'starts_with', 'blablabla');
|
|
$result3 = $stmt3->process($this->csv);
|
|
|
|
self::assertCount(0, $result3);
|
|
}
|
|
|
|
public function testOrWhere(): void
|
|
{
|
|
$stmt1 = (new Statement())
|
|
->orWhere(0, 'starts_with', 'ja')
|
|
->orWhere(0, 'ends_with', 'hn');
|
|
$result1 = $stmt1->process($this->csv);
|
|
|
|
self::assertCount(2, $result1);
|
|
self::assertEquals('john', $result1->first()[0]);
|
|
|
|
$stmt3 = (new Statement())->orWhere(2, 'starts_with', 'blablabla');
|
|
$result3 = $stmt3->process($this->csv);
|
|
|
|
self::assertCount(0, $result3);
|
|
}
|
|
|
|
public function testOrderBy(): void
|
|
{
|
|
$calculated = $this->stmt
|
|
->orderBy(fn (array $rowA, array $rowB): int => strcmp($rowA[0], $rowB[0])) /* @phpstan-ignore-line */
|
|
->process($this->csv);
|
|
|
|
self::assertSame(array_reverse($this->expected), array_values([...$calculated]));
|
|
}
|
|
|
|
public function testOrderByColumn(): void
|
|
{
|
|
$calculated = $this->stmt
|
|
->orderByAsc(0)
|
|
->process($this->csv);
|
|
|
|
self::assertSame(array_reverse($this->expected), array_values([...$calculated]));
|
|
|
|
$calculated = $this->stmt
|
|
->orderByDesc(0)
|
|
->process($this->csv);
|
|
|
|
self::assertSame($this->expected, array_values([...$calculated]));
|
|
}
|
|
|
|
public function testOrderByColumnThrowsExceptionIfTheOffsetDoesNotExists(): void
|
|
{
|
|
$this->expectException(QueryException::class);
|
|
|
|
$this->stmt->orderByDesc(-42)->process($this->csv);
|
|
}
|
|
|
|
public function testOrderByWithEquity(): void
|
|
{
|
|
$calculated = $this->stmt
|
|
->orderBy(fn (array $rowA, array $rowB): int => strlen($rowA[0]) <=> strlen($rowB[0])) /* @phpstan-ignore-line */
|
|
->process($this->csv);
|
|
|
|
self::assertSame($this->expected, array_values([...$calculated]));
|
|
}
|
|
|
|
public function testHeaderMapperOnStatement(): void
|
|
{
|
|
$results = (new Statement())
|
|
->process($this->csv, [2 => 'e-mail', 1 => 'lastname', 33 => 'does not exists']);
|
|
self::assertSame(['e-mail', 'lastname', 'does not exists'], $results->getHeader());
|
|
self::assertSame([
|
|
'e-mail' => 'john.doe@example.com',
|
|
'lastname' => 'doe',
|
|
'does not exists' => null,
|
|
], $results->first());
|
|
}
|
|
|
|
public function testOrderByDoesNotThrowOnInvalidOffsetOrLimit(): void
|
|
{
|
|
$document = <<<CSV
|
|
Integer,Float,Text,Multiline Text,Date and Time
|
|
1,1.11,Foo,"Foo
|
|
Bar",2020-01-01 01:01:01
|
|
2,1.22,Bar,"Bar
|
|
Baz",2020-02-02 02:02:02
|
|
3,1.33,Baz,"Baz
|
|
Foo",2020-03-03 03:03:03
|
|
CSV;
|
|
|
|
$csv = Reader::fromString($document);
|
|
$csv->setHeaderOffset(0);
|
|
$constraints = (new Statement())
|
|
->select('Integer', 'Text', 'Date and Time')
|
|
->where(fn (array $record): bool => (float) $record['Float'] < 1.3)
|
|
->orderBy(fn (array $record1, array $record2): int => (int) $record2['Integer'] <=> (int) $record1['Integer']) /* @phpstan-ignore-line */
|
|
->limit(5)
|
|
->offset(2);
|
|
|
|
self::assertSame([], $constraints->process($csv)->nth(42));
|
|
}
|
|
|
|
public function testItCanCompareNullValue(): void
|
|
{
|
|
$document = <<<CSV
|
|
Title,Name,Number
|
|
Commander,Fred,104
|
|
Officer,John,117
|
|
Major,Avery
|
|
CSV;
|
|
|
|
$csv = Reader::fromString($document);
|
|
$csv->setHeaderOffset(0);
|
|
|
|
$statement = (new Statement())
|
|
->andWhere('Number', Comparison::Contains, '117');
|
|
|
|
self::assertCount(1, $statement->process($csv));
|
|
}
|
|
|
|
public function testselectAllExcept(): void
|
|
{
|
|
$document = <<<CSV
|
|
Title,Name,Number
|
|
Commander,Fred,104
|
|
Officer,John,117
|
|
Major,Avery
|
|
CSV;
|
|
|
|
$csv = Reader::fromString($document);
|
|
$csv->setHeaderOffset(0);
|
|
|
|
$statement = (new Statement())
|
|
->limit(1)
|
|
->selectAllExcept('Number');
|
|
|
|
self::assertSame(['Title' => 'Commander', 'Name' => 'Fred'], $statement->process($csv)->first());
|
|
}
|
|
}
|