1<?php 2 3namespace dokuwiki\plugin\structnotification\test; 4 5use dokuwiki\plugin\struct\meta\Value; 6use dokuwiki\plugin\struct\meta\Column; 7 8/** 9 * Unit tests for structnotification plugin 10 * 11 * @group plugin_structnotification 12 * @group plugins 13 */ 14class NotificationTest extends \DokuWikiTest 15{ 16 protected $pluginsEnabled = ['struct', 'structnotification']; 17 18 /** @var \action_plugin_structnotification_notification */ 19 protected $action; 20 21 public function setUp(): void 22 { 23 parent::setUp(); 24 $this->action = new \action_plugin_structnotification_notification(); 25 } 26 27 /** 28 * Test date parsing with different formats 29 */ 30 public function test_predicateTrue_date_formats() 31 { 32 $datetime = date('Y-m-d H:i:s', strtotime('+5 days')); 33 $this->assertTrue($this->callPrivateMethod('predicateTrue', [$datetime, 'before', 10])); 34 35 $datetime = date('Y/d/m', strtotime('+5 days')); 36 $this->assertTrue($this->callPrivateMethod('predicateTrue', [$datetime, 'before', 10])); 37 } 38 39 /** 40 * Test predicateTrue() with 'before' operator 41 */ 42 public function test_predicateTrue_before() 43 { 44 // Test date that is 5 days from now should match "10 days before" 45 $futureDate = date('Y-m-d', strtotime('+5 days')); 46 $this->assertTrue($this->callPrivateMethod('predicateTrue', [$futureDate, 'before', 10])); 47 48 // Test date that is 15 days from now should not match "10 days before" 49 $farFutureDate = date('Y-m-d', strtotime('+15 days')); 50 $this->assertFalse($this->callPrivateMethod('predicateTrue', [$farFutureDate, 'before', 10])); 51 52 // Test today should match "1 day before" 53 $today = date('Y-m-d'); 54 $this->assertTrue($this->callPrivateMethod('predicateTrue', [$today, 'before', 1])); 55 } 56 57 /** 58 * Test predicateTrue() with 'after' operator 59 */ 60 public function test_predicateTrue_after() 61 { 62 // Test date that is 15 days ago should match "10 days after" 63 $farPastDate = date('Y-m-d', strtotime('-15 days')); 64 $this->assertTrue($this->callPrivateMethod('predicateTrue', [$farPastDate, 'after', 10])); 65 66 // Test date that is 5 days ago should not match "10 days after" 67 $pastDate = date('Y-m-d', strtotime('-5 days')); 68 $this->assertFalse($this->callPrivateMethod('predicateTrue', [$pastDate, 'after', 10])); 69 70 // Test today should not match "1 day after" 71 $today = date('Y-m-d'); 72 $this->assertFalse($this->callPrivateMethod('predicateTrue', [$today, 'after', 1])); 73 } 74 75 /** 76 * Test predicateTrue() with invalid operator 77 */ 78 public function test_predicateTrue_invalid_operator() 79 { 80 $today = date('Y-m-d'); 81 $this->assertFalse($this->callPrivateMethod('predicateTrue', [$today, 'invalid', 5])); 82 } 83 84 /** 85 * Test edge cases for predicateTrue() 86 */ 87 public function test_predicateTrue_edge_cases() 88 { 89 // Test with 0 days - both should match today 90 $today = date('Y-m-d'); 91 $this->assertTrue($this->callPrivateMethod('predicateTrue', [$today, 'before', 0])); 92 $this->assertTrue($this->callPrivateMethod('predicateTrue', [$today, 'after', 0])); 93 } 94 95 /** 96 * Test replacePlaceholders() 97 */ 98 public function test_replacePlaceholders() 99 { 100 // Mock Value objects 101 $value1 = $this->createMockValue('schema1', 'field1', 'Test Value 1'); 102 $value2 = $this->createMockValue('schema1', 'field2', 'Test Value 2'); 103 $values = [$value1, $value2]; 104 105 $message = 'Hello @@schema1.field1@@, your @@schema1.field2@@ is ready.'; 106 $expected = 'Hello Test Value 1, your Test Value 2 is ready.'; 107 108 $result = $this->callPrivateMethod('replacePlaceholders', [$message, $values]); 109 $this->assertEquals($expected, $result); 110 } 111 112 /** 113 * Test replacePlaceholders with no matches 114 */ 115 public function test_replacePlaceholders_no_matches() 116 { 117 $value1 = $this->createMockValue('schema1', 'field1', 'Test Value 1'); 118 $values = [$value1]; 119 120 $message = 'Hello @@schema2.field2@@, no replacement here.'; 121 $expected = 'Hello @@schema2.field2@@, no replacement here.'; 122 123 $result = $this->callPrivateMethod('replacePlaceholders', [$message, $values]); 124 $this->assertEquals($expected, $result); 125 } 126 127 /** 128 * Test early return in addFiltersToSearch() 129 */ 130 public function test_addFiltersToSearch_empty_filters() 131 { 132 $search = $this->createMock(\dokuwiki\plugin\struct\meta\Search::class); 133 $search->expects($this->never())->method('addFilter'); 134 135 $this->callPrivateMethod('addFiltersToSearch', [&$search, '']); 136 } 137 138 /** 139 * Test addFiltersToSearch method with actual filters 140 */ 141 public function test_addFiltersToSearch_with_filters() 142 { 143 $search = $this->createMock(\dokuwiki\plugin\struct\meta\Search::class); 144 145 // Track the calls to addFilter 146 $callCount = 0; 147 $expectedCalls = [ 148 ['field1', 'value1', '=', 'AND'], 149 ['field2', '10', '>', 'AND'] 150 ]; 151 152 // Expect addFilter to be called twice (once for each filter line) 153 $search->expects($this->exactly(2)) 154 ->method('addFilter') 155 ->willReturnCallback(function($colname, $value, $comp, $logic) use (&$callCount, $expectedCalls) { 156 $this->assertEquals($expectedCalls[$callCount][0], $colname); 157 $this->assertEquals($expectedCalls[$callCount][1], $value); 158 $this->assertEquals($expectedCalls[$callCount][2], $comp); 159 $this->assertEquals($expectedCalls[$callCount][3], $logic); 160 $callCount++; 161 }); 162 163 // Add filters 164 $filters = "field1 = value1\r\nfield2 > 10"; 165 $this->callPrivateMethod('addFiltersToSearch', [&$search, $filters]); 166 } 167 168 /** 169 * Helper method to call private methods 170 */ 171 private function callPrivateMethod($methodName, $args = []) 172 { 173 $reflection = new \ReflectionClass($this->action); 174 $method = $reflection->getMethod($methodName); 175 $method->setAccessible(true); 176 return $method->invokeArgs($this->action, $args); 177 } 178 179 /** 180 * Helper method to create mock Value objects 181 */ 182 private function createMockValue($schema, $label, $rawValue) 183 { 184 $column = $this->createMock(Column::class); 185 $column->method('getTable')->willReturn($schema); 186 $column->method('getLabel')->willReturn($label); 187 188 $value = $this->createMock(Value::class); 189 $value->method('getColumn')->willReturn($column); 190 $value->method('getRawValue')->willReturn($rawValue); 191 $value->method('getDisplayValue')->willReturn($rawValue); 192 193 return $value; 194 } 195} 196