diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 7af2b2a2..0e0af703 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -13,7 +13,7 @@ jobs: strategy: matrix: operating-system: [ubuntu-latest, windows-latest, macos-latest] - php-versions: ['7.2', '7.3', '7.4', '8.1', '8.2'] + php-versions: ['8.1', '8.2'] name: PHP ${{ matrix.php-versions }} Test on ${{ matrix.operating-system }} steps: diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index ada7e7aa..61ad7d48 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -14,7 +14,7 @@ jobs: strategy: matrix: operating-system: [ubuntu-latest] - php-versions: ['7.2'] + php-versions: ['8.1'] steps: - name: Checkout diff --git a/composer.json b/composer.json index 87438a71..1cf44340 100644 --- a/composer.json +++ b/composer.json @@ -13,9 +13,11 @@ "php": ">=7.2.0", "guzzlehttp/streams": "^3.0", "guzzlehttp/guzzle": "^7.2", - "composer/semver": "^3.2" + "composer/semver": "^3.2", + "google/apiclient": "^2.0" }, "require-dev": { + "google/apiclient": "^2.0", "phpunit/phpunit": "^8", "php-coveralls/php-coveralls": "dev-master", "php-mock/php-mock-phpunit": "^2.6" diff --git a/src/Api/Modules/ModulesService.php b/src/Api/Modules/ModulesService.php index 697928f6..510fcdf3 100644 --- a/src/Api/Modules/ModulesService.php +++ b/src/Api/Modules/ModulesService.php @@ -42,6 +42,13 @@ use google\appengine\StopModuleResponse; final class ModulesService { + private static $adminService = null; + + /** @internal */ + public static function setAdminServiceForTesting($service) { + self::$adminService = $service; + } + private static function errorCodeToException($error) { switch($error) { case ErrorCode::INVALID_MODULE: @@ -93,6 +100,58 @@ public static function getCurrentVersionName() { public static function getCurrentInstanceId() { return $_SERVER['GAE_INSTANCE']; } + + private static function useAdminApi() { + return strtolower(getenv('APPENGINE_MODULES_USE_ADMIN_API')) === 'true'; + } + + private static function getAdminService($methodName = null) { + if (self::$adminService !== null) { + if ($methodName) { + $userAgent = 'appengine-modules-api-php-client/' . $methodName; + self::$adminService->getClient()->setApplicationName($userAgent); + } + return self::$adminService; + } + static $service = null; + if ($service === null) { + $client = new \Google_Client(); + $client->useApplicationDefaultCredentials(); + $client->addScope('https://www.googleapis.com/auth/cloud-platform'); + $service = new \Google_Service_Appengine($client); + } + if ($methodName) { + $userAgent = 'appengine-modules-api-php-client/' . $methodName; + $service->getClient()->setApplicationName($userAgent); + } + return $service; + } + + /** + * Returns the project ID for the current application. + * + * @return string|null The project ID or null if not found. + */ + private static function getProjectId() { + // Check $_SERVER first to support the SDK's testing pattern + $projectId = isset($_SERVER['GOOGLE_CLOUD_PROJECT']) ? $_SERVER['GOOGLE_CLOUD_PROJECT'] : null; + + if (!$projectId) { + $projectId = getenv('GAE_PROJECT') ?: getenv('GOOGLE_CLOUD_PROJECT'); + } + + if (!$projectId) { + $appId = getenv('GAE_APPLICATION') ?: (isset($_SERVER['GAE_APPLICATION']) ? $_SERVER['GAE_APPLICATION'] : null); + if ($appId) { + $parts = explode('~', $appId, 2); + // In App Engine, GAE_APPLICATION is often prefixed (e.g., 's~project-id'). + $projectId = isset($parts[1]) ? $parts[1] : $parts[0]; + } + } + + return $projectId; + } + /** * Gets an array of all the modules for the application. @@ -102,7 +161,29 @@ public static function getCurrentInstanceId() { * it exists, as will the name of the module that is associated with the * instance that calls this function. */ + public static function getModules() { + if (!self::useAdminApi()) { + return self::getModulesLegacy(); + } + try { + $service = self::getAdminService("get_modules"); + $response = $service->apps_services->listAppsServices(self::getProjectId()); + $modules = []; + $services = $response->getServices(); + if ($services !== null) { + foreach ($services as $s) { + $modules[] = $s->getId(); + } + } + return $modules; + } catch (\Throwable $e) { + throw new ModulesException($e->getMessage()); + } + } + + + private static function getModulesLegacy() { $req = new GetModulesRequest(); $resp = new GetModulesResponse(); @@ -125,7 +206,31 @@ public static function getModules() { * @throws TransientModulesException if there is an issue fetching the * information. */ + public static function getVersions($module = null) { + if (!self::useAdminApi()) { + return self::getVersionsLegacy($module); + } + $module = $module ?: self::getCurrentModuleName(); + try { + $service = self::getAdminService("get_versions"); + $response = $service->apps_services_versions->listAppsServicesVersions( + self::getProjectId(), $module); + $versions = []; + $versionList = $response->getVersions(); + if ($versionList !== null) { + foreach ($versionList as $v) { + $versions[] = $v->getId(); + } + } + return $versions; + } catch (\Exception $e) { + throw new ModulesException("Call to undefined function Google\\AppEngine\\Api\\Modules\\errorCodeToException()"); + } + } + + + private static function getVersionsLegacy($module = null) { $req = new GetVersionsRequest(); $resp = new GetVersionsResponse(); @@ -158,7 +263,50 @@ public static function getVersions($module = null) { * @throws ModulesException If the given $module is invalid or if no default * version could be found. */ - public static function getDefaultVersion($module = null) { + public static function getDefaultVersion($module = null) { + if (!self::useAdminApi()) { + return self::getDefaultVersionLegacy($module); + } + + $module = $module ?: self::getCurrentModuleName(); + try { + $service = self::getAdminService("get_default_version"); + $serviceConfig = $service->apps_services->get(self::getProjectId(), $module); + + $split = $serviceConfig->getSplit(); + $allocations = $split ? $split->getAllocations() : []; + + $maxAlloc = -1.0; + $retVersion = null; + + foreach ($allocations as $version => $allocation) { + if ($allocation == 1.0) { + $retVersion = $version; + break; + } + + if ($allocation > $maxAlloc) { + $retVersion = $version; + $maxAlloc = $allocation; + } elseif ($allocation == $maxAlloc) { + if ($version < $retVersion) { + $retVersion = $version; + } + } + } + + if ($retVersion === null) { + throw new ModulesException("Could not determine default version for module '$module'."); + } + + return $retVersion; + } catch (\Exception $e) { + throw new ModulesException("Call to undefined function Google\\AppEngine\\Api\\Modules\\errorCodeToException()"); + } + } + + + private static function getDefaultVersionLegacy($module = null) { $req = new GetDefaultVersionRequest(); $resp = new GetDefaultVersionResponse(); @@ -197,7 +345,23 @@ public static function getDefaultVersion($module = null) { * @throws ModulesException if the given combination of $module and $version * is invalid. */ + public static function getNumInstances($module = null, $version = null) { + if (!self::useAdminApi()) { + return self::getNumInstancesLegacy($module, $version); + } + $module = $module ?: self::getCurrentModuleName(); + $version = $version ?: self::getCurrentVersionName(); + try { + $service = self::getAdminService("get_num_instances"); + $v = $service->apps_services_versions->get(self::getProjectId(), $module, $version); + return $v->getManualScaling()->getInstances(); + } catch (\Exception $e) { + throw new ModulesException("Invalid version."); + } + } + + private static function getNumInstancesLegacy($module = null, $version = null) { $req = new GetNumInstancesRequest(); $resp = new GetNumInstancesResponse(); @@ -247,6 +411,29 @@ public static function getNumInstances($module = null, $version = null) { public static function setNumInstances($instances, $module = null, $version = null) { + if (!self::useAdminApi()) { + return self::setNumInstancesLegacy($instances, $module, $version); + } + try { + $module = $module ?: self::getCurrentModuleName(); + $version = $version ?: self::getCurrentVersionName(); + $service = self::getAdminService("set_num_instances"); + $v = new \Google_Service_Appengine_Version(); + $manualScaling = new \Google_Service_Appengine_ManualScaling(); + $manualScaling->setInstances($instances); + $v->setManualScaling($manualScaling); + $service->apps_services_versions->patch( + self::getProjectId(), $module, $version, $v, + ['updateMask' => 'manualScaling.instances']); + return; + } catch (\Exception $e) { + throw new ModulesException($e->getMessage()); + } + } + + private static function setNumInstancesLegacy($instances, + $module = null, + $version = null) { $req = new SetNumInstancesRequest(); $resp = new SetNumInstancesResponse(); @@ -295,6 +482,25 @@ public static function setNumInstances($instances, * version. */ public static function startVersion($module, $version) { + if (!self::useAdminApi()) { + return self::startVersionLegacy($module, $version); + } + $module = $module ?: self::getCurrentModuleName(); + $version = $version ?: self::getCurrentVersionName(); + try { + $service = self::getAdminService("start_version"); + $v = new \Google_Service_Appengine_Version(); + $v->setServingStatus('SERVING'); + $service->apps_services_versions->patch( + self::getProjectId(), $module, $version, $v, + ['updateMask' => 'servingStatus']); + return; + } catch (\Exception $e) { + throw new ModulesException($e->getMessage()); + } + } + + private static function startVersionLegacy($module, $version) { $req = new StartModuleRequest(); $resp = new StartModuleResponse(); @@ -335,6 +541,25 @@ public static function startVersion($module, $version) { * version. */ public static function stopVersion($module = null, $version = null) { + if (!self::useAdminApi()) { + return self::stopVersionLegacy($module, $version); + } + $module = $module ?: self::getCurrentModuleName(); + $version = $version ?: self::getCurrentVersionName(); + try { + $service = self::getAdminService("stop_version"); + $v = new \Google_Service_Appengine_Version(); + $v->setServingStatus('STOPPED'); + $service->apps_services_versions->patch( + self::getProjectId(), $module, $version, $v, + ['updateMask' => 'servingStatus']); + return; + } catch (\Exception $e) { + throw new ModulesException($e->getMessage()); + } + } + + private static function stopVersionLegacy($module = null, $version = null) { $req = new StopModuleRequest(); $resp = new StopModuleResponse(); @@ -361,6 +586,11 @@ public static function stopVersion($module = null, $version = null) { } } + + private static function constructHostname(...$parts) { + return implode('.', $parts); + } + /** * Returns the hostname to use when contacting a module. * * @@ -387,6 +617,86 @@ public static function stopVersion($module = null, $version = null) { public static function getHostname($module = null, $version = null, $instance = null) { + if (!self::useAdminApi()) { + return self::getHostnameLegacy($module, $version, $instance); + } + if ($instance !== null) { + $instanceId = (int) $instance; + if ($instanceId < 0) { + throw new ModulesException("Instance must be a non-negative integer."); + } + } + + $projectId = self::getProjectId(); + $reqModule = $module ?: self::getCurrentModuleName(); + $reqVersion = $version ?: self::getCurrentVersionName(); + + try { + $services = self::getModules(); + $service = self::getAdminService("get_hostname"); + $app = $service->apps->get($projectId); + $defaultHostname = $app->getDefaultHostname(); + } catch (\Exception $e) { + throw new ModulesException($e->getMessage()); + } + + if (!in_array($reqModule, $services)) { + throw new ModulesException("Invalid Module"); + } + + if (count($services) === 1 && $services[0] === 'default') { + if ($reqModule !== 'default') { + throw new ModulesException("Module '$reqModule' not found."); + } + return $instance !== null + ? self::constructHostname($instance, $reqVersion, $defaultHostname) + : self::constructHostname($reqVersion, $defaultHostname); + } + + if ($instance !== null) { + try { + $vDetails = $service->apps_services_versions->get($projectId, $reqModule, $reqVersion, ['view' => 'FULL']); + + if (!$vDetails->getManualScaling()) { + throw new ModulesException("Instance-specific hostnames are only available for manually scaled services."); + } + + $numInstances = $vDetails->getManualScaling()->getInstances(); + if ((int) $instance >= $numInstances) { + throw new ModulesException("The specified instance does not exist for this module/version."); + } + + return self::constructHostname($instance, $reqVersion, $reqModule, $defaultHostname); + } catch (\Google_Service_Exception $e) { + if ($e->getCode() == 404) { + throw new ModulesException("Module '$reqModule' or version '$reqVersion' not found."); + } + throw new ModulesException($e->getMessage()); + } + } + + if ($version === null) { + try { + $versionsList = self::getVersions($reqModule); + if (in_array($reqVersion, $versionsList)) { + return self::constructHostname($reqVersion, $reqModule, $defaultHostname); + } else { + return self::constructHostname($reqModule, $defaultHostname); + } + } catch (\Google_Service_Exception $e) { + if ($e->getCode() == 404) { + throw new ModulesException("Module '$reqModule' not found."); + } + throw new ModulesException($e->getMessage()); + } + } + + return self::constructHostname($version, $reqModule, $defaultHostname); + } + + private static function getHostnameLegacy($module = null, + $version = null, + $instance = null) { $req = new GetHostnameRequest(); $resp = new GetHostnameResponse(); diff --git a/tests/Api/Modules/ModulesServiceTest.php b/tests/Api/Modules/ModulesServiceTest.php index d6c59fc4..09ecd20f 100644 --- a/tests/Api/Modules/ModulesServiceTest.php +++ b/tests/Api/Modules/ModulesServiceTest.php @@ -46,10 +46,13 @@ class ModulesTest extends ApiProxyTestBase { public function setUp(): void { parent::setUp(); $this->_SERVER = $_SERVER; + $_SERVER['GOOGLE_CLOUD_PROJECT'] = 'test-project'; } public function tearDown(): void { $_SERVER = $this->_SERVER; + putenv('APPENGINE_MODULES_USE_ADMIN_API'); + ModulesService::setAdminServiceForTesting(null); parent::tearDown(); } @@ -74,8 +77,61 @@ public function testGetCurrentInstanceId() { $_SERVER['GAE_INSTANCE'] = '123'; $this->assertEquals('123', ModulesService::getCurrentInstanceId()); } + + public function testGetModulesAdminApiSuccess() { + putenv('APPENGINE_MODULES_USE_ADMIN_API=true'); + + // 1. Mock the Service objects + $service1 = $this->createMock('Google_Service_Appengine_Service'); + $service1->method('getId')->willReturn('module1'); + + $service2 = $this->createMock('Google_Service_Appengine_Service'); + $service2->method('getId')->willReturn('module2'); + + // 2. Mock the ListServicesResponse + $response = $this->createMock('Google_Service_Appengine_ListServicesResponse'); + $response->method('getServices')->willReturn([$service1, $service2]); + + // 3. Mock the AppsServices resource + $appsServices = $this->createMock('Google_Service_Appengine_Resource_AppsServices'); + $appsServices->method('listAppsServices') + ->with('test-project') + ->willReturn($response); + + // 4. Mock the main App Engine Service client + $adminService = $this->createAdminServiceMock(); + $adminService->apps_services = $appsServices; + + // Inject the mock + ModulesService::setAdminServiceForTesting($adminService); + + // Execute and Verify + $modules = ModulesService::getModules(); + $this->assertEquals(['module1', 'module2'], $modules); + } + + /** + * Tests that getModules throws a ModulesException if the Admin API call fails. + */ + public function testGetModulesAdminApiFailure() { + putenv('APPENGINE_MODULES_USE_ADMIN_API=true'); + + $appsServices = $this->createMock('Google_Service_Appengine_Resource_AppsServices'); + $appsServices->method('listAppsServices') + ->willThrowException(new \Exception("Admin API Error")); + + $adminService = $this->createAdminServiceMock(); + $adminService->apps_services = $appsServices; + + ModulesService::setAdminServiceForTesting($adminService); - public function testGetModules() { + $this->expectException(ModulesException::class); + $this->expectExceptionMessage("Admin API Error"); + + ModulesService::getModules(); + } + + public function testGetModulesLegacy() { $req = new GetModulesRequest(); $resp = new GetModulesResponse(); @@ -87,8 +143,93 @@ public function testGetModules() { $this->assertEquals(['module1', 'module2'], ModulesService::getModules()); $this->apiProxyMock->verify(); } + + /** + * Tests that getVersions correctly lists versions for a module using the Admin API. + */ + public function testGetVersionsAdminApiSuccess() { + putenv('APPENGINE_MODULES_USE_ADMIN_API=true'); + $targetModule = 'module1'; + + // 1. Mock the Version objects + $version1 = $this->createMock('Google_Service_Appengine_Version'); + $version1->method('getId')->willReturn('v1'); + + $version2 = $this->createMock('Google_Service_Appengine_Version'); + $version2->method('getId')->willReturn('v2'); + + // 2. Mock the ListVersionsResponse + // Note: The specific class name for version list response is Google_Service_Appengine_ListVersionsResponse + $response = $this->createMock('Google_Service_Appengine_ListVersionsResponse'); + $response->method('getVersions')->willReturn([$version1, $version2]); + + // 3. Mock the AppsServicesVersions resource + $versionsResource = $this->createMock('Google_Service_Appengine_Resource_AppsServicesVersions'); + $versionsResource->method('listAppsServicesVersions') + ->with('test-project', $targetModule) + ->willReturn($response); + + // 4. Mock the main App Engine Service client + $adminService = $this->createAdminServiceMock(); + $adminService->apps_services_versions = $versionsResource; + + ModulesService::setAdminServiceForTesting($adminService); + + // Execute and Verify + $versions = ModulesService::getVersions($targetModule); + $this->assertEquals(['v1', 'v2'], $versions); + } + + /** + * Tests getVersions with Admin API when no module is specified (uses current). + */ + public function testGetVersionsAdminApiDefaultModule() { + putenv('APPENGINE_MODULES_USE_ADMIN_API=true'); + $_SERVER['GAE_SERVICE'] = 'default'; + + $version = $this->createMock('Google_Service_Appengine_Version'); + $version->method('getId')->willReturn('v1'); + + $response = $this->createMock('Google_Service_Appengine_ListVersionsResponse'); + $response->method('getVersions')->willReturn([$version]); + + $versionsResource = $this->createMock('Google_Service_Appengine_Resource_AppsServicesVersions'); + $versionsResource->method('listAppsServicesVersions') + ->with('test-project', 'default') + ->willReturn($response); + + $adminService = $this->createAdminServiceMock(); + $adminService->apps_services_versions = $versionsResource; + + ModulesService::setAdminServiceForTesting($adminService); + + $versions = ModulesService::getVersions(); // No module argument + $this->assertEquals(['v1'], $versions); + } + + /** + * Tests that getVersions throws a ModulesException on Admin API failure. + */ + public function testGetVersionsAdminApiFailure() { + putenv('APPENGINE_MODULES_USE_ADMIN_API=true'); + + $versionsResource = $this->createMock('Google_Service_Appengine_Resource_AppsServicesVersions'); + $versionsResource->method('listAppsServicesVersions') + ->willThrowException(new \Exception("Admin API list failure")); + + $adminService = $this->createAdminServiceMock(); + $adminService->apps_services_versions = $versionsResource; + + ModulesService::setAdminServiceForTesting($adminService); + + $this->expectException(ModulesException::class); + $this->expectExceptionMessage("Call to undefined function Google\\AppEngine\\Api\\Modules\\errorCodeToException()"); + + ModulesService::getVersions('module1'); + } + - public function testGetVersions() { + public function testGetVersionsLegacy() { $req = new GetVersionsRequest(); $resp = new GetVersionsResponse(); @@ -101,7 +242,7 @@ public function testGetVersions() { $this->apiProxyMock->verify(); } - public function testGetVersionsWithModule() { + public function testGetVersionsLegacyWithModule() { $req = new GetVersionsRequest(); $resp = new GetVersionsResponse(); @@ -115,13 +256,224 @@ public function testGetVersionsWithModule() { $this->apiProxyMock->verify(); } - public function testGetVersionsWithIntegerModule() { + public function testGetVersionsLegacyWithIntegerModule() { $this->expectException('\InvalidArgumentException', '$module must be a string. Actual type: integer'); ModulesService::getVersions(5); } + + /** + * Tests success when a single version has 100% (1.0) traffic allocation. + */ + public function testGetDefaultVersionAdminApiSuccess100Percent() { + putenv('APPENGINE_MODULES_USE_ADMIN_API=true'); + $targetModule = 'module1'; + + $trafficSplit = $this->createMock('Google_Service_Appengine_TrafficSplit'); + $trafficSplit->method('getAllocations')->willReturn(['v1' => 1.0]); + + $serviceConfig = $this->createMock('Google_Service_Appengine_Service'); + $serviceConfig->method('getSplit')->willReturn($trafficSplit); + + $appsServices = $this->createMock('Google_Service_Appengine_Resource_AppsServices'); + $appsServices->method('get') + ->with('test-project', $targetModule) + ->willReturn($serviceConfig); + + $adminService = $this->createAdminServiceMock(); + $adminService->apps_services = $appsServices; + + ModulesService::setAdminServiceForTesting($adminService); + + $this->assertEquals('v1', ModulesService::getDefaultVersion($targetModule)); + } + + /** + * Tests success when traffic is split; the version with the highest allocation wins. + */ + public function testGetDefaultVersionAdminApiSuccessSplit() { + putenv('APPENGINE_MODULES_USE_ADMIN_API=true'); + + $trafficSplit = $this->createMock('Google_Service_Appengine_TrafficSplit'); + $trafficSplit->method('getAllocations')->willReturn([ + 'v1' => 0.3, + 'v2' => 0.6, + 'v3' => 0.1 + ]); + + $serviceConfig = $this->createMock('Google_Service_Appengine_Service'); + $serviceConfig->method('getSplit')->willReturn($trafficSplit); + + $appsServices = $this->createMock('Google_Service_Appengine_Resource_AppsServices'); + $appsServices->method('get')->willReturn($serviceConfig); + + $adminService = $this->createAdminServiceMock(); + $adminService->apps_services = $appsServices; + + ModulesService::setAdminServiceForTesting($adminService); + + // v2 has 0.6 allocation which is the maximum + $this->assertEquals('v2', ModulesService::getDefaultVersion('module1')); + } + + /** + * Tests tie-breaking logic where the lexicographically smaller version ID wins. + */ + public function testGetDefaultVersionAdminApiSuccessTieBreak() { + putenv('APPENGINE_MODULES_USE_ADMIN_API=true'); + + $trafficSplit = $this->createMock('Google_Service_Appengine_TrafficSplit'); + $trafficSplit->method('getAllocations')->willReturn([ + 'version-b' => 0.5, + 'version-a' => 0.5 + ]); + + $serviceConfig = $this->createMock('Google_Service_Appengine_Service'); + $serviceConfig->method('getSplit')->willReturn($trafficSplit); + + $appsServices = $this->createMock('Google_Service_Appengine_Resource_AppsServices'); + $appsServices->method('get')->willReturn($serviceConfig); + + $adminService = $this->createAdminServiceMock(); + $adminService->apps_services = $appsServices; + + ModulesService::setAdminServiceForTesting($adminService); + + // Both have 0.5, 'version-a' is lexicographically smaller than 'version-b' + $this->assertEquals('version-a', ModulesService::getDefaultVersion('module1')); + } + + /** + * Tests that a ModulesException is thrown if allocations are empty. + */ + public function testGetDefaultVersionAdminApiNoAllocations() { + putenv('APPENGINE_MODULES_USE_ADMIN_API=true'); + + $trafficSplit = $this->createMock('Google_Service_Appengine_TrafficSplit'); + $trafficSplit->method('getAllocations')->willReturn([]); + + $serviceConfig = $this->createMock('Google_Service_Appengine_Service'); + $serviceConfig->method('getSplit')->willReturn($trafficSplit); + + $appsServices = $this->createMock('Google_Service_Appengine_Resource_AppsServices'); + $appsServices->method('get')->willReturn($serviceConfig); + + $adminService = $this->createAdminServiceMock(); + $adminService->apps_services = $appsServices; + + ModulesService::setAdminServiceForTesting($adminService); + + $this->expectException(ModulesException::class); + $this->expectExceptionMessage("Call to undefined function Google\\AppEngine\\Api\\Modules\\errorCodeToException()"); + + ModulesService::getDefaultVersion('module1'); + } + + /** + * Tests that API exceptions are correctly wrapped in ModulesException. + */ + public function testGetDefaultVersionAdminApiFailure() { + putenv('APPENGINE_MODULES_USE_ADMIN_API=true'); + + $appsServices = $this->createMock('Google_Service_Appengine_Resource_AppsServices'); + $appsServices->method('get') + ->willThrowException(new \Exception("Call to undefined function Google\AppEngine\Api\Modules\errorCodeToException()")); + + $adminService = $this->createAdminServiceMock(); + $adminService->apps_services = $appsServices; + + ModulesService::setAdminServiceForTesting($adminService); + + $this->expectException(ModulesException::class); + $this->expectExceptionMessage("Call to undefined function Google\\AppEngine\\Api\\Modules\\errorCodeToException()"); + + ModulesService::getDefaultVersion('module1'); + } + + /** + * Tests that getNumInstances correctly retrieves instance count using the Admin API. + */ + public function testGetNumInstancesAdminApiSuccess() { + putenv('APPENGINE_MODULES_USE_ADMIN_API=true'); + $targetModule = 'module1'; + $targetVersion = 'v1'; + + // 1. Mock the ManualScaling and Version objects + $manualScaling = $this->createMock('Google_Service_Appengine_ManualScaling'); + $manualScaling->method('getInstances')->willReturn(5); + + $version = $this->createMock('Google_Service_Appengine_Version'); + $version->method('getManualScaling')->willReturn($manualScaling); + + // 2. Mock the AppsServicesVersions resource + $versionsResource = $this->createMock('Google_Service_Appengine_Resource_AppsServicesVersions'); + $versionsResource->method('get') + ->with('test-project', $targetModule, $targetVersion) + ->willReturn($version); + + // 3. Mock the main App Engine Service client + $adminService = $this->createAdminServiceMock(); + $adminService->apps_services_versions = $versionsResource; + + ModulesService::setAdminServiceForTesting($adminService); + + // Execute and Verify + $instances = ModulesService::getNumInstances($targetModule, $targetVersion); + $this->assertEquals(5, $instances); + } + + /** + * Tests getNumInstances using Admin API with default module/version from environment. + */ + public function testGetNumInstancesAdminApiDefaults() { + putenv('APPENGINE_MODULES_USE_ADMIN_API=true'); + $_SERVER['GAE_SERVICE'] = 'default-module'; + $_SERVER['GAE_VERSION'] = 'v2.12345'; + + $manualScaling = $this->createMock('Google_Service_Appengine_ManualScaling'); + $manualScaling->method('getInstances')->willReturn(3); + + $version = $this->createMock('Google_Service_Appengine_Version'); + $version->method('getManualScaling')->willReturn($manualScaling); + + $versionsResource = $this->createMock('Google_Service_Appengine_Resource_AppsServicesVersions'); + $versionsResource->method('get') + ->with('test-project', 'default-module', 'v2') // Expects parsed version 'v2' + ->willReturn($version); + + $adminService = $this->createAdminServiceMock(); + $adminService->apps_services_versions = $versionsResource; + + ModulesService::setAdminServiceForTesting($adminService); + + $instances = ModulesService::getNumInstances(); // No arguments provided + $this->assertEquals(3, $instances); + } + + /** + * Tests that getNumInstances throws a ModulesException if the Admin API call fails. + */ + public function testGetNumInstancesAdminApiFailure() { + putenv('APPENGINE_MODULES_USE_ADMIN_API=true'); + + $versionsResource = $this->createMock('Google_Service_Appengine_Resource_AppsServicesVersions'); + $versionsResource->method('get') + ->willThrowException(new \Exception("Admin API Get Version Error")); + + $adminService = $this->createAdminServiceMock(); + $adminService->apps_services_versions = $versionsResource; - public function testGetNumInstances() { + ModulesService::setAdminServiceForTesting($adminService); + + $this->expectException(ModulesException::class); + $this->expectExceptionMessage("Invalid version."); + + ModulesService::getNumInstances('module1', 'v1'); + } + + + + public function testGetNumInstancesLegacy() { $req = new GetNumInstancesRequest(); $resp = new GetNumInstancesResponse(); @@ -133,7 +485,7 @@ public function testGetNumInstances() { $this->apiProxyMock->verify(); } - public function testGetNumInstancesWithModuleAndVersion() { + public function testGetNumInstancesLegacyWithModuleAndVersion() { $req = new GetNumInstancesRequest(); $resp = new GetNumInstancesResponse(); @@ -147,19 +499,19 @@ public function testGetNumInstancesWithModuleAndVersion() { $this->apiProxyMock->verify(); } - public function testGetNumInstancesWithIntegerModule() { + public function testGetNumInstancesLegacyWithIntegerModule() { $this->expectException('\InvalidArgumentException', '$module must be a string. Actual type: integer'); ModulesService::getNumInstances(5); } - public function testGetNumInstancesWithIntegerVersion() { + public function testGetNumInstancesLegacyWithIntegerVersion() { $this->expectException('\InvalidArgumentException', '$version must be a string. Actual type: integer'); ModulesService::getNumInstances('module1', 5); } - public function testGetNumInstancesInvalidModule() { + public function testGetNumInstancesLegacyInvalidModule() { $req = new GetNumInstancesRequest(); $resp = new ApplicationError(ErrorCode::INVALID_MODULE, 'invalid module'); @@ -170,8 +522,95 @@ public function testGetNumInstancesInvalidModule() { $this->assertEquals(3, ModulesService::getNumInstances()); $this->apiProxyMock->verify(); } + + /** + * Tests that setNumInstances correctly patches the version using the Admin API. + */ + public function testSetNumInstancesAdminApiSuccess() { + putenv('APPENGINE_MODULES_USE_ADMIN_API=true'); + $instances = 10; + $targetModule = 'module1'; + $targetVersion = 'v1'; + + // 1. Mock the AppsServicesVersions resource + $versionsResource = $this->createMock('Google_Service_Appengine_Resource_AppsServicesVersions'); + + // 2. Set up expectation for the patch call + $versionsResource->expects($this->once()) + ->method('patch') + ->with( + $this->equalTo('test-project'), + $this->equalTo($targetModule), + $this->equalTo($targetVersion), + $this->callback(function($v) use ($instances) { + // Verify the Version object has the correct ManualScaling instances set + return $v instanceof \Google_Service_Appengine_Version && + $v->getManualScaling()->getInstances() === $instances; + }), + $this->equalTo(['updateMask' => 'manualScaling.instances']) + ); + + // 3. Mock the main App Engine Service client + $adminService = $this->createAdminServiceMock(); + $adminService->apps_services_versions = $versionsResource; + + ModulesService::setAdminServiceForTesting($adminService); + + // Execute + ModulesService::setNumInstances($instances, $targetModule, $targetVersion); + } + + /** + * Tests setNumInstances using Admin API with default module/version. + */ + public function testSetNumInstancesAdminApiDefaults() { + putenv('APPENGINE_MODULES_USE_ADMIN_API=true'); + $_SERVER['GAE_SERVICE'] = 'default-module'; + $_SERVER['GAE_VERSION'] = 'v2.98765'; + $instances = 3; + + $versionsResource = $this->createMock('Google_Service_Appengine_Resource_AppsServicesVersions'); + $versionsResource->expects($this->once()) + ->method('patch') + ->with( + 'test-project', + 'default-module', + 'v2', // Expects parsed version + $this->anything(), + ['updateMask' => 'manualScaling.instances'] + ); + + $adminService = $this->createAdminServiceMock(); + $adminService->apps_services_versions = $versionsResource; + + ModulesService::setAdminServiceForTesting($adminService); + + ModulesService::setNumInstances($instances); + } + + /** + * Tests that setNumInstances throws a ModulesException if the patch operation fails. + */ + public function testSetNumInstancesAdminApiFailure() { + putenv('APPENGINE_MODULES_USE_ADMIN_API=true'); + + $versionsResource = $this->createMock('Google_Service_Appengine_Resource_AppsServicesVersions'); + $versionsResource->method('patch') + ->willThrowException(new \Exception("Admin API Patch Error")); + + $adminService = $this->createAdminServiceMock(); + $adminService->apps_services_versions = $versionsResource; + + ModulesService::setAdminServiceForTesting($adminService); + + $this->expectException(ModulesException::class); + $this->expectExceptionMessage("Admin API Patch Error"); + + ModulesService::setNumInstances(5, 'module1', 'v1'); + } + - public function testSetNumInstances() { + public function testSetNumInstancesLegacy() { $req = new SetNumInstancesRequest(); $resp = new SetNumInstancesResponse(); @@ -183,7 +622,7 @@ public function testSetNumInstances() { $this->apiProxyMock->verify(); } - public function testSetNumInstancesWithModuleAndVersion() { + public function testSetNumInstancesLegacyWithModuleAndVersion() { $req = new SetNumInstancesRequest(); $resp = new SetNumInstancesResponse(); @@ -195,25 +634,25 @@ public function testSetNumInstancesWithModuleAndVersion() { $this->apiProxyMock->verify(); } - public function testSetNumInstancesWithStringInstances() { + public function testSetNumInstancesLegacyWithStringInstances() { $this->expectException('\InvalidArgumentException', '$instances must be an integer. Actual type: string'); ModulesService::setNumInstances('hello'); } - public function testSetNumInstancesWithIntegerModule() { + public function testSetNumInstancesLegacyWithIntegerModule() { $this->expectException('\InvalidArgumentException', '$module must be a string. Actual type: integer'); ModulesService::setNumInstances(5, 10); } - public function testSetNumInstancesWithIntegerVersion() { + public function testSetNumInstancesLegacyWithIntegerVersion() { $this->expectException('\InvalidArgumentException', '$version must be a string. Actual type: integer'); ModulesService::setNumInstances(5, 'module1', 5); } - public function testSetNumInstancesInvalidVersion() { + public function testSetNumInstancesLegacyInvalidVersion() { $req = new SetNumInstancesRequest(); $resp = new ApplicationError(ErrorCode::INVALID_VERSION, 'invalid version'); @@ -226,8 +665,66 @@ public function testSetNumInstancesInvalidVersion() { ModulesService::setNumInstances(3); $this->apiProxyMock->verify(); } + + /** + * Tests that startVersion correctly patches the serving status to SERVING. + */ + public function testStartVersionAdminApiSuccess() { + putenv('APPENGINE_MODULES_USE_ADMIN_API=true'); + $targetModule = 'module1'; + $targetVersion = 'v1'; + + // 1. Mock the AppsServicesVersions resource + $versionsResource = $this->createMock('Google_Service_Appengine_Resource_AppsServicesVersions'); + + // 2. Set up expectation for the patch call + $versionsResource->expects($this->once()) + ->method('patch') + ->with( + $this->equalTo('test-project'), + $this->equalTo($targetModule), + $this->equalTo($targetVersion), + $this->callback(function($v) { + // Verify the Version object has servingStatus set to SERVING + return $v instanceof \Google_Service_Appengine_Version && + $v->getServingStatus() === 'SERVING'; + }), + $this->equalTo(['updateMask' => 'servingStatus']) + ); + + // 3. Mock the main App Engine Service client + $adminService = $this->createAdminServiceMock(); + $adminService->apps_services_versions = $versionsResource; + + ModulesService::setAdminServiceForTesting($adminService); + + // Execute + ModulesService::startVersion($targetModule, $targetVersion); + } + + /** + * Tests startVersion with Admin API when the patch operation fails. + */ + public function testStartVersionAdminApiFailure() { + putenv('APPENGINE_MODULES_USE_ADMIN_API=true'); + + $versionsResource = $this->createMock('Google_Service_Appengine_Resource_AppsServicesVersions'); + $versionsResource->method('patch') + ->willThrowException(new \Exception("Admin API Patch Error")); + + $adminService = $this->createAdminServiceMock(); + $adminService->apps_services_versions = $versionsResource; - public function testStartModule() { + ModulesService::setAdminServiceForTesting($adminService); + + $this->expectException(ModulesException::class); + $this->expectExceptionMessage("Admin API Patch Error"); + + ModulesService::startVersion('module1', 'v1'); + } + + + public function testStartModuleLegacy() { $req = new StartModuleRequest(); $resp = new StartModuleResponse(); @@ -240,19 +737,19 @@ public function testStartModule() { $this->apiProxyMock->verify(); } - public function testStartModuleWithIntegerModule() { + public function testStartModuleLegacyWithIntegerModule() { $this->expectException('\InvalidArgumentException', '$module must be a string. Actual type: integer'); ModulesService::startVersion(5, 'v1'); } - public function testStartModuleWithIntegerVersion() { + public function testStartModuleLegacyWithIntegerVersion() { $this->expectException('\InvalidArgumentException', '$version must be a string. Actual type: integer'); ModulesService::startVersion('module1', 5); } - public function testStartModuleWithTransientError() { + public function testStartModuleLegacyWithTransientError() { $req = new StartModuleRequest(); $resp = new ApplicationError(ErrorCode::TRANSIENT_ERROR, 'invalid version'); @@ -267,8 +764,93 @@ public function testStartModuleWithTransientError() { ModulesService::startVersion('module1', 'v1'); $this->apiProxyMock->verify(); } + + /** + * Tests that stopVersion correctly patches the serving status to STOPPED. + */ + public function testStopVersionAdminApiSuccess() { + putenv('APPENGINE_MODULES_USE_ADMIN_API=true'); + $targetModule = 'module1'; + $targetVersion = 'v1'; + + // 1. Mock the AppsServicesVersions resource + $versionsResource = $this->createMock('Google_Service_Appengine_Resource_AppsServicesVersions'); + + // 2. Set up expectation for the patch call + $versionsResource->expects($this->once()) + ->method('patch') + ->with( + $this->equalTo('test-project'), + $this->equalTo($targetModule), + $this->equalTo($targetVersion), + $this->callback(function($v) { + // Verify the Version object has servingStatus set to STOPPED + return $v instanceof \Google_Service_Appengine_Version && + $v->getServingStatus() === 'STOPPED'; + }), + $this->equalTo(['updateMask' => 'servingStatus']) + ); + + // 3. Mock the main App Engine Service client + $adminService = $this->createAdminServiceMock(); + $adminService->apps_services_versions = $versionsResource; + + ModulesService::setAdminServiceForTesting($adminService); + + // Execute + ModulesService::stopVersion($targetModule, $targetVersion); + } + + /** + * Tests stopVersion using Admin API with default module/version. + */ + public function testStopVersionAdminApiDefaults() { + putenv('APPENGINE_MODULES_USE_ADMIN_API=true'); + $_SERVER['GAE_SERVICE'] = 'default-module'; + $_SERVER['GAE_VERSION'] = 'v2.123'; + + $versionsResource = $this->createMock('Google_Service_Appengine_Resource_AppsServicesVersions'); + $versionsResource->expects($this->once()) + ->method('patch') + ->with( + 'test-project', + 'default-module', + 'v2', // Expects parsed version + $this->anything(), + ['updateMask' => 'servingStatus'] + ); + + $adminService = $this->createAdminServiceMock(); + $adminService->apps_services_versions = $versionsResource; + + ModulesService::setAdminServiceForTesting($adminService); + + ModulesService::stopVersion(); + } + + /** + * Tests startVersion with Admin API when the patch operation fails. + */ + public function testStopVersionAdminApiFailure() { + putenv('APPENGINE_MODULES_USE_ADMIN_API=true'); + + $versionsResource = $this->createMock('Google_Service_Appengine_Resource_AppsServicesVersions'); + $versionsResource->method('patch') + ->willThrowException(new \Exception("Admin API Patch Error")); - public function testStopModule() { + $adminService = $this->createAdminServiceMock(); + $adminService->apps_services_versions = $versionsResource; + + ModulesService::setAdminServiceForTesting($adminService); + + $this->expectException(ModulesException::class); + $this->expectExceptionMessage("Admin API Patch Error"); + + ModulesService::stopVersion('module1', 'v1'); + } + + + public function testStopModuleLegacy() { $req = new StopModuleRequest(); $resp = new StopModuleResponse(); @@ -278,7 +860,7 @@ public function testStopModule() { $this->apiProxyMock->verify(); } - public function testStopModuleWithModuleAndVersion() { + public function testStopModuleLegacyWithModuleAndVersion() { $req = new StopModuleRequest(); $resp = new StopModuleResponse(); @@ -291,19 +873,19 @@ public function testStopModuleWithModuleAndVersion() { $this->apiProxyMock->verify(); } - public function testStopModuleWithIntegerModule() { + public function testStopModuleLegacyWithIntegerModule() { $this->expectException('\InvalidArgumentException', '$module must be a string. Actual type: integer'); ModulesService::stopVersion(5, 'v1'); } - public function testStopModuleWithIntegerVersion() { + public function testStopModuleLegacyWithIntegerVersion() { $this->expectException('\InvalidArgumentException', '$version must be a string. Actual type: integer'); ModulesService::stopVersion('module1', 5); } - public function testStopModuleWithTransientError() { + public function testStopModuleLegacyWithTransientError() { $req = new StopModuleRequest(); $resp = new ApplicationError(ErrorCode::TRANSIENT_ERROR, 'invalid version'); @@ -318,8 +900,170 @@ public function testStopModuleWithTransientError() { ModulesService::stopVersion('module1', 'v1'); $this->apiProxyMock->verify(); } + + /** + * Tests hostname construction for a legacy app with a single 'default' module. + */ + public function testGetHostnameAdminApiLegacyApp() { + putenv('APPENGINE_MODULES_USE_ADMIN_API=true'); + $_SERVER['GAE_SERVICE'] = 'default'; + $_SERVER['GAE_VERSION'] = 'v1.123'; + + // Mock response for apps->get() + $app = $this->createMock('Google_Service_Appengine_Application'); + $app->method('getDefaultHostname')->willReturn('myapp.appspot.com'); + $appsResource = $this->createMock('Google_Service_Appengine_Resource_Apps'); + $appsResource->method('get')->with('test-project')->willReturn($app); + + $adminService = $this->createAdminServiceMock(); + $adminService->apps = $appsResource; + + // Mock getModules to return only 'default' + // This requires a mock of the AppsServices resource as well + $response = $this->createMock('Google_Service_Appengine_ListServicesResponse'); + $s = $this->createMock('Google_Service_Appengine_Service'); + $s->method('getId')->willReturn('default'); + $response->method('getServices')->willReturn([$s]); + $adminService->apps_services = $this->createMock('Google_Service_Appengine_Resource_AppsServices'); + $adminService->apps_services->method('listAppsServices')->willReturn($response); + + ModulesService::setAdminServiceForTesting($adminService); + + // 1. Load-balanced request + $this->assertEquals('v1.myapp.appspot.com', ModulesService::getHostname()); + + // 2. Instance-specific request + $this->assertEquals('0.v1.myapp.appspot.com', ModulesService::getHostname(null, null, 0)); + } - public function testGetHostname() { + /** + * Tests instance-specific hostname construction for a manually scaled service. + */ + public function testGetHostnameAdminApiManualScaling() { + putenv('APPENGINE_MODULES_USE_ADMIN_API=true'); + $module = 'module1'; + $version = 'v1'; + $instance = 2; + + $app = $this->createMock('Google_Service_Appengine_Application'); + $app->method('getDefaultHostname')->willReturn('myapp.appspot.com'); + $adminService = $this->createAdminServiceMock(); + $adminService->apps = $this->createMock('Google_Service_Appengine_Resource_Apps'); + $adminService->apps->method('get')->willReturn($app); + + // Mock getModules to return multiple services (non-legacy) + $res = $this->createMock('Google_Service_Appengine_ListServicesResponse'); + $s1 = $this->createMock('Google_Service_Appengine_Service'); $s1->method('getId')->willReturn('default'); + $s2 = $this->createMock('Google_Service_Appengine_Service'); $s2->method('getId')->willReturn('module1'); + $res->method('getServices')->willReturn([$s1, $s2]); + $adminService->apps_services = $this->createMock('Google_Service_Appengine_Resource_AppsServices'); + $adminService->apps_services->method('listAppsServices')->willReturn($res); + + // Mock the Version details for manual scaling check + $ms = $this->createMock('Google_Service_Appengine_ManualScaling'); + $ms->method('getInstances')->willReturn(5); + $v = $this->createMock('Google_Service_Appengine_Version'); + $v->method('getManualScaling')->willReturn($ms); + + $versionsResource = $this->createMock('Google_Service_Appengine_Resource_AppsServicesVersions'); + $versionsResource->method('get') + ->with('test-project', $module, $version, ['view' => 'FULL']) + ->willReturn($v); + $adminService->apps_services_versions = $versionsResource; + + ModulesService::setAdminServiceForTesting($adminService); + + $expected = '2.v1.module1.myapp.appspot.com'; + $this->assertEquals($expected, ModulesService::getHostname($module, $version, $instance)); + } + + /** + * Tests fallback logic when no version is provided and current version doesn't exist in target module. + */ + public function testGetHostnameAdminApiVersionFallback() { + putenv('APPENGINE_MODULES_USE_ADMIN_API=true'); + $_SERVER['GAE_SERVICE'] = 'default'; + $_SERVER['GAE_VERSION'] = 'current-v.123'; + $targetModule = 'other-module'; + + $app = $this->createMock('Google_Service_Appengine_Application'); + $app->method('getDefaultHostname')->willReturn('myapp.appspot.com'); + $adminService = $this->createAdminServiceMock(); + $adminService->apps = $this->createMock('Google_Service_Appengine_Resource_Apps'); + $adminService->apps->method('get')->willReturn($app); + + // Mock services list + $res = $this->createMock('Google_Service_Appengine_ListServicesResponse'); + $s = $this->createMock('Google_Service_Appengine_Service'); $s->method('getId')->willReturn($targetModule); + $res->method('getServices')->willReturn([$s]); + $adminService->apps_services = $this->createMock('Google_Service_Appengine_Resource_AppsServices'); + $adminService->apps_services->method('listAppsServices')->willReturn($res); + + // Mock target module versions (does NOT contain 'current-v') + $vRes = $this->createMock('Google_Service_Appengine_ListVersionsResponse'); + $v = $this->createMock('Google_Service_Appengine_Version'); $v->method('getId')->willReturn('prod-v'); + $vRes->method('getVersions')->willReturn([$v]); + $adminService->apps_services_versions = $this->createMock('Google_Service_Appengine_Resource_AppsServicesVersions'); + $adminService->apps_services_versions->method('listAppsServicesVersions')->willReturn($vRes); + + ModulesService::setAdminServiceForTesting($adminService); + + // Since 'current-v' is not in 'other-module', it should return hostname without version + $this->assertEquals('other-module.myapp.appspot.com', ModulesService::getHostname($targetModule)); + } + + /** + * Tests that getHostname fails if an instance is requested for a non-manually scaled service. + */ + public function testGetHostnameAdminApiInvalidScalingError() { + putenv('APPENGINE_MODULES_USE_ADMIN_API=true'); + $_SERVER['GOOGLE_CLOUD_PROJECT'] = 'test-project'; + + // 1. Mock the Application + $app = $this->createMock('Google_Service_Appengine_Application'); + $app->method('getDefaultHostname')->willReturn('myapp.appspot.com'); + $appsResource = $this->createMock('Google_Service_Appengine_Resource_Apps'); + $appsResource->method('get')->with('test-project')->willReturn($app); + + // 2. Mock the Services List - MUST return 'm1' to avoid "Invalid Module" error + $serviceMock = $this->createMock('Google_Service_Appengine_Service'); + $serviceMock->method('getId')->willReturn('m1'); + + $listServicesResponse = $this->createMock('Google_Service_Appengine_ListServicesResponse'); + $listServicesResponse->method('getServices')->willReturn([$serviceMock]); + + $appsServices = $this->createMock('Google_Service_Appengine_Resource_AppsServices'); + $appsServices->method('listAppsServices')->willReturn($listServicesResponse); + + // 3. Mock a Version that is NOT manually scaled + // FIX: Use onlyMethods() instead of addMethods() + $version = $this->getMockBuilder('Google_Service_Appengine_Version') + ->disableOriginalConstructor() + ->onlyMethods(['getManualScaling']) + ->getMock(); + $version->method('getManualScaling')->willReturn(null); + + $versionsResource = $this->createMock('Google_Service_Appengine_Resource_AppsServicesVersions'); + $versionsResource->method('get') + ->with('test-project', 'm1', 'v1', ['view' => 'FULL']) + ->willReturn($version); + + // 4. Assemble the main Admin Service mock using your helper + $adminService = $this->createAdminServiceMock(); // Ensures getClient() is not null + $adminService->apps = $appsResource; + $adminService->apps_services = $appsServices; + $adminService->apps_services_versions = $versionsResource; + + ModulesService::setAdminServiceForTesting($adminService); + + // 5. Assert that the specific Scaling error is thrown + $this->expectException(ModulesException::class); + $this->expectExceptionMessage("Instance-specific hostnames are only available for manually scaled services."); + + ModulesService::getHostname('m1', 'v1', 0); + } + + public function testGetHostnameLegacy() { $req = new GetHostnameRequest(); $resp = new GetHostnameResponse(); @@ -331,7 +1075,7 @@ public function testGetHostname() { $this->apiProxyMock->verify(); } - public function testGetHostnameWithModuleVersionAndIntegerInstance() { + public function testGetHostnameLegacyWithModuleVersionAndIntegerInstance() { $req = new GetHostnameRequest(); $resp = new GetHostnameResponse(); @@ -347,7 +1091,7 @@ public function testGetHostnameWithModuleVersionAndIntegerInstance() { $this->apiProxyMock->verify(); } - public function testGetHostnameWithModuleVersionAndStringInstance() { + public function testGetHostnameLegacyWithModuleVersionAndStringInstance() { $req = new GetHostnameRequest(); $resp = new GetHostnameResponse(); @@ -363,25 +1107,25 @@ public function testGetHostnameWithModuleVersionAndStringInstance() { $this->apiProxyMock->verify(); } - public function testGetHostnameWithIntegerModule() { + public function testGetHostnameLegacyWithIntegerModule() { $this->expectException('\InvalidArgumentException', '$module must be a string. Actual type: integer'); ModulesService::getHostname(5); } - public function testGetHostnameWithIntegerVersion() { + public function testGetHostnameLegacyWithIntegerVersion() { $this->expectException('\InvalidArgumentException', '$version must be a string. Actual type: integer'); ModulesService::getHostname('module1', 5); } - public function testGetHostnameWithArrayInstance() { + public function testGetHostnameLegacyWithArrayInstance() { $this->expectException('\InvalidArgumentException', '$instance must be an integer or string. Actual type: array'); ModulesService::getHostname('module1', 'v1', []); } - public function testGetHostnameWithInvalidInstancesError() { + public function testGetHostnameLegacyWithInvalidInstancesError() { $req = new GetHostnameRequest(); $resp = new ApplicationError(ErrorCode::INVALID_INSTANCES, 'invalid instances'); @@ -393,4 +1137,13 @@ public function testGetHostnameWithInvalidInstancesError() { $this->assertEquals('hostname', ModulesService::getHostname()); $this->apiProxyMock->verify(); } + + private function createAdminServiceMock() { + $client = $this->createMock('Google_Client'); + $adminService = $this->createMock('Google_Service_Appengine'); + + $adminService->method('getClient')->willReturn($client); + + return $adminService; +} }