diff --git a/src/app/Http/Controllers/API/V4/Admin/DiscountsController.php b/src/app/Http/Controllers/API/V4/Admin/DiscountsController.php new file mode 100644 index 00000000..e6487db4 --- /dev/null +++ b/src/app/Http/Controllers/API/V4/Admin/DiscountsController.php @@ -0,0 +1,42 @@ +orderBy('discount')->get() + ->map(function ($discount) use (&$discounts) { + $label = $discount->discount . '% - ' . $discount->description; + + if ($discount->code) { + $label .= " [{$discount->code}]"; + } + + $discounts[] = [ + 'id' => $discount->id, + 'discount' => $discount->discount, + 'code' => $discount->code, + 'description' => $discount->description, + 'label' => $label, + ]; + }); + + return response()->json([ + 'status' => 'success', + 'list' => $discounts, + 'count' => count($discounts), + ]); + } +} diff --git a/src/app/Http/Controllers/API/V4/Admin/WalletsController.php b/src/app/Http/Controllers/API/V4/Admin/WalletsController.php index ae13008c..7606364a 100644 --- a/src/app/Http/Controllers/API/V4/Admin/WalletsController.php +++ b/src/app/Http/Controllers/API/V4/Admin/WalletsController.php @@ -1,7 +1,49 @@ errorResponse(404); + } + + if (array_key_exists('discount', $request->input())) { + if (empty($request->discount)) { + $wallet->discount()->dissociate(); + $wallet->save(); + } elseif ($discount = Discount::find($request->discount)) { + $wallet->discount()->associate($discount); + $wallet->save(); + } + } + + $response = $wallet->toArray(); + + if ($wallet->discount) { + $response['discount'] = $wallet->discount->discount; + $response['discount_description'] = $wallet->discount->description; + } + + $response['status'] = 'success'; + $response['message'] = \trans('app.wallet-update-success'); + + return response()->json($response); + } } diff --git a/src/app/Http/Controllers/API/V4/WalletsController.php b/src/app/Http/Controllers/API/V4/WalletsController.php index c275ba4d..e4a5f4a5 100644 --- a/src/app/Http/Controllers/API/V4/WalletsController.php +++ b/src/app/Http/Controllers/API/V4/WalletsController.php @@ -1,93 +1,93 @@ 'Choose :plan', 'process-user-new' => 'User registered', 'process-user-ldap-ready' => 'User created', 'process-user-imap-ready' => 'User mailbox created', 'process-domain-new' => 'Custom domain registered', 'process-domain-ldap-ready' => 'Custom domain created', 'process-domain-verified' => 'Custom domain verified', 'process-domain-confirmed' => 'Custom domain ownership verified', 'domain-verify-success' => 'Domain verified successfully.', 'user-update-success' => 'User data updated successfully.', 'user-create-success' => 'User created successfully.', 'user-delete-success' => 'User deleted successfully.', 'search-foundxdomains' => ':x domains have been found.', 'search-foundxusers' => ':x user accounts have been found.', + + 'wallet-update-success' => 'User wallet updated successfully.', ]; diff --git a/src/resources/vue/Admin/User.vue b/src/resources/vue/Admin/User.vue index 94337d2c..2a3ae14b 100644 --- a/src/resources/vue/Admin/User.vue +++ b/src/resources/vue/Admin/User.vue @@ -1,348 +1,418 @@ diff --git a/src/routes/api.php b/src/routes/api.php index eef59d1d..fc5bcf2c 100644 --- a/src/routes/api.php +++ b/src/routes/api.php @@ -1,98 +1,99 @@ 'api', 'prefix' => 'auth' ], function ($router) { Route::post('login', 'API\AuthController@login'); Route::group( ['middleware' => 'auth:api'], function ($router) { Route::get('info', 'API\AuthController@info'); Route::post('logout', 'API\AuthController@logout'); Route::post('refresh', 'API\AuthController@refresh'); } ); } ); Route::group( [ 'domain' => \config('app.domain'), 'middleware' => 'api', 'prefix' => 'auth' ], function ($router) { Route::post('password-reset/init', 'API\PasswordResetController@init'); Route::post('password-reset/verify', 'API\PasswordResetController@verify'); Route::post('password-reset', 'API\PasswordResetController@reset'); Route::get('signup/plans', 'API\SignupController@plans'); Route::post('signup/init', 'API\SignupController@init'); Route::post('signup/verify', 'API\SignupController@verify'); Route::post('signup', 'API\SignupController@signup'); } ); Route::group( [ 'domain' => \config('app.domain'), 'middleware' => 'auth:api', 'prefix' => 'v4' ], function () { Route::apiResource('domains', API\V4\DomainsController::class); Route::get('domains/{id}/confirm', 'API\V4\DomainsController@confirm'); Route::apiResource('entitlements', API\V4\EntitlementsController::class); Route::apiResource('packages', API\V4\PackagesController::class); Route::apiResource('skus', API\V4\SkusController::class); Route::apiResource('users', API\V4\UsersController::class); Route::apiResource('wallets', API\V4\WalletsController::class); Route::post('payments', 'API\V4\PaymentsController@store'); } ); Route::group( [ 'domain' => \config('app.domain'), ], function () { Route::post('webhooks/payment/mollie', 'API\V4\PaymentsController@webhook'); } ); Route::group( [ 'domain' => 'admin.' . \config('app.domain'), 'middleware' => ['auth:api', 'admin'], 'prefix' => 'v4', ], function () { Route::apiResource('domains', API\V4\Admin\DomainsController::class); Route::get('domains/{id}/confirm', 'API\V4\Admin\DomainsController@confirm'); Route::apiResource('entitlements', API\V4\Admin\EntitlementsController::class); Route::apiResource('packages', API\V4\Admin\PackagesController::class); Route::apiResource('skus', API\V4\Admin\SkusController::class); Route::apiResource('users', API\V4\Admin\UsersController::class); Route::apiResource('wallets', API\V4\Admin\WalletsController::class); + Route::apiResource('discounts', API\V4\Admin\DiscountsController::class); } ); diff --git a/src/tests/Browser/Admin/UserTest.php b/src/tests/Browser/Admin/UserTest.php index 3b630d00..d0b27ecb 100644 --- a/src/tests/Browser/Admin/UserTest.php +++ b/src/tests/Browser/Admin/UserTest.php @@ -1,342 +1,402 @@ getTestUser('john@kolab.org'); $john->setSettings([ 'phone' => '+48123123123', ]); $wallet = $john->wallets()->first(); $wallet->discount()->dissociate(); $wallet->balance = 0; $wallet->save(); } /** * {@inheritDoc} */ public function tearDown(): void { $john = $this->getTestUser('john@kolab.org'); $john->setSettings([ 'phone' => null, ]); $wallet = $john->wallets()->first(); $wallet->discount()->dissociate(); $wallet->balance = 0; $wallet->save(); parent::tearDown(); } /** * Test user info page (unauthenticated) */ public function testUserUnauth(): void { // Test that the page requires authentication $this->browse(function (Browser $browser) { $jack = $this->getTestUser('jack@kolab.org'); $browser->visit('/user/' . $jack->id)->on(new Home()); }); } /** * Test user info page */ public function testUserInfo(): void { $this->browse(function (Browser $browser) { $jack = $this->getTestUser('jack@kolab.org'); $page = new UserPage($jack->id); $browser->visit(new Home()) ->submitLogon('jeroen@jeroen.jeroen', 'jeroen', true) ->on(new Dashboard()) ->visit($page) ->on($page); // Assert main info box content $browser->assertSeeIn('@user-info .card-title', $jack->email) ->with('@user-info form', function (Browser $browser) use ($jack) { $browser->assertElementsCount('.row', 7) ->assertSeeIn('.row:nth-child(1) label', 'Managed by') ->assertSeeIn('.row:nth-child(1) #manager a', 'john@kolab.org') ->assertSeeIn('.row:nth-child(2) label', 'ID (Created at)') ->assertSeeIn('.row:nth-child(2) #userid', "{$jack->id} ({$jack->created_at})") ->assertSeeIn('.row:nth-child(3) label', 'Status') ->assertSeeIn('.row:nth-child(3) #status span.text-success', 'Active') ->assertSeeIn('.row:nth-child(4) label', 'First name') ->assertSeeIn('.row:nth-child(4) #first_name', 'Jack') ->assertSeeIn('.row:nth-child(5) label', 'Last name') ->assertSeeIn('.row:nth-child(5) #last_name', 'Daniels') ->assertSeeIn('.row:nth-child(6) label', 'External email') ->assertMissing('.row:nth-child(6) #external_email a') ->assertSeeIn('.row:nth-child(7) label', 'Country') ->assertSeeIn('.row:nth-child(7) #country', 'United States of America'); }); // Some tabs are loaded in background, wait a second $browser->pause(500) ->assertElementsCount('@nav a', 5); // Assert Finances tab $browser->assertSeeIn('@nav #tab-finances', 'Finances') ->with('@user-finances', function (Browser $browser) { $browser->assertSeeIn('.card-title', 'Account balance') ->assertSeeIn('.card-title .text-success', '0,00 CHF') ->with('form', function (Browser $browser) { $browser->assertElementsCount('.row', 1) ->assertSeeIn('.row:nth-child(1) label', 'Discount') ->assertSeeIn('.row:nth-child(1) #discount span', 'none'); }); }); // Assert Aliases tab $browser->assertSeeIn('@nav #tab-aliases', 'Aliases (1)') ->click('@nav #tab-aliases') ->whenAvailable('@user-aliases', function (Browser $browser) { $browser->assertElementsCount('table tbody tr', 1) ->assertSeeIn('table tbody tr:first-child td:first-child', 'jack.daniels@kolab.org') ->assertMissing('table tfoot'); }); // Assert Subscriptions tab $browser->assertSeeIn('@nav #tab-subscriptions', 'Subscriptions (3)') ->click('@nav #tab-subscriptions') ->with('@user-subscriptions', function (Browser $browser) { $browser->assertElementsCount('table tbody tr', 3) ->assertSeeIn('table tbody tr:nth-child(1) td:first-child', 'User Mailbox') ->assertSeeIn('table tbody tr:nth-child(1) td:last-child', '4,44 CHF') ->assertSeeIn('table tbody tr:nth-child(2) td:first-child', 'Storage Quota 2 GB') ->assertSeeIn('table tbody tr:nth-child(2) td:last-child', '0,00 CHF') ->assertSeeIn('table tbody tr:nth-child(3) td:first-child', 'Groupware Features') ->assertSeeIn('table tbody tr:nth-child(3) td:last-child', '5,55 CHF') ->assertMissing('table tfoot'); }); // Assert Domains tab $browser->assertSeeIn('@nav #tab-domains', 'Domains (0)') ->click('@nav #tab-domains') ->with('@user-domains', function (Browser $browser) { $browser->assertElementsCount('table tbody tr', 0) ->assertSeeIn('table tfoot tr td', 'There are no domains in this account.'); }); // Assert Users tab $browser->assertSeeIn('@nav #tab-users', 'Users (0)') ->click('@nav #tab-users') ->with('@user-users', function (Browser $browser) { $browser->assertElementsCount('table tbody tr', 0) ->assertSeeIn('table tfoot tr td', 'There are no users in this account.'); }); }); } /** * Test user info page (continue) * * @depends testUserInfo */ public function testUserInfo2(): void { $this->browse(function (Browser $browser) { $john = $this->getTestUser('john@kolab.org'); $page = new UserPage($john->id); $discount = Discount::where('code', 'TEST')->first(); $wallet = $john->wallet(); $wallet->discount()->associate($discount); $wallet->debit(2010); $wallet->save(); // Click the managed-by link on Jack's page $browser->click('@user-info #manager a') ->on($page); // Assert main info box content $browser->assertSeeIn('@user-info .card-title', $john->email) ->with('@user-info form', function (Browser $browser) use ($john) { $ext_email = $john->getSetting('external_email'); $browser->assertElementsCount('.row', 8) ->assertSeeIn('.row:nth-child(1) label', 'ID (Created at)') ->assertSeeIn('.row:nth-child(1) #userid', "{$john->id} ({$john->created_at})") ->assertSeeIn('.row:nth-child(2) label', 'Status') ->assertSeeIn('.row:nth-child(2) #status span.text-success', 'Active') ->assertSeeIn('.row:nth-child(3) label', 'First name') ->assertSeeIn('.row:nth-child(3) #first_name', 'John') ->assertSeeIn('.row:nth-child(4) label', 'Last name') ->assertSeeIn('.row:nth-child(4) #last_name', 'Doe') ->assertSeeIn('.row:nth-child(5) label', 'Phone') ->assertSeeIn('.row:nth-child(5) #phone', $john->getSetting('phone')) ->assertSeeIn('.row:nth-child(6) label', 'External email') ->assertSeeIn('.row:nth-child(6) #external_email a', $ext_email) ->assertAttribute('.row:nth-child(6) #external_email a', 'href', "mailto:$ext_email") ->assertSeeIn('.row:nth-child(7) label', 'Address') ->assertSeeIn('.row:nth-child(7) #billing_address', $john->getSetting('billing_address')) ->assertSeeIn('.row:nth-child(8) label', 'Country') ->assertSeeIn('.row:nth-child(8) #country', 'United States of America'); }); // Some tabs are loaded in background, wait a second $browser->pause(500) ->assertElementsCount('@nav a', 5); // Assert Finances tab $browser->assertSeeIn('@nav #tab-finances', 'Finances') ->with('@user-finances', function (Browser $browser) { $browser->assertSeeIn('.card-title', 'Account balance') ->assertSeeIn('.card-title .text-danger', '-20,10 CHF') ->with('form', function (Browser $browser) { $browser->assertElementsCount('.row', 1) ->assertSeeIn('.row:nth-child(1) label', 'Discount') ->assertSeeIn('.row:nth-child(1) #discount span', '10% - Test voucher'); }); }); // Assert Aliases tab $browser->assertSeeIn('@nav #tab-aliases', 'Aliases (1)') ->click('@nav #tab-aliases') ->whenAvailable('@user-aliases', function (Browser $browser) { $browser->assertElementsCount('table tbody tr', 1) ->assertSeeIn('table tbody tr:first-child td:first-child', 'john.doe@kolab.org') ->assertMissing('table tfoot'); }); // Assert Subscriptions tab $browser->assertSeeIn('@nav #tab-subscriptions', 'Subscriptions (3)') ->click('@nav #tab-subscriptions') ->with('@user-subscriptions', function (Browser $browser) { $browser->assertElementsCount('table tbody tr', 3) ->assertSeeIn('table tbody tr:nth-child(1) td:first-child', 'User Mailbox') ->assertSeeIn('table tbody tr:nth-child(1) td:last-child', '3,99 CHF/month¹') ->assertSeeIn('table tbody tr:nth-child(2) td:first-child', 'Storage Quota 2 GB') ->assertSeeIn('table tbody tr:nth-child(2) td:last-child', '0,00 CHF/month¹') ->assertSeeIn('table tbody tr:nth-child(3) td:first-child', 'Groupware Features') ->assertSeeIn('table tbody tr:nth-child(3) td:last-child', '4,99 CHF/month¹') ->assertMissing('table tfoot') ->assertSeeIn('table + .hint', '¹ applied discount: 10% - Test voucher'); }); // Assert Domains tab $browser->assertSeeIn('@nav #tab-domains', 'Domains (1)') ->click('@nav #tab-domains') ->with('@user-domains table', function (Browser $browser) { $browser->assertElementsCount('tbody tr', 1) ->assertSeeIn('tbody tr:nth-child(1) td:first-child a', 'kolab.org') ->assertVisible('tbody tr:nth-child(1) td:first-child svg.text-success') ->assertMissing('tfoot'); }); // Assert Users tab $browser->assertSeeIn('@nav #tab-users', 'Users (3)') ->click('@nav #tab-users') ->with('@user-users table', function (Browser $browser) { $browser->assertElementsCount('tbody tr', 3) ->assertSeeIn('tbody tr:nth-child(1) td:first-child a', 'jack@kolab.org') ->assertVisible('tbody tr:nth-child(1) td:first-child svg.text-success') ->assertSeeIn('tbody tr:nth-child(2) td:first-child a', 'joe@kolab.org') ->assertVisible('tbody tr:nth-child(2) td:first-child svg.text-success') ->assertSeeIn('tbody tr:nth-child(3) td:first-child a', 'ned@kolab.org') ->assertVisible('tbody tr:nth-child(3) td:first-child svg.text-success') ->assertMissing('tfoot'); }); }); // Now we go to Ned's info page, he's a controller on John's wallet $this->browse(function (Browser $browser) { $ned = $this->getTestUser('ned@kolab.org'); $page = new UserPage($ned->id); $browser->click('@user-users tbody tr:nth-child(3) td:first-child a') ->on($page); // Assert main info box content $browser->assertSeeIn('@user-info .card-title', $ned->email) ->with('@user-info form', function (Browser $browser) use ($ned) { $browser->assertSeeIn('.row:nth-child(2) label', 'ID (Created at)') ->assertSeeIn('.row:nth-child(2) #userid', "{$ned->id} ({$ned->created_at})"); }); // Some tabs are loaded in background, wait a second $browser->pause(500) ->assertElementsCount('@nav a', 5); // Assert Finances tab $browser->assertSeeIn('@nav #tab-finances', 'Finances') ->with('@user-finances', function (Browser $browser) { $browser->assertSeeIn('.card-title', 'Account balance') ->assertSeeIn('.card-title .text-success', '0,00 CHF') ->with('form', function (Browser $browser) { $browser->assertElementsCount('.row', 1) ->assertSeeIn('.row:nth-child(1) label', 'Discount') ->assertSeeIn('.row:nth-child(1) #discount span', 'none'); }); }); // Assert Aliases tab $browser->assertSeeIn('@nav #tab-aliases', 'Aliases (0)') ->click('@nav #tab-aliases') ->whenAvailable('@user-aliases', function (Browser $browser) { $browser->assertElementsCount('table tbody tr', 0) ->assertSeeIn('table tfoot tr td', 'This user has no email aliases.'); }); // Assert Subscriptions tab, we expect John's discount here $browser->assertSeeIn('@nav #tab-subscriptions', 'Subscriptions (5)') ->click('@nav #tab-subscriptions') ->with('@user-subscriptions', function (Browser $browser) { $browser->assertElementsCount('table tbody tr', 5) ->assertSeeIn('table tbody tr:nth-child(1) td:first-child', 'User Mailbox') ->assertSeeIn('table tbody tr:nth-child(1) td:last-child', '3,99 CHF/month¹') ->assertSeeIn('table tbody tr:nth-child(2) td:first-child', 'Storage Quota 2 GB') ->assertSeeIn('table tbody tr:nth-child(2) td:last-child', '0,00 CHF/month¹') ->assertSeeIn('table tbody tr:nth-child(3) td:first-child', 'Groupware Features') ->assertSeeIn('table tbody tr:nth-child(3) td:last-child', '4,99 CHF/month¹') ->assertSeeIn('table tbody tr:nth-child(4) td:first-child', 'Activesync') ->assertSeeIn('table tbody tr:nth-child(4) td:last-child', '0,90 CHF/month¹') ->assertSeeIn('table tbody tr:nth-child(5) td:first-child', '2-Factor Authentication') ->assertSeeIn('table tbody tr:nth-child(5) td:last-child', '0,00 CHF/month¹') ->assertMissing('table tfoot') ->assertSeeIn('table + .hint', '¹ applied discount: 10% - Test voucher'); }); // We don't expect John's domains here $browser->assertSeeIn('@nav #tab-domains', 'Domains (0)') ->click('@nav #tab-domains') ->with('@user-domains', function (Browser $browser) { $browser->assertElementsCount('table tbody tr', 0) ->assertSeeIn('table tfoot tr td', 'There are no domains in this account.'); }); // We don't expect John's users here $browser->assertSeeIn('@nav #tab-users', 'Users (0)') ->click('@nav #tab-users') ->with('@user-users', function (Browser $browser) { $browser->assertElementsCount('table tbody tr', 0) ->assertSeeIn('table tfoot tr td', 'There are no users in this account.'); }); }); } + + /** + * Test editing wallet discount + * + * @depends testUserInfo2 + */ + public function testWalletDiscount(): void + { + $this->browse(function (Browser $browser) { + $john = $this->getTestUser('john@kolab.org'); + + $browser->visit(new UserPage($john->id)) + ->pause(100) + ->click('@user-finances #discount button') + // Test dialog content, and closing it with Cancel button + ->with(new Dialog('#discount-dialog'), function (Browser $browser) { + $browser->assertSeeIn('@title', 'Account discount') + ->assertFocused('@body select') + ->assertSelected('@body select', '') + ->assertSeeIn('@button-cancel', 'Cancel') + ->assertSeeIn('@button-action', 'Submit') + ->click('@button-cancel'); + }) + ->assertMissing('#discount-dialog') + ->click('@user-finances #discount button') + // Change the discount + ->with(new Dialog('#discount-dialog'), function (Browser $browser) { + $browser->click('@body select') + ->click('@body select option:nth-child(2)') + ->click('@button-action'); + }) + ->assertToast(Toast::TYPE_SUCCESS, '', 'User wallet updated successfully.') + ->assertSeeIn('#discount span', '10% - Test voucher') + ->click('@nav #tab-subscriptions') + ->with('@user-subscriptions', function (Browser $browser) { + $browser->assertSeeIn('table tbody tr:nth-child(1) td:last-child', '3,99 CHF/month¹') + ->assertSeeIn('table tbody tr:nth-child(2) td:last-child', '0,00 CHF/month¹') + ->assertSeeIn('table tbody tr:nth-child(3) td:last-child', '4,99 CHF/month¹') + ->assertSeeIn('table + .hint', '¹ applied discount: 10% - Test voucher'); + }) + // Change back to 'none' + ->click('@nav #tab-finances') + ->click('@user-finances #discount button') + ->with(new Dialog('#discount-dialog'), function (Browser $browser) { + $browser->click('@body select') + ->click('@body select option:nth-child(1)') + ->click('@button-action'); + }) + ->assertToast(Toast::TYPE_SUCCESS, '', 'User wallet updated successfully.') + ->assertSeeIn('#discount span', 'none') + ->click('@nav #tab-subscriptions') + ->with('@user-subscriptions', function (Browser $browser) { + $browser->assertSeeIn('table tbody tr:nth-child(1) td:last-child', '4,44 CHF/month') + ->assertSeeIn('table tbody tr:nth-child(2) td:last-child', '0,00 CHF/month') + ->assertSeeIn('table tbody tr:nth-child(3) td:last-child', '5,55 CHF/month') + ->assertMissing('table + .hint'); + }); + }); + } } diff --git a/src/tests/Feature/Controller/Admin/DiscountsTest.php b/src/tests/Feature/Controller/Admin/DiscountsTest.php new file mode 100644 index 00000000..3eac8d5e --- /dev/null +++ b/src/tests/Feature/Controller/Admin/DiscountsTest.php @@ -0,0 +1,61 @@ +getTestUser('john@kolab.org'); + $admin = $this->getTestUser('jeroen@jeroen.jeroen'); + + // Non-admin user + $response = $this->actingAs($user)->get("api/v4/discounts"); + $response->assertStatus(403); + + // Admin user + $response = $this->actingAs($admin)->get("api/v4/discounts"); + $response->assertStatus(200); + + $json = $response->json(); + + $discount_test = Discount::where('code', 'TEST')->first(); + $discount_free = Discount::where('discount', 100)->first(); + + $this->assertSame(3, $json['count']); + $this->assertSame($discount_test->id, $json['list'][0]['id']); + $this->assertSame($discount_test->discount, $json['list'][0]['discount']); + $this->assertSame($discount_test->code, $json['list'][0]['code']); + $this->assertSame($discount_test->description, $json['list'][0]['description']); + $this->assertSame('10% - Test voucher [TEST]', $json['list'][0]['label']); + + $this->assertSame($discount_free->id, $json['list'][2]['id']); + $this->assertSame($discount_free->discount, $json['list'][2]['discount']); + $this->assertSame($discount_free->code, $json['list'][2]['code']); + $this->assertSame($discount_free->description, $json['list'][2]['description']); + $this->assertSame('100% - Free Account', $json['list'][2]['label']); + } +} diff --git a/src/tests/Feature/Controller/Admin/WalletsTest.php b/src/tests/Feature/Controller/Admin/WalletsTest.php new file mode 100644 index 00000000..9c42edbf --- /dev/null +++ b/src/tests/Feature/Controller/Admin/WalletsTest.php @@ -0,0 +1,70 @@ +getTestUser('john@kolab.org'); + $admin = $this->getTestUser('jeroen@jeroen.jeroen'); + $wallet = $user->wallets()->first(); + $discount = Discount::where('code', 'TEST')->first(); + + // Non-admin user + $response = $this->actingAs($user)->put("api/v4/wallets/{$wallet->id}", []); + $response->assertStatus(403); + + // Admin user - setting a discount + $post = ['discount' => $discount->id]; + $response = $this->actingAs($admin)->put("api/v4/wallets/{$wallet->id}", $post); + $response->assertStatus(200); + + $json = $response->json(); + + $this->assertSame('success', $json['status']); + $this->assertSame('User wallet updated successfully.', $json['message']); + $this->assertSame($wallet->id, $json['id']); + $this->assertSame($discount->discount, $json['discount']); + $this->assertSame($discount->id, $json['discount_id']); + $this->assertSame($discount->description, $json['discount_description']); + $this->assertSame($discount->id, $wallet->fresh()->discount->id); + + // Admin user - removing a discount + $post = ['discount' => null]; + $response = $this->actingAs($admin)->put("api/v4/wallets/{$wallet->id}", $post); + $response->assertStatus(200); + + $json = $response->json(); + + $this->assertSame('success', $json['status']); + $this->assertSame('User wallet updated successfully.', $json['message']); + $this->assertSame($wallet->id, $json['id']); + $this->assertSame(null, $json['discount_id']); + $this->assertTrue(empty($json['discount_description'])); + $this->assertSame(null, $wallet->fresh()->discount); + } +}