Commit 0363bd45 authored by 朱思嘉's avatar 朱思嘉

feat(application): application

parent d2faec26
......@@ -29,6 +29,8 @@ class Application implements ClientInterface
private const TOKEN_CACHE_KEY = "Auth.%s";
/** @var string jwtToken */
private $jwtToken = "";
......@@ -71,6 +73,10 @@ class Application implements ClientInterface
throw new RuntimeException("请完善配置信息中的App Secret");
}
if (empty($option->getCustomer())){
throw new RuntimeException("请完善配置信息中的 Customer");
}
$this->option = $option;
$base_url = $option->getBaseUrl();
......@@ -81,6 +87,7 @@ class Application implements ClientInterface
if ($logger) {
$this->logger = $logger;
$this->logger->info("Application initialized with customer information: " . $this->option->getCustomer());
}
$this->cache = new FilesystemAdapter();
......@@ -163,7 +170,7 @@ class Application implements ClientInterface
$this->jwtToken = $this->getAccessTokenFromCache();
} catch (CacheInvalidArgumentException $exception) {
// 缓存异常
throw new ApplicationException("auth cache error " . $exception->getMessage());
throw new ApplicationException("auth cache error " . $exception->getMessage(),$request, [], $exception, ['customer' => $this->option->getCustomer()]);
} catch (TransferException $exception) {
// 超时异常
if ($this->logger) {
......@@ -175,7 +182,7 @@ class Application implements ClientInterface
if ($this->logger) {
$this->logger->error("sdk error, cause by:" . $exception->getMessage());
}
throw new ApplicationException($exception->getMessage(), $request, [], $exception);
throw new ApplicationException($exception->getMessage(), $request, [], $exception ,['customer' => $this->option->getCustomer()]);
}
}
$authMiddleware = $this->option->getAuthorizationMiddleware();
......@@ -203,9 +210,9 @@ class Application implements ClientInterface
$this->logger->error("sdk error, bad client!");
}
if (!($request instanceof SdkRequest)) {
throw new ApplicationException($exception->getMessage(), null, [], $exception);
throw new ApplicationException($exception->getMessage(), null, [], $exception,['customer' => $this->option->getCustomer()]);
}
throw new ApplicationException($exception->getMessage(), $request, [], $exception);
throw new ApplicationException($exception->getMessage(), $request, [], $exception,['customer' => $this->option->getCustomer()]);
}
$this->lastRequestContext['expend'] = sprintf("%dms", microtime(true) * 1000 - $start * 1000);
......@@ -229,10 +236,10 @@ class Application implements ClientInterface
// context 是没有处理前的上下文
$context = $this->lastRequestContext;
if ($context['request']) {
if (isset($context['request'])) {
unset($context['request']);
}
if ($context['response']) {
if (isset($context['response'])) {
unset($context['response']);
}
......@@ -240,14 +247,14 @@ class Application implements ClientInterface
if ($this->logger) {
$this->logger->error("sdk error, Upstream service fail", $context);
}
throw new ApplicationException("SDK Upstream Failed", $request, []);
throw new ApplicationException("SDK Upstream Failed", $request, [], null, ['customer' => $this->option->getCustomer()]);
}
if ($response->getStatusCode() == 401) {
if ($this->logger) {
$this->logger->error("sdk error, auth fail", $context);
}
throw new ApplicationException("auth Failed", $request, []);
throw new ApplicationException("auth Failed", $request, [], null, ['customer' => $this->option->getCustomer()]);
}
try {
......@@ -268,7 +275,7 @@ class Application implements ClientInterface
if ($this->logger) {
$this->logger->error("sdk error, bad response content.", $this->lastRequestContext);
}
throw new ApplicationException("Content Format error.", $request, []);
throw new ApplicationException("Content Format error.", $request, [], null, ['customer' => $this->option->getCustomer()]);
}
$version = $response->getHeader('ETag');
......@@ -293,4 +300,13 @@ class Application implements ClientInterface
{
return $this->lastRequestContext;
}
/**
* 用于测试的方法,允许注入客户端对象
*
*/
public function setClient(Client $client): void
{
$this->client = $client;
}
}
......@@ -11,6 +11,12 @@ class ApplicationException extends ConnectException implements SdkExceptionInter
{
use QueryPath;
/**
* 额外信息
* @var array<string, mixed>
*/
private array $additionalData = [];
/**
* 应用异常
*
......@@ -18,15 +24,28 @@ class ApplicationException extends ConnectException implements SdkExceptionInter
* @param SdkRequest|null $request
* @param array<string, mixed> $handlerContext
* @param Throwable|null $previous
* @param array<string, mixed> $additionalData
*/
public function __construct(
string $message,
SdkRequest $request = null,
array $handlerContext = [],
?Throwable $previous = null
?Throwable $previous = null,
array $additionalData = []
) {
$this->endpoint = is_null($request) ? "" : $request->getEndpoint();
$this->action = is_null($request) ? "" : $request->getAction();
$this->additionalData = $additionalData;
parent::__construct($message, $request, $previous, $handlerContext);
}
/**
* 获取额外的数据
*
* @return array<string, mixed>
*/
public function getAdditionalData(): array
{
return $this->additionalData;
}
}
......@@ -29,6 +29,8 @@ abstract class Option
/** @var string App Key 应用标志 */
private $appId;
/** @var string App Secret 应用密钥 */
private $customer;
/** @var string SDK 调用者信息 */
private $appSecret;
/** @var string SDK 的 Stage 环境 */
private $stage = "development";
......@@ -77,13 +79,23 @@ abstract class Option
*/
public function setStage(string $stage): self
{
if(empty(static::ENDPOINT_HOSTS[$stage])){
if (empty(static::ENDPOINT_HOSTS[$stage])) {
throw new InvalidArgumentException("set an undefine stage");
}
$this->stage = $stage;
return $this;
}
/**
* @param string $caller
* @return Option
*/
public function setCustomer(string $customer): self
{
$this->customer = $customer;
return $this;
}
/**
* @param float $timeOut
* @return Option
......@@ -112,6 +124,14 @@ abstract class Option
return $this->appSecret;
}
/**
* @return string
*/
public function getCustomer(): string
{
return $this->customer;
}
/**
* @return string
*/
......
<?php
declare(strict_types=1);
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Exception\TransferException;
use Jiwei\EasyHttpSdk\Application;
use Jiwei\EasyHttpSdk\Exception\ApplicationException;
use Jiwei\EasyHttpSdk\Exception\GuiltyResultException;
use Jiwei\EasyHttpSdk\Exception\SdkException;
use Jiwei\EasyHttpSdk\Exception\TimeOutExcetpion;
use Jiwei\EasyHttpSdk\Exception\UnknowResultException;
use Jiwei\EasyHttpSdk\Http\SdkRequest;
use Jiwei\EasyHttpSdk\Option;
use Jiwei\EasyHttpSdk\Policy\DefaultErrorHandlingPolicy;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use GuzzleHttp\Client as GuzzleClient;
final class ApplicationTest extends TestCase
class ApplicationTest extends TestCase
{
private Option $option;
private LoggerInterface $logger;
private ClientInterface $client;
private RequestInterface $mockRequest;
protected function setUp(): void
{
$this->option = $this->getMockBuilder(Option::class)
->disableOriginalConstructor()
->getMock();
$this->logger = $this->createMock(LoggerInterface::class);
$this->client = $this->createMock(GuzzleClient::class);
$this->mockRequest = $this->createMock(RequestInterface::class);
}
/*
*
* when the option for the App ID is missing.
*
* */
public function testConstructorThrowsExceptionWhenOptionAppIdIsMissing() : void
{
$this->option->method('getAppId')->willReturn('');
$this->expectException(\RuntimeException::class);
$this->expectExceptionMessageMatches('/请完善配置信息中的App Key/');
new Application($this->option, $this->logger);
}
/*
*
* when the option for the App Secret is missing.
*
* */
public function testConstructorThrowsExceptionWhenOptionAppSecretIsMissing() : void
{
$this->option->method('getAppId')->willReturn('chipsuglpf123dd');
$this->option->method('getAppSecret')->willReturn('');
$this->expectException(\RuntimeException::class);
$this->expectExceptionMessageMatches('/请完善配置信息中的App Secret/');
new Application($this->option, $this->logger);
}
/*
*
* when the option for the Customer is missing.
*
* */
public function testConstructorThrowsExceptionWhenOptionCustomerIsMissing() : void
{
$this->option->method('getAppId')->willReturn('chipsuglpf123dd');
$this->option->method('getAppSecret')->willReturn('2cojfyk0vx1an4quie3wglr8h97spmmt');
$this->option->method('getCustomer')->willReturn('');
$this->expectException(\RuntimeException::class);
$this->expectExceptionMessageMatches('/请完善配置信息中的 Customer/');
new Application($this->option, $this->logger);
}
/*
*
* when the option for the Baseurl is missing.
*
* */
public function testConstructorThrowsExceptionWhenOptionBaseUrlIsMissing() : void
{
$this->option->method('getAppId')->willReturn('chipsuglpf123dd');
$this->option->method('getAppSecret')->willReturn('2cojfyk0vx1an4quie3wglr8h97spmmt');
$this->option->method('getCustomer')->willReturn('test');
$this->option->method('getBaseUrl')->willReturn('');
$this->expectException(\RuntimeException::class);
$this->expectExceptionMessageMatches('/请检查配置信息中的Base Url/');
new Application($this->option, $this->logger);
}
/*
*
* Analog timeout exception
*
* */
public function testSendRequestHandlesTimeOutExcetpion(): void
{
$this->setOption();
$this->client->expects($this->once())
->method('send')
->willThrowException(new TransferException());
$this->expectException(TimeOutExcetpion::class);
$this->expectExceptionMessage('time out.');
$application = new Application($this->option, $this->logger);
$application->setClient($this->client);
$request = $this->createMock(SdkRequest::class);
$application->sendRequest($request);
}
/*
*
* Analog application exception
*
* */
public function testSendRequestHandlesApplicationException(): void
{
$this->setOption();
$mockException = $this->createMock(GuzzleException::class);
$this->client->expects($this->once())
->method('send')
->willThrowException($mockException);
$this->expectException(ApplicationException::class);
$application = new Application($this->option, $this->logger);
$application->setClient($this->client);
$request = $this->createMock(SdkRequest::class);
$application->sendRequest($request);
}
/*
*
* Analog successful request
*
* */
public function testSendRequestHandlesSuccessfulRequest(): void
{
$this->setOption();
$successfulResponse = $this->createMock(ResponseInterface::class);
$this->client->expects($this->once())
->method('send')
->willReturn($successfulResponse);
$application = new Application($this->option, $this->logger);
$application->setClient($this->client);
$request = $this->createMock(SdkRequest::class);
$response = $application->sendRequest($request);
$this->assertInstanceOf(ResponseInterface::class, $response);
}
/*
*
* Simulate ApplicationException
*
* */
public function testResultDecodeHandlesApplicationException(): void
{
$this->setOption();
$response = $this->createMock(ResponseInterface::class);
$exceptionMessage = "SDK Upstream Failed";
// 设置模拟响应对象的行为,使其返回状态码 500
$response->expects($this->once())
->method('getStatusCode')
->willReturn(500);
// 断言应该抛出 ApplicationException 异常
$this->expectException(ApplicationException::class);
$this->expectExceptionMessage($exceptionMessage);
// 创建 Application 对象并调用 resultDecode 方法
$application = new Application($this->option, $this->logger);
$request = $this->createMock(SdkRequest::class);
$application->resultDecode($request, $response);
}
/*
*
* Simulate AuthException
*
* */
public function testResultDecodeHandlesApplicationAuthException(): void
{
$this->setOption();
$response = $this->createMock(ResponseInterface::class);
$request = $this->createMock(SdkRequest::class); // 创建 SdkRequest 对象
$exceptionMessage = "auth Failed";
// 设置模拟响应对象的行为,使其返回状态码 401
$response->expects($this->any())
->method('getStatusCode')
->willReturn(401);
// 断言应该抛出 ApplicationException 异常
$this->expectException(ApplicationException::class);
$this->expectExceptionMessage($exceptionMessage);
// 创建 Application 对象并调用 resultDecode 方法
$application = new Application($this->option, $this->logger);
$application->resultDecode($request, $response);
}
/*
*
* Simulate GuiltyResultException
*
* */
public function testResultDecodeHandlesGuiltyResultException(): void
{
$this->setOption();
// 设置请求和响应对象的模拟
$request = $this->createMock(SdkRequest::class);
$response = $this->createMock(ResponseInterface::class);
$handlingPolicy = $this->createMock(DefaultErrorHandlingPolicy::class);
$this->option->expects($this->any())
->method('handlingPolicy')
->willReturn($handlingPolicy);
$handlingPolicy->expects($this->any())
->method('process')
->willReturnCallback(function () use ($response) {
throw new GuiltyResultException('Guilty Result');
});
// 创建 Application 对象并调用 resultDecode 方法
$application = new Application($this->option, $this->logger);
$this->expectException(SdkException::class);
$application->resultDecode($request, $response);
}
/*
*
* Simulate UnknowResultException
*
* */
public function testResultDecodeHandlesUnknowResultException(): void
{
$this->setOption();
// 设置请求和响应对象的模拟
$request = $this->createMock(SdkRequest::class);
$response = $this->createMock(ResponseInterface::class);
$handlingPolicy = $this->createMock(DefaultErrorHandlingPolicy::class);
$this->option->expects($this->any())
->method('handlingPolicy')
->willReturn($handlingPolicy);
$handlingPolicy->expects($this->any())
->method('process')
->willReturnCallback(function () use ($response) {
throw new UnknowResultException('Unknow Result');
});
// 创建 Application 对象并调用 resultDecode 方法
$application = new Application($this->option, $this->logger);
$this->expectException(ApplicationException::class);
$application->resultDecode($request, $response);
}
/*
*
* Simulate ResultDecodeHandlesSuccessful
*
* */
public function testResultDecodeHandlesSuccessfulResponse(): void
{
$this->setOption();
// 设置请求和响应对象的模拟
$request = $this->createMock(SdkRequest::class);
$response = $this->createMock(ResponseInterface::class);
// 模拟处理结果
$handlingResult = ['code' => 'success']; // 请根据实际情况设置处理结果
// 模拟 handlingPolicy 方法返回的对象
$handlingPolicy = $this->createMock(DefaultErrorHandlingPolicy::class);
$this->option->expects($this->once())
->method('handlingPolicy')
->willReturn($handlingPolicy);
// 模拟处理结果
$handlingPolicy->expects($this->once())
->method('process')
->willReturn($handlingResult);
// 设置日志记录器模拟
$application = new Application($this->option, $this->logger);
// 调用 resultDecode 方法
$result = $application->resultDecode($request, $response);
// 断言结果正确
$this->assertEquals($handlingResult, $result);
}
/*
*
* option Base
*
* */
public function setOption(): void
{
$this->option->method('getAppId')->willReturn('chipsuglpf123dd');
$this->option->method('getAppSecret')->willReturn('2cojfyk0vx1an4quie3wglr8h97spmmt');
$this->option->method('getCustomer')->willReturn('test');
$this->option->method('getBaseUrl')->willReturn('https://jiweidev.jiweinet.com');
}
}
<?php
declare(strict_types=1);
use Jiwei\EasyHttpSdk\Option;
use Jiwei\EasyHttpSdk\Middleware\Auth\JwtMiddleware;
use Jiwei\EasyHttpSdk\Policy\DefaultErrorHandlingPolicy;
use Jiwei\EasyHttpSdk\Policy\HandlingPolicyInterface;
use PHPUnit\Framework\TestCase;
final class OptionTest extends TestCase
class OptionTest extends TestCase
{
private Option $option;
protected mixed $option = null;
/**
* 测试Option实现实例的初始化(以匿名函数形式)
*
* @return void
*/
public function setUp(): void
{
parent::setUp(); // TODO: Change the autogenerated stub
$this->option = new class () extends Option {
/** @var string API鉴权地址 */
private const AUTH_API_ROUTE = "/api/access/token";
......@@ -39,41 +39,139 @@ final class OptionTest extends TestCase
};
}
/**
*
* valid AppId
*
*/
public function testSetAppId(): void
{
$appId = 'test_app_id';
$result = $this->option->setAppId($appId);
$this->assertSame($appId, $this->option->getAppId());
$this->assertInstanceOf(Option::class, $result);
}
/**
*
* valid AppSecret
*
*/
public function testSetAppSecret(): void
{
$appSecret = 'test_app_secret';
$result = $this->option->setAppSecret($appSecret);
$this->assertSame($appSecret, $this->option->getAppSecret());
$this->assertInstanceOf(Option::class, $result);
}
/**
*
* valid Stage
*
*/
public function testSetStageWithValidStage(): void
{
$stage = 'development';
$result = $this->option->setStage($stage);
$this->assertSame($stage, $this->option->getStage());
$this->assertInstanceOf(Option::class, $result);
}
/**
*
* invalid Stage
*
*/
public function testSetStageWithInvalidStage(): void
{
$this->expectException(\InvalidArgumentException::class);
$invalidStage = 'invalid_stage';
$this->option->setStage($invalidStage);
}
/**
* 测试
*
* @return void
* Error handling policy validation
*
*/
public function testOptionDefaultDebug()
public function testHandlingPolicy(): void
{
$checkDebug = $this->option->isDebug();
$this->assertEquals($checkDebug, false, "默认的 debug 级别应该是 false ");
$policy = $this->option->handlingPolicy();
$this->assertInstanceOf(HandlingPolicyInterface::class, $policy);
$this->assertInstanceOf(DefaultErrorHandlingPolicy::class, $policy);
}
/**
* Undocumented function
*
* @return void
* Verify the authentication time
*
*/
public function testOptionSetUndefineStage()
public function testGetAuthExpires(): void
{
$defaultStage = $this->option->getStage();
$this->assertEquals($defaultStage, "development", "默认的 stage 应该是 development ");
$this->expectException(InvalidArgumentException::class);
$this->option->setStage("WoooHaa");
$this->assertSame(2400, $this->option->getAuthExpires());
}
/**
* Undocumented function
*
* @return void
* Verify the timeout time
*
*/
public function testGetTimeOut(): void
{
$this->assertSame(3.0, $this->option->getTimeOut());
}
/**
*
* Verify the url time
*
*/
public function testGetBaseUrlWithDifferentStages(): void
{
$this->assertSame('127.0.0.1', $this->option->setStage('local')->getBaseUrl());
$this->assertSame('dev.test.com', $this->option->setStage('development')->getBaseUrl());
$this->assertSame('pro.test.com', $this->option->setStage('production')->getBaseUrl());
}
/**
*
* Verify Debug
*
*/
public function testIsDebug(): void
{
$this->assertFalse($this->option->setDebug(false)->isDebug());
$this->assertTrue($this->option->setDebug(true)->isDebug());
}
/**
*
* Verify Customer
*
*/
public function testGetCustomerAndSetCustomer(): void
{
$this->assertSame('test_customer', $this->option->setCustomer('test_customer')->getCustomer());
}
/**
*
* Verify Middleware
*
*/
public function testOptionBaseUrl()
public function testGetAuthorizationMiddlewareWithoutAuthMiddleware(): void
{
$defaultUrl = $this->option->getBaseUrl();
$this->assertEquals($defaultUrl, "dev.test.com", "默认的 stage 应该是 development,所以默认的 url 是 devtest");
$this->option->setStage("production");
$proUrl = $this->option->getBaseUrl();
$this->assertEquals($proUrl, "pro.test.com", "设置 Pro stage 应该可以正常显示");
$this->assertSame(JwtMiddleware::class, $this->option->getAuthorizationMiddleware());
}
}
<?php
use Jiwei\EasyHttpSdk\Option;
use PHPUnit\Framework\TestCase;
final class OptionTest extends TestCase
{
protected mixed $option = null;
/**
* 测试Option实现实例的初始化(以匿名函数形式)
*
* @return void
*/
public function setUp(): void
{
$this->option = new class () extends Option {
/** @var string API鉴权地址 */
private const AUTH_API_ROUTE = "/api/access/token";
/** @var int API鉴权过期时间 */
const AUTH_CACHE_EXPIRES_AT = 2400;
/** @var array<string, string> API HOST */
const ENDPOINT_HOSTS = [
"local" => "127.0.0.1",
"development" => "dev.test.com",
"production" => "pro.test.com",
];
public function authorization(): Closure
{
return function () {
echo sprintf("根据Auth API[%s]获取了TOKEN", self::AUTH_API_ROUTE);
return "i am a token";
};
}
};
}
/**
* 测试
*
* @return void
*/
public function testOptionDefaultDebug()
{
$checkDebug = $this->option->isDebug();
$this->assertEquals($checkDebug, false, "默认的 debug 级别应该是 false ");
}
/**
* Undocumented function
*
* @return void
*/
public function testOptionSetUndefineStage()
{
$defaultStage = $this->option->getStage();
$this->assertEquals($defaultStage, "development", "默认的 stage 应该是 development ");
$this->expectException(InvalidArgumentException::class);
$this->option->setStage("WoooHaa");
}
/**
* Undocumented function
*
* @return void
*/
public function testOptionBaseUrl()
{
$defaultUrl = $this->option->getBaseUrl();
$this->assertEquals($defaultUrl, "dev.test.com", "默认的 stage 应该是 development,所以默认的 url 是 devtest");
$this->option->setStage("production");
$proUrl = $this->option->getBaseUrl();
$this->assertEquals($proUrl, "pro.test.com", "设置 Pro stage 应该可以正常显示");
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment