diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d3e7a60..f4a7e2d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -20,7 +20,7 @@ jobs: include: - laravel: 10.* testbench: 8.* - phpunit: ^10.0 + pest: ^2.23 steps: - name: Checkout code @@ -46,9 +46,9 @@ jobs: - name: Install dependencies - L${{ matrix.laravel }} run: | - composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" "phpunit/phpunit:${{ matrix.phpunit }}" --no-interaction --no-update - composer require --dev "jiannei/laravel-enum" --no-interaction --no-update + composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" "pestphp/pest:${{ matrix.pest }}" --no-interaction --no-update + composer require --dev "jiannei/laravel-enum:dev-main" --no-interaction --no-update composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction - name: Execute tests - run: vendor/bin/phpunit \ No newline at end of file + run: vendor/bin/pest \ No newline at end of file diff --git a/composer.json b/composer.json index 873a803..bcb0727 100644 --- a/composer.json +++ b/composer.json @@ -14,8 +14,8 @@ }, "require-dev": { "orchestra/testbench": "^8.0", - "jiannei/laravel-enum": "^3.0", - "phpunit/phpunit": "^10.0" + "pestphp/pest": "^2.23", + "jiannei/laravel-enum": "dev-main" }, "autoload": { "psr-4": { @@ -38,8 +38,13 @@ } }, "scripts": { - "test": "vendor/bin/phpunit" + "test": "vendor/bin/pest" }, "minimum-stability": "dev", - "prefer-stable" : true + "prefer-stable" : true, + "config": { + "allow-plugins": { + "pestphp/pest-plugin": true + } + } } diff --git a/config/response.php b/config/response.php index 5ac5189..ff466cc 100644 --- a/config/response.php +++ b/config/response.php @@ -24,15 +24,8 @@ 'error_code' => false, - // You can use enumerations to define the code when the response is returned, - // and set the response message according to the locale - // - // The following two enumeration packages are good choices - // - // https://github.com/Jiannei/laravel-enum - // https://github.com/BenSampo/laravel-enum - - 'enum' => '', // \Jiannei\Enum\Laravel\Repositories\Enums\HttpStatusCodeEnum::class + // lang/zh_CN/enums.php + 'locale' => 'enums',// enums.\Jiannei\Enum\Laravel\Support\Enums\HttpStatusCode::class // You can set some attributes (eg:code/message/header/options) for the exception, and it will override the default attributes of the exception 'exception' => [ diff --git a/src/Support/Facades/Response.php b/src/Support/Facades/Response.php index e24854b..0b62f41 100644 --- a/src/Support/Facades/Response.php +++ b/src/Support/Facades/Response.php @@ -18,16 +18,16 @@ * @method static JsonResponse accepted($data = null, string $message = '', string $location = '') * @method static JsonResponse created($data = null, string $message = '', string $location = '') * @method static JsonResponse noContent(string $message = '') - * @method static JsonResponse localize(int $code = 200, array $headers = [], int $option = 0) - * @method static JsonResponse ok(string $message = '', int $code = 200, array $headers = [], int $option = 0) - * @method static JsonResponse success($data = null, string $message = '', int $code = 200, array $headers = [], int $option = 0) + * @method static JsonResponse localize(int|\BackedEnum $code = 200, array $headers = [], int $option = 0) + * @method static JsonResponse ok(string $message = '', int|\BackedEnum $code = 200, array $headers = [], int $option = 0) + * @method static JsonResponse success($data = null, string $message = '', int|\BackedEnum $code = 200, array $headers = [], int $option = 0) * @method static void errorBadRequest(?string $message = '') * @method static void errorUnauthorized(string $message = '') * @method static void errorForbidden(string $message = '') * @method static void errorNotFound(string $message = '') * @method static void errorMethodNotAllowed(string $message = '') * @method static void errorInternal(string $message = '') - * @method static JsonResponse fail(string $message = '', int $code = 500, $errors = null, array $header = [], int $options = 0) + * @method static JsonResponse fail(string $message = '', int|\BackedEnum $code = 500, $errors = null, array $header = [], int $options = 0) * * @see \Jiannei\Response\Laravel\Response */ diff --git a/src/Support/Format.php b/src/Support/Format.php index 41aba74..e026d52 100644 --- a/src/Support/Format.php +++ b/src/Support/Format.php @@ -51,7 +51,7 @@ public function response( string $from = 'success' ): JsonResponse { return new JsonResponse( - $this->data($data, $message, $code, $errors), + $this->data($data, $message, $code, $errors, $from), $this->formatStatusCode($code, $from), $headers, $option @@ -67,7 +67,7 @@ public function response( * @param null $errors * @return array */ - public function data($data, ?string $message, int|\BackedEnum $code, $errors = null): array + public function data($data, ?string $message, int|\BackedEnum $code, $errors = null, $from = 'success'): array { $data = match (true) { $data instanceof ResourceCollection => $this->resourceCollection($data), @@ -78,7 +78,7 @@ public function data($data, ?string $message, int|\BackedEnum $code, $errors = n }; return $this->formatDataFields([ - 'status' => $this->formatStatus($code), + 'status' => $this->formatStatus($code,$from), 'code' => $this->formatBusinessCode($code), 'message' => $this->formatMessage($code, $message), 'data' => $data ?: (object) $data, @@ -134,10 +134,11 @@ public function jsonResource(JsonResource $resource): array */ protected function formatMessage(int|\BackedEnum $code, ?string $message): ?string { - $localizationKey = Config::get('response.localization', 'response'); + $localizationKey = join('.', [Config::get('response.locale', 'enums'), $this->formatBusinessCode($code)]); return match (true) { - !$message && Lang::has($localizationKey.$code) => Lang::get($localizationKey), + !$message && Lang::has($localizationKey) => Lang::get($localizationKey), + $code instanceof \BackedEnum && method_exists($code, 'description') => $code->description(), default => $message }; } @@ -150,7 +151,7 @@ protected function formatMessage(int|\BackedEnum $code, ?string $message): ?stri */ protected function formatBusinessCode(int|\BackedEnum $code): int { - return enum_exists($code) ? $code->value : $code; + return $code instanceof \BackedEnum ? $code->value : $code; } /** @@ -159,9 +160,9 @@ protected function formatBusinessCode(int|\BackedEnum $code): int * @param int|\BackedEnum $code * @return string */ - protected function formatStatus(int|\BackedEnum $code): string + protected function formatStatus(int|\BackedEnum $code, string $from = 'success'): string { - $statusCode = $this->formatStatusCode($code); + $statusCode = $this->formatStatusCode($code, $from); return match (true) { ($statusCode >= 400 && $statusCode <= 499) => 'error',// client error @@ -179,12 +180,9 @@ protected function formatStatus(int|\BackedEnum $code): string */ protected function formatStatusCode(int|\BackedEnum $code, string $from = 'success'): int { - $code = match (true) { - $from === 'fail' => (Config::get('response.error_code') ?: $code), - default => $this->formatBusinessCode($code) - }; + $code = $from === 'fail' ? (Config::get('response.error_code') ?: $code) : $code; - return (int) substr($code, 0, 3); + return (int) substr($this->formatBusinessCode($code), 0, 3); } /** diff --git a/src/Support/Traits/JsonResponseTrait.php b/src/Support/Traits/JsonResponseTrait.php index ba7a3fe..cc6372f 100644 --- a/src/Support/Traits/JsonResponseTrait.php +++ b/src/Support/Traits/JsonResponseTrait.php @@ -67,7 +67,7 @@ public function noContent(string $message = ''): JsonResponse * Alias of success method, no need to specify data parameter. * * @param string $message - * @param int $code + * @param int|\BackedEnum $code * @param array $headers * @param int $option * @return JsonResponse @@ -81,7 +81,7 @@ public function ok(string $message = '', int|\BackedEnum $code = 200, array $hea * Alias of the successful method, no need to specify the message and data parameters. * You can use ResponseCodeEnum to localize the message. * - * @param int $code + * @param int|\BackedEnum $code * @param array $headers * @param int $option * @return JsonResponse @@ -177,13 +177,13 @@ public function fail(string $message = '', int|\BackedEnum $code = 500, $errors * * @param mixed $data * @param string $message - * @param int $code + * @param int|\BackedEnum $code * @param array $headers * @param int $option * @return JsonResponse */ public function success($data = [], string $message = '', int|\BackedEnum $code = 200, array $headers = [], int $option = 0) { - return Format::response(...func_get_args()); + return Format::response($data, $message, $code, null, $headers, $option); } } diff --git a/tests/Enums/ResponseEnum.php b/tests/Enums/ResponseEnum.php new file mode 100644 index 0000000..1c47b1e --- /dev/null +++ b/tests/Enums/ResponseEnum.php @@ -0,0 +1,32 @@ + 200001,也就是有 001 ~ 999 个编号可以用来表示业务成功的情况,当然你可以根据实际需求继续增加位数,但必须要求是 200 开头 + // 举个栗子:你可以定义 001 ~ 099 表示系统状态;100 ~ 199 表示授权业务;200 ~ 299 表示用户业务... + case SERVICE_REGISTER_SUCCESS = 200101; + case SERVICE_LOGIN_SUCCESS = 200102; + + // 业务操作错误码(外部服务或内部服务调用...) + case SERVICE_REGISTER_ERROR = 500101; + case SERVICE_LOGIN_ERROR = 500102; + + // 客户端错误码:400 ~ 499 开头,后拼接 3 位 + case CLIENT_PARAMETER_ERROR = 400001; + case CLIENT_CREATED_ERROR = 400002; + case CLIENT_DELETED_ERROR = 400003; + + // 服务端操作错误码:500 ~ 599 开头,后拼接 3 位 + case SYSTEM_ERROR = 500001; + case SYSTEM_UNAVAILABLE = 500002; + case SYSTEM_CACHE_CONFIG_ERROR = 500003; + case SYSTEM_CACHE_MISSED_ERROR = 500004; + case SYSTEM_CONFIG_ERROR = 500005; +} \ No newline at end of file diff --git a/tests/FailTest.php b/tests/FailTest.php deleted file mode 100644 index 1dbb3c4..0000000 --- a/tests/FailTest.php +++ /dev/null @@ -1,151 +0,0 @@ - - * - * This source file is subject to the MIT license that is bundled - * with this source code in the file LICENSE. - */ - -namespace Jiannei\Response\Laravel\Tests; - -use Illuminate\Http\Exceptions\HttpResponseException; -use Illuminate\Support\Arr; -use Jiannei\Response\Laravel\Support\Facades\Response; -use Jiannei\Response\Laravel\Support\Traits\ExceptionTrait; -use Jiannei\Response\Laravel\Tests\Repositories\Enums\ResponseCodeEnum; -use Symfony\Component\HttpKernel\Exception\HttpException; -use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; -use Throwable; - -class FailTest extends TestCase -{ - use ExceptionTrait; - - public function testFail() - { - try { - // 方式一:Controller 中直接返回失败,这里本质上是通过 JsonResponse 是抛出了一个 HttpResponseException,需要捕获异常后才能拿到真实响应 - // 不需要在前面加 return - Response::fail(); - } catch (HttpResponseException $e) { - $response = $e->getResponse(); - - $this->assertEquals(500, $response->getStatusCode()); - - $expectedJson = json_encode([ - 'status' => 'fail', - 'code' => 500, - 'message' => ResponseCodeEnum::fromValue(500)->description, - // 这里应该是与 ResponseCodeEnum 中 500 状态码对应的描述,如果没有定义则取 Symfony\Component\HttpFoundation\Response - // 中标准的定义 - 'data' => (object) [], - 'error' => (object) [], - ]); - - $this->assertJsonStringEqualsJsonString($expectedJson, $response->getContent()); - } - } - - public function testFailWithMessage() - { - try { - // 方式二:Controller 中返回指定的 Message - Response::fail('操作失败'); - } catch (HttpResponseException $e) { - $response = $e->getResponse(); - - $this->assertEquals(500, $response->getStatusCode()); - - $expectedJson = json_encode([ - 'status' => 'fail', - 'code' => 500, - 'message' => '操作失败', - 'data' => (object) [], - 'error' => (object) [], - ]); - $this->assertJsonStringEqualsJsonString($expectedJson, $response->getContent()); - } - } - - public function testFailWithCustomCodeAndMessage() - { - try { - // 方式三:Controller 中返回预先定义的业务错误码和错误描述 - Response::fail('', ResponseCodeEnum::SERVICE_LOGIN_ERROR); - } catch (HttpResponseException $e) { - $response = $e->getResponse(); - - $this->assertEquals(500, $response->getStatusCode()); - - $expectedJson = json_encode([ - 'status' => 'fail', - 'code' => ResponseCodeEnum::SERVICE_LOGIN_ERROR, // 预期返回指定的业务错误码 - 'message' => ResponseCodeEnum::fromValue(ResponseCodeEnum::SERVICE_LOGIN_ERROR)->description, // 预期根据业务码取相应的错误描述 - 'data' => (object) [], - 'error' => (object) [], - ]); - $this->assertJsonStringEqualsJsonString($expectedJson, $response->getContent()); - } - } - - public function testFailOutController() - { - try { - // 方式四:Controller 中默认引入了 ResponseTrait;在没有引入 ResponseTrait 的地方可以直接使用 abort 来抛出 HttpException 异常然后返回错误信息 - abort(ResponseCodeEnum::SYSTEM_ERROR); - } catch (HttpException $httpException) { - $response = Response::fail( - '', - $this->isHttpException($httpException) ? $httpException->getStatusCode() : 500, - $this->convertExceptionToArray($httpException), - $this->isHttpException($httpException) ? $httpException->getHeaders() : [], - JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES - ); - - $expectedJson = json_encode([ - 'status' => 'fail', - 'code' => ResponseCodeEnum::SYSTEM_ERROR, - 'message' => ResponseCodeEnum::fromValue(ResponseCodeEnum::SYSTEM_ERROR)->description, - 'data' => (object) [], - 'error' => $this->convertExceptionToArray($httpException), - ], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); - - $this->assertJsonStringEqualsJsonString($expectedJson, $response->getContent()); - } - } - - /** - * Determine if the given exception is an HTTP exception. - * - * @param Throwable $e - * @return bool - */ - protected function isHttpException(Throwable $e) - { - return $e instanceof HttpExceptionInterface; - } - - /** - * Convert the given exception to an array. - * - * @param Throwable $e - * @return array - */ - protected function convertExceptionToArray(Throwable $e) - { - return config('app.debug', false) ? [ - 'message' => $e->getMessage(), - 'exception' => get_class($e), - 'file' => $e->getFile(), - 'line' => $e->getLine(), - 'trace' => collect($e->getTrace())->map(function ($trace) { - return Arr::except($trace, ['args']); - })->all(), - ] : [ - 'message' => $this->isHttpException($e) ? $e->getMessage() : 'Server Error', - ]; - } -} diff --git a/tests/Feature/ExampleTest.php b/tests/Feature/ExampleTest.php new file mode 100644 index 0000000..61cd84c --- /dev/null +++ b/tests/Feature/ExampleTest.php @@ -0,0 +1,5 @@ +toBeTrue(); +}); diff --git a/tests/FormatTest.php b/tests/FormatTest.php deleted file mode 100644 index 103e1e8..0000000 --- a/tests/FormatTest.php +++ /dev/null @@ -1,27 +0,0 @@ - - * - * This source file is subject to the MIT license that is bundled - * with this source code in the file LICENSE. - */ - -namespace Jiannei\Response\Laravel\Tests; - -use Jiannei\Response\Laravel\Support\Facades\Response; - -class FormatTest extends TestCase -{ - public function testAddExtraField() - { - $response = Response::success(); - - $this->assertEquals(200, $response->status()); - - $this->assertArrayHasKey('extra', $response->getData(true)); - $this->assertArrayHasKey('time', $response->getData(true)['extra']); - } -} diff --git a/tests/Pest.php b/tests/Pest.php new file mode 100644 index 0000000..cc3ee12 --- /dev/null +++ b/tests/Pest.php @@ -0,0 +1,45 @@ +in('Unit'); + +/* +|-------------------------------------------------------------------------- +| Expectations +|-------------------------------------------------------------------------- +| +| When you're writing tests, you often need to check that values meet certain conditions. The +| "expect()" function gives you access to a set of "expectations" methods that you can use +| to assert different things. Of course, you may extend the Expectation API at any time. +| +*/ + +expect()->extend('toBeOne', function () { + return $this->toBe(1); +}); + +/* +|-------------------------------------------------------------------------- +| Functions +|-------------------------------------------------------------------------- +| +| While Pest is very powerful out-of-the-box, you may have some testing code specific to your +| project that you don't want to repeat in every file. Here you can also expose helpers as +| global functions to help you to reduce the number of lines of code in your test files. +| +*/ + +function something() +{ + // .. +} diff --git a/tests/Repositories/Enums/ResponseCodeEnum.php b/tests/Repositories/Enums/ResponseCodeEnum.php deleted file mode 100644 index d7e0205..0000000 --- a/tests/Repositories/Enums/ResponseCodeEnum.php +++ /dev/null @@ -1,43 +0,0 @@ - - * - * This source file is subject to the MIT license that is bundled - * with this source code in the file LICENSE. - */ - -namespace Jiannei\Response\Laravel\Tests\Repositories\Enums; - -use Jiannei\Enum\Laravel\Repositories\Enums\HttpStatusCodeEnum; - -class ResponseCodeEnum extends HttpStatusCodeEnum -{ - // 定制/覆盖 HTTP 协议状态码 - const HTTP_OK = 200; - const HTTP_UNAUTHORIZED = 401; - - // 业务操作正确码:1xx、2xx、3xx 开头,后拼接 3 位 - // 200 + 001 => 200001,也就是有 001 ~ 999 个编号可以用来表示业务成功的情况,当然你可以根据实际需求继续增加位数,但必须要求是 200 开头 - // 举个栗子:你可以定义 001 ~ 099 表示系统状态;100 ~ 199 表示授权业务;200 ~ 299 表示用户业务... - const SERVICE_REGISTER_SUCCESS = 200101; - const SERVICE_LOGIN_SUCCESS = 200102; - - // 客户端错误码:400 ~ 499 开头,后拼接 3 位 - const CLIENT_PARAMETER_ERROR = 400001; - const CLIENT_CREATED_ERROR = 400002; - const CLIENT_DELETED_ERROR = 400003; - - // 服务端操作错误码:500 ~ 599 开头,后拼接 3 位 - const SYSTEM_ERROR = 500001; - const SYSTEM_UNAVAILABLE = 500002; - const SYSTEM_CACHE_CONFIG_ERROR = 500003; - const SYSTEM_CACHE_MISSED_ERROR = 500004; - const SYSTEM_CONFIG_ERROR = 500005; - - // 业务操作错误码(外部服务或内部服务调用...) - const SERVICE_REGISTER_ERROR = 500101; - const SERVICE_LOGIN_ERROR = 500102; -} diff --git a/tests/SuccessTest.php b/tests/SuccessTest.php deleted file mode 100644 index 12654ba..0000000 --- a/tests/SuccessTest.php +++ /dev/null @@ -1,342 +0,0 @@ - - * - * This source file is subject to the MIT license that is bundled - * with this source code in the file LICENSE. - */ - -namespace Jiannei\Response\Laravel\Tests; - -use Illuminate\Foundation\Testing\RefreshDatabase; -use Illuminate\Support\Arr; -use Jiannei\Response\Laravel\Support\Facades\Response; -use Jiannei\Response\Laravel\Tests\Repositories\Enums\ResponseCodeEnum; -use Jiannei\Response\Laravel\Tests\Repositories\Models\User; -use Jiannei\Response\Laravel\Tests\Repositories\Resources\UserCollection; -use Jiannei\Response\Laravel\Tests\Repositories\Resources\UserResource; - -class SuccessTest extends TestCase -{ - use RefreshDatabase; - - public function testSuccess() - { - // 方式一:直接返回响应成功 - $response = Response::success(); // 注意:这里必须使用 this->response() 函数方式调用,因为 MakesHttpRequests 中有 response 属性 - - $this->assertEquals(200, $response->status()); - - $expectedJson = json_encode([ - 'status' => 'success', - 'code' => 200, - 'message' => ResponseCodeEnum::fromValue(200)->description, - 'data' => (object) [], - 'error' => (object) [], - ]); - $this->assertJsonStringEqualsJsonString($expectedJson, $response->getContent()); - } - - public function testCreated() - { - // 方式二:返回创建成功 - $response = Response::created(); - - $this->assertEquals(201, $response->status()); - - $expectedJson = json_encode([ - 'status' => 'success', - 'code' => 201, - 'message' => ResponseCodeEnum::fromValue(201)->description, - 'data' => (object) [], - 'error' => (object) [], - ]); - $this->assertJsonStringEqualsJsonString($expectedJson, $response->getContent()); - } - - public function testAccepted() - { - // 方式三:返回接收成功 - $response = Response::accepted(); - - $this->assertEquals(202, $response->status()); - - $expectedJson = json_encode([ - 'status' => 'success', - 'code' => 202, - 'message' => ResponseCodeEnum::fromValue(202)->description, - 'data' => (object) [], - 'error' => (object) [], - ]); - $this->assertJsonStringEqualsJsonString($expectedJson, $response->getContent()); - } - - public function testNoContent() - { - // 方式四:返回空内容;创建成功或删除成功等场景 - $response = Response::noContent(); - - $this->assertEquals(204, $response->status()); - - $expectedJson = json_encode([ - 'status' => 'success', - 'code' => 204, - 'message' => ResponseCodeEnum::fromValue(204)->description, - 'data' => (object) [], - 'error' => (object) [], - ]); - - $this->assertJsonStringEqualsJsonString($expectedJson, $response->getContent()); - } - - public function testSuccessWithArrayData() - { - // 方式五:返回普通的数组 - $data = [ - 'name' => 'Jiannei', - 'email' => 'longjian.huang@foxmail.com', - ]; - $response = Response::success($data); - - $this->assertEquals(200, $response->status()); - - $expectedJson = json_encode([ - 'status' => 'success', - 'code' => 200, - 'message' => ResponseCodeEnum::fromValue(200)->description, - 'data' => $data, - 'error' => (object) [], - ]); - $this->assertJsonStringEqualsJsonString($expectedJson, $response->getContent()); - } - - public function testSuccessWithResourceData() - { - // 方式六:返回 Api resource - $user = User::factory()->make(); - $response = Response::success(new UserResource($user)); - - $this->assertEquals(200, $response->status()); - $expectedJson = json_encode([ - 'status' => 'success', - 'code' => 200, - 'message' => ResponseCodeEnum::fromValue(200)->description, - 'data' => [ - 'nickname' => $user->name, - 'email' => $user->email, - ], - 'error' => (object) [], - ]); - - $this->assertJsonStringEqualsJsonString($expectedJson, $response->getContent()); - } - - public function testSuccessWithCollectionData() - { - // 方式七:返回 Api collection - User::factory()->count(3)->create(); - $users = User::all(); - $response = Response::success(new UserCollection($users)); - - $this->assertEquals(200, $response->status()); - - $data = $users->map(function ($item) { - return [ - 'nickname' => $item->name, - 'email' => $item->email, - ]; - })->all(); - $expectedJson = json_encode([ - 'status' => 'success', - 'code' => 200, - 'message' => ResponseCodeEnum::fromValue(200)->description, - 'data' => ['data' => $data], - 'error' => (object) [], - ]); - $this->assertJsonStringEqualsJsonString($expectedJson, $response->getContent()); - } - - public function testSuccessWithPaginatedData() - { - // 方式八:返回分页的 Api collection - User::factory()->count(20)->create(); - $users = User::query()->paginate(); - - $response = Response::success(new UserCollection($users)); - - $this->assertEquals(200, $response->status()); - - $formatData = Arr::map($users->items(), fn ($item) => [ - 'nickname' => $item->name, - 'email' => $item->email, - ]); - - $data = [ - 'data' => $formatData, - 'meta' => [ - 'pagination' => [ - 'count' => $users->lastItem(), - 'per_page' => $users->perPage(), - 'current_page' => $users->currentPage(), - 'total' => $users->total(), - 'total_pages' => $users->lastPage(), - 'links' => array_filter([ - 'previous' => $users->previousPageUrl(), - 'next' => $users->nextPageUrl(), - ]), - ], - ], - ]; - $expectedJson = json_encode([ - 'status' => 'success', - 'code' => 200, - 'message' => ResponseCodeEnum::fromValue(200)->description, - 'data' => $data, - 'error' => (object) [], - ]); - - $this->assertJsonStringEqualsJsonString($expectedJson, $response->getContent()); - } - - public function testSuccessWithMessage() - { - // 方式九:返回指定的 Message - $response = Response::success([], '成功'); - - $expectedJson = json_encode([ - 'status' => 'success', - 'code' => 200, - 'message' => '成功', - 'data' => (object) [], - 'error' => (object) [], - ]); - - $this->assertJsonStringEqualsJsonString($expectedJson, $response->getContent()); - } - - public function testSuccessWithCustomMessageAndCode() - { - // 方式十:根据预定义的「业务码」和「对应的描述信息」返回 - $response = Response::success([], '', ResponseCodeEnum::SERVICE_LOGIN_SUCCESS); - - $expectedJson = json_encode([ - 'status' => 'success', - 'code' => ResponseCodeEnum::SERVICE_LOGIN_SUCCESS, // 返回自定义的业务码 - 'message' => ResponseCodeEnum::fromValue(ResponseCodeEnum::SERVICE_LOGIN_SUCCESS)->description, // 根据业务码取多语言的业务描述 - 'data' => (object) [], - 'error' => (object) [], - ]); - - $this->assertJsonStringEqualsJsonString($expectedJson, $response->getContent()); - } - - public function testLengthAwarePaginator() - { - User::factory()->count(20)->create(); - $users = User::query()->paginate(); - - $response = Response::success($users); - - $this->assertEquals(200, $response->status()); - - $formatData = Arr::map($users->items(), fn ($item) => $item->toArray()); - - $data = [ - 'data' => $formatData, - 'meta' => [ - 'pagination' => [ - 'count' => $users->lastItem(), - 'per_page' => $users->perPage(), - 'current_page' => $users->currentPage(), - 'total' => $users->total(), - 'total_pages' => $users->lastPage(), - 'links' => array_filter([ - 'previous' => $users->previousPageUrl(), - 'next' => $users->nextPageUrl(), - ]), - ], - ], - ]; - $expectedJson = json_encode([ - 'status' => 'success', - 'code' => 200, - 'message' => ResponseCodeEnum::fromValue(200)->description, - 'data' => $data, - 'error' => (object) [], - ]); - - $this->assertJsonStringEqualsJsonString($expectedJson, $response->getContent()); - } - - public function testSimplePaginator() - { - User::factory()->count(20)->create(); - $users = User::query()->simplePaginate(); - - $response = Response::success($users); - - $this->assertEquals(200, $response->status()); - - $formatData = Arr::map($users->items(), fn ($item) => $item->toArray()); - - $data = [ - 'data' => $formatData, - 'meta' => [ - 'pagination' => [ - 'count' => $users->lastItem(), - 'per_page' => $users->perPage(), - 'current_page' => $users->currentPage(), - 'links' => array_filter([ - 'previous' => $users->previousPageUrl(), - 'next' => $users->nextPageUrl(), - ]), - ], - ], - ]; - $expectedJson = json_encode([ - 'status' => 'success', - 'code' => 200, - 'message' => ResponseCodeEnum::fromValue(200)->description, - 'data' => $data, - 'error' => (object) [], - ]); - - $this->assertJsonStringEqualsJsonString($expectedJson, $response->getContent()); - } - - public function testCursorPaginator() - { - User::factory()->count(20)->create(); - $users = User::query()->cursorPaginate(); - - $response = Response::success($users); - - $this->assertEquals(200, $response->status()); - - $formatData = Arr::map($users->items(), fn ($item) => $item->toArray()); - - $data = [ - 'data' => $formatData, - 'meta' => [ - 'cursor' => [ - 'current' => $users->cursor()?->encode(), - 'prev' => $users->previousCursor()?->encode(), - 'next' => $users->nextCursor()?->encode(), - 'count' => count($users->items()), - ], - ], - ]; - $expectedJson = json_encode([ - 'status' => 'success', - 'code' => 200, - 'message' => ResponseCodeEnum::fromValue(200)->description, - 'data' => $data, - 'error' => (object) [], - ]); - - $this->assertJsonStringEqualsJsonString($expectedJson, $response->getContent()); - } -} diff --git a/tests/Support/Format.php b/tests/Support/Format.php index 6cc0897..5e2afcd 100644 --- a/tests/Support/Format.php +++ b/tests/Support/Format.php @@ -13,7 +13,7 @@ class Format extends \Jiannei\Response\Laravel\Support\Format { - public function data(mixed $data, ?string $message, int|\BackedEnum $code, $errors = null): array + public function data(mixed $data, ?string $message, int|\BackedEnum $code, $errors = null, $from = 'success'): array { return [ 'status' => $this->formatStatus($code), diff --git a/tests/TestCase.php b/tests/TestCase.php index 730dbde..2ec6713 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -11,6 +11,9 @@ namespace Jiannei\Response\Laravel\Tests; +use Illuminate\Contracts\Config\Repository; +use Jiannei\Response\Laravel\Tests\Enums\ResponseEnum; + abstract class TestCase extends \Orchestra\Testbench\TestCase { protected function getPackageProviders($app) @@ -34,16 +37,19 @@ protected function defineEnvironment($app) { $app['path.lang'] = __DIR__.'/lang'; - $app['config']->set('database.default', 'sqlite'); + tap($app['config'], function (Repository $config) { + $config->set('app.locale', 'zh_CN'); + $config->set('database.default', 'sqlite'); + $config->set('database.connections.sqlite', [ + 'driver' => 'sqlite', + 'database' => ':memory:', + 'prefix' => '', + ]); - $app['config']->set('database.connections.sqlite', [ - 'driver' => 'sqlite', - 'database' => ':memory:', - 'prefix' => '', - ]); + $config->set('response.locale', 'enums.'.ResponseEnum::class); + }); - $app['config']->set('response.enum', \Jiannei\Response\Laravel\Tests\Repositories\Enums\ResponseCodeEnum::class); - if ($this instanceof FormatTest) { + if ($this instanceof \P\Tests\Unit\FormatTest) { $app['config']->set('response.format', [ 'class' => \Jiannei\Response\Laravel\Tests\Support\Format::class, ]); diff --git a/tests/Unit/FailTest.php b/tests/Unit/FailTest.php new file mode 100644 index 0000000..ad874f6 --- /dev/null +++ b/tests/Unit/FailTest.php @@ -0,0 +1,129 @@ +getResponse(); + + expect($response->getStatusCode())->toEqual(500); + + $expectedJson = json_encode([ + 'status' => 'fail', + 'code' => 500, + 'message' => '', + 'data' => (object) [], + 'error' => (object) [], + ]); + + $this->assertJsonStringEqualsJsonString($expectedJson, $response->getContent()); + } +}); + +test('fail with message', function () { + try { + // 方式二:Controller 中返回指定的 Message + Response::fail('操作失败'); + } catch (HttpResponseException $e) { + $response = $e->getResponse(); + + expect($response->getStatusCode())->toEqual(500); + + $expectedJson = json_encode([ + 'status' => 'fail', + 'code' => 500, + 'message' => '操作失败', + 'data' => (object) [], + 'error' => (object) [], + ]); + $this->assertJsonStringEqualsJsonString($expectedJson, $response->getContent()); + } +}); + +test('fail with custom code and message', function () { + try { + // 方式三:Controller 中返回预先定义的业务错误码和错误描述 + Response::fail(code: ResponseEnum::SERVICE_LOGIN_ERROR); + } catch (HttpResponseException $e) { + $response = $e->getResponse(); + + expect($response->getStatusCode())->toEqual(500); + + $expectedJson = json_encode([ + 'status' => 'fail', + 'code' => ResponseEnum::SERVICE_LOGIN_ERROR->value, // 预期返回指定的业务错误码 + 'message' => ResponseEnum::fromValue(ResponseEnum::SERVICE_LOGIN_ERROR->value)->description(), // 预期根据业务码取相应的错误描述 + 'data' => (object) [], + 'error' => (object) [], + ]); + $this->assertJsonStringEqualsJsonString($expectedJson, $response->getContent()); + } +}); + +test('fail out controller', function () { + try { + // 方式四:Controller 中默认引入了 ResponseTrait;在没有引入 ResponseTrait 的地方可以直接使用 abort 来抛出 HttpException 异常然后返回错误信息 + abort(ResponseEnum::SYSTEM_ERROR->value); + } catch (HttpException $httpException) { + $response = Response::fail( + '', + isHttpException($httpException) ? $httpException->getStatusCode() : 500, + convertExceptionToArray($httpException), + isHttpException($httpException) ? $httpException->getHeaders() : [], + JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES + ); + + $expectedJson = json_encode([ + 'status' => 'fail', + 'code' => ResponseEnum::SYSTEM_ERROR, + 'message' => ResponseEnum::fromValue(ResponseEnum::SYSTEM_ERROR->value)->description(), + 'data' => (object) [], + 'error' => convertExceptionToArray($httpException), + ], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); + + $this->assertJsonStringEqualsJsonString($expectedJson, $response->getContent()); + } +}); + +/** + * Determine if the given exception is an HTTP exception. + * + * @param Throwable $e + * @return bool + */ +function isHttpException(Throwable $e) +{ + return $e instanceof HttpExceptionInterface; +} + +/** + * Convert the given exception to an array. + * + * @param Throwable $e + * @return array + */ +function convertExceptionToArray(Throwable $e) +{ + return config('app.debug', false) ? [ + 'message' => $e->getMessage(), + 'exception' => get_class($e), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'trace' => collect($e->getTrace())->map(function ($trace) { + return Arr::except($trace, ['args']); + })->all(), + ] : [ + 'message' => isHttpException($e) ? $e->getMessage() : 'Server Error', + ]; +} diff --git a/tests/Unit/FormatTest.php b/tests/Unit/FormatTest.php new file mode 100644 index 0000000..5e577a4 --- /dev/null +++ b/tests/Unit/FormatTest.php @@ -0,0 +1,13 @@ +status())->toEqual(200) + ->and($response->getData(true))->toHaveKey('extra') + ->and($response->getData(true)['extra'])->toHaveKey('time'); + +}); diff --git a/tests/Unit/SuccessTest.php b/tests/Unit/SuccessTest.php new file mode 100644 index 0000000..af61e27 --- /dev/null +++ b/tests/Unit/SuccessTest.php @@ -0,0 +1,307 @@ +status())->toEqual(200) + ->and($response->getContent())->toBeJson(json_encode([ + 'status' => 'success', + 'code' => 200, + 'message' => '操作成功', + 'data' => [], + 'error' => (object) [], + ])); +}); + +test('created', function () { + // 方式二:返回创建成功 + $response = Response::created(); + + expect($response->status())->toEqual(201) + ->and($response->getContent())->toBeJson(json_encode([ + 'status' => 'success', + 'code' => 201, + 'message' => '', + 'data' => (object) [], + 'error' => (object) [], + ])); +}); + +test('accepted', function () { + // 方式三:返回接收成功 + $response = Response::accepted(); + + expect($response->status())->toEqual(202) + ->and($response->getContent())->toBeJson(json_encode([ + 'status' => 'success', + 'code' => 202, + 'message' => '', + 'data' => (object) [], + 'error' => (object) [], + ])); +}); + +test('no content', function () { + // 方式四:返回空内容;创建成功或删除成功等场景 + $response = Response::noContent(); + + expect($response->status())->toEqual(204) + ->and($response->getContent())->toBeJson(json_encode([ + 'status' => 'success', + 'code' => 204, + 'message' => '', + 'data' => (object) [], + 'error' => (object) [], + ])); +}); + +test('success with array data', function () { + // 方式五:返回普通的数组 + $data = [ + 'name' => 'Jiannei', + 'email' => 'longjian.huang@foxmail.com', + ]; + $response = Response::success($data); + + expect($response->status())->toEqual(200); + + $expectedJson = json_encode([ + 'status' => 'success', + 'code' => 200, + 'message' => '操作成功', + 'data' => $data, + 'error' => (object) [], + ]); + $this->assertJsonStringEqualsJsonString($expectedJson, $response->getContent()); +}); + +test('success with resource data', function () { + // 方式六:返回 Api resource + $user = User::factory()->make(); + $response = Response::success(new UserResource($user)); + + expect($response->status())->toEqual(200); + $expectedJson = json_encode([ + 'status' => 'success', + 'code' => 200, + 'message' => '操作成功', + 'data' => [ + 'nickname' => $user->name, + 'email' => $user->email, + ], + 'error' => (object) [], + ]); + + $this->assertJsonStringEqualsJsonString($expectedJson, $response->getContent()); +}); + +test('success with collection data', function () { + // 方式七:返回 Api collection + User::factory()->count(3)->create(); + $users = User::all(); + $response = Response::success(new UserCollection($users)); + + expect($response->status())->toEqual(200); + + $data = $users->map(function ($item) { + return [ + 'nickname' => $item->name, + 'email' => $item->email, + ]; + })->all(); + $expectedJson = json_encode([ + 'status' => 'success', + 'code' => 200, + 'message' => '操作成功', + 'data' => ['data' => $data], + 'error' => (object) [], + ]); + $this->assertJsonStringEqualsJsonString($expectedJson, $response->getContent()); +}); + +test('success with paginated data', function () { + // 方式八:返回分页的 Api collection + User::factory()->count(20)->create(); + $users = User::query()->paginate(); + + $response = Response::success(new UserCollection($users)); + + expect($response->status())->toEqual(200); + + $formatData = Arr::map($users->items(), fn($item) => [ + 'nickname' => $item->name, + 'email' => $item->email, + ]); + + $data = [ + 'data' => $formatData, + 'meta' => [ + 'pagination' => [ + 'count' => $users->lastItem(), + 'per_page' => $users->perPage(), + 'current_page' => $users->currentPage(), + 'total' => $users->total(), + 'total_pages' => $users->lastPage(), + 'links' => array_filter([ + 'previous' => $users->previousPageUrl(), + 'next' => $users->nextPageUrl(), + ]), + ], + ], + ]; + $expectedJson = json_encode([ + 'status' => 'success', + 'code' => 200, + 'message' => '操作成功', + 'data' => $data, + 'error' => (object) [], + ]); + + $this->assertJsonStringEqualsJsonString($expectedJson, $response->getContent()); +}); + +test('success with message', function () { + // 方式九:返回指定的 Message + $response = Response::success([], '成功'); + + $expectedJson = json_encode([ + 'status' => 'success', + 'code' => 200, + 'message' => '成功', + 'data' => (object) [], + 'error' => (object) [], + ]); + + $this->assertJsonStringEqualsJsonString($expectedJson, $response->getContent()); +}); + +test('success with custom message and code', function () { + // 方式十:根据预定义的「业务码」和「对应的描述信息」返回 + $response = Response::success([], '', ResponseEnum::SERVICE_LOGIN_SUCCESS); + + $expectedJson = json_encode([ + 'status' => 'success', + 'code' => ResponseEnum::SERVICE_LOGIN_SUCCESS->value, // 返回自定义的业务码 + 'message' => ResponseEnum::fromValue(ResponseEnum::SERVICE_LOGIN_SUCCESS->value)->description(), // 根据业务码取多语言的业务描述 + 'data' => (object) [], + 'error' => (object) [], + ]); + + $this->assertJsonStringEqualsJsonString($expectedJson, $response->getContent()); +}); + +test('length aware paginator', function () { + User::factory()->count(20)->create(); + $users = User::query()->paginate(); + + $response = Response::success($users); + + expect($response->status())->toEqual(200); + + $formatData = Arr::map($users->items(), fn($item) => $item->toArray()); + + $data = [ + 'data' => $formatData, + 'meta' => [ + 'pagination' => [ + 'count' => $users->lastItem(), + 'per_page' => $users->perPage(), + 'current_page' => $users->currentPage(), + 'total' => $users->total(), + 'total_pages' => $users->lastPage(), + 'links' => array_filter([ + 'previous' => $users->previousPageUrl(), + 'next' => $users->nextPageUrl(), + ]), + ], + ], + ]; + $expectedJson = json_encode([ + 'status' => 'success', + 'code' => 200, + 'message' => '操作成功', + 'data' => $data, + 'error' => (object) [], + ]); + + $this->assertJsonStringEqualsJsonString($expectedJson, $response->getContent()); +}); + +test('simple paginator', function () { + User::factory()->count(20)->create(); + $users = User::query()->simplePaginate(); + + $response = Response::success($users); + + expect($response->status())->toEqual(200); + + $formatData = Arr::map($users->items(), fn($item) => $item->toArray()); + + $data = [ + 'data' => $formatData, + 'meta' => [ + 'pagination' => [ + 'count' => $users->lastItem(), + 'per_page' => $users->perPage(), + 'current_page' => $users->currentPage(), + 'links' => array_filter([ + 'previous' => $users->previousPageUrl(), + 'next' => $users->nextPageUrl(), + ]), + ], + ], + ]; + $expectedJson = json_encode([ + 'status' => 'success', + 'code' => 200, + 'message' => '操作成功', + 'data' => $data, + 'error' => (object) [], + ]); + + $this->assertJsonStringEqualsJsonString($expectedJson, $response->getContent()); +}); + +test('cursor paginator', function () { + User::factory()->count(20)->create(); + $users = User::query()->cursorPaginate(); + + $response = Response::success($users); + + expect($response->status())->toEqual(200); + + $formatData = Arr::map($users->items(), fn($item) => $item->toArray()); + + $data = [ + 'data' => $formatData, + 'meta' => [ + 'cursor' => [ + 'current' => $users->cursor()?->encode(), + 'prev' => $users->previousCursor()?->encode(), + 'next' => $users->nextCursor()?->encode(), + 'count' => count($users->items()), + ], + ], + ]; + $expectedJson = json_encode([ + 'status' => 'success', + 'code' => 200, + 'message' => '操作成功', + 'data' => $data, + 'error' => (object) [], + ]); + + $this->assertJsonStringEqualsJsonString($expectedJson, $response->getContent()); +}); diff --git a/tests/lang/zh_CN/enums.php b/tests/lang/zh_CN/enums.php index 29475c3..de20e9a 100644 --- a/tests/lang/zh_CN/enums.php +++ b/tests/lang/zh_CN/enums.php @@ -9,33 +9,36 @@ * with this source code in the file LICENSE. */ -use Jiannei\Response\Laravel\Tests\Repositories\Enums\ResponseCodeEnum; +use Jiannei\Enum\Laravel\Support\Enums\HttpStatusCode; +use Jiannei\Response\Laravel\Tests\Enums\ResponseEnum; return [ - // 响应状态码 - ResponseCodeEnum::class => [ - // 成功 - ResponseCodeEnum::HTTP_OK => '操作成功', // 自定义 HTTP 状态码返回消息 - ResponseCodeEnum::HTTP_UNAUTHORIZED => '授权失败', + 200 => '成功',// 直接通过状态码取多语言 + + // 结合 Enum 取多语言 + ResponseEnum::class => [ + // 标准 HTTP 状态码 + HttpStatusCode::HTTP_OK->value => '操作成功', + HttpStatusCode::HTTP_UNAUTHORIZED->value => '授权失败', // 业务操作成功 - ResponseCodeEnum::SERVICE_REGISTER_SUCCESS => '注册成功', - ResponseCodeEnum::SERVICE_LOGIN_SUCCESS => '登录成功', + ResponseEnum::SERVICE_REGISTER_SUCCESS->value => '注册成功', + ResponseEnum::SERVICE_LOGIN_SUCCESS->value => '登录成功', + + // 业务操作失败:授权业务 + ResponseEnum::SERVICE_REGISTER_ERROR->value => '注册失败', + ResponseEnum::SERVICE_LOGIN_ERROR->value => '登录失败', // 客户端错误 - ResponseCodeEnum::CLIENT_PARAMETER_ERROR => '参数错误', - ResponseCodeEnum::CLIENT_CREATED_ERROR => '数据已存在', - ResponseCodeEnum::CLIENT_DELETED_ERROR => '数据不存在', + ResponseEnum::CLIENT_PARAMETER_ERROR->value => '参数错误', + ResponseEnum::CLIENT_CREATED_ERROR->value => '数据已存在', + ResponseEnum::CLIENT_DELETED_ERROR->value => '数据不存在', // 服务端错误 - ResponseCodeEnum::SYSTEM_ERROR => '服务器错误', - ResponseCodeEnum::SYSTEM_UNAVAILABLE => '服务器正在维护,暂不可用', - ResponseCodeEnum::SYSTEM_CACHE_CONFIG_ERROR => '缓存配置错误', - ResponseCodeEnum::SYSTEM_CACHE_MISSED_ERROR => '缓存未命中', - ResponseCodeEnum::SYSTEM_CONFIG_ERROR => '系统配置错误', - - // 业务操作失败:授权业务 - ResponseCodeEnum::SERVICE_REGISTER_ERROR => '注册失败', - ResponseCodeEnum::SERVICE_LOGIN_ERROR => '登录失败', + ResponseEnum::SYSTEM_ERROR->value => '服务器错误', + ResponseEnum::SYSTEM_UNAVAILABLE->value => '服务器正在维护,暂不可用', + ResponseEnum::SYSTEM_CACHE_CONFIG_ERROR->value => '缓存配置错误', + ResponseEnum::SYSTEM_CACHE_MISSED_ERROR->value => '缓存未命中', + ResponseEnum::SYSTEM_CONFIG_ERROR->value => '系统配置错误', ], ];