diff --git a/database/factories/TwoFAccountFactory.php b/database/factories/TwoFAccountFactory.php index 8ac0c98c..7e93ca3c 100644 --- a/database/factories/TwoFAccountFactory.php +++ b/database/factories/TwoFAccountFactory.php @@ -20,14 +20,19 @@ $factory->define(TwoFAccount::class, function (Faker $faker) { + $account = $faker->safeEmail; + $service = $faker->unique()->domainName; + $secret = Base32::encodeUpper($faker->regexify('[A-Z0-9]{8}')); + return [ 'otp_type' => 'totp', - 'account' => $faker->safeEmail, - 'service' => $faker->unique()->domainName, - 'secret' => Base32::encodeUpper($faker->regexify('[A-Z0-9]{8}')), + 'account' => $account, + 'service' => $service, + 'secret' => $secret, 'algorithm' => 'sha1', 'digits' => 6, 'period' => 30, + 'legacy_uri' => 'otpauth://hotp/' . $service . ':' . $account . '?secret=' . $secret . '&issuer=' . $service, 'icon' => '', ]; }); \ No newline at end of file diff --git a/tests/Api/v1/Controllers/GroupControllerTest.php b/tests/Api/v1/Controllers/GroupControllerTest.php new file mode 100644 index 00000000..c322eb90 --- /dev/null +++ b/tests/Api/v1/Controllers/GroupControllerTest.php @@ -0,0 +1,336 @@ +user = factory(User::class)->create(); + } + + + /** + * @test + */ + public function test_index_returns_group_collection_with_pseudo_group() + { + factory(Group::class, 3)->create(); + + $response = $this->actingAs($this->user, 'api') + ->json('GET', '/api/v1/groups') + ->assertOk() + ->assertJsonCount(4, $key = null) + ->assertJsonStructure([ + '*' => [ + 'id', + 'name', + 'twofaccounts_count', + ] + ]) + ->assertJsonFragment([ + 'id' => 0, + 'name' => 'All', + 'twofaccounts_count' => 0, + ]); + } + + + /** + * @test + */ + public function test_store_returns_created_group_resource() + { + $response = $this->actingAs($this->user, 'api') + ->json('POST', '/api/v1/groups', [ + 'name' => 'My second group', + ]) + ->assertCreated() + ->assertExactJson([ + 'id' => 1, + 'name' => 'My second group', + 'twofaccounts_count' => 0, + ]); + } + + + /** + * @test + */ + public function test_store_invalid_data_returns_validation_error() + { + $response = $this->actingAs($this->user, 'api') + ->json('POST', '/api/v1/groups', [ + 'name' => null, + ]) + ->assertStatus(422); + } + + + /** + * @test + */ + public function test_show_returns_group_resource() + { + $group = factory(Group::class)->create([ + 'name' => 'My group', + ]); + + $response = $this->actingAs($this->user, 'api') + ->json('GET', '/api/v1/groups/' . $group->id) + ->assertOk() + ->assertExactJson([ + 'id' => 1, + 'name' => 'My group', + 'twofaccounts_count' => 0, + ]); + } + + + /** + * @test + */ + public function test_show_missing_group_returns_not_found() + { + $response = $this->actingAs($this->user, 'api') + ->json('GET', '/api/v1/groups/1000') + ->assertNotFound() + ->assertJsonStructure([ + 'message' + ]); + } + + + /** + * @test + */ + public function test_update_returns_updated_group_resource() + { + $group = factory(Group::class)->create(); + + $response = $this->actingAs($this->user, 'api') + ->json('PUT', '/api/v1/groups/' . $group->id, [ + 'name' => 'name updated', + ]) + ->assertOk() + ->assertExactJson([ + 'id' => 1, + 'name' => 'name updated', + 'twofaccounts_count' => 0, + ]); + } + + + /** + * @test + */ + public function test_update_missing_group_returns_not_found() + { + $response = $this->actingAs($this->user, 'api') + ->json('PUT', '/api/v1/groups/1000', [ + 'name' => 'testUpdate', + ]) + ->assertNotFound() + ->assertJsonStructure([ + 'message' + ]); + } + + + /** + * @test + */ + public function test_update_with_invalid_data_returns_validation_error() + { + $group = factory(Group::class)->create(); + + $response = $this->actingAs($this->user, 'api') + ->json('PUT', '/api/v1/groups/' . $group->id, [ + 'name' => null, + ]) + ->assertStatus(422); + } + + + /** + * @test + */ + public function test_assign_accounts_returns_updated_group_resource() + { + $group = factory(Group::class)->create(); + $accounts = factory(TwoFAccount::class, 2)->create(); + + $response = $this->actingAs($this->user, 'api') + ->json('POST', '/api/v1/groups/' . $group->id . '/assign', [ + 'ids' => [1,2], + ]) + ->assertOk() + ->assertExactJson([ + 'id' => $group->id, + 'name' => $group->name, + 'twofaccounts_count' => 2, + ]); + } + + + /** + * @test + */ + public function test_assign_accounts_to_missing_group_returns_not_found() + { + $accounts = factory(TwoFAccount::class, 2)->create(); + + $response = $this->actingAs($this->user, 'api') + ->json('POST', '/api/v1/groups/1000/assign', [ + 'ids' => [1,2], + ]) + ->assertNotFound() + ->assertJsonStructure([ + 'message' + ]); + } + + + /** + * @test + */ + public function test_assign_invalid_accounts_returns_validation_error() + { + $group = factory(Group::class)->create(); + $accounts = factory(TwoFAccount::class, 2)->create(); + + $response = $this->actingAs($this->user, 'api') + ->json('POST', '/api/v1/groups/' . $group->id . '/assign', [ + 'ids' => 1, + ]) + ->assertStatus(422); + } + + + /** + * @test + */ + public function test_get_assigned_accounts_returns_twofaccounts_collection() + { + $group = factory(Group::class)->create(); + $accounts = factory(TwoFAccount::class, 2)->create(); + + $assign = $this->actingAs($this->user, 'api') + ->json('POST', '/api/v1/groups/' . $group->id . '/assign', [ + 'ids' => [1,2], + ]); + + $response = $this->actingAs($this->user, 'api') + ->json('GET', '/api/v1/groups/' . $group->id . '/twofaccounts') + ->assertOk() + ->assertJsonCount(2) + ->assertJsonStructure([ + '*' => [ + 'group_id', + 'service', + 'account', + 'icon', + 'otp_type', + 'digits', + 'algorithm', + 'period', + 'counter' + ] + ]); + } + + + /** + * @test + */ + public function test_get_assigned_accounts_returns_twofaccounts_collection_with_secret() + { + $group = factory(Group::class)->create(); + $accounts = factory(TwoFAccount::class, 2)->create(); + + $assign = $this->actingAs($this->user, 'api') + ->json('POST', '/api/v1/groups/' . $group->id . '/assign', [ + 'ids' => [1,2], + ]); + + $response = $this->actingAs($this->user, 'api') + ->json('GET', '/api/v1/groups/' . $group->id . '/twofaccounts?withSecret=1') + ->assertOk() + ->assertJsonCount(2) + ->assertJsonStructure([ + '*' => [ + 'group_id', + 'service', + 'account', + 'icon', + 'secret', + 'otp_type', + 'digits', + 'algorithm', + 'period', + 'counter' + ] + ]); + } + + + /** + * @test + */ + public function test_get_assigned_accounts_of_missing_group_returns_not_found() + { + $response = $this->actingAs($this->user, 'api') + ->json('GET', '/api/v1/groups/1000/twofaccounts') + ->assertNotFound() + ->assertJsonStructure([ + 'message' + ]); + } + + + /** + * test Group deletion via API + * + * @test + */ + public function test_destroy_group_returns_success() + { + $group = factory(Group::class)->create(); + + $response = $this->actingAs($this->user, 'api') + ->json('DELETE', '/api/v1/groups/' . $group->id) + ->assertNoContent(); + } + + + /** + * test Group deletion via API + * + * @test + */ + public function test_destroy_missing_group_returns_not_found() + { + $response = $this->actingAs($this->user, 'api') + ->json('DELETE', '/api/v1/groups/1000') + ->assertNotFound() + ->assertJsonStructure([ + 'message' + ]); + } +} diff --git a/tests/Api/v1/Controllers/IconControllerTest.php b/tests/Api/v1/Controllers/IconControllerTest.php new file mode 100644 index 00000000..b29344f8 --- /dev/null +++ b/tests/Api/v1/Controllers/IconControllerTest.php @@ -0,0 +1,65 @@ +image('testIcon.jpg'); + + $response = $this->json('POST', '/api/v1/icons', [ + 'icon' => $file, + ]) + ->assertCreated() + ->assertJsonStructure([ + 'filename' + ]); + } + + + /** + * @test + */ + public function test_upload_with_invalid_data_returns_validation_error() + { + $response = $this->json('POST', '/api/v1/icons', [ + 'icon' => null, + ]) + ->assertStatus(422); + } + + + /** + * @test + */ + public function test_delete_icon_returns_success() + { + $response = $this->json('DELETE', '/api/v1/icons/testIcon.jpg') + ->assertNoContent(204); + + } + + + /** + * @test + */ + public function test_delete_invalid_icon_returns_success() + { + $response = $this->json('DELETE', '/api/v1/icons/null') + ->assertNoContent(204); + + } + +} \ No newline at end of file diff --git a/tests/Api/v1/Controllers/QrcodeControllerTest.php b/tests/Api/v1/Controllers/QrcodeControllerTest.php new file mode 100644 index 00000000..1f3c5168 --- /dev/null +++ b/tests/Api/v1/Controllers/QrcodeControllerTest.php @@ -0,0 +1,123 @@ +user = factory(User::class)->create(); + } + + + /** + * @test + */ + public function test_show_qrcode_returns_base64_image() + { + $twofaccount = factory(TwoFAccount::class)->create([ + 'otp_type' => 'totp', + 'account' => 'account', + 'service' => 'service', + 'secret' => 'A4GRFHZVRBGY7UIW', + 'algorithm' => 'sha1', + 'digits' => 6, + 'period' => 30, + 'legacy_uri' => 'otpauth://hotp/service:account?secret=A4GRFHZVRBGY7UIW&issuer=service', + ]); + + $response = $this->actingAs($this->user, 'api') + ->json('GET', '/api/v1/twofaccounts/' . $twofaccount->id . '/qrcode') + ->assertJsonStructure([ + 'qrcode', + ]) + ->assertOk(); + + $this->assertStringStartsWith('data:image/png;base64', $response->getData()->qrcode); + } + + + /** + * @test + */ + public function test_show_missing_qrcode_returns_not_found() + { + $response = $this->actingAs($this->user, 'api') + ->json('GET', '/api/v1/twofaccounts/1000/qrcode') + ->assertNotFound() + ->assertJsonStructure([ + 'message' + ]); + } + + + /** + * @test + */ + public function test_decode_qrcode_return_success() + { + $file = LocalFile::fake()->validQrcode(); + + $response = $this->withHeaders(['Content-Type' => 'multipart/form-data']) + ->actingAs($this->user, 'api') + ->json('POST', '/api/v1/qrcode/decode', [ + 'qrcode' => $file, + 'inputFormat' => 'fileUpload' + ]) + ->assertOk() + ->assertExactJson([ + 'data' => 'otpauth://totp/test@test.com?secret=A4GRFHVIRBGY7UIW', + ]); + } + + + /** + * @test + */ + public function test_decode_missing_qrcode_return_validation_error() + { + + $response = $this->actingAs($this->user, 'api') + ->json('POST', '/api/v1/qrcode/decode', [ + 'qrcode' => '', + ]) + ->assertStatus(422); + } + + + /** + * @test + */ + public function test_decode_invalid_qrcode_return_bad_request() + { + $file = LocalFile::fake()->invalidQrcode(); + + $response = $this->withHeaders(['Content-Type' => 'multipart/form-data']) + ->actingAs($this->user, 'api') + ->json('POST', '/api/v1/qrcode/decode', [ + 'qrcode' => $file, + 'inputFormat' => 'fileUpload' + ]) + ->assertStatus(400) + ->assertJsonStructure([ + 'message', + ]); + } +} \ No newline at end of file diff --git a/tests/Api/v1/Controllers/TwoFAccountControllerTest.php b/tests/Api/v1/Controllers/TwoFAccountControllerTest.php new file mode 100644 index 00000000..19b244bb --- /dev/null +++ b/tests/Api/v1/Controllers/TwoFAccountControllerTest.php @@ -0,0 +1,985 @@ +user = factory(User::class)->create(); + } + + + /** + * @test + */ + public function test_index_returns_twofaccount_collection() + { + $twofaccount = factory(TwoFAccount::class, 3)->create(); + + $response = $this->actingAs($this->user, 'api') + ->json('GET', '/api/v1/twofaccounts') + ->assertOk() + ->assertJsonCount(3, $key = null) + ->assertJsonStructure([ + '*' => [ + 'id', + 'group_id', + 'service', + 'account', + 'icon', + 'otp_type', + 'digits', + 'algorithm', + 'period', + 'counter' + ] + ] + ); + } + + + /** + * @test + */ + public function test_index_returns_twofaccount_collection_with_secret() + { + $twofaccount = factory(TwoFAccount::class, 3)->create(); + + $response = $this->actingAs($this->user, 'api') + ->json('GET', '/api/v1/twofaccounts?withSecret=1') + ->assertOk() + ->assertJsonCount(3, $key = null) + ->assertJsonStructure([ + '*' => [ + 'id', + 'group_id', + 'service', + 'account', + 'icon', + 'otp_type', + 'secret', + 'digits', + 'algorithm', + 'period', + 'counter' + ] + ] + ); + } + + + /** + * @test + */ + public function test_show_twofaccount_returns_twofaccount_resource() + { + $twofaccount = factory(TwoFAccount::class)->create(); + + $response = $this->actingAs($this->user, 'api') + ->json('GET', '/api/v1/twofaccounts/' . $twofaccount->id) + ->assertOk() + ->assertJsonStructure([ + 'id', + 'group_id', + 'service', + 'account', + 'icon', + 'otp_type', + 'secret', + 'digits', + 'algorithm', + 'period', + 'counter' + ]); + } + + + /** + * @test + */ + public function test_show_twofaccount_returns_twofaccount_resource_without_secret() + { + $twofaccount = factory(TwoFAccount::class)->create(); + + $response = $this->actingAs($this->user, 'api') + ->json('GET', '/api/v1/twofaccounts/' . $twofaccount->id . '?withSecret=0') + ->assertOk() + ->assertJsonStructure([ + 'id', + 'group_id', + 'service', + 'account', + 'icon', + 'otp_type', + 'digits', + 'algorithm', + 'period', + 'counter' + ]); + } + + + /** + * @test + */ + public function test_show_missing_twofaccount_returns_not_found() + { + $response = $this->actingAs($this->user, 'api') + ->json('GET', '/api/v1/twofaccounts/1000') + ->assertNotFound() + ->assertJsonStructure([ + 'message' + ]); + } + + + /** + * @dataProvider provideDataForTestStoreStructure + * @test + */ + public function test_store_returns_success_with_consistent_resource_structure(array $data) + { + Storage::put('test.png', 'emptied to prevent missing resource replaced by null by the model getter'); + + $response = $this->actingAs($this->user, 'api') + ->json('POST', '/api/v1/twofaccounts', $data) + ->assertCreated() + ->assertJsonStructure([ + 'id', + 'group_id', + 'service', + 'account', + 'icon', + 'otp_type', + 'secret', + 'digits', + 'algorithm', + 'period', + 'counter' + ]); + } + + + /** + * Provide data for TwoFAccount store test + */ + public function provideDataForTestStoreStructure() : array + { + return [ + [[ + 'uri' => self::TOTP_FULL_CUSTOM_URI, + ]], + [[ + 'uri' => self::TOTP_SHORT_URI, + ]], + [[ + 'service' => self::SERVICE, + 'account' => self::ACCOUNT, + 'icon' => self::ICON, + 'otp_type' => 'totp', + 'secret' => self::SECRET, + 'digits' => self::DIGITS_CUSTOM, + 'algorithm' => self::ALGORITHM_CUSTOM, + 'period' => self::PERIOD_CUSTOM, + 'counter' => null, + ]], + [[ + 'account' => self::ACCOUNT, + 'otp_type' => 'totp', + 'secret' => self::SECRET, + ]], + [[ + 'uri' => self::HOTP_FULL_CUSTOM_URI, + ]], + [[ + 'uri' => self::HOTP_SHORT_URI, + ]], + [[ + 'service' => self::SERVICE, + 'account' => self::ACCOUNT, + 'icon' => self::ICON, + 'otp_type' => 'hotp', + 'secret' => self::SECRET, + 'digits' => self::DIGITS_CUSTOM, + 'algorithm' => self::ALGORITHM_CUSTOM, + 'period' => null, + 'counter' => self::COUNTER_CUSTOM, + ]], + [[ + 'account' => self::ACCOUNT, + 'otp_type' => 'hotp', + 'secret' => self::SECRET, + ]], + ]; + } + + + /** + * @test + */ + public function test_store_totp_using_fully_custom_uri_returns_consistent_resource() + { + $response = $this->actingAs($this->user, 'api') + ->json('POST', '/api/v1/twofaccounts', [ + 'uri' => self::TOTP_FULL_CUSTOM_URI, + ]) + ->assertJsonFragment([ + 'service' => self::SERVICE, + 'account' => self::ACCOUNT, + 'otp_type' => 'totp', + 'secret' => self::SECRET, + 'digits' => self::DIGITS_CUSTOM, + 'algorithm' => self::ALGORITHM_CUSTOM, + 'period' => self::PERIOD_CUSTOM, + 'counter' => null, + ]); + } + + + /** + * @test + */ + public function test_store_totp_using_short_uri_returns_resource_with_default_otp_parameter() + { + $response = $this->actingAs($this->user, 'api') + ->json('POST', '/api/v1/twofaccounts', [ + 'uri' => self::TOTP_SHORT_URI, + ]) + ->assertJsonFragment([ + 'service' => null, + 'account' => self::ACCOUNT, + 'otp_type' => 'totp', + 'secret' => self::SECRET, + 'digits' => self::DIGITS_DEFAULT, + 'algorithm' => self::ALGORITHM_DEFAULT, + 'period' => self::PERIOD_DEFAULT, + 'counter' => null, + ]); + } + + + /** + * @test + */ + public function test_store_totp_using_fully_custom_parameters_returns_consistent_resource() + { + $response = $this->actingAs($this->user, 'api') + ->json('POST', '/api/v1/twofaccounts', [ + 'service' => self::SERVICE, + 'account' => self::ACCOUNT, + 'otp_type' => 'totp', + 'secret' => self::SECRET, + 'digits' => self::DIGITS_CUSTOM, + 'algorithm' => self::ALGORITHM_CUSTOM, + 'period' => self::PERIOD_CUSTOM, + 'counter' => null, + ]) + ->assertJsonFragment([ + 'service' => self::SERVICE, + 'account' => self::ACCOUNT, + 'otp_type' => 'totp', + 'secret' => self::SECRET, + 'digits' => self::DIGITS_CUSTOM, + 'algorithm' => self::ALGORITHM_CUSTOM, + 'period' => self::PERIOD_CUSTOM, + 'counter' => null, + ]); + } + + + /** + * @test + */ + public function test_store_totp_using_minimum_parameters_returns_consistent_resource() + { + $response = $this->actingAs($this->user, 'api') + ->json('POST', '/api/v1/twofaccounts', [ + 'account' => self::ACCOUNT, + 'otp_type' => 'totp', + 'secret' => self::SECRET, + ]) + ->assertJsonFragment([ + 'service' => null, + 'account' => self::ACCOUNT, + 'otp_type' => 'totp', + 'secret' => self::SECRET, + 'digits' => self::DIGITS_DEFAULT, + 'algorithm' => self::ALGORITHM_DEFAULT, + 'period' => self::PERIOD_DEFAULT, + 'counter' => null, + ]); + } + + + /** + * @test + */ + public function test_store_hotp_using_fully_custom_uri_returns_consistent_resource() + { + $response = $this->actingAs($this->user, 'api') + ->json('POST', '/api/v1/twofaccounts', [ + 'uri' => self::HOTP_FULL_CUSTOM_URI, + ]) + ->assertJsonFragment([ + 'service' => self::SERVICE, + 'account' => self::ACCOUNT, + 'otp_type' => 'hotp', + 'secret' => self::SECRET, + 'digits' => self::DIGITS_CUSTOM, + 'algorithm' => self::ALGORITHM_CUSTOM, + 'period' => null, + 'counter' => self::COUNTER_CUSTOM, + ]); + } + + + /** + * @test + */ + public function test_store_hotp_using_short_uri_returns_resource_with_default_otp_parameter() + { + $response = $this->actingAs($this->user, 'api') + ->json('POST', '/api/v1/twofaccounts', [ + 'uri' => self::HOTP_SHORT_URI, + ]) + ->assertJsonFragment([ + 'service' => null, + 'account' => self::ACCOUNT, + 'otp_type' => 'hotp', + 'secret' => self::SECRET, + 'digits' => self::DIGITS_DEFAULT, + 'algorithm' => self::ALGORITHM_DEFAULT, + 'period' => null, + 'counter' => self::COUNTER_DEFAULT, + ]); + } + + + /** + * @test + */ + public function test_store_hotp_using_fully_custom_parameters_returns_consistent_resource() + { + $response = $this->actingAs($this->user, 'api') + ->json('POST', '/api/v1/twofaccounts', [ + 'service' => self::SERVICE, + 'account' => self::ACCOUNT, + 'otp_type' => 'hotp', + 'secret' => self::SECRET, + 'digits' => self::DIGITS_CUSTOM, + 'algorithm' => self::ALGORITHM_CUSTOM, + 'period' => null, + 'counter' => self::COUNTER_CUSTOM, + ]) + ->assertJsonFragment([ + 'service' => self::SERVICE, + 'account' => self::ACCOUNT, + 'otp_type' => 'hotp', + 'secret' => self::SECRET, + 'digits' => self::DIGITS_CUSTOM, + 'algorithm' => self::ALGORITHM_CUSTOM, + 'period' => null, + 'counter' => self::COUNTER_CUSTOM, + ]); + } + + + /** + * @test + */ + public function test_store_hotp_using_minimum_parameters_returns_consistent_resource() + { + $response = $this->actingAs($this->user, 'api') + ->json('POST', '/api/v1/twofaccounts', [ + 'account' => self::ACCOUNT, + 'otp_type' => 'hotp', + 'secret' => self::SECRET, + ]) + ->assertJsonFragment([ + 'service' => null, + 'account' => self::ACCOUNT, + 'otp_type' => 'hotp', + 'secret' => self::SECRET, + 'digits' => self::DIGITS_DEFAULT, + 'algorithm' => self::ALGORITHM_DEFAULT, + 'period' => null, + 'counter' => self::COUNTER_DEFAULT, + ]); + } + + + /** + * @test + */ + public function test_store_with_invalid_uri_returns_validation_error() + { + $response = $this->actingAs($this->user, 'api') + ->json('POST', '/api/v1/twofaccounts', [ + 'uri' => self::INVALID_OTPAUTH_URI, + ]) + ->assertStatus(422); + } + + + /** + * @test + */ + public function test_update_totp_returns_success_with_updated_resource() + { + $twofaccount = factory(TwoFAccount::class)->create(); + + $response = $this->actingAs($this->user, 'api') + ->json('PUT', '/api/v1/twofaccounts/' . $twofaccount->id, [ + 'service' => self::SERVICE, + 'account' => self::ACCOUNT, + 'icon' => self::ICON, + 'otp_type' => 'totp', + 'secret' => self::SECRET, + 'digits' => self::DIGITS_CUSTOM, + 'algorithm' => self::ALGORITHM_CUSTOM, + 'period' => self::PERIOD_CUSTOM, + 'counter' => null, + ]) + ->assertOk() + ->assertJsonFragment([ + 'service' => self::SERVICE, + 'account' => self::ACCOUNT, + 'icon' => self::ICON, + 'otp_type' => 'totp', + 'secret' => self::SECRET, + 'digits' => self::DIGITS_CUSTOM, + 'algorithm' => self::ALGORITHM_CUSTOM, + 'period' => self::PERIOD_CUSTOM, + 'counter' => null, + ]); + } + + + /** + * @test + */ + public function test_update_hotp_returns_success_with_updated_resource() + { + $twofaccount = factory(TwoFAccount::class)->create(); + + $response = $this->actingAs($this->user, 'api') + ->json('PUT', '/api/v1/twofaccounts/' . $twofaccount->id, [ + 'service' => self::SERVICE, + 'account' => self::ACCOUNT, + 'icon' => self::ICON, + 'otp_type' => 'hotp', + 'secret' => self::SECRET, + 'digits' => self::DIGITS_CUSTOM, + 'algorithm' => self::ALGORITHM_CUSTOM, + 'period' => null, + 'counter' => self::COUNTER_CUSTOM, + ]) + ->assertOk() + ->assertJsonFragment([ + 'service' => self::SERVICE, + 'account' => self::ACCOUNT, + 'icon' => self::ICON, + 'otp_type' => 'hotp', + 'secret' => self::SECRET, + 'digits' => self::DIGITS_CUSTOM, + 'algorithm' => self::ALGORITHM_CUSTOM, + 'period' => null, + 'counter' => self::COUNTER_CUSTOM, + ]); + } + + + /** + * @test + */ + public function test_update_missing_twofaccount_returns_not_found() + { + $response = $this->actingAs($this->user, 'api') + ->json('PUT', '/api/v1/twofaccounts/1000', [ + 'service' => self::SERVICE, + 'account' => self::ACCOUNT, + 'icon' => self::ICON, + 'otp_type' => 'totp', + 'secret' => self::SECRET, + 'digits' => self::DIGITS_CUSTOM, + 'algorithm' => self::ALGORITHM_CUSTOM, + 'period' => self::PERIOD_CUSTOM, + 'counter' => null, + ]) + ->assertNotFound(); + } + + + /** + * @test + */ + public function test_update_twofaccount_with_invalid_data_returns_validation_error() + { + $twofaccount = factory(TwoFAccount::class)->create(); + + $response = $this->actingAs($this->user, 'api') + ->json('PUT', '/api/v1/twofaccounts/' . $twofaccount->id, [ + 'service' => self::SERVICE, + 'account' => null, + 'icon' => self::ICON, + 'otp_type' => 'totp', + 'secret' => self::SECRET, + 'digits' => self::DIGITS_CUSTOM, + 'algorithm' => self::ALGORITHM_CUSTOM, + 'period' => self::PERIOD_CUSTOM, + 'counter' => null, + ]) + ->assertStatus(422); + } + + + + + + + + + + + + + + + + + + + + + + + + /** + * test Hotp TwoFAccount display via API + * + * @test + */ + public function testHotpTwofaccountDisplayWithCounterIncrement() + { + $twofaccount = factory(TwoFAccount::class)->create([ + 'service' => 'testTOTP', + 'account' => 'test@test.com', + 'uri' => 'otpauth://hotp/test@test.com?secret=A4GRFHVVRBGY7UIW&issuer=test&counter=1', + ]); + + $response = $this->actingAs($this->user, 'api') + ->json('POST', '/api/v1/twofaccounts/otp', ['id' => $twofaccount->id]) + ->assertStatus(200); + + $response = $this->actingAs($this->user, 'api') + ->json('GET', '/api/v1/twofaccounts/' . $twofaccount->id) + ->assertStatus(200) + ->assertJsonFragment([ + 'service' => 'testTOTP', + 'account' => 'test@test.com', + 'group_id' => null, + 'isConsistent' => true, + 'otpType' => 'hotp', + 'digits' => 6, + 'hotpCounter' => 2, + 'imageLink' => null, + ]) + ->assertJsonMissing([ + 'uri' => 'otpauth://hotp/test@test.com?secret=A4GRFHVVRBGY7UIW&issuer=test', + 'secret' => 'A4GRFHVVRBGY7UIW', + 'algorithm' => 'sha1', + ]); + } + + + /** + * test TwoFAccount preview via API + * + * @test + */ + public function testTwofaccountPreview() + { + Storage::put('test.png', 'emptied to prevent missing resource replaced by null by the model getter'); + + $response = $this->actingAs($this->user, 'api') + ->json('POST', '/api/v1/twofaccounts/preview', [ + 'uri' => 'otpauth://totp/service:account?secret=A4GRFHVVRBGY7UIW&issuer=service&image=https%3A%2F%2Fen.opensuse.org%2Fimages%2F4%2F44%2FButton-filled-colour.png', + ]) + ->assertStatus(200) + ->assertJsonFragment([ + 'service' => 'service', + 'account' => 'account', + 'uri' => 'otpauth://totp/service:account?secret=A4GRFHVVRBGY7UIW&issuer=service&image=https%3A%2F%2Fen.opensuse.org%2Fimages%2F4%2F44%2FButton-filled-colour.png', + 'secret' => 'A4GRFHVVRBGY7UIW', + 'algorithm' => 'sha1', + 'otpType' => 'totp', + 'digits' => 6, + 'totpPeriod' => 30, + 'hotpCounter' => null, + 'imageLink' => 'https://en.opensuse.org/images/4/44/Button-filled-colour.png', + ]) + ->assertJsonStructure([ + 'icon' + ]); + } + + + /** + * test TwoFAccount preview with unreachable image parameter via API + * + * @test + */ + public function testTwofaccountPreviewWithUnreachableImage() + { + + $response = $this->actingAs($this->user, 'api') + ->json('POST', '/api/v1/twofaccounts/preview', [ + 'uri' => 'otpauth://totp/service:account?secret=A4GRFHVVRBGY7UIW&issuer=service&image=https%3A%2F%2Fen.opensuse.org%2Fimage.png', + ]) + ->assertStatus(200) + ->assertJsonMissing([ + 'icon' + ]); + } + + + /** + * test show account when uri field remains encrypted via API + * + * @test + */ + public function testShowAccountWithUndecipheredUri() + { + $response = $this->actingAs($this->user, 'api') + ->json('POST', '/api/v1/twofaccounts', [ + 'service' => 'testCreation', + 'account' => 'test@example.org', + 'uri' => 'otpauth://totp/test@test.com?secret=A4GRFHZVRBGY7UIW&issuer=test', + 'icon' => 'test.png', + ]) + ->assertStatus(201); + + DB::table('twofaccounts') + ->where('id', 1) + ->update([ + 'uri' => '**encrypted**', + ]); + + $response = $this->actingAs($this->user, 'api') + ->json('GET', '/api/v1/twofaccounts/1') + ->assertStatus(200) + ->assertJsonFragment([ + 'isConsistent' => false, + ]); + } + + + /** + * test totp token generation for a given existing account via API + * + * @test + */ + public function testTotpTokenGenerationWithAccountId() + { + $twofaccount = factory(TwoFAccount::class)->create([ + 'service' => 'testService', + 'account' => 'testAccount', + 'uri' => 'otpauth://totp/testService:testAccount?secret=A4GRFHVVRBGY7UIW&issuer=testService' + ]); + + $response = $this->actingAs($this->user, 'api') + ->json('POST', '/api/v1/twofaccounts/otp', ['id' => $twofaccount->id]) + ->assertStatus(200) + ->assertJsonStructure([ + 'token', + 'totpTimestamp', + 'totpPeriod', + ]); + } + + + /** + * test hotp token generation for a given existing account via API + * + * @test + */ + public function testHotpTokenGenerationWithAccountId() + { + $twofaccount = factory(TwoFAccount::class)->create([ + 'service' => 'testService', + 'account' => 'testAccount', + 'uri' => 'otpauth://hotp/testService:testAccount?secret=A4GRFHVVRBGY7UIW&issuer=testService&counter=1' + ]); + + $response = $this->actingAs($this->user, 'api') + ->json('POST', '/api/v1/twofaccounts/otp', ['id' => $twofaccount->id]) + ->assertStatus(200) + ->assertJsonStructure([ + 'token', + ]); + } + + + /** + * test token generation by providing an URI via API + * + * @test + */ + public function testTokenGenerationWithUri() + { + $uri = 'otpauth://totp/service:account?secret=A4GRFHVVRBGY7UIW&issuer=service'; + + $response = $this->actingAs($this->user, 'api') + ->json('POST', '/api/v1/twofaccounts/otp', ['otp' => ['uri' => $uri]]) + ->assertStatus(200) + ->assertJsonStructure([ + 'token', + 'totpTimestamp', + 'totpPeriod', + ]); + } + + + /** + * test totp token generation by providing an array of otp attributes without URI via API + * + * @test + */ + public function testTotpTokenGenerationWithAttributesArray() + { + $response = $this->actingAs($this->user, 'api') + ->json('POST', '/api/v1/twofaccounts/otp', ['otp' => [ + 'service' => 'service', + 'account' => 'account', + 'otpType' => 'totp', + 'secret' => 'A4GRFHVVRBGY7UIW', + 'secretIsBase32Encoded' => 1, + 'digits' => 6, + 'totpPeriod' => 30, + 'algorithm' => 'sha1', + 'uri' => '' + ]]) + ->assertStatus(200) + ->assertJsonStructure([ + 'token', + 'totpTimestamp', + 'totpPeriod', + ]); + } + + + /** + * test hotp token generation by providing an array of otp attributes without URI via API + * + * @test + */ + public function testHotpTokenGenerationWithAttributesArray() + { + $response = $this->actingAs($this->user, 'api') + ->json('POST', '/api/v1/twofaccounts/otp', ['otp' => [ + 'service' => 'service', + 'account' => 'account', + 'otpType' => 'hotp', + 'secret' => 'A4GRFHVVRBGY7UIW', + 'secretIsBase32Encoded' => 1, + 'digits' => 6, + 'hotpCounter' => 1, + 'algorithm' => 'sha1', + 'uri' => '' + ]]) + ->assertStatus(200) + ->assertJsonStructure([ + 'token', + 'hotpCounter', + ]); + } + + + /** + * test token generation by providing an array of otp attributes with a bad otp type via API + * + * @test + */ + public function testTokenGenerationWithBadOtptypeAttribute() + { + $response = $this->actingAs($this->user, 'api') + ->json('POST', '/api/v1/twofaccounts/otp', ['otp' => [ + 'service' => 'service', + 'account' => 'account', + 'otpType' => 'otp', + 'secret' => 'A4GRFHVVRBGY7UIW', + 'secretIsBase32Encoded' => 1, + 'digits' => 6, + 'totpPeriod' => 30, + 'algorithm' => 'sha1', + 'uri' => '' + ]]) + ->assertStatus(422) + ->assertJsonStructure([ + 'errors' => [ + 'otpType' + ] + ]); + } + + + /** + * test token generation by providing an array of otp attributes without secret via API + * + * @test + */ + public function testTokenGenerationWithMissingSecretAttribute() + { + $response = $this->actingAs($this->user, 'api') + ->json('POST', '/api/v1/twofaccounts/otp', ['otp' => [ + 'service' => 'service', + 'account' => 'account', + 'otpType' => 'totp', + 'secret' => 'A4GRFHVVRBGY7UIW', + 'secretIsBase32Encoded' => 1, + 'digits' => 'x', + 'totpPeriod' => 'y', + 'algorithm' => 'sha1', + 'uri' => '' + ]]) + ->assertStatus(422) + ->assertJsonStructure([ + 'errors' => [ + 'qrcode' + ] + ]); + } + + + /** + * test token generation by providing an array of bad attributes via API + * + * @test + */ + public function testTokenGenerationWithBadAttribute() + { + $response = $this->actingAs($this->user, 'api') + ->json('POST', '/api/v1/twofaccounts/otp', ['otp' => [ + 'service' => 'service', + 'account' => 'account', + 'otpType' => 'totp', + 'secret' => '', + 'secretIsBase32Encoded' => 1, + 'digits' => 6, + 'totpPeriod' => 30, + 'algorithm' => 'sha1', + 'uri' => '' + ]]) + ->assertStatus(422) + ->assertJsonStructure([ + 'errors' => [ + 'secret' + ] + ]); + } + + + /** + * test TwoFAccount index fetching via API + * + * @test + */ + public function testTwofaccountCount() + { + $twofaccount = factory(TwoFAccount::class, 3)->create(); + + $response = $this->actingAs($this->user, 'api') + ->json('GET', '/api/v1/twofaccounts/count') + ->assertStatus(200) + ->assertJson([ + 'count' => 3 + ] + ); + } + + + /** + * test TwoFAccount deletion via API + * + * @test + */ + public function testTwofaccountDeletion() + { + $twofaccount = factory(TwoFAccount::class)->create(); + + $response = $this->actingAs($this->user, 'api') + ->json('DELETE', '/api/v1/twofaccounts/' . $twofaccount->id) + ->assertStatus(204); + } + + + /** + * test TwoFAccounts batch deletion via API + * + * @test + */ + public function testTwofaccountBatchDestroy() + { + factory(TwoFAccount::class, 3)->create(); + + $ids = \Illuminate\Support\Facades\DB::table('twofaccounts')->value('id'); + + $response = $this->actingAs($this->user, 'api') + ->json('DELETE', '/api/v1/twofaccounts/batch', [ + 'data' => $ids]) + ->assertStatus(204); + } + + + /** + * test TwoFAccounts reorder + * + * @test + */ + public function testTwofaccountReorder() + { + factory(TwoFAccount::class, 3)->create(); + + $response = $this->actingAs($this->user, 'api') + ->json('POST', '/api/v1/twofaccounts/reorder', [ + 'orderedIds' => [3,2,1]]) + ->assertStatus(200); + } + +} diff --git a/tests/Api/v1/Requests/GroupAssignRequestTest.php b/tests/Api/v1/Requests/GroupAssignRequestTest.php new file mode 100644 index 00000000..d7b084ab --- /dev/null +++ b/tests/Api/v1/Requests/GroupAssignRequestTest.php @@ -0,0 +1,91 @@ +once() + ->andReturn(true); + + $request = new GroupAssignRequest(); + + $this->assertTrue($request->authorize()); + } + + /** + * @dataProvider provideValidData + */ + public function test_valid_data(array $data) : void + { + $request = new GroupAssignRequest(); + $validator = Validator::make($data, $request->rules()); + + $this->assertFalse($validator->fails()); + } + + /** + * Provide Valid data for validation test + */ + public function provideValidData() : array + { + return [ + [[ + 'ids' => [ + 1, 2, 3 + ] + ]], + ]; + } + + /** + * @dataProvider provideInvalidData + */ + public function test_invalid_data(array $data) : void + { + $request = new GroupAssignRequest(); + $validator = Validator::make($data, $request->rules()); + + $this->assertTrue($validator->fails()); + } + + /** + * Provide invalid data for validation test + */ + public function provideInvalidData() : array + { + return [ + [[ + 'ids' => null // required + ]], + [[ + 'ids' => '1,2,3' // array + ]], + [[ + 'ids' => [ + 'a', 'b', 'c' // array of integers + ] + ]], + [[ + 'ids' => [ + true, false // array of integers + ] + ]], + ]; + } + +} \ No newline at end of file diff --git a/tests/Api/v1/Requests/GroupStoreRequestTest.php b/tests/Api/v1/Requests/GroupStoreRequestTest.php new file mode 100644 index 00000000..95fdc739 --- /dev/null +++ b/tests/Api/v1/Requests/GroupStoreRequestTest.php @@ -0,0 +1,100 @@ +once() + ->andReturn(true); + + $request = new GroupStoreRequest(); + + $this->assertTrue($request->authorize()); + } + + /** + * @dataProvider provideValidData + */ + public function test_valid_data(array $data) : void + { + $request = new GroupStoreRequest(); + $validator = Validator::make($data, $request->rules()); + + $this->assertFalse($validator->fails()); + } + + /** + * Provide Valid data for validation test + */ + public function provideValidData() : array + { + return [ + [[ + 'name' => 'validWord' + ]], + ]; + } + + /** + * @dataProvider provideInvalidData + */ + public function test_invalid_data(array $data) : void + { + $group = new Group([ + 'name' => $this->uniqueGroupName, + ]); + + $group->save(); + + $request = new GroupStoreRequest(); + $validator = Validator::make($data, $request->rules()); + + $this->assertTrue($validator->fails()); + } + + /** + * Provide invalid data for validation test + */ + public function provideInvalidData() : array + { + return [ + [[ + 'name' => '' // required + ]], + [[ + 'name' => true // string + ]], + [[ + 'name' => 8 // string + ]], + [[ + 'name' => 'mmmmmmoooooorrrrrreeeeeeettttttthhhhhhaaaaaaannnnnn32cccccchhhhhaaaaaarrrrrrsssssss' // max:32 + ]], + [[ + 'name' => $this->uniqueGroupName // unique + ]], + ]; + } + +} \ No newline at end of file diff --git a/tests/Api/v1/Requests/QrCodeDecodeRequestTest.php b/tests/Api/v1/Requests/QrCodeDecodeRequestTest.php new file mode 100644 index 00000000..5bfd8b07 --- /dev/null +++ b/tests/Api/v1/Requests/QrCodeDecodeRequestTest.php @@ -0,0 +1,88 @@ +once() + ->andReturn(true); + + $request = new QrCodeDecodeRequest(); + + $this->assertTrue($request->authorize()); + } + + /** + * @dataProvider provideValidData + */ + public function test_valid_data(array $data) : void + { + $request = new QrCodeDecodeRequest(); + $validator = Validator::make($data, $request->rules()); + + $this->assertFalse($validator->fails()); + } + + /** + * Provide Valid data for validation test + */ + public function provideValidData() : array + { + $file = LocalFile::fake()->validQrcode(); + + return [ + [[ + 'qrcode' => $file + ]], + ]; + } + + /** + * @dataProvider provideInvalidData + */ + public function test_invalid_data(array $data) : void + { + $request = new QrCodeDecodeRequest(); + $validator = Validator::make($data, $request->rules()); + + $this->assertTrue($validator->fails()); + } + + /** + * Provide invalid data for validation test + */ + public function provideInvalidData() : array + { + return [ + [[ + 'qrcode' => null // required + ]], + [[ + 'qrcode' => true // image + ]], + [[ + 'qrcode' => 8 // image + ]], + [[ + 'qrcode' => 'string' // image + ]], + ]; + } + +} \ No newline at end of file diff --git a/tests/Api/v1/Requests/SettingStoreRequestTest.php b/tests/Api/v1/Requests/SettingStoreRequestTest.php new file mode 100644 index 00000000..4d0f2a0c --- /dev/null +++ b/tests/Api/v1/Requests/SettingStoreRequestTest.php @@ -0,0 +1,110 @@ +once() + ->andReturn(true); + + $request = new SettingStoreRequest(); + + $this->assertTrue($request->authorize()); + } + + /** + * @dataProvider provideValidData + */ + public function test_valid_data(array $data) : void + { + $request = new SettingStoreRequest(); + $validator = Validator::make($data, $request->rules()); + + $this->assertFalse($validator->fails()); + } + + /** + * Provide Valid data for validation test + */ + public function provideValidData() : array + { + return [ + [[ + 'key' => 'MyKey', + 'value' => true + ]], + [[ + 'key' => 'MyKey', + 'value' => 'MyValue' + ]], + [[ + 'key' => 'MyKey', + 'value' => 10 + ]], + ]; + } + + /** + * @dataProvider provideInvalidData + */ + public function test_invalid_data(array $data) : void + { + $settingService = resolve('App\Services\SettingServiceInterface'); + $settingService->set($this->uniqueKey, 'uniqueValue'); + + $request = new SettingStoreRequest(); + $validator = Validator::make($data, $request->rules()); + + $this->assertTrue($validator->fails()); + } + + /** + * Provide invalid data for validation test + */ + public function provideInvalidData() : array + { + return [ + [[ + 'key' => null, // required + 'value' => '' + ]], + [[ + 'key' => 'my-key', // alpha + 'value' => 'MyValue' + ]], + [[ + 'key' => 10, // alpha + 'value' => 'MyValue' + ]], + [[ + 'key' => 'mmmmmmoooooorrrrrreeeeeeettttttthhhhhhaaaaaaannnnnn128cccccchhhhhaaaaaarrrrrraaaaaaaccccccttttttttteeeeeeeeerrrrrrrrsssssss', // max:128 + 'value' => 'MyValue' + ]], + [[ + 'key' => $this->uniqueKey, // unique + 'value' => 'MyValue' + ]], + ]; + } + +} \ No newline at end of file diff --git a/tests/Api/v1/Requests/SettingUpdateRequestTest.php b/tests/Api/v1/Requests/SettingUpdateRequestTest.php new file mode 100644 index 00000000..20f99471 --- /dev/null +++ b/tests/Api/v1/Requests/SettingUpdateRequestTest.php @@ -0,0 +1,85 @@ +once() + ->andReturn(true); + + $request = new SettingUpdateRequest(); + + $this->assertTrue($request->authorize()); + } + + /** + * @dataProvider provideValidData + */ + public function test_valid_data(array $data) : void + { + $request = new SettingUpdateRequest(); + $validator = Validator::make($data, $request->rules()); + + $this->assertFalse($validator->fails()); + } + + /** + * Provide Valid data for validation test + */ + public function provideValidData() : array + { + return [ + [[ + 'value' => true + ]], + [[ + 'value' => 'MyValue' + ]], + [[ + 'value' => 10 + ]], + ]; + } + + /** + * @dataProvider provideInvalidData + */ + public function test_invalid_data(array $data) : void + { + $request = new SettingUpdateRequest(); + $validator = Validator::make($data, $request->rules()); + + $this->assertTrue($validator->fails()); + } + + /** + * Provide invalid data for validation test + */ + public function provideInvalidData() : array + { + return [ + [[ + 'value' => '' // required + ]], + [[ + 'value' => null // required + ]], + ]; + } + +} \ No newline at end of file diff --git a/tests/Api/v1/Requests/TwoFAccountBatchRequestTest.php b/tests/Api/v1/Requests/TwoFAccountBatchRequestTest.php new file mode 100644 index 00000000..8fc57ed7 --- /dev/null +++ b/tests/Api/v1/Requests/TwoFAccountBatchRequestTest.php @@ -0,0 +1,112 @@ +once() + ->andReturn(true); + + $request = new TwoFAccountBatchRequest(); + + $this->assertTrue($request->authorize()); + } + + /** + * @dataProvider provideValidData + */ + public function test_valid_data(array $data) : void + { + $request = new TwoFAccountBatchRequest(); + $validator = Validator::make($data, $request->rules()); + + $this->assertFalse($validator->fails()); + } + + /** + * Provide Valid data for validation test + */ + public function provideValidData() : array + { + return [ + [[ + 'ids' => '1' + ]], + [[ + 'ids' => '1,2,5' + ]], + ]; + } + + /** + * @dataProvider provideInvalidData + */ + public function test_invalid_data(array $data) : void + { + $request = new TwoFAccountBatchRequest(); + $validator = Validator::make($data, $request->rules()); + + $this->assertTrue($validator->fails()); + } + + /** + * Provide invalid data for validation test + */ + public function provideInvalidData() : array + { + return [ + [[ + 'ids' => '' // required + ]], + [[ + 'ids' => null // required + ]], + [[ + 'ids' => true // string + ]], + [[ + 'ids' => 10 // string + ]], + [[ + 'ids' => 'notaCommaSeparatedList' // regex + ]], + [[ + 'ids' => 'a,b' // regex + ]], + [[ + 'ids' => 'a,1' // regex + ]], + [[ + 'ids' => ',1,2' // regex + ]], + [[ + 'ids' => '1,,2' // regex + ]], + [[ + 'ids' => '1,2,' // regex + ]], + [[ + 'ids' => ',1,2,' // regex + ]], + [[ + 'ids' => '1;2' // regex + ]], + ]; + } + +} \ No newline at end of file diff --git a/tests/Api/v1/Requests/TwoFAccountReorderRequestTest.php b/tests/Api/v1/Requests/TwoFAccountReorderRequestTest.php new file mode 100644 index 00000000..9cfe8931 --- /dev/null +++ b/tests/Api/v1/Requests/TwoFAccountReorderRequestTest.php @@ -0,0 +1,91 @@ +once() + ->andReturn(true); + + $request = new TwoFAccountReorderRequest(); + + $this->assertTrue($request->authorize()); + } + + /** + * @dataProvider provideValidData + */ + public function test_valid_data(array $data) : void + { + $request = new TwoFAccountReorderRequest(); + $validator = Validator::make($data, $request->rules()); + + $this->assertFalse($validator->fails()); + } + + /** + * Provide Valid data for validation test + */ + public function provideValidData() : array + { + return [ + [[ + 'orderedIds' => [1,2,5] + ]], + [[ + 'orderedIds' => [5] + ]], + ]; + } + + /** + * @dataProvider provideInvalidData + */ + public function test_invalid_data(array $data) : void + { + $request = new TwoFAccountReorderRequest(); + $validator = Validator::make($data, $request->rules()); + + $this->assertTrue($validator->fails()); + } + + /** + * Provide invalid data for validation test + */ + public function provideInvalidData() : array + { + return [ + [[ + 'orderedIds' => [] // required + ]], + [[ + 'orderedIds' => null // required + ]], + [[ + 'orderedIds' => 0 // array + ]], + [[ + 'orderedIds' => 'string' // array + ]], + [[ + 'orderedIds' => true // array + ]], + ]; + } + +} \ No newline at end of file diff --git a/tests/Api/v1/Requests/TwoFAccountStoreRequestTest.php b/tests/Api/v1/Requests/TwoFAccountStoreRequestTest.php new file mode 100644 index 00000000..92c39922 --- /dev/null +++ b/tests/Api/v1/Requests/TwoFAccountStoreRequestTest.php @@ -0,0 +1,171 @@ +once() + ->andReturn(true); + + $request = new TwoFAccountStoreRequest(); + + $this->assertTrue($request->authorize()); + } + + /** + * @dataProvider provideValidData + */ + public function test_valid_data(array $data) : void + { + $request = new TwoFAccountStoreRequest(); + $validator = Validator::make($data, $request->rules()); + + $this->assertFalse($validator->fails()); + } + + /** + * Provide Valid data for validation test + */ + public function provideValidData() : array + { + return [ + [[ + 'service' => 'MyService', + 'account' => 'MyAccount', + 'icon' => 'icon.png', + 'otp_type' => 'totp', + 'secret' => 'A4GRFHZVRBGY7UIW', + 'digits' => 6, + 'algorithm' => 'sha1', + 'period' => 30, + ]], + [[ + 'service' => 'MyService', + 'account' => 'MyAccount', + 'icon' => 'icon.png', + 'otp_type' => 'hotp', + 'secret' => 'A4GRFHZVRBGY7UIW', + 'digits' => 6, + 'algorithm' => 'sha1', + 'counter' => 10, + ]], + [[ + 'service' => null, + 'account' => 'MyAccount', + 'icon' => null, + 'otp_type' => 'hotp', + 'secret' => 'A4GRFHZVRBGY7UIW', + 'digits' => null, + 'algorithm' => null, + 'counter' => null, + ]], + [[ + 'account' => 'MyAccount', + 'otp_type' => 'totp', + ]], + [[ + 'account' => 'MyAccount', + 'otp_type' => 'totp', + 'algorithm' => 'sha256', + ]], + [[ + 'account' => 'MyAccount', + 'otp_type' => 'totp', + 'algorithm' => 'sha512', + ]], + [[ + 'account' => 'MyAccount', + 'otp_type' => 'totp', + 'algorithm' => 'md5', + ]], + ]; + } + + /** + * @dataProvider provideInvalidData + */ + public function test_invalid_data(array $data) : void + { + $request = new TwoFAccountStoreRequest(); + $validator = Validator::make($data, $request->rules()); + + $this->assertTrue($validator->fails()); + } + + /** + * Provide invalid data for validation test + */ + public function provideInvalidData() : array + { + return [ + [[ + 'account' => 'My:Account', + 'otp_type' => 'totp', + ]], + [[ + 'account' => 'MyAccount', + 'otp_type' => 'Xotp', + ]], + [[ + 'account' => 'MyAccount', + 'otp_type' => null, + ]], + [[ + 'account' => 'MyAccount', + 'otp_type' => 'totp', + 'service' => 'My:Service', + ]], + [[ + 'account' => 'MyAccount', + 'otp_type' => 'totp', + 'secret' => 'notaBase32String', + ]], + [[ + 'account' => 'MyAccount', + 'otp_type' => 'totp', + 'secret' => 123456, + ]], + [[ + 'account' => 'MyAccount', + 'otp_type' => 'totp', + 'digits' => 5, + ]], + [[ + 'account' => 'MyAccount', + 'otp_type' => 'totp', + 'digits' => 11, + ]], + [[ + 'account' => 'MyAccount', + 'otp_type' => 'totp', + 'period' => 0, + ]], + [[ + 'account' => 'MyAccount', + 'otp_type' => 'hotp', + 'counter' => -1, + ]], + [[ + 'account' => 'MyAccount', + 'otp_type' => 'totp', + 'algorithm' => 'shaX', + ]], + ]; + } + +} \ No newline at end of file diff --git a/tests/Api/v1/Requests/TwoFAccountUpdateRequestTest.php b/tests/Api/v1/Requests/TwoFAccountUpdateRequestTest.php new file mode 100644 index 00000000..a16f0819 --- /dev/null +++ b/tests/Api/v1/Requests/TwoFAccountUpdateRequestTest.php @@ -0,0 +1,222 @@ +once() + ->andReturn(true); + + $request = new TwoFAccountUpdateRequest(); + + $this->assertTrue($request->authorize()); + } + + /** + * @dataProvider provideValidData + */ + public function test_valid_data(array $data) : void + { + $request = new TwoFAccountUpdateRequest(); + $validator = Validator::make($data, $request->rules()); + + $this->assertFalse($validator->fails()); + } + + /** + * Provide Valid data for validation test + */ + public function provideValidData() : array + { + return [ + [[ + 'service' => 'MyService', + 'account' => 'MyAccount', + 'icon' => 'icon.png', + 'otp_type' => 'totp', + 'secret' => 'A4GRFHZVRBGY7UIW', + 'digits' => 6, + 'algorithm' => 'sha1', + 'period' => 30, + ]], + [[ + 'service' => 'MyService', + 'account' => 'MyAccount', + 'icon' => 'icon.png', + 'otp_type' => 'hotp', + 'secret' => 'A4GRFHZVRBGY7UIW', + 'digits' => 6, + 'algorithm' => 'sha1', + 'counter' => 10, + ]], + [[ + 'service' => null, + 'account' => 'MyAccount', + 'icon' => null, + 'otp_type' => 'hotp', + 'secret' => 'A4GRFHZVRBGY7UIW', + 'digits' => 6, + 'algorithm' => 'sha1', + 'period' => null, + 'counter' => 15, + ]], + ]; + } + + /** + * @dataProvider provideInvalidData + */ + public function test_invalid_data(array $data) : void + { + $request = new TwoFAccountUpdateRequest(); + $validator = Validator::make($data, $request->rules()); + + $this->assertTrue($validator->fails()); + } + + /** + * Provide invalid data for validation test + */ + public function provideInvalidData() : array + { + return [ + [[ + 'service' => null, + 'account' => 'My:Account', + 'icon' => null, + 'otp_type' => 'hotp', + 'secret' => 'A4GRFHZVRBGY7UIW', + 'digits' => 6, + 'algorithm' => 'sha1', + 'period' => null, + 'counter' => 15, + ]], + [[ + 'service' => 'My:Service', + 'account' => 'MyAccount', + 'icon' => null, + 'otp_type' => 'hotp', + 'secret' => 'A4GRFHZVRBGY7UIW', + 'digits' => 6, + 'algorithm' => 'sha1', + 'period' => null, + 'counter' => 15, + ]], + [[ + 'service' => null, + 'account' => 'My:Account', + 'icon' => null, + 'otp_type' => 'Xotp', + 'secret' => 'A4GRFHZVRBGY7UIW', + 'digits' => 6, + 'algorithm' => 'sha1', + 'period' => null, + 'counter' => 15, + ]], + [[ + 'service' => null, + 'account' => 'MyAccount', + 'icon' => null, + 'otp_type' => 'hotp', + 'secret' => 'notaBase32String', + 'digits' => 6, + 'algorithm' => 'sha1', + 'period' => null, + 'counter' => 15, + ]], + [[ + 'service' => 'MyService', + 'account' => 'MyAccount', + 'icon' => null, + 'otp_type' => 'totp', + 'secret' => 'A4GRFHZVRBGY7UIW', + 'digits' => 5, + 'algorithm' => 'sha1', + 'period' => null, + 'counter' => 15, + ]], + [[ + 'service' => 'MyService', + 'account' => 'MyAccount', + 'icon' => null, + 'otp_type' => 'totp', + 'secret' => 'A4GRFHZVRBGY7UIW', + 'digits' => 11, + 'algorithm' => 'sha1', + 'period' => null, + 'counter' => 15, + ]], + [[ + 'service' => 'MyService', + 'account' => 'MyAccount', + 'icon' => null, + 'otp_type' => 'totp', + 'secret' => 'A4GRFHZVRBGY7UIW', + 'digits' => 6, + 'algorithm' => 'Xsha1', + 'period' => null, + 'counter' => 15, + ]], + [[ + 'service' => 'MyService', + 'account' => 'MyAccount', + 'icon' => null, + 'otp_type' => 'totp', + 'secret' => 'A4GRFHZVRBGY7UIW', + 'digits' => 6, + 'algorithm' => 'sha1', + 'period' => 0, + 'counter' => 15, + ]], + [[ + 'service' => 'MyService', + 'account' => 'MyAccount', + 'icon' => null, + 'otp_type' => 'totp', + 'secret' => 'A4GRFHZVRBGY7UIW', + 'digits' => 5, + 'algorithm' => 'sha1', + 'period' => null, + 'counter' => -1, + ]], + [[ + 'service' => 'MyService', + 'account' => 'MyAccount', + 'icon' => null, + 'otp_type' => 'totp', + 'secret' => 'A4GRFHZVRBGY7UIW', + 'digits' => null, + 'algorithm' => 'sha1', + 'period' => null, + 'counter' => 15, + ]], + [[ + 'service' => 'MyService', + 'account' => 'MyAccount', + 'icon' => null, + 'otp_type' => 'totp', + 'secret' => 'A4GRFHZVRBGY7UIW', + 'digits' => 6, + 'algorithm' => null, + 'period' => null, + 'counter' => 15, + ]], + ]; + } + +} \ No newline at end of file diff --git a/tests/Api/v1/Requests/TwoFAccountUriRequestTest.php b/tests/Api/v1/Requests/TwoFAccountUriRequestTest.php new file mode 100644 index 00000000..fff07e7c --- /dev/null +++ b/tests/Api/v1/Requests/TwoFAccountUriRequestTest.php @@ -0,0 +1,91 @@ +once() + ->andReturn(true); + + $request = new TwoFAccountUriRequest(); + + $this->assertTrue($request->authorize()); + } + + /** + * @dataProvider provideValidData + */ + public function test_valid_data(array $data) : void + { + $request = new TwoFAccountUriRequest(); + $validator = Validator::make($data, $request->rules()); + + $this->assertFalse($validator->fails()); + } + + /** + * Provide Valid data for validation test + */ + public function provideValidData() : array + { + return [ + [[ + 'uri' => 'otpauth://totp/test@test.com?secret=A4GRFHZVRBGY7UIW&issuer=test' + ]], + [[ + 'uri' => 'otpauth://hotp/test@test.com?secret=A4GRFHZVRBGY7UIW&issuer=test' + ]], + ]; + } + + /** + * @dataProvider provideInvalidData + */ + public function test_invalid_data(array $data) : void + { + $request = new TwoFAccountUriRequest(); + $validator = Validator::make($data, $request->rules()); + + $this->assertTrue($validator->fails()); + } + + /** + * Provide invalid data for validation test + */ + public function provideInvalidData() : array + { + return [ + [[ + 'uri' => null // required + ]], + [[ + 'uri' => '' // required + ]], + [[ + 'uri' => true // string + ]], + [[ + 'uri' => 8 // string + ]], + [[ + 'uri' => 'otpXauth://totp/test@test.com?secret=A4GRFHZVRBGY7UIW&issuer=test' // regex + ]], + ]; + } + +} \ No newline at end of file diff --git a/tests/Api/v1/Requests/UserPatchPwdRequestTest.php b/tests/Api/v1/Requests/UserPatchPwdRequestTest.php new file mode 100644 index 00000000..b81d94ea --- /dev/null +++ b/tests/Api/v1/Requests/UserPatchPwdRequestTest.php @@ -0,0 +1,98 @@ +once() + ->andReturn(true); + + $request = new UserPatchPwdRequest(); + + $this->assertTrue($request->authorize()); + } + + /** + * @dataProvider provideValidData + */ + public function test_valid_data(array $data) : void + { + $request = new UserPatchPwdRequest(); + $validator = Validator::make($data, $request->rules()); + + $this->assertFalse($validator->fails()); + } + + /** + * Provide Valid data for validation test + */ + public function provideValidData() : array + { + return [ + [[ + 'currentPassword' => 'newPassword', + 'password' => 'newPassword', + ]], + ]; + } + + /** + * @dataProvider provideInvalidData + */ + public function test_invalid_data(array $data) : void + { + $request = new UserPatchPwdRequest(); + $validator = Validator::make($data, $request->rules()); + + $this->assertTrue($validator->fails()); + } + + /** + * Provide invalid data for validation test + */ + public function provideInvalidData() : array + { + return [ + [[ + 'currentPassword' => '', // required + 'password' => 'newPassword', + ]], + [[ + 'currentPassword' => 'currentPassword', + 'password' => '', // required + ]], + [[ + 'currentPassword' => 'newPassword', + 'password' => 'anotherPassword', // confirmed + ]], + [[ + 'currentPassword' => 'pwd', + 'password' => 'pwd', // min:8 + ]], + [[ + 'currentPassword' => 'pwd', + 'password' => true, // string + ]], + [[ + 'currentPassword' => 'pwd', + 'password' => 10, // string + ]], + ]; + } + +} \ No newline at end of file diff --git a/tests/Api/v1/Requests/UserStoreRequestTest.php b/tests/Api/v1/Requests/UserStoreRequestTest.php new file mode 100644 index 00000000..f8a10a4c --- /dev/null +++ b/tests/Api/v1/Requests/UserStoreRequestTest.php @@ -0,0 +1,135 @@ +assertTrue($request->authorize()); + } + + /** + * @dataProvider provideValidData + */ + public function test_valid_data(array $data) : void + { + $request = new UserStoreRequest(); + $validator = Validator::make($data, $request->rules()); + + $this->assertFalse($validator->fails()); + } + + /** + * Provide Valid data for validation test + */ + public function provideValidData() : array + { + return [ + [[ + 'name' => 'John', + 'email' => 'john@example.com', + 'password' => 'MyPassword', + 'password_confirmation' => 'MyPassword', + ]], + ]; + } + + /** + * @dataProvider provideInvalidData + */ + public function test_invalid_data(array $data) : void + { + $user = new \App\User( + [ + 'name' => 'John', + 'email' => 'john@example.com', + 'password' => 'MyPassword', + 'password_confirmation' => 'MyPassword', + ] + ); + $user->save(); + + $request = new UserStoreRequest(); + $validator = Validator::make($data, $request->rules()); + + $this->assertTrue($validator->fails()); + } + + /** + * Provide invalid data for validation test + */ + public function provideInvalidData() : array + { + return [ + [[ + 'name' => 'John', // unique + 'email' => 'john@example.com', + 'password' => 'MyPassword', + 'password_confirmation' => 'MyPassword', + ]], + [[ + 'name' => '', // required + 'email' => 'john@example.com', + 'password' => 'MyPassword', + 'password_confirmation' => 'MyPassword', + ]], + [[ + 'name' => 'John', + 'email' => '', // required + 'password' => 'MyPassword', + 'password_confirmation' => 'MyPassword', + ]], + [[ + 'name' => 'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz', // max:255 + 'email' => 'john@example.com', + 'password' => 'MyPassword', + 'password_confirmation' => 'MyPassword', + ]], + [[ + 'name' => 'John', + 'email' => 'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz@example.com', // max:255 + 'password' => 'MyPassword', + 'password_confirmation' => 'MyPassword', + ]], + [[ + 'name' => 'John', + 'email' => 'johnexample.com', // email + 'password' => 'MyPassword', + 'password_confirmation' => 'MyPassword', + ]], + [[ + 'name' => 'John', + 'email' => 'john@example.com', + 'password' => '', // required + 'password_confirmation' => '', // required + ]], + [[ + 'name' => 'John', + 'email' => 'john@example.com', + 'password' => 'MyPassword', + 'password_confirmation' => 'anotherPassword', // confirmed + ]], + [[ + 'name' => 'John', + 'email' => 'john@example.com', + 'password' => 'pwd', // min:8 + 'password_confirmation' => 'pwd', + ]], + ]; + } + +} \ No newline at end of file diff --git a/tests/Api/v1/Requests/UserUpdateRequestTest.php b/tests/Api/v1/Requests/UserUpdateRequestTest.php new file mode 100644 index 00000000..29a8cdbe --- /dev/null +++ b/tests/Api/v1/Requests/UserUpdateRequestTest.php @@ -0,0 +1,115 @@ +once() + ->andReturn(true); + + $request = new UserUpdateRequest(); + + $this->assertTrue($request->authorize()); + } + + /** + * @dataProvider provideValidData + */ + public function test_valid_data(array $data) : void + { + $request = new UserUpdateRequest(); + $validator = Validator::make($data, $request->rules()); + + $this->assertFalse($validator->fails()); + } + + /** + * Provide Valid data for validation test + */ + public function provideValidData() : array + { + return [ + [[ + 'name' => 'John', + 'email' => 'john@example.com', + 'password' => 'MyPassword' + ]], + ]; + } + + /** + * @dataProvider provideInvalidData + */ + public function test_invalid_data(array $data) : void + { + $request = new UserUpdateRequest(); + $validator = Validator::make($data, $request->rules()); + + $this->assertTrue($validator->fails()); + } + + /** + * Provide invalid data for validation test + */ + public function provideInvalidData() : array + { + return [ + [[ + 'name' => '', // required + 'email' => 'john@example.com', + 'password' => 'MyPassword', + ]], + [[ + 'name' => 'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz', // max:255 + 'email' => 'john@example.com', + 'password' => 'MyPassword', + ]], + [[ + 'name' => true, // string + 'email' => 'john@example.com', + 'password' => 'MyPassword', + ]], + [[ + 'name' => 'John', + 'email' => '', // required + 'password' => 'MyPassword', + ]], + [[ + 'name' => 'John', + 'email' => 0, // string + 'password' => 'MyPassword', + ]], + [[ + 'name' => 'John', + 'email' => 'johnexample.com', // email + 'password' => 'MyPassword', + ]], + [[ + 'name' => 'John', + 'email' => 'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz@example.com', // max:255 + 'password' => 'MyPassword', + ]], + [[ + 'name' => 'John', + 'email' => 'john@example.com', + 'password' => '', // required + ]], + ]; + } + +} \ No newline at end of file diff --git a/tests/FeatureTestCase.php b/tests/FeatureTestCase.php new file mode 100644 index 00000000..921c8035 --- /dev/null +++ b/tests/FeatureTestCase.php @@ -0,0 +1,24 @@ + 2]); + } +} diff --git a/tests/TestCase.php b/tests/TestCase.php index f5b43798..6dfe07bb 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -10,15 +10,9 @@ abstract class TestCase extends BaseTestCase { use CreatesApplication; - /** - * Rollback and execute migrations for each test. - */ - use DatabaseTransactions; - protected function setUp(): void { parent::setUp(); - Artisan::call('migrate'); - Artisan::call('passport:install',['--verbose' => 2]); } + } diff --git a/tests/Unit/GroupTest.php b/tests/Unit/GroupTest.php deleted file mode 100644 index a6c91aa9..00000000 --- a/tests/Unit/GroupTest.php +++ /dev/null @@ -1,175 +0,0 @@ -user = factory(User::class)->create(); - } - - - /** - * test Group display via API - * - * @test - */ - public function testGroupDisplay() - { - - $group = factory(Group::class)->create([ - 'name' => 'My group', - ]); - - - $response = $this->actingAs($this->user, 'api') - ->json('GET', '/api/groups/' . $group->id) - ->assertStatus(200) - ->assertJsonFragment([ - 'name' => 'My group', - ]); - } - - - /** - * test missing Group display via API - * - * @test - */ - public function testMissingGroupDisplay() - { - $response = $this->actingAs($this->user, 'api') - ->json('GET', '/api/groups/1000') - ->assertStatus(404); - } - - - /** - * test Group creation via API - * - * @test - */ - public function testGroupCreation() - { - $response = $this->actingAs($this->user, 'api') - ->json('POST', '/api/groups', [ - 'name' => 'My second group', - ]) - ->assertStatus(201) - ->assertJsonFragment([ - 'name' => 'My second group', - ]); - } - - - /** - * test Group creation when fields are empty via API - * - * @test - */ - public function testGroupCreationWithEmptyRequest() - { - $response = $this->actingAs($this->user, 'api') - ->json('POST', '/api/groups', [ - 'name' => '', - ]) - ->assertStatus(422); - } - - - /** - * test Group update via API - * - * @test - */ - public function testGroupUpdate() - { - $group = factory(Group::class)->create(); - - $response = $this->actingAs($this->user, 'api') - ->json('PUT', '/api/groups/' . $group->id, [ - 'name' => 'name updated', - ]) - ->assertStatus(200) - ->assertJsonFragment([ - 'name' => 'name updated', - ]); - } - - - /** - * test Group update via API - * - * @test - */ - public function testGroupUpdateOfMissingGroup() - { - $group = factory(Group::class)->create(); - $id = $group->id; - $group->delete(); - - $response = $this->actingAs($this->user, 'api') - ->json('PUT', '/api/groups/' . $id, [ - 'name' => 'testUpdate', - ]) - ->assertStatus(404); - } - - - /** - * test Group index fetching via API - * - * @test - */ - public function testGroupIndexListing() - { - factory(Group::class, 3)->create(); - - $response = $this->actingAs($this->user, 'api') - ->json('GET', '/api/groups') - ->assertStatus(200) - ->assertJsonCount(4, $key = null) - ->assertJsonStructure([ - '*' => [ - 'id', - 'name', - 'twofaccounts_count', - 'isActive', - ] - ]) - ->assertJsonFragment([ - 'id' => 0, - 'name' => 'All', - ]); - } - - - /** - * test Group deletion via API - * - * @test - */ - public function testGroupDeletion() - { - $group = factory(Group::class)->create(); - - $response = $this->actingAs($this->user, 'api') - ->json('DELETE', '/api/groups/' . $group->id) - ->assertStatus(204); - } -} diff --git a/tests/Unit/IconTest.php b/tests/Unit/IconTest.php deleted file mode 100644 index ec1a24e4..00000000 --- a/tests/Unit/IconTest.php +++ /dev/null @@ -1,61 +0,0 @@ -json('POST', '/api/icon/upload', [ - 'icon' => '', - ]) - ->assertStatus(422); - } - - - /** - * test upload icon via API - * - * @test - */ - public function testIconUpload() - { - - $file = UploadedFile::fake()->image('testIcon.jpg'); - - $response = $this->json('POST', '/api/icon/upload', [ - 'icon' => $file, - ]) - ->assertStatus(201); - - } - - - /** - * test delete an uploaded icon via API - * - * @test - */ - public function testIconDelete() - { - - $response = $this->json('DELETE', '/api/icon/delete/testIcon.jpg') - ->assertStatus(204); - - } - -} \ No newline at end of file diff --git a/tests/Unit/QrcodeTest.php b/tests/Unit/QrcodeTest.php deleted file mode 100644 index 7677d9fb..00000000 --- a/tests/Unit/QrcodeTest.php +++ /dev/null @@ -1,80 +0,0 @@ -json('POST', '/api/qrcode/decode', [ - 'qrcode' => '', - ]) - ->assertStatus(422); - } - - - /** - * test decode an invalid QR code uplloaded via API - * - * @test - */ - // public function testDecodeInvalidQrcode() - // { - // $file = LocalFile::fake()->invalidQrcode(); - - // $response = $this->withHeaders([ - // 'Content-Type' => 'multipart/form-data', - // ]) - // ->json('POST', '/api/qrcode/decode', [ - // 'qrcode' => $file, - // 'inputFormat' => 'fileUpload' - // ]); - - // $response->assertStatus(422); - // } - - - /** - * test Decode a qrcode via API - * - * @test - */ - public function testDecodeValidQrcode() - { - Options::store(array('useBasicQrcodeReader' => true)); - - $file = LocalFile::fake()->validQrcode(); - - $response = $this->withHeaders(['Content-Type' => 'multipart/form-data']) - ->json('POST', '/api/qrcode/decode', [ - 'qrcode' => $file, - 'inputFormat' => 'fileUpload' - ]); - - $response->assertStatus(200) - ->assertJsonStructure([ - 'uri', - ]); - } - -} \ No newline at end of file