Compare commits

..

2 Commits

Author SHA1 Message Date
Steve Boyd
226e278719
Merge 0d824ccaab into 6bb9a0b33d 2024-10-14 06:53:43 +00:00
Steve Boyd
0d824ccaab NEW Validate DBFields 2024-10-14 19:53:35 +13:00
17 changed files with 175 additions and 113 deletions

View File

@ -16,8 +16,8 @@ class DateFieldValidator extends FieldValidator
protected function validateValue(): ValidationResult protected function validateValue(): ValidationResult
{ {
$result = ValidationResult::create(); $result = ValidationResult::create();
// Allow empty values // Allow empty strings
if (!$this->value) { if ($this->value === '') {
return $result; return $result;
} }
// Not using symfony/validator because it was allowing d-m-Y format strings // Not using symfony/validator because it was allowing d-m-Y format strings

View File

@ -117,6 +117,31 @@ class TextField extends FormField implements TippableFieldInterface
return $data; return $data;
} }
/**
* Validate this field
*
* @param Validator $validator
* @return bool
*/
public function validate($validator)
{
$result = true;
if (!is_null($this->maxLength) && mb_strlen($this->value ?? '') > $this->maxLength) {
$name = strip_tags($this->Title() ? $this->Title() : $this->getName());
$validator->validationError(
$this->name,
_t(
'SilverStripe\\Forms\\TextField.VALIDATEMAXLENGTH',
'The value for {name} must not exceed {maxLength} characters in length',
['name' => $name, 'maxLength' => $this->maxLength]
),
"validation"
);
$result = false;
}
return $this->extendValidationResult($result, $validator);
}
public function getSchemaValidation() public function getSchemaValidation()
{ {
$rules = parent::getSchemaValidation(); $rules = parent::getSchemaValidation();

View File

@ -31,6 +31,9 @@ class DBCurrency extends DBDecimal
*/ */
public function Nice(): string public function Nice(): string
{ {
if (!is_float($this->value)) {
return '';
}
$val = static::config()->get('currency_symbol') . number_format(abs($this->value ?? 0.0) ?? 0.0, 2); $val = static::config()->get('currency_symbol') . number_format(abs($this->value ?? 0.0) ?? 0.0, 2);
if ($this->value < 0) { if ($this->value < 0) {
return "($val)"; return "($val)";
@ -44,6 +47,9 @@ class DBCurrency extends DBDecimal
*/ */
public function Whole(): string public function Whole(): string
{ {
if (!is_float($this->value)) {
return '';
}
$val = static::config()->get('currency_symbol') . number_format(abs($this->value ?? 0.0) ?? 0.0, 0); $val = static::config()->get('currency_symbol') . number_format(abs($this->value ?? 0.0) ?? 0.0, 0);
if ($this->value < 0) { if ($this->value < 0) {
return "($val)"; return "($val)";
@ -53,15 +59,14 @@ class DBCurrency extends DBDecimal
public function setValue(mixed $value, null|array|ModelData $record = null, bool $markChanged = true): static public function setValue(mixed $value, null|array|ModelData $record = null, bool $markChanged = true): static
{ {
$matches = null; parent::setValue($value, $record, $markChanged);
if (is_numeric($value)) { if (is_string($this->value)) {
$this->value = $value; $symbol = static::config()->get('currency_symbol');
} elseif (preg_match('/-?\$?[0-9,]+(.[0-9]+)?([Ee][0-9]+)?/', $value ?? '', $matches)) { $val = str_replace(['$', ',', $symbol], '', $this->value);
$this->value = str_replace(['$', ',', static::config()->get('currency_symbol')], '', $matches[0] ?? ''); if (is_numeric($val)) {
} else { $this->value = (float) $val;
$this->value = 0; }
} }
return $this; return $this;
} }

View File

@ -57,6 +57,11 @@ class DBDate extends DBField
return $this; return $this;
} }
public function nullValue(): string
{
return '';
}
/** /**
* Parse timestamp or iso8601-ish date into standard iso8601 format * Parse timestamp or iso8601-ish date into standard iso8601 format
* *

View File

@ -27,11 +27,6 @@ class DBDecimal extends DBField
*/ */
protected int $decimalSize = 2; protected int $decimalSize = 2;
/**
* Default value
*/
protected float|int|string $defaultValue = 0;
/** /**
* Create a new Decimal field. * Create a new Decimal field.
*/ */
@ -70,7 +65,7 @@ class DBDecimal extends DBField
$parts = [ $parts = [
'datatype' => 'decimal', 'datatype' => 'decimal',
'precision' => "$this->wholeSize,$this->decimalSize", 'precision' => "$this->wholeSize,$this->decimalSize",
'default' => $this->defaultValue, 'default' => $this->getDefaultValue(),
'arrayValue' => $this->arrayValue 'arrayValue' => $this->arrayValue
]; ];

View File

@ -5,6 +5,7 @@ namespace SilverStripe\ORM\FieldType;
use SilverStripe\Core\Config\Config; use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Validation\FieldValidation\EnumFieldValidator; use SilverStripe\Core\Validation\FieldValidation\EnumFieldValidator;
use SilverStripe\Core\Validation\FieldValidation\MultiEnumFieldValidator; use SilverStripe\Core\Validation\FieldValidation\MultiEnumFieldValidator;
use SilverStripe\Core\Validation\FieldValidation\StringFieldValidator;
use SilverStripe\Forms\CheckboxSetField; use SilverStripe\Forms\CheckboxSetField;
use SilverStripe\Forms\MultiSelectField; use SilverStripe\Forms\MultiSelectField;
use SilverStripe\ORM\Connect\MySQLDatabase; use SilverStripe\ORM\Connect\MySQLDatabase;
@ -16,7 +17,8 @@ use SilverStripe\ORM\DB;
class DBMultiEnum extends DBEnum class DBMultiEnum extends DBEnum
{ {
private static array $field_validators = [ private static array $field_validators = [
// disable parent field validator // disable parent field validators
StringFieldValidator::class => null,
EnumFieldValidator::class => null, EnumFieldValidator::class => null,
// enable multi enum field validator // enable multi enum field validator
MultiEnumFieldValidator::class => ['getEnum'], MultiEnumFieldValidator::class => ['getEnum'],

View File

@ -1691,7 +1691,7 @@ class Member extends DataObject
// Overwrite the Password property with the hashed value // Overwrite the Password property with the hashed value
$this->Password = $encryption_details['password']; $this->Password = $encryption_details['password'];
$this->Salt = $encryption_details['salt']; $this->Salt = $encryption_details['salt'] ?: '';
$this->PasswordEncryption = $encryption_details['algorithm']; $this->PasswordEncryption = $encryption_details['algorithm'];
// If we haven't manually set a password expiry // If we haven't manually set a password expiry

View File

@ -11,20 +11,20 @@ class BooleanFieldValidatorTest extends SapphireTest
public static function provideValidate(): array public static function provideValidate(): array
{ {
return [ return [
'valid-int-1' => [ 'valid-true' => [
'value' => 1,
'expected' => true,
],
'valid-int-0' => [
'value' => 0,
'expected' => true,
],
'invvalid-true' => [
'value' => true, 'value' => true,
'expected' => true,
],
'valid-false' => [
'value' => false,
'expected' => true,
],
'invalid-int-1' => [
'value' => 1,
'expected' => false, 'expected' => false,
], ],
'invalid-false' => [ 'invalid-int-0' => [
'value' => false, 'value' => 0,
'expected' => false, 'expected' => false,
], ],
'invalid-string-1' => [ 'invalid-string-1' => [

View File

@ -15,6 +15,10 @@ class DateFieldValidatorTest extends SapphireTest
'value' => '2020-09-15', 'value' => '2020-09-15',
'expected' => true, 'expected' => true,
], ],
'valid-blank-string' => [
'value' => '',
'expected' => true,
],
'invalid' => [ 'invalid' => [
'value' => '2020-02-30', 'value' => '2020-02-30',
'expected' => false, 'expected' => false,

View File

@ -21,16 +21,16 @@ class EnumFieldValidatorTest extends SapphireTest
'allowedValues' => [123, 456], 'allowedValues' => [123, 456],
'expected' => true, 'expected' => true,
], ],
'valid-none' => [
'value' => '',
'allowedValues' => ['cat', 'dog'],
'expected' => true,
],
'invalid' => [ 'invalid' => [
'value' => 'fish', 'value' => 'fish',
'allowedValues' => ['cat', 'dog'], 'allowedValues' => ['cat', 'dog'],
'expected' => false, 'expected' => false,
], ],
'invalid-none' => [
'value' => '',
'allowedValues' => ['cat', 'dog'],
'expected' => false,
],
'invalid-null' => [ 'invalid-null' => [
'value' => null, 'value' => null,
'allowedValues' => ['cat', 'dog'], 'allowedValues' => ['cat', 'dog'],

View File

@ -6,7 +6,6 @@ use SilverStripe\Dev\SapphireTest;
use SilverStripe\Forms\TextField; use SilverStripe\Forms\TextField;
use SilverStripe\Forms\RequiredFields; use SilverStripe\Forms\RequiredFields;
use SilverStripe\Forms\Tip; use SilverStripe\Forms\Tip;
use PHPUnit\Framework\Attributes\DataProvider;
class TextFieldTest extends SapphireTest class TextFieldTest extends SapphireTest
{ {
@ -46,42 +45,4 @@ class TextFieldTest extends SapphireTest
$textField->setTip(new Tip('TestTip')); $textField->setTip(new Tip('TestTip'));
$this->assertArrayHasKey('tip', $textField->getSchemaDataDefaults()); $this->assertArrayHasKey('tip', $textField->getSchemaDataDefaults());
} }
public static function provideSetValue(): array
{
return [
'string' => [
'value' => 'fish',
'expected' => 'fish',
],
'string-blank' => [
'value' => '',
'expected' => '',
],
'null' => [
'value' => null,
'expected' => '',
],
'zero' => [
'value' => 0,
'expected' => 0,
],
'true' => [
'value' => true,
'expected' => true,
],
'false' => [
'value' => false,
'expected' => false,
],
];
}
#[DataProvider('provideSetValue')]
public function testSetValue(mixed $value, mixed $expected): void
{
$field = new TextField('TestField');
$field->setValue($value);
$this->assertSame($expected, $field->getValue());
}
} }

View File

@ -5,6 +5,7 @@ namespace SilverStripe\ORM\Tests;
use SilverStripe\Forms\CurrencyField; use SilverStripe\Forms\CurrencyField;
use SilverStripe\ORM\FieldType\DBCurrency; use SilverStripe\ORM\FieldType\DBCurrency;
use SilverStripe\Dev\SapphireTest; use SilverStripe\Dev\SapphireTest;
use PHPUnit\Framework\Attributes\DataProvider;
class DBCurrencyTest extends SapphireTest class DBCurrencyTest extends SapphireTest
{ {
@ -15,11 +16,6 @@ class DBCurrencyTest extends SapphireTest
// Test basic operation // Test basic operation
'$50.00' => ['$50.00', '$50'], '$50.00' => ['$50.00', '$50'],
// Test removal of junk text
'this is -50.29 dollars' => ['($50.29)', '($50)'],
'this is -50.79 dollars' => ['($50.79)', '($51)'],
'this is 50.79 dollars' => ['$50.79', '$51'],
// Test negative numbers // Test negative numbers
'-1000' => ['($1,000.00)','($1,000)'], '-1000' => ['($1,000.00)','($1,000)'],
'-$2,000' => ['($2,000.00)', '($2,000)'], '-$2,000' => ['($2,000.00)', '($2,000)'],
@ -30,9 +26,6 @@ class DBCurrencyTest extends SapphireTest
// Test scientific notation // Test scientific notation
'5.68434188608E-14' => ['$0.00', '$0'], '5.68434188608E-14' => ['$0.00', '$0'],
'5.68434188608E7' => ['$56,843,418.86', '$56,843,419'], '5.68434188608E7' => ['$56,843,418.86', '$56,843,419'],
"Sometimes Es are still bad: 51 dollars, even though they\'re used in scientific notation"
=> ['$51.00', '$51'],
"What about 5.68434188608E7 in the middle of a string" => ['$56,843,418.86', '$56,843,419'],
]; ];
foreach ($tests as $value => $niceValues) { foreach ($tests as $value => $niceValues) {
@ -51,4 +44,75 @@ class DBCurrencyTest extends SapphireTest
$this->assertInstanceOf(CurrencyField::class, $scaffoldedField); $this->assertInstanceOf(CurrencyField::class, $scaffoldedField);
} }
public static function provideSetValue(): array
{
// Most test cases covered by DBCurrencyTest, only testing a subset here
return [
'currency' => [
'value' => '$1.23',
'expected' => 1.23,
],
'negative-currency' => [
'value' => "-$1.23",
'expected' => -1.23,
],
'scientific-1' => [
'value' => 5.68434188608E-14,
'expected' => 5.68434188608E-14,
],
'scientific-2' => [
'value' => 5.68434188608E7,
'expected' => 56843418.8608,
],
'scientific-1-string' => [
'value' => '5.68434188608E-14',
'expected' => 5.68434188608E-14,
],
'scientific-2-string' => [
'value' => '5.68434188608E7',
'expected' => 56843418.8608,
],
'int' => [
'value' => 1,
'expected' => 1.0,
],
'string-int' => [
'value' => "1",
'expected' => 1.0,
],
'string-float' => [
'value' => '1.2',
'expected' => 1.2,
],
'value-in-string' => [
'value' => 'this is 50.29 dollars',
'expected' => 'this is 50.29 dollars',
],
'scientific-value-in-string' => [
'value' => '5.68434188608E7 a string',
'expected' => '5.68434188608E7 a string',
],
'value-in-brackets' => [
'value' => '(100)',
'expected' => '(100)',
],
'non-numeric' => [
'value' => 'fish',
'expected' => 'fish',
],
'null' => [
'value' => null,
'expected' => null,
],
];
}
#[DataProvider('provideSetValue')]
public function testSetValue(mixed $value, mixed $expected): void
{
$field = new DBCurrency('MyField');
$field->setValue($value);
$this->assertSame($expected, $field->getValue());
}
} }

View File

@ -79,24 +79,6 @@ class DBDatetimeTest extends SapphireTest
); );
} }
public function testSetNullAndZeroValues()
{
$date = DBDatetime::create_field('Datetime', '');
$this->assertNull($date->getValue(), 'Empty string evaluates to NULL');
$date = DBDatetime::create_field('Datetime', null);
$this->assertNull($date->getValue(), 'NULL is set as NULL');
$date = DBDatetime::create_field('Datetime', false);
$this->assertNull($date->getValue(), 'Boolean FALSE evaluates to NULL');
$date = DBDatetime::create_field('Datetime', '0');
$this->assertEquals('1970-01-01 00:00:00', $date->getValue(), 'String zero is UNIX epoch time');
$date = DBDatetime::create_field('Datetime', 0);
$this->assertEquals('1970-01-01 00:00:00', $date->getValue(), 'Numeric zero is UNIX epoch time');
}
public function testExtendedDateTimes() public function testExtendedDateTimes()
{ {
$date = DBDatetime::create_field('Datetime', '1600-10-10 15:32:24'); $date = DBDatetime::create_field('Datetime', '1600-10-10 15:32:24');

View File

@ -159,11 +159,11 @@ class DBEnumTest extends SapphireTest
], ],
'empty-string' => [ 'empty-string' => [
'value' => '', 'value' => '',
'expected' => 'blue', 'expected' => '',
], ],
'null' => [ 'null' => [
'value' => null, 'value' => null,
'expected' => 'blue', 'expected' => null,
], ],
]; ];
} }
@ -175,4 +175,14 @@ class DBEnumTest extends SapphireTest
$field->setValue($value); $field->setValue($value);
$this->assertSame($expected, $field->getValue()); $this->assertSame($expected, $field->getValue());
} }
public function testSaveDefaultValue()
{
$obj = new FieldType\DBEnumTestObject();
$obj->Colour = null;
$id = $obj->write();
// Fetch the object from the database
$colour = FieldType\DBEnumTestObject::get()->byID($id)->Colour;
$this->assertEquals('Red', $colour);
}
} }

View File

@ -425,16 +425,20 @@ class DBFieldTest extends SapphireTest
{ {
$expectedBaseDefault = null; $expectedBaseDefault = null;
$expectedDefaults = [ $expectedDefaults = [
DBBoolean::class => 0, DBBoolean::class => false,
DBDecimal::class => 0.0, DBDecimal::class => 0.0,
DBInt::class => 0, DBInt::class => 0,
DBFloat::class => 0.0, DBFloat::class => 0.0,
]; ];
$count = 0;
$classes = ClassInfo::subclassesFor(DBField::class); $classes = ClassInfo::subclassesFor(DBField::class);
foreach ($classes as $class) { foreach ($classes as $class) {
if (is_a($class, TestOnly::class, true)) { if (is_a($class, TestOnly::class, true)) {
continue; continue;
} }
if (!str_starts_with($class, 'SilverStripe\ORM\FieldType')) {
continue;
}
$reflector = new ReflectionClass($class); $reflector = new ReflectionClass($class);
if ($reflector->isAbstract()) { if ($reflector->isAbstract()) {
continue; continue;
@ -448,7 +452,11 @@ class DBFieldTest extends SapphireTest
} }
$field = new $class('TestField'); $field = new $class('TestField');
$this->assertSame($expected, $field->getValue(), $class); $this->assertSame($expected, $field->getValue(), $class);
$count++;
} }
// Assert that we have tested all classes e.g. namespace wasn't changed, no new classes were added
// that haven't been tested
$this->assertSame(29, $count);
} }
public function testFieldValidatorConfig(): void public function testFieldValidatorConfig(): void

View File

@ -17,7 +17,7 @@ class DBForiegnKeyTest extends SapphireTest
], ],
'string' => [ 'string' => [
'value' => '2', 'value' => '2',
'expected' => '2', 'expected' => 2,
], ],
'zero' => [ 'zero' => [
'value' => 0, 'value' => 0,

View File

@ -24,22 +24,23 @@ class DecimalTest extends SapphireTest
{ {
parent::setUp(); parent::setUp();
$this->testDataObject = $this->objFromFixture(DecimalTest\TestObject::class, 'test-dataobject'); $this->testDataObject = $this->objFromFixture(DecimalTest\TestObject::class, 'test-dataobject');
$x=1;
} }
public function testDefaultValue() public function testDefaultValue()
{ {
$this->assertEquals( $this->assertSame(
$this->testDataObject->MyDecimal1,
0, 0,
$this->testDataObject->MyDecimal1,
'Database default for Decimal type is 0' 'Database default for Decimal type is 0'
); );
} }
public function testSpecifiedDefaultValue() public function testSpecifiedDefaultValue()
{ {
$this->assertEquals( $this->assertSame(
$this->testDataObject->MyDecimal2,
2.5, 2.5,
$this->testDataObject->MyDecimal2,
'Default value for Decimal type is set to 2.5' 'Default value for Decimal type is set to 2.5'
); );
} }
@ -61,28 +62,28 @@ class DecimalTest extends SapphireTest
public function testLongValueStoredCorrectly() public function testLongValueStoredCorrectly()
{ {
$this->assertEquals( $this->assertSame(
$this->testDataObject->MyDecimal5,
1.0, 1.0,
$this->testDataObject->MyDecimal5,
'Long default long decimal value is rounded correctly' 'Long default long decimal value is rounded correctly'
); );
$this->assertEqualsWithDelta( $this->assertEqualsWithDelta(
$this->testDataObject->MyDecimal5,
0.99999999999999999999, 0.99999999999999999999,
$this->testDataObject->MyDecimal5,
PHP_FLOAT_EPSILON, PHP_FLOAT_EPSILON,
'Long default long decimal value is correct within float epsilon' 'Long default long decimal value is correct within float epsilon'
); );
$this->assertEquals( $this->assertSame(
$this->testDataObject->MyDecimal6,
8.0, 8.0,
$this->testDataObject->MyDecimal6,
'Long decimal value with a default value is rounded correctly' 'Long decimal value with a default value is rounded correctly'
); );
$this->assertEqualsWithDelta( $this->assertEqualsWithDelta(
$this->testDataObject->MyDecimal6,
7.99999999999999999999, 7.99999999999999999999,
$this->testDataObject->MyDecimal6,
PHP_FLOAT_EPSILON, PHP_FLOAT_EPSILON,
'Long decimal value is within epsilon if longer than allowed number of float digits' 'Long decimal value is within epsilon if longer than allowed number of float digits'
); );