12 Commits

Author SHA1 Message Date
33a48f47de Update unit tests to allow skip if AWS API flood
on Github a lot of API tests will fail with "T001" flooding, so we just skip them
2025-01-21 11:11:56 +09:00
cf5ece0b5f github flow yml had tabs instead of spaces 2025-01-21 11:07:10 +09:00
db8df612b4 Remove phpunit from github actions 2025-01-21 11:05:58 +09:00
75f4d0b10a Add phpstan to compoer dev install 2025-01-21 11:02:55 +09:00
7fed1c2a85 Readme update 2025-01-21 10:55:11 +09:00
28a9e390cc Code clean up with declare strict and php stan level 9 checks
add "declare(strict_types=1);" to all pages

Add a json handler class to handle that json_decode always returns array<mixed> and throws error otherwise
On failure these will throw the normal JSON encoded error was FAILURE with code "J-" and the number is the json error
If 0 then the return was null or some other problem that did not return an array

amount is float and not string and checks are done that the returned value is a float

various updates for phpdoc array delcarations
- curl header is array<int, string>
- the log array is proper declared as array with string key and a list of mixed arrays
2025-01-21 10:48:41 +09:00
a565d2899b Comment typo fix 2024-08-21 11:22:33 +09:00
58b126ab83 Rename the github action job 2024-05-22 18:42:48 +09:00
e9a6332ad0 Add skip on error for funds test 2024-05-22 18:26:46 +09:00
a79fd519ed Gitbub action cache 2024-05-22 18:02:01 +09:00
04aa9fa019 Github actions add 2024-05-22 17:27:20 +09:00
d86ad8c051 Switch to PSR12 with spaces instead of tabs 2023-01-19 12:51:12 +09:00
22 changed files with 2935 additions and 2782 deletions

49
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,49 @@
name: CI
run-name: ${{ github.actor}} runs CI
on: [push]
jobs:
ci-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: php-actions/composer@v6
env:
COMPOSER_ROOT_VERSION: dev-master
- name: "Restore result cache"
uses: actions/cache/restore@v4
with:
path: ./tmp
key: "result-cache-v1-${{ matrix.php-version }}-${{ github.run_id }}"
restore-keys: |
result-cache-v1-${{ matrix.php-version }}-
- name: PHPStan Static Analysis
uses: php-actions/phpstan@v3
with:
path: src/
- name: "Save result cache"
uses: actions/cache/save@v4
if: always()
with:
path: ./tmp
key: "result-cache-v1-${{ matrix.php-version }}-${{ github.run_id }}"
# We need to use phpunit from the self install to get the class paths
# Skip auto tests here, there are too many problems with flooding of the AWS API from github
- name: PHPunit Tests Prepare
env:
AWS_GIFT_CARD_ENDPOINT: "${{ secrets.AWS_GIFT_CARD_ENDPOINT }}"
AWS_GIFT_CARD_KEY: "${{ secrets.AWS_GIFT_CARD_KEY }}"
AWS_GIFT_CARD_SECRET: "${{ secrets.AWS_GIFT_CARD_SECRET }}"
AWS_GIFT_CARD_PARTNER_ID: "${{ secrets.AWS_GIFT_CARD_PARTNER_ID }}"
AWS_GIFT_CARD_CURRENCY: "${{ secrets.AWS_GIFT_CARD_CURRENCY }}"
run: |
echo "AWS_GIFT_CARD_ENDPOINT=${AWS_GIFT_CARD_ENDPOINT}" > test/.env;
echo "AWS_GIFT_CARD_KEY=${AWS_GIFT_CARD_KEY}" >> test/.env;
echo "AWS_GIFT_CARD_SECRET=${AWS_GIFT_CARD_SECRET}" >> test/.env;
echo "AWS_GIFT_CARD_PARTNER_ID=${AWS_GIFT_CARD_PARTNER_ID}" >> test/.env;
echo "AWS_GIFT_CARD_CURRENCY=${AWS_GIFT_CARD_CURRENCY}" >> test/.env;
echo "AWS_DEBUG=1" >> test/.env;
- name: PHPunit Tests
run: |
vendor/bin/phpunit

