From fc8114b34b5044539b8181b9086bbfea7f8f6cff Mon Sep 17 00:00:00 2001 From: Jorijn Schrijvershof Date: Sun, 12 Dec 2021 12:48:32 +0100 Subject: [PATCH 1/9] bootstrap the behat testing framework this commit also changes the way how the container is being built, because we need to share the same container between two parties: the bin/bitcoin-dca entry script and the behat framework --- behat.yml | 12 + bin/bitcoin-dca | 61 +--- composer.json | 9 +- composer.lock | 409 ++++++++++++++++++++++++-- config/services.yaml | 8 +- features/bootstrap/TaggingContext.php | 67 +++++ features/tags.feature | 16 + src/Factory/ContainerFactory.php | 72 +++++ 8 files changed, 576 insertions(+), 78 deletions(-) create mode 100644 behat.yml create mode 100644 features/bootstrap/TaggingContext.php create mode 100644 features/tags.feature create mode 100644 src/Factory/ContainerFactory.php diff --git a/behat.yml b/behat.yml new file mode 100644 index 0000000..0a4c7bf --- /dev/null +++ b/behat.yml @@ -0,0 +1,12 @@ +default: + formatters: + pretty: true + suites: + domain_features: + services: Jorijn\Bitcoin\Dca\Factory\ContainerFactory::createContainer + paths: + - '%paths.base%/features' + contexts: + - Features\Jorijn\Bitcoin\Dca\TaggingContext: + - '@application' + - '@repository.tag_integer.balance' diff --git a/bin/bitcoin-dca b/bin/bitcoin-dca index c58e554..2a8ec8b 100755 --- a/bin/bitcoin-dca +++ b/bin/bitcoin-dca @@ -1,22 +1,12 @@ #!/usr/bin/env php loadEnv(__DIR__.'/../.env'); -} +use Jorijn\Bitcoin\Dca\Factory\ContainerFactory; +use Symfony\Component\Dotenv\Dotenv; set_error_handler( static function ($errno, $errstr, $errfile, $errline) { @@ -28,43 +18,10 @@ set_error_handler( } ); -$containerCache = __DIR__.'/../var/cache/container.php'; -$containerConfigCache = new ConfigCache($containerCache, $_SERVER['DEBUG'] ?? false); - -if (!$containerConfigCache->isFresh()) { - $containerBuilder = new ContainerBuilder(); - - // load the DI config - $loader = new YamlFileLoader($containerBuilder, new FileLocator(__DIR__.'/../config/')); - $loader->load('services.yaml'); - - $containerBuilder->addCompilerPass(new AddConsoleCommandPass()); - $containerBuilder->addCompilerPass(new RegisterListenersPass()); - $containerBuilder->addCompilerPass(new SerializerPass()); - $containerBuilder->setParameter('application.path', dirname(__DIR__)); - - $versionFile = dirname(__DIR__).DIRECTORY_SEPARATOR.'version.json'; - if (file_exists($versionFile)) { - $version = json_decode(file_get_contents($versionFile), true, 512, JSON_THROW_ON_ERROR); - if (isset($version['version'])) { - $containerBuilder->setParameter('application_version', $version['version']); - } - } - - $containerBuilder->compile(); - - // write the compiled container to file - $dumper = new PhpDumper($containerBuilder); - $containerConfigCache->write( - $dumper->dump(['class' => 'BitcoinDcaContainer']), - $containerBuilder->getResources() - ); +if (file_exists(dirname(__DIR__).DIRECTORY_SEPARATOR.'.env')) { + $dotenv = new Dotenv(); + $dotenv->loadEnv(dirname(__DIR__).DIRECTORY_SEPARATOR.'.env'); } -require_once $containerCache; -$container = new BitcoinDcaContainer(); - -$application = $container->get('application'); -$application->setCommandLoader($container->get('console.command_loader')); -$application->setDispatcher($container->get('event_dispatcher')); -$application->run(); +$container = ContainerFactory::createContainer(); +$container->get('application')->run(); diff --git a/composer.json b/composer.json index cbfd1df..691f888 100644 --- a/composer.json +++ b/composer.json @@ -47,15 +47,18 @@ }, "autoload-dev": { "psr-4": { - "Tests\\Jorijn\\Bitcoin\\Dca\\": "tests/" + "Tests\\Jorijn\\Bitcoin\\Dca\\": "tests/", + "Features\\Jorijn\\Bitcoin\\Dca\\": "features/bootstrap/" } }, "scripts": { "test": "vendor/bin/phpunit --testdox", - "php-cs-fixer": "tools/php-cs-fixer/vendor/bin/php-cs-fixer fix" + "php-cs-fixer": "tools/php-cs-fixer/vendor/bin/php-cs-fixer fix", + "behat": "vendor/bin/behat" }, "require-dev": { "phpunit/phpunit": "^9.1", - "rector/rector": "^0.12.5" + "rector/rector": "^0.12.5", + "behat/behat": "^3.10" } } diff --git a/composer.lock b/composer.lock index a9c5657..b5f5064 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "7347297809960203cc621944451b8f5c", + "content-hash": "45ca599a55932eab25e132f003930a74", "packages": [ { "name": "async-aws/core", @@ -1224,27 +1224,22 @@ }, { "name": "psr/container", - "version": "2.0.2", + "version": "1.1.2", "source": { "type": "git", "url": "https://github.com/php-fig/container.git", - "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", - "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", "shasum": "" }, "require": { "php": ">=7.4.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, "autoload": { "psr-4": { "Psr\\Container\\": "src/" @@ -1271,9 +1266,9 @@ ], "support": { "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/2.0.2" + "source": "https://github.com/php-fig/container/tree/1.1.2" }, - "time": "2021-11-05T16:47:00+00:00" + "time": "2021-11-05T16:50:12+00:00" }, { "name": "psr/event-dispatcher", @@ -3811,21 +3806,21 @@ }, { "name": "symfony/service-contracts", - "version": "v3.0.0", + "version": "v2.4.1", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "36715ebf9fb9db73db0cb24263c79077c6fe8603" + "reference": "d664541b99d6fb0247ec5ff32e87238582236204" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/36715ebf9fb9db73db0cb24263c79077c6fe8603", - "reference": "36715ebf9fb9db73db0cb24263c79077c6fe8603", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/d664541b99d6fb0247ec5ff32e87238582236204", + "reference": "d664541b99d6fb0247ec5ff32e87238582236204", "shasum": "" }, "require": { - "php": ">=8.0.2", - "psr/container": "^2.0" + "php": ">=7.2.5", + "psr/container": "^1.1" }, "conflict": { "ext-psr": "<1.1|>=2" @@ -3836,7 +3831,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "3.0-dev" + "dev-main": "2.4-dev" }, "thanks": { "name": "symfony/contracts", @@ -3873,7 +3868,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.0.0" + "source": "https://github.com/symfony/service-contracts/tree/v2.4.1" }, "funding": [ { @@ -3889,7 +3884,7 @@ "type": "tidelift" } ], - "time": "2021-11-04T17:53:12+00:00" + "time": "2021-11-04T16:37:19+00:00" }, { "name": "symfony/string", @@ -4122,6 +4117,205 @@ } ], "packages-dev": [ + { + "name": "behat/behat", + "version": "v3.10.0", + "source": { + "type": "git", + "url": "https://github.com/Behat/Behat.git", + "reference": "a55661154079cf881ef643b303bfaf67bae3a09f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Behat/Behat/zipball/a55661154079cf881ef643b303bfaf67bae3a09f", + "reference": "a55661154079cf881ef643b303bfaf67bae3a09f", + "shasum": "" + }, + "require": { + "behat/gherkin": "^4.9.0", + "behat/transliterator": "^1.2", + "ext-mbstring": "*", + "php": "^7.2 || ^8.0", + "psr/container": "^1.0", + "symfony/config": "^4.4 || ^5.0 || ^6.0", + "symfony/console": "^4.4 || ^5.0 || ^6.0", + "symfony/dependency-injection": "^4.4 || ^5.0 || ^6.0", + "symfony/event-dispatcher": "^4.4 || ^5.0 || ^6.0", + "symfony/translation": "^4.4 || ^5.0 || ^6.0", + "symfony/yaml": "^4.4 || ^5.0 || ^6.0" + }, + "require-dev": { + "container-interop/container-interop": "^1.2", + "herrera-io/box": "~1.6.1", + "phpunit/phpunit": "^8.5 || ^9.0", + "symfony/process": "^4.4 || ^5.0 || ^6.0", + "vimeo/psalm": "^4.8" + }, + "suggest": { + "ext-dom": "Needed to output test results in JUnit format." + }, + "bin": [ + "bin/behat" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Behat\\Behat\\": "src/Behat/Behat/", + "Behat\\Testwork\\": "src/Behat/Testwork/", + "Behat\\Step\\": "src/Behat/Step/", + "Behat\\Hook\\": "src/Behat/Hook/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + } + ], + "description": "Scenario-oriented BDD framework for PHP", + "homepage": "http://behat.org/", + "keywords": [ + "Agile", + "BDD", + "ScenarioBDD", + "Scrum", + "StoryBDD", + "User story", + "business", + "development", + "documentation", + "examples", + "symfony", + "testing" + ], + "support": { + "issues": "https://github.com/Behat/Behat/issues", + "source": "https://github.com/Behat/Behat/tree/v3.10.0" + }, + "time": "2021-11-02T20:09:40+00:00" + }, + { + "name": "behat/gherkin", + "version": "v4.9.0", + "source": { + "type": "git", + "url": "https://github.com/Behat/Gherkin.git", + "reference": "0bc8d1e30e96183e4f36db9dc79caead300beff4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Behat/Gherkin/zipball/0bc8d1e30e96183e4f36db9dc79caead300beff4", + "reference": "0bc8d1e30e96183e4f36db9dc79caead300beff4", + "shasum": "" + }, + "require": { + "php": "~7.2|~8.0" + }, + "require-dev": { + "cucumber/cucumber": "dev-gherkin-22.0.0", + "phpunit/phpunit": "~8|~9", + "symfony/yaml": "~3|~4|~5" + }, + "suggest": { + "symfony/yaml": "If you want to parse features, represented in YAML files" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev" + } + }, + "autoload": { + "psr-0": { + "Behat\\Gherkin": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + } + ], + "description": "Gherkin DSL parser for PHP", + "homepage": "http://behat.org/", + "keywords": [ + "BDD", + "Behat", + "Cucumber", + "DSL", + "gherkin", + "parser" + ], + "support": { + "issues": "https://github.com/Behat/Gherkin/issues", + "source": "https://github.com/Behat/Gherkin/tree/v4.9.0" + }, + "time": "2021-10-12T13:05:09+00:00" + }, + { + "name": "behat/transliterator", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/Behat/Transliterator.git", + "reference": "3c4ec1d77c3d05caa1f0bf8fb3aae4845005c7fc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Behat/Transliterator/zipball/3c4ec1d77c3d05caa1f0bf8fb3aae4845005c7fc", + "reference": "3c4ec1d77c3d05caa1f0bf8fb3aae4845005c7fc", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "chuyskywalker/rolling-curl": "^3.1", + "php-yaoi/php-yaoi": "^1.0", + "phpunit/phpunit": "^4.8.36|^6.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2-dev" + } + }, + "autoload": { + "psr-4": { + "Behat\\Transliterator\\": "src/Behat/Transliterator" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Artistic-1.0" + ], + "description": "String transliterator", + "keywords": [ + "i18n", + "slug", + "transliterator" + ], + "support": { + "issues": "https://github.com/Behat/Transliterator/issues", + "source": "https://github.com/Behat/Transliterator/tree/v1.3.0" + }, + "time": "2020-01-14T16:39:13+00:00" + }, { "name": "doctrine/instantiator", "version": "1.4.0", @@ -6152,6 +6346,179 @@ ], "time": "2020-09-28T06:39:44+00:00" }, + { + "name": "symfony/translation", + "version": "v6.0.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "b7956e00c6e03546f2ba489fc50f7c47933e76b8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/b7956e00c6e03546f2ba489fc50f7c47933e76b8", + "reference": "b7956e00c6e03546f2ba489fc50f7c47933e76b8", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-mbstring": "~1.0", + "symfony/translation-contracts": "^2.3|^3.0" + }, + "conflict": { + "symfony/config": "<5.4", + "symfony/console": "<5.4", + "symfony/dependency-injection": "<5.4", + "symfony/http-kernel": "<5.4", + "symfony/twig-bundle": "<5.4", + "symfony/yaml": "<5.4" + }, + "provide": { + "symfony/translation-implementation": "2.3|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0", + "symfony/console": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/finder": "^5.4|^6.0", + "symfony/http-client-contracts": "^1.1|^2.0|^3.0", + "symfony/http-kernel": "^5.4|^6.0", + "symfony/intl": "^5.4|^6.0", + "symfony/polyfill-intl-icu": "^1.21", + "symfony/service-contracts": "^1.1.2|^2|^3", + "symfony/yaml": "^5.4|^6.0" + }, + "suggest": { + "psr/log-implementation": "To use logging capability in translator", + "symfony/config": "", + "symfony/yaml": "" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools to internationalize your application", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/translation/tree/v6.0.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-12-08T15:13:44+00:00" + }, + { + "name": "symfony/translation-contracts", + "version": "v3.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "1b6ea5a7442af5a12dba3dbd6d71034b5b234e77" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/1b6ea5a7442af5a12dba3dbd6d71034b5b234e77", + "reference": "1b6ea5a7442af5a12dba3dbd6d71034b5b234e77", + "shasum": "" + }, + "require": { + "php": ">=8.0.2" + }, + "suggest": { + "symfony/translation-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Translation\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to translation", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/translation-contracts/tree/v3.0.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-09-07T12:43:40+00:00" + }, { "name": "theseer/tokenizer", "version": "1.2.1", diff --git a/config/services.yaml b/config/services.yaml index 3ce0e0b..dbfb4be 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -256,12 +256,16 @@ services: arguments: - '%application_title%' - '%application_version%' + calls: + - setCommandLoader: [ '@console.command_loader' ] + - setDispatcher: [ '@event_dispatcher' ] ###################################################################### # Repositories ###################################################################### repository.tag_integer.balance: class: Jorijn\Bitcoin\Dca\Repository\JsonFileTaggedIntegerRepository + public: true arguments: - '%application.path%/var/storage/balance.db' @@ -313,7 +317,7 @@ services: class: Jorijn\Bitcoin\Dca\EventListener\Notifications\SendEmailOnBuyListener parent: event_listener.notify_via_email calls: - - setTemplateLocation: ['%application.path%/resources/templates/buy_notification.email.html.php'] + - setTemplateLocation: [ '%application.path%/resources/templates/buy_notification.email.html.php' ] tags: - { name: kernel.event_listener, event: Jorijn\Bitcoin\Dca\Event\BuySuccessEvent, method: onBuy } @@ -321,7 +325,7 @@ services: class: Jorijn\Bitcoin\Dca\EventListener\Notifications\SendEmailOnWithdrawListener parent: event_listener.notify_via_email calls: - - setTemplateLocation: ['%application.path%/resources/templates/withdraw_notification.email.html.php'] + - setTemplateLocation: [ '%application.path%/resources/templates/withdraw_notification.email.html.php' ] tags: - { name: kernel.event_listener, event: Jorijn\Bitcoin\Dca\Event\WithdrawSuccessEvent, method: onWithdraw } diff --git a/features/bootstrap/TaggingContext.php b/features/bootstrap/TaggingContext.php new file mode 100644 index 0000000..f4ec99d --- /dev/null +++ b/features/bootstrap/TaggingContext.php @@ -0,0 +1,67 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Features\Jorijn\Bitcoin\Dca; + +use Behat\Behat\Context\Context; +use Jorijn\Bitcoin\Dca\Repository\TaggedIntegerRepositoryInterface; +use Symfony\Component\Console\Application; + +class TaggingContext implements Context +{ + public function __construct( + protected Application $application, + protected TaggedIntegerRepositoryInterface $repository + ) { + } + + /** + * @Given /^there is no information for tag "([^"]*)" yet$/ + */ + public function thereIsNoInformationForTagYet(string $tag): void + { + \assert(0 === $this->repository->get($tag)); + } + + /** + * @When /^the current Bitcoin price is (\d+) dollar$/ + */ + public function theCurrentBitcoinPriceIsDollar(int $price): void + { + throw new \Behat\Behat\Tester\Exception\PendingException(); + } + + /** + * @Given /^I buy (\d+) dollar worth of Bitcoin for tag "([^"]*)"$/ + */ + public function iBuyDollarWorthOfBitcoinForTag(int $dollars, string $tag): void + { + throw new \Behat\Behat\Tester\Exception\PendingException(); + } + + /** + * @Then /^I expect the balance of tag "([^"]*)" to be (\d+) satoshis/ + */ + public function iExpectTheBalanceOfTagToBeSatoshis(string $tag, int $satoshis): void + { + throw new \Behat\Behat\Tester\Exception\PendingException(); + } + + /** + * @Given /^the balance for tag "([^"]*)" is (\d+) satoshis$/ + */ + public function theBalanceForTagIsSatoshis(string $tag, int $satoshis): void + { + throw new \Behat\Behat\Tester\Exception\PendingException(); + } +} diff --git a/features/tags.feature b/features/tags.feature new file mode 100644 index 0000000..06f7719 --- /dev/null +++ b/features/tags.feature @@ -0,0 +1,16 @@ +Feature: Tagging + In order to create a sub-account for example, their mother, the user of + Bitcoin DCA should be able to tag purchases with a unique identifier allowing + them to withdraw those specific funds later on. + + Scenario: Buying with a tag for the first time + Given there is no information for tag "mom" yet + When the current Bitcoin price is 20000 dollar + And I buy 10 dollar worth of Bitcoin for tag "mom" + Then I expect the balance of tag "mom" to be 50000 satoshis + + Scenario: Subsequent buying for the same tag increases its balance + Given the balance for tag "mom" is 50000 satoshis + When the current Bitcoin price is 20000 dollar + And I buy 10 dollar worth of Bitcoin for tag "mom" + Then I expect the balance of tag "mom" to be 100000 satoshis diff --git a/src/Factory/ContainerFactory.php b/src/Factory/ContainerFactory.php new file mode 100644 index 0000000..f1418ac --- /dev/null +++ b/src/Factory/ContainerFactory.php @@ -0,0 +1,72 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Jorijn\Bitcoin\Dca\Factory; + +use Psr\Container\ContainerInterface; +use Symfony\Component\Config\ConfigCache; +use Symfony\Component\Config\FileLocator; +use Symfony\Component\Console\DependencyInjection\AddConsoleCommandPass; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Dumper\PhpDumper; +use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; +use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass; +use Symfony\Component\Serializer\DependencyInjection\SerializerPass; + +class ContainerFactory +{ + public static function createContainer(): ContainerInterface + { + $projectDirectory = \dirname(__DIR__, 2); + $containerCache = $projectDirectory.\DIRECTORY_SEPARATOR.'var'.\DIRECTORY_SEPARATOR.'cache'.\DIRECTORY_SEPARATOR.'container.php'; + $containerConfigCache = new ConfigCache($containerCache, isset($_SERVER['DEBUG']) && (bool) $_SERVER['DEBUG']); + + if (!$containerConfigCache->isFresh()) { + $containerBuilder = new ContainerBuilder(); + $containerBuilder->addCompilerPass(new AddConsoleCommandPass()); + $containerBuilder->addCompilerPass(new RegisterListenersPass()); + $containerBuilder->addCompilerPass(new SerializerPass()); + $containerBuilder->setParameter('application.path', $projectDirectory); + + // load the DI config + $loader = new YamlFileLoader($containerBuilder, new FileLocator($projectDirectory.\DIRECTORY_SEPARATOR.'config')); + $loader->load('services.yaml'); + + try { + $versionFile = $projectDirectory.\DIRECTORY_SEPARATOR.'version.json'; + if (file_exists($versionFile)) { + $version = json_decode(file_get_contents($versionFile), true, 512, JSON_THROW_ON_ERROR); + if (isset($version['version'])) { + $containerBuilder->setParameter('application_version', $version['version']); + } + } + } catch (\JsonException) { + // this should not happen because the JSON would be corrupt, but since + // version information is optional lets not make a big deal of it. + } + + $containerBuilder->compile(); + + // write the compiled container to file + $dumper = new PhpDumper($containerBuilder); + $containerConfigCache->write( + $dumper->dump(['class' => 'BitcoinDcaContainer']), + $containerBuilder->getResources() + ); + } + + require_once $containerCache; + + return new \BitcoinDcaContainer(); + } +} From 8b8b4faa913bfb20eff2017a92f01b3514595468 Mon Sep 17 00:00:00 2001 From: Jorijn Schrijvershof Date: Fri, 17 Dec 2021 11:13:17 +0100 Subject: [PATCH 2/9] tweak the first tests, add integration testing step to CI --- .env.test | 9 ++++++ .github/workflows/run-tests-on-change.yml | 12 ++++++-- Dockerfile | 8 ------ behat.yml | 3 ++ bin/bitcoin-dca | 6 ---- config/services.yaml | 1 + docker-compose.yml | 2 -- features/bootstrap/ApplicationContext.php | 17 ++++++++++++ features/bootstrap/BuyingContext.php | 30 ++++++++++++++++++++ features/bootstrap/TaggingContext.php | 34 +++++++++++++++-------- features/tags.feature | 6 ++-- src/Factory/ContainerFactory.php | 5 ++++ 12 files changed, 100 insertions(+), 33 deletions(-) create mode 100644 .env.test create mode 100644 features/bootstrap/ApplicationContext.php create mode 100644 features/bootstrap/BuyingContext.php diff --git a/.env.test b/.env.test new file mode 100644 index 0000000..e9dc647 --- /dev/null +++ b/.env.test @@ -0,0 +1,9 @@ +TESTING=1 +DEBUG=1 +EXCHANGE=bl3p +BL3P_PRIVATE_KEY=foo +BL3P_PUBLIC_KEY=bar +NOTIFICATION_EMAIL_ENABLED=false +NOTIFICATION_TELEGRAM_ENABLED=false +BASE_CURRENCY=EUR +DISABLE_VERSION_CHECK=off diff --git a/.github/workflows/run-tests-on-change.yml b/.github/workflows/run-tests-on-change.yml index 54536d4..6b8e432 100644 --- a/.github/workflows/run-tests-on-change.yml +++ b/.github/workflows/run-tests-on-change.yml @@ -16,17 +16,23 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v1 - - name: Build the Docker image and execute the testsuite + - name: Build the Docker development image uses: docker/build-push-action@v2 with: context: . push: false - target: testing_stage + target: development_build load: true tags: jorijn/bitcoin-dca:ci cache-from: type=registry,ref=jorijn/bitcoin-dca:latest - - name: Extract the test logging artifacts from the container that was built + - name: Run the PHPUnit tests + - run: docker run --rm --entrypoint= -v ${GITHUB_WORKSPACE}:/app/ jorijn/bitcoin-dca:ci vendor/bin/phpunit --testdox --coverage-clover /tmp/tests_coverage.xml --log-junit /tmp/tests_log.xml + + - name: Run the Behat integration tests + - run: docker run --rm --entrypoint= -v ${GITHUB_WORKSPACE}:/app/ jorijn/bitcoin-dca:ci vendor/bin/behat -f pretty + + - name: Extract the test logging artifacts generated from the PHPUnit tests run: | docker run --rm --entrypoint= -v ${GITHUB_WORKSPACE}:/app/ jorijn/bitcoin-dca:ci sh -c "cp /tmp/tests_*.xml /app/" sed -i "s/\/app\//\/github\/workspace\//g" tests_coverage.xml tests_log.xml diff --git a/Dockerfile b/Dockerfile index 7c236ec..1694177 100644 --- a/Dockerfile +++ b/Dockerfile @@ -57,15 +57,7 @@ RUN apk --no-cache update \ && pecl install pcov xdebug \ && docker-php-ext-enable pcov xdebug -################################################################################################################## -# Test Stage -################################################################################################################## -FROM development_build AS testing_stage - -# run the test script(s) from composer, this validates the application before allowing the build to succeed -# this does make the tests run multiple times, but with different architectures RUN composer install --no-interaction --no-plugins --no-scripts --prefer-dist --no-ansi --ignore-platform-reqs -RUN vendor/bin/phpunit --testdox --coverage-clover /tmp/tests_coverage.xml --log-junit /tmp/tests_log.xml ################################################################################################################## # Production Stage diff --git a/behat.yml b/behat.yml index 0a4c7bf..e604000 100644 --- a/behat.yml +++ b/behat.yml @@ -7,6 +7,9 @@ default: paths: - '%paths.base%/features' contexts: + - Features\Jorijn\Bitcoin\Dca\ApplicationContext: ~ + - Features\Jorijn\Bitcoin\Dca\BuyingContext: + - '@service.buy.mockexchange' - Features\Jorijn\Bitcoin\Dca\TaggingContext: - '@application' - '@repository.tag_integer.balance' diff --git a/bin/bitcoin-dca b/bin/bitcoin-dca index 2a8ec8b..d77e8ec 100755 --- a/bin/bitcoin-dca +++ b/bin/bitcoin-dca @@ -6,7 +6,6 @@ declare(strict_types=1); require_once dirname(__DIR__).DIRECTORY_SEPARATOR.'vendor'.DIRECTORY_SEPARATOR.'autoload.php'; use Jorijn\Bitcoin\Dca\Factory\ContainerFactory; -use Symfony\Component\Dotenv\Dotenv; set_error_handler( static function ($errno, $errstr, $errfile, $errline) { @@ -18,10 +17,5 @@ set_error_handler( } ); -if (file_exists(dirname(__DIR__).DIRECTORY_SEPARATOR.'.env')) { - $dotenv = new Dotenv(); - $dotenv->loadEnv(dirname(__DIR__).DIRECTORY_SEPARATOR.'.env'); -} - $container = ContainerFactory::createContainer(); $container->get('application')->run(); diff --git a/config/services.yaml b/config/services.yaml index dbfb4be..3b9c4bf 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -508,6 +508,7 @@ services: ## MockExchange (fake one that allows automated testing without spending $$$) ## service.buy.mockexchange: + public: true arguments: - '%env(bool:TESTING)%' - '%env(BASE_CURRENCY)%' diff --git a/docker-compose.yml b/docker-compose.yml index 87430db..cee54e9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -15,5 +15,3 @@ services: volumes: - .:/app:cached - ./vendor:/app/vendor:delegated - env_file: - - .env diff --git a/features/bootstrap/ApplicationContext.php b/features/bootstrap/ApplicationContext.php new file mode 100644 index 0000000..30287e7 --- /dev/null +++ b/features/bootstrap/ApplicationContext.php @@ -0,0 +1,17 @@ +buyService->setBitcoinPrice($price); + } + + /** + * @Given /^the buying fee will be (\d+\.\d+) ([A-Z]{3})$/ + */ + public function theCurrentFeeIsBTC(string $feeAmount, string $feeCurrency): void + { + $this->buyService->setFeeAmount($feeAmount); + $this->buyService->setFeeCurrency($feeCurrency); + } +} diff --git a/features/bootstrap/TaggingContext.php b/features/bootstrap/TaggingContext.php index f4ec99d..771142a 100644 --- a/features/bootstrap/TaggingContext.php +++ b/features/bootstrap/TaggingContext.php @@ -16,6 +16,8 @@ use Behat\Behat\Context\Context; use Jorijn\Bitcoin\Dca\Repository\TaggedIntegerRepositoryInterface; use Symfony\Component\Console\Application; +use Symfony\Component\Console\Input\ArrayInput; +use Symfony\Component\Console\Output\BufferedOutput; class TaggingContext implements Context { @@ -30,15 +32,7 @@ public function __construct( */ public function thereIsNoInformationForTagYet(string $tag): void { - \assert(0 === $this->repository->get($tag)); - } - - /** - * @When /^the current Bitcoin price is (\d+) dollar$/ - */ - public function theCurrentBitcoinPriceIsDollar(int $price): void - { - throw new \Behat\Behat\Tester\Exception\PendingException(); + $this->theBalanceForTagIsSatoshis($tag, 0); } /** @@ -46,7 +40,22 @@ public function theCurrentBitcoinPriceIsDollar(int $price): void */ public function iBuyDollarWorthOfBitcoinForTag(int $dollars, string $tag): void { - throw new \Behat\Behat\Tester\Exception\PendingException(); + $command = $this->application->find('buy'); + $inputArguments = new ArrayInput( + [ + 'amount' => $dollars, + '--yes' => true, + '--tag' => $tag, + ] + ); + + $output = new BufferedOutput(); + $exitStatus = $command->run($inputArguments, $output); + + \assert( + 0 === $exitStatus, + sprintf('exit code is not 0, actual: %d (output: %s)', $exitStatus, $output->fetch()) + ); } /** @@ -54,7 +63,8 @@ public function iBuyDollarWorthOfBitcoinForTag(int $dollars, string $tag): void */ public function iExpectTheBalanceOfTagToBeSatoshis(string $tag, int $satoshis): void { - throw new \Behat\Behat\Tester\Exception\PendingException(); + $balance = $this->repository->get($tag); + \assert($satoshis === $balance, sprintf('balance is not %d, actual: %d', $satoshis, $balance)); } /** @@ -62,6 +72,6 @@ public function iExpectTheBalanceOfTagToBeSatoshis(string $tag, int $satoshis): */ public function theBalanceForTagIsSatoshis(string $tag, int $satoshis): void { - throw new \Behat\Behat\Tester\Exception\PendingException(); + $this->repository->set($tag, $satoshis); } } diff --git a/features/tags.feature b/features/tags.feature index 06f7719..1970ae2 100644 --- a/features/tags.feature +++ b/features/tags.feature @@ -6,11 +6,13 @@ Feature: Tagging Scenario: Buying with a tag for the first time Given there is no information for tag "mom" yet When the current Bitcoin price is 20000 dollar + And the buying fee will be 0.00000200 BTC And I buy 10 dollar worth of Bitcoin for tag "mom" - Then I expect the balance of tag "mom" to be 50000 satoshis + Then I expect the balance of tag "mom" to be 49800 satoshis Scenario: Subsequent buying for the same tag increases its balance Given the balance for tag "mom" is 50000 satoshis When the current Bitcoin price is 20000 dollar + And the buying fee will be 0.00000200 BTC And I buy 10 dollar worth of Bitcoin for tag "mom" - Then I expect the balance of tag "mom" to be 100000 satoshis + Then I expect the balance of tag "mom" to be 99800 satoshis diff --git a/src/Factory/ContainerFactory.php b/src/Factory/ContainerFactory.php index f1418ac..fc1d725 100644 --- a/src/Factory/ContainerFactory.php +++ b/src/Factory/ContainerFactory.php @@ -20,6 +20,7 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Dumper\PhpDumper; use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; +use Symfony\Component\Dotenv\Dotenv; use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass; use Symfony\Component\Serializer\DependencyInjection\SerializerPass; @@ -28,6 +29,10 @@ class ContainerFactory public static function createContainer(): ContainerInterface { $projectDirectory = \dirname(__DIR__, 2); + + $dotenv = new Dotenv(); + $dotenv->loadEnv($projectDirectory.\DIRECTORY_SEPARATOR.'.env', 'ENV'); + $containerCache = $projectDirectory.\DIRECTORY_SEPARATOR.'var'.\DIRECTORY_SEPARATOR.'cache'.\DIRECTORY_SEPARATOR.'container.php'; $containerConfigCache = new ConfigCache($containerCache, isset($_SERVER['DEBUG']) && (bool) $_SERVER['DEBUG']); From 0442b81e451c5531e3cf7578f3865a43be21c1e5 Mon Sep 17 00:00:00 2001 From: Jorijn Schrijvershof Date: Fri, 17 Dec 2021 11:14:30 +0100 Subject: [PATCH 3/9] fix syntax error in pipeline --- .github/workflows/run-tests-on-change.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/run-tests-on-change.yml b/.github/workflows/run-tests-on-change.yml index 6b8e432..aa1f809 100644 --- a/.github/workflows/run-tests-on-change.yml +++ b/.github/workflows/run-tests-on-change.yml @@ -27,10 +27,12 @@ jobs: cache-from: type=registry,ref=jorijn/bitcoin-dca:latest - name: Run the PHPUnit tests - - run: docker run --rm --entrypoint= -v ${GITHUB_WORKSPACE}:/app/ jorijn/bitcoin-dca:ci vendor/bin/phpunit --testdox --coverage-clover /tmp/tests_coverage.xml --log-junit /tmp/tests_log.xml + - run: | + docker run --rm --entrypoint= -v ${GITHUB_WORKSPACE}:/app/ jorijn/bitcoin-dca:ci vendor/bin/phpunit --testdox --coverage-clover /tmp/tests_coverage.xml --log-junit /tmp/tests_log.xml - name: Run the Behat integration tests - - run: docker run --rm --entrypoint= -v ${GITHUB_WORKSPACE}:/app/ jorijn/bitcoin-dca:ci vendor/bin/behat -f pretty + - run: | + docker run --rm --entrypoint= -v ${GITHUB_WORKSPACE}:/app/ jorijn/bitcoin-dca:ci vendor/bin/behat -f pretty - name: Extract the test logging artifacts generated from the PHPUnit tests run: | From e3c980566c1cd3dc11214da875d9b3aa6ad761b9 Mon Sep 17 00:00:00 2001 From: Jorijn Schrijvershof Date: Fri, 17 Dec 2021 11:15:15 +0100 Subject: [PATCH 4/9] fix syntax error in pipeline --- .github/workflows/run-tests-on-change.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/run-tests-on-change.yml b/.github/workflows/run-tests-on-change.yml index aa1f809..7f90eb6 100644 --- a/.github/workflows/run-tests-on-change.yml +++ b/.github/workflows/run-tests-on-change.yml @@ -27,12 +27,10 @@ jobs: cache-from: type=registry,ref=jorijn/bitcoin-dca:latest - name: Run the PHPUnit tests - - run: | - docker run --rm --entrypoint= -v ${GITHUB_WORKSPACE}:/app/ jorijn/bitcoin-dca:ci vendor/bin/phpunit --testdox --coverage-clover /tmp/tests_coverage.xml --log-junit /tmp/tests_log.xml + run: docker run --rm --entrypoint= -v ${GITHUB_WORKSPACE}:/app/ jorijn/bitcoin-dca:ci vendor/bin/phpunit --testdox --coverage-clover /tmp/tests_coverage.xml --log-junit /tmp/tests_log.xml - name: Run the Behat integration tests - - run: | - docker run --rm --entrypoint= -v ${GITHUB_WORKSPACE}:/app/ jorijn/bitcoin-dca:ci vendor/bin/behat -f pretty + run: docker run --rm --entrypoint= -v ${GITHUB_WORKSPACE}:/app/ jorijn/bitcoin-dca:ci vendor/bin/behat -f pretty - name: Extract the test logging artifacts generated from the PHPUnit tests run: | From 13b57ca874f88feb640a2d39c5878c31ed3d7b77 Mon Sep 17 00:00:00 2001 From: Jorijn Schrijvershof Date: Fri, 17 Dec 2021 11:32:22 +0100 Subject: [PATCH 5/9] set workdir for dev builds --- Dockerfile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Dockerfile b/Dockerfile index 1694177..d87004f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -59,6 +59,8 @@ RUN apk --no-cache update \ RUN composer install --no-interaction --no-plugins --no-scripts --prefer-dist --no-ansi --ignore-platform-reqs +WORKDIR /app/ + ################################################################################################################## # Production Stage ################################################################################################################## @@ -66,5 +68,7 @@ FROM base_image as production_build COPY docker/php-production.ini "$PHP_INI_DIR/php.ini" +WORKDIR /app/ + # run the app to precompile the DI container RUN /app/bin/bitcoin-dca From 4bcb643327685f537e344b5724fe7f07ee13e6bb Mon Sep 17 00:00:00 2001 From: Jorijn Schrijvershof Date: Fri, 17 Dec 2021 11:37:55 +0100 Subject: [PATCH 6/9] move composer install to runtime instead of build --- .github/workflows/run-tests-on-change.yml | 3 +++ Dockerfile | 2 -- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/run-tests-on-change.yml b/.github/workflows/run-tests-on-change.yml index 7f90eb6..f6ac5c6 100644 --- a/.github/workflows/run-tests-on-change.yml +++ b/.github/workflows/run-tests-on-change.yml @@ -26,6 +26,9 @@ jobs: tags: jorijn/bitcoin-dca:ci cache-from: type=registry,ref=jorijn/bitcoin-dca:latest + - name: Install the development dependencies + run: docker run --rm --entrypoint= -v ${GITHUB_WORKSPACE}:/app/ jorijn/bitcoin-dca:ci composer install --no-interaction --no-plugins --no-scripts --prefer-dist --no-ansi --ignore-platform-reqs + - name: Run the PHPUnit tests run: docker run --rm --entrypoint= -v ${GITHUB_WORKSPACE}:/app/ jorijn/bitcoin-dca:ci vendor/bin/phpunit --testdox --coverage-clover /tmp/tests_coverage.xml --log-junit /tmp/tests_log.xml diff --git a/Dockerfile b/Dockerfile index d87004f..3f7f2f2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -57,8 +57,6 @@ RUN apk --no-cache update \ && pecl install pcov xdebug \ && docker-php-ext-enable pcov xdebug -RUN composer install --no-interaction --no-plugins --no-scripts --prefer-dist --no-ansi --ignore-platform-reqs - WORKDIR /app/ ################################################################################################################## From 805466d43ea88f2c890ae378eab8f7756b3c291b Mon Sep 17 00:00:00 2001 From: Jorijn Schrijvershof Date: Fri, 17 Dec 2021 11:42:39 +0100 Subject: [PATCH 7/9] removed the artifact convert stage, not needed anymore --- .github/workflows/run-tests-on-change.yml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/.github/workflows/run-tests-on-change.yml b/.github/workflows/run-tests-on-change.yml index f6ac5c6..4dd2a78 100644 --- a/.github/workflows/run-tests-on-change.yml +++ b/.github/workflows/run-tests-on-change.yml @@ -30,16 +30,13 @@ jobs: run: docker run --rm --entrypoint= -v ${GITHUB_WORKSPACE}:/app/ jorijn/bitcoin-dca:ci composer install --no-interaction --no-plugins --no-scripts --prefer-dist --no-ansi --ignore-platform-reqs - name: Run the PHPUnit tests - run: docker run --rm --entrypoint= -v ${GITHUB_WORKSPACE}:/app/ jorijn/bitcoin-dca:ci vendor/bin/phpunit --testdox --coverage-clover /tmp/tests_coverage.xml --log-junit /tmp/tests_log.xml + run: | + docker run --rm --entrypoint= -v ${GITHUB_WORKSPACE}:/app/ jorijn/bitcoin-dca:ci vendor/bin/phpunit --testdox --coverage-clover /app/tests_coverage.xml --log-junit /app/tests_log.xml + sed -i "s/\/app\//\/github\/workspace\//g" tests_coverage.xml tests_log.xml - name: Run the Behat integration tests run: docker run --rm --entrypoint= -v ${GITHUB_WORKSPACE}:/app/ jorijn/bitcoin-dca:ci vendor/bin/behat -f pretty - - name: Extract the test logging artifacts generated from the PHPUnit tests - run: | - docker run --rm --entrypoint= -v ${GITHUB_WORKSPACE}:/app/ jorijn/bitcoin-dca:ci sh -c "cp /tmp/tests_*.xml /app/" - sed -i "s/\/app\//\/github\/workspace\//g" tests_coverage.xml tests_log.xml - - name: Upload logging artifacts uses: actions/upload-artifact@v2 with: From 94a525636f55aa770600916e895cadb37264c5f5 Mon Sep 17 00:00:00 2001 From: Jorijn Schrijvershof Date: Fri, 17 Dec 2021 20:27:24 +0100 Subject: [PATCH 8/9] added new scenarios for withdrawal --- behat.yml | 2 + config/services.yaml | 1 + features/bootstrap/ApplicationContext.php | 11 ++++ features/bootstrap/BuyingContext.php | 11 ++++ features/bootstrap/TaggingContext.php | 50 +++++++++++++++++++ features/bootstrap/WithdrawingContext.php | 49 ++++++++++++++++++ features/tags.feature | 34 +++++++++++++ src/Exception/MockClientException.php | 18 +++++++ .../MockExchangeWithdrawService.php | 8 +++ 9 files changed, 184 insertions(+) create mode 100644 features/bootstrap/WithdrawingContext.php create mode 100644 src/Exception/MockClientException.php diff --git a/behat.yml b/behat.yml index e604000..d6e86f2 100644 --- a/behat.yml +++ b/behat.yml @@ -10,6 +10,8 @@ default: - Features\Jorijn\Bitcoin\Dca\ApplicationContext: ~ - Features\Jorijn\Bitcoin\Dca\BuyingContext: - '@service.buy.mockexchange' + - Features\Jorijn\Bitcoin\Dca\WithdrawingContext: + - '@service.withdraw.mockexchange' - Features\Jorijn\Bitcoin\Dca\TaggingContext: - '@application' - '@repository.tag_integer.balance' diff --git a/config/services.yaml b/config/services.yaml index 3b9c4bf..8c223bc 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -517,6 +517,7 @@ services: - { name: exchange-buy-service, priority: 1000 } service.withdraw.mockexchange: + public: true class: Jorijn\Bitcoin\Dca\Service\MockExchange\MockExchangeWithdrawService arguments: - '%env(bool:TESTING)%' diff --git a/features/bootstrap/ApplicationContext.php b/features/bootstrap/ApplicationContext.php index 30287e7..38a1de5 100644 --- a/features/bootstrap/ApplicationContext.php +++ b/features/bootstrap/ApplicationContext.php @@ -1,5 +1,16 @@ + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + namespace Features\Jorijn\Bitcoin\Dca; use Behat\Behat\Context\Context; diff --git a/features/bootstrap/BuyingContext.php b/features/bootstrap/BuyingContext.php index 9c37ef1..8d823d8 100644 --- a/features/bootstrap/BuyingContext.php +++ b/features/bootstrap/BuyingContext.php @@ -1,5 +1,16 @@ + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + namespace Features\Jorijn\Bitcoin\Dca; use Behat\Behat\Context\Context; diff --git a/features/bootstrap/TaggingContext.php b/features/bootstrap/TaggingContext.php index 771142a..7818906 100644 --- a/features/bootstrap/TaggingContext.php +++ b/features/bootstrap/TaggingContext.php @@ -18,6 +18,7 @@ use Symfony\Component\Console\Application; use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Output\BufferedOutput; +use Symfony\Component\Console\Output\NullOutput; class TaggingContext implements Context { @@ -58,6 +59,29 @@ public function iBuyDollarWorthOfBitcoinForTag(int $dollars, string $tag): void ); } + /** + * @When /^I withdraw the entire balance for tag "([^"]*)"$/ + */ + public function iWithdrawTheEntireBalanceForTag(string $tag): void + { + $command = $this->application->find('withdraw'); + $inputArguments = new ArrayInput( + [ + '--all' => true, + '--yes' => true, + '--tag' => $tag, + ] + ); + + $output = new BufferedOutput(); + $exitStatus = $command->run($inputArguments, $output); + + \assert( + 0 === $exitStatus, + sprintf('exit code is not 0, actual: %d (output: %s)', $exitStatus, $output->fetch()) + ); + } + /** * @Then /^I expect the balance of tag "([^"]*)" to be (\d+) satoshis/ */ @@ -69,9 +93,35 @@ public function iExpectTheBalanceOfTagToBeSatoshis(string $tag, int $satoshis): /** * @Given /^the balance for tag "([^"]*)" is (\d+) satoshis$/ + * @Given /^the balance for tag "([^"]*)" is still (\d+) satoshis$/ */ public function theBalanceForTagIsSatoshis(string $tag, int $satoshis): void { $this->repository->set($tag, $satoshis); } + + /** + * @When /^I withdraw the entire balance for tag "([^"]*)" and it fails$/ + */ + public function iWithdrawTheEntireBalanceForTagAndItFails(string $tag): void + { + $command = $this->application->find('withdraw'); + $inputArguments = new ArrayInput( + [ + '--all' => true, + '--yes' => true, + '--tag' => $tag, + ] + ); + + $exceptionThrown = false; + + try { + $command->run($inputArguments, new NullOutput()); + } catch (\Throwable) { + $exceptionThrown = true; + } + + \assert(true === $exceptionThrown, 'withdraw did not fail'); + } } diff --git a/features/bootstrap/WithdrawingContext.php b/features/bootstrap/WithdrawingContext.php new file mode 100644 index 0000000..8af168a --- /dev/null +++ b/features/bootstrap/WithdrawingContext.php @@ -0,0 +1,49 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Features\Jorijn\Bitcoin\Dca; + +use Behat\Behat\Context\Context; +use Jorijn\Bitcoin\Dca\Service\MockExchange\MockExchangeWithdrawService; + +class WithdrawingContext implements Context +{ + public function __construct(protected MockExchangeWithdrawService $withdrawService) + { + } + + /** + * @Given /^the balance on the exchange is (\d+) satoshis$/ + */ + public function theBalanceOnTheExchangeIsSatoshis(int $satoshis): void + { + $this->withdrawService->setAvailableBalance($satoshis); + } + + /** + * @Given /^the withdrawal fee on the exchange is going to be (\d+) satoshis$/ + */ + public function theWithdrawalFeeOnTheExchangeIsGoingToBeSatoshis(int $satoshis): void + { + $this->withdrawService->setWithdrawFeeInSatoshis($satoshis); + } + + /** + * @Given /^I expect the balance of the exchange to be (\d+) satoshis$/ + */ + public function iExpectTheBalanceOfTheExchangeToBeSatoshis(int $satoshis): void + { + $balance = $this->withdrawService->getAvailableBalance(); + \assert($satoshis === $balance, sprintf('available balance is not %d, actual: %d', $satoshis, $balance)); + } +} diff --git a/features/tags.feature b/features/tags.feature index 1970ae2..7ee37f6 100644 --- a/features/tags.feature +++ b/features/tags.feature @@ -16,3 +16,37 @@ Feature: Tagging And the buying fee will be 0.00000200 BTC And I buy 10 dollar worth of Bitcoin for tag "mom" Then I expect the balance of tag "mom" to be 99800 satoshis + + Scenario: Buying Bitcoin for mom shouldn't increase dad's balance + Given the balance for tag "mom" is 0 satoshis + And the balance for tag "dad" is 0 satoshis + When the current Bitcoin price is 30000 dollar + And the buying fee will be 0.00000100 BTC + And I buy 25 dollar worth of Bitcoin for tag "mom" + Then I expect the balance of tag "mom" to be 83233 satoshis + And I expect the balance of tag "dad" to be 0 satoshis + + Scenario: I want to withdraw all of mom's balance + Given the balance for tag "mom" is 500000 satoshis + And the balance on the exchange is 1000000 satoshis + And the withdrawal fee on the exchange is going to be 500 satoshis + When I withdraw the entire balance for tag "mom" + Then I expect the balance of tag "mom" to be 0 satoshis + And I expect the balance of the exchange to be 499500 satoshis + + Scenario: I want to withdraw mom's balance but there isn't enough on the exchange + Given the balance for tag "mom" is 2000 satoshis + And the balance on the exchange is 2000 satoshis + And the withdrawal fee on the exchange is going to be 500 satoshis + When I withdraw the entire balance for tag "mom" and it fails + Then the balance for tag "mom" is still 2000 satoshis + + Scenario: I want to withdraw mom's balance and leave dad's balance alone + Given the balance for tag "mom" is 2000 satoshis + And the balance for tag "dad" is 2000 satoshis + And the balance on the exchange is 5000 satoshis + And the withdrawal fee on the exchange is going to be 100 satoshis + When I withdraw the entire balance for tag "mom" + Then I expect the balance of tag "mom" to be 0 satoshis + And I expect the balance of tag "dad" to be 2000 satoshis + And I expect the balance of the exchange to be 2900 satoshis diff --git a/src/Exception/MockClientException.php b/src/Exception/MockClientException.php new file mode 100644 index 0000000..8cf281e --- /dev/null +++ b/src/Exception/MockClientException.php @@ -0,0 +1,18 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Jorijn\Bitcoin\Dca\Exception; + +class MockClientException extends \RuntimeException +{ +} diff --git a/src/Service/MockExchange/MockExchangeWithdrawService.php b/src/Service/MockExchange/MockExchangeWithdrawService.php index 68a4ac3..b3e27fe 100644 --- a/src/Service/MockExchange/MockExchangeWithdrawService.php +++ b/src/Service/MockExchange/MockExchangeWithdrawService.php @@ -13,6 +13,7 @@ namespace Jorijn\Bitcoin\Dca\Service\MockExchange; +use Jorijn\Bitcoin\Dca\Exception\MockClientException; use Jorijn\Bitcoin\Dca\Model\CompletedWithdraw; use Jorijn\Bitcoin\Dca\Service\WithdrawServiceInterface; @@ -32,6 +33,13 @@ public function __construct(protected bool $isEnabled) public function withdraw(int $balanceToWithdraw, string $addressToWithdrawTo): CompletedWithdraw { + if (($this->availableBalance - $this->withdrawFeeInSatoshis) < $balanceToWithdraw) { + throw new MockClientException('balance is insufficient'); + } + + $this->availableBalance -= $balanceToWithdraw; + $this->availableBalance -= $this->withdrawFeeInSatoshis; + return new CompletedWithdraw($addressToWithdrawTo, $balanceToWithdraw, sprintf('MOCK_%s', time())); } From 74efc1a2f4cd037eeba008b00ac2b77aa03d1592 Mon Sep 17 00:00:00 2001 From: Jorijn Schrijvershof Date: Fri, 17 Dec 2021 20:31:43 +0100 Subject: [PATCH 9/9] add withdraw address for the test to succeed --- .env.test | 1 + 1 file changed, 1 insertion(+) diff --git a/.env.test b/.env.test index e9dc647..9ef2ce5 100644 --- a/.env.test +++ b/.env.test @@ -7,3 +7,4 @@ NOTIFICATION_EMAIL_ENABLED=false NOTIFICATION_TELEGRAM_ENABLED=false BASE_CURRENCY=EUR DISABLE_VERSION_CHECK=off +WITHDRAW_ADDRESS=bc1qnz3d3dy27tgwsfazmf933n9gdk00r3m2pyrhzf