Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
| 1 | <?php |
||
| 23 | class DbQueryTest extends PHPUnit_Framework_TestCase { |
||
| 24 | |||
| 25 | /** |
||
| 26 | * @var DbQuery $object |
||
| 27 | */ |
||
| 28 | protected $object; |
||
| 29 | |||
| 30 | protected $db; |
||
| 31 | |||
| 32 | public function setUp() { |
||
| 33 | parent::setUp(); |
||
| 34 | |||
| 35 | $this->db = new DbEscape(); |
||
| 36 | $this->object = new DbQuery($this->db); |
||
| 37 | } |
||
| 38 | |||
| 39 | public function tearDown() { |
||
| 40 | unset($this->object); |
||
| 41 | parent::tearDown(); |
||
| 42 | } |
||
| 43 | |||
| 44 | /** |
||
| 45 | * @covers ::__construct |
||
| 46 | */ |
||
| 47 | public function test__construct() { |
||
| 48 | $this->assertAttributeEquals($this->db, 'db', $this->object); |
||
| 49 | } |
||
| 50 | |||
| 51 | /** |
||
| 52 | * @covers ::build |
||
| 53 | */ |
||
| 54 | public function testBuild() { |
||
| 55 | $this->assertEquals('DBAL\DbQuery', get_class($query = DbQuery::build($this->db))); |
||
| 56 | $this->assertAttributeEquals($this->db, 'db', $query); |
||
| 57 | } |
||
| 58 | |||
| 59 | /** |
||
| 60 | * @covers ::escape |
||
| 61 | */ |
||
| 62 | public function testEscape() { |
||
| 63 | // TODO - create real DB connection here |
||
| 64 | $this->assertEquals('\0\n\r \\\\z \\\\ \\\' \"', $result = invokeMethod($this->object, 'escape', array("\0\n\r \z \\ ' \""))); |
||
| 65 | $this->assertEquals(19, strlen($result)); |
||
| 66 | } |
||
| 67 | |||
| 68 | /** |
||
| 69 | * @covers ::escapeEmulator |
||
| 70 | */ |
||
| 71 | public function testEscapeEmulator() { |
||
| 72 | $this->assertEquals('\0\n\r \\\\z \\\\ \\\' \"', $result = invokeMethod($this->object, 'escapeEmulator', array("\0\n\r \z \\ ' \""))); |
||
| 73 | $this->assertEquals(19, strlen($result)); |
||
| 74 | } |
||
| 75 | |||
| 76 | /** |
||
| 77 | * @covers ::stringValue |
||
| 78 | */ |
||
| 79 | public function testStringValue() { |
||
| 80 | $this->assertEquals('\'The\"test\\\\of\\\'escape\'', $result = invokeMethod($this->object, 'stringValue', array('The"test\of\'escape'))); |
||
| 81 | } |
||
| 82 | |||
| 83 | /** |
||
| 84 | * @covers ::quote |
||
| 85 | */ |
||
| 86 | public function testQuote() { |
||
| 87 | $this->assertEquals('`The\"test\\\\of\\\'escape`', $result = invokeMethod($this->object, 'quote', array('The"test\of\'escape'))); |
||
| 88 | } |
||
| 89 | |||
| 90 | /** |
||
| 91 | * @covers ::makeAdjustString |
||
| 92 | */ |
||
| 93 | public function testMakeAdjustString() { |
||
| 94 | $this->assertEquals("'test'", $result = invokeMethod($this->object, 'makeAdjustString', array('test', 1))); |
||
| 95 | $this->assertEquals("`f1` = `f1` + ('v1')", $result = invokeMethod($this->object, 'makeAdjustString', array('v1', 'f1'))); |
||
| 96 | } |
||
| 97 | |||
| 98 | /** |
||
| 99 | * @covers ::makeFieldEqualValue |
||
| 100 | */ |
||
| 101 | public function testMakeFieldEqualValue() { |
||
| 102 | $this->assertEquals("'test2'", $result = invokeMethod($this->object, 'makeFieldEqualValue', array('test2', 1))); |
||
| 103 | $this->assertEquals("`f2` = 'v2'", $result = invokeMethod($this->object, 'makeFieldEqualValue', array('v2', 'f2'))); |
||
| 104 | } |
||
| 105 | |||
| 106 | /** |
||
| 107 | * @covers ::quoteTable |
||
| 108 | */ |
||
| 109 | public function testQuoteTable() { |
||
| 110 | $this->assertEquals('`{{The\"test\\\\of\\\'escape}}`', $result = invokeMethod($this->object, 'quoteTable', array('The"test\of\'escape'))); |
||
| 111 | } |
||
| 112 | |||
| 113 | public function dataCastAsDbValue() { |
||
| 114 | return array( |
||
| 115 | array(10, TYPE_INTEGER, 10, TYPE_INTEGER), |
||
| 116 | array(PHP_INT_MAX, TYPE_INTEGER, PHP_INT_MAX, TYPE_INTEGER), |
||
| 117 | |||
| 118 | array(11.0, TYPE_DOUBLE, 11.0, TYPE_DOUBLE), |
||
| 119 | array(PHP_INT_MAX + 1, TYPE_DOUBLE, PHP_INT_MAX + 1, TYPE_DOUBLE), |
||
| 120 | |||
| 121 | array(0, TYPE_INTEGER, false, TYPE_BOOLEAN), |
||
| 122 | array(1, TYPE_INTEGER, true, TYPE_BOOLEAN), |
||
| 123 | |||
| 124 | array('NULL', TYPE_STRING, null, TYPE_NULL), |
||
| 125 | |||
| 126 | array('\'\'', TYPE_STRING, '', TYPE_EMPTY), |
||
| 127 | |||
| 128 | array('\'\'', TYPE_STRING, '', TYPE_STRING), |
||
| 129 | array('\'\0\n\r \\\\z \\\\ \\\' \"\'', TYPE_STRING, "\0\n\r \z \\ ' \"", TYPE_STRING), |
||
| 130 | array('\'The\"test\\\\of\\\'escape\'', TYPE_STRING, 'The"test\of\'escape', TYPE_STRING), |
||
| 131 | |||
| 132 | array('\'a:0:{}\'', TYPE_STRING, array(), TYPE_ARRAY), |
||
| 133 | array('\'a:1:{s:4:\"k\\\'ey\";s:9:\"st\\\'ri\"n\\\\g\";}\'', TYPE_STRING, array('k\'ey' => 'st\'ri"n\\g'), TYPE_ARRAY), |
||
| 134 | ); |
||
| 135 | } |
||
| 136 | |||
| 137 | /** |
||
| 138 | * @covers ::makeValueSafe |
||
| 139 | * @dataProvider dataCastAsDbValue |
||
| 140 | */ |
||
| 141 | public function testCastAsDbValue($expected, $type, $value, $originalType) { |
||
| 142 | if ($originalType != TYPE_NULL && $originalType != TYPE_EMPTY) { |
||
| 143 | $this->assertInternalType($originalType, $value); |
||
| 144 | } |
||
| 145 | $this->assertEquals($expected, invokeMethod($this->object, 'makeValueSafe', array($value))); |
||
| 146 | $this->assertInternalType($type, invokeMethod($this->object, 'makeValueSafe', array($value))); |
||
| 147 | } |
||
| 148 | |||
| 149 | /** |
||
| 150 | * @covers ::setTable |
||
| 151 | * @covers ::setOneRow |
||
| 152 | * @covers ::setValues |
||
| 153 | * @covers ::setValuesDanger |
||
| 154 | * @covers ::setAdjust |
||
| 155 | * @covers ::setAdjustDanger |
||
| 156 | * @covers ::setFields |
||
| 157 | * @covers ::setWhereArray |
||
| 158 | * @covers ::setWhereArrayDanger |
||
| 159 | */ |
||
| 160 | public function testSetters() { |
||
| 161 | $result = $this->object |
||
| 162 | ->setTable('table') |
||
| 163 | ->setOneRow(DbQuery::DB_RECORD_ONE) |
||
| 164 | ->setValues(array('f1' => 'v1')) |
||
| 165 | ->setValuesDanger(array('f2 <= v1')) |
||
| 166 | ->setAdjust(array('f7' => '7')) |
||
| 167 | ->setAdjustDanger(array('f3' => '3')) |
||
| 168 | ->setFields(array('f4' => 'v4')) |
||
| 169 | ->setWhereArray(array('f5' => '5')) |
||
| 170 | ->setWhereArrayDanger(array('f6' => '6')); |
||
| 171 | |||
| 172 | // Fluid interface is working |
||
| 173 | $this->assertEquals($this->object, $result); |
||
| 174 | |||
| 175 | $this->assertAttributeEquals('table', 'table', $this->object); |
||
| 176 | $this->assertAttributeEquals(DbQuery::DB_RECORD_ONE, 'isOneRow', $this->object); |
||
| 177 | $this->assertAttributeEquals(array('f1' => 'v1'), 'values', $this->object); |
||
| 178 | $this->assertAttributeEquals(array('f2 <= v1'), 'valuesDanger', $this->object); |
||
| 179 | $this->assertAttributeEquals(array('f7' => '7'), 'adjust', $this->object); |
||
| 180 | $this->assertAttributeEquals(array('f3' => '3'), 'adjustDanger', $this->object); |
||
| 181 | $this->assertAttributeEquals(array('f4' => 'v4'), 'fields', $this->object); |
||
| 182 | $this->assertAttributeEquals(array('f5' => '5'), 'where', $this->object); |
||
| 183 | $this->assertAttributeEquals(array('f6' => '6'), 'whereDanger', $this->object); |
||
| 184 | } |
||
| 185 | |||
| 186 | public function dataBuildCommand() { |
||
| 187 | return array( |
||
| 188 | array('', ''), |
||
| 189 | array('SELECT', DbQuery::SELECT), |
||
| 190 | array('INSERT INTO `{{theTable}}`', DbQuery::INSERT), |
||
| 191 | array('INSERT IGNORE INTO `{{theTable}}`', DbQuery::INSERT_IGNORE), |
||
| 192 | array('REPLACE INTO `{{theTable}}`', DbQuery::REPLACE), |
||
| 193 | array('UPDATE `{{theTable}}`', DbQuery::UPDATE), |
||
| 194 | array('DELETE FROM `{{theTable}}`', DbQuery::DELETE), |
||
| 195 | ); |
||
| 196 | } |
||
| 197 | |||
| 198 | /** |
||
| 199 | * @covers ::buildCommand |
||
| 200 | * @covers ::__toString |
||
| 201 | * @dataProvider dataBuildCommand |
||
| 202 | */ |
||
| 203 | public function testBuildCommand($expected, $command) { |
||
| 204 | $this->object->setTable('theTable'); |
||
| 205 | // $property = getPrivateProperty($this->object, 'command'); |
||
| 206 | // $property->setValue($this->object, $command); |
||
| 207 | invokeMethod($this->object, 'buildCommand', array($command)); |
||
| 208 | $this->assertEquals($expected, $this->object->__toString()); |
||
| 209 | } |
||
| 210 | |||
| 211 | |||
| 212 | /** |
||
| 213 | * @covers ::buildSetFields |
||
| 214 | */ |
||
| 215 | public function testBuildSetFields() { |
||
| 216 | $this->object |
||
| 217 | ->setValues(array('f1' => 's1', 'f2' => 1)) |
||
| 218 | ->setValuesDanger(array('f3 = 5, f4 = "str4" ')) |
||
| 219 | ->setAdjust(array('f5' => 1, 'f6' => 'str6', 0 => 'quotedString')) |
||
| 220 | ->setAdjustDanger(array('f7 = f7 + 2 ')); |
||
| 221 | |||
| 222 | invokeMethod($this->object, 'buildSetFields', array()); |
||
| 223 | $this->assertEquals(" SET f3 = 5, f4 = \"str4\" ,`f1` = 's1',`f2` = 1,f7 = f7 + 2 ,`f5` = `f5` + (1),`f6` = `f6` + ('str6'),'quotedString'", $this->object->__toString()); |
||
| 224 | } |
||
| 225 | |||
| 226 | |||
| 227 | /** |
||
| 228 | * @covers ::buildFieldNames |
||
| 229 | */ |
||
| 230 | public function testBuildFieldNames() { |
||
| 231 | $this->object->setFields(array('v1', 'v2')); |
||
| 232 | invokeMethod($this->object, 'buildFieldNames', array()); |
||
| 233 | $this->assertEquals('`v1`,`v2`', $this->object->__toString()); |
||
| 234 | } |
||
| 235 | |||
| 236 | /** |
||
| 237 | * @covers ::buildValuesVector |
||
| 238 | */ |
||
| 239 | public function testBuildValuesVector() { |
||
| 240 | $this->object->setValues(array( |
||
| 241 | array('f1' => 'string', 'f2' => 1, 'f3' => null), |
||
| 242 | array('f1' => 'v1', 'f2' => 2, 'f3' => null), |
||
| 243 | )); |
||
| 244 | $this->object->setValuesDanger('(DANGER!)'); |
||
| 245 | invokeMethod($this->object, 'buildValuesVector', array()); |
||
| 246 | $this->assertEquals("(DANGER!),('string',1,NULL),('v1',2,NULL)", $this->object->__toString()); |
||
| 247 | } |
||
| 248 | |||
| 249 | |||
| 250 | /** |
||
| 251 | * @covers ::buildWhere |
||
| 252 | */ |
||
| 253 | public function testBuildWhere() { |
||
| 254 | invokeMethod($this->object, 'buildWhere', array()); |
||
| 255 | $this->assertEquals('', $this->object->__toString()); |
||
| 256 | |||
| 257 | $this->object |
||
| 258 | ->setWhereArray(array('f1' => 's1', 0 => 'quotedString')) |
||
| 259 | ->setWhereArrayDanger(array('f2' => 's2')); |
||
| 260 | invokeMethod($this->object, 'buildWhere', array()); |
||
| 261 | $this->assertEquals(" WHERE s2 AND `f1` = 's1' AND 'quotedString'", $this->object->__toString()); |
||
| 262 | } |
||
| 263 | |||
| 264 | |||
| 265 | /** |
||
| 266 | * @covers ::buildLimit |
||
| 267 | */ |
||
| 268 | public function testBuildLimit() { |
||
| 269 | $this->object->setOneRow(DbQuery::DB_RECORD_ONE); |
||
| 270 | invokeMethod($this->object, 'buildLimit', array()); |
||
| 271 | $this->assertEquals(" LIMIT 1", $this->object->__toString()); |
||
| 272 | } |
||
| 273 | |||
| 274 | |||
| 275 | /** |
||
| 276 | * @covers ::delete |
||
| 277 | */ |
||
| 278 | public function testDelete() { |
||
| 279 | $this->object |
||
| 280 | ->setTable('aT') |
||
| 281 | ->setWhereArray(array('f1' => 's1', 0 => 'ANDinQuote')) |
||
| 282 | ->setWhereArrayDanger(array('f2' => 'f3 = 1')) |
||
| 283 | ->setOneRow(DbQuery::DB_RECORD_ONE); |
||
| 284 | $this->assertEquals("DELETE FROM `{{aT}}` WHERE f3 = 1 AND `f1` = 's1' AND 'ANDinQuote' LIMIT 1", $this->object->delete()); |
||
| 285 | } |
||
| 286 | |||
| 287 | |||
| 288 | /** |
||
| 289 | * @covers ::update |
||
| 290 | */ |
||
| 291 | public function testUpdate() { |
||
| 292 | $this->object |
||
| 293 | ->setTable('aT') |
||
| 294 | ->setValues(array('f1' => 's1')) |
||
| 295 | ->setValuesDanger(array(' f3 = 5 ')) |
||
| 296 | ->setAdjust(array('f5' => 1, 'f6' => -1)) |
||
| 297 | ->setAdjustDanger(array(' f7 = f7 + 2 ')) |
||
| 298 | ->setWhereArray(array('f1' => 's1')) |
||
| 299 | ->setWhereArrayDanger(array('f2' => 'f3 = 1')) |
||
| 300 | ->setOneRow(DbQuery::DB_RECORD_ONE); |
||
| 301 | $this->assertEquals( |
||
| 302 | "UPDATE `{{aT}}`" . |
||
| 303 | " SET f3 = 5 ,`f1` = 's1'," . |
||
| 304 | " f7 = f7 + 2 ,`f5` = `f5` + (1),`f6` = `f6` + (-1)" . |
||
| 305 | " WHERE f3 = 1 AND `f1` = 's1' LIMIT 1", |
||
| 306 | $this->object->update() |
||
| 307 | ); |
||
| 308 | } |
||
| 309 | |||
| 310 | |||
| 311 | |||
| 312 | /** |
||
| 313 | * Insert SET test |
||
| 314 | * |
||
| 315 | * @covers ::insert |
||
| 316 | * @covers ::setInsertCommand |
||
| 317 | */ |
||
| 318 | public function testInsertSet() { |
||
| 319 | $this->object |
||
| 320 | ->setTable('aT') |
||
| 321 | ->setValues(array('f1' => 's1')) |
||
| 322 | ->setValuesDanger(array(' f3 = 5 ')) |
||
| 323 | ->setAdjust(array('f5' => 1, 'f6' => -1)) |
||
| 324 | ->setAdjustDanger(array(' f7 = f7 + 2 ')); |
||
| 325 | $this->assertEquals( |
||
| 326 | "INSERT INTO `{{aT}}`" . |
||
| 327 | " SET f3 = 5 ,`f1` = 's1'," . |
||
| 328 | " f7 = f7 + 2 ,`f5` = `f5` + (1),`f6` = `f6` + (-1)", |
||
| 329 | invokeMethod($this->object, 'insert', array(DbQuery::DB_INSERT_PLAIN)) |
||
| 330 | ); |
||
| 331 | |||
| 332 | $this->object->setTable('aT2')->setAdjust(array('f25' => 21, 'f26' => -21)); |
||
| 333 | $this->assertEquals( |
||
| 334 | "INSERT IGNORE INTO `{{aT2}}`" . |
||
| 335 | " SET f3 = 5 ,`f1` = 's1'," . |
||
| 336 | " f7 = f7 + 2 ,`f5` = `f5` + (1),`f6` = `f6` + (-1)," . |
||
| 337 | "`f25` = `f25` + (21),`f26` = `f26` + (-21)", |
||
| 338 | invokeMethod($this->object, 'insert', array(DbQuery::DB_INSERT_IGNORE)) |
||
| 339 | ); |
||
| 340 | |||
| 341 | $this->object->setTable('aT2')->setAdjust(array('f36' => -31)); |
||
| 342 | $this->assertEquals( |
||
| 343 | "REPLACE INTO `{{aT2}}`" . |
||
| 344 | " SET f3 = 5 ,`f1` = 's1'," . |
||
| 345 | " f7 = f7 + 2 ,`f5` = `f5` + (1),`f6` = `f6` + (-1)," . |
||
| 346 | "`f25` = `f25` + (21),`f26` = `f26` + (-21),`f36` = `f36` + (-31)", |
||
| 347 | invokeMethod($this->object, 'insert', array(DbQuery::DB_INSERT_REPLACE)) |
||
| 348 | ); |
||
| 349 | } |
||
| 350 | |||
| 351 | |||
| 352 | /** |
||
| 353 | * Insert BATCH test |
||
| 354 | * |
||
| 355 | * @covers ::insert |
||
| 356 | * @covers ::setInsertCommand |
||
| 357 | */ |
||
| 358 | public function testInsertBatch() { |
||
| 359 | $this->object |
||
| 360 | ->setTable('aT') |
||
| 361 | ->setFields(array('f1', 'f2')) |
||
| 362 | ->setValues(array(array('v1', 1))) |
||
| 363 | ; |
||
| 364 | $this->assertEquals( |
||
| 365 | "INSERT INTO `{{aT}}` (`f1`,`f2`) VALUES " . |
||
| 366 | "('v1',1)" |
||
| 367 | , |
||
| 368 | invokeMethod($this->object, 'insert', array(DbQuery::DB_INSERT_PLAIN)) |
||
| 369 | ); |
||
| 370 | |||
| 371 | $this->object |
||
| 372 | ->setTable('aT2') |
||
| 373 | ->setValues(array(array('v2', 2))) |
||
| 374 | ; |
||
| 375 | $this->assertEquals( |
||
| 376 | "INSERT IGNORE INTO `{{aT2}}` (`f1`,`f2`) VALUES " . |
||
| 377 | "('v1',1),('v2',2)" |
||
| 378 | , |
||
| 379 | invokeMethod($this->object, 'insert', array(DbQuery::DB_INSERT_IGNORE)) |
||
| 380 | ); |
||
| 381 | |||
| 382 | $this->object |
||
| 383 | ->setTable('aT2') |
||
| 384 | ->setValues(array(array('v3', 3),array('v4', 4))) |
||
| 385 | ->setValuesDanger(array('this IS danger!')) |
||
| 386 | ; |
||
| 387 | $this->assertEquals( |
||
| 388 | "REPLACE INTO `{{aT2}}` (`f1`,`f2`) VALUES " . |
||
| 389 | "this IS danger!,('v1',1),('v2',2),('v3',3),('v4',4)" |
||
| 390 | , |
||
| 391 | invokeMethod($this->object, 'insert', array(DbQuery::DB_INSERT_REPLACE)) |
||
| 392 | ); |
||
| 393 | } |
||
| 394 | |||
| 395 | /** |
||
| 396 | * @covers ::select |
||
| 397 | */ |
||
| 398 | public function testSelect() { |
||
| 399 | $this->object |
||
| 400 | ->setTable('aT') |
||
| 401 | // ->setFields(array('f1', 'f2')) // TODO - unused |
||
| 402 | ->setWhereArray(array('f1' => 's1')) |
||
| 403 | ->setWhereArrayDanger(array('f2' => '(f3 = 1 OR f3 = 2)')) |
||
| 404 | ->setOneRow(DbQuery::DB_RECORD_ONE) |
||
| 405 | ; |
||
| 406 | $this->assertEquals( |
||
| 407 | "SELECT * FROM `{{aT}}` WHERE (f3 = 1 OR f3 = 2) AND `f1` = 's1' LIMIT 1", |
||
| 408 | $this->object->select() |
||
| 409 | ); |
||
| 410 | } |
||
| 411 | |||
| 412 | } |
||
| 413 |