Add phpUnit testing for Amazon Incentives class
This commit is contained in:
@@ -26,6 +26,7 @@
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9",
|
||||
"gullevek/dotenv": "dev-master"
|
||||
"gullevek/dotenv": "dev-master",
|
||||
"dg/bypass-finals": "dev-master"
|
||||
}
|
||||
}
|
||||
|
||||
59
composer.lock
generated
59
composer.lock
generated
@@ -4,9 +4,63 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "412ca7fe7d62707c222169f4a7deeaec",
|
||||
"content-hash": "717f24a1578a5d25a0522cfb908b13cc",
|
||||
"packages": [],
|
||||
"packages-dev": [
|
||||
{
|
||||
"name": "dg/bypass-finals",
|
||||
"version": "dev-master",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/dg/bypass-finals.git",
|
||||
"reference": "fb62dc6ab1a097e234fa1567943d8e87ea4d0842"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/dg/bypass-finals/zipball/fb62dc6ab1a097e234fa1567943d8e87ea4d0842",
|
||||
"reference": "fb62dc6ab1a097e234fa1567943d8e87ea4d0842",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"nette/tester": "^2.3",
|
||||
"phpstan/phpstan": "^0.12"
|
||||
},
|
||||
"default-branch": true,
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"classmap": [
|
||||
"src/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-3-Clause",
|
||||
"GPL-2.0",
|
||||
"GPL-3.0"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "David Grudl",
|
||||
"homepage": "https://davidgrudl.com"
|
||||
}
|
||||
],
|
||||
"description": "Removes final keyword from source code on-the-fly and allows mocking of final methods and classes",
|
||||
"keywords": [
|
||||
"finals",
|
||||
"mocking",
|
||||
"phpunit",
|
||||
"testing",
|
||||
"unit"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/dg/bypass-finals/issues",
|
||||
"source": "https://github.com/dg/bypass-finals/tree/master"
|
||||
},
|
||||
"time": "2022-04-14T12:24:34+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/instantiator",
|
||||
"version": "1.5.x-dev",
|
||||
@@ -2090,7 +2144,8 @@
|
||||
"aliases": [],
|
||||
"minimum-stability": "dev",
|
||||
"stability-flags": {
|
||||
"gullevek/dotenv": 20
|
||||
"gullevek/dotenv": 20,
|
||||
"dg/bypass-finals": 20
|
||||
},
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
|
||||
@@ -2,4 +2,8 @@
|
||||
colors="true"
|
||||
verbose="true"
|
||||
>
|
||||
<!-- Below removes final from classes for mock tests -->
|
||||
<extensions>
|
||||
<extension class="test\phpUnit\Hook\BypassFinalHook" file="test/phpUnit/Hook/BypassFinalHook.php" />
|
||||
</extensions>
|
||||
</phpunit>
|
||||
|
||||
140
test/agcod_http_return_samples.md
Normal file
140
test/agcod_http_return_samples.md
Normal file
@@ -0,0 +1,140 @@
|
||||
# Return data from AWS as json string
|
||||
|
||||
## Mock tests self
|
||||
|
||||
Tests run in local mock tests without connection to AWS
|
||||
|
||||
### funds
|
||||
|
||||
```json
|
||||
{"availableFunds":{"amount":0.0,"currencyCode":"JPY"},"status":"SUCCESS","timestamp":"20220610T085450Z"}
|
||||
```
|
||||
|
||||
### buy
|
||||
|
||||
```json
|
||||
{"cardInfo":{"cardNumber":null,"cardStatus":"Fulfilled","expirationDate":null,"value":{"amount":1000.0,"currencyCode":"JPY"}},"creationRequestId":"PartnerId_62a309167e7a4","gcClaimCode":"LJ49-AKDUV6-UYCP","gcExpirationDate":"Thu Jun 10 14:59:59 UTC 2032","gcId":"5535125272070255","status":"SUCCESS"}
|
||||
```
|
||||
|
||||
### cancel
|
||||
|
||||
```json
|
||||
{"creationRequestId":"EG3bd_62a309167e7a4","gcId":"5535125272070255","status":"SUCCESS"}
|
||||
```
|
||||
|
||||
### buy other 1
|
||||
|
||||
```json
|
||||
{"cardInfo":{"cardNumber":null,"cardStatus":"RefundedToPurchaser","expirationDate":null,"value":{"amount":1000.0,"currencyCode":"JPY"}},"creationRequestId":"PartnerId_62a309167e7a4","gcClaimCode":"LJ49-AKDUV6-UYCP","gcExpirationDate":"Thu Jun 10 14:59:59 UTC 2032","gcId":"5535125272070255","status":"SUCCESS"}
|
||||
```
|
||||
|
||||
### buy aother 2
|
||||
|
||||
```json
|
||||
{"cardInfo":{"cardNumber":null,"cardStatus":"Fulfilled","expirationDate":null,"value":{"amount":1000.0,"currencyCode":"JPY"}},"creationRequestId":"PartnerId_62a30923e9705","gcClaimCode":"UM97-FD5QKK-WKCT","gcExpirationDate":"Thu Jun 10 14:59:59 UTC 2032","gcId":"5540334324324221","status":"SUCCESS"}
|
||||
```
|
||||
|
||||
### buy other 2 (same create request id)
|
||||
|
||||
```json
|
||||
{"cardInfo":{"cardNumber":null,"cardStatus":"Fulfilled","expirationDate":null,"value":{"amount":1000.0,"currencyCode":"JPY"}},"creationRequestId":"PartnerId_62a30923e9705","gcClaimCode":"UM97-FD5QKK-WKCT","gcExpirationDate":"Thu Jun 10 14:59:59 UTC 2032","gcId":"5540334324324221","status":"SUCCESS"}
|
||||
```
|
||||
|
||||
## AWS GCOD Mocks
|
||||
|
||||
Mocking on AWS side, these will be returned if given request ID codes are sent.
|
||||
A working test account must exist for this
|
||||
|
||||
## success
|
||||
|
||||
### buy gift card F0000
|
||||
|
||||
```json
|
||||
{"cardInfo":{"cardNumber":null,"cardStatus":"Fulfilled","expirationDate":null,"value":{"amount":500.0,"currencyCode":"JPY"}},"creationRequestId":"F0000","gcClaimCode":"ZYXW-VUTS-RQPO","gcExpirationDate":null,"gcId":"ABC123ZYX987","status":"SUCCESS"}
|
||||
```
|
||||
|
||||
## errors
|
||||
|
||||
### F1000 -> F100
|
||||
|
||||
```json
|
||||
{"agcodResponse":{"__type":"CreateGiftCardResponse:http://internal.amazon.com/coral/com.amazonaws.agcod/","cardInfo":null,"creationRequestId":null,"gcClaimCode":null,"gcExpirationDate":null,"gcId":null,"status":"FAILURE"},"errorCode":"F100","errorType":"GeneralError","message":"General Error"}
|
||||
```
|
||||
|
||||
### F2003 -> F200
|
||||
|
||||
```json
|
||||
{"agcodResponse":{"__type":"CreateGiftCardResponse:http://internal.amazon.com/coral/com.amazonaws.agcod/","cardInfo":null,"creationRequestId":null,"gcClaimCode":null,"gcExpirationDate":null,"gcId":null,"status":"FAILURE"},"errorCode":"F200","errorType":"InvalidAmountInput","message":"Amount can't be null"}
|
||||
```
|
||||
|
||||
### F2004 -> F200
|
||||
|
||||
```json
|
||||
{"agcodResponse":{"__type":"CreateGiftCardResponse:http://internal.amazon.com/coral/com.amazonaws.agcod/","cardInfo":null,"creationRequestId":null,"gcClaimCode":null,"gcExpirationDate":null,"gcId":null,"status":"FAILURE"},"errorCode":"F200","errorType":"InvalidAmountValue","message":"Amount must be larger than 0"}
|
||||
```
|
||||
|
||||
### F2005 -> F200
|
||||
|
||||
```json
|
||||
{"agcodResponse":{"__type":"CreateGiftCardResponse:http://internal.amazon.com/coral/com.amazonaws.agcod/","cardInfo":null,"creationRequestId":null,"gcClaimCode":null,"gcExpirationDate":null,"gcId":null,"status":"FAILURE"},"errorCode":"F200","errorType":"InvalidCurrencyCodeInput","message":"Currency Code can't be null or empty"}
|
||||
```
|
||||
|
||||
### F2010 -> F200
|
||||
|
||||
```json
|
||||
{"agcodResponse":{"__type":"CreateGiftCardResponse:http://internal.amazon.com/coral/com.amazonaws.agcod/","cardInfo":null,"creationRequestId":null,"gcClaimCode":null,"gcExpirationDate":null,"gcId":null,"status":"FAILURE"},"errorCode":"F200","errorType":"CardAlreadyActivatedWithDifferentRequestId","message":"The card was already activated with a different request id"}
|
||||
```
|
||||
|
||||
### F2015 -> F200
|
||||
|
||||
```json
|
||||
{"agcodResponse":{"__type":"CreateGiftCardResponse:http://internal.amazon.com/coral/com.amazonaws.agcod/","cardInfo":null,"creationRequestId":null,"gcClaimCode":null,"gcExpirationDate":null,"gcId":null,"status":"FAILURE"},"errorCode":"F200","errorType":"MaxAmountExceeded","message":"Max Amount Exceeded"}
|
||||
```
|
||||
|
||||
### F2016 -> F200
|
||||
|
||||
```json
|
||||
{"agcodResponse":{"__type":"CreateGiftCardResponse:http://internal.amazon.com/coral/com.amazonaws.agcod/","cardInfo":null,"creationRequestId":null,"gcClaimCode":null,"gcExpirationDate":null,"gcId":null,"status":"FAILURE"},"errorCode":"F200","errorType":"CurrencyCodeMismatch","message":"Currency Code Mismatch"}
|
||||
```
|
||||
|
||||
### F2017 -> F200
|
||||
|
||||
```json
|
||||
{"agcodResponse":{"__type":"CreateGiftCardResponse:http://internal.amazon.com/coral/com.amazonaws.agcod/","cardInfo":null,"creationRequestId":null,"gcClaimCode":null,"gcExpirationDate":null,"gcId":null,"status":"FAILURE"},"errorCode":"F200","errorType":"FractionalAmountNotAllowed","message":"Fractional Amount Not Allowed"}
|
||||
```
|
||||
|
||||
### F2047 -> F200
|
||||
|
||||
```json
|
||||
{"agcodResponse":{"__type":"CreateGiftCardResponse:http://internal.amazon.com/coral/com.amazonaws.agcod/","cardInfo":null,"creationRequestId":null,"gcClaimCode":null,"gcExpirationDate":null,"gcId":null,"status":"FAILURE"},"errorCode":"F200","errorType":"CancelRequestArrivedAfterTimeLimit","message":"Cancellation cannot be processed as too much time has elapsed since creation"}
|
||||
```
|
||||
|
||||
### F3003 -> F300
|
||||
|
||||
```json
|
||||
{"agcodResponse":{"__type":"CreateGiftCardResponse:http://internal.amazon.com/coral/com.amazonaws.agcod/","cardInfo":null,"creationRequestId":null,"gcClaimCode":null,"gcExpirationDate":null,"gcId":null,"status":"FAILURE"},"errorCode":"F300","errorType":"InsufficientFunds","message":"Insufficient Funds"}
|
||||
```
|
||||
|
||||
### F3005 -> F300
|
||||
|
||||
```json
|
||||
{"agcodResponse":{"__type":"CreateGiftCardResponse:http://internal.amazon.com/coral/com.amazonaws.agcod/","cardInfo":null,"creationRequestId":null,"gcClaimCode":null,"gcExpirationDate":null,"gcId":null,"status":"FAILURE"},"errorCode":"F300","errorType":"GeneralError","message":"General Error"}
|
||||
```
|
||||
|
||||
### F3010 -> F300
|
||||
|
||||
```json
|
||||
{"agcodResponse":{"__type":"CreateGiftCardResponse:http://internal.amazon.com/coral/com.amazonaws.agcod/","cardInfo":null,"creationRequestId":null,"gcClaimCode":null,"gcExpirationDate":null,"gcId":null,"status":"FAILURE"},"errorCode":"F300","errorType":"CustomerSurpassedDailyVelocityLimit","message":"Customer has exceeded daily velocity limit"}
|
||||
```
|
||||
|
||||
### F4000 -> F400
|
||||
|
||||
```json
|
||||
{"agcodResponse":{"__type":"CreateGiftCardResponse:http://internal.amazon.com/coral/com.amazonaws.agcod/","cardInfo":null,"creationRequestId":null,"gcClaimCode":null,"gcExpirationDate":null,"gcId":null,"status":"RESEND"},"errorCode":"F400","errorType":"SystemTemporarilyUnavailable","message":"System Temporarily Unavailable"}
|
||||
```
|
||||
|
||||
### F5000 -> F500
|
||||
|
||||
```json
|
||||
{"agcodResponse":{"__type":"CreateGiftCardResponse:http://internal.amazon.com/coral/com.amazonaws.agcod/","cardInfo":null,"creationRequestId":null,"gcClaimCode":null,"gcExpirationDate":null,"gcId":null,"status":"FAILURE"},"errorCode":"F500","errorType":"GeneralError","message":"General Error"}
|
||||
```
|
||||
@@ -59,6 +59,7 @@ $loader->addPsr4('gullevek\\', __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_
|
||||
// print "LOADER: <pre>" . print_r($loader, true) . "</pre>";
|
||||
|
||||
use gullevek\AmazonIncentives\AmazonIncentives;
|
||||
use gullevek\AmazonIncentives\Exceptions\AmazonErrors;
|
||||
use gullevek\dotEnv\DotEnv;
|
||||
|
||||
// load env data with dotenv
|
||||
@@ -75,30 +76,34 @@ print "<h1>Amazon Gift Card Incentives</h1><br>";
|
||||
// optional
|
||||
// debug: AWS_DEBUG (if not set: off)
|
||||
|
||||
// run info test (prints ENV vars)
|
||||
$run_info_test = !empty($_GET['info']) ? true : false;
|
||||
// run test to get funds info
|
||||
$run_fund_test = !empty($_GET['fund']) ? true : false;
|
||||
// run the normal get/cancel gift card tests
|
||||
$run_gift_tests = !empty($_GET['gift']) ? true : false;
|
||||
// run mock error check tests
|
||||
$run_mocks = !empty($_GET['mocks']) ? true : false;
|
||||
|
||||
// should we print debug info
|
||||
$debug_print = !empty($_GET['debug']) ? true : false;
|
||||
// how long to wait between each call
|
||||
$debug_wait = 2;
|
||||
// if set to true will print all the debug logs too
|
||||
$mock_debug = !empty($_GET['debug_mock']) ? true : false;
|
||||
// wait in seconds between mock tests
|
||||
$mock_wait = 2;
|
||||
|
||||
if (empty($_GET)) {
|
||||
print "<b>Use _GET parameters to start tests</b>";
|
||||
}
|
||||
|
||||
// open debug file output
|
||||
$fp = fopen('log/debug.' . date('YmdHis') . '.log', 'w');
|
||||
if (!is_resource($fp)) {
|
||||
die("Cannot open log debug file");
|
||||
}
|
||||
|
||||
// run info test (prints ENV vars)
|
||||
$run_info_test = false;
|
||||
// run test to get funds info
|
||||
$run_fund_test = true;
|
||||
// run the normal get/cancel gift card tests
|
||||
$run_gift_tests = true;
|
||||
// run mock error check tests
|
||||
$run_mocks = true;
|
||||
|
||||
// should we print debug info
|
||||
$debug_print = false;
|
||||
// how long to wait between each call
|
||||
$debug_wait = 2;
|
||||
// if set to true will print all the debug logs too
|
||||
$mock_debug = false;
|
||||
// wait in seconds between mock tests
|
||||
$mock_wait = 2;
|
||||
|
||||
if ($run_info_test === true) {
|
||||
$aws = new AmazonIncentives();
|
||||
$aws_check_me = $aws->checkMe();
|
||||
@@ -120,7 +125,7 @@ if ($run_fund_test === true) {
|
||||
}
|
||||
fwrite($fp, writeLog((array)$aws_test));
|
||||
} catch (Exception $e) {
|
||||
$error = AmazonIncentives::decodeExceptionMessage($e->getMessage());
|
||||
$error = AmazonErrors::decodeExceptionMessage($e->getMessage());
|
||||
printException('getAvailableFunds', $e->getCode(), $error, $debug_print);
|
||||
fwrite($fp, writeLog($error));
|
||||
};
|
||||
@@ -152,7 +157,7 @@ if ($run_gift_tests === true) {
|
||||
}
|
||||
fwrite($fp, writeLog((array)$aws_test));
|
||||
} catch (\Exception $e) {
|
||||
$error = AmazonIncentives::decodeExceptionMessage($e->getMessage());
|
||||
$error = AmazonErrors::decodeExceptionMessage($e->getMessage());
|
||||
printException('buyGiftCard', $e->getCode(), $error, $debug_print);
|
||||
fwrite($fp, writeLog($error));
|
||||
}
|
||||
@@ -169,7 +174,7 @@ if ($run_gift_tests === true) {
|
||||
}
|
||||
fwrite($fp, writeLog((array)$aws_test));
|
||||
} catch (\Exception $e) {
|
||||
$error = AmazonIncentives::decodeExceptionMessage($e->getMessage());
|
||||
$error = AmazonErrors::decodeExceptionMessage($e->getMessage());
|
||||
print "AWS: cancelGiftCard: " . $error['status']
|
||||
. " [" . $e->getCode() . "]: "
|
||||
. $error['code'] . " | " . $error['type']
|
||||
@@ -197,7 +202,7 @@ if ($run_gift_tests === true) {
|
||||
}
|
||||
fwrite($fp, writeLog((array)$aws_test));
|
||||
} catch (\Exception $e) {
|
||||
$error = AmazonIncentives::decodeExceptionMessage($e->getMessage());
|
||||
$error = AmazonErrors::decodeExceptionMessage($e->getMessage());
|
||||
printException('buyGiftCard', $e->getCode(), $error, $debug_print);
|
||||
fwrite($fp, writeLog($error));
|
||||
}
|
||||
@@ -221,7 +226,7 @@ if ($run_gift_tests === true) {
|
||||
}
|
||||
fwrite($fp, writeLog((array)$aws_test));
|
||||
} catch (\Exception $e) {
|
||||
$error = AmazonIncentives::decodeExceptionMessage($e->getMessage());
|
||||
$error = AmazonErrors::decodeExceptionMessage($e->getMessage());
|
||||
printException('cancelGiftCard', $e->getCode(), $error, $debug_print);
|
||||
fwrite($fp, writeLog($error));
|
||||
}
|
||||
@@ -242,7 +247,7 @@ if ($run_gift_tests === true) {
|
||||
}
|
||||
fwrite($fp, writeLog((array)$aws_test));
|
||||
} catch (\Exception $e) {
|
||||
$error = AmazonIncentives::decodeExceptionMessage($e->getMessage());
|
||||
$error = AmazonErrors::decodeExceptionMessage($e->getMessage());
|
||||
printException('buyGiftCard', $e->getCode(), $error, $debug_print);
|
||||
fwrite($fp, writeLog($error));
|
||||
}
|
||||
@@ -295,7 +300,7 @@ if ($run_mocks === true) {
|
||||
}
|
||||
fwrite($fp, writeLog((array)$aws_test));
|
||||
} catch (Exception $e) {
|
||||
$error = AmazonIncentives::decodeExceptionMessage($e->getMessage());
|
||||
$error = AmazonErrors::decodeExceptionMessage($e->getMessage());
|
||||
print "AWS: MOCK: " . $creation_id . ": buyGiftCard: " . $error['status']
|
||||
. " [" . $e->getCode() . "]: "
|
||||
. $error['code'] . " | " . $error['type']
|
||||
|
||||
@@ -2,10 +2,11 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace tests;
|
||||
namespace test\phpUnit;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use gullevek\AmazonIncentives\AmazonIncentives;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use gullevek\AmazonIncentives;
|
||||
use gullevek\dotEnv\DotEnv;
|
||||
|
||||
/**
|
||||
@@ -15,71 +16,932 @@ use gullevek\dotEnv\DotEnv;
|
||||
*/
|
||||
final class AmazonIncentivesTest extends TestCase
|
||||
{
|
||||
public function amazonIncentivesProvider(): array
|
||||
/** @var int wait tme in seconds between AWS side mock calls */
|
||||
private $mock_wait = 1;
|
||||
|
||||
/**
|
||||
* Client curl exception testing
|
||||
*
|
||||
* @testdox AWS Incentives curl exception handling
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testAwsIncentivesCurlException(): void
|
||||
{
|
||||
// this is the exceptio we want
|
||||
$this->expectException(AmazonIncentives\Exceptions\AmazonErrors::class);
|
||||
// we don't need a class here, we just need client
|
||||
$client = new AmazonIncentives\Client\Client();
|
||||
// produce any error
|
||||
$client->request('invalid', [], '');
|
||||
}
|
||||
|
||||
/**
|
||||
* curl/connection error checks
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function amazonIncentivesProviderErrors(): array
|
||||
{
|
||||
// parameter data only for this
|
||||
// 0: url
|
||||
// 1: expected status
|
||||
// 2: expected code
|
||||
// 3: expected type
|
||||
return [
|
||||
'empty' => [
|
||||
// C001
|
||||
'C002 error' => [
|
||||
'url' => 'invalid',
|
||||
'expected_status' => 'FAILURE',
|
||||
'expected_error' => 'C002',
|
||||
'expected_type' => 'CurlError'
|
||||
],
|
||||
// T001 timeout
|
||||
// 'T001 error' => [
|
||||
// 'url' => 'https://timeout.teq.jp',
|
||||
// 'expected_status' => 'RESEND',
|
||||
// 'expected_error' => 'T001',
|
||||
// 'expected_type' => 'RateExceeded'
|
||||
// ],
|
||||
// other error
|
||||
'E999 error' => [
|
||||
'url' => 'https://www.yahoo.co.jp',
|
||||
'expected_status' => 'FAILURE',
|
||||
'expected_error' => 'E999',
|
||||
'expected_type' => 'OtherUnknownError'
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test errors thrown in Client class
|
||||
*
|
||||
* @dataProvider amazonIncentivesProviderErrors
|
||||
* @testdox AWS Incentives error handling [$_dataName]
|
||||
*
|
||||
* @param string $url
|
||||
* @return void
|
||||
*/
|
||||
public function testAwsIncentivesCurlErrors(
|
||||
string $url,
|
||||
string $expected_status,
|
||||
string $expected_error,
|
||||
string $expected_type
|
||||
): void {
|
||||
// HANDLE:
|
||||
// * Init error
|
||||
// - C001/Curl init error
|
||||
// * Client errors (C002)/false:
|
||||
// - CURLE_COULDNT_CONNECT
|
||||
// - CURLE_COULDNT_RESOLVE_HOST
|
||||
// - CURLE_OPERATION_TIMEOUTED
|
||||
// - CURLE_SSL_PEER_CERTIFICATE
|
||||
// - 0/OTHER
|
||||
// * Client errors other
|
||||
// - T001/Rate exceeded
|
||||
// - E999/Other error
|
||||
|
||||
// try/catch
|
||||
// -decodeExceptionMessage (static)
|
||||
|
||||
// we don't need the full interface here, we just need client class
|
||||
$client = new AmazonIncentives\Client\Client();
|
||||
try {
|
||||
// set expected throw error
|
||||
$result = $client->request($url, [], '');
|
||||
print "R: " . $result . "\n";
|
||||
} catch (AmazonIncentives\Exceptions\AmazonErrors $e) {
|
||||
$curl_error = AmazonIncentives\Exceptions\AmazonErrors::decodeExceptionMessage($e->getMessage());
|
||||
// print "E-B: " . print_r($curl_error, true) . "\n";
|
||||
$this->assertEquals(
|
||||
$expected_status,
|
||||
$curl_error['status'],
|
||||
'Assert error status'
|
||||
);
|
||||
$this->assertEquals(
|
||||
$expected_error,
|
||||
$curl_error['code'],
|
||||
'Assert error code'
|
||||
);
|
||||
$this->assertEquals(
|
||||
$expected_type,
|
||||
$curl_error['type'],
|
||||
'Assert error type'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* init amazon incentive interface
|
||||
*
|
||||
* @param array $connect
|
||||
* @param bool $mock
|
||||
* @param array|null $mock_response
|
||||
* @return AmazonIncentives\AmazonIncentives
|
||||
*/
|
||||
private function awsIncentivesStartUp(
|
||||
array $connect,
|
||||
bool $mock,
|
||||
?array $mock_response,
|
||||
): AmazonIncentives\AmazonIncentives {
|
||||
$env_folder = $connect['env_folder'] ?? '';
|
||||
$env_file = $connect['env_file'] ?? '';
|
||||
$parameters = $connect['parameters'] ?? [];
|
||||
// reset _ENV always
|
||||
$_ENV = [];
|
||||
// env file read status
|
||||
$status = null;
|
||||
if (!empty($env_folder)) {
|
||||
if (!empty($env_file)) {
|
||||
$status = DotEnv::readEnvFile($env_folder, $env_file);
|
||||
} else {
|
||||
$status = DotEnv::readEnvFile($env_folder);
|
||||
}
|
||||
}
|
||||
|
||||
// ENV must match _ENV vars if set
|
||||
if (!empty($env_folder) && $status != 0) {
|
||||
// abort with error
|
||||
$this->markTestSkipped(
|
||||
'Cannot read .env file needed for AWS tests: ' . $status
|
||||
);
|
||||
}
|
||||
|
||||
// MOCK:
|
||||
// - for all buyGiftCard|cancelGiftCard|getAvailableFunds
|
||||
// WHAT:
|
||||
// \AWS->getCode|cancelCode|getBalance
|
||||
// -> \AWS->makeReqeust
|
||||
// -> NEW Client->request <= MOCK this
|
||||
// NOT MOCK:
|
||||
// any error calls in Client->request or exceptions
|
||||
|
||||
if ($mock === true) {
|
||||
// create a new config with or without parameters
|
||||
$agcod_config = new AmazonIncentives\Config\Config(
|
||||
$parameters['key'] ?? null,
|
||||
$parameters['secret'] ?? null,
|
||||
$parameters['partner'] ?? null,
|
||||
$parameters['endpoint'] ?? null,
|
||||
$parameters['currency'] ?? null,
|
||||
$parameters['debug'] ?? null
|
||||
);
|
||||
|
||||
// MOCK CLIENT
|
||||
// Master mock the Client class for request call
|
||||
// If we wan't to get errors thrown
|
||||
/** @var AmazonIncentives\Client\Client&MockObject */
|
||||
$client_mock = $this->createPartialMock(AmazonIncentives\Client\Client::class, ['request']);
|
||||
// set the needed return here
|
||||
$client_mock->method('request')->willReturn(json_encode($mock_response));
|
||||
|
||||
// MOCK AWS and attache above class in client return
|
||||
/** @var AmazonIncentives\AWS\AWS&MockObject */
|
||||
$aws_mock = $this->getMockBuilder(AmazonIncentives\AWS\AWS::class)
|
||||
->setConstructorArgs([$agcod_config])
|
||||
->onlyMethods(['newClient'])
|
||||
->getMock();
|
||||
// attach mocked client
|
||||
$aws_mock->method('newClient')->willReturn($client_mock);
|
||||
|
||||
// MOCK AMAZONINCENTIVES
|
||||
/** @var AmazonIncentives\AmazonIncentives&MockObject */
|
||||
$agcod = $this->getMockBuilder(AmazonIncentives\AmazonIncentives::class)
|
||||
->setConstructorArgs([
|
||||
$parameters['key'] ?? null,
|
||||
$parameters['secret'] ?? null,
|
||||
$parameters['partner'] ?? null,
|
||||
$parameters['endpoint'] ?? null,
|
||||
$parameters['currency'] ?? null,
|
||||
$parameters['debug'] ?? null
|
||||
])
|
||||
->onlyMethods(['newAWS'])
|
||||
->getMock();
|
||||
// attach mocked AWS class
|
||||
$agcod->method('newAWS')->willReturn($aws_mock);
|
||||
} else {
|
||||
// if we mock, we mock the Client->request
|
||||
$agcod = new AmazonIncentives\AmazonIncentives(
|
||||
$parameters['key'] ?? null,
|
||||
$parameters['secret'] ?? null,
|
||||
$parameters['partner'] ?? null,
|
||||
$parameters['endpoint'] ?? null,
|
||||
$parameters['currency'] ?? null,
|
||||
$parameters['debug'] ?? null
|
||||
);
|
||||
}
|
||||
|
||||
return $agcod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds the configs for loading data from .env for parameter
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function awsIncentivesProvider(): array
|
||||
{
|
||||
// 0: .env file folder
|
||||
// 1: .env file name (if not set use .env)
|
||||
// 2: parameters that override _ENV variables
|
||||
return [
|
||||
// this is with real test account data
|
||||
'env_test' => [
|
||||
'env_folder' => __DIR__ . DIRECTORY_SEPARATOR . '..',
|
||||
'env_file' => null,
|
||||
'parameters' => null
|
||||
],
|
||||
// this is for mocking only
|
||||
'parameter_dummy' => [
|
||||
'env_folder' => null,
|
||||
'env_file' => null,
|
||||
]
|
||||
'parameters' => [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
'http://i.dont.exist.at.all',
|
||||
'JPY'
|
||||
]
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
* @dataProvider amazonIncentivesProvider
|
||||
* @testdox AmazonIncentives tests [$_dataName]
|
||||
* @return array
|
||||
*/
|
||||
public function amazonIncentivesProviderGetFunds(): array
|
||||
{
|
||||
// remove final keyword
|
||||
// BypassFinals::enable();
|
||||
// get connectors
|
||||
$connectors = $this->awsIncentivesProvider();
|
||||
// 0: connect array (env file, env folder, parameters array)
|
||||
// 1: mock or normal call
|
||||
// 2: if mock connect response must be defined here
|
||||
// 3: exepcted response array
|
||||
return [
|
||||
'non mock test data' => [
|
||||
'connect' => $connectors['env_test'],
|
||||
'mock' => false,
|
||||
'mock_response' => null,
|
||||
'expected' => [
|
||||
//
|
||||
]
|
||||
],
|
||||
'mock data test' => [
|
||||
'connect' => $connectors['parameter_dummy'],
|
||||
'mock' => true,
|
||||
'mock_response' => [
|
||||
'availableFunds' => [
|
||||
'amount' => 0.0,
|
||||
'currencyCode' => 'JPY',
|
||||
],
|
||||
'status' => 'SUCCESS',
|
||||
'timestamp' => '20220610T085450Z',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
* @dataProvider amazonIncentivesProviderGetFunds
|
||||
* @testdox AWS Incentives get available funds [$_dataName]
|
||||
*
|
||||
* @param array $connect
|
||||
* @param bool $mock
|
||||
* @param array|null $mock_response
|
||||
* @return void
|
||||
*/
|
||||
public function testAmazonIncentives(?string $env_folder, ?string $env_file): void
|
||||
public function testAwsIncentivesGetAvailableFunds(
|
||||
array $connect,
|
||||
bool $mock,
|
||||
?array $mock_response
|
||||
): void {
|
||||
// load class
|
||||
$agcod = $this->awsIncentivesStartUp(
|
||||
$connect,
|
||||
$mock,
|
||||
$mock_response,
|
||||
);
|
||||
|
||||
// - getAvailableFunds: get available fund
|
||||
// - getStatus
|
||||
// - getAmount
|
||||
// - getCurrency
|
||||
// - getTimestamp
|
||||
$funds = $agcod->getAvailableFunds();
|
||||
// if not mock do type check
|
||||
// if mock do matching check from mcok
|
||||
if ($mock === false) {
|
||||
$this->assertEquals(
|
||||
'SUCCESS',
|
||||
$funds->getStatus(),
|
||||
'Assert status is success'
|
||||
);
|
||||
// numeric number
|
||||
$this->assertIsNumeric(
|
||||
$funds->getAmount(),
|
||||
'Assert amoount is numerc'
|
||||
);
|
||||
// USD, JPY, etc
|
||||
$this->assertIsString(
|
||||
$funds->getCurrency(),
|
||||
'Assert currency is string'
|
||||
);
|
||||
// 20220610T085450Z
|
||||
$this->assertMatchesRegularExpression(
|
||||
"/^\d{8}T\d{6}Z$/",
|
||||
$funds->getTimestamp(),
|
||||
'Assert timestamp matches regex'
|
||||
);
|
||||
} else {
|
||||
$this->assertEquals(
|
||||
$mock_response['status'],
|
||||
$funds->getStatus(),
|
||||
'Assert mock status'
|
||||
);
|
||||
$this->assertEquals(
|
||||
$mock_response['availableFunds']['amount'],
|
||||
$funds->getAmount(),
|
||||
'Assert mock amount'
|
||||
);
|
||||
$this->assertEquals(
|
||||
$mock_response['availableFunds']['currencyCode'],
|
||||
$funds->getCurrency(),
|
||||
'Assert mock currency code'
|
||||
);
|
||||
$this->assertEquals(
|
||||
$mock_response['timestamp'],
|
||||
$funds->getTimestamp(),
|
||||
'Assert mock timestamp'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function amazonIncentivesProviderBuy(): array
|
||||
{
|
||||
// get connectors
|
||||
$connectors = $this->awsIncentivesProvider();
|
||||
// 0: connect array (env file, env folder, parameters array)
|
||||
// 1: mock or normal call
|
||||
// 2: if mock connect response must be defined here
|
||||
// 3: exepcted response array
|
||||
// 4: value in float
|
||||
return [
|
||||
'non mock test data' => [
|
||||
'connect' => $connectors['env_test'],
|
||||
'mock' => false,
|
||||
'mock_response' => null,
|
||||
'amount' => 500.0,
|
||||
],
|
||||
'mock data test' => [
|
||||
'connect' => $connectors['parameter_dummy'],
|
||||
'mock' => true,
|
||||
'mock_response' => [
|
||||
'cardInfo' => [
|
||||
'cardNumber' => null,
|
||||
'cardStatus' => 'Fulfilled',
|
||||
'expirationDate' => null,
|
||||
'value' => [
|
||||
'amount' => 1000.0,
|
||||
'currencyCode' => 'JPY',
|
||||
],
|
||||
],
|
||||
'creationRequestId' => 'PartnerId_62a309167e7a4',
|
||||
'gcClaimCode' => 'LJ49-AKDUV6-UYCP',
|
||||
'gcExpirationDate' => 'Thu Jun 10 14:59:59 UTC 2032',
|
||||
'gcId' => '5535125272070255',
|
||||
'status' => 'SUCCESS',
|
||||
],
|
||||
'amount' => 1000.0,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
* @dataProvider amazonIncentivesProviderBuy
|
||||
* @testdox AWS Incentives buy gift card [$_dataName]
|
||||
*
|
||||
* @param array $connect
|
||||
* @param bool $mock
|
||||
* @param array|null $mock_response
|
||||
* @param float $amount
|
||||
* @return void
|
||||
*/
|
||||
public function testAwsIncentivesBuyGiftCard(
|
||||
array $connect,
|
||||
bool $mock,
|
||||
?array $mock_response,
|
||||
float $amount
|
||||
): void {
|
||||
// - init plain
|
||||
// * via ::make()
|
||||
|
||||
// - buyGiftCard: buy gift card
|
||||
// - getCreationRequestId
|
||||
// - getId
|
||||
// - getClaimCode
|
||||
// - getExpirationDate
|
||||
// - getStatus
|
||||
// - cancelGiftCard: cancel gift card
|
||||
// - getAvailableFunds: get available fund
|
||||
// - getAmount
|
||||
// - getCurrency
|
||||
// - getTimestamp
|
||||
|
||||
// try/catch
|
||||
// -decodeExceptionMessage (static)
|
||||
$this->markTestSkipped('Not yet implemented: AmazonIncentives');
|
||||
// load class
|
||||
$agcod = $this->awsIncentivesStartUp(
|
||||
$connect,
|
||||
$mock,
|
||||
$mock_response,
|
||||
);
|
||||
|
||||
$response = $agcod->buyGiftCard($amount);
|
||||
|
||||
if ($mock === false) {
|
||||
// type check
|
||||
$this->assertEquals(
|
||||
'SUCCESS',
|
||||
$response->getStatus(),
|
||||
'Assert status'
|
||||
);
|
||||
// creation request id must start with partner id
|
||||
$this->assertStringStartsWith(
|
||||
$agcod->checkMe()['CONFIG']->getPartner(),
|
||||
$response->getCreationRequestId(),
|
||||
'Assert creation request id starts with partner id'
|
||||
);
|
||||
// gift card id is number
|
||||
$this->assertIsNumeric(
|
||||
$response->getId(),
|
||||
'Assert gift card id is numeric'
|
||||
);
|
||||
// claim code is 4-6-4 alphanumeric
|
||||
$this->assertIsString(
|
||||
$response->getClaimCode(),
|
||||
'Assert claim code is string'
|
||||
);
|
||||
// only for requests outside US/Australia cards
|
||||
// expiration date: Thu Jun 10 14:59:59 UTC 2032
|
||||
} else {
|
||||
// value match to mock response
|
||||
$this->assertEquals(
|
||||
$mock_response['status'],
|
||||
$response->getStatus(),
|
||||
'Assert mock status'
|
||||
);
|
||||
$this->assertEquals(
|
||||
$mock_response['creationRequestId'],
|
||||
$response->getCreationRequestId(),
|
||||
'Assert mock creation request id'
|
||||
);
|
||||
$this->assertEquals(
|
||||
$mock_response['gcId'],
|
||||
$response->getId(),
|
||||
'Assert mock gift card id'
|
||||
);
|
||||
$this->assertEquals(
|
||||
$mock_response['gcClaimCode'],
|
||||
$response->getClaimCode(),
|
||||
'Assert mock claim code'
|
||||
);
|
||||
$this->assertEquals(
|
||||
$mock_response['gcExpirationDate'],
|
||||
$response->getExpirationDate(),
|
||||
'Assert mock expiration date'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function checkMeProvider(): array
|
||||
/**
|
||||
* Buy a gift card and use same creation request id to get another gift card
|
||||
* has to return same data ggain
|
||||
*
|
||||
* @dataProvider amazonIncentivesProviderBuy
|
||||
* @testdox AWS Incentives buy gift card and again with same creation request id [$_dataName]
|
||||
*
|
||||
* @param array $connect
|
||||
* @param bool $mock
|
||||
* @param array|null $mock_response
|
||||
* @param float $amount
|
||||
* @return void
|
||||
*/
|
||||
public function testAwsIncentivesSameBuyGiftCard(
|
||||
array $connect,
|
||||
bool $mock,
|
||||
?array $mock_response,
|
||||
float $amount
|
||||
): void {
|
||||
// load class
|
||||
$agcod = $this->awsIncentivesStartUp(
|
||||
$connect,
|
||||
$mock,
|
||||
$mock_response,
|
||||
);
|
||||
// get one
|
||||
$response_a = $agcod->buyGiftCard($amount);
|
||||
// get one again with same code
|
||||
$response_b = $agcod->buyGiftCard($amount, $response_a->getCreationRequestId());
|
||||
|
||||
// a and b must be equalt
|
||||
$this->assertEquals(
|
||||
$response_a->getStatus(),
|
||||
$response_b->getStatus(),
|
||||
'Assert status'
|
||||
);
|
||||
$this->assertEquals(
|
||||
$response_a->getCreationRequestId(),
|
||||
$response_b->getCreationRequestId(),
|
||||
'Assert creation request id'
|
||||
);
|
||||
$this->assertEquals(
|
||||
$response_a->getId(),
|
||||
$response_b->getId(),
|
||||
'Assert gift card id'
|
||||
);
|
||||
$this->assertEquals(
|
||||
$response_a->getClaimCode(),
|
||||
$response_b->getClaimCode(),
|
||||
'Assert claim code'
|
||||
);
|
||||
$this->assertEquals(
|
||||
$response_a->getExpirationDate(),
|
||||
$response_b->getExpirationDate(),
|
||||
'Assert expiration date'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function amazonIncentivesProviderCancel(): array
|
||||
{
|
||||
// get connectors
|
||||
$connectors = $this->awsIncentivesProvider();
|
||||
// 0: connect array (env file, env folder, parameters array)
|
||||
// 1: mock or normal call
|
||||
// 2: if mock connect response must be defined here
|
||||
// 3: exepcted response array
|
||||
return [
|
||||
'default' => [
|
||||
'env_folder' => null,
|
||||
'env_file' => null,
|
||||
'expected' => [],
|
||||
]
|
||||
'non mock test data' => [
|
||||
'connect' => $connectors['env_test'],
|
||||
'mock' => false,
|
||||
'mock_response' => null,
|
||||
],
|
||||
'mock data test' => [
|
||||
'connect' => $connectors['parameter_dummy'],
|
||||
'mock' => true,
|
||||
'mock_response' => [
|
||||
'creationRequestId' => 'PartnerId_62a309167e7a4',
|
||||
'gcId' => '5535125272070255',
|
||||
'status' => 'SUCCESS',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
* @dataProvider amazonIncentivesProviderCancel
|
||||
* @testdox AWS Incentives cancel gift card [$_dataName]
|
||||
*
|
||||
* @param array $connect
|
||||
* @param bool $mock
|
||||
* @param array|null $mock_response
|
||||
* @return void
|
||||
*/
|
||||
public function testAwsIncentivesCancelGiftCard(
|
||||
array $connect,
|
||||
bool $mock,
|
||||
?array $mock_response
|
||||
): void {
|
||||
// - cancelGiftCard: cancel gift card
|
||||
// load class
|
||||
$agcod = $this->awsIncentivesStartUp(
|
||||
$connect,
|
||||
$mock,
|
||||
$mock_response,
|
||||
);
|
||||
|
||||
if ($mock === false) {
|
||||
// get a gift card, then cancel it
|
||||
$purchase = $agcod->buyGiftCard(500.0);
|
||||
$response = $agcod->cancelGiftCard(
|
||||
$purchase->getCreationRequestId(),
|
||||
$purchase->getId()
|
||||
);
|
||||
$this->assertEquals(
|
||||
'SUCCESS',
|
||||
$response->getStatus(),
|
||||
'Assert mock status'
|
||||
);
|
||||
// creation request id must start with partner id
|
||||
$this->assertStringStartsWith(
|
||||
$agcod->checkMe()['CONFIG']->getPartner(),
|
||||
$response->getCreationRequestId(),
|
||||
'Assert creation request id starts with partner id'
|
||||
);
|
||||
// gift card id is number
|
||||
$this->assertIsNumeric(
|
||||
$response->getId(),
|
||||
'Assert gift card id is numeric'
|
||||
);
|
||||
} else {
|
||||
$response = $agcod->cancelGiftCard(
|
||||
$mock_response['creationRequestId'],
|
||||
$mock_response['gcId']
|
||||
);
|
||||
$this->assertEquals(
|
||||
$mock_response['status'],
|
||||
$response->getStatus(),
|
||||
'Assert mock status'
|
||||
);
|
||||
$this->assertEquals(
|
||||
$mock_response['creationRequestId'],
|
||||
$response->getCreationRequestId(),
|
||||
'Assert mock creation request id'
|
||||
);
|
||||
$this->assertEquals(
|
||||
$mock_response['gcId'],
|
||||
$response->getId(),
|
||||
'Assert mock gift card id'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* list of AWS mock codes for AWS side mock testing
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function awsIncentivesMockProvider(): array
|
||||
{
|
||||
return [
|
||||
'successMock' => [
|
||||
'creation_request_id' => 'F0000',
|
||||
'return_code' => '',
|
||||
'status' => 'SUCCESS'
|
||||
],
|
||||
'SimpleAmountIsNull' => [
|
||||
'creation_request_id' => 'F1000',
|
||||
'return_code' => 'F100',
|
||||
'status' => 'FAILURE'
|
||||
],
|
||||
'InvalidAmountInput' => [
|
||||
'creation_request_id' => 'F2003',
|
||||
'return_code' => 'F200',
|
||||
'status' => 'FAILURE'
|
||||
],
|
||||
'InvalidAmountValue' => [
|
||||
'creation_request_id' => 'F2004',
|
||||
'return_code' => 'F200',
|
||||
'status' => 'FAILURE'
|
||||
],
|
||||
'InvalidCurrencyCodeInput' => [
|
||||
'creation_request_id' => 'F2005',
|
||||
'return_code' => 'F200',
|
||||
'status' => 'FAILURE'
|
||||
],
|
||||
'CardActivatedWithDifferentRequestId' => [
|
||||
'creation_request_id' => 'F2010',
|
||||
'return_code' => 'F200',
|
||||
'status' => 'FAILURE'
|
||||
],
|
||||
'MaxAmountExceeded' => [
|
||||
'creation_request_id' => 'F2015',
|
||||
'return_code' => 'F200',
|
||||
'status' => 'FAILURE'
|
||||
],
|
||||
'CurrencyCodeMismatch' => [
|
||||
'creation_request_id' => 'F2016',
|
||||
'return_code' => 'F200',
|
||||
'status' => 'FAILURE'
|
||||
],
|
||||
'FractionalAmountNotAllowed' => [
|
||||
'creation_request_id' => 'F2017',
|
||||
'return_code' => 'F200',
|
||||
'status' => 'FAILURE'
|
||||
],
|
||||
'CancelRequestArrivedAfterTimeLimit' => [
|
||||
'creation_request_id' => 'F2047',
|
||||
'return_code' => 'F200',
|
||||
'status' => 'FAILURE'
|
||||
],
|
||||
'InsufficientFunds' => [
|
||||
'creation_request_id' => 'F3003',
|
||||
'return_code' => 'F300',
|
||||
'status' => 'FAILURE'
|
||||
],
|
||||
'AccountHasProblems' => [
|
||||
'creation_request_id' => 'F3005',
|
||||
'return_code' => 'F300',
|
||||
'status' => 'FAILURE'
|
||||
],
|
||||
'CustomerSurpassedDailyVelocityLimit' => [
|
||||
'creation_request_id' => 'F3010',
|
||||
'return_code' => 'F300',
|
||||
'status' => 'FAILURE'
|
||||
],
|
||||
'SystemTemporarilyUnavailable' => [
|
||||
'creation_request_id' => 'F4000',
|
||||
'return_code' => 'F400',
|
||||
'status' => 'RESEND'
|
||||
],
|
||||
'UnknownError' => [
|
||||
'creation_request_id' => 'F5000',
|
||||
'return_code' => 'F500',
|
||||
'status' => 'FAILURE'
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* NOTE: Must have a valid test user connection setup
|
||||
* This only works with a valid server connection.
|
||||
* Runs through AWS Incentives mock values and checks the return code and status
|
||||
*
|
||||
* @dataProvider awsIncentivesMockProvider
|
||||
* @testdox AWS Incentives Mock $creation_request_id will be $expected_status with $expected_code [$_dataName]
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testAwsIncentivesWithMocks(
|
||||
string $creation_request_id,
|
||||
string $expected_code,
|
||||
string $expected_status,
|
||||
): void {
|
||||
// reset _ENV for reading
|
||||
$_ENV = [];
|
||||
// read the .env file
|
||||
$status = DotEnv::readEnvFile(__DIR__ . DIRECTORY_SEPARATOR . '..');
|
||||
// if loading failed, abort
|
||||
if ($status != 0) {
|
||||
// abort with error
|
||||
$this->markTestSkipped(
|
||||
'Cannot read .env file needed for AWS mock tests: ' . $status
|
||||
);
|
||||
}
|
||||
// if no value set, set to 500
|
||||
$value = $_ENV['AWS_MOCK_VALUE'] ?? 500;
|
||||
// run tests
|
||||
try {
|
||||
$aws_gcod = AmazonIncentives\AmazonIncentives::make()->buyGiftCard(
|
||||
(float)$value,
|
||||
$creation_request_id
|
||||
);
|
||||
$this->assertEquals(
|
||||
$expected_status,
|
||||
$aws_gcod->getStatus(),
|
||||
'Assert status ok in AWS GCOD mocks'
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
$error = AmazonIncentives\Exceptions\AmazonErrors::decodeExceptionMessage($e->getMessage());
|
||||
$this->assertEquals(
|
||||
[
|
||||
'code' => $expected_code,
|
||||
'status' => $expected_status,
|
||||
],
|
||||
[
|
||||
'code' => $error['code'],
|
||||
'status' => $error['status'],
|
||||
],
|
||||
'Assert status failed in AWS GCOD mocks'
|
||||
);
|
||||
}
|
||||
// wait a moment between tests
|
||||
sleep($this->mock_wait);
|
||||
}
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function checkMeProvider(): array
|
||||
{
|
||||
// 0: .env file folder
|
||||
// 1: .env file name (if not set use .env)
|
||||
// 2: parameters that override _ENV variables
|
||||
return [
|
||||
'default all empty' => [
|
||||
'use_env' => null,
|
||||
'env_file' => null,
|
||||
'parameters' => null,
|
||||
],
|
||||
'set parameters' => [
|
||||
'env_folder' => null,
|
||||
'env_file' => null,
|
||||
'parameters' => [
|
||||
'key' => 'key',
|
||||
'secret' => 'secret',
|
||||
'partner' => 'partner id',
|
||||
'endpoint' => 'https://endpoint.test.com',
|
||||
'currency' => 'currency',
|
||||
'debug' => true,
|
||||
],
|
||||
'expected' => [],
|
||||
],
|
||||
'load from env' => [
|
||||
'env_folder' => __DIR__ . DIRECTORY_SEPARATOR . '..',
|
||||
'env_file' => null,
|
||||
'parameters' => null,
|
||||
],
|
||||
'load from env, but override parameter' => [
|
||||
'env_folder' => __DIR__ . DIRECTORY_SEPARATOR . '..',
|
||||
'env_file' => null,
|
||||
'parameters' => [
|
||||
'key' => 'key',
|
||||
'secret' => 'secret',
|
||||
'partner' => 'partner id',
|
||||
'endpoint' => 'https://endpoint.test.com',
|
||||
'currency' => 'currency',
|
||||
]
|
||||
]
|
||||
// test missing parameter, set vie _ENV
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the checkMe function that will work with or without any settings
|
||||
* passed on.
|
||||
* This also tests basic loading
|
||||
* - parseing for endoint as url
|
||||
* - override check for _ENV vs parameter
|
||||
*
|
||||
* @cover ::checkMe
|
||||
* @dataProvider checkMeProvider
|
||||
* @testdox AmazonIncentives tests [$_dataName]
|
||||
*
|
||||
* @param string|null $env_folder
|
||||
* @param string|null $env_file
|
||||
* @param array|null $parameters
|
||||
* @return void
|
||||
*/
|
||||
public function testCheckMe(?string $env_folder, ?string $env_file, array $expected): void
|
||||
public function testCheckMe(?string $env_folder, ?string $env_file, ?array $parameters): void
|
||||
{
|
||||
$aws = new AmazonIncentives();
|
||||
// reset _ENV before each run to avoid nothing to load errors
|
||||
$_ENV = [];
|
||||
// env load status
|
||||
$status = null;
|
||||
if (!empty($env_folder)) {
|
||||
if (!empty($env_file)) {
|
||||
$status = DotEnv::readEnvFile($env_folder, $env_file);
|
||||
} else {
|
||||
$status = DotEnv::readEnvFile($env_folder);
|
||||
}
|
||||
}
|
||||
if (!empty($parameters)) {
|
||||
$aws = new AmazonIncentives\AmazonIncentives(
|
||||
$parameters['key'],
|
||||
$parameters['secret'],
|
||||
$parameters['partner'],
|
||||
$parameters['endpoint'],
|
||||
$parameters['currency'],
|
||||
$parameters['debug'] ?? null,
|
||||
);
|
||||
} else {
|
||||
$aws = new AmazonIncentives\AmazonIncentives();
|
||||
}
|
||||
$aws_check_me = $aws->checkMe();
|
||||
// ENV must match _ENV vars if set
|
||||
if (!empty($env_folder) && $status != 0) {
|
||||
// abort with error
|
||||
$this->markTestSkipped(
|
||||
'Cannot read .env file needed: ' . $status
|
||||
);
|
||||
} elseif (!empty($env_folder)) {
|
||||
$this->assertEquals(
|
||||
$_ENV,
|
||||
$aws_check_me['ENV'],
|
||||
'Assert _ENV set equal'
|
||||
);
|
||||
}
|
||||
// compare that data matches
|
||||
print "CM: " . print_r($aws_check_me, true) . "\n";
|
||||
// print "CM: " . print_r($aws_check_me, true) . "\n";
|
||||
// CONFIG must match to parameters or ENV, parsed host name check
|
||||
$this->assertEquals(
|
||||
// parameter > _ENV -> empty
|
||||
!empty($parameters['partner']) ?
|
||||
$parameters['partner'] :
|
||||
$_ENV['AWS_GIFT_CARD_PARTNER_ID'] ?? '',
|
||||
$aws_check_me['CONFIG']->getPartner(),
|
||||
'Assert config matching input'
|
||||
);
|
||||
// KEY must match access_key/AWS_GIFT_CARD_KEY
|
||||
$this->assertEquals(
|
||||
$aws_check_me['CONFIG']->getAccessKey(),
|
||||
$aws_check_me['KEY'],
|
||||
'Assert access key m'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
21
test/phpUnit/Hook/BypassFinalHook.php
Normal file
21
test/phpUnit/Hook/BypassFinalHook.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
// strip the final name from a to be mocked class
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace test\phpUnit\Hook;
|
||||
|
||||
use DG\BypassFinals;
|
||||
use PHPUnit\Runner\BeforeFirstTestHook;
|
||||
|
||||
// only works if it is the FIRST load and not before EACH test
|
||||
final class BypassFinalHook implements BeforeFirstTestHook
|
||||
{
|
||||
public function executeBeforeFirstTest(): void
|
||||
{
|
||||
BypassFinals::enable();
|
||||
}
|
||||
}
|
||||
|
||||
// __END__
|
||||
1
vendor/composer/autoload_classmap.php
vendored
1
vendor/composer/autoload_classmap.php
vendored
@@ -7,6 +7,7 @@ $baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
|
||||
'DG\\BypassFinals' => $vendorDir . '/dg/bypass-finals/src/BypassFinals.php',
|
||||
'PHPUnit\\Exception' => $vendorDir . '/phpunit/phpunit/src/Exception.php',
|
||||
'PHPUnit\\Framework\\ActualValueIsNotAnObjectException' => $vendorDir . '/phpunit/phpunit/src/Framework/Exception/ActualValueIsNotAnObjectException.php',
|
||||
'PHPUnit\\Framework\\Assert' => $vendorDir . '/phpunit/phpunit/src/Framework/Assert.php',
|
||||
|
||||
1
vendor/composer/autoload_static.php
vendored
1
vendor/composer/autoload_static.php
vendored
@@ -76,6 +76,7 @@ class ComposerStaticInit0c8f6bec90a6d60040a922f19a1f0e64
|
||||
|
||||
public static $classMap = array (
|
||||
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
|
||||
'DG\\BypassFinals' => __DIR__ . '/..' . '/dg/bypass-finals/src/BypassFinals.php',
|
||||
'PHPUnit\\Exception' => __DIR__ . '/..' . '/phpunit/phpunit/src/Exception.php',
|
||||
'PHPUnit\\Framework\\ActualValueIsNotAnObjectException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Exception/ActualValueIsNotAnObjectException.php',
|
||||
'PHPUnit\\Framework\\Assert' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Assert.php',
|
||||
|
||||
58
vendor/composer/installed.json
vendored
58
vendor/composer/installed.json
vendored
@@ -1,5 +1,62 @@
|
||||
{
|
||||
"packages": [
|
||||
{
|
||||
"name": "dg/bypass-finals",
|
||||
"version": "dev-master",
|
||||
"version_normalized": "dev-master",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/dg/bypass-finals.git",
|
||||
"reference": "fb62dc6ab1a097e234fa1567943d8e87ea4d0842"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/dg/bypass-finals/zipball/fb62dc6ab1a097e234fa1567943d8e87ea4d0842",
|
||||
"reference": "fb62dc6ab1a097e234fa1567943d8e87ea4d0842",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"nette/tester": "^2.3",
|
||||
"phpstan/phpstan": "^0.12"
|
||||
},
|
||||
"time": "2022-04-14T12:24:34+00:00",
|
||||
"default-branch": true,
|
||||
"type": "library",
|
||||
"installation-source": "dist",
|
||||
"autoload": {
|
||||
"classmap": [
|
||||
"src/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-3-Clause",
|
||||
"GPL-2.0",
|
||||
"GPL-3.0"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "David Grudl",
|
||||
"homepage": "https://davidgrudl.com"
|
||||
}
|
||||
],
|
||||
"description": "Removes final keyword from source code on-the-fly and allows mocking of final methods and classes",
|
||||
"keywords": [
|
||||
"finals",
|
||||
"mocking",
|
||||
"phpunit",
|
||||
"testing",
|
||||
"unit"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/dg/bypass-finals/issues",
|
||||
"source": "https://github.com/dg/bypass-finals/tree/master"
|
||||
},
|
||||
"install-path": "../dg/bypass-finals"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/instantiator",
|
||||
"version": "1.5.x-dev",
|
||||
@@ -2184,6 +2241,7 @@
|
||||
],
|
||||
"dev": true,
|
||||
"dev-package-names": [
|
||||
"dg/bypass-finals",
|
||||
"doctrine/instantiator",
|
||||
"gullevek/dotenv",
|
||||
"myclabs/deep-copy",
|
||||
|
||||
15
vendor/composer/installed.php
vendored
15
vendor/composer/installed.php
vendored
@@ -5,11 +5,22 @@
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../../',
|
||||
'aliases' => array(),
|
||||
'reference' => '1e836e9a2bd6e41c4555cf917fe645b453cb5b79',
|
||||
'reference' => 'fd5477269b9a133448cecb5854b784033d6c6c62',
|
||||
'name' => 'gullevek/amazon-incentives',
|
||||
'dev' => true,
|
||||
),
|
||||
'versions' => array(
|
||||
'dg/bypass-finals' => array(
|
||||
'pretty_version' => 'dev-master',
|
||||
'version' => 'dev-master',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../dg/bypass-finals',
|
||||
'aliases' => array(
|
||||
0 => '9999999-dev',
|
||||
),
|
||||
'reference' => 'fb62dc6ab1a097e234fa1567943d8e87ea4d0842',
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
'doctrine/instantiator' => array(
|
||||
'pretty_version' => '1.5.x-dev',
|
||||
'version' => '1.5.9999999.9999999-dev',
|
||||
@@ -25,7 +36,7 @@
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../../',
|
||||
'aliases' => array(),
|
||||
'reference' => '1e836e9a2bd6e41c4555cf917fe645b453cb5b79',
|
||||
'reference' => 'fd5477269b9a133448cecb5854b784033d6c6c62',
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'gullevek/dotenv' => array(
|
||||
|
||||
26
vendor/dg/bypass-finals/composer.json
vendored
Normal file
26
vendor/dg/bypass-finals/composer.json
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "dg/bypass-finals",
|
||||
"description": "Removes final keyword from source code on-the-fly and allows mocking of final methods and classes",
|
||||
"keywords": ["testing", "unit", "phpunit", "mocking", "finals"],
|
||||
"license": ["BSD-3-Clause", "GPL-2.0", "GPL-3.0"],
|
||||
"authors": [
|
||||
{
|
||||
"name": "David Grudl",
|
||||
"homepage": "https://davidgrudl.com"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=7.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"nette/tester": "^2.3",
|
||||
"phpstan/phpstan": "^0.12"
|
||||
},
|
||||
"autoload": {
|
||||
"classmap": ["src/"]
|
||||
},
|
||||
"scripts": {
|
||||
"phpstan": "phpstan analyse",
|
||||
"tester": "tester tests -s"
|
||||
}
|
||||
}
|
||||
49
vendor/dg/bypass-finals/license.md
vendored
Normal file
49
vendor/dg/bypass-finals/license.md
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
Licenses
|
||||
========
|
||||
|
||||
Good news! You may use Nette Tester under the terms of either
|
||||
the New BSD License or the GNU General Public License (GPL) version 2 or 3.
|
||||
|
||||
|
||||
|
||||
New BSD License
|
||||
---------------
|
||||
|
||||
Copyright (c) 2004, 2013 David Grudl (https://davidgrudl.com)
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of "Nette Tester" nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
This software is provided by the copyright holders and contributors "as is" and
|
||||
any express or implied warranties, including, but not limited to, the implied
|
||||
warranties of merchantability and fitness for a particular purpose are
|
||||
disclaimed. In no event shall the copyright owner or contributors be liable for
|
||||
any direct, indirect, incidental, special, exemplary, or consequential damages
|
||||
(including, but not limited to, procurement of substitute goods or services;
|
||||
loss of use, data, or profits; or business interruption) however caused and on
|
||||
any theory of liability, whether in contract, strict liability, or tort
|
||||
(including negligence or otherwise) arising in any way out of the use of this
|
||||
software, even if advised of the possibility of such damage.
|
||||
|
||||
|
||||
|
||||
GNU General Public License
|
||||
--------------------------
|
||||
|
||||
GPL licenses are very very long, so instead of including them here we offer
|
||||
you URLs with full text:
|
||||
|
||||
- [GPL version 2](http://www.gnu.org/licenses/gpl-2.0.html)
|
||||
- [GPL version 3](http://www.gnu.org/licenses/gpl-3.0.html)
|
||||
64
vendor/dg/bypass-finals/readme.md
vendored
Normal file
64
vendor/dg/bypass-finals/readme.md
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
Bypass Finals
|
||||
=============
|
||||
|
||||
[](https://packagist.org/packages/dg/bypass-finals)
|
||||
[](https://github.com/dg/bypass-finals/actions)
|
||||
[](https://github.com/dg/bypass-finals/releases)
|
||||
[](https://github.com/dg/bypass-finals/blob/master/license.md)
|
||||
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
Removes final keywords from source code on-the-fly and allows mocking of final methods and classes.
|
||||
It can be used together with any test tool such as PHPUnit, Mockery or [Nette Tester](https://tester.nette.org).
|
||||
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
The recommended way to install is through Composer:
|
||||
|
||||
```
|
||||
composer require dg/bypass-finals --dev
|
||||
```
|
||||
|
||||
It requires PHP version 7.1 (or 5.6 in case of release 1.1) and supports PHP up to 8.1.
|
||||
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
Simply call this:
|
||||
|
||||
```php
|
||||
DG\BypassFinals::enable();
|
||||
```
|
||||
|
||||
You need to enable it before the classes you want to remove the final are loaded. So call it as soon as possible,
|
||||
preferably right after `vendor/autoload.php` is loaded.
|
||||
|
||||
Note that final internal PHP classes like `Closure` cannot be mocked.
|
||||
|
||||
You can choose to only bypass finals in specific files or directories:
|
||||
|
||||
```php
|
||||
DG\BypassFinals::setWhitelist([
|
||||
'*/Nette/*',
|
||||
]);
|
||||
```
|
||||
|
||||
This gives you finer control and can solve issues with certain frameworks and libraries.
|
||||
|
||||
You can try to increase performance by using the cache (the directory must exist):
|
||||
|
||||
```php
|
||||
DG\BypassFinals::$cacheDir = __DIR__ . '/tmp';
|
||||
```
|
||||
|
||||
Support Project
|
||||
---------------
|
||||
|
||||
Do you like BypassFinals?
|
||||
|
||||
[](https://nette.org/make-donation?to=bypass-finals)
|
||||
312
vendor/dg/bypass-finals/src/BypassFinals.php
vendored
Normal file
312
vendor/dg/bypass-finals/src/BypassFinals.php
vendored
Normal file
@@ -0,0 +1,312 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DG;
|
||||
|
||||
|
||||
/**
|
||||
* Removes keyword final from source codes.
|
||||
*/
|
||||
class BypassFinals
|
||||
{
|
||||
private const PROTOCOL = 'file';
|
||||
|
||||
/** @var resource|null */
|
||||
public $context;
|
||||
|
||||
/** @var ?string */
|
||||
public static $cacheDir;
|
||||
|
||||
/** @var resource|null */
|
||||
private $handle;
|
||||
|
||||
/** @var array */
|
||||
private static $pathWhitelist = ['*'];
|
||||
|
||||
/** @var ?object */
|
||||
private static $prevWrapper;
|
||||
|
||||
|
||||
public static function enable(): void
|
||||
{
|
||||
$meta = stream_get_meta_data(fopen(__FILE__, 'r'));
|
||||
self::$prevWrapper = $meta['wrapper_data'] ?? null;
|
||||
stream_wrapper_unregister(self::PROTOCOL);
|
||||
stream_wrapper_register(self::PROTOCOL, self::class);
|
||||
}
|
||||
|
||||
|
||||
public static function setWhitelist(array $whitelist): void
|
||||
{
|
||||
foreach ($whitelist as &$mask) {
|
||||
$mask = strtr($mask, '\\', '/');
|
||||
}
|
||||
|
||||
self::$pathWhitelist = $whitelist;
|
||||
}
|
||||
|
||||
|
||||
public function dir_closedir(): void
|
||||
{
|
||||
closedir($this->handle);
|
||||
}
|
||||
|
||||
|
||||
public function dir_opendir(string $path, int $options): bool
|
||||
{
|
||||
$this->handle = $this->context
|
||||
? $this->native('opendir', $path, $this->context)
|
||||
: $this->native('opendir', $path);
|
||||
return (bool) $this->handle;
|
||||
}
|
||||
|
||||
|
||||
public function dir_readdir()
|
||||
{
|
||||
return readdir($this->handle);
|
||||
}
|
||||
|
||||
|
||||
public function dir_rewinddir(): bool
|
||||
{
|
||||
return (bool) rewinddir($this->handle);
|
||||
}
|
||||
|
||||
|
||||
public function mkdir(string $path, int $mode, int $options): bool
|
||||
{
|
||||
$recursive = (bool) ($options & STREAM_MKDIR_RECURSIVE);
|
||||
return $this->context
|
||||
? $this->native('mkdir', $path, $mode, $recursive, $this->context)
|
||||
: $this->native('mkdir', $path, $mode, $recursive);
|
||||
}
|
||||
|
||||
|
||||
public function rename(string $pathFrom, string $pathTo): bool
|
||||
{
|
||||
return $this->context
|
||||
? $this->native('rename', $pathFrom, $pathTo, $this->context)
|
||||
: $this->native('rename', $pathFrom, $pathTo);
|
||||
}
|
||||
|
||||
|
||||
public function rmdir(string $path, int $options): bool
|
||||
{
|
||||
return $this->context
|
||||
? $this->native('rmdir', $path, $this->context)
|
||||
: $this->native('rmdir', $path);
|
||||
}
|
||||
|
||||
|
||||
public function stream_cast(int $castAs)
|
||||
{
|
||||
return $this->handle;
|
||||
}
|
||||
|
||||
|
||||
public function stream_close(): void
|
||||
{
|
||||
fclose($this->handle);
|
||||
}
|
||||
|
||||
|
||||
public function stream_eof(): bool
|
||||
{
|
||||
return feof($this->handle);
|
||||
}
|
||||
|
||||
|
||||
public function stream_flush(): bool
|
||||
{
|
||||
return fflush($this->handle);
|
||||
}
|
||||
|
||||
|
||||
public function stream_lock(int $operation): bool
|
||||
{
|
||||
return $operation
|
||||
? flock($this->handle, $operation)
|
||||
: true;
|
||||
}
|
||||
|
||||
|
||||
public function stream_metadata(string $path, int $option, $value): bool
|
||||
{
|
||||
switch ($option) {
|
||||
case STREAM_META_TOUCH:
|
||||
return $this->native('touch', $path, $value[0] ?? time(), $value[1] ?? time());
|
||||
case STREAM_META_OWNER_NAME:
|
||||
case STREAM_META_OWNER:
|
||||
return $this->native('chown', $path, $value);
|
||||
case STREAM_META_GROUP_NAME:
|
||||
case STREAM_META_GROUP:
|
||||
return $this->native('chgrp', $path, $value);
|
||||
case STREAM_META_ACCESS:
|
||||
return $this->native('chmod', $path, $value);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public function stream_open(string $path, string $mode, int $options, ?string &$openedPath): bool
|
||||
{
|
||||
$usePath = (bool) ($options & STREAM_USE_PATH);
|
||||
if ($mode === 'rb' && pathinfo($path, PATHINFO_EXTENSION) === 'php' && self::isPathInWhiteList($path)) {
|
||||
if (self::$prevWrapper) {
|
||||
$content = null;
|
||||
self::$prevWrapper->stream_open($path, $mode, $options, $openedPath);
|
||||
while (!self::$prevWrapper->stream_eof()) {
|
||||
$content .= self::$prevWrapper->stream_read(8192);
|
||||
}
|
||||
|
||||
self::$prevWrapper->stream_close();
|
||||
} else {
|
||||
$content = $this->native('file_get_contents', $path, $usePath, $this->context);
|
||||
}
|
||||
if (!is_string($content)) {
|
||||
return false;
|
||||
}
|
||||
$modified = self::cachedRemoveFinals($content);
|
||||
if ($modified !== $content) {
|
||||
$this->handle = tmpfile();
|
||||
$this->native('fwrite', $this->handle, $modified);
|
||||
$this->native('fseek', $this->handle, 0);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
$this->handle = $this->context
|
||||
? $this->native('fopen', $path, $mode, $usePath, $this->context)
|
||||
: $this->native('fopen', $path, $mode, $usePath);
|
||||
return (bool) $this->handle;
|
||||
}
|
||||
|
||||
|
||||
public function stream_read(int $count)
|
||||
{
|
||||
return fread($this->handle, $count);
|
||||
}
|
||||
|
||||
|
||||
public function stream_seek(int $offset, int $whence = SEEK_SET): bool
|
||||
{
|
||||
return fseek($this->handle, $offset, $whence) === 0;
|
||||
}
|
||||
|
||||
|
||||
public function stream_set_option(int $option, int $arg1, ?int $arg2): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public function stream_stat()
|
||||
{
|
||||
return fstat($this->handle);
|
||||
}
|
||||
|
||||
|
||||
public function stream_tell()
|
||||
{
|
||||
return ftell($this->handle);
|
||||
}
|
||||
|
||||
|
||||
public function stream_truncate(int $newSize): bool
|
||||
{
|
||||
return ftruncate($this->handle, $newSize);
|
||||
}
|
||||
|
||||
|
||||
public function stream_write(string $data)
|
||||
{
|
||||
return fwrite($this->handle, $data);
|
||||
}
|
||||
|
||||
|
||||
public function unlink(string $path): bool
|
||||
{
|
||||
return $this->native('unlink', $path);
|
||||
}
|
||||
|
||||
|
||||
public function url_stat(string $path, int $flags)
|
||||
{
|
||||
$func = $flags & STREAM_URL_STAT_LINK ? 'lstat' : 'stat';
|
||||
return $flags & STREAM_URL_STAT_QUIET
|
||||
? @$this->native($func, $path)
|
||||
: $this->native($func, $path);
|
||||
}
|
||||
|
||||
|
||||
private function native(string $func)
|
||||
{
|
||||
stream_wrapper_restore(self::PROTOCOL);
|
||||
try {
|
||||
return $func(...array_slice(func_get_args(), 1));
|
||||
} finally {
|
||||
stream_wrapper_unregister(self::PROTOCOL);
|
||||
stream_wrapper_register(self::PROTOCOL, self::class);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static function cachedRemoveFinals(string $code): string
|
||||
{
|
||||
if (stripos($code, 'final') === false) {
|
||||
// do nothing
|
||||
|
||||
} elseif (self::$cacheDir) {
|
||||
$hash = sha1($code);
|
||||
if ($file = @fopen(self::$cacheDir . '/' . $hash, 'r')) { // @ may not exist
|
||||
flock($file, LOCK_SH);
|
||||
if ($res = stream_get_contents($file)) {
|
||||
return $res;
|
||||
}
|
||||
}
|
||||
$code = self::removeFinals($code);
|
||||
if ($file = @fopen(self::$cacheDir . '/' . $hash, 'x')) { // @ may exist
|
||||
flock($file, LOCK_EX);
|
||||
fwrite($file, $code);
|
||||
}
|
||||
|
||||
} else {
|
||||
$code = self::removeFinals($code);
|
||||
}
|
||||
|
||||
return $code;
|
||||
}
|
||||
|
||||
|
||||
public static function removeFinals(string $code): string
|
||||
{
|
||||
try {
|
||||
$tokens = token_get_all($code, TOKEN_PARSE);
|
||||
} catch (\ParseError $e) {
|
||||
return $code;
|
||||
}
|
||||
|
||||
$code = '';
|
||||
foreach ($tokens as $token) {
|
||||
$code .= is_array($token)
|
||||
? ($token[0] === T_FINAL ? '' : $token[1])
|
||||
: $token;
|
||||
}
|
||||
|
||||
return $code;
|
||||
}
|
||||
|
||||
|
||||
private static function isPathInWhiteList(string $path): bool
|
||||
{
|
||||
$path = strtr($path, '\\', '/');
|
||||
foreach (self::$pathWhitelist as $mask) {
|
||||
if (fnmatch($mask, $path)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user