Skip to content

Commit

Permalink
Created filter for recipe files
Browse files Browse the repository at this point in the history
Signed-off-by: Christian Wolf <[email protected]>
  • Loading branch information
christianlupus committed Jul 6, 2022
1 parent d5a7eb0 commit 44e85bf
Show file tree
Hide file tree
Showing 3 changed files with 247 additions and 0 deletions.
26 changes: 26 additions & 0 deletions lib/Helper/Filter/AbstractRecipeFilter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace OCA\Cookbook\Helper\Filter;

use OCA\Cookbook\Exception\InvalidRecipeException;
use OCP\Files\File;

/**
* An abstract filter on a recipe.
*
* A filter should have a single purpose that is serves and implement this interface
*/
interface AbstractRecipeFilter {
/**
* Filter the given recipe according to the filter class specification.
*
* This function can make changes to the recipe array to carry out the needed changes.
* In order to signal if the JSON file needs updating, the return value must be true if and only if the recipe was changed.
*
* @param array $json The recipe data as array
* @param File $recipe The file containing the recipe for further processing
* @return boolean true, if and only if the recipe was changed
* @throws InvalidRecipeException if the recipe was not correctly filtered
*/
public function apply(array &$json, File $recipe): bool;
}
120 changes: 120 additions & 0 deletions lib/Helper/Filter/DB/RecipeDatesFilter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
<?php

namespace OCA\Cookbook\Helper\Filter\DB;

use DateTime;
use DateTimeImmutable;
use OCA\Cookbook\Helper\Filter\AbstractRecipeFilter;
use OCP\Files\File;

/**
* Ensure the dates of a recipe are valid
*
* This filter will update the recipe to have both valid dateCreated and dateModified.
* If the dates are given in correct format, nothing is changed.
*
* If only the dateModified is given, the dateCreated is set to the same value.
*
* If neither is given, the file modification time of the JSON file is taken into account.
*/
class RecipeDatesFilter implements AbstractRecipeFilter {

private const DATE_CREATED = 'dateCreated';
private const DATE_MODIFIED = 'dateModified';

private const PATTERN_DATE = '\d{4}-\d{2}-\d{2}';
private const PATTERN_TIME = '\d{1,2}:\d{2}:\d{2}(?:\.\d+)?';

/** @var string */
private $patternDate;

public function __construct()
{
$this->patternDate = join('|', [
'^' . self::PATTERN_DATE . '$',
'^' . self::PATTERN_DATE . 'T' . self::PATTERN_TIME . '$'
]);
}

public function apply(array &$json, File $recipe): bool {
$ret = false;

// First ensure the entries are present in general
$this->ensureEntryExists($json, self::DATE_CREATED, $ret);
$this->ensureEntryExists($json, self::DATE_MODIFIED, $ret);

// Ensure the date formats are valid
$this->checkDateFormat($json, self::DATE_CREATED, $ret);
$this->checkDateFormat($json, self::DATE_MODIFIED, $ret);

if(is_null($json['dateCreated'])) {
if (is_null($json['dateModified'])) {
// No dates have been set. Fall back to time of file
$json['dateCreated'] = $this->getTimeFromFile($recipe);
$ret = true;
} else {
// Copy over the modification time to the creation time
$json['dateCreated'] = $json['dateModified'];
$ret = true;
}
}
/*
The else case is not considered:
If only the creation time is given, this is a valid recipe (no modifications so far).
If both are given, no problem is present.
*/

return $ret;
}

private function getTimeFromFile(File $file): string {
$timestamp = $file->getCreationTime();
if($timestamp === 0) {
$timestamp = $file->getUploadTime();
}
if($timestamp === 0) {
$timestamp = $file->getMTime();
}

return $this->getDateFromTimestamp($timestamp);
}

private function getDateFromTimestamp(int $timestamp): string {
$date = new DateTime();
$date->setTimestamp($timestamp);

return $date->format(DateTime::ISO8601);
}

private function ensureEntryExists(array &$json, string $name, bool &$ret) {
if(!array_key_exists($name, $json)) {
$json[$name] = null;
$ret = true;
}
}

private function checkDateFormat(array &$json, string $name, bool &$ret) {
if($json[$name] === null) {
return;
}

// Check for valid date format
if(preg_match('/' . $this->patternDate . '/', $json[$name]) === 1) {
return;
}

// Last desperate approach: Is it a timestamp?
if(preg_match('/^\d+$/', $json[$name])) {
if($json[$name] > 0) {
$json[$name] = $this->getDateFromTimestamp($json[$name]);
$ret = true;
return;
}
}

// We cannot read the format. Removing it from teh recipe
$json[$name] = null;
$ret = true;
return;
}
}
101 changes: 101 additions & 0 deletions tests/Unit/Helper/Filter/DB/RecipeDatesFilterTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
<?php

