| 1 | <?php |
| 2 | /************************************************************* |
| 3 | # 076 License |
| 4 | |
| 5 | Copyright (c) テクニカル諏訪子 |
| 6 | |
| 7 | Permission is hereby granted to any person obtaining a copy of the software |
| 8 | Little Beast (the "Software") to use, modify, merge, copy, publish, distribute, |
| 9 | sublicense, and/or sell copies of the Software, subject to the following conditions: |
| 10 | |
| 11 | 1. **Origin Attribution**: |
| 12 | - You must not misrepresent the origin of the Software; you must not claim |
| 13 | you created the original Software. |
| 14 | - If the Software is used in a product, you must either: |
| 15 | a. Provide clear attribution in the product's documentation, user interface, |
| 16 | or other visible areas, **OR** |
| 17 | b. Pay the original developers a fee they specify in writing. |
| 18 | 2. **Usage Restriction**: |
| 19 | - The Software, or any derivative works, dependencies, or libraries |
| 20 | incorporating it, must not be used for censorship or to suppress freedom of |
| 21 | speech, expression, or creativity. Prohibited uses include, but are not |
| 22 | limited to: |
| 23 | - Censorship of so-called "hate speech", visuals, non-mainstream opinions, |
| 24 | ideas, or objective reality. |
| 25 | - Tools or systems designed to restrict access to information or |
| 26 | artistic works. |
| 27 | 3. **Notice Preservation**: |
| 28 | - This license and the above copyright notice must remain intact in all copies |
| 29 | of the source code. |
| 30 | |
| 31 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, |
| 32 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A |
| 33 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT |
| 34 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF |
| 35 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE |
| 36 | OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
| 37 | */ |
| 38 | namespace Std\Test; |
| 39 | |
| 40 | require_once __DIR__.'/../../../autoload.php'; |
| 41 | |
| 42 | use Result; |
| 43 | use Std\Lib\Tester; |
| 44 | use Std\Lib\Curl; |
| 45 | use Std\Lib\Openprovider; |
| 46 | use Std\Test\Helper\MockServer; |
| 47 | |
| 48 | $test = new Tester([ |
| 49 | 'colorOutput' => true, |
| 50 | 'verboseOutput' => true |
| 51 | ]); |
| 52 | |
| 53 | // 偽物のAPIサーバーに繋ぐOpenprovider(本物のキャッシュは使わない) |
| 54 | $cacheName = 'lbtest_op_'.bin2hex(random_bytes(4)); |
| 55 | $make = fn(string $baseUrl, ?string $cacheDir = null) => new class($baseUrl, $cacheDir ?? $cacheName) extends Openprovider { |
| 56 | public function __construct(string $baseUrl, string $cacheDir) { |
| 57 | $this->BASEURL = $baseUrl; |
| 58 | $this->cacheDir = $cacheDir; |
| 59 | parent::__construct(); |
| 60 | } |
| 61 | protected function setupCurl(string $uri, string $resType = 'GET', array $payload = []): Curl { |
| 62 | return parent::setupCurl($uri, $resType, $payload)->setBlockPrivateAddresses(false)->setTimeout(5); |
| 63 | } |
| 64 | }; |
| 65 | |
| 66 | $test->describe('Openprovider(偽物のAPIサーバー)', function($test) use ($make): void { |
| 67 | if (!OPENPROVIDER_ENABLED) { |
| 68 | $test->skipAll('OPENPROVIDER_ENABLEDが無効'); |
| 69 | } |
| 70 | $srv = OPENPROVIDER_ENABLED ? MockServer::start('http-server.php') : null; |
| 71 | if (OPENPROVIDER_ENABLED && $srv === null) { |
| 72 | $test->skipAll('偽物のサーバーを起動出来ない(proc_openが無効?)'); |
| 73 | } |
| 74 | $base = $srv ? $srv->url('/v1beta') : ''; |
| 75 | // 失敗するテストではassert()を止める(DEBUG_MODEだとAssertionErrorになる) |
| 76 | $quiet = function(callable $fn) { |
| 77 | $old = ini_get('zend.assertions'); |
| 78 | if ($old !== '-1') ini_set('zend.assertions', '0'); |
| 79 | // assert_not_null()等の「assert機能性が無効です」の警告も出さない |
| 80 | set_error_handler(fn(int $no, string $msg) => $no === E_USER_WARNING && str_contains($msg, 'assert')); |
| 81 | try { |
| 82 | return $fn(); |
| 83 | } finally { |
| 84 | restore_error_handler(); |
| 85 | if ($old !== '-1') ini_set('zend.assertions', $old); |
| 86 | } |
| 87 | }; |
| 88 | $count = function(string $path) use ($srv): int { |
| 89 | $c = (new Curl($srv->url('/count?path='.urlencode($path))))->setBlockPrivateAddresses(false); |
| 90 | $c->execute(); |
| 91 | return (int)(json_decode($c->getResponseBody(), true)['count'] ?? -1); |
| 92 | }; |
| 93 | |
| 94 | $test->it('ログインしてトークンを受け取れるはず', function($test) use ($make, $base): void { |
| 95 | $op = $make($base); |
| 96 | $res = $op->login(); |
| 97 | $test->assertTrue($res->isSuccess); |
| 98 | $test->assertEquals('tok123', $res->data['data']['token']); |
| 99 | $test->assertArrayHasKey('last_auth', $res->data); |
| 100 | }); |
| 101 | |
| 102 | $test->it('2回目はキャッシュのトークンを使うはず', function($test) use ($make, $base, $count): void { |
| 103 | $before = $count('/v1beta/auth/login'); |
| 104 | $res = $make($base)->login(); |
| 105 | $test->assertTrue($res->isSuccess); |
| 106 | $test->assertEquals($before, $count('/v1beta/auth/login')); |
| 107 | }); |
| 108 | |
| 109 | $test->it('ログインの失敗はエラーの説明を返すはず', function($test) use ($make, $srv, $quiet): void { |
| 110 | // 以前は配列をResult::Error()に渡してTypeErrorになった |
| 111 | $op = $make($srv->url('/bad/v1beta')); |
| 112 | $op->deleteCache('login'); |
| 113 | $res = $quiet(fn() => $op->login()); |
| 114 | $test->assertFalse($res->isSuccess); |
| 115 | $test->assertEquals('Authentication/Authorization Failed', $res->message); |
| 116 | }); |
| 117 | |
| 118 | $test->it('ドメインがあるかどうか確認出来るはず', function($test) use ($make, $base): void { |
| 119 | $op = $make($base); |
| 120 | $op->login(); |
| 121 | $res = $op->checkDomainAvailable(['076.moe', '076.xxx', '076.co.jp']); |
| 122 | $test->assertTrue($res->isSuccess); |
| 123 | $test->assertEquals([ |
| 124 | ['domain' => '076.moe', 'status' => 'active', 'reason' => 'In use'], |
| 125 | ['domain' => '076.xxx', 'status' => 'free'], |
| 126 | // 以前は「co.jp」の様なTLDで「不正なドメイン形」になった |
| 127 | ['domain' => '076.co.jp', 'status' => 'free'], |
| 128 | ], $res->data); |
| 129 | }); |
| 130 | |
| 131 | $test->it('不正なドメインを拒否するはず', function($test) use ($make, $base): void { |
| 132 | $op = $make($base); |
| 133 | foreach (['076', '.moe', '076.'] as $d) { |
| 134 | $res = $op->checkDomainAvailable([$d]); |
| 135 | $test->assertFalse($res->isSuccess, "「{$d}」が拒否されなかった"); |
| 136 | $test->assertStringContains('不正なドメイン形', $res->message); |
| 137 | } |
| 138 | }); |
| 139 | |
| 140 | $test->it('ドメイン一覧を受け取り、キャッシュするはず', function($test) use ($make, $base, $count): void { |
| 141 | $op = $make($base); |
| 142 | $op->login(); |
| 143 | $res = $op->listDomains(); |
| 144 | $test->assertTrue($res->isSuccess); |
| 145 | $test->assertEquals(1, $res->data[0]['id']); |
| 146 | |
| 147 | $before = $count('/v1beta/domains'); |
| 148 | $test->assertTrue($op->listDomains()->isSuccess); |
| 149 | $test->assertEquals($before, $count('/v1beta/domains')); |
| 150 | }); |
| 151 | |
| 152 | $test->it('トークンが無いとエラーになるはず', function($test) use ($make, $base, $quiet): void { |
| 153 | $op = $make($base); |
| 154 | $op->deleteCache('listdomains'); |
| 155 | // login()しない |
| 156 | $res = $quiet(fn() => $op->listDomains()); |
| 157 | $test->assertFalse($res->isSuccess); |
| 158 | }); |
| 159 | |
| 160 | $test->it('JSONではないエラーレスポンスでも落ちないはず', function($test) use ($make, $base, $quiet): void { |
| 161 | $op = $make($base.'/gateway?'); |
| 162 | $res = $quiet(fn() => $op->listTlds()); |
| 163 | $test->assertFalse($res->isSuccess); |
| 164 | }); |
| 165 | |
| 166 | $test->it('接続出来ない場合はエラーになるはず', function($test) use ($make, $quiet): void { |
| 167 | $probe = stream_socket_server('tcp://127.0.0.1:0'); |
| 168 | $port = (int)substr(strrchr(stream_socket_get_name($probe, false), ':'), 1); |
| 169 | fclose($probe); |
| 170 | $op = $make("http://127.0.0.1:{$port}/v1beta"); |
| 171 | $op->deleteCache('login'); |
| 172 | $res = $quiet(fn() => $op->login()); |
| 173 | $test->assertFalse($res->isSuccess); |
| 174 | $test->assertStringContains('CURLの実行に失敗', $res->message); |
| 175 | }); |
| 176 | |
| 177 | $srv?->stop(); |
| 178 | }); |
| 179 | |
| 180 | $test->describe('Openprovider(全部のAPI)', function($test) use ($make, $cacheName): void { |
| 181 | if (!OPENPROVIDER_ENABLED) { |
| 182 | $test->skipAll('OPENPROVIDER_ENABLEDが無効'); |
| 183 | } |
| 184 | $srv = OPENPROVIDER_ENABLED ? MockServer::start('http-server.php') : null; |
| 185 | if (OPENPROVIDER_ENABLED && $srv === null) { |
| 186 | $test->skipAll('偽物のサーバーを起動出来ない(proc_openが無効?)'); |
| 187 | } |
| 188 | $base = $srv ? $srv->url('/v1beta') : ''; |
| 189 | $ctl = function(string $path, ?array $body = null) use ($srv): array { |
| 190 | $c = (new Curl($srv->url($path)))->setBlockPrivateAddresses(false)->setTimeout(5); |
| 191 | if ($body !== null) $c->setMethod('POST')->setPostRaw(json_encode($body)); |
| 192 | $c->execute(); |
| 193 | return (array)json_decode($c->getResponseBody(), true); |
| 194 | }; |
| 195 | // 次のレスポンスを決める |
| 196 | $respond = fn(array $data, int $status = 200, int $code = 0, string $desc = '') => $ctl('/__next', ['status' => $status, 'body' => ['code' => $code, 'desc' => $desc, 'data' => $data]]); |
| 197 | // 配列のキーを並べ替える(JSONの順番に関係なく比べる為) |
| 198 | $sorted = function(mixed $v) use (&$sorted): mixed { |
| 199 | if (!is_array($v)) return $v; |
| 200 | if (!array_is_list($v)) ksort($v); |
| 201 | return array_map($sorted, $v); |
| 202 | }; |
| 203 | $quiet = function(callable $fn) { |
| 204 | $old = ini_get('zend.assertions'); |
| 205 | if ($old !== '-1') ini_set('zend.assertions', '0'); |
| 206 | set_error_handler(fn(int $no, string $msg) => $no === E_USER_WARNING && str_contains($msg, 'assert')); |
| 207 | try { |
| 208 | return $fn(); |
| 209 | } finally { |
| 210 | restore_error_handler(); |
| 211 | if ($old !== '-1') ini_set('zend.assertions', $old); |
| 212 | } |
| 213 | }; |
| 214 | // テスト毎にキャッシュの無いOpenprovider(キャッシュでリクエストが省略されない様に) |
| 215 | $n = 0; |
| 216 | $fresh = function() use ($make, $base, $cacheName, &$n) { |
| 217 | $op = $make($base, $cacheName.'_'.(++$n)); |
| 218 | $op->login(); |
| 219 | return $op; |
| 220 | }; |
| 221 | |
| 222 | // [メソッド, 呼び出し, HTTPメソッド, パス, クエリー, 本文(GET・DELETEはnull)] |
| 223 | $owner = (string)(OPENPROVIDER['owner_handle'] ?? ''); |
| 224 | $ns = [['name' => 'ns1.076.moe', 'ip' => '192.0.2.1']]; |
| 225 | $calls = [ |
| 226 | // ドメイン |
| 227 | ['createDomain', fn($op) => $op->createDomain('076studio.jp', ['owner_handle' => 'OW1', 'name_servers' => ['ns1.076.moe'], 'is_private_whois_enabled' => true], 2), |
| 228 | 'POST', '/domains', '', ['domain' => ['name' => '076studio', 'extension' => 'jp'], 'period' => 2, 'owner_handle' => 'OW1', |
| 229 | 'admin_handle' => $owner, 'tech_handle' => $owner, 'billing_handle' => $owner, 'reseller_handle' => $owner, |
| 230 | 'name_servers' => [['name' => 'ns1.076.moe']], 'is_private_whois_enabled' => true, 'autorenew' => 'default']], |
| 231 | ['suggestDomainname', fn($op) => $op->suggestDomainname(['name' => '076', 'tlds' => ['moe']]), 'POST', '/domains/suggest-name', '', ['name' => '076', 'tlds' => ['moe']]], |
| 232 | ['tradeDomainname', fn($op) => $op->tradeDomainname(['domain' => ['name' => '076', 'extension' => 'moe'], 'owner_handle' => 'OW2']), |
| 233 | 'POST', '/domains/trade', '', ['domain' => ['name' => '076', 'extension' => 'moe'], 'owner_handle' => 'OW2']], |
| 234 | ['transferDomainname', fn($op) => $op->transferDomainname(['domain' => ['name' => '076', 'extension' => 'moe'], 'auth_code' => 'abc']), |
| 235 | 'POST', '/domains/transfer', '', ['domain' => ['name' => '076', 'extension' => 'moe'], 'auth_code' => 'abc']], |
| 236 | ['getDomainname', fn($op) => $op->getDomainname(5, ['with_history' => true]), 'GET', '/domains/5', 'with_history=true', null], |
| 237 | ['updateDomainname', fn($op) => $op->updateDomainname(5, ['autorenew' => 'on']), 'PUT', '/domains/5', '', ['autorenew' => 'on']], |
| 238 | ['deleteDomainname', fn($op) => $op->deleteDomainname(5, ['type' => 'transfer']), 'DELETE', '/domains/5', 'type=transfer', null], |
| 239 | ['retryLastDomainnameOperation', fn($op) => $op->retryLastDomainnameOperation(5), 'POST', '/domains/5/last-operation/restart', '', ['id' => 5]], |
| 240 | ['renewDomainname', fn($op) => $op->renewDomainname(5, ['period' => 1]), 'POST', '/domains/5/renew', '', ['id' => 5, 'period' => 1]], |
| 241 | ['restoreDomainname', fn($op) => $op->restoreDomainname(5), 'POST', '/domains/5/restore', '', ['id' => 5]], |
| 242 | ['approveDomainnameTransfer', fn($op) => $op->approveDomainnameTransfer(5, ['approve' => 1]), 'POST', '/domains/5/transfer/approve', '', ['id' => 5, 'approve' => 1]], |
| 243 | ['sendDomainnameFoa1', fn($op) => $op->sendDomainnameFoa1(5), 'POST', '/domains/5/transfer/send-foa1', '', ['id' => 5]], |
| 244 | ['getDomainAdditionalData', fn($op) => $op->getDomainAdditionalData(['domain.name' => '076', 'domain.extension' => 'jp']), |
| 245 | 'GET', '/domains/additional-data', 'domain.name=076&domain.extension=jp', null], |
| 246 | ['getDomainCustomerAdditionalData', fn($op) => $op->getDomainCustomerAdditionalData(['domain.name' => '076', 'domain.extension' => 'jp']), |
| 247 | 'GET', '/domains/additional-data/customers', 'domain.name=076&domain.extension=jp', null], |
| 248 | ['getDomainPrices', fn($op) => $op->getDomainPrices(['domain.name' => '076', 'domain.extension' => 'moe', 'operation' => 'create']), |
| 249 | 'GET', '/domains/prices', 'domain.name=076&domain.extension=moe&operation=create', null], |
| 250 | ['getAuthCode', fn($op) => $op->getAuthCode(5), 'GET', '/domains/5/authcode', '', null], |
| 251 | ['resetAuthCode', fn($op) => $op->resetAuthCode(5), 'POST', '/domains/5/authcode/reset', '', ['id' => 5]], |
| 252 | ['listTlds', fn($op) => $op->listTlds(), 'GET', '/tlds', 'limit=10&offset=0&order=ASC', null], |
| 253 | ['getTld', fn($op) => $op->getTld('moe', ['with_price' => true]), 'GET', '/tlds/moe', 'with_price=true', null], |
| 254 | // 請求 |
| 255 | ['listInvoices', fn($op) => $op->listInvoices(), 'GET', '/invoices', 'limit=10&offset=0', null], |
| 256 | ['listPayments', fn($op) => $op->listPayments(), 'GET', '/payments', 'limit=10&offset=0', null], |
| 257 | ['getSuggestedTopUpAmount', fn($op) => $op->getSuggestedTopUpAmount(), 'GET', '/payments/suggested-top-up-amount', '', null], |
| 258 | ['listTransactions', fn($op) => $op->listTransactions(['limit' => 3]), 'GET', '/transactions', 'limit=3', null], |
| 259 | // DNS |
| 260 | ['createDomainToken', fn($op) => $op->createDomainToken(['domain' => '076.moe', 'zone_provider' => 'sectigo']), |
| 261 | 'POST', '/dns/domain-token', '', ['domain' => '076.moe', 'zone_provider' => 'sectigo']], |
| 262 | ['listNameservers', fn($op) => $op->listNameservers(), 'GET', '/dns/nameservers', 'limit=25&offset=0&order_by=name&order=asc', null], |
| 263 | ['createNameserver', fn($op) => $op->createNameserver($ns[0]), 'POST', '/dns/nameservers', '', $ns[0]], |
| 264 | ['getNameserver', fn($op) => $op->getNameserver('ns1.076.moe'), 'GET', '/dns/nameservers/ns1.076.moe', '', null], |
| 265 | ['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']], |
| 266 | ['deleteNameserver', fn($op) => $op->deleteNameserver('ns1.076.moe'), 'DELETE', '/dns/nameservers/ns1.076.moe', '', null], |
| 267 | ['listNsGroups', fn($op) => $op->listNsGroups(), 'GET', '/dns/nameservers/groups', 'limit=25&offset=0&order_by.ns_group=asc', null], |
| 268 | ['createNsGroup', fn($op) => $op->createNsGroup(['ns_group' => 'grp', 'name_servers' => $ns]), 'POST', '/dns/nameservers/groups', '', ['ns_group' => 'grp', 'name_servers' => $ns]], |
| 269 | ['getNsGroup', fn($op) => $op->getNsGroup('my group'), 'GET', '/dns/nameservers/groups/my%20group', '', null], |
| 270 | ['updateNsGroup', fn($op) => $op->updateNsGroup('grp', ['name_servers' => $ns]), 'PUT', '/dns/nameservers/groups/grp', '', ['ns_group' => 'grp', 'name_servers' => $ns]], |
| 271 | ['deleteNsGroup', fn($op) => $op->deleteNsGroup('grp'), 'DELETE', '/dns/nameservers/groups/grp', '', null], |
| 272 | ['listDnsTemplate', fn($op) => $op->listDnsTemplate(['with_records' => true]), 'GET', '/dns/templates', 'with_records=true', null], |
| 273 | ['createDnsTemplate', fn($op) => $op->createDnsTemplate(['name' => 'web', 'records' => [['type' => 'A', 'value' => '192.0.2.1']]]), |
| 274 | 'POST', '/dns/templates', '', ['name' => 'web', 'records' => [['type' => 'A', 'value' => '192.0.2.1']]]], |
| 275 | ['getDnsTemplate', fn($op) => $op->getDnsTemplate(3), 'GET', '/dns/templates/3', '', null], |
| 276 | ['deleteDnsTemplate', fn($op) => $op->deleteDnsTemplate(3), 'DELETE', '/dns/templates/3', '', null], |
| 277 | ['listDnsZones', fn($op) => $op->listDnsZones(), 'GET', '/dns/zones', 'limit=25&offset=0&order_by.name=asc', null], |
| 278 | ['createDnsZone', fn($op) => $op->createDnsZone(['domain' => ['name' => '076', 'extension' => 'moe'], 'type' => 'master']), |
| 279 | 'POST', '/dns/zones', '', ['domain' => ['name' => '076', 'extension' => 'moe'], 'type' => 'master']], |
| 280 | ['getDnsZone', fn($op) => $op->getDnsZone('076.moe', ['with_records' => true]), 'GET', '/dns/zones/076.moe', 'with_records=true', null], |
| 281 | ['updateDnsZone', fn($op) => $op->updateDnsZone('076.moe', ['records' => ['add' => [['type' => 'A', 'name' => 'www', 'value' => '192.0.2.1', 'ttl' => 900]]]]), |
| 282 | 'PUT', '/dns/zones/076.moe', '', ['name' => '076.moe', 'records' => ['add' => [['type' => 'A', 'name' => 'www', 'value' => '192.0.2.1', 'ttl' => 900]]]]], |
| 283 | ['deleteDnsZone', fn($op) => $op->deleteDnsZone('076.moe', ['provider' => 'openprovider']), 'DELETE', '/dns/zones/076.moe', 'provider=openprovider', null], |
| 284 | ['listZoneRecords', fn($op) => $op->listZoneRecords('076.moe', ['type' => 'A']), 'GET', '/dns/zones/076.moe/records', 'type=A', null], |
| 285 | // EasyDMARC |
| 286 | ['getEasydmarc', fn($op) => $op->getEasydmarc('076.co.jp'), 'GET', '/easydmarcs', 'domain.name=076&domain.extension=co.jp', null], |
| 287 | ['createEasydmarc', fn($op) => $op->createEasydmarc('076.moe', 'OW1'), 'POST', '/easydmarcs', '', ['domain' => ['name' => '076', 'extension' => 'moe'], 'owner_handle' => 'OW1']], |
| 288 | ['listEasydmarcs', fn($op) => $op->listEasydmarcs(['domain' => '076.moe', 'status' => ['ACT', 'FAI']]), |
| 289 | 'GET', '/easydmarcs/list', 'status=ACT&status=FAI&domain.name=076&domain.extension=moe', null], |
| 290 | ['deleteEasydmarc', fn($op) => $op->deleteEasydmarc(7), 'DELETE', '/easydmarcs/7', '', null], |
| 291 | ['retryEasydmarc', fn($op) => $op->retryEasydmarc(7), 'POST', '/easydmarcs/7/retry', '', ['id' => 7]], |
| 292 | ['getEasydmarcSso', fn($op) => $op->getEasydmarcSso(7, 'dashboard'), 'GET', '/easydmarcs/7/sso', 'type=dashboard', null], |
| 293 | // メールテンプレート |
| 294 | ['listEmails', fn($op) => $op->listEmails(['group' => 'ive']), 'GET', '/emails', 'group=ive', null], |
| 295 | ['createEmail', fn($op) => $op->createEmail(['name' => 'mail', 'group' => 'ive']), 'POST', '/emails', '', ['name' => 'mail', 'group' => 'ive']], |
| 296 | ['updateEmail', fn($op) => $op->updateEmail(9, ['is_active' => false]), 'PUT', '/emails/9', '', ['id' => 9, 'is_active' => false]], |
| 297 | ['deleteEmail', fn($op) => $op->deleteEmail(9), 'DELETE', '/emails/9', '', null], |
| 298 | // ライセンス |
| 299 | ['listLicenses', fn($op) => $op->listLicenses(['product' => 'plesk']), 'GET', '/licenses', 'product=plesk', null], |
| 300 | ['resetLicenseHwid', fn($op) => $op->resetLicenseHwid('plesk', 11), 'POST', '/licenses/hwids/reset/plesk/11', '', ['key_id' => 11, 'product' => 'plesk']], |
| 301 | ['listLicenseItems', fn($op) => $op->listLicenseItems(['product' => 'plesk']), 'GET', '/licenses/items', 'product=plesk', null], |
| 302 | ['listPleskLicenses', fn($op) => $op->listPleskLicenses(), 'GET', '/licenses/plesk', '', null], |
| 303 | ['createPleskLicense', fn($op) => $op->createPleskLicense(['items' => ['PLSK-12-WEB-PRO'], 'period' => 1]), |
| 304 | 'POST', '/licenses/plesk', '', ['items' => ['PLSK-12-WEB-PRO'], 'period' => 1]], |
| 305 | ['getPleskKey', fn($op) => $op->getPleskKey(11), 'GET', '/licenses/plesk/key/11', '', null], |
| 306 | ['getPleskLicense', fn($op) => $op->getPleskLicense(11), 'GET', '/licenses/plesk/11', '', null], |
| 307 | ['updatePleskLicense', fn($op) => $op->updatePleskLicense(11, ['title' => 'web']), 'PUT', '/licenses/plesk/11', '', ['key_id' => 11, 'title' => 'web']], |
| 308 | ['deletePleskLicense', fn($op) => $op->deletePleskLicense(11), 'DELETE', '/licenses/plesk/11', '', null], |
| 309 | ['listVirtuozzoLicenses', fn($op) => $op->listVirtuozzoLicenses(['limit' => 5]), 'GET', '/licenses/virtuozzo', 'limit=5', null], |
| 310 | ['createVirtuozzoLicense', fn($op) => $op->createVirtuozzoLicense(['items' => ['VZ'], 'pcs_capacity' => 2]), |
| 311 | 'POST', '/licenses/virtuozzo', '', ['items' => ['VZ'], 'pcs_capacity' => 2]], |
| 312 | ['getVirtuozzoLicense', fn($op) => $op->getVirtuozzoLicense(12), 'GET', '/licenses/virtuozzo/12', '', null], |
| 313 | ['updateVirtuozzoLicense', fn($op) => $op->updateVirtuozzoLicense(12, ['suspended' => true]), 'PUT', '/licenses/virtuozzo/12', '', ['key_id' => 12, 'suspended' => true]], |
| 314 | ['deleteVirtuozzoLicense', fn($op) => $op->deleteVirtuozzoLicense(12), 'DELETE', '/licenses/virtuozzo/12', '', null], |
| 315 | ['getVirtuozzoKey', fn($op) => $op->getVirtuozzoKey(12), 'GET', '/licenses/virtuozzo/12/key', '', null], |
| 316 | // リセラー・顧客様 |
| 317 | ['listContacts', fn($op) => $op->listContacts(['role' => 'tech']), 'GET', '/contacts', 'role=tech', null], |
| 318 | ['createContact', fn($op) => $op->createContact(['username' => 'staff1', 'role' => 'tech']), 'POST', '/contacts', '', ['username' => 'staff1', 'role' => 'tech']], |
| 319 | ['getContact', fn($op) => $op->getContact(4, true), 'GET', '/contacts/4', 'with_additional_data=true', null], |
| 320 | ['updateContact', fn($op) => $op->updateContact(4, ['comments' => 'メモ']), 'PUT', '/contacts/4', '', ['id' => 4, 'comments' => 'メモ']], |
| 321 | ['deleteContact', fn($op) => $op->deleteContact(4), 'DELETE', '/contacts/4', '', null], |
| 322 | ['getContactNotificationSetting', fn($op) => $op->getContactNotificationSetting(), 'GET', '/contact/notification-setting', '', null], |
| 323 | ['updateContactNotificationSetting', fn($op) => $op->updateContactNotificationSetting(['marketing_promotion' => false]), |
| 324 | 'POST', '/contact/notification-setting', '', ['marketing_promotion' => false]], |
| 325 | ['listCustomers', fn($op) => $op->listCustomers(['handle_pattern' => 'AB*']), 'GET', '/customers', 'handle_pattern=AB%2A', null], |
| 326 | ['createCustomer', fn($op) => $op->createCustomer(['email' => 'a@076.moe']), 'POST', '/customers', '', ['email' => 'a@076.moe']], |
| 327 | ['getCustomer', fn($op) => $op->getCustomer('AB123-NL', true), 'GET', '/customers/AB123-NL', 'with_additional_data=true', null], |
| 328 | ['updateCustomer', fn($op) => $op->updateCustomer('AB123-NL', ['email' => 'b@076.moe']), 'PUT', '/customers/AB123-NL', '', ['handle' => 'AB123-NL', 'email' => 'b@076.moe']], |
| 329 | ['deleteCustomer', fn($op) => $op->deleteCustomer('AB123-NL'), 'DELETE', '/customers/AB123-NL', '', null], |
| 330 | ['listEmailVerifications', fn($op) => $op->listEmailVerifications(['email' => 'a@076.moe']), 'GET', '/customers/verifications/emails/domains', 'email=a%40076.moe', null], |
| 331 | ['restartEmailVerifications', fn($op) => $op->restartEmailVerifications('a@076.moe'), 'POST', '/customers/verifications/emails/restart', '', ['email' => 'a@076.moe']], |
| 332 | ['startEmailVerifications', fn($op) => $op->startEmailVerifications('a@076.moe', 'AB123-NL', 'ja'), |
| 333 | 'POST', '/customers/verifications/emails/start', '', ['email' => 'a@076.moe', 'handle' => 'AB123-NL', 'language' => 'ja']], |
| 334 | ['getReseller', fn($op) => $op->getReseller(['with_statistics' => true]), 'GET', '/resellers', 'with_statistics=true', null], |
| 335 | ['updateReseller', fn($op) => $op->updateReseller(42, ['comments' => 'x']), 'PUT', '/resellers/42', '', ['id' => 42, 'comments' => 'x']], |
| 336 | ['listSettings', fn($op) => $op->listSettings(), 'GET', '/resellers/settings', '', null], |
| 337 | ['listStatistics', fn($op) => $op->listStatistics(), 'GET', '/resellers/statistics', '', null], |
| 338 | ['listTags', fn($op) => $op->listTags(['key' => 'k']), 'GET', '/tags', 'key=k', null], |
| 339 | ['createTag', fn($op) => $op->createTag(['key' => 'k', 'value' => 'v']), 'POST', '/tags', '', ['key' => 'k', 'value' => 'v']], |
| 340 | ['deleteTag', fn($op) => $op->deleteTag(['key' => 'k', 'value' => 'v']), 'DELETE', '/tags', 'key=k&value=v', null], |
| 341 | // SpamExpert |
| 342 | ['generateLoginUrlSpamExpert', fn($op) => $op->generateLoginUrlSpamExpert(['domain_or_email' => '076.moe']), |
| 343 | 'POST', '/spam-expert/generate-login-url', '', ['domain_or_email' => '076.moe']], |
| 344 | ['createSeDomain', fn($op) => $op->createSeDomain(['domain_name' => '076.moe']), 'POST', '/spam-expert/domains', '', ['domain_name' => '076.moe']], |
| 345 | ['getSeDomain', fn($op) => $op->getSeDomain('076.moe', ['with_records' => true]), 'GET', '/spam-expert/domains/076.moe', 'with_records=true', null], |
| 346 | ['updateSeDomain', fn($op) => $op->updateSeDomain('076.moe', ['aliases' => ['076.jp']]), 'PUT', '/spam-expert/domains/076.moe', '', ['domain_name' => '076.moe', 'aliases' => ['076.jp']]], |
| 347 | ['deleteSeDomain', fn($op) => $op->deleteSeDomain('076.moe', ['bundle' => true]), 'DELETE', '/spam-expert/domains/076.moe', 'bundle=true', null], |
| 348 | // SSL証明書 |
| 349 | ['listApproverEmails', fn($op) => $op->listApproverEmails(['product_id' => 1, 'domain' => '076.moe']), 'GET', '/ssl/approver-emails', 'product_id=1&domain=076.moe', null], |
| 350 | ['createCsr', fn($op) => $op->createCsr(['common_name' => '076.moe', 'bits' => 2048]), 'POST', '/ssl/csr', '', ['common_name' => '076.moe', 'bits' => 2048]], |
| 351 | ['decodeCsr', fn($op) => $op->decodeCsr('-----BEGIN CERTIFICATE REQUEST-----'), 'POST', '/ssl/csr/decode', '', ['csr' => '-----BEGIN CERTIFICATE REQUEST-----']], |
| 352 | ['listSslOrders', fn($op) => $op->listSslOrders(['status' => ['ACT', 'PAI']]), 'GET', '/ssl/orders', 'status=ACT&status=PAI', null], |
| 353 | ['createSslOrder', fn($op) => $op->createSslOrder(['product_id' => 1, 'period' => 1]), 'POST', '/ssl/orders', '', ['product_id' => 1, 'period' => 1]], |
| 354 | ['getSslOrder', fn($op) => $op->getSslOrder(8), 'GET', '/ssl/orders/8', '', null], |
| 355 | ['updateSslOrder', fn($op) => $op->updateSslOrder(8, ['autorenew' => 'on']), 'PUT', '/ssl/orders/8', '', ['id' => 8, 'autorenew' => 'on']], |
| 356 | ['cancelSslOrder', fn($op) => $op->cancelSslOrder(8), 'POST', '/ssl/orders/8/cancel', '', ['id' => 8]], |
| 357 | ['reissueSslOrder', fn($op) => $op->reissueSslOrder(8, ['csr' => 'csr']), 'POST', '/ssl/orders/8/reissue', '', ['id' => 8, 'csr' => 'csr']], |
| 358 | ['renewSslOrder', fn($op) => $op->renewSslOrder(8), 'POST', '/ssl/orders/8/renew', '', ['id' => 8]], |
| 359 | ['updateSslApproverEmail', fn($op) => $op->updateSslApproverEmail(8, 'admin@076.moe'), 'PUT', '/ssl/orders/8/approver-email', '', ['id' => 8, 'approver_email' => 'admin@076.moe']], |
| 360 | ['resendSslApproverEmail', fn($op) => $op->resendSslApproverEmail(8), 'POST', '/ssl/orders/8/approver-email/resend', '', ['id' => 8]], |
| 361 | ['createSslOtpToken', fn($op) => $op->createSslOtpToken(8), 'POST', '/ssl/orders/8/otp-tokens', '', ['id' => 8]], |
| 362 | ['listSslProducts', fn($op) => $op->listSslProducts(['with_price' => true]), 'GET', '/ssl/products', 'with_price=true', null], |
| 363 | ['getSslProduct', fn($op) => $op->getSslProduct(1), 'GET', '/ssl/products/1', '', null], |
| 364 | ]; |
| 365 | |
| 366 | foreach ($calls as [$name, $call, $method, $path, $query, $body]) { |
| 367 | $test->it("{$name}()は{$method} {$path}を呼ぶはず", function($test) use ($fresh, $respond, $ctl, $sorted, $call, $method, $path, $query, $body): void { |
| 368 | $data = ['id' => 1, 'name' => 'テスト']; |
| 369 | $respond($data); |
| 370 | $res = $call($fresh()); |
| 371 | $test->assertTrue($res->isSuccess, (string)$res->message); |
| 372 | $test->assertSame($sorted(['code' => 0, 'desc' => '', 'data' => $data]), $sorted($res->data)); |
| 373 | $req = $ctl('/__last'); |
| 374 | $test->assertEquals($method, $req['method']); |
| 375 | $test->assertEquals("/v1beta{$path}", $req['path']); |
| 376 | $test->assertEquals($query, $req['query']); |
| 377 | if ($body === null) $test->assertEquals('', $req['body'], 'GET・DELETEに本文は無い'); |
| 378 | else $test->assertSame($sorted($body), $sorted(json_decode($req['body'], true))); |
| 379 | }); |
| 380 | } |
| 381 | |
| 382 | $test->it('一覧と確認はresultsだけを返すはず', function($test) use ($fresh, $respond, $ctl): void { |
| 383 | $respond(['results' => [['id' => 1]], 'total' => 1]); |
| 384 | $res = $fresh()->listDomains(['status' => 'ACT', 'limit' => 5]); |
| 385 | $test->assertEquals([['id' => 1]], $res->data); |
| 386 | $test->assertEquals('status=ACT&limit=5', $ctl('/__last')['query']); |
| 387 | |
| 388 | $respond(['results' => [['domain' => '076.moe', 'status' => 'free']]]); |
| 389 | $res = $fresh()->checkDomainAvailable(['076.moe'], true); |
| 390 | $test->assertEquals([['domain' => '076.moe', 'status' => 'free']], $res->data); |
| 391 | $test->assertEquals(['domains' => [['name' => '076', 'extension' => 'moe']], 'with_price' => true], json_decode($ctl('/__last')['body'], true)); |
| 392 | }); |
| 393 | |
| 394 | $test->it('ログインでリセラーIDを記録するはず', function($test) use ($fresh): void { |
| 395 | $test->assertEquals(42, $fresh()->getResellerId()); |
| 396 | }); |
| 397 | |
| 398 | $test->it('APIのエラー(codeが0以外、successがfalse、HTTPのエラー)は失敗のはず', function($test) use ($fresh, $respond, $quiet): void { |
| 399 | $respond([], 200, 316, 'Domain is not available'); |
| 400 | $res = $fresh()->getDomainname(5); |
| 401 | $test->assertFalse($res->isSuccess); |
| 402 | $test->assertStringContains('Domain is not available', $res->message); |
| 403 | |
| 404 | $respond(['success' => false]); |
| 405 | $test->assertFalse($fresh()->deleteDnsZone('076.moe')->isSuccess); |
| 406 | |
| 407 | $respond([], 400, 10001, 'Invalid request'); |
| 408 | $res = $quiet(fn() => $fresh()->createDnsZone(['type' => 'master'])); |
| 409 | $test->assertFalse($res->isSuccess); |
| 410 | $test->assertStringContains('Invalid request', $res->message); |
| 411 | }); |
| 412 | |
| 413 | $test->it('不正なドメインはリクエストを送らずに失敗するはず', function($test) use ($fresh, $ctl): void { |
| 414 | $op = $fresh(); |
| 415 | $before = $ctl('/__last'); |
| 416 | foreach (['getEasydmarc' => fn() => $op->getEasydmarc('076'), 'createEasydmarc' => fn() => $op->createEasydmarc('.moe'), |
| 417 | 'listEasydmarcs' => fn() => $op->listEasydmarcs(['domain' => '076.']), 'createDomain' => fn() => $op->createDomain('076', [])] as $name => $fn) { |
| 418 | $res = $fn(); |
| 419 | $test->assertFalse($res->isSuccess, $name); |
| 420 | $test->assertStringContains('不正なドメイン形', $res->message); |
| 421 | } |
| 422 | $test->assertEquals($before, $ctl('/__last')); |
| 423 | }); |
| 424 | |
| 425 | $test->it('一覧はキャッシュし、変更したらキャッシュを消すはず', function($test) use ($fresh, $respond, $ctl): void { |
| 426 | $op = $fresh(); |
| 427 | $respond(['results' => [['name' => '076.moe']], 'total' => 1]); |
| 428 | $test->assertTrue($op->listDnsZones()->isSuccess); |
| 429 | $last = $ctl('/__last'); |
| 430 | $cached = $op->listDnsZones(); |
| 431 | $test->assertEquals([['name' => '076.moe']], $cached->data['data']['results'], 'キャッシュから同じ形で返す'); |
| 432 | $test->assertFalse(isset($cached->data['query']) || isset($cached->data['lifespan'])); |
| 433 | $test->assertEquals($last, $ctl('/__last'), 'キャッシュがあればリクエストしない'); |
| 434 | |
| 435 | $respond(['success' => true]); |
| 436 | $test->assertTrue($op->createDnsZone(['domain' => ['name' => '076', 'extension' => 'jp']])->isSuccess); |
| 437 | $respond(['results' => [], 'total' => 2]); |
| 438 | $test->assertEquals(2, $op->listDnsZones()->data['data']['total'], '追加した後は取り直す'); |
| 439 | // 違うクエリーは取り直す |
| 440 | $respond(['results' => [], 'total' => 3]); |
| 441 | $test->assertEquals(3, $op->listDnsZones(['limit' => 1])->data['data']['total']); |
| 442 | }); |
| 443 | |
| 444 | $srv?->stop(); |
| 445 | // テスト毎のキャッシュを削除する |
| 446 | foreach (glob(ROOT.'/data/cache/'.$cacheName.'_*') ?: [] as $dir) { |
| 447 | foreach (glob("{$dir}/*") ?: [] as $f) @unlink($f); |
| 448 | @rmdir($dir); |
| 449 | } |
| 450 | }); |
| 451 | |
| 452 | // テスト用のキャッシュを削除する |
| 453 | $dir = ROOT.'/data/cache/'.$cacheName; |
| 454 | foreach (glob("{$dir}/*") ?: [] as $f) @unlink($f); |
| 455 | @rmdir($dir); |
| 456 | |
| 457 | $test->printSummary(); |