View File

@@ -26,6 +26,7 @@
// use Phan\Config; // use Phan\Config;
return [ return [
"minimum_target_php_version" => "7.4",
// If true, missing properties will be created when // If true, missing properties will be created when
// they are first seen. If false, we'll report an // they are first seen. If false, we'll report an
// error message. // error message.
@@ -65,7 +66,7 @@ return [
'directory_list' => [ 'directory_list' => [
// Change this to include the folders you wish to analyze // Change this to include the folders you wish to analyze
// (and the folders of their dependencies) // (and the folders of their dependencies)
'.' 'src/'
// 'www', // 'www',
// To speed up analysis, we recommend going back later and // To speed up analysis, we recommend going back later and
// limiting this to only the vendor/ subdirectories your // limiting this to only the vendor/ subdirectories your

View File

@@ -127,6 +127,11 @@ fif code is C001 curl failed to init
if code is C002 a curl error has happened if code is C002 a curl error has happened
### J-number
if a JSON error was encountered during some encoding this error will be found.
The number is the json error code.
### empty error code ### empty error code
any other NON amazon error will have only 'message' set if run through decode any other NON amazon error will have only 'message' set if run through decode

View File

@@ -27,6 +27,8 @@
"require-dev": { "require-dev": {
"phpunit/phpunit": "^9", "phpunit/phpunit": "^9",
"gullevek/dotenv": "dev-master", "gullevek/dotenv": "dev-master",
"dg/bypass-finals": "dev-master" "dg/bypass-finals": "dev-master",
"phpstan/phpstan": "2.1.x-dev",
"phpstan/phpstan-deprecation-rules": "2.0.x-dev"
} }
} }

View File

@@ -1,10 +1,10 @@
# PHP Stan Config # PHP Stan Config
parameters: parameters:
tmpDir: /tmp/phpstan-codeblocks-amazon-incentives tmpDir: %currentWorkingDirectory%/tmp/phpstan-codeblocks-amazon-incentives
level: 8 level: 9
paths: paths:
- %currentWorkingDirectory% - %currentWorkingDirectory%/src
excludePaths: excludePaths:
# ignore composer # ignore composer
- vendor - vendor

View File

@@ -1,9 +1,14 @@
<phpunit <phpunit
colors="true" colors="true"
verbose="true" verbose="false"
> >
<!-- Below removes final from classes for mock tests --> <!-- Below removes final from classes for mock tests -->
<extensions> <extensions>
<extension class="test\phpUnit\Hook\BypassFinalHook" file="test/phpUnit/Hook/BypassFinalHook.php" /> <extension class="test\phpUnit\Hook\BypassFinalHook" file="test/phpUnit/Hook/BypassFinalHook.php" />
</extensions> </extensions>
<testsuites>
<testsuite name="unit">
<directory>test/phpUnit/</directory>
</testsuite>
</testsuites>
</phpunit> </phpunit>

View File

@@ -1,5 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace gullevek\AmazonIncentives\AWS; namespace gullevek\AmazonIncentives\AWS;
use gullevek\AmazonIncentives\Client\Client; use gullevek\AmazonIncentives\Client\Client;
@@ -9,6 +11,7 @@ use gullevek\AmazonIncentives\Debug\AmazonDebug;
use gullevek\AmazonIncentives\Response\CancelResponse; use gullevek\AmazonIncentives\Response\CancelResponse;
use gullevek\AmazonIncentives\Response\CreateBalanceResponse; use gullevek\AmazonIncentives\Response\CreateBalanceResponse;
use gullevek\AmazonIncentives\Response\CreateResponse; use gullevek\AmazonIncentives\Response\CreateResponse;
use gullevek\AmazonIncentives\Handle\Json;
class AWS class AWS
{ {
@@ -72,12 +75,12 @@ class AWS
$canonical_request = $this->getCanonicalRequest($service_operation, $payload); $canonical_request = $this->getCanonicalRequest($service_operation, $payload);
$date_time_string = $this->getTimestamp(); $date_time_string = $this->getTimestamp();
AmazonDebug::writeLog(['call' => __METHOD__]); AmazonDebug::writeLog(['call' => __METHOD__]);
$result = json_decode($this->makeRequest( $result = Json::jsonDecode($this->makeRequest(
$payload, $payload,
$canonical_request, $canonical_request,
$service_operation, $service_operation,
$date_time_string $date_time_string
), true); ));
return new CreateResponse($result); return new CreateResponse($result);
} }
@@ -97,12 +100,12 @@ class AWS
$canonical_request = $this->getCanonicalRequest($service_operation, $payload); $canonical_request = $this->getCanonicalRequest($service_operation, $payload);
$date_time_string = $this->getTimestamp(); $date_time_string = $this->getTimestamp();
AmazonDebug::writeLog(['call' => __METHOD__]); AmazonDebug::writeLog(['call' => __METHOD__]);
$result = json_decode($this->makeRequest( $result = Json::jsonDecode($this->makeRequest(
$payload, $payload,
$canonical_request, $canonical_request,
$service_operation, $service_operation,
$date_time_string $date_time_string
), true); ));
return new CancelResponse($result); return new CancelResponse($result);
} }
@@ -120,12 +123,12 @@ class AWS
$canonical_request = $this->getCanonicalRequest($service_operation, $payload); $canonical_request = $this->getCanonicalRequest($service_operation, $payload);
$date_time_string = $this->getTimestamp(); $date_time_string = $this->getTimestamp();
AmazonDebug::writeLog(['call' => __METHOD__]); AmazonDebug::writeLog(['call' => __METHOD__]);
$result = json_decode($this->makeRequest( $result = Json::jsonDecode($this->makeRequest(
$payload, $payload,
$canonical_request, $canonical_request,
$service_operation, $service_operation,
$date_time_string $date_time_string
), true); ));
return new CreateBalanceResponse($result); return new CreateBalanceResponse($result);
} }
@@ -212,7 +215,7 @@ class AWS
* @param string $date_time_string Ymd\THis\Z encoded timestamp, getTimestamp() * @param string $date_time_string Ymd\THis\Z encoded timestamp, getTimestamp()
* @param string $service_target Target service in the agcod string: * @param string $service_target Target service in the agcod string:
* Value like com.amazonaws.agcod.<sn>.<so> * Value like com.amazonaws.agcod.<sn>.<so>
* @return array<mixed> Header data as array for curl request * @return array<int,string> Header data as array for curl request
*/ */
public function buildHeaders( public function buildHeaders(
string $payload, string $payload,

View File

@@ -30,12 +30,12 @@ final class AmazonIncentives
* @param bool|null $debug Debug flag * @param bool|null $debug Debug flag
*/ */
public function __construct( public function __construct(
string $key = null, ?string $key = null,
string $secret = null, ?string $secret = null,
string $partner = null, ?string $partner = null,
string $endpoint = null, ?string $endpoint = null,
string $currency = null, ?string $currency = null,
bool $debug = null ?bool $debug = null
) { ) {
// load AWS settings // load AWS settings
// fail here if settings missing // fail here if settings missing
@@ -65,7 +65,7 @@ final class AmazonIncentives
* *
* @throws AmazonErrors * @throws AmazonErrors
*/ */
public function buyGiftCard(float $value, string $creation_request_id = null): Response\CreateResponse public function buyGiftCard(float $value, ?string $creation_request_id = null): Response\CreateResponse
{ {
return ($this->newAWS())->getCode($value, $creation_request_id); return ($this->newAWS())->getCode($value, $creation_request_id);
} }
@@ -109,12 +109,12 @@ final class AmazonIncentives
* @return AmazonIncentives self class * @return AmazonIncentives self class
*/ */
public static function make( public static function make(
string $key = null, ?string $key = null,
string $secret = null, ?string $secret = null,
string $partner = null, ?string $partner = null,
string $endpoint = null, ?string $endpoint = null,
string $currency = null, ?string $currency = null,
bool $debug = null ?bool $debug = null
): AmazonIncentives { ): AmazonIncentives {
return new static($key, $secret, $partner, $endpoint, $currency, $debug); return new static($key, $secret, $partner, $endpoint, $currency, $debug);
} }

View File

@@ -1,9 +1,12 @@
<?php <?php
declare(strict_types=1);
namespace gullevek\AmazonIncentives\Client; namespace gullevek\AmazonIncentives\Client;
use gullevek\AmazonIncentives\Exceptions\AmazonErrors; use gullevek\AmazonIncentives\Exceptions\AmazonErrors;
use gullevek\AmazonIncentives\Debug\AmazonDebug; use gullevek\AmazonIncentives\Debug\AmazonDebug;
use gullevek\AmazonIncentives\Handle\Json;
class Client implements ClientInterface class Client implements ClientInterface
{ {
@@ -16,7 +19,7 @@ class Client implements ClientInterface
* *
* @param string $url The URL being requested, * @param string $url The URL being requested,
* including domain and protocol * including domain and protocol
* @param array<mixed> $headers Headers to be used in the request * @param array<int,string> $headers Headers to be used in the request
* @param array<mixed>|string $params Can be nested for arrays and hashes * @param array<mixed>|string $params Can be nested for arrays and hashes
* @return string Result as json string * @return string Result as json string
*/ */
@@ -50,7 +53,12 @@ class Client implements ClientInterface
$err = curl_errno($handle); $err = curl_errno($handle);
AmazonDebug::writeLog(['CURL_REQUEST_RESULT' => $result]); AmazonDebug::writeLog(['CURL_REQUEST_RESULT' => $result]);
// extract all the error codes from Amazon // extract all the error codes from Amazon
$result_ar = json_decode((string)$result, true); // note we do not care about result errors here, if json decode fails, just set to empty
try {
$result_ar = Json::jsonDecode((string)$result);
} catch (AmazonErrors $e) {
$result_ar = [];
}
// if message is 'Rate exceeded', set different error // if message is 'Rate exceeded', set different error
if (($result_ar['message'] ?? '') == 'Rate exceeded') { if (($result_ar['message'] ?? '') == 'Rate exceeded') {
$error_status = 'RESEND'; $error_status = 'RESEND';

View File

@@ -1,5 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace gullevek\AmazonIncentives\Client; namespace gullevek\AmazonIncentives\Client;
interface ClientInterface interface ClientInterface

View File

@@ -1,5 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace gullevek\AmazonIncentives\Config; namespace gullevek\AmazonIncentives\Config;
class Config implements ConfigInterface class Config implements ConfigInterface
@@ -85,7 +87,8 @@ class Config implements ConfigInterface
case 'AWS_GIFT_CARD_PARTNER_ID': case 'AWS_GIFT_CARD_PARTNER_ID':
case 'AWS_GIFT_CARD_ENDPOINT': case 'AWS_GIFT_CARD_ENDPOINT':
case 'AWS_GIFT_CARD_CURRENCY': case 'AWS_GIFT_CARD_CURRENCY':
$return = (string)($_ENV[$key] ?? ''); $return = !empty($_ENV[$key]) && is_string($_ENV[$key]) ?
$_ENV[$key] : '';
break; break;
default: default:
break; break;

View File

@@ -1,5 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace gullevek\AmazonIncentives\Config; namespace gullevek\AmazonIncentives\Config;
interface ConfigInterface interface ConfigInterface

View File

@@ -1,5 +1,7 @@
<?php <?php
declare(strict_types=1);
// simple write all into an array that we can poll in the return group // simple write all into an array that we can poll in the return group
// to activate AmazonDebug::setDebug(true) must be called once // to activate AmazonDebug::setDebug(true) must be called once
@@ -7,7 +9,7 @@ namespace gullevek\AmazonIncentives\Debug;
class AmazonDebug class AmazonDebug
{ {
/** @var array<mixed> Log data array log id -> array of log entries */ /** @var array<string,array<array<mixed>>> Log data array log id -> array of log entries */
private static $log = []; private static $log = [];
/** @var bool debug flag */ /** @var bool debug flag */
private static $debug = false; private static $debug = false;

View File

@@ -1,5 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace gullevek\AmazonIncentives\Exceptions; namespace gullevek\AmazonIncentives\Exceptions;
use RuntimeException; use RuntimeException;
@@ -32,7 +34,7 @@ final class AmazonErrors extends RuntimeException
'code' => $error_code, 'code' => $error_code,
'type' => $error_type, 'type' => $error_type,
'message' => $message, 'message' => $message,
// atach log data if exists // attach log data if exists
'log_id' => AmazonDebug::getId(), 'log_id' => AmazonDebug::getId(),
'log' => AmazonDebug::getLog(), 'log' => AmazonDebug::getLog(),
])) ?: 'AmazonErrors: json encode problem: ' . $message, ])) ?: 'AmazonErrors: json encode problem: ' . $message,
@@ -52,7 +54,7 @@ final class AmazonErrors extends RuntimeException
{ {
$message_ar = json_decode($message, true); $message_ar = json_decode($message, true);
// if we have an error, build empty block and only fill message // if we have an error, build empty block and only fill message
if (json_last_error()) { if (json_last_error() || $message_ar === null || !is_array($message_ar)) {
$message_ar = [ $message_ar = [
'status' => '', 'status' => '',
'code' => '', 'code' => '',

42
src/Handle/Json.php Normal file
View File

@@ -0,0 +1,42 @@
<?php
/**
* Handle json conversions
*/
declare(strict_types=1);
namespace gullevek\AmazonIncentives\Handle;
use gullevek\AmazonIncentives\Exceptions\AmazonErrors;
class Json
{
/**
* decode json string
*
* @param string $json
* @return array<mixed>
* @throws AmazonErrors
*/
public static function jsonDecode(string $json): array
{
$result = json_decode($json, true);
if (
($json_last_error = json_last_error()) ||
$result === null ||
!is_array($result)
) {
throw AmazonErrors::getError(
'FAILURE',
'J-' . $json_last_error,
'jsonDecoreError',
'Failed to decode json data',
0
);
}
return $result;
}
}
// __END__

View File

@@ -1,5 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace gullevek\AmazonIncentives\Response; namespace gullevek\AmazonIncentives\Response;
use gullevek\AmazonIncentives\Debug\AmazonDebug; use gullevek\AmazonIncentives\Debug\AmazonDebug;

View File

@@ -1,13 +1,15 @@
<?php <?php
declare(strict_types=1);
namespace gullevek\AmazonIncentives\Response; namespace gullevek\AmazonIncentives\Response;
use gullevek\AmazonIncentives\Debug\AmazonDebug; use gullevek\AmazonIncentives\Debug\AmazonDebug;
class CreateBalanceResponse class CreateBalanceResponse
{ {
/** @var string Amazon Gift Card Balance Amount */ /** @var float Amazon Gift Card Balance Amount */
protected $amount = ''; protected $amount = 0;
/** @var string Amazon Gift Card Balance Currency */ /** @var string Amazon Gift Card Balance Currency */
protected $currency = ''; protected $currency = '';
/** @var string Amazon Gift Card Balance Status */ /** @var string Amazon Gift Card Balance Status */
@@ -41,9 +43,9 @@ class CreateBalanceResponse
/** /**
* Return the current available funds amount * Return the current available funds amount
* *
* @return string Funds amount in set currency * @return float Funds amount in set currency
*/ */
public function getAmount(): string public function getAmount(): float
{ {
return $this->amount; return $this->amount;
} }
@@ -98,10 +100,18 @@ class CreateBalanceResponse
*/ */
public function parseJsonResponse(array $json_response): self public function parseJsonResponse(array $json_response): self
{ {
if (array_key_exists('amount', $json_response['availableFunds'])) { if (
$this->amount = $json_response['availableFunds']['amount']; is_array($json_response['availableFunds']) &&
array_key_exists('amount', $json_response['availableFunds']) &&
is_numeric($json_response['availableFunds']['amount'])
) {
$this->amount = (float)$json_response['availableFunds']['amount'];
} }
if (array_key_exists('currencyCode', $json_response['availableFunds'])) { if (
is_array($json_response['availableFunds']) &&
array_key_exists('currencyCode', $json_response['availableFunds']) &&
is_string($json_response['availableFunds']['currencyCode'])
) {
$this->currency = $json_response['availableFunds']['currencyCode']; $this->currency = $json_response['availableFunds']['currencyCode'];
} }
// SUCCESS, FAILURE, RESEND // SUCCESS, FAILURE, RESEND

View File

@@ -1,5 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace gullevek\AmazonIncentives\Response; namespace gullevek\AmazonIncentives\Response;
use gullevek\AmazonIncentives\Debug\AmazonDebug; use gullevek\AmazonIncentives\Debug\AmazonDebug;

View File

@@ -109,7 +109,7 @@ final class AmazonIncentivesTest extends TestCase
try { try {
// set expected throw error // set expected throw error
$result = $client->request($url, [], ''); $result = $client->request($url, [], '');
print "R: " . $result . "\n"; $this->assertTrue(true, 'Successful client request');
} catch (AmazonIncentives\Exceptions\AmazonErrors $e) { } catch (AmazonIncentives\Exceptions\AmazonErrors $e) {
$curl_error = AmazonIncentives\Exceptions\AmazonErrors::decodeExceptionMessage($e->getMessage()); $curl_error = AmazonIncentives\Exceptions\AmazonErrors::decodeExceptionMessage($e->getMessage());
// print "E-B: " . print_r($curl_error, true) . "\n"; // print "E-B: " . print_r($curl_error, true) . "\n";
@@ -333,7 +333,13 @@ final class AmazonIncentivesTest extends TestCase
// - getAmount // - getAmount
// - getCurrency // - getCurrency
// - getTimestamp // - getTimestamp
try {
$funds = $agcod->getAvailableFunds(); $funds = $agcod->getAvailableFunds();
} catch (\Exception $e) {
$this->markTestSkipped(
$e->getMessage()
);
}
// if not mock do type check // if not mock do type check
// if mock do matching check from mcok // if mock do matching check from mcok
if ($mock === false) { if ($mock === false) {
@@ -345,7 +351,7 @@ final class AmazonIncentivesTest extends TestCase
// numeric number // numeric number
$this->assertIsNumeric( $this->assertIsNumeric(
$funds->getAmount(), $funds->getAmount(),
'Assert amoount is numerc' 'Assert amoount is numeric'
); );
// USD, JPY, etc // USD, JPY, etc
$this->assertIsString( $this->assertIsString(
@@ -948,6 +954,11 @@ final class AmazonIncentivesTest extends TestCase
); );
} catch (\Exception $e) { } catch (\Exception $e) {
$error = AmazonIncentives\Exceptions\AmazonErrors::decodeExceptionMessage($e->getMessage()); $error = AmazonIncentives\Exceptions\AmazonErrors::decodeExceptionMessage($e->getMessage());
if ($error['code'] == "T001") {
$this->markTestSkipped(
"Skipped because of flooding"
);
}
$this->assertEquals( $this->assertEquals(
[ [
'code' => $expected_code, 'code' => $expected_code,

2
tmp/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
*
!.gitignore