namespace OCA\Cookbook\tests\Unit\Helper\Filter\DB;

use OCA\Cookbook\Helper\Filter\DB\RecipeDatesFilter;
use OCP\Files\File;
use PHPUnit\Framework\MockObject\Stub;
use PHPUnit\Framework\TestCase;

/**
* @covers OCA\Cookbook\Helper\Filter\DB\RecipeDatesFilter
*/
class RecipeDatesFilterTest extends TestCase {
/** @var RecipeDatesFilter */
private $dut;

protected function setUp(): void
{
$this->dut = new RecipeDatesFilter();
}

public function dpFromJson() {
yield ['2022-07-06T11:08:54', null, '2022-07-06T11:08:54', null, false];
yield [1657098534, 0, '2022-07-06T09:08:54+0000', null, true];
yield [1657098534, 1657098540, '2022-07-06T09:08:54+0000', '2022-07-06T09:09:00+0000', true];
yield [null, 1657098540, '2022-07-06T09:09:00+0000', '2022-07-06T09:09:00+0000', true];
yield [0, 1657098540, '2022-07-06T09:09:00+0000', '2022-07-06T09:09:00+0000', true];
}

/**
* @dataProvider dpFromJson
*/
public function testFilterFromJson($created, $modified, $expectedCreation, $expectedModification, $updated) {
$recipe = [
'name' => 'my Recipe',
'dateCreated' => $created,
'dateModified' => $modified
];
$copy = $recipe;

$file = $this->createStub(File::class);

$ret = $this->dut->apply($recipe, $file);

$this->assertEquals($updated, $ret, 'Reporting of modification status');
$this->assertEquals($expectedCreation, $recipe['dateCreated'], 'Wrong creation date');
$this->assertEquals($expectedModification, $recipe['dateModified'], 'Wrong modification date');

unset($recipe['dateCreated']);
unset($recipe['dateModified']);
unset($copy['dateCreated']);
unset($copy['dateModified']);

$this->assertEquals($copy, $recipe, 'Other entries must not change.');
}

public function dpFromFile() {
yield ['2022-07-06T09:08:54+0000', false, false, 1657098534, 1657098535, 1657098536];
yield ['2022-07-06T09:08:55+0000', false, false, 0, 1657098535, 1657098536];
yield ['2022-07-06T09:08:56+0000', false, false, 0, 0, 1657098536];
yield ['2022-07-06T09:08:54+0000', true, true, 1657098534, 1657098535, 1657098536];
}

/**
* @dataProvider dpFromFile
*/
public function testFilterFromFile($creation, $creationPresent, $modificationPresent, $creationTime, $uploadTime, $mTime) {
$recipe = [
'name' => 'my Recipe',
];
if($creationPresent) {
$recipe['dateCreated'] = null;
}
if($modificationPresent) {
$recipe['dateModified'] = null;
}

$copy = $recipe;

/** @var Stub|File */
$file = $this->createStub(File::class);
$file->method('getCreationTime')->willReturn($creationTime);
$file->method('getUploadTime')->willReturn($uploadTime);
$file->method('getMTime')->willReturn($mTime);

$ret = $this->dut->apply($recipe, $file);

$this->assertTrue($ret, 'Reporting of modification status');

$this->assertEquals($creation, $recipe['dateCreated'], 'Wrong creation date');
$this->assertNull($recipe['dateModified'], 'Wrong modification date');

unset($recipe['dateCreated']);
unset($recipe['dateModified']);
unset($copy['dateCreated']);
unset($copy['dateModified']);

$this->assertEquals($copy, $recipe, 'Other entries must not change.');

}
}

0 comments on commit 44e85bf

Please sign in to comment.