. /** * MantisBT Tests * * @package Tests * @subpackage UnitTests * @copyright Copyright 2025 MantisBT Team - mantisbt-dev@lists.sourceforge.net * @link https://mantisbt.org */ namespace Mantis\tests\rest; use Psr\Http\Message\ResponseInterface; class RestIssueUpdateCategory extends RestBase { const CFG_ALLOW_NO_CAT = 'allow_no_category'; /** * @var int Issue id */ private int $issue_id; /** * @var false|null */ private $save_config = null; public function setUp(): void { parent::setUp(); # Create a test issue $t_issue_to_add = $this->getIssueToAdd(); $t_response = $this->builder()->post( '/issues', $t_issue_to_add )->send(); $this->issue_id = $this->getJson( $t_response, HTTP_STATUS_CREATED )->issue->id; $this->deleteIssueAfterRun($this->issue_id ); } public function tearDown(): void { parent::tearDown(); # Restore config if it has been changed if( $this->save_config !== null) { $this->restoreConfig( self::CFG_ALLOW_NO_CAT, $this->save_config ); } } /** * Generates a unique category name for the test project. * * The name is generated by appending a random number to the prefix 'cat-'. * * @return string A unique category name. */ private function getUniqueCategoryName(): string { do { $t_name = 'cat-' . rand(); } while( !category_is_unique( $this->getProjectId(), $t_name ) ); return $t_name; } /** * Updates the category of a specified issue. * * @param int $p_issue_id The ID of the issue to update. * @param mixed $p_category The new category payload to assign. * * @return ResponseInterface The response object after performing the update. */ private function updateCategory( int $p_issue_id, $p_category ): ResponseInterface { $t_payload = ['category' => $p_category]; return $this->builder()->patch( "/issues/$p_issue_id", $t_payload )->send(); } /** * Test setting the category from various hardcoded cases. * @dataProvider providerSetCategory * * @param mixed $p_category Category payload. * @param int $p_status_code Expected status code. * @param int|null $p_expected_id Expected category id after update (null == unchanged). */ public function testUpdateIssueCategory1( $p_category, int $p_status_code, int $p_expected_id = null) { $t_response = $this->updateCategory( $this->issue_id, $p_category ); $t_json = $this->getJson( $t_response, $p_status_code ); if( $t_response->getStatusCode() == HTTP_STATUS_SUCCESS ) { $this->assertEquals( $p_expected_id, $t_json->issues[0]->category->id, "Updated category id does not match expected" ); } } /** * Data provider for {@see testUpdateIssueCategory1()}. * * @return array[] */ public function providerSetCategory() { return [ # Scalar - API expects a category name (string) [0, HTTP_STATUS_NOT_FOUND], [false, HTTP_STATUS_NOT_FOUND], [1, HTTP_STATUS_NOT_FOUND], [9999, HTTP_STATUS_NOT_FOUND], ['General', HTTP_STATUS_SUCCESS, 1], ['Non-existing category', HTTP_STATUS_NOT_FOUND], # By ID [['id' => 'General'], HTTP_STATUS_BAD_REQUEST], [['id' => false], HTTP_STATUS_BAD_REQUEST], [['id' => 1], HTTP_STATUS_SUCCESS, 1], [['id' => 9999], HTTP_STATUS_NOT_FOUND], # By Name [['name' => ''], HTTP_STATUS_NOT_FOUND], [['name' => 'General' ], HTTP_STATUS_SUCCESS, 1], [['name' => 'Non-existing category'], HTTP_STATUS_NOT_FOUND], ]; } /** * Test changing to a new category. * * Since we need to dynamically create the category and this can't be done * using the providerSetCategory() data provider, we test it separately. */ public function testUpdateIssueCategory2() { # Create a category for the tests # Use Core API as we don't yet have a REST API endpoint for that (#32470) $t_category_id = category_add($this->getProjectId(), $this->getUniqueCategoryName() ); $t_category_name = category_get_name( $t_category_id ); # If payload is scalar then it is expected to be a category name, so passing the id should fail $t_response = $this->updateCategory( $this->issue_id, $t_category_id ); $this->assertEquals( HTTP_STATUS_NOT_FOUND, $t_response->getStatusCode() ); $t_cases = [ $t_category_name, ['id' => $t_category_id], ['name' => $t_category_name], ]; foreach( $t_cases as $t_category ) { $t_response = $this->updateCategory( $this->issue_id, $t_category ); $t_json = $this->getJson( $t_response ); $this->assertEquals( $t_category_id, $t_json->issues[0]->category->id, "Updated category matches" ); } category_remove( $t_category_id ); } /** * Test clearing the category. * * Tests with both {@see $g_allow_no_category} ON and OFF. * * @dataProvider providerClearCategory */ public function testUpdateClearCategory( $p_category ) { # Test with mandatory category $this->save_config = $this->setConfig( self::CFG_ALLOW_NO_CAT, OFF ); # Status code if( $p_category === null || is_array( $p_category ) && ( array_key_exists( 'id', $p_category ) && $p_category['id'] === null || array_key_exists( 'name', $p_category ) && $p_category['name'] === null ) ) { $t_expected = HTTP_STATUS_BAD_REQUEST; } else { $t_expected = HTTP_STATUS_NOT_FOUND; } $t_response = $this->updateCategory( $this->issue_id, $p_category ); $this->assertEquals( $t_expected, $t_response->getStatusCode(), "REST API returned unexpected Status Code" ); # Allowing empty category config_set( self::CFG_ALLOW_NO_CAT, ON ); $t_response = $this->updateCategory( $this->issue_id, $p_category ); $t_json = $this->getJson( $t_response ); $this->assertObjectNotHasProperty( 'category', $t_json->issues[0] ); } /** * Data provider for {@see testUpdateClearCategory()}. * * All values allowing to unset the Category, if the server's configuration * allows it ({@see $g_allow_no_category}). * * @return array[] */ public function providerClearCategory() { return [ [null], [['id' => null]], [['id' => 0]], [['name' => null]], ]; } }