diff --git a/README.md b/README.md
index f94a499..c811a71 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
# Alibaba SDK
[![build](https://github.com/kyto-gmbh/alibaba-sdk-php/actions/workflows/build.yml/badge.svg)](https://github.com/kyto-gmbh/alibaba-sdk-php/actions/workflows/build.yml)
-Alibaba SDK for PHP. This package provides a structured interface to communicate with [Alibaba Open Platform](https://developer.alibaba.com/en/doc.htm?spm=a219a.7629140.0.0.188675fe5JPvEa#?docType=1&docId=118496).
+Alibaba SDK for PHP. This package provides a structured interface to communicate with [Alibaba Open Platform](https://openapi.alibaba.com/doc/doc.htm?spm=a2o9m.11223882.0.0.1566722cTOuz7W#/?docId=19).
> Note, package is in development therefore public interface could be changed in future releases.
@@ -23,10 +23,12 @@ require __DIR__ . '/vendor/autoload.php';
use Kyto\Alibaba\Facade;
-$alibaba = Facade::create('api-key', 'api-secret');
+$alibaba = Facade::create('app-key', 'app-secret');
$alibaba->category->get('0'); // @return Kyto\Alibaba\Model\Category
```
+> For the API usage workflow see [Alibaba API Workflow](docs/workflow.md).
+
## Endpoints
Currently implemented endpoints:
@@ -34,13 +36,12 @@ Currently implemented endpoints:
facade
├─ getAuthorizationUrl - Get user authorization url
├─ token/ - Token endpoint
-│ └─ new - Obtain new session token
+│ ├─ new - Obtain new access token
+│ └─ refresh - Refresh access token
├─ category/ - Category endpoint
-│ ├─ get - Get product listing category
-│ ├─ getAttributes - Get system-defined attributes based on category ID
-│ └─ getLevelAttribute - Get next-level attribute based on category, attribute and value ID (e.g. car_model values)
+│ └─ get - Get product listing category
└─ product/ - Product endpoint
- └─ getGroup - Get product group
+ └─ getSchema - Get rules and fields for new product release
```
## Credits
diff --git a/composer.json b/composer.json
index 7627741..b4e4dba 100644
--- a/composer.json
+++ b/composer.json
@@ -13,7 +13,6 @@
"require": {
"php": "^8.1",
"ext-json": "*",
- "ext-mbstring": "*",
"symfony/http-client": "^5.4 || ^6"
},
"require-dev": {
diff --git a/docs/workflow.md b/docs/workflow.md
new file mode 100644
index 0000000..43b51e5
--- /dev/null
+++ b/docs/workflow.md
@@ -0,0 +1,24 @@
+# Alibaba API Workflow
+
+To be able to interact with Alibaba Open Platform, you need to follow the steps below:
+1. [Create the application](https://openapi.alibaba.com/app/index.htm?spm=a2o9m.11193487.0.0.35b913a0AlGs39#/app/create?_k=lrqyds)
+on the [Alibaba Open Platform](https://openapi.alibaba.com/) specifying all the required permissions (app category).
+This may require you to have an Alibaba account and company verification process to be complete.
+2. Obtain the `app-key` and `app-secret` from the created application.
+This is available in the application settings after Alibaba approves the application.
+3. Authorize the application to access the seller data.
+Use the [`Facade::getAuthorizationUrl`](../src/Facade.php#L53) method to get the URL to authorize the application.
+This will require to log in as a seller and grant the permissions to the application.
+As a result, you will be redirected to callback URL with the authorization code.
+4. Obtain the `access-token` from the authorization response.
+Use the [`Facade::token->new`](../src/Endpoint/TokenEndpoint.php#L39) method to get the `access-token` (and `refresh-token`).
+5. Use the `access-token` to interact with the Alibaba Open Platform.
+
+> Every request to the Alibaba Open Platform requires the `access-token` and valid `signature`.
+> Signature is a hash of the `app-key`, `app-secret`, `timestamp` and `request payload`.
+> Signature is generated by the SDK automatically.
+
+See the following schema for the Alibaba API workflow:
+[![Alibab API workflow](./workflow.svg)](./workflow.svg)
+
+For more details see the Alibaba Open Platform [quick start guide](https://openapi.alibaba.com/doc/doc.htm?spm=a2o9m.11223882.0.0.1566722cTOuz7W#/?docId=51).
diff --git a/docs/workflow.svg b/docs/workflow.svg
new file mode 100644
index 0000000..b3efcd2
--- /dev/null
+++ b/docs/workflow.svg
@@ -0,0 +1,46 @@
+
diff --git a/src/Client.php b/src/Client.php
index c9048a7..423e8e8 100644
--- a/src/Client.php
+++ b/src/Client.php
@@ -14,7 +14,7 @@
class Client
{
public function __construct(
- private string $apiKey,
+ private string $key,
private string $secret,
private HttpClientInterface $httpClient,
private Clock $clock,
@@ -27,18 +27,19 @@ public function __construct(
* @param mixed[] $payload JSON serializable data
* @return mixed[] Decoded JSON as associative array
*/
- public function request(array $payload): array
+ public function request(string $endpoint, array $payload): array
{
+ $endpoint = str_starts_with($endpoint, '/') ? $endpoint : '/' . $endpoint;
$headers = ['User-Agent' => 'Kyto Alibaba Client'];
- $body = $this->getBody($payload);
+ $body = $this->getBody($endpoint, $payload);
- $response = $this->httpClient->request('POST', 'https://api.taobao.com/router/rest', [
+ $response = $this->httpClient->request('POST', 'https://openapi-api.alibaba.com/rest' . $endpoint, [
'headers' => $headers,
'body' => $body,
]);
$data = $response->toArray();
- $this->throwOnError($data);
+ $this->throwOnError($endpoint, $data);
return $data;
}
@@ -46,61 +47,71 @@ public function request(array $payload): array
* @param mixed[] $payload
* @return mixed[]
*/
- private function getBody(array $payload): array
+ private function getBody(string $endpoint, array $payload): array
{
$payload = array_merge([
- 'app_key' => $this->apiKey,
+ 'app_key' => $this->key,
'timestamp' => $this->getTimestamp(),
- 'format' => 'json',
- 'v' => '2.0',
], $payload);
- return $this->getSignedBody($payload);
+ return $this->getSignedBody($endpoint, $payload);
}
/**
* All API calls must include a valid signature. Requests with invalid signatures will be rejected.
+ * @link https://openapi.alibaba.com/doc/doc.htm?docId=19#/?docId=60
+ * @link https://openapi.alibaba.com/doc/doc.htm?docId=19#/?docId=58
*
* @param mixed[] $body
* @return mixed[] Same body plus "sign_method" and "sign" values
*/
- private function getSignedBody(array $body): array
+ private function getSignedBody(string $endpoint, array $body): array
{
unset($body['sign']);
- $body['sign_method'] = 'md5';
-
+ $body['sign_method'] = 'sha256';
ksort($body);
- $hashString = '';
+
+ $hashString = $endpoint;
foreach ($body as $key => $value) {
$hashString .= $key . $value;
}
- $hashString = $this->secret . $hashString . $this->secret;
- $body['sign'] = mb_strtoupper(md5($hashString));
+ $body['sign'] = strtoupper(hash_hmac('sha256', $hashString, $this->secret));
return $body;
}
/**
- * Required by Alibaba API specs to be in GMT+8 timezone
+ * Required by Alibaba to be in microseconds and (seems like) in UTC timezone.
*/
private function getTimestamp(): string
{
- return $this->clock->now('GMT+8')->format('Y-m-d H:i:s');
+ return $this->clock->now('UTC')->format('Uv');
}
/**
* @param mixed[] $data
*/
- private function throwOnError(array $data): void
+ private function throwOnError(string $endpoint, array $data): void
{
- $errorResponse = $data['error_response'] ?? null;
+ if (isset($data['type'], $data['code'])) {
+ throw new ResponseException(
+ $endpoint,
+ $data['type'],
+ $data['message'],
+ $data['code'],
+ $data['request_id'],
+ $data['_trace_id_'],
+ );
+ }
- if ($errorResponse !== null) {
+ if (isset($data['result']['success']) && (bool) $data['result']['success'] !== true) {
throw new ResponseException(
- $errorResponse['msg'],
- (int) $errorResponse['code'],
- $errorResponse['sub_msg'],
- $errorResponse['sub_code'],
+ $endpoint,
+ 'SYSTEM', // it's empty for this type of error, therefore we use "SYSTEM"
+ $data['result']['message_info'],
+ $data['result']['msg_code'],
+ $data['request_id'],
+ $data['_trace_id_'],
);
}
}
diff --git a/src/Endpoint/CategoryEndpoint.php b/src/Endpoint/CategoryEndpoint.php
index 09b33d2..f817510 100644
--- a/src/Endpoint/CategoryEndpoint.php
+++ b/src/Endpoint/CategoryEndpoint.php
@@ -6,11 +6,9 @@
use Kyto\Alibaba\Client;
use Kyto\Alibaba\Exception\ResponseException;
-use Kyto\Alibaba\Exception\UnexpectedResultException;
use Kyto\Alibaba\Factory\CategoryFactory;
use Kyto\Alibaba\Model\Category;
-use Kyto\Alibaba\Model\CategoryAttribute;
-use Kyto\Alibaba\Model\CategoryLevelAttribute;
+use Kyto\Alibaba\Model\Token;
class CategoryEndpoint
{
@@ -33,80 +31,20 @@ public function __construct(
/**
* Get product listing category
- * @link https://developer.alibaba.com/en/doc.htm?spm=a219a.7629140.0.0.188675fe5JPvEa#?docType=2&docId=50064
+ * @link https://openapi.alibaba.com/doc/api.htm?spm=a2o9m.11223882.0.0.1566722cTOuz7W#/api?cid=1&path=/icbu/product/category/get&methodType=GET/POST
*
* @param ?string $id Provide `null` to fetch root categories
* @throws ResponseException
*/
- public function get(?string $id = null): Category
+ public function get(Token $token, ?string $id = null): Category
{
$id = $id ?? '0'; // '0' to fetch root categories
- $data = $this->client->request([
- 'method' => 'alibaba.icbu.category.get.new',
- 'cat_id' => $id
+ $data = $this->client->request('/icbu/product/category/get', [
+ 'access_token' => $token->token,
+ 'cat_id' => $id,
]);
return $this->categoryFactory->createCategory($data);
}
-
- /**
- * Get system-defined attributes based on category ID
- * @link https://developer.alibaba.com/en/doc.htm?spm=a219a.7629140.0.0.188675fe5JPvEa#?docType=2&docId=25348
- *
- * @return CategoryAttribute[]
- * @throws ResponseException
- */
- public function getAttributes(string $categoryId): array
- {
- $data = $this->client->request([
- 'method' => 'alibaba.icbu.category.attribute.get',
- 'cat_id' => $categoryId,
- ]);
-
- $result = [];
-
- $attributes = $data['alibaba_icbu_category_attribute_get_response']['attributes']['attribute'];
- foreach ($attributes as $attribute) {
- $result[] = $this->categoryFactory->createAttribute($attribute);
- }
-
- return $result;
- }
-
- /**
- * Get next-level attribute based on category, attribute and optionally level attribute value ID.
- * @link https://developer.alibaba.com/en/doc.htm?spm=a2728.12183079.k2mwm9fd.1.4b3630901WuQWY#?docType=2&docId=48659
- *
- * @param ?string $valueId provide null to fetch root level
- * @throws ResponseException|UnexpectedResultException
- */
- public function getLevelAttribute(
- string $categoryId,
- string $attributeId,
- ?string $valueId = null
- ): CategoryLevelAttribute {
- $attributeValueRequest = [
- 'cat_id' => $categoryId,
- 'attr_id' => $attributeId,
- 'value_id' => $valueId ?? '0'
- ];
-
- $data = $this->client->request([
- 'method' => 'alibaba.icbu.category.level.attr.get',
- 'attribute_value_request' => json_encode($attributeValueRequest)
- ]);
-
- $errorMessage = sprintf(
- 'Result list for category id: "%s", attribute id: "%s", value id: "%s" is empty.',
- $categoryId,
- $attributeId,
- $valueId
- );
-
- $attribute = $data['alibaba_icbu_category_level_attr_get_response']['result_list']
- ?? throw new UnexpectedResultException($errorMessage);
-
- return $this->categoryFactory->createLevelAttribute($attribute);
- }
}
diff --git a/src/Endpoint/ProductEndpoint.php b/src/Endpoint/ProductEndpoint.php
index e654c32..35d7b76 100644
--- a/src/Endpoint/ProductEndpoint.php
+++ b/src/Endpoint/ProductEndpoint.php
@@ -5,9 +5,8 @@
namespace Kyto\Alibaba\Endpoint;
use Kyto\Alibaba\Client;
-use Kyto\Alibaba\Exception\ResponseException;
use Kyto\Alibaba\Factory\ProductFactory;
-use Kyto\Alibaba\Model\ProductGroup;
+use Kyto\Alibaba\Model\Category;
use Kyto\Alibaba\Model\Token;
class ProductEndpoint
@@ -25,27 +24,28 @@ public static function create(Client $client): self
*/
public function __construct(
private Client $client,
- private ProductFactory $productFactory,
+ private ProductFactory $productFactory, // @phpstan-ignore-line
) {
}
/**
- * Get product group information.
- * @link https://developer.alibaba.com/en/doc.htm?spm=a219a.7629140.0.0.188675fe5JPvEa#?docType=2&docId=25299
+ * Obtain the page rules and fill-in fields for new product release.
+ * @link https://openapi.alibaba.com/doc/api.htm?spm=a2o9m.11193531.0.0.2fabf453CIh7hC#/api?cid=1&path=/alibaba/icbu/product/schema/get&methodType=GET/POST
*
- * @param ?string $id Provide `null` to fetch root groups
- * @throws ResponseException
+ * @param string $language Allowed values are not documented in the API docs.
+ * Seems like uses the same locale codes as on Alibaba website:
+ * en_US, es_ES, fr_FR, it_IT, de_DE, pt_PT, ru_RU, ja_JP,
+ * ar_SA, ko_KR, tr_TR, vi_VN, th_TH, id_ID, he_IL, hi_IN, zh_CN
+ * @return array
*/
- public function getGroup(Token $token, ?string $id = null): ProductGroup
+ public function getSchema(Token $token, Category $category, string $language = 'en_US'): array
{
- $id = $id ?? '-1'; // '-1' to fetch root groups
-
- $data = $this->client->request([
- 'method' => 'alibaba.icbu.product.group.get',
- 'session' => $token->token,
- 'group_id' => $id
+ $data = $this->client->request('/alibaba/icbu/product/schema/get', [
+ 'cat_id' => $category->id,
+ 'access_token' => $token->token,
+ 'language' => $language,
]);
- return $this->productFactory->createGroup($data);
+ return $data; // TODO: Add normalized model for response
}
}
diff --git a/src/Endpoint/TokenEndpoint.php b/src/Endpoint/TokenEndpoint.php
index 04be7e1..d6568ad 100644
--- a/src/Endpoint/TokenEndpoint.php
+++ b/src/Endpoint/TokenEndpoint.php
@@ -8,6 +8,7 @@
use Kyto\Alibaba\Exception\ResponseException;
use Kyto\Alibaba\Factory\TokenFactory;
use Kyto\Alibaba\Model\Token;
+use Kyto\Alibaba\Util\Clock;
class TokenEndpoint
{
@@ -16,7 +17,7 @@ class TokenEndpoint
*/
public static function create(Client $client): self
{
- return new self($client, new TokenFactory());
+ return new self($client, new TokenFactory(new Clock()));
}
/**
@@ -30,18 +31,35 @@ public function __construct(
/**
* To obtain authorization code see corresponding facade method.
- * @link https://open.taobao.com/api.htm?spm=a219a.7386653.0.0.41449b714zR8KI&docId=25388&docType=2&source=search
+ * @link https://openapi.alibaba.com/doc/api.htm?spm=a2o9m.11193531.0.0.2fabf453xGO6n7#/api?cid=4&path=/auth/token/create&methodType=GET/POST
* @see \Kyto\Alibaba\Facade::getAuthorizationUrl
*
- * @throws ResponseException|\JsonException
+ * @throws ResponseException
*/
public function new(string $authorizationCode): Token
{
- $data = $this->client->request([
- 'method' => 'taobao.top.auth.token.create',
+ $data = $this->client->request('/auth/token/create', [
'code' => $authorizationCode,
]);
return $this->tokenFactory->createToken($data);
}
+
+ /**
+ * @link https://openapi.alibaba.com/doc/api.htm?spm=a2o9m.11193531.0.0.2fabf453CIh7hC#/api?cid=4&path=/auth/token/refresh&methodType=GET/POST
+ *
+ * @throws ResponseException
+ */
+ public function refresh(Token $token): Token
+ {
+ $data = $this->client->request('/auth/token/refresh', [
+ 'refresh_token' => $token->refreshToken,
+ ]);
+
+ if (!isset($data['account'])) {
+ $data['account'] = $token->account;
+ }
+
+ return $this->tokenFactory->createToken($data);
+ }
}
diff --git a/src/Exception/ResponseException.php b/src/Exception/ResponseException.php
index b1351c7..165cbac 100644
--- a/src/Exception/ResponseException.php
+++ b/src/Exception/ResponseException.php
@@ -4,29 +4,63 @@
namespace Kyto\Alibaba\Exception;
+/**
+ * Represents an error response from Alibaba API.
+ * @link https://openapi.alibaba.com/doc/doc.htm?docId=19#/?docId=63
+ */
class ResponseException extends AlibabaException
{
/**
* @internal
*/
public function __construct(
+ private string $endpoint,
+ private string $type,
string $message,
- int $code,
- private string $subMessage,
- private string $subCode,
+ private string $erorrCode,
+ private string $requestId,
+ private string $traceId,
?\Throwable $previous = null
) {
- $message = sprintf('%s. Sub-code: "%s". Sub-message: "%s".', $message, $this->subCode, $this->subMessage);
- parent::__construct($message, $code, $previous);
+ $message = sprintf(
+ '[%s] %s. Endpoint: "%s". Request id: "%s". Trace id: "%s".',
+ $this->erorrCode,
+ $message,
+ $this->endpoint,
+ $this->requestId,
+ $this->traceId,
+ );
+ parent::__construct($message, 0, $previous);
}
- public function getSubMessage(): string
+ public function getEndpoint(): string
{
- return $this->subMessage;
+ return $this->endpoint;
}
- public function getSubCode(): string
+ /**
+ * @return string Known values are:
+ * - SYSTEM: API platform error
+ * - ISV: Business data error
+ * - ISP: Backend service error
+ */
+ public function getType(): string
+ {
+ return $this->type;
+ }
+
+ public function getErrorCode(): string
+ {
+ return $this->erorrCode;
+ }
+
+ public function getRequestId(): string
+ {
+ return $this->requestId;
+ }
+
+ public function getTraceId(): string
{
- return $this->subCode;
+ return $this->traceId;
}
}
diff --git a/src/Exception/UnexpectedResultException.php b/src/Exception/UnexpectedResultException.php
deleted file mode 100644
index 4cfb418..0000000
--- a/src/Exception/UnexpectedResultException.php
+++ /dev/null
@@ -1,19 +0,0 @@
-category = CategoryEndpoint::create($this->client);
@@ -45,19 +45,17 @@ public function __construct(
/**
* Making GET request to this URL will ask to login to Alibaba and authorize this API key to have access
* to the account. In other words client should visit this url and authorize App to access Alibaba account by API.
- * @link https://developer.alibaba.com/en/doc.htm?spm=a219a.7629140.0.0.188675fe5JPvEa#?docType=1&docId=118416
+ * @link https://openapi.alibaba.com/doc/doc.htm?spm=a2o9m.11193494.0.0.50dd3a3armsNgS#/?docId=56
*
- * @param string $callbackUrl URL where authorization code returned. Via method GET in "code" parameter.
+ * @param string $callbackUrl URL where authorization code returned. Via method GET in "code" parameter. Should be
+ * the same as in the App settings in Alibaba.
*/
public function getAuthorizationUrl(string $callbackUrl): string
{
- return 'https://oauth.alibaba.com/authorize?' . http_build_query([
+ return 'https://openapi-auth.alibaba.com/oauth/authorize?' . http_build_query([
'response_type' => 'code',
- 'client_id' => $this->apiKey,
'redirect_uri' => $callbackUrl,
- 'State' => '1212',
- 'view' => 'web',
- 'sp' => 'ICBU',
+ 'client_id' => $this->key,
]);
}
}
diff --git a/src/Factory/CategoryFactory.php b/src/Factory/CategoryFactory.php
index 1764804..73a94b8 100644
--- a/src/Factory/CategoryFactory.php
+++ b/src/Factory/CategoryFactory.php
@@ -4,15 +4,8 @@
namespace Kyto\Alibaba\Factory;
-use Kyto\Alibaba\Enum\InputType;
-use Kyto\Alibaba\Enum\ShowType;
-use Kyto\Alibaba\Enum\ValueType;
-use Kyto\Alibaba\Model\CategoryLevelAttribute;
-use Kyto\Alibaba\Model\CategoryLevelAttributeValue;
use Kyto\Alibaba\Util\Formatter;
use Kyto\Alibaba\Model\Category;
-use Kyto\Alibaba\Model\CategoryAttribute;
-use Kyto\Alibaba\Model\CategoryAttributeValue;
/**
* @internal
@@ -24,7 +17,7 @@ class CategoryFactory
*/
public function createCategory(array $data): Category
{
- $category = $data['alibaba_icbu_category_get_new_response']['category'];
+ $category = $data['result']['result'];
$model = new Category();
$model->id = (string) $category['category_id'];
@@ -32,85 +25,8 @@ public function createCategory(array $data): Category
$model->nameCN = (string) ($category['cn_name'] ?? '');
$model->level = (int) $category['level'];
$model->isLeaf = (bool) $category['leaf_category'];
- $model->parents = Formatter::getAsArrayOfString($category['parent_ids']['number'] ?? []);
- $model->children = Formatter::getAsArrayOfString($category['child_ids']['number'] ?? []);
-
- return $model;
- }
-
- /**
- * @param array $data
- */
- public function createAttribute(array $data): CategoryAttribute
- {
- $model = new CategoryAttribute();
-
- $model->id = (string) $data['attr_id'];
- $model->name = (string) $data['en_name'];
- $model->isRequired = (bool) $data['required'];
-
- $model->inputType = InputType::from($data['input_type']);
- $model->showType = ShowType::from($data['show_type']);
- $model->valueType = ValueType::from($data['value_type']);
-
- $model->isSku = (bool) $data['sku_attribute'];
- $model->hasCustomizeImage = (bool) $data['customize_image'];
- $model->hasCustomizeValue = (bool) $data['customize_value'];
- $model->isCarModel = (bool) $data['car_model'];
-
- $model->units = Formatter::getAsArrayOfString($data['units']['string'] ?? []);
-
- $values = $data['attribute_values']['attribute_value'] ?? [];
- foreach ($values as $value) {
- $model->values[] = $this->createAttributeValue($value);
- }
-
- return $model;
- }
-
- /**
- * @param array $data
- */
- public function createAttributeValue(array $data): CategoryAttributeValue
- {
- $model = new CategoryAttributeValue();
-
- $model->id = (string) $data['attr_value_id'];
- $model->name = (string) $data['en_name'];
- $model->isSku = (bool) $data['sku_value'];
- $model->childAttributes = Formatter::getAsArrayOfString($data['child_attrs']['number'] ?? []);
-
- return $model;
- }
-
- /**
- * @param array $data
- */
- public function createLevelAttribute(array $data): CategoryLevelAttribute
- {
- $model = new CategoryLevelAttribute();
-
- $model->id = (string) $data['property_id'];
- $model->name = (string) $data['property_en_name'];
-
- $model->values = [];
- $decodedValues = json_decode($data['values'], true);
- foreach ($decodedValues as $value) {
- $model->values[] = $this->createLevelAttributeValue($value);
- }
-
- return $model;
- }
-
- /**
- * @param array $data
- */
- public function createLevelAttributeValue(array $data): CategoryLevelAttributeValue
- {
- $model = new CategoryLevelAttributeValue();
- $model->name = (string) $data['name'];
- $model->id = (string) $data['id'];
- $model->isLeaf = isset($data['leaf']);
+ $model->parents = Formatter::getAsArrayOfString($category['parent_ids'] ?? []);
+ $model->children = Formatter::getAsArrayOfString($category['child_ids'] ?? []);
return $model;
}
diff --git a/src/Factory/ProductFactory.php b/src/Factory/ProductFactory.php
index 12a35d6..ad6141a 100644
--- a/src/Factory/ProductFactory.php
+++ b/src/Factory/ProductFactory.php
@@ -4,27 +4,9 @@
namespace Kyto\Alibaba\Factory;
-use Kyto\Alibaba\Model\ProductGroup;
-use Kyto\Alibaba\Util\Formatter;
-
/**
* @internal
*/
class ProductFactory
{
- /**
- * @param mixed[] $data
- */
- public function createGroup(array $data): ProductGroup
- {
- $group = $data['alibaba_icbu_product_group_get_response']['product_group'];
-
- $model = new ProductGroup();
- $model->id = (string) $group['group_id'];
- $model->name = (string) ($group['group_name'] ?? '');
- $model->parent = isset($group['parent_id']) ? (string) $group['parent_id'] : null;
- $model->children = Formatter::getAsArrayOfString($group['children_id_list']['number'] ?? []);
-
- return $model;
- }
}
diff --git a/src/Factory/TokenFactory.php b/src/Factory/TokenFactory.php
index a74549a..8374be8 100644
--- a/src/Factory/TokenFactory.php
+++ b/src/Factory/TokenFactory.php
@@ -5,40 +5,48 @@
namespace Kyto\Alibaba\Factory;
use Kyto\Alibaba\Model\Token;
+use Kyto\Alibaba\Util\Clock;
+/**
+ * @internal
+ */
class TokenFactory
{
+ public function __construct(
+ private Clock $clock,
+ ) {
+ }
+
/**
* @param mixed[] $data
*/
public function createToken(array $data): Token
{
- $jsonResult = $data['top_auth_token_create_response']['token_result'];
- $token = json_decode($jsonResult, true, 512, JSON_THROW_ON_ERROR);
+ $baseDatetime = $this->clock->now();
$model = new Token();
- $model->userId = (string) $token['user_id'];
- $model->userName = $token['user_nick'] ?? null;
+ $model->account = (string) $data['account'];
- $model->token = (string) $token['access_token'];
- $model->tokenExpireAt = $this->getMillisecondsAsDateTime((int) $token['expire_time']);
+ $model->token = (string) $data['access_token'];
+ $model->tokenExpireAt = $this->getExpiresInAsDateTime($baseDatetime, (int) $data['expires_in']);
- $model->refreshToken = (string) $token['refresh_token'];
- $model->refreshTokenExpireAt = $this->getMillisecondsAsDateTime((int) $token['refresh_token_valid_time']);
+ $model->refreshToken = (string) $data['refresh_token'];
+ $model->refreshTokenExpireAt = $this->getExpiresInAsDateTime($baseDatetime, (int) $data['refresh_expires_in']);
return $model;
}
/**
- * @param int $milliseconds Alibaba provides Unix time in milliseconds
+ * It is recommended by the Alibaba API docs to refresh the token 30 minutes before it expires.
+ * @link https://openapi.alibaba.com/doc/doc.htm?spm=a2o9m.11223882.0.0.1566722cTOuz7W#/?docId=56
*/
- private function getMillisecondsAsDateTime(int $milliseconds): \DateTimeImmutable
+ private function getExpiresInAsDateTime(\DateTime $baseDatetime, int $expiresIn): \DateTimeImmutable
{
- $value = (string) ($milliseconds / 1000);
- $datetime = \DateTimeImmutable::createFromFormat('U.u', $value);
- if ($datetime === false) {
- throw new \UnexpectedValueException(sprintf('Unable to parse "%s" as microtime.', $milliseconds));
- }
- return $datetime;
+ $recommendedExpire = (int) ($expiresIn - (30 * 60)); // 30 minutes before actual expiration
+ $expiresIn = $recommendedExpire > 0 ? $recommendedExpire : $expiresIn;
+
+ $modifier = sprintf('+%d seconds', $expiresIn);
+ $datetime = (clone $baseDatetime)->modify($modifier);
+ return \DateTimeImmutable::createFromInterface($datetime);
}
}
diff --git a/src/Model/CategoryAttribute.php b/src/Model/CategoryAttribute.php
deleted file mode 100644
index 3b79432..0000000
--- a/src/Model/CategoryAttribute.php
+++ /dev/null
@@ -1,31 +0,0 @@
-httpClient = $this->createMock(HttpClientInterface::class);
$this->clock = $this->createMock(Clock::class);
- $this->client = new Client(self::API_KEY, self::API_SECRET, $this->httpClient, $this->clock);
+ $this->client = new Client(self::KEY, self::SECRET, $this->httpClient, $this->clock);
}
public function tearDown(): void
@@ -39,10 +39,10 @@ public function tearDown(): void
}
#[DataProvider('requestDataProvider')]
- public function testRequest(bool $isSuccess, array $responseData): void
+ public function testRequest(bool $isSuccess, string $endpoint, array $responseData): void
{
- $timestamp = '2022-11-11 12:37:45';
- $timezone = 'GMT+8';
+ $timestamp = '2024-04-30 18:17:25';
+ $timezone = 'UTC';
$datetime = \DateTime::createFromFormat('Y-m-d H:i:s', $timestamp, new \DateTimeZone($timezone));
$this->clock
@@ -59,20 +59,18 @@ public function testRequest(bool $isSuccess, array $responseData): void
->method('request')
->with(
'POST',
- 'https://api.taobao.com/router/rest',
+ 'https://openapi-api.alibaba.com/rest/some/endpoint',
[
'headers' => [
'User-Agent' => 'Kyto Alibaba Client',
],
'body' => [
- 'app_key' => self::API_KEY,
- 'timestamp' => $timestamp,
- 'format' => 'json',
- 'v' => '2.0',
+ 'app_key' => self::KEY,
+ 'timestamp' => '1714501045000',
'hello' => 'world',
'test' => 'data',
- 'sign_method' => 'md5',
- 'sign' => 'E6B0CBA032759D6C2A4BC0136252672F',
+ 'sign_method' => 'sha256',
+ 'sign' => '99486884A406C07BC1EF420C886F8422B3FE18BD7420CF9CB65B82027430BF7C',
]
]
)
@@ -82,20 +80,31 @@ public function testRequest(bool $isSuccess, array $responseData): void
$this->expectException(ResponseException::class);
}
- $actual = $this->client->request(['hello' => 'world', 'test' => 'data']);
+ $actual = $this->client->request($endpoint, ['hello' => 'world', 'test' => 'data']);
self::assertSame($responseData, $actual);
}
public static function requestDataProvider(): array
{
return [
- 'success' => [true, ['successful' => 'response']],
- 'error' => [false, ['error_response' => [
- 'code' => '1',
- 'msg' => 'Error happened',
- 'sub_code' => 'api.error',
- 'sub_msg' => 'Not working'
- ]]],
+ 'success' => [true, '/some/endpoint', ['successful' => 'response']],
+ 'success, no slash prefix endpoint' => [true, 'some/endpoint', ['successful' => 'response']],
+ 'error 1' => [false, '/some/endpoint', [
+ 'type' => 'ISP',
+ 'code' => 'ErrorHappened',
+ 'message' => 'Error happened please fix',
+ 'request_id' => '2101d05f17144750947504007',
+ '_trace_id_' => '21032cac17144750947448194e339b'
+ ]],
+ 'error 2' => [false, '/some/endpoint', [
+ 'result' => [
+ 'success' => false,
+ 'message_info' => 'Error happened please fix',
+ 'msg_code' => 'isp.error-happened',
+ ],
+ 'request_id' => '2101d05f17144750947504007',
+ '_trace_id_' => '21032cac17144750947448194e339b'
+ ]],
];
}
}
diff --git a/tests/Endpoint/CategoryEndpointTest.php b/tests/Endpoint/CategoryEndpointTest.php
index 8c45633..f7a8a6f 100644
--- a/tests/Endpoint/CategoryEndpointTest.php
+++ b/tests/Endpoint/CategoryEndpointTest.php
@@ -8,8 +8,7 @@
use Kyto\Alibaba\Endpoint\CategoryEndpoint;
use Kyto\Alibaba\Factory\CategoryFactory;
use Kyto\Alibaba\Model\Category;
-use Kyto\Alibaba\Model\CategoryAttribute;
-use Kyto\Alibaba\Model\CategoryLevelAttribute;
+use Kyto\Alibaba\Model\Token;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
@@ -43,16 +42,20 @@ public function testCreate(): void
public function testGet(): void
{
+ $accessToken = 'access-token';
$id = '1';
$data = ['response' => 'data'];
$this->client
->expects(self::once())
->method('request')
- ->with([
- 'method' => 'alibaba.icbu.category.get.new',
- 'cat_id' => $id,
- ])
+ ->with(
+ '/icbu/product/category/get',
+ [
+ 'access_token' => $accessToken,
+ 'cat_id' => $id,
+ ]
+ )
->willReturn($data);
$category = new Category();
@@ -63,68 +66,10 @@ public function testGet(): void
->with($data)
->willReturn($category);
- $actual = $this->categoryEndpoint->get($id);
- self::assertSame($category, $actual);
- }
-
- public function testGetAttributes(): void
- {
- $id = '1';
- $attributes = [
- ['Attribute 1'],
- ['Attribute 2'],
- ];
- $data = ['alibaba_icbu_category_attribute_get_response' => ['attributes' => ['attribute' => $attributes]]];
-
- $this->client
- ->expects(self::once())
- ->method('request')
- ->with([
- 'method' => 'alibaba.icbu.category.attribute.get',
- 'cat_id' => $id,
- ])
- ->willReturn($data);
-
- $result = [
- new CategoryAttribute(),
- new CategoryAttribute(),
- ];
+ $token = new Token();
+ $token->token = $accessToken;
- $this->categoryFactory
- ->expects(self::exactly(2))
- ->method('createAttribute')
- ->willReturnMap([
- [['Attribute 1'], $result[0]],
- [['Attribute 2'], $result[1]],
- ]);
-
- $actual = $this->categoryEndpoint->getAttributes($id);
- self::assertSame($result, $actual);
- }
-
- public function testGetLevelAttributes(): void
- {
- $attributeValueRequestBody = '{"cat_id":"1","attr_id":"1","value_id":"0"}';
- $levelAttribute = ['LevelAttribute'];
- $data = ['alibaba_icbu_category_level_attr_get_response' => ['result_list' => $levelAttribute]];
-
- $this->client
- ->expects(self::once())
- ->method('request')
- ->with([
- 'method' => 'alibaba.icbu.category.level.attr.get',
- 'attribute_value_request' => $attributeValueRequestBody,
- ])
- ->willReturn($data);
-
- $result = new CategoryLevelAttribute();
-
- $this->categoryFactory
- ->expects(self::once())
- ->method('createLevelAttribute')
- ->willReturn($result);
-
- $actual = $this->categoryEndpoint->getLevelAttribute('1', '1', null);
- self::assertSame($result, $actual);
+ $actual = $this->categoryEndpoint->get($token, $id);
+ self::assertSame($category, $actual);
}
}
diff --git a/tests/Endpoint/TokenEndpointTest.php b/tests/Endpoint/TokenEndpointTest.php
index 26b926d..2c79ab1 100644
--- a/tests/Endpoint/TokenEndpointTest.php
+++ b/tests/Endpoint/TokenEndpointTest.php
@@ -47,10 +47,12 @@ public function testNew(): void
$this->client
->expects(self::once())
->method('request')
- ->with([
- 'method' => 'taobao.top.auth.token.create',
- 'code' => $authorizationCode,
- ])
+ ->with(
+ '/auth/token/create',
+ [
+ 'code' => $authorizationCode,
+ ]
+ )
->willReturn($data);
$token = new Token();
@@ -64,4 +66,36 @@ public function testNew(): void
$actual = $this->tokenEndpoint->new($authorizationCode);
self::assertSame($token, $actual);
}
+
+ public function testRefresh(): void
+ {
+ $refreshToken = 'refresh-token';
+ $data = ['response' => 'data', 'account' => 'user@example.com'];
+
+ $token = new Token();
+ $token->account = 'user@example.com';
+ $token->refreshToken = $refreshToken;
+
+ $this->client
+ ->expects(self::once())
+ ->method('request')
+ ->with(
+ '/auth/token/refresh',
+ [
+ 'refresh_token' => $refreshToken,
+ ]
+ )
+ ->willReturn($data);
+
+ $expected = new Token();
+
+ $this->tokenFactory
+ ->expects(self::once())
+ ->method('createToken')
+ ->with($data)
+ ->willReturn($expected);
+
+ $actual = $this->tokenEndpoint->refresh($token);
+ self::assertSame($expected, $actual);
+ }
}
diff --git a/tests/Exception/ResponseExceptionTest.php b/tests/Exception/ResponseExceptionTest.php
index 21f98ec..cd637c2 100644
--- a/tests/Exception/ResponseExceptionTest.php
+++ b/tests/Exception/ResponseExceptionTest.php
@@ -11,18 +11,26 @@ class ResponseExceptionTest extends TestCase
{
public function testConstruct(): void
{
- $message = 'Message';
- $code = 1;
- $subMessage = 'Sub-message';
- $subCode = 'sub.code';
+ $endpoint = '/example/test/get';
+ $type = 'SYSTEM';
+ $message = 'Error happened please fix';
+ $errorCode = 'ErrorHappened';
+ $requestId = '2101d05f17144750947504007';
+ $traceId = '21032cac17144750947448194e339b';
$previous = new \RuntimeException('Previous');
- $exception = new ResponseException($message, $code, $subMessage, $subCode, $previous);
+ $exception = new ResponseException($endpoint, $type, $message, $errorCode, $requestId, $traceId, $previous);
- self::assertSame('Message. Sub-code: "sub.code". Sub-message: "Sub-message".', $exception->getMessage());
- self::assertSame($code, $exception->getCode());
- self::assertSame($subMessage, $exception->getSubMessage());
- self::assertSame($subCode, $exception->getSubCode());
+ $expectedMessage = '[ErrorHappened] Error happened please fix.'
+ . ' Endpoint: "/example/test/get".'
+ . ' Request id: "2101d05f17144750947504007".'
+ . ' Trace id: "21032cac17144750947448194e339b".';
+ self::assertSame($expectedMessage, $exception->getMessage());
+
+ self::assertSame($type, $exception->getType());
+ self::assertSame($errorCode, $exception->getErrorCode());
+ self::assertSame($requestId, $exception->getRequestId());
+ self::assertSame($traceId, $exception->getTraceId());
self::assertSame($previous, $exception->getPrevious());
}
}
diff --git a/tests/Exception/UnexpectedResultExceptionTest.php b/tests/Exception/UnexpectedResultExceptionTest.php
deleted file mode 100644
index e3d39d8..0000000
--- a/tests/Exception/UnexpectedResultExceptionTest.php
+++ /dev/null
@@ -1,24 +0,0 @@
-getMessage());
- self::assertSame($code, $exception->getCode());
- self::assertSame($previous, $exception->getPrevious());
- }
-}
diff --git a/tests/FacadeTest.php b/tests/FacadeTest.php
index 8d2db90..f1ea064 100644
--- a/tests/FacadeTest.php
+++ b/tests/FacadeTest.php
@@ -42,10 +42,12 @@ public function testGetAuthorizationUrl(): void
$callbackURL = 'https://example.com/callback';
$actual = $this->facade->getAuthorizationUrl($callbackURL);
$expected = sprintf(
- 'https://oauth.alibaba.com/authorize?response_type=code&client_id=%s&redirect_uri=%s'
- . '&State=1212&view=web&sp=ICBU',
- urlencode(self::API_KEY),
+ 'https://openapi-auth.alibaba.com/oauth/authorize'
+ . '?response_type=code'
+ . '&redirect_uri=%s'
+ . '&client_id=%s',
urlencode($callbackURL),
+ urlencode(self::API_KEY),
);
self::assertSame($expected, $actual);
diff --git a/tests/Factory/CategoryFactoryTest.php b/tests/Factory/CategoryFactoryTest.php
index 7583208..6a53686 100644
--- a/tests/Factory/CategoryFactoryTest.php
+++ b/tests/Factory/CategoryFactoryTest.php
@@ -4,15 +4,8 @@
namespace Kyto\Alibaba\Tests\Factory;
-use Kyto\Alibaba\Enum\InputType;
-use Kyto\Alibaba\Enum\ShowType;
-use Kyto\Alibaba\Enum\ValueType;
use Kyto\Alibaba\Factory\CategoryFactory;
use Kyto\Alibaba\Model\Category;
-use Kyto\Alibaba\Model\CategoryAttribute;
-use Kyto\Alibaba\Model\CategoryAttributeValue;
-use Kyto\Alibaba\Model\CategoryLevelAttribute;
-use Kyto\Alibaba\Model\CategoryLevelAttributeValue;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
@@ -39,20 +32,18 @@ public function testCreateCategory(array $data, Category $expected): void
self::assertEquals($expected, $actual);
}
- public static function createCategoryDataProvider(): array
+ public static function createCategoryDataProvider(): \Generator
{
- $cases = [];
-
$data = [
- 'alibaba_icbu_category_get_new_response' => [
- 'category' => [
+ 'result' => [
+ 'result' => [
'category_id' => 1,
'name' => 'Example',
'cn_name' => '例子',
'level' => 2,
'leaf_category' => false,
- 'parent_ids' => ['number' => [2, 3]],
- 'child_ids' => ['number' => [4, 5]],
+ 'parent_ids' => [2, 3],
+ 'child_ids' => [4, 5],
],
],
];
@@ -66,11 +57,11 @@ public static function createCategoryDataProvider(): array
$model->parents = ['2', '3'];
$model->children = ['4', '5'];
- $cases['full-result'] = [$data, $model];
+ yield 'full-result' => [$data, $model];
$data = [
- 'alibaba_icbu_category_get_new_response' => [
- 'category' => [
+ 'result' => [
+ 'result' => [
'category_id' => 1,
'name' => 'Example',
'cn_name' => '例子',
@@ -89,237 +80,6 @@ public static function createCategoryDataProvider(): array
$model->parents = [];
$model->children = [];
- $cases['no-parents-and-children'] = [$data, $model];
-
- return $cases;
- }
-
- #[DataProvider('createAttributeDataProvider')]
- public function testCreateAttribute(array $data, CategoryAttribute $expected): void
- {
- $actual = $this->categoryFactory->createAttribute($data);
- self::assertEquals($expected, $actual);
- }
-
- public static function createAttributeDataProvider(): array
- {
- $cases = [];
-
- $data = [
- 'attr_id' => 1,
- 'en_name' => 'Example',
- 'required' => true,
- 'input_type' => 'single_select',
- 'show_type' => 'list_box',
- 'value_type' => 'string',
- 'sku_attribute' => false,
- 'customize_image' => false,
- 'customize_value' => false,
- 'car_model' => false,
- 'attribute_values' => [
- 'attribute_value' => [
- [
- 'attr_value_id' => 11,
- 'en_name' => 'Value 1',
- 'sku_value' => false,
- 'child_attrs' => [
- 'number' => [2, 3]
- ]
- ],
- [
- 'attr_value_id' => 12,
- 'en_name' => 'Value 2',
- 'sku_value' => true,
- ],
- ]
- ],
- ];
-
- $model = new CategoryAttribute();
- $model->id = '1';
- $model->name = 'Example';
- $model->isRequired = true;
- $model->inputType = InputType::SINGLE_SELECT;
- $model->showType = ShowType::LIST_BOX;
- $model->valueType = ValueType::STRING;
- $model->isSku = false;
- $model->hasCustomizeImage = false;
- $model->hasCustomizeValue = false;
- $model->isCarModel = false;
- $model->units = [];
-
- $value1 = new CategoryAttributeValue();
- $value1->id = '11';
- $value1->name = 'Value 1';
- $value1->isSku = false;
- $value1->childAttributes = ['2', '3'];
-
- $value2 = new CategoryAttributeValue();
- $value2->id = '12';
- $value2->name = 'Value 2';
- $value2->isSku = true;
- $value2->childAttributes = [];
-
- $model->values = [$value1, $value2];
-
- $cases['list_box'] = [$data, $model];
-
- $data = [
- 'attr_id' => 1,
- 'en_name' => 'Example',
- 'required' => true,
- 'input_type' => 'input',
- 'show_type' => 'input',
- 'value_type' => 'number',
- 'sku_attribute' => false,
- 'customize_image' => false,
- 'customize_value' => false,
- 'car_model' => false,
- 'units' => ['string' => ['mm', 'cm']],
- ];
-
- $model = new CategoryAttribute();
- $model->id = '1';
- $model->name = 'Example';
- $model->isRequired = true;
- $model->inputType = InputType::INPUT;
- $model->showType = ShowType::INPUT;
- $model->valueType = ValueType::NUMBER;
- $model->isSku = false;
- $model->hasCustomizeImage = false;
- $model->hasCustomizeValue = false;
- $model->isCarModel = false;
- $model->units = ['mm', 'cm'];
- $model->values = [];
-
- $cases['input'] = [$data, $model];
-
- return $cases;
- }
-
- #[DataProvider('createAttributeValueDataProvider')]
- public function testCreateAttributeValue(array $data, CategoryAttributeValue $expected): void
- {
- $actual = $this->categoryFactory->createAttributeValue($data);
- self::assertEquals($expected, $actual);
- }
-
- public static function createAttributeValueDataProvider(): array
- {
- $cases = [];
-
- $data = [
- 'attr_value_id' => 11,
- 'en_name' => 'Value 1',
- 'sku_value' => false,
- 'child_attrs' => [
- 'number' => [2, 3]
- ]
- ];
-
- $model = new CategoryAttributeValue();
- $model->id = '11';
- $model->name = 'Value 1';
- $model->isSku = false;
- $model->childAttributes = ['2', '3'];
-
- $cases['with-children'] = [$data, $model];
-
- $data = [
- 'attr_value_id' => 11,
- 'en_name' => 'Value 1',
- 'sku_value' => false,
- ];
-
- $model = new CategoryAttributeValue();
- $model->id = '11';
- $model->name = 'Value 1';
- $model->isSku = false;
- $model->childAttributes = [];
-
- $cases['no-children'] = [$data, $model];
-
- return $cases;
- }
-
- #[DataProvider('createLevelAttributeDataProvider')]
- public function testCreateLevelAttribute(array $data, CategoryLevelAttribute $expected): void
- {
- $actual = $this->categoryFactory->createLevelAttribute($data);
- self::assertEquals($expected, $actual);
- }
-
- public static function createLevelAttributeDataProvider(): \Generator
- {
- $data = [
- 'property_id' => '123',
- 'property_en_name' => 'someName',
- 'values' => '{}'
- ];
-
- $expected = new CategoryLevelAttribute();
- $expected->id = '123';
- $expected->name = 'someName';
- $expected->values = [];
-
- yield 'no values' => [$data, $expected];
-
- $data = [
- 'property_id' => '123',
- 'property_en_name' => 'someName',
- 'values' => '[{"id":"1","name":"valueNoLeaf"},{"id":2,"name":"valueIsLeaf","leaf":true}]'
- ];
-
- $levelValueNoLeaf = new CategoryLevelAttributeValue();
- $levelValueNoLeaf->id = '1';
- $levelValueNoLeaf->name = 'valueNoLeaf';
- $levelValueNoLeaf->isLeaf = false;
-
- $levelValueIsLeaf = new CategoryLevelAttributeValue();
- $levelValueIsLeaf->id = '2';
- $levelValueIsLeaf->name = 'valueIsLeaf';
- $levelValueIsLeaf->isLeaf = true;
-
- $expected = new CategoryLevelAttribute();
- $expected->id = '123';
- $expected->name = 'someName';
- $expected->values = [$levelValueNoLeaf, $levelValueIsLeaf];
-
- yield 'with values' => [$data, $expected];
- }
-
- #[DataProvider('createLevelAttributeValueDataProvider')]
- public function testCreateLevelAttributeValue(array $data, CategoryLevelAttributeValue $expected): void
- {
- $actual = $this->categoryFactory->createLevelAttributeValue($data);
- self::assertEquals($expected, $actual);
- }
-
- public static function createLevelAttributeValueDataProvider(): \Generator
- {
- $data = [
- "id" => "1",
- "name" => "valueNoLeaf"
- ];
-
- $expected = new CategoryLevelAttributeValue();
- $expected->name = 'valueNoLeaf';
- $expected->id = '1';
- $expected->isLeaf = false;
-
- yield 'no leaf' => [$data, $expected];
-
- $data = [
- "id" => "1",
- "name" => "valueIsLeaf",
- "leaf" => true
- ];
-
- $expected = new CategoryLevelAttributeValue();
- $expected->name = 'valueIsLeaf';
- $expected->id = '1';
- $expected->isLeaf = true;
-
- yield 'is leaf' => [$data, $expected];
+ yield 'no-parents-and-children' => [$data, $model];
}
}
diff --git a/tests/Factory/TokenFactoryTest.php b/tests/Factory/TokenFactoryTest.php
index 822997b..d7ad93a 100644
--- a/tests/Factory/TokenFactoryTest.php
+++ b/tests/Factory/TokenFactoryTest.php
@@ -6,77 +6,53 @@
use Kyto\Alibaba\Factory\TokenFactory;
use Kyto\Alibaba\Model\Token;
-use PHPUnit\Framework\Attributes\DataProvider;
+use Kyto\Alibaba\Util\Clock;
+use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
class TokenFactoryTest extends TestCase
{
+ private Clock&MockObject $clock;
private TokenFactory $tokenFactory;
public function setUp(): void
{
- $this->tokenFactory = new TokenFactory();
+ $this->clock = $this->createMock(Clock::class);
+ $this->tokenFactory = new TokenFactory($this->clock);
}
public function tearDown(): void
{
unset(
+ $this->clock,
$this->tokenFactory,
);
}
- #[DataProvider('createTokenDataProvider')]
- public function testCreateToken(array $data, Token $expected): void
+ public function testCreateToken(): void
{
- $actual = $this->tokenFactory->createToken($data);
- self::assertEquals($expected, $actual);
- }
-
- public static function createTokenDataProvider(): array
- {
- $cases = [];
-
- $tokenData = [
- 'user_id' => '123',
- 'user_nick' => 'example',
- 'access_token' => 'access-token',
- 'expire_time' => 1468663236386,
- 'refresh_token' => 'refresh-token',
- 'refresh_token_valid_time' => 1469643536337,
- ];
- $tokenResult = json_encode($tokenData, JSON_THROW_ON_ERROR);
- $data = ['top_auth_token_create_response' => ['token_result' => $tokenResult]];
-
- $expected = new Token();
- $expected->userId = '123';
- $expected->userName = 'example';
- $expected->token = 'access-token';
- $expected->tokenExpireAt = (new \DateTimeImmutable())->setDate(2016, 7, 16)->setTime(10, 0, 36, 386000);
- $expected->refreshToken = 'refresh-token';
- $expected->refreshTokenExpireAt = (new \DateTimeImmutable())->setDate(2016, 7, 27)->setTime(18, 18, 56, 337000);
-
- $cases['full-result'] = [$data, $expected];
-
- $tokenData = [
- 'user_id' => '123',
+ $this->clock
+ ->method('now')
+ ->willReturn(
+ new \DateTime('2016-07-16T10:00:00'),
+ );
+
+ $data = [
+ 'account' => 'user@example.com',
'access_token' => 'access-token',
- 'expire_time' => 1468663236386,
+ 'expires_in' => 120,
'refresh_token' => 'refresh-token',
- 'refresh_token_valid_time' => 1469643536337,
+ 'refresh_expires_in' => 7200,
];
- $tokenResult = json_encode($tokenData, JSON_THROW_ON_ERROR);
- $data = ['top_auth_token_create_response' => ['token_result' => $tokenResult]];
$expected = new Token();
- $expected->userId = '123';
- $expected->userName = null;
+ $expected->account = 'user@example.com';
$expected->token = 'access-token';
- $expected->tokenExpireAt = (new \DateTimeImmutable())->setDate(2016, 7, 16)->setTime(10, 0, 36, 386000);
+ $expected->tokenExpireAt = (new \DateTimeImmutable())->setDate(2016, 7, 16)->setTime(10, 2, 0);
$expected->refreshToken = 'refresh-token';
- $expected->refreshTokenExpireAt = (new \DateTimeImmutable())->setDate(2016, 7, 27)->setTime(18, 18, 56, 337000);
-
- $cases['no-username'] = [$data, $expected];
+ $expected->refreshTokenExpireAt = (new \DateTimeImmutable())->setDate(2016, 7, 16)->setTime(11, 30, 0);
- return $cases;
+ $actual = $this->tokenFactory->createToken($data);
+ self::assertEquals($expected, $actual);
}
}