true, 'verboseOutput' => true ]); // 偽物のAPIサーバーに繋ぐOpenprovider(本物のキャッシュは使わない) $cacheName = 'lbtest_op_'.bin2hex(random_bytes(4)); $make = fn(string $baseUrl, ?string $cacheDir = null) => new class($baseUrl, $cacheDir ?? $cacheName) extends Openprovider { public function __construct(string $baseUrl, string $cacheDir) { $this->BASEURL = $baseUrl; $this->cacheDir = $cacheDir; parent::__construct(); } protected function setupCurl(string $uri, string $resType = 'GET', array $payload = []): Curl { return parent::setupCurl($uri, $resType, $payload)->setBlockPrivateAddresses(false)->setTimeout(5); } }; $test->describe('Openprovider(偽物のAPIサーバー)', function($test) use ($make): void { if (!OPENPROVIDER_ENABLED) { $test->skipAll('OPENPROVIDER_ENABLEDが無効'); } $srv = OPENPROVIDER_ENABLED ? MockServer::start('http-server.php') : null; if (OPENPROVIDER_ENABLED && $srv === null) { $test->skipAll('偽物のサーバーを起動出来ない(proc_openが無効?)'); } $base = $srv ? $srv->url('/v1beta') : ''; // 失敗するテストではassert()を止める(DEBUG_MODEだとAssertionErrorになる) $quiet = function(callable $fn) { $old = ini_get('zend.assertions'); if ($old !== '-1') ini_set('zend.assertions', '0'); // assert_not_null()等の「assert機能性が無効です」の警告も出さない set_error_handler(fn(int $no, string $msg) => $no === E_USER_WARNING && str_contains($msg, 'assert')); try { return $fn(); } finally { restore_error_handler(); if ($old !== '-1') ini_set('zend.assertions', $old); } }; $count = function(string $path) use ($srv): int { $c = (new Curl($srv->url('/count?path='.urlencode($path))))->setBlockPrivateAddresses(false); $c->execute(); return (int)(json_decode($c->getResponseBody(), true)['count'] ?? -1); }; $test->it('ログインしてトークンを受け取れるはず', function($test) use ($make, $base): void { $op = $make($base); $res = $op->login(); $test->assertTrue($res->isSuccess); $test->assertEquals('tok123', $res->data['data']['token']); $test->assertArrayHasKey('last_auth', $res->data); }); $test->it('2回目はキャッシュのトークンを使うはず', function($test) use ($make, $base, $count): void { $before = $count('/v1beta/auth/login'); $res = $make($base)->login(); $test->assertTrue($res->isSuccess); $test->assertEquals($before, $count('/v1beta/auth/login')); }); $test->it('ログインの失敗はエラーの説明を返すはず', function($test) use ($make, $srv, $quiet): void { // 以前は配列をResult::Error()に渡してTypeErrorになった $op = $make($srv->url('/bad/v1beta')); $op->deleteCache('login'); $res = $quiet(fn() => $op->login()); $test->assertFalse($res->isSuccess); $test->assertEquals('Authentication/Authorization Failed', $res->message); }); $test->it('ドメインがあるかどうか確認出来るはず', function($test) use ($make, $base): void { $op = $make($base); $op->login(); $res = $op->checkDomainAvailable(['076.moe', '076.xxx', '076.co.jp']); $test->assertTrue($res->isSuccess); $test->assertEquals([ ['domain' => '076.moe', 'status' => 'active', 'reason' => 'In use'], ['domain' => '076.xxx', 'status' => 'free'], // 以前は「co.jp」の様なTLDで「不正なドメイン形」になった ['domain' => '076.co.jp', 'status' => 'free'], ], $res->data); }); $test->it('不正なドメインを拒否するはず', function($test) use ($make, $base): void { $op = $make($base); foreach (['076', '.moe', '076.'] as $d) { $res = $op->checkDomainAvailable([$d]); $test->assertFalse($res->isSuccess, "「{$d}」が拒否されなかった"); $test->assertStringContains('不正なドメイン形', $res->message); } }); $test->it('ドメイン一覧を受け取り、キャッシュするはず', function($test) use ($make, $base, $count): void { $op = $make($base); $op->login(); $res = $op->listDomains(); $test->assertTrue($res->isSuccess); $test->assertEquals(1, $res->data[0]['id']); $before = $count('/v1beta/domains'); $test->assertTrue($op->listDomains()->isSuccess); $test->assertEquals($before, $count('/v1beta/domains')); }); $test->it('トークンが無いとエラーになるはず', function($test) use ($make, $base, $quiet): void { $op = $make($base); $op->deleteCache('listdomains'); // login()しない $res = $quiet(fn() => $op->listDomains()); $test->assertFalse($res->isSuccess); }); $test->it('JSONではないエラーレスポンスでも落ちないはず', function($test) use ($make, $base, $quiet): void { $op = $make($base.'/gateway?'); $res = $quiet(fn() => $op->listTlds()); $test->assertFalse($res->isSuccess); }); $test->it('接続出来ない場合はエラーになるはず', function($test) use ($make, $quiet): void { $probe = stream_socket_server('tcp://127.0.0.1:0'); $port = (int)substr(strrchr(stream_socket_get_name($probe, false), ':'), 1); fclose($probe); $op = $make("http://127.0.0.1:{$port}/v1beta"); $op->deleteCache('login'); $res = $quiet(fn() => $op->login()); $test->assertFalse($res->isSuccess); $test->assertStringContains('CURLの実行に失敗', $res->message); }); $srv?->stop(); }); $test->describe('Openprovider(全部のAPI)', function($test) use ($make, $cacheName): void { if (!OPENPROVIDER_ENABLED) { $test->skipAll('OPENPROVIDER_ENABLEDが無効'); } $srv = OPENPROVIDER_ENABLED ? MockServer::start('http-server.php') : null; if (OPENPROVIDER_ENABLED && $srv === null) { $test->skipAll('偽物のサーバーを起動出来ない(proc_openが無効?)'); } $base = $srv ? $srv->url('/v1beta') : ''; $ctl = function(string $path, ?array $body = null) use ($srv): array { $c = (new Curl($srv->url($path)))->setBlockPrivateAddresses(false)->setTimeout(5); if ($body !== null) $c->setMethod('POST')->setPostRaw(json_encode($body)); $c->execute(); return (array)json_decode($c->getResponseBody(), true); }; // 次のレスポンスを決める $respond = fn(array $data, int $status = 200, int $code = 0, string $desc = '') => $ctl('/__next', ['status' => $status, 'body' => ['code' => $code, 'desc' => $desc, 'data' => $data]]); // 配列のキーを並べ替える(JSONの順番に関係なく比べる為) $sorted = function(mixed $v) use (&$sorted): mixed { if (!is_array($v)) return $v; if (!array_is_list($v)) ksort($v); return array_map($sorted, $v); }; $quiet = function(callable $fn) { $old = ini_get('zend.assertions'); if ($old !== '-1') ini_set('zend.assertions', '0'); set_error_handler(fn(int $no, string $msg) => $no === E_USER_WARNING && str_contains($msg, 'assert')); try { return $fn(); } finally { restore_error_handler(); if ($old !== '-1') ini_set('zend.assertions', $old); } }; // テスト毎にキャッシュの無いOpenprovider(キャッシュでリクエストが省略されない様に) $n = 0; $fresh = function() use ($make, $base, $cacheName, &$n) { $op = $make($base, $cacheName.'_'.(++$n)); $op->login(); return $op; }; // [メソッド, 呼び出し, HTTPメソッド, パス, クエリー, 本文(GET・DELETEはnull)] $owner = (string)(OPENPROVIDER['owner_handle'] ?? ''); $ns = [['name' => 'ns1.076.moe', 'ip' => '192.0.2.1']]; $calls = [ // ドメイン ['createDomain', fn($op) => $op->createDomain('076studio.jp', ['owner_handle' => 'OW1', 'name_servers' => ['ns1.076.moe'], 'is_private_whois_enabled' => true], 2), 'POST', '/domains', '', ['domain' => ['name' => '076studio', 'extension' => 'jp'], 'period' => 2, 'owner_handle' => 'OW1', 'admin_handle' => $owner, 'tech_handle' => $owner, 'billing_handle' => $owner, 'reseller_handle' => $owner, 'name_servers' => [['name' => 'ns1.076.moe']], 'is_private_whois_enabled' => true, 'autorenew' => 'default']], ['suggestDomainname', fn($op) => $op->suggestDomainname(['name' => '076', 'tlds' => ['moe']]), 'POST', '/domains/suggest-name', '', ['name' => '076', 'tlds' => ['moe']]], ['tradeDomainname', fn($op) => $op->tradeDomainname(['domain' => ['name' => '076', 'extension' => 'moe'], 'owner_handle' => 'OW2']), 'POST', '/domains/trade', '', ['domain' => ['name' => '076', 'extension' => 'moe'], 'owner_handle' => 'OW2']], ['transferDomainname', fn($op) => $op->transferDomainname(['domain' => ['name' => '076', 'extension' => 'moe'], 'auth_code' => 'abc']), 'POST', '/domains/transfer', '', ['domain' => ['name' => '076', 'extension' => 'moe'], 'auth_code' => 'abc']], ['getDomainname', fn($op) => $op->getDomainname(5, ['with_history' => true]), 'GET', '/domains/5', 'with_history=true', null], ['updateDomainname', fn($op) => $op->updateDomainname(5, ['autorenew' => 'on']), 'PUT', '/domains/5', '', ['autorenew' => 'on']], ['deleteDomainname', fn($op) => $op->deleteDomainname(5, ['type' => 'transfer']), 'DELETE', '/domains/5', 'type=transfer', null], ['retryLastDomainnameOperation', fn($op) => $op->retryLastDomainnameOperation(5), 'POST', '/domains/5/last-operation/restart', '', ['id' => 5]], ['renewDomainname', fn($op) => $op->renewDomainname(5, ['period' => 1]), 'POST', '/domains/5/renew', '', ['id' => 5, 'period' => 1]], ['restoreDomainname', fn($op) => $op->restoreDomainname(5), 'POST', '/domains/5/restore', '', ['id' => 5]], ['approveDomainnameTransfer', fn($op) => $op->approveDomainnameTransfer(5, ['approve' => 1]), 'POST', '/domains/5/transfer/approve', '', ['id' => 5, 'approve' => 1]], ['sendDomainnameFoa1', fn($op) => $op->sendDomainnameFoa1(5), 'POST', '/domains/5/transfer/send-foa1', '', ['id' => 5]], ['getDomainAdditionalData', fn($op) => $op->getDomainAdditionalData(['domain.name' => '076', 'domain.extension' => 'jp']), 'GET', '/domains/additional-data', 'domain.name=076&domain.extension=jp', null], ['getDomainCustomerAdditionalData', fn($op) => $op->getDomainCustomerAdditionalData(['domain.name' => '076', 'domain.extension' => 'jp']), 'GET', '/domains/additional-data/customers', 'domain.name=076&domain.extension=jp', null], ['getDomainPrices', fn($op) => $op->getDomainPrices(['domain.name' => '076', 'domain.extension' => 'moe', 'operation' => 'create']), 'GET', '/domains/prices', 'domain.name=076&domain.extension=moe&operation=create', null], ['getAuthCode', fn($op) => $op->getAuthCode(5), 'GET', '/domains/5/authcode', '', null], ['resetAuthCode', fn($op) => $op->resetAuthCode(5), 'POST', '/domains/5/authcode/reset', '', ['id' => 5]], ['listTlds', fn($op) => $op->listTlds(), 'GET', '/tlds', 'limit=10&offset=0&order=ASC', null], ['getTld', fn($op) => $op->getTld('moe', ['with_price' => true]), 'GET', '/tlds/moe', 'with_price=true', null], // 請求 ['listInvoices', fn($op) => $op->listInvoices(), 'GET', '/invoices', 'limit=10&offset=0', null], ['listPayments', fn($op) => $op->listPayments(), 'GET', '/payments', 'limit=10&offset=0', null], ['getSuggestedTopUpAmount', fn($op) => $op->getSuggestedTopUpAmount(), 'GET', '/payments/suggested-top-up-amount', '', null], ['listTransactions', fn($op) => $op->listTransactions(['limit' => 3]), 'GET', '/transactions', 'limit=3', null], // DNS ['createDomainToken', fn($op) => $op->createDomainToken(['domain' => '076.moe', 'zone_provider' => 'sectigo']), 'POST', '/dns/domain-token', '', ['domain' => '076.moe', 'zone_provider' => 'sectigo']], ['listNameservers', fn($op) => $op->listNameservers(), 'GET', '/dns/nameservers', 'limit=25&offset=0&order_by=name&order=asc', null], ['createNameserver', fn($op) => $op->createNameserver($ns[0]), 'POST', '/dns/nameservers', '', $ns[0]], ['getNameserver', fn($op) => $op->getNameserver('ns1.076.moe'), 'GET', '/dns/nameservers/ns1.076.moe', '', null], ['updateNameserver', fn($op) => $op->updateNameserver('ns1.076.moe', ['ip' => '192.0.2.2']), 'PUT', '/dns/nameservers/ns1.076.moe', '', ['name' => 'ns1.076.moe', 'ip' => '192.0.2.2']], ['deleteNameserver', fn($op) => $op->deleteNameserver('ns1.076.moe'), 'DELETE', '/dns/nameservers/ns1.076.moe', '', null], ['listNsGroups', fn($op) => $op->listNsGroups(), 'GET', '/dns/nameservers/groups', 'limit=25&offset=0&order_by.ns_group=asc', null], ['createNsGroup', fn($op) => $op->createNsGroup(['ns_group' => 'grp', 'name_servers' => $ns]), 'POST', '/dns/nameservers/groups', '', ['ns_group' => 'grp', 'name_servers' => $ns]], ['getNsGroup', fn($op) => $op->getNsGroup('my group'), 'GET', '/dns/nameservers/groups/my%20group', '', null], ['updateNsGroup', fn($op) => $op->updateNsGroup('grp', ['name_servers' => $ns]), 'PUT', '/dns/nameservers/groups/grp', '', ['ns_group' => 'grp', 'name_servers' => $ns]], ['deleteNsGroup', fn($op) => $op->deleteNsGroup('grp'), 'DELETE', '/dns/nameservers/groups/grp', '', null], ['listDnsTemplate', fn($op) => $op->listDnsTemplate(['with_records' => true]), 'GET', '/dns/templates', 'with_records=true', null], ['createDnsTemplate', fn($op) => $op->createDnsTemplate(['name' => 'web', 'records' => [['type' => 'A', 'value' => '192.0.2.1']]]), 'POST', '/dns/templates', '', ['name' => 'web', 'records' => [['type' => 'A', 'value' => '192.0.2.1']]]], ['getDnsTemplate', fn($op) => $op->getDnsTemplate(3), 'GET', '/dns/templates/3', '', null], ['deleteDnsTemplate', fn($op) => $op->deleteDnsTemplate(3), 'DELETE', '/dns/templates/3', '', null], ['listDnsZones', fn($op) => $op->listDnsZones(), 'GET', '/dns/zones', 'limit=25&offset=0&order_by.name=asc', null], ['createDnsZone', fn($op) => $op->createDnsZone(['domain' => ['name' => '076', 'extension' => 'moe'], 'type' => 'master']), 'POST', '/dns/zones', '', ['domain' => ['name' => '076', 'extension' => 'moe'], 'type' => 'master']], ['getDnsZone', fn($op) => $op->getDnsZone('076.moe', ['with_records' => true]), 'GET', '/dns/zones/076.moe', 'with_records=true', null], ['updateDnsZone', fn($op) => $op->updateDnsZone('076.moe', ['records' => ['add' => [['type' => 'A', 'name' => 'www', 'value' => '192.0.2.1', 'ttl' => 900]]]]), 'PUT', '/dns/zones/076.moe', '', ['name' => '076.moe', 'records' => ['add' => [['type' => 'A', 'name' => 'www', 'value' => '192.0.2.1', 'ttl' => 900]]]]], ['deleteDnsZone', fn($op) => $op->deleteDnsZone('076.moe', ['provider' => 'openprovider']), 'DELETE', '/dns/zones/076.moe', 'provider=openprovider', null], ['listZoneRecords', fn($op) => $op->listZoneRecords('076.moe', ['type' => 'A']), 'GET', '/dns/zones/076.moe/records', 'type=A', null], // EasyDMARC ['getEasydmarc', fn($op) => $op->getEasydmarc('076.co.jp'), 'GET', '/easydmarcs', 'domain.name=076&domain.extension=co.jp', null], ['createEasydmarc', fn($op) => $op->createEasydmarc('076.moe', 'OW1'), 'POST', '/easydmarcs', '', ['domain' => ['name' => '076', 'extension' => 'moe'], 'owner_handle' => 'OW1']], ['listEasydmarcs', fn($op) => $op->listEasydmarcs(['domain' => '076.moe', 'status' => ['ACT', 'FAI']]), 'GET', '/easydmarcs/list', 'status=ACT&status=FAI&domain.name=076&domain.extension=moe', null], ['deleteEasydmarc', fn($op) => $op->deleteEasydmarc(7), 'DELETE', '/easydmarcs/7', '', null], ['retryEasydmarc', fn($op) => $op->retryEasydmarc(7), 'POST', '/easydmarcs/7/retry', '', ['id' => 7]], ['getEasydmarcSso', fn($op) => $op->getEasydmarcSso(7, 'dashboard'), 'GET', '/easydmarcs/7/sso', 'type=dashboard', null], // メールテンプレート ['listEmails', fn($op) => $op->listEmails(['group' => 'ive']), 'GET', '/emails', 'group=ive', null], ['createEmail', fn($op) => $op->createEmail(['name' => 'mail', 'group' => 'ive']), 'POST', '/emails', '', ['name' => 'mail', 'group' => 'ive']], ['updateEmail', fn($op) => $op->updateEmail(9, ['is_active' => false]), 'PUT', '/emails/9', '', ['id' => 9, 'is_active' => false]], ['deleteEmail', fn($op) => $op->deleteEmail(9), 'DELETE', '/emails/9', '', null], // ライセンス ['listLicenses', fn($op) => $op->listLicenses(['product' => 'plesk']), 'GET', '/licenses', 'product=plesk', null], ['resetLicenseHwid', fn($op) => $op->resetLicenseHwid('plesk', 11), 'POST', '/licenses/hwids/reset/plesk/11', '', ['key_id' => 11, 'product' => 'plesk']], ['listLicenseItems', fn($op) => $op->listLicenseItems(['product' => 'plesk']), 'GET', '/licenses/items', 'product=plesk', null], ['listPleskLicenses', fn($op) => $op->listPleskLicenses(), 'GET', '/licenses/plesk', '', null], ['createPleskLicense', fn($op) => $op->createPleskLicense(['items' => ['PLSK-12-WEB-PRO'], 'period' => 1]), 'POST', '/licenses/plesk', '', ['items' => ['PLSK-12-WEB-PRO'], 'period' => 1]], ['getPleskKey', fn($op) => $op->getPleskKey(11), 'GET', '/licenses/plesk/key/11', '', null], ['getPleskLicense', fn($op) => $op->getPleskLicense(11), 'GET', '/licenses/plesk/11', '', null], ['updatePleskLicense', fn($op) => $op->updatePleskLicense(11, ['title' => 'web']), 'PUT', '/licenses/plesk/11', '', ['key_id' => 11, 'title' => 'web']], ['deletePleskLicense', fn($op) => $op->deletePleskLicense(11), 'DELETE', '/licenses/plesk/11', '', null], ['listVirtuozzoLicenses', fn($op) => $op->listVirtuozzoLicenses(['limit' => 5]), 'GET', '/licenses/virtuozzo', 'limit=5', null], ['createVirtuozzoLicense', fn($op) => $op->createVirtuozzoLicense(['items' => ['VZ'], 'pcs_capacity' => 2]), 'POST', '/licenses/virtuozzo', '', ['items' => ['VZ'], 'pcs_capacity' => 2]], ['getVirtuozzoLicense', fn($op) => $op->getVirtuozzoLicense(12), 'GET', '/licenses/virtuozzo/12', '', null], ['updateVirtuozzoLicense', fn($op) => $op->updateVirtuozzoLicense(12, ['suspended' => true]), 'PUT', '/licenses/virtuozzo/12', '', ['key_id' => 12, 'suspended' => true]], ['deleteVirtuozzoLicense', fn($op) => $op->deleteVirtuozzoLicense(12), 'DELETE', '/licenses/virtuozzo/12', '', null], ['getVirtuozzoKey', fn($op) => $op->getVirtuozzoKey(12), 'GET', '/licenses/virtuozzo/12/key', '', null], // リセラー・顧客様 ['listContacts', fn($op) => $op->listContacts(['role' => 'tech']), 'GET', '/contacts', 'role=tech', null], ['createContact', fn($op) => $op->createContact(['username' => 'staff1', 'role' => 'tech']), 'POST', '/contacts', '', ['username' => 'staff1', 'role' => 'tech']], ['getContact', fn($op) => $op->getContact(4, true), 'GET', '/contacts/4', 'with_additional_data=true', null], ['updateContact', fn($op) => $op->updateContact(4, ['comments' => 'メモ']), 'PUT', '/contacts/4', '', ['id' => 4, 'comments' => 'メモ']], ['deleteContact', fn($op) => $op->deleteContact(4), 'DELETE', '/contacts/4', '', null], ['getContactNotificationSetting', fn($op) => $op->getContactNotificationSetting(), 'GET', '/contact/notification-setting', '', null], ['updateContactNotificationSetting', fn($op) => $op->updateContactNotificationSetting(['marketing_promotion' => false]), 'POST', '/contact/notification-setting', '', ['marketing_promotion' => false]], ['listCustomers', fn($op) => $op->listCustomers(['handle_pattern' => 'AB*']), 'GET', '/customers', 'handle_pattern=AB%2A', null], ['createCustomer', fn($op) => $op->createCustomer(['email' => 'a@076.moe']), 'POST', '/customers', '', ['email' => 'a@076.moe']], ['getCustomer', fn($op) => $op->getCustomer('AB123-NL', true), 'GET', '/customers/AB123-NL', 'with_additional_data=true', null], ['updateCustomer', fn($op) => $op->updateCustomer('AB123-NL', ['email' => 'b@076.moe']), 'PUT', '/customers/AB123-NL', '', ['handle' => 'AB123-NL', 'email' => 'b@076.moe']], ['deleteCustomer', fn($op) => $op->deleteCustomer('AB123-NL'), 'DELETE', '/customers/AB123-NL', '', null], ['listEmailVerifications', fn($op) => $op->listEmailVerifications(['email' => 'a@076.moe']), 'GET', '/customers/verifications/emails/domains', 'email=a%40076.moe', null], ['restartEmailVerifications', fn($op) => $op->restartEmailVerifications('a@076.moe'), 'POST', '/customers/verifications/emails/restart', '', ['email' => 'a@076.moe']], ['startEmailVerifications', fn($op) => $op->startEmailVerifications('a@076.moe', 'AB123-NL', 'ja'), 'POST', '/customers/verifications/emails/start', '', ['email' => 'a@076.moe', 'handle' => 'AB123-NL', 'language' => 'ja']], ['getReseller', fn($op) => $op->getReseller(['with_statistics' => true]), 'GET', '/resellers', 'with_statistics=true', null], ['updateReseller', fn($op) => $op->updateReseller(42, ['comments' => 'x']), 'PUT', '/resellers/42', '', ['id' => 42, 'comments' => 'x']], ['listSettings', fn($op) => $op->listSettings(), 'GET', '/resellers/settings', '', null], ['listStatistics', fn($op) => $op->listStatistics(), 'GET', '/resellers/statistics', '', null], ['listTags', fn($op) => $op->listTags(['key' => 'k']), 'GET', '/tags', 'key=k', null], ['createTag', fn($op) => $op->createTag(['key' => 'k', 'value' => 'v']), 'POST', '/tags', '', ['key' => 'k', 'value' => 'v']], ['deleteTag', fn($op) => $op->deleteTag(['key' => 'k', 'value' => 'v']), 'DELETE', '/tags', 'key=k&value=v', null], // SpamExpert ['generateLoginUrlSpamExpert', fn($op) => $op->generateLoginUrlSpamExpert(['domain_or_email' => '076.moe']), 'POST', '/spam-expert/generate-login-url', '', ['domain_or_email' => '076.moe']], ['createSeDomain', fn($op) => $op->createSeDomain(['domain_name' => '076.moe']), 'POST', '/spam-expert/domains', '', ['domain_name' => '076.moe']], ['getSeDomain', fn($op) => $op->getSeDomain('076.moe', ['with_records' => true]), 'GET', '/spam-expert/domains/076.moe', 'with_records=true', null], ['updateSeDomain', fn($op) => $op->updateSeDomain('076.moe', ['aliases' => ['076.jp']]), 'PUT', '/spam-expert/domains/076.moe', '', ['domain_name' => '076.moe', 'aliases' => ['076.jp']]], ['deleteSeDomain', fn($op) => $op->deleteSeDomain('076.moe', ['bundle' => true]), 'DELETE', '/spam-expert/domains/076.moe', 'bundle=true', null], // SSL証明書 ['listApproverEmails', fn($op) => $op->listApproverEmails(['product_id' => 1, 'domain' => '076.moe']), 'GET', '/ssl/approver-emails', 'product_id=1&domain=076.moe', null], ['createCsr', fn($op) => $op->createCsr(['common_name' => '076.moe', 'bits' => 2048]), 'POST', '/ssl/csr', '', ['common_name' => '076.moe', 'bits' => 2048]], ['decodeCsr', fn($op) => $op->decodeCsr('-----BEGIN CERTIFICATE REQUEST-----'), 'POST', '/ssl/csr/decode', '', ['csr' => '-----BEGIN CERTIFICATE REQUEST-----']], ['listSslOrders', fn($op) => $op->listSslOrders(['status' => ['ACT', 'PAI']]), 'GET', '/ssl/orders', 'status=ACT&status=PAI', null], ['createSslOrder', fn($op) => $op->createSslOrder(['product_id' => 1, 'period' => 1]), 'POST', '/ssl/orders', '', ['product_id' => 1, 'period' => 1]], ['getSslOrder', fn($op) => $op->getSslOrder(8), 'GET', '/ssl/orders/8', '', null], ['updateSslOrder', fn($op) => $op->updateSslOrder(8, ['autorenew' => 'on']), 'PUT', '/ssl/orders/8', '', ['id' => 8, 'autorenew' => 'on']], ['cancelSslOrder', fn($op) => $op->cancelSslOrder(8), 'POST', '/ssl/orders/8/cancel', '', ['id' => 8]], ['reissueSslOrder', fn($op) => $op->reissueSslOrder(8, ['csr' => 'csr']), 'POST', '/ssl/orders/8/reissue', '', ['id' => 8, 'csr' => 'csr']], ['renewSslOrder', fn($op) => $op->renewSslOrder(8), 'POST', '/ssl/orders/8/renew', '', ['id' => 8]], ['updateSslApproverEmail', fn($op) => $op->updateSslApproverEmail(8, 'admin@076.moe'), 'PUT', '/ssl/orders/8/approver-email', '', ['id' => 8, 'approver_email' => 'admin@076.moe']], ['resendSslApproverEmail', fn($op) => $op->resendSslApproverEmail(8), 'POST', '/ssl/orders/8/approver-email/resend', '', ['id' => 8]], ['createSslOtpToken', fn($op) => $op->createSslOtpToken(8), 'POST', '/ssl/orders/8/otp-tokens', '', ['id' => 8]], ['listSslProducts', fn($op) => $op->listSslProducts(['with_price' => true]), 'GET', '/ssl/products', 'with_price=true', null], ['getSslProduct', fn($op) => $op->getSslProduct(1), 'GET', '/ssl/products/1', '', null], ]; foreach ($calls as [$name, $call, $method, $path, $query, $body]) { $test->it("{$name}()は{$method} {$path}を呼ぶはず", function($test) use ($fresh, $respond, $ctl, $sorted, $call, $method, $path, $query, $body): void { $data = ['id' => 1, 'name' => 'テスト']; $respond($data); $res = $call($fresh()); $test->assertTrue($res->isSuccess, (string)$res->message); $test->assertSame($sorted(['code' => 0, 'desc' => '', 'data' => $data]), $sorted($res->data)); $req = $ctl('/__last'); $test->assertEquals($method, $req['method']); $test->assertEquals("/v1beta{$path}", $req['path']); $test->assertEquals($query, $req['query']); if ($body === null) $test->assertEquals('', $req['body'], 'GET・DELETEに本文は無い'); else $test->assertSame($sorted($body), $sorted(json_decode($req['body'], true))); }); } $test->it('一覧と確認はresultsだけを返すはず', function($test) use ($fresh, $respond, $ctl): void { $respond(['results' => [['id' => 1]], 'total' => 1]); $res = $fresh()->listDomains(['status' => 'ACT', 'limit' => 5]); $test->assertEquals([['id' => 1]], $res->data); $test->assertEquals('status=ACT&limit=5', $ctl('/__last')['query']); $respond(['results' => [['domain' => '076.moe', 'status' => 'free']]]); $res = $fresh()->checkDomainAvailable(['076.moe'], true); $test->assertEquals([['domain' => '076.moe', 'status' => 'free']], $res->data); $test->assertEquals(['domains' => [['name' => '076', 'extension' => 'moe']], 'with_price' => true], json_decode($ctl('/__last')['body'], true)); }); $test->it('ログインでリセラーIDを記録するはず', function($test) use ($fresh): void { $test->assertEquals(42, $fresh()->getResellerId()); }); $test->it('APIのエラー(codeが0以外、successがfalse、HTTPのエラー)は失敗のはず', function($test) use ($fresh, $respond, $quiet): void { $respond([], 200, 316, 'Domain is not available'); $res = $fresh()->getDomainname(5); $test->assertFalse($res->isSuccess); $test->assertStringContains('Domain is not available', $res->message); $respond(['success' => false]); $test->assertFalse($fresh()->deleteDnsZone('076.moe')->isSuccess); $respond([], 400, 10001, 'Invalid request'); $res = $quiet(fn() => $fresh()->createDnsZone(['type' => 'master'])); $test->assertFalse($res->isSuccess); $test->assertStringContains('Invalid request', $res->message); }); $test->it('不正なドメインはリクエストを送らずに失敗するはず', function($test) use ($fresh, $ctl): void { $op = $fresh(); $before = $ctl('/__last'); foreach (['getEasydmarc' => fn() => $op->getEasydmarc('076'), 'createEasydmarc' => fn() => $op->createEasydmarc('.moe'), 'listEasydmarcs' => fn() => $op->listEasydmarcs(['domain' => '076.']), 'createDomain' => fn() => $op->createDomain('076', [])] as $name => $fn) { $res = $fn(); $test->assertFalse($res->isSuccess, $name); $test->assertStringContains('不正なドメイン形', $res->message); } $test->assertEquals($before, $ctl('/__last')); }); $test->it('一覧はキャッシュし、変更したらキャッシュを消すはず', function($test) use ($fresh, $respond, $ctl): void { $op = $fresh(); $respond(['results' => [['name' => '076.moe']], 'total' => 1]); $test->assertTrue($op->listDnsZones()->isSuccess); $last = $ctl('/__last'); $cached = $op->listDnsZones(); $test->assertEquals([['name' => '076.moe']], $cached->data['data']['results'], 'キャッシュから同じ形で返す'); $test->assertFalse(isset($cached->data['query']) || isset($cached->data['lifespan'])); $test->assertEquals($last, $ctl('/__last'), 'キャッシュがあればリクエストしない'); $respond(['success' => true]); $test->assertTrue($op->createDnsZone(['domain' => ['name' => '076', 'extension' => 'jp']])->isSuccess); $respond(['results' => [], 'total' => 2]); $test->assertEquals(2, $op->listDnsZones()->data['data']['total'], '追加した後は取り直す'); // 違うクエリーは取り直す $respond(['results' => [], 'total' => 3]); $test->assertEquals(3, $op->listDnsZones(['limit' => 1])->data['data']['total']); }); $srv?->stop(); // テスト毎のキャッシュを削除する foreach (glob(ROOT.'/data/cache/'.$cacheName.'_*') ?: [] as $dir) { foreach (glob("{$dir}/*") ?: [] as $f) @unlink($f); @rmdir($dir); } }); // テスト用のキャッシュを削除する $dir = ROOT.'/data/cache/'.$cacheName; foreach (glob("{$dir}/*") ?: [] as $f) @unlink($f); @rmdir($dir); $test->printSummary();