Merge branch 'dev' into 'master'

Implemented "ely/mojang-api"

See merge request elyby/accounts!6
This commit is contained in:
ErickSkrauch 2019-05-09 22:34:22 +00:00
commit d58c245ecf
10 changed files with 110 additions and 255 deletions

View File

@ -1,56 +0,0 @@
<?php
namespace common\components\Mojang;
use common\components\Mojang\exceptions\MojangApiException;
use common\components\Mojang\exceptions\NoContentException;
use common\components\Mojang\response\UsernameToUUIDResponse;
use Yii;
class Api {
/**
* @param string $username
* @param int $atTime
*
* @return UsernameToUUIDResponse
* @throws MojangApiException
* @throws NoContentException|\GuzzleHttp\Exception\RequestException
* @url http://wiki.vg/Mojang_API#Username_-.3E_UUID_at_time
*/
public function usernameToUUID($username, $atTime = null) {
$query = [];
if ($atTime !== null) {
$query['atTime'] = $atTime;
}
$response = $this->getClient()->get($this->buildUsernameToUUIDRoute($username), $query);
if ($response->getStatusCode() === 204) {
throw new NoContentException('Username not found');
}
if ($response->getStatusCode() !== 200) {
throw new MojangApiException('Unexpected request result');
}
$data = json_decode($response->getBody(), true);
$responseObj = new UsernameToUUIDResponse();
$responseObj->id = $data['id'];
$responseObj->name = $data['name'];
$responseObj->legacy = isset($data['legacy']);
$responseObj->demo = isset($data['demo']);
return $responseObj;
}
/**
* @return \GuzzleHttp\Client
*/
protected function getClient() {
return Yii::$app->guzzle;
}
protected function buildUsernameToUUIDRoute($username) {
return 'https://api.mojang.com/users/profiles/minecraft/' . $username;
}
}

View File

@ -1,8 +0,0 @@
<?php
namespace common\components\Mojang\exceptions;
use Exception;
class MojangApiException extends Exception {
}

View File

@ -1,6 +0,0 @@
<?php
namespace common\components\Mojang\exceptions;
class NoContentException extends MojangApiException {
}

View File

@ -1,29 +0,0 @@
<?php
namespace common\components\Mojang\response;
/**
* http://wiki.vg/Mojang_API#Username_-.3E_UUID_at_time
*/
class UsernameToUUIDResponse {
/**
* @var string uuid пользователя без разделения на дефисы
*/
public $id;
/**
* @var string ник пользователя в настоящем времени
*/
public $name;
/**
* @var bool если имеет значение true, то значит аккаунт не мигрирован в Mojang аккаунт
*/
public $legacy = false;
/**
* @var bool будет иметь значение true, если аккаунт находится в демо-режиме (не приобретена лицензия)
*/
public $demo = false;
}

View File

