/ */ class P4Cms_Record_FilterTest extends TestCase { /** * Test creation of filters with good specifications. */ public function testGoodFilter() { $tests = array( array( 'label' => __LINE__ .': simple match', 'filter' => P4Cms_Record_Filter::create()->add('field', 'value'), 'expression' => 'attr-field~=\\^value\\$' ), array( 'label' => __LINE__ .': match containing wildcards', 'filter' => P4Cms_Record_Filter::create()->add('field', '*value...'), 'expression' => 'attr-field~=\\^\\\*value\\.\\.\\.\\$' ), array( 'label' => __LINE__ .': match containing regex characters', 'filter' => P4Cms_Record_Filter::create()->add('field', '$v?(a)l[u]e|^'), 'expression' => 'attr-field~=\\^\\\\\$v\\\?\\\\\(a\\\\\)l\\\\\[u\\\\\]e\\\\\|\\\\\^\\$' ), array( 'label' => __LINE__ .': match containing newline/return characters', 'filter' => P4Cms_Record_Filter::create()->add('field', "va\nl\rue"), 'expression' => "attr-field~=\^va\\\\\\\n" . "l\\\\\\\r" .'ue\\$' ), array( 'label' => __LINE__ .': multi-field match', 'filter' => P4Cms_Record_Filter::create() ->add('field', 'value') ->add('foo', 'bar'), 'expression' => 'attr-field~=\\^value\\$ & attr-foo~=\\^bar\\$' ), array( 'label' => __LINE__ .': multi-value match', 'filter' => P4Cms_Record_Filter::create() ->add('field', 'value') ->add('foo', array('bar', 'bof')), 'expression' => 'attr-field~=\\^value\\$ & (attr-foo~=\\^bar\\$ | attr-foo~=\\^bof\\$)' ), array( 'label' => __LINE__ .': multi-value negated match', 'filter' => P4Cms_Record_Filter::create() ->add('field', 'value') ->add('foo', array('bar', 'bof'), '!='), 'expression' => 'attr-field~=\\^value\\$ &^ (attr-foo~=\\^bar\\$ | attr-foo~=\\^bof\\$)' ), array( 'label' => __LINE__ .': inverted match', 'filter' => P4Cms_Record_Filter::create()->add( 'field', 'value', P4Cms_Record_Filter::COMPARE_NOT_EQUAL ), 'expression' => '^attr-field~=\\^value\\$' ), array( 'label' => __LINE__ .': case-insensitive match', 'filter' => P4Cms_Record_Filter::create()->add( 'field', 'value', P4Cms_Record_Filter::COMPARE_EQUAL, null, true ), 'expression' => 'attr-field~=\\^[Vv][Aa][Ll][Uu][Ee]\\$' ), array( 'label' => __LINE__ .': regex match', 'filter' => P4Cms_Record_Filter::create()->add( 'field', 'value', P4Cms_Record_Filter::COMPARE_REGEX ), 'expression' => 'attr-field~=value' ), array( 'label' => __LINE__ .': regex match, case-sensitive', 'filter' => P4Cms_Record_Filter::create()->add( 'field', 'value', P4Cms_Record_Filter::COMPARE_REGEX, null, false ), 'expression' => 'attr-field~=value' ), array( 'label' => __LINE__ .': regex match, with wildcards', 'filter' => P4Cms_Record_Filter::create()->add( 'field', '.*value...', P4Cms_Record_Filter::COMPARE_REGEX, null, false ), 'expression' => 'attr-field~=.*value...' ), array( 'label' => __LINE__ .': inverted regex match', 'filter' => P4Cms_Record_Filter::create()->add( 'field', 'value', P4Cms_Record_Filter::COMPARE_NOT_REGEX ), 'expression' => '^attr-field~=value' ), array( 'label' => __LINE__ .': regex match alternatives', 'filter' => P4Cms_Record_Filter::create()->add( 'field', '(V|v)alue', P4Cms_Record_Filter::COMPARE_REGEX, null, false ), 'expression' => 'attr-field~=\\(V\\|v\\)alue' ), array( 'label' => __LINE__ .': regex match square brackets', 'filter' => P4Cms_Record_Filter::create()->add( 'field', '^[Vv]alue$', P4Cms_Record_Filter::COMPARE_REGEX, null, false ), 'expression' => 'attr-field~=\\^[Vv]alue\\$' ), array( 'label' => __LINE__ .': regex match square brackets, nocase', 'filter' => P4Cms_Record_Filter::create()->add( 'field', 'va[lX]ue', P4Cms_Record_Filter::COMPARE_REGEX, null, true ), 'expression' => 'attr-field~=[Vv][Aa][LlXx][Uu][Ee]' ), array( 'label' => __LINE__ .': regex match square brackets, nocase, with escapes', 'filter' => P4Cms_Record_Filter::create()->add( 'field', 'va[lX\\]\\\\]ue', P4Cms_Record_Filter::COMPARE_REGEX, null, true ), 'expression' => 'attr-field~=[Vv][Aa][LlXx\\\\]\\\\\\\\][Uu][Ee]' ), array( 'label' => __LINE__ .': regex match question mark', 'filter' => P4Cms_Record_Filter::create()->add( 'field', '^v?alue$', P4Cms_Record_Filter::COMPARE_REGEX, null, false ), 'expression' => 'attr-field~=\^v?alue\$' ), array( 'label' => __LINE__ .': empty string match', 'filter' => P4Cms_Record_Filter::create()->add('field', ''), 'expression' => 'attr-field~=\\^\\$' ), array( 'label' => __LINE__ .': null match', 'filter' => P4Cms_Record_Filter::create()->add('field', null), 'expression' => 'attr-field~=\\^\\$' ), array( 'label' => __LINE__ .': simple contains match', 'filter' => P4Cms_Record_Filter::create()->add( 'field', 'foo', P4Cms_Record_Filter::COMPARE_CONTAINS ), 'expression' => 'attr-field~=foo' ), array( 'label' => __LINE__ .': case-insensitive contains match', 'filter' => P4Cms_Record_Filter::create()->add( 'field', 'foo', P4Cms_Record_Filter::COMPARE_CONTAINS, null, true ), 'expression' => 'attr-field~=[Ff][Oo][Oo]' ), array( 'label' => __LINE__ .': contains match with special chars', 'filter' => P4Cms_Record_Filter::create()->add( 'field', 'foo bar-', P4Cms_Record_Filter::COMPARE_CONTAINS ), 'expression' => 'attr-field~=foo\ bar\-' ), ); foreach ($tests as $test) { $this->assertSame( $test['expression'], $test['filter']->getExpression(), $test['label'] ); } } /** * Test filter construction with an array of options. */ public function testSimpleArrayConstruction() { $simpleFilterConfig = array( 'fields' => array('title' => 'News', 'body' => 'today'), 'fstat' => array('headAction' => 'edit', 'headType' => 'xtext'), 'search' => array('fields' => array('title', 'body'), 'query' => 'search keywords'), 'categories' => array('foo', 'bar') ); $simpleArrayFilter = new P4Cms_Record_Filter($simpleFilterConfig); $simpleManualFilter = P4Cms_Record_Filter::create() ->add('title', 'News') ->add('body', 'today') ->addFstat('headAction', 'edit') ->addFstat('headType', 'xtext') ->addSearch(array('title', 'body'), 'search keywords') ->setOption('categories', array('foo', 'bar')); $this->assertSame( $simpleManualFilter->getExpression(), $simpleArrayFilter->getExpression(), __LINE__ .': P4Cms_Record_Filter constructed with an array of options' ); $this->assertSame( array('foo', 'bar'), $simpleArrayFilter->getOption('categories'), __LINE__ .': extra options stored in filter' ); } /** * Test filter construction with an array of options. */ public function testComplexArrayConstruction() { $complexFilterConfig = array( 'fields' => array( array( 'field' => 'title', 'value' => 'News', 'comparison' => P4Cms_Record_Filter::COMPARE_EQUAL, 'connective' => P4Cms_Record_Filter::CONNECTIVE_OR, 'caseInsensitive' => true), array( 'field' => 'body', 'value' => 'today', 'comparison' => P4Cms_Record_Filter::COMPARE_CONTAINS, 'connective' => P4Cms_Record_Filter::CONNECTIVE_OR)), 'fstat' => array( array( 'field' => 'headAction', 'value' => 'edit', 'comparison' => P4Cms_Record_Filter::COMPARE_EQUAL, 'connective' => P4Cms_Record_Filter::CONNECTIVE_OR, 'caseInsensitive' => false), array( 'field' => 'headType', 'value' => 'xtext', 'comparison' => P4Cms_Record_Filter::COMPARE_EQUAL, 'connective' => P4Cms_Record_Filter::CONNECTIVE_AND, 'caseInsensitive' => false)), 'search' => array('fields' => array('title', 'body'), 'query' => 'search keywords'), 'categories' => array('foo', 'bar') ); $complexArrayFilter = new P4Cms_Record_Filter($complexFilterConfig); $complexManualFilter = P4Cms_Record_Filter::create() ->add('title', 'News', P4Cms_Record_Filter::COMPARE_EQUAL, P4Cms_Record_Filter::CONNECTIVE_OR, true) ->add('body', 'today', P4Cms_Record_Filter::COMPARE_CONTAINS, P4Cms_Record_Filter::CONNECTIVE_OR) ->addFstat( 'headAction', 'edit', P4Cms_Record_Filter::COMPARE_EQUAL, P4Cms_Record_Filter::CONNECTIVE_OR, false ) ->addFstat( 'headType', 'xtext', P4Cms_Record_Filter::COMPARE_EQUAL, P4Cms_Record_Filter::CONNECTIVE_AND, false ) ->addSearch(array('title', 'body'), 'search keywords') ->setOption('categories', array('foo', 'bar')); $this->assertSame( $complexManualFilter->getExpression(), $complexArrayFilter->getExpression(), __LINE__ .': P4Cms_Record_Filter constructed with an array of options' ); $this->assertSame( array('foo', 'bar'), $complexArrayFilter->getOption('categories'), __LINE__ .': extra options stored in filter' ); } /** * Test filter construction with a string. */ public function testStringConstruction() { $stringFilter = new P4Cms_Record_Filter(P4Cms_Record_Filter::FALSE_EXPRESSION); $this->assertSame( P4Cms_Record_Filter::FALSE_EXPRESSION, $stringFilter->getExpression(), __LINE__ .': P4Cms_Record_Filter constructed with a the false expression' ); $stringFilter = new P4Cms_Record_Filter('foobar'); $this->assertSame( 'foobar', $stringFilter->getExpression(), __LINE__ .': P4Cms_Record_Filter constructed with a string' ); } /** * Test building a filter object with a missing field option. */ public function testMissingFields() { $arrayFilterConfig = array( 'fields' => array( array( 'field' => '', 'value' => 'News')), 'fstat' => array(), 'search' => array() ); $this->setExpectedException( 'InvalidArgumentException', 'Cannot add condition. Field must be a non-empty string.' ); $filter = new P4Cms_Record_Filter($arrayFilterConfig); } /** * Test storing and retrieving custom filter options that don't correspond to * built-in options. */ public function testFilterOptions() { $optionFilter = new P4Cms_Record_Filter(); $newFilter = $optionFilter->setOption('test', array('foo', 'bar')); $this->assertInstanceOf( 'P4Cms_Record_Filter', $newFilter, __LINE__ .': P4Cms_Record_Filter object returned from setOption()' ); $this->assertSame( array('foo', 'bar'), $newFilter->getOption('test'), __LINE__ .': test options stored in filter' ); $this->assertNull( $newFilter->getOption('does not exist'), __LINE__ .': null returned for option that does not exist.' ); } /** * Test behaviour of filters with invalid specifications. */ public function testBadFilter() { $tests = array( array( 'label' => __LINE__ .': null field', 'field' => null, 'value' => '', 'comparison' => '', 'connective' => '', 'caseInsensitive' => '', 'error' => array( 'InvalidArgumentException' => 'Cannot add condition. Field must be a non-empty string.' ), ), array( 'label' => __LINE__ .': empty field', 'field' => '', 'value' => '', 'comparison' => '', 'connective' => '', 'caseInsensitive' => '', 'error' => array( 'InvalidArgumentException' => 'Cannot add condition. Field must be a non-empty string.' ), ), array( 'label' => __LINE__ .': non-string field', 'field' => array(), 'value' => '', 'comparison' => '', 'connective' => '', 'caseInsensitive' => '', 'error' => array( 'InvalidArgumentException' => 'Cannot add condition. Field must be a non-empty string.' ), ), array( 'label' => __LINE__ .': empty array value', 'field' => 'field', 'value' => array(), 'comparison' => '', 'connective' => '', 'caseInsensitive' => '', 'error' => array( 'InvalidArgumentException' => 'Cannot add condition.' . ' Value must be null, a string or an array of strings.' ), ), array( 'label' => __LINE__ .': array value containing non-string', 'field' => 'field', 'value' => array(array()), 'comparison' => P4Cms_Record_Filter::COMPARE_EQUAL, 'connective' => P4Cms_Record_Filter::CONNECTIVE_AND, 'caseInsensitive' => '', 'error' => array( 'InvalidArgumentException' => 'Cannot add condition.' . ' Value array must contain only strings.' ), ), array( 'label' => __LINE__ .': object value', 'field' => 'field', 'value' => new stdClass, 'comparison' => '', 'connective' => '', 'caseInsensitive' => '', 'error' => array( 'InvalidArgumentException' => 'Cannot add condition.' . ' Value must be null, a string or an array of strings.' ), ), array( 'label' => __LINE__ .': empty comparison', 'field' => 'field', 'value' => 'value', 'comparison' => '', 'connective' => '', 'caseInsensitive' => '', 'error' => array( 'InvalidArgumentException' => 'Cannot add condition. Invalid comparison operator specified.' ), ), array( 'label' => __LINE__ .': empty connective', 'field' => 'field', 'value' => 'value', 'comparison' => P4Cms_Record_Filter::COMPARE_EQUAL, 'connective' => '', 'caseInsensitive' => '', 'error' => array( 'InvalidArgumentException' => 'Cannot add condition. Invalid connective specified.' ), ), ); foreach ($tests as $test) { extract($test); try { P4Cms_Record_Filter::create()->add($field, $value, $comparison, $connective, $caseInsensitive); $this->fail($label.' Unexpected success'); } catch (PHPUnit_Framework_AssertionFailedError $e) { $this->fail($e->getMessage()); } catch (PHPUnit_Framework_ExpectationFailedError $e) { $this->fail($e->getMessage()); } catch (Exception $e) { if (!$test['error']) { $this->fail("$label - Unexpected exception (". get_class($e) .') :'. $e->getMessage()); } else { list($class, $error) = each($test['error']); $this->assertEquals( $class, get_class($e), "$label - expected exception class: ". $e->getMessage() ); $this->assertEquals( $error, $e->getMessage(), "$label - expected exception message" ); } } } } /** * Test behaviour of subfilters with invalid specifications. */ public function testBadSubFilter() { $tests = array( array( 'label' => __LINE__ .': invalid filter type', 'filter' => '', 'connective' => '' ), array( 'label' => __LINE__ .': invalid connective', 'filter' => P4Cms_Record_Filter::create(), 'connective' => '' ), ); foreach ($tests as $test) { extract($test); try { P4Cms_Record_Filter::create()->addSubFilter($filter, $connective); $this->fail($label.' Expected exception'); } catch (InvalidArgumentException $e) { $this->assertTrue(true, $label); } } } }