createMock(\medoo::class); $repository = new CouponRepository($mockDb); $result = $repository->find(0); $this->assertIsArray($result); $this->assertSame(0, (int)$result['id']); $this->assertSame(1, (int)$result['status']); $this->assertSame(1, (int)$result['one_time']); $this->assertSame([], $result['categories']); } public function testFindNormalizesCouponData(): void { $mockDb = $this->createMock(\medoo::class); $mockDb->expects($this->once()) ->method('get') ->with('pp_shop_coupon', '*', ['id' => 15]) ->willReturn([ 'id' => '15', 'name' => 'KOD15', 'status' => '1', 'send' => 0, 'used' => '1', 'type' => '1', 'amount' => '15.00', 'one_time' => '0', 'include_discounted_product' => '1', 'categories' => '[4,6,6]', 'used_count' => '3', ]); $repository = new CouponRepository($mockDb); $result = $repository->find(15); $this->assertSame(15, (int)$result['id']); $this->assertSame(1, (int)$result['status']); $this->assertSame(0, (int)$result['send']); $this->assertSame(1, (int)$result['used']); $this->assertSame(0, (int)$result['one_time']); $this->assertSame(1, (int)$result['include_discounted_product']); $this->assertSame([4, 6], $result['categories']); $this->assertSame(3, (int)$result['used_count']); } public function testSaveInsertsCouponAndReturnsId(): void { $mockDb = $this->createMock(\medoo::class); $insertRow = null; $mockDb->expects($this->once()) ->method('insert') ->willReturnCallback(function ($table, $row) use (&$insertRow) { $this->assertSame('pp_shop_coupon', $table); $insertRow = $row; }); $mockDb->expects($this->once()) ->method('id') ->willReturn(321); $repository = new CouponRepository($mockDb); $id = $repository->save([ 'name' => ' KOD25 ', 'status' => 'on', 'send' => '1', 'used' => 0, 'type' => 1, 'amount' => '25,50', 'one_time' => 'on', 'include_discounted_product' => 'on', 'categories' => [1, '2', 'abc', 2], ]); $this->assertSame(321, $id); $this->assertIsArray($insertRow); $this->assertSame('KOD25', $insertRow['name'] ?? ''); $this->assertSame(1, (int)($insertRow['status'] ?? 0)); $this->assertSame(1, (int)($insertRow['send'] ?? 0)); $this->assertSame(0, (int)($insertRow['used'] ?? 1)); $this->assertSame('25.50', $insertRow['amount'] ?? null); $this->assertSame('[1,2]', $insertRow['categories'] ?? null); } public function testSaveUpdatesCouponAndReturnsId(): void { $mockDb = $this->createMock(\medoo::class); $updateRow = null; $updateWhere = null; $mockDb->expects($this->once()) ->method('update') ->willReturnCallback(function ($table, $row, $where) use (&$updateRow, &$updateWhere) { $this->assertSame('pp_shop_coupon', $table); $updateRow = $row; $updateWhere = $where; return true; }); $mockDb->expects($this->never())->method('insert'); $mockDb->expects($this->never())->method('id'); $repository = new CouponRepository($mockDb); $id = $repository->save([ 'id' => 77, 'name' => 'KOD77', 'status' => '0', 'send' => 'off', 'used' => '0', 'type' => 1, 'amount' => '', 'one_time' => '0', 'include_discounted_product' => 0, 'categories' => '', ]); $this->assertSame(77, $id); $this->assertIsArray($updateRow); $this->assertArrayHasKey('amount', $updateRow); $this->assertArrayHasKey('categories', $updateRow); $this->assertNull($updateRow['amount']); $this->assertNull($updateRow['categories']); $this->assertSame(0, (int)($updateRow['status'] ?? 1)); $this->assertSame(['id' => 77], $updateWhere); } public function testDeleteReturnsFalseForInvalidId(): void { $mockDb = $this->createMock(\medoo::class); $mockDb->expects($this->never())->method('delete'); $repository = new CouponRepository($mockDb); $this->assertFalse($repository->delete(0)); } public function testDeleteReturnsTrueWhenDatabaseDeleteSucceeds(): void { $mockDb = $this->createMock(\medoo::class); $mockDb->expects($this->once()) ->method('delete') ->with('pp_shop_coupon', ['id' => 55]) ->willReturn(true); $repository = new CouponRepository($mockDb); $this->assertTrue($repository->delete(55)); } public function testListForAdminWhitelistsSortAndDirection(): void { $mockDb = $this->createMock(\medoo::class); $queries = []; $mockDb->method('query') ->willReturnCallback(function ($sql, $params = []) use (&$queries) { $queries[] = ['sql' => $sql, 'params' => $params]; if (strpos($sql, 'COUNT(0)') !== false) { return new class { public function fetchAll() { return [[1]]; } }; } return new class { public function fetchAll() { return [[ 'id' => 1, 'name' => 'KOD', 'status' => 1, 'used' => 0, 'type' => 1, 'amount' => '10', 'one_time' => 1, 'send' => 0, 'include_discounted_product' => 0, 'categories' => '[3,5]', 'date_used' => null, 'used_count' => 7, ]]; } }; }); $repository = new CouponRepository($mockDb); $result = $repository->listForAdmin( [], 'date_used DESC; DROP TABLE pp_shop_coupon; --', 'DESC; DELETE FROM pp_users; --', 1, 999 ); $this->assertCount(2, $queries); $dataSql = $queries[1]['sql']; $this->assertMatchesRegularExpression('/ORDER BY\s+sc\.name\s+ASC,\s+sc\.id\s+DESC/i', $dataSql); $this->assertStringNotContainsString('DROP TABLE', $dataSql); $this->assertStringNotContainsString('DELETE FROM pp_users', $dataSql); $this->assertMatchesRegularExpression('/LIMIT\s+100\s+OFFSET\s+0/i', $dataSql); $this->assertSame([3, 5], $result['items'][0]['categories']); } public function testCategoriesTreeReturnsHierarchy(): void { $mockDb = $this->createMock(\medoo::class); $mockDb->method('select') ->willReturnCallback(function ($table, $columns, $where) { if ($table === 'pp_shop_categories' && array_key_exists('parent_id', $where)) { if ($where['parent_id'] === null) { return [['id' => 10]]; } if ((int)$where['parent_id'] === 10) { return [['id' => 11]]; } return []; } if ($table === 'pp_shop_categories_langs') { if ((int)$where['category_id'] === 10) { return [['lang_id' => 'pl', 'title' => 'Kategoria A']]; } if ((int)$where['category_id'] === 11) { return [['lang_id' => 'pl', 'title' => 'Podkategoria A1']]; } return []; } if ($table === 'pp_langs') { return [['id' => 'pl', 'start' => 1, 'o' => 1]]; } return []; }); $mockDb->method('get') ->willReturnCallback(function ($table, $columns, $where) { if ($table === 'pp_shop_categories') { $id = (int)$where['id']; return ['id' => $id, 'status' => 1]; } return null; }); $repository = new CouponRepository($mockDb); $tree = $repository->categoriesTree(null); $this->assertCount(1, $tree); $this->assertSame(10, (int)$tree[0]['id']); $this->assertSame('Kategoria A', $tree[0]['title']); $this->assertCount(1, $tree[0]['subcategories']); $this->assertSame(11, (int)$tree[0]['subcategories'][0]['id']); } public function testFindByNameReturnsObjectWhenFound(): void { $mockDb = $this->createMock(\medoo::class); $mockDb->expects($this->once()) ->method('get') ->with('pp_shop_coupon', '*', ['name' => 'RABAT10']) ->willReturn([ 'id' => '5', 'name' => 'RABAT10', 'status' => '1', 'used' => '0', 'type' => '1', 'amount' => '10.00', 'one_time' => '1', 'include_discounted_product' => '0', 'categories' => '[1,2]', 'used_count' => '0', ]); $repository = new CouponRepository($mockDb); $result = $repository->findByName('RABAT10'); $this->assertIsObject($result); $this->assertSame(5, $result->id); $this->assertSame('RABAT10', $result->name); $this->assertSame(1, $result->status); $this->assertSame(0, $result->used); $this->assertSame(1, $result->type); $this->assertSame('10.00', $result->amount); $this->assertSame(1, $result->one_time); $this->assertSame(0, $result->include_discounted_product); $this->assertSame('[1,2]', $result->categories); $this->assertSame(0, $result->used_count); } public function testFindByNameReturnsNullWhenNotFound(): void { $mockDb = $this->createMock(\medoo::class); $mockDb->expects($this->once()) ->method('get') ->willReturn(null); $repository = new CouponRepository($mockDb); $this->assertNull($repository->findByName('NIEISTNIEJACY')); } public function testFindByNameReturnsNullForEmptyName(): void { $mockDb = $this->createMock(\medoo::class); $mockDb->expects($this->never())->method('get'); $repository = new CouponRepository($mockDb); $this->assertNull($repository->findByName('')); $this->assertNull($repository->findByName(' ')); } public function testIsAvailableReturnsTrueForActiveCoupon(): void { $mockDb = $this->createMock(\medoo::class); $repository = new CouponRepository($mockDb); $coupon = (object)['id' => 1, 'status' => 1, 'used' => 0]; $this->assertTrue($repository->isAvailable($coupon)); } public function testIsAvailableReturnsFalseForUsedCoupon(): void { $mockDb = $this->createMock(\medoo::class); $repository = new CouponRepository($mockDb); $coupon = (object)['id' => 1, 'status' => 1, 'used' => 1]; $this->assertFalse($repository->isAvailable($coupon)); } public function testIsAvailableReturnsFalseForInactiveCoupon(): void { $mockDb = $this->createMock(\medoo::class); $repository = new CouponRepository($mockDb); $coupon = (object)['id' => 1, 'status' => 0, 'used' => 0]; $this->assertFalse($repository->isAvailable($coupon)); } public function testIsAvailableReturnsFalseForNullCoupon(): void { $mockDb = $this->createMock(\medoo::class); $repository = new CouponRepository($mockDb); $this->assertFalse($repository->isAvailable(null)); } public function testIsAvailableWorksWithArray(): void { $mockDb = $this->createMock(\medoo::class); $repository = new CouponRepository($mockDb); $this->assertTrue($repository->isAvailable(['id' => 1, 'status' => 1, 'used' => 0])); $this->assertFalse($repository->isAvailable(['id' => 0, 'status' => 1, 'used' => 0])); } public function testMarkAsUsedCallsUpdate(): void { $mockDb = $this->createMock(\medoo::class); $mockDb->expects($this->once()) ->method('update') ->willReturnCallback(function ($table, $row, $where) { $this->assertSame('pp_shop_coupon', $table); $this->assertSame(1, $row['used']); $this->assertArrayHasKey('date_used', $row); $this->assertSame(['id' => 10], $where); }); $repository = new CouponRepository($mockDb); $repository->markAsUsed(10); } public function testMarkAsUsedSkipsInvalidId(): void { $mockDb = $this->createMock(\medoo::class); $mockDb->expects($this->never())->method('update'); $repository = new CouponRepository($mockDb); $repository->markAsUsed(0); } public function testIncrementUsedCountCallsUpdate(): void { $mockDb = $this->createMock(\medoo::class); $mockDb->expects($this->once()) ->method('update') ->willReturnCallback(function ($table, $row, $where) { $this->assertSame('pp_shop_coupon', $table); $this->assertSame(1, $row['used_count[+]']); $this->assertSame(['id' => 7], $where); }); $repository = new CouponRepository($mockDb); $repository->incrementUsedCount(7); } public function testIncrementUsedCountSkipsInvalidId(): void { $mockDb = $this->createMock(\medoo::class); $mockDb->expects($this->never())->method('update'); $repository = new CouponRepository($mockDb); $repository->incrementUsedCount(0); } }