@ -18,6 +18,7 @@ return [
'container' => [ 'container' => [
'definitions' => [ 'definitions' => [
GuzzleHttp\ClientInterface::class => GuzzleHttp\Client::class, GuzzleHttp\ClientInterface::class => GuzzleHttp\Client::class,
Ely\Mojang\Api::class => Ely\Mojang\Api::class,
], ],
], ],
'components' => [ 'components' => [

View File

@ -4,12 +4,12 @@ declare(strict_types=1);
namespace common\tasks; namespace common\tasks;
use api\exceptions\ThisShouldNotHappenException; use api\exceptions\ThisShouldNotHappenException;
use common\components\Mojang\Api as MojangApi;
use common\components\Mojang\exceptions\MojangApiException;
use common\components\Mojang\exceptions\NoContentException;
use common\models\Account; use common\models\Account;
use common\models\MojangUsername; use common\models\MojangUsername;
use GuzzleHttp\Exception\RequestException; use Ely\Mojang\Api as MojangApi;
use Ely\Mojang\Exception\MojangApiException;
use Ely\Mojang\Exception\NoContentException;
use GuzzleHttp\Exception\GuzzleException;
use Yii; use Yii;
use yii\queue\JobInterface; use yii\queue\JobInterface;
@ -31,14 +31,15 @@ class PullMojangUsername implements JobInterface {
*/ */
public function execute($queue) { public function execute($queue) {
Yii::$app->statsd->inc('queue.pullMojangUsername.attempt'); Yii::$app->statsd->inc('queue.pullMojangUsername.attempt');
$mojangApi = $this->createMojangApi(); /** @var MojangApi $mojangApi */
$mojangApi = Yii::$app->get(MojangApi::class);
try { try {
$response = $mojangApi->usernameToUUID($this->username); $response = $mojangApi->usernameToUUID($this->username);
Yii::$app->statsd->inc('queue.pullMojangUsername.found'); Yii::$app->statsd->inc('queue.pullMojangUsername.found');
} catch (NoContentException $e) { } catch (NoContentException $e) {
$response = false; $response = false;
Yii::$app->statsd->inc('queue.pullMojangUsername.not_found'); Yii::$app->statsd->inc('queue.pullMojangUsername.not_found');
} catch (RequestException | MojangApiException $e) { } catch (GuzzleException | MojangApiException $e) {
Yii::$app->statsd->inc('queue.pullMojangUsername.error'); Yii::$app->statsd->inc('queue.pullMojangUsername.error');
return; return;
} }
@ -52,10 +53,10 @@ class PullMojangUsername implements JobInterface {
} else { } else {
if ($mojangUsername === null) { if ($mojangUsername === null) {
$mojangUsername = new MojangUsername(); $mojangUsername = new MojangUsername();
$mojangUsername->username = $response->name; $mojangUsername->username = $response->getName();
$mojangUsername->uuid = $response->id; $mojangUsername->uuid = $response->getId();
} else { } else {
$mojangUsername->uuid = $response->id; $mojangUsername->uuid = $response->getId();
$mojangUsername->touch('last_pulled_at'); $mojangUsername->touch('last_pulled_at');
} }
@ -65,8 +66,4 @@ class PullMojangUsername implements JobInterface {
} }
} }
protected function createMojangApi(): MojangApi {
return new MojangApi();
}
} }

View File

@ -1,54 +0,0 @@
<?php
namespace common\tests\unit\components\Mojang;
use common\components\Mojang\Api;
use common\components\Mojang\response\UsernameToUUIDResponse;
use common\tests\unit\TestCase;
use GuzzleHttp\Client as GuzzleClient;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\Response;
use Yii;
class ApiTest extends TestCase {
/**
* @var MockHandler
*/
private $handler;
public function _before() {
parent::_before();
$this->handler = new MockHandler();
$handler = HandlerStack::create($this->handler);
Yii::$app->set('guzzle', new GuzzleClient([
'handler' => $handler,
]));
}
public function testUsernameToUUID() {
$this->handler->append(new Response(200, [], '{"id": "7125ba8b1c864508b92bb5c042ccfe2b","name": "KrisJelbring"}'));
$response = (new Api())->usernameToUUID('KrisJelbring');
$this->assertInstanceOf(UsernameToUUIDResponse::class, $response);
$this->assertSame('7125ba8b1c864508b92bb5c042ccfe2b', $response->id);
$this->assertSame('KrisJelbring', $response->name);
}
/**
* @expectedException \common\components\Mojang\exceptions\NoContentException
*/
public function testUsernameToUUIDNoContent() {
$this->handler->append(new Response(204));
(new Api())->usernameToUUID('some-non-exists-user');
}
/**
* @expectedException \GuzzleHttp\Exception\RequestException
*/
public function testUsernameToUUID404() {
$this->handler->append(new Response(404, [], '{"error":"Not Found","errorMessage":"The server has not found anything matching the request URI"}'));
(new Api())->usernameToUUID('#hashedNickname');
}
}

View File

@ -3,14 +3,17 @@ declare(strict_types=1);
namespace common\tests\unit\tasks; namespace common\tests\unit\tasks;
use common\components\Mojang\Api;
use common\components\Mojang\exceptions\NoContentException;
use common\components\Mojang\response\UsernameToUUIDResponse;
use common\models\Account; use common\models\Account;
use common\models\MojangUsername; use common\models\MojangUsername;
use common\tasks\PullMojangUsername; use common\tasks\PullMojangUsername;
use common\tests\fixtures\MojangUsernameFixture; use common\tests\fixtures\MojangUsernameFixture;
use common\tests\unit\TestCase; use common\tests\unit\TestCase;
use Ely\Mojang\Api as MojangApi;
use Ely\Mojang\Exception\NoContentException;
use Ely\Mojang\Response\ProfileInfo;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Response;
use Yii;
use yii\queue\Queue; use yii\queue\Queue;
/** /**
@ -18,12 +21,8 @@ use yii\queue\Queue;
*/ */
class PullMojangUsernameTest extends TestCase { class PullMojangUsernameTest extends TestCase {
private $expectedResponse; /** @var \PHPUnit\Framework\MockObject\Builder\InvocationMocker */
private $mockedMethod;
/**
* @var PullMojangUsername
*/
private $task;
public function _fixtures() { public function _fixtures() {
return [ return [
@ -34,33 +33,11 @@ class PullMojangUsernameTest extends TestCase {
public function _before() { public function _before() {
parent::_before(); parent::_before();
/** @var PullMojangUsername|\PHPUnit_Framework_MockObject_MockObject $task */ /** @var \PHPUnit\Framework\MockObject\MockObject|MojangApi $mockApi */
$task = $this->getMockBuilder(PullMojangUsername::class) $mockApi = $this->createMock(MojangApi::class);
->setMethods(['createMojangApi']) $this->mockedMethod = $mockApi->method('usernameToUUID');
->getMock();
/** @var Api|\PHPUnit_Framework_MockObject_MockObject $apiMock */ Yii::$app->set(MojangApi::class, $mockApi);
$apiMock = $this->getMockBuilder(Api::class)
->setMethods(['usernameToUUID'])
->getMock();
$apiMock
->expects($this->any())
->method('usernameToUUID')
->willReturnCallback(function() {
if ($this->expectedResponse === false) {
throw new NoContentException();
}
return $this->expectedResponse;
});
$task
->expects($this->any())
->method('createMojangApi')
->willReturn($apiMock);
$this->task = $task;
} }
public function testCreateFromAccount() { public function testCreateFromAccount() {
@ -71,15 +48,13 @@ class PullMojangUsernameTest extends TestCase {
} }
public function testExecuteUsernameExists() { public function testExecuteUsernameExists() {
$expectedResponse = new UsernameToUUIDResponse(); $this->mockedMethod->willReturn(new ProfileInfo('069a79f444e94726a5befca90e38aaf5', 'Notch'));
$expectedResponse->id = '069a79f444e94726a5befca90e38aaf5';
$expectedResponse->name = 'Notch';
$this->expectedResponse = $expectedResponse;
/** @var \common\models\MojangUsername $mojangUsernameFixture */ /** @var \common\models\MojangUsername $mojangUsernameFixture */
$mojangUsernameFixture = $this->tester->grabFixture('mojangUsernames', 'Notch'); $mojangUsernameFixture = $this->tester->grabFixture('mojangUsernames', 'Notch');
$this->task->username = 'Notch'; $task = new PullMojangUsername();
$this->task->execute(mock(Queue::class)); $task->username = 'Notch';
$task->execute(mock(Queue::class));
/** @var MojangUsername|null $mojangUsername */ /** @var MojangUsername|null $mojangUsername */
$mojangUsername = MojangUsername::findOne('Notch'); $mojangUsername = MojangUsername::findOne('Notch');
$this->assertInstanceOf(MojangUsername::class, $mojangUsername); $this->assertInstanceOf(MojangUsername::class, $mojangUsername);
@ -88,15 +63,13 @@ class PullMojangUsernameTest extends TestCase {
} }
public function testExecuteChangedUsernameExists() { public function testExecuteChangedUsernameExists() {
$expectedResponse = new UsernameToUUIDResponse(); $this->mockedMethod->willReturn(new ProfileInfo('069a79f444e94726a5befca90e38aaf5', 'Notch'));
$expectedResponse->id = '069a79f444e94726a5befca90e38aaf5';
$expectedResponse->name = 'Notch';
$this->expectedResponse = $expectedResponse;
/** @var MojangUsername $mojangUsernameFixture */ /** @var MojangUsername $mojangUsernameFixture */
$mojangUsernameFixture = $this->tester->grabFixture('mojangUsernames', 'Notch'); $mojangUsernameFixture = $this->tester->grabFixture('mojangUsernames', 'Notch');
$this->task->username = 'Notch'; $task = new PullMojangUsername();
$this->task->execute(mock(Queue::class)); $task->username = 'Notch';
$task->execute(mock(Queue::class));
/** @var MojangUsername|null $mojangUsername */ /** @var MojangUsername|null $mojangUsername */
$mojangUsername = MojangUsername::findOne('Notch'); $mojangUsername = MojangUsername::findOne('Notch');
$this->assertInstanceOf(MojangUsername::class, $mojangUsername); $this->assertInstanceOf(MojangUsername::class, $mojangUsername);
@ -105,40 +78,37 @@ class PullMojangUsernameTest extends TestCase {
} }
public function testExecuteChangedUsernameNotExists() { public function testExecuteChangedUsernameNotExists() {
$expectedResponse = new UsernameToUUIDResponse(); $this->mockedMethod->willReturn(new ProfileInfo('607153852b8c4909811f507ed8ee737f', 'Chest'));
$expectedResponse->id = '607153852b8c4909811f507ed8ee737f';
$expectedResponse->name = 'Chest';
$this->expectedResponse = $expectedResponse;
$this->task->username = 'Chest'; $task = new PullMojangUsername();
$this->task->execute(mock(Queue::class)); $task->username = 'Chest';
$task->execute(mock(Queue::class));
/** @var MojangUsername|null $mojangUsername */ /** @var MojangUsername|null $mojangUsername */
$mojangUsername = MojangUsername::findOne('Chest'); $mojangUsername = MojangUsername::findOne('Chest');
$this->assertInstanceOf(MojangUsername::class, $mojangUsername); $this->assertInstanceOf(MojangUsername::class, $mojangUsername);
} }
public function testExecuteRemoveIfExistsNoMore() { public function testExecuteRemoveIfExistsNoMore() {
$this->expectedResponse = false; $this->mockedMethod->willThrowException(new NoContentException(new Request('', ''), new Response()));
$username = $this->tester->grabFixture('mojangUsernames', 'not-exists')['username']; $username = $this->tester->grabFixture('mojangUsernames', 'not-exists')['username'];
$this->task->username = $username; $task = new PullMojangUsername();
$this->task->execute(mock(Queue::class)); $task->username = $username;
$task->execute(mock(Queue::class));
/** @var MojangUsername|null $mojangUsername */ /** @var MojangUsername|null $mojangUsername */
$mojangUsername = MojangUsername::findOne($username); $mojangUsername = MojangUsername::findOne($username);
$this->assertNull($mojangUsername); $this->assertNull($mojangUsername);
} }
public function testExecuteUuidUpdated() { public function testExecuteUuidUpdated() {
$expectedResponse = new UsernameToUUIDResponse(); $this->mockedMethod->willReturn(new ProfileInfo('f498513ce8c84773be26ecfc7ed5185d', 'jeb'));
$expectedResponse->id = 'f498513ce8c84773be26ecfc7ed5185d';
$expectedResponse->name = 'jeb';
$this->expectedResponse = $expectedResponse;
/** @var MojangUsername $mojangInfo */ /** @var MojangUsername $mojangInfo */
$mojangInfo = $this->tester->grabFixture('mojangUsernames', 'uuid-changed'); $mojangInfo = $this->tester->grabFixture('mojangUsernames', 'uuid-changed');
$username = $mojangInfo['username']; $username = $mojangInfo['username'];
$this->task->username = $username; $task = new PullMojangUsername();
$this->task->execute(mock(Queue::class)); $task->username = $username;
$task->execute(mock(Queue::class));
/** @var MojangUsername|null $mojangUsername */ /** @var MojangUsername|null $mojangUsername */
$mojangUsername = MojangUsername::findOne($username); $mojangUsername = MojangUsername::findOne($username);
$this->assertInstanceOf(MojangUsername::class, $mojangUsername); $this->assertInstanceOf(MojangUsername::class, $mojangUsername);

View File

@ -7,27 +7,28 @@
"require": { "require": {
"php": "^7.2", "php": "^7.2",
"ext-json": "*", "ext-json": "*",
"ext-libxml": "*",
"ext-mbstring": "*", "ext-mbstring": "*",
"ext-pdo": "*", "ext-pdo": "*",
"ext-libxml": "*",
"ext-simplexml": "*", "ext-simplexml": "*",
"yiisoft/yii2": "2.0.15.1", "bacon/bacon-qr-code": "^1.0",
"yiisoft/yii2-swiftmailer": "~2.1.0", "domnikl/statsd": "^2.6",
"ramsey/uuid": "^3.5", "ely/email-renderer": "dev-master#8aa2e71c5b3b8e4a726c3c090b2997030ba29f73",
"league/oauth2-server": "^4.1", "ely/mojang-api": "^0.2.0",
"yiisoft/yii2-redis": "~2.0.0",
"guzzlehttp/guzzle": "^6.0.0",
"ely/yii2-tempmail-validator": "^2.0", "ely/yii2-tempmail-validator": "^2.0",
"emarref/jwt": "~1.0.3", "emarref/jwt": "~1.0.3",
"ely/email-renderer": "dev-master#8aa2e71c5b3b8e4a726c3c090b2997030ba29f73",
"mito/yii2-sentry": "^1.0",
"spomky-labs/otphp": "^9.0.2",
"bacon/bacon-qr-code": "^1.0",
"paragonie/constant_time_encoding": "^2.0",
"webmozart/assert": "^1.2.0",
"goaop/framework": "^2.2.0", "goaop/framework": "^2.2.0",
"domnikl/statsd": "^2.6", "guzzlehttp/guzzle": "^6.0.0",
"yiisoft/yii2-queue": "~2.1.0" "league/oauth2-server": "^4.1",
"mito/yii2-sentry": "^1.0",
"paragonie/constant_time_encoding": "^2.0",
"ramsey/uuid": "^3.5",
"spomky-labs/otphp": "^9.0.2",
"webmozart/assert": "^1.2.0",
"yiisoft/yii2": "2.0.15.1",
"yiisoft/yii2-queue": "~2.1.0",
"yiisoft/yii2-redis": "~2.0.0",
"yiisoft/yii2-swiftmailer": "~2.1.0"
}, },
"require-dev": { "require-dev": {
"codeception/codeception": "^2.5.3", "codeception/codeception": "^2.5.3",

63
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "029519509cc9ceb2a06e09ba680257b2", "content-hash": "ae2318fe3bd54e8c670c5969ba5c3e82",
"packages": [ "packages": [
{ {
"name": "bacon/bacon-qr-code", "name": "bacon/bacon-qr-code",
@ -118,8 +118,7 @@
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/RobinHerbots/Inputmask/zipball/5e670ad62f50c738388d4dcec78d2888505ad77b", "url": "https://api.github.com/repos/RobinHerbots/Inputmask/zipball/5e670ad62f50c738388d4dcec78d2888505ad77b",
"reference": "5e670ad62f50c738388d4dcec78d2888505ad77b", "reference": "5e670ad62f50c738388d4dcec78d2888505ad77b"
"shasum": null
}, },
"require": { "require": {
"bower-asset/jquery": ">=1.7" "bower-asset/jquery": ">=1.7"
@ -140,8 +139,7 @@
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/jquery/jquery-dist/zipball/77d2a51d0520d2ee44173afdf4e40a9201f5964e", "url": "https://api.github.com/repos/jquery/jquery-dist/zipball/77d2a51d0520d2ee44173afdf4e40a9201f5964e",
"reference": "77d2a51d0520d2ee44173afdf4e40a9201f5964e", "reference": "77d2a51d0520d2ee44173afdf4e40a9201f5964e"
"shasum": null
}, },
"type": "bower-asset", "type": "bower-asset",
"license": [ "license": [
@ -159,8 +157,7 @@
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/bestiejs/punycode.js/zipball/38c8d3131a82567bfef18da09f7f4db68c84f8a3", "url": "https://api.github.com/repos/bestiejs/punycode.js/zipball/38c8d3131a82567bfef18da09f7f4db68c84f8a3",
"reference": "38c8d3131a82567bfef18da09f7f4db68c84f8a3", "reference": "38c8d3131a82567bfef18da09f7f4db68c84f8a3"
"shasum": null
}, },
"type": "bower-asset" "type": "bower-asset"
}, },
@ -175,8 +172,7 @@
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/getsentry/raven-js-bower/zipball/c8b3a6040be6928e2f57fa5eec4d7afc31750235", "url": "https://api.github.com/repos/getsentry/raven-js-bower/zipball/c8b3a6040be6928e2f57fa5eec4d7afc31750235",
"reference": "c8b3a6040be6928e2f57fa5eec4d7afc31750235", "reference": "c8b3a6040be6928e2f57fa5eec4d7afc31750235"
"shasum": null
}, },
"type": "bower-asset" "type": "bower-asset"
}, },
@ -191,8 +187,7 @@
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/yiisoft/jquery-pjax/zipball/aef7b953107264f00234902a3880eb50dafc48be", "url": "https://api.github.com/repos/yiisoft/jquery-pjax/zipball/aef7b953107264f00234902a3880eb50dafc48be",
"reference": "aef7b953107264f00234902a3880eb50dafc48be", "reference": "aef7b953107264f00234902a3880eb50dafc48be"
"shasum": null
}, },
"require": { "require": {
"bower-asset/jquery": ">=1.8" "bower-asset/jquery": ">=1.8"
@ -651,6 +646,50 @@
], ],
"time": "2017-10-04T17:17:10+00:00" "time": "2017-10-04T17:17:10+00:00"
}, },
{
"name": "ely/mojang-api",
"version": "0.2.0",
"source": {
"type": "git",
"url": "https://github.com/elyby/mojang-api.git",
"reference": "1bb4365e555ae6210ca6a7327d49948dbff857d9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/elyby/mojang-api/zipball/1bb4365e555ae6210ca6a7327d49948dbff857d9",
"reference": "1bb4365e555ae6210ca6a7327d49948dbff857d9",
"shasum": ""
},
"require": {
"ext-json": "*",
"ext-mbstring": "*",
"guzzlehttp/guzzle": "^6.0.0",
"php": ">=7.1.0",
"ramsey/uuid": "^3.0.0"
},
"require-dev": {
"ely/php-code-style": "^0.3.0",
"phpunit/phpunit": "^7.0.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Ely\\Mojang\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "ErickSkrauch",
"email": "erickskrauch@yandex.ru"
}
],
"description": "Library for access to Mojang API.",
"time": "2019-05-07T08:31:39+00:00"
},
{ {
"name": "ely/yii2-tempmail-validator", "name": "ely/yii2-tempmail-validator",
"version": "2.0.0", "version": "2.0.0",
@ -5697,9 +5736,9 @@
"platform": { "platform": {
"php": "^7.2", "php": "^7.2",
"ext-json": "*", "ext-json": "*",
"ext-libxml": "*",
"ext-mbstring": "*", "ext-mbstring": "*",
"ext-pdo": "*", "ext-pdo": "*",
"ext-libxml": "*",
"ext-simplexml": "*" "ext-simplexml": "*"
}, },
"platform-dev": [] "platform-dev": []