createMock(\medoo::class); $repository = new PromotionRepository($mockDb); $result = $repository->find(0); $this->assertIsArray($result); $this->assertSame(0, (int)$result['id']); $this->assertSame(1, (int)$result['status']); $this->assertNull($result['date_from']); $this->assertSame([], $result['categories']); $this->assertSame([], $result['condition_categories']); } public function testSaveInsertsPromotionAndReturnsId(): void { $mockDb = $this->createMock(\medoo::class); $insertRow = null; $mockDb->expects($this->once()) ->method('insert') ->willReturnCallback(function ($table, $row) use (&$insertRow) { $this->assertSame('pp_shop_promotion', $table); $this->assertArrayHasKey('name', $row); $insertRow = $row; }); $mockDb->expects($this->once()) ->method('id') ->willReturn(123); $repository = new PromotionRepository($mockDb); $id = $repository->save([ 'name' => 'Promocja testowa', 'status' => 'on', 'condition_type' => 1, 'discount_type' => 1, 'amount' => '10', 'date_from' => '2026-02-01', 'categories' => [1, 2], ]); $this->assertSame(123, $id); $this->assertIsArray($insertRow); $this->assertSame('2026-02-01', $insertRow['date_from'] ?? null); } public function testDeleteReturnsFalseForInvalidId(): void { $mockDb = $this->createMock(\medoo::class); $mockDb->expects($this->never())->method('delete'); $repository = new PromotionRepository($mockDb); $this->assertFalse($repository->delete(0)); } public function testDeleteReturnsTrueWhenDatabaseDeleteSucceeds(): void { $mockDb = $this->createMock(\medoo::class); $mockDb->expects($this->once()) ->method('delete') ->with('pp_shop_promotion', ['id' => 55]) ->willReturn(true); $repository = new PromotionRepository($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' => 'Promo', 'status' => 1, 'condition_type' => 1, 'date_to' => null, ]]; } }; }); $repository = new PromotionRepository($mockDb); $repository->listForAdmin( [], 'date_to DESC; DROP TABLE pp_shop_promotion; --', 'DESC; DELETE FROM pp_users; --', 1, 500 ); $this->assertCount(2, $queries); $dataSql = $queries[1]['sql']; $this->assertMatchesRegularExpression('/ORDER BY\s+sp\.id\s+DESC,\s+sp\.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); } 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 PromotionRepository($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']); } }