diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..b5e4bbb --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,42 @@ +name: Run Tests + +on: + push: + branches: + - main + pull_request: + branches: + - main +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 8.2 + extensions: mysqli + + - name: Install Composer dependencies + working-directory: ./code + run: composer install --prefer-dist --no-progress + + - name: Copy .env.example to .env + working-directory: ./code + run: cp .env.example .env + + - name: Generate app key + working-directory: ./code + run: php artisan key:generate + + - name: Run database migrations + working-directory: ./code + run: php artisan migrate + + - name: Run tests + working-directory: ./code + run: vendor/bin/phpunit \ No newline at end of file diff --git a/code/app/DTO/Request.php b/code/app/DTO/Request.php new file mode 100644 index 0000000..f459af5 --- /dev/null +++ b/code/app/DTO/Request.php @@ -0,0 +1,30 @@ +amount; + } + + public function getCurrency(): string + { + return $this->amount->getCurrency()->getCode(); + } +} diff --git a/code/app/DTO/Transaction.php b/code/app/DTO/Transaction.php new file mode 100644 index 0000000..dc0741f --- /dev/null +++ b/code/app/DTO/Transaction.php @@ -0,0 +1,30 @@ +amount; + } + + public function getCurrency(): string + { + return $this->amount->getCurrency()->getCode(); + } +} diff --git a/code/app/Http/Controllers/TransactionController.php b/code/app/Http/Controllers/TransactionController.php new file mode 100644 index 0000000..c59a0f1 --- /dev/null +++ b/code/app/Http/Controllers/TransactionController.php @@ -0,0 +1,36 @@ +validator->validate($userRequest, $transaction); + + return response()->json(['isValid' => $isValid]); + } catch (InvalidArgumentException $e) { + return response()->json([ + 'error' => 'Validation failed', + 'message' => $e->getMessage() + ], 400); + } + } +} diff --git a/code/app/Providers/AppServiceProvider.php b/code/app/Providers/AppServiceProvider.php new file mode 100644 index 0000000..605176f --- /dev/null +++ b/code/app/Providers/AppServiceProvider.php @@ -0,0 +1,31 @@ +app->singleton(RequestMoneyValidator::class, function ($app) { + return new RequestMoneyValidator( + deviation: config('app.transaction_deviation') + ); + }); + } + + /** + * Bootstrap any application services. + */ + public function boot(): void + { + // + } +} diff --git a/code/app/Services/RequestMoneyValidator.php b/code/app/Services/RequestMoneyValidator.php new file mode 100644 index 0000000..a004e9e --- /dev/null +++ b/code/app/Services/RequestMoneyValidator.php @@ -0,0 +1,40 @@ +deviation, + FILTER_VALIDATE_INT, + ['options' => ['min_range' => 0, 'max_range' => 100]] + )) { + throw new InvalidArgumentException('Deviation must be between 0 and 100.'); + } + + if ($request->getCurrency() !== $transaction->getCurrency()) { + return false; + } + + $deviationPercentage = (string) ($this->deviation / 100); + $deviationAmount = $transaction->getAmount()->multiply($deviationPercentage); + + $minAmount = $transaction->getAmount()->subtract($deviationAmount); + $maxAmount = $transaction->getAmount()->add($deviationAmount); + + return $request->getAmount()->greaterThanOrEqual($minAmount) + && $request->getAmount()->lessThanOrEqual($maxAmount); + } +} diff --git a/code/config/app.php b/code/config/app.php index d0ac589..d4f63ac 100644 --- a/code/config/app.php +++ b/code/config/app.php @@ -123,5 +123,5 @@ 'store' => env('APP_MAINTENANCE_STORE', 'database'), ], - 'transaction_deviation' => env('TRANSACTION_DEVIATION', 5), + 'transaction_deviation' => (int) env('TRANSACTION_DEVIATION', 5), ]; diff --git a/code/routes/web.php b/code/routes/web.php new file mode 100644 index 0000000..358b4c6 --- /dev/null +++ b/code/routes/web.php @@ -0,0 +1,10 @@ +get('/'); - - $response->assertStatus(200); - } -} diff --git a/code/tests/Feature/TransactionControllerTest.php b/code/tests/Feature/TransactionControllerTest.php new file mode 100644 index 0000000..d08b2cc --- /dev/null +++ b/code/tests/Feature/TransactionControllerTest.php @@ -0,0 +1,31 @@ +getJson('/validate-transaction'); + + $response->assertStatus(200) + ->assertJson(['isValid' => false]); + } + + public function test_validate_transaction_invalid_deviation(): void + { + config(['app.transaction_deviation' => 150]); + + $response = $this->getJson('/validate-transaction'); + + $response->assertStatus(400) + ->assertJson([ + 'error' => 'Validation failed', + 'message' => 'Deviation must be between 0 and 100.' + ]); + } +} diff --git a/code/tests/Unit/RequestMoneyValidatorTest.php b/code/tests/Unit/RequestMoneyValidatorTest.php new file mode 100644 index 0000000..131d34c --- /dev/null +++ b/code/tests/Unit/RequestMoneyValidatorTest.php @@ -0,0 +1,74 @@ +assertFalse($validator->validate($request, $transaction)); + } + + public function test_validate_within_deviation(): void + { + $request = Request::create(9500, 'USD'); + $transaction = Transaction::create(9000, 'USD'); + + $validator = new RequestMoneyValidator(10); + + $this->assertTrue($validator->validate($request, $transaction)); + } + + public function test_validate_outside_deviation(): void + { + $request = Request::create(10000, 'USD'); + $transaction = Transaction::create(9000, 'USD'); + + $validator = new RequestMoneyValidator(5); + + $this->assertFalse($validator->validate($request, $transaction)); + } + + public function test_validate_exact_amount(): void + { + $request = Request::create(10000, 'USD'); + $transaction = Transaction::create(10000, 'USD'); + $validator = new RequestMoneyValidator(10); + + $this->assertTrue($validator->validate($request, $transaction)); + } + + public function test_validate_small_deviation(): void + { + $request = Request::create(9950, 'USD'); + $transaction = Transaction::create(10000, 'USD'); + + $validator = new RequestMoneyValidator(1); + + $this->assertTrue($validator->validate($request, $transaction)); + } + + public function test_negative_deviation_throws_exception(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Deviation must be between 0 and 100.'); + + $request = Request::create(10000, 'USD'); + $transaction = Transaction::create(9000, 'USD'); + $validator = new RequestMoneyValidator(-5); + + $validator->validate($request, $transaction); + } +}