diff --git a/.github/workflows/backend_test.yml b/.github/workflows/backend_test.yml new file mode 100644 index 0000000..e38875b --- /dev/null +++ b/.github/workflows/backend_test.yml @@ -0,0 +1,48 @@ +name: Run Laravel API Feature Tests + +on: + pull_request: + branches: + - development + paths: + - 'API/**' + +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' # the PHP version this project requires + extensions: mbstring, pdo_sqlite + ini-values: | + memory_limit=-1 + coverage: none + + - name: Install dependencies + run: | + cd API + composer install --prefer-dist --no-interaction + cp .env.test .env + + - name: Configure environment for SQLite in-memory database + run: | + cd API + echo "DB_CONNECTION=sqlite" >> .env + echo "DB_DATABASE=:memory:" >> .env + php artisan key:generate + + - name: Run migrations + run: | + cd API + php artisan migrate --force + + - name: Run tests + run: | + cd API + php artisan test --testsuite=Feature diff --git a/API/.env.example b/API/.env.test similarity index 94% rename from API/.env.example rename to API/.env.test index 7b49625..0152d6d 100644 --- a/API/.env.example +++ b/API/.env.test @@ -4,6 +4,7 @@ APP_KEY= APP_DEBUG=true APP_TIMEZONE=UTC APP_URL=http://localhost +FRONTEND_BASE_URL=www.speedcartapp.com APP_LOCALE=en APP_FALLBACK_LOCALE=en @@ -19,7 +20,7 @@ LOG_STACK=single LOG_DEPRECATIONS_CHANNEL=null LOG_LEVEL=debug -DB_CONNECTION=sqlite +# DB_CONNECTION=sqlite # DB_HOST=127.0.0.1 # DB_PORT=3306 # DB_DATABASE=laravel diff --git a/API/app/Http/Controllers/Api/GroceryItemController.php b/API/app/Http/Controllers/Api/GroceryItemController.php index 5516a1f..a12f769 100644 --- a/API/app/Http/Controllers/Api/GroceryItemController.php +++ b/API/app/Http/Controllers/Api/GroceryItemController.php @@ -99,27 +99,22 @@ public function update(Request $request, $id) public function destroy(Request $request, $id) { - try { - $groceryItem = GroceryItem::findOrFail($id); - - $shoppingList = ShoppingList::findOrFail($groceryItem->shopping_list_id); + $groceryItem = GroceryItem::findOrFail($id); + + $shoppingList = ShoppingList::findOrFail($groceryItem->shopping_list_id); - // This will automatically call the `update` method in the ShoppingListPolicy - $this->authorize('update', $shoppingList); // Throws a 403 if not authorized + // This will automatically call the `update` method in the ShoppingListPolicy + $this->authorize('update', $shoppingList); // Throws a 403 if not authorized - Log::info("Deleting item: " . print_r($groceryItem, true)); - $groceryItem->delete(); + Log::info("Deleting item: " . print_r($groceryItem, true)); + $groceryItem->delete(); - // Also update shopping list name "updated_at" field (users should know the shopping list has been - // modified without having to see the individual items; this is done via this functionality and shown - // for the Dashboard front end component list items) - $shoppingList->updated_at = now(); // Update the timestamp - $shoppingList->save(); + // Also update shopping list name "updated_at" field (users should know the shopping list has been + // modified without having to see the individual items; this is done via this functionality and shown + // for the Dashboard front end component list items) + $shoppingList->updated_at = now(); // Update the timestamp + $shoppingList->save(); - return response()->json(['message' => 'Grocery item deleted successfully'], 200); - } catch (\Exception $e) { - Log::error('Error deleting grocery item: ' . $e->getMessage()); - return response()->json(['error' => 'Could not delete grocery item'], 500); - } + return response()->json(['message' => 'Grocery item deleted successfully'], 200); } } diff --git a/API/app/Http/Controllers/Api/ShoppingListController.php b/API/app/Http/Controllers/Api/ShoppingListController.php index f976849..63a7d30 100644 --- a/API/app/Http/Controllers/Api/ShoppingListController.php +++ b/API/app/Http/Controllers/Api/ShoppingListController.php @@ -17,8 +17,6 @@ use Illuminate\Support\Facades\Auth; use Illuminate\Foundation\Auth\Access\AuthorizesRequests; -define('DEBUG_MODE', 0); - class ShoppingListController extends Controller { @@ -157,18 +155,13 @@ public function update(Request $request, $id) public function destroy(Request $request, $id) { - try { - $shoppingList = ShoppingList::findOrFail($id); + $shoppingList = ShoppingList::findOrFail($id); - // This will automatically call the `delete` method in the ShoppingListPolicy - $this->authorize('delete', $shoppingList); // Throws a 403 if not authorized - - $shoppingList->delete(); + // This will automatically call the `delete` method in the ShoppingListPolicy + $this->authorize('delete', $shoppingList); // Throws a 403 if not authorized + + $shoppingList->delete(); - return response()->json(['message' => 'Shopping list deleted successfully'], 200); - } catch (\Exception $e) { - Log::error('Error deleting shopping list: ' . $e->getMessage()); - return response()->json(['error' => 'Could not delete shopping list'], 500); - } + return response()->json(['message' => 'Shopping list deleted successfully'], 200); } } diff --git a/API/app/Http/Controllers/GoogleAuthenticationController.php b/API/app/Http/Controllers/GoogleAuthenticationController.php index 4949d54..d915691 100644 --- a/API/app/Http/Controllers/GoogleAuthenticationController.php +++ b/API/app/Http/Controllers/GoogleAuthenticationController.php @@ -14,11 +14,6 @@ use App\Http\Controllers\Controller; use Illuminate\Support\Facades\Auth; - -define('DEBUG_MODE', 0); -// Fetch the client ID from the environment variable -define('GOOGLE_CLIENT_ID', env('GOOGLE_CLIENT_ID')); - class GoogleAuthenticationController extends Controller { /** diff --git a/API/app/Http/Controllers/ListPermissionsController.php b/API/app/Http/Controllers/ListPermissionsController.php index 45c91e3..2ce97b9 100644 --- a/API/app/Http/Controllers/ListPermissionsController.php +++ b/API/app/Http/Controllers/ListPermissionsController.php @@ -12,8 +12,6 @@ use Illuminate\Support\Str; use Illuminate\Support\Facades\Auth; -define('DEBUG_MODE', 0); - class ListPermissionsController extends BaseController { @@ -38,6 +36,13 @@ public function createShareLink($id, Request $request) { $shoppingList = ShoppingList::findOrFail($id); Log::info("Shopping list id: " . $shoppingList->list_id . " and we started with id: " . $id); + + // Grab user from sanctum + $user = Auth::user(); + + if ($shoppingList->user_id != $user->user_id) { + abort(403); + } $maxRetries = 10; // Maximum number of retries $retryCount = 0; diff --git a/API/app/Models/SharedLink.php b/API/app/Models/SharedLink.php index f037dc1..96055d0 100644 --- a/API/app/Models/SharedLink.php +++ b/API/app/Models/SharedLink.php @@ -2,9 +2,12 @@ namespace App\Models; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Factories\HasFactory; class SharedLink extends Model { + use HasFactory; + protected $table = 'shared_links'; // Fillable properties, allowing mass assignment diff --git a/API/app/Providers/AppServiceProvider.php b/API/app/Providers/AppServiceProvider.php index 3ec0f61..ec4fdc6 100644 --- a/API/app/Providers/AppServiceProvider.php +++ b/API/app/Providers/AppServiceProvider.php @@ -28,5 +28,12 @@ public function register(): void public function boot(): void { // + if (!defined('DEBUG_MODE')) { + define('DEBUG_MODE', 1); + } + if (!defined('GOOGLE_CLIENT_ID')) { + // Fetch the client ID from the environment variable + define('GOOGLE_CLIENT_ID', env('GOOGLE_CLIENT_ID')); + } } } diff --git a/API/database/factories/GroceryItemFactory.php b/API/database/factories/GroceryItemFactory.php new file mode 100644 index 0000000..4741bda --- /dev/null +++ b/API/database/factories/GroceryItemFactory.php @@ -0,0 +1,35 @@ +first(); + $list = ShoppingList::inRandomOrder()->first(); + return [ + 'name' => $this->faker->word(), // Random name for the grocery item + 'is_food' => false, + 'shopping_list_id' => $list->list_id, + ]; + } +} diff --git a/API/database/factories/SharedLinkFactory.php b/API/database/factories/SharedLinkFactory.php new file mode 100644 index 0000000..fd78630 --- /dev/null +++ b/API/database/factories/SharedLinkFactory.php @@ -0,0 +1,26 @@ + (string) Str::uuid(), + 'expires_at' => Carbon::now()->addDays(7), + 'can_update' => false, + 'can_delete' => false, + 'shopping_list_id' => function () { + return \App\Models\ShoppingList::factory()->create()->list_id; + } + ]; + } +} \ No newline at end of file diff --git a/API/database/factories/ShoppingListFactory.php b/API/database/factories/ShoppingListFactory.php new file mode 100644 index 0000000..8e43c17 --- /dev/null +++ b/API/database/factories/ShoppingListFactory.php @@ -0,0 +1,34 @@ +first(); + + return [ + 'name' => $this->faker->word(), // Random name for the shopping list + 'user_id' => $user ? $user->user_id : null, // Assign a user_id from an existing user + 'route_id' => null, // Set to null if you don't want to assign a route by default + ]; + } +} diff --git a/API/database/factories/UserFactory.php b/API/database/factories/UserFactory.php index 584104c..6ce9df8 100644 --- a/API/database/factories/UserFactory.php +++ b/API/database/factories/UserFactory.php @@ -3,42 +3,19 @@ namespace Database\Factories; use Illuminate\Database\Eloquent\Factories\Factory; -use Illuminate\Support\Facades\Hash; -use Illuminate\Support\Str; -/** - * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User> - */ +use App\Models\User; + class UserFactory extends Factory { - /** - * The current password being used by the factory. - */ - protected static ?string $password; + protected $model = User::class; - /** - * Define the model's default state. - * - * @return array - */ - public function definition(): array + public function definition() { return [ - 'name' => fake()->name(), - 'email' => fake()->unique()->safeEmail(), - 'email_verified_at' => now(), - 'password' => static::$password ??= Hash::make('password'), - 'remember_token' => Str::random(10), + 'user_id' => $this->faker->uuid(), + 'username' => $this->faker->userName(), + // Add any other necessary fields here ]; } - - /** - * Indicate that the model's email address should be unverified. - */ - public function unverified(): static - { - return $this->state(fn (array $attributes) => [ - 'email_verified_at' => null, - ]); - } } diff --git a/API/phpunit.xml b/API/phpunit.xml index 506b9a3..60b9b16 100644 --- a/API/phpunit.xml +++ b/API/phpunit.xml @@ -23,7 +23,7 @@ - + diff --git a/API/tests/Feature/ExampleTest.php b/API/tests/Feature/ExampleTest.php deleted file mode 100644 index 8364a84..0000000 --- a/API/tests/Feature/ExampleTest.php +++ /dev/null @@ -1,19 +0,0 @@ -get('/'); - - $response->assertStatus(200); - } -} diff --git a/API/tests/Feature/GroceryItemControllerTest.php b/API/tests/Feature/GroceryItemControllerTest.php new file mode 100644 index 0000000..ccac5d4 --- /dev/null +++ b/API/tests/Feature/GroceryItemControllerTest.php @@ -0,0 +1,158 @@ +create(); + $shoppingList = ShoppingList::factory()->create(['user_id' => $user->user_id]); + + $this->actingAs($user) + ->postJson('/grocery-items', [ + 'name' => 'Apples', + 'quantity' => 5, + 'is_food' => true, + 'shopping_list_id' => $shoppingList->list_id, + ]) + ->assertStatus(201) + ->assertJsonFragment(['name' => 'Apples', 'quantity' => 5, 'is_food' => true]); + + $this->assertDatabaseHas('grocery_items', [ + 'name' => 'Apples', + 'quantity' => 5, + 'is_food' => true, + 'shopping_list_id' => $shoppingList->list_id, + ]); + } + + public function testStoreGroceryItemUnauthorized() + { + $user = User::factory()->create(); + $otherUser = User::factory()->create(); + $shoppingList = ShoppingList::factory()->create(['user_id' => $otherUser->user_id]); + + $this->actingAs($user) + ->postJson('/grocery-items', [ + 'name' => 'Bananas', + 'quantity' => 10, + 'is_food' => true, + 'shopping_list_id' => $shoppingList->list_id, + ]) + ->assertStatus(403); + } + + public function testShowGroceryItems() + { + $user = User::factory()->create(); + $shoppingList = ShoppingList::factory()->create(['user_id' => $user->user_id]); + $groceryItem = GroceryItem::factory()->create([ + 'name' => 'Oranges', + 'quantity' => 3, + 'is_food' => true, + 'shopping_list_id' => $shoppingList->list_id, + ]); + + $response = $this->actingAs($user)->getJson('/grocery-items/' . $shoppingList->list_id); + + $response->assertStatus(200); + + $response->assertJsonFragment([ + 'name' => 'Oranges', + 'quantity' => 3, + 'is_food' => 1, + ]); + } + + public function testShowGroceryItemsUnauthorized() + { + $user = User::factory()->create(); + $otherUser = User::factory()->create(); + $shoppingList = ShoppingList::factory()->create(['user_id' => $otherUser->user_id]); + + $this->actingAs($user) + ->getJson('/grocery-items/' . $shoppingList->list_id) + ->assertStatus(403); + } + + public function testUpdateGroceryItem() + { + $user = User::factory()->create(); + $shoppingList = ShoppingList::factory()->create(['user_id' => $user->user_id]); + $groceryItem = GroceryItem::factory()->create([ + 'name' => 'Milk', + 'quantity' => 1, + 'is_food' => true, + 'shopping_list_id' => $shoppingList->list_id, + ]); + + $this->actingAs($user) + ->putJson('/grocery-items/' . $groceryItem->item_id, [ + 'name' => 'Almond Milk', + 'quantity' => 2, + 'is_food' => true, + ]) + ->assertStatus(200) + ->assertJsonFragment(['name' => 'Almond Milk', 'quantity' => 2, 'is_food' => true]); + + $this->assertDatabaseHas('grocery_items', [ + 'item_id' => $groceryItem->item_id, + 'name' => 'Almond Milk', + 'quantity' => 2, + ]); + } + + public function testUpdateGroceryItemUnauthorized() + { + $user = User::factory()->create(); + $otherUser = User::factory()->create(); + $shoppingList = ShoppingList::factory()->create(['user_id' => $otherUser->user_id]); + $groceryItem = GroceryItem::factory()->create([ + 'shopping_list_id' => $shoppingList->list_id, + 'name' => 'Juice', + ]); + + $this->actingAs($user) + ->putJson('/grocery-items/' . $groceryItem->item_id, [ + 'name' => 'Orange Juice', + 'quantity' => 2, + 'is_food' => true, + ]) + ->assertStatus(403); + } + + public function testDeleteGroceryItem() + { + $user = User::factory()->create(); + $shoppingList = ShoppingList::factory()->create(['user_id' => $user->user_id]); + $groceryItem = GroceryItem::factory()->create(['shopping_list_id' => $shoppingList->list_id]); + + $this->actingAs($user) + ->deleteJson('/grocery-items/' . $groceryItem->item_id) + ->assertStatus(200) + ->assertJsonFragment(['message' => 'Grocery item deleted successfully']); + + $this->assertDatabaseMissing('grocery_items', ['item_id' => $groceryItem->item_id]); + } + + public function testDeleteGroceryItemUnauthorized() + { + $user = User::factory()->create(); + $otherUser = User::factory()->create(); + $shoppingList = ShoppingList::factory()->create(['user_id' => $otherUser->user_id, 'name' => 'foo']); + $groceryItem = GroceryItem::factory()->create(['shopping_list_id' => $shoppingList->list_id]); + + $this->actingAs($user) + ->deleteJson('/grocery-items/' . $groceryItem->item_id) + ->assertStatus(403); + } +} diff --git a/API/tests/Feature/ListPermissionsControllerTest.php b/API/tests/Feature/ListPermissionsControllerTest.php new file mode 100644 index 0000000..c3daec3 --- /dev/null +++ b/API/tests/Feature/ListPermissionsControllerTest.php @@ -0,0 +1,277 @@ +create(); + $list = ShoppingList::factory()->create([ + 'user_id' => $user->user_id, + 'name' => 'Test Shopping List' + ]); + + $this->actingAs($user); + + $response = $this->postJson('/share/' . $list->list_id); + + $response->assertStatus(200); + + // Verify the database entry with all required fields + $this->assertDatabaseHas('shared_links', [ + 'shopping_list_id' => $list->list_id, + ]); + + // Verify that all required fields are present + $sharedLink = \DB::table('shared_links') + ->where('shopping_list_id', $list->list_id) + ->first(); + + $this->assertNotNull($sharedLink); + $this->assertNotNull($sharedLink->token); + $this->assertNotNull($sharedLink->expires_at); + } + + public function testVerifyShareLinkValid(): void + { + $user = User::factory()->create(); + $otherUser = User::factory()->create(); + $list = ShoppingList::factory()->create([ + 'user_id' => $user->user_id, + 'name' => 'Test Shopping List' + ]); + $shareLink = SharedLink::factory()->create([ + 'shopping_list_id' => $list->list_id + ]); + + $response = $this->actingAs($otherUser); + + $response = $this->getJson('/share/' . $shareLink->token); + + $response->assertStatus(201); + + // Ensure chosen permissions (default from factory) were properly set + $this->assertDatabaseHas('shared_shopping_list_perms', [ + 'shopping_list_id' => $list->list_id, + 'user_id' => $otherUser->user_id, + 'can_update' => 0, + 'can_delete' => 0 + ]); + } + + public function testCreateShareLinkChainUnauthorized() { + $user = User::factory()->create(); + $otherUser = User::factory()->create(); + $list = ShoppingList::factory()->create([ + 'user_id' => $user->user_id, + 'name' => 'Test Shopping List' + ]); + $shareLink = SharedLink::factory()->create([ + 'shopping_list_id' => $list->list_id + ]); + + $response = $this->actingAs($otherUser); + + // Verify the first link with otherUser to give them permissions + $response = $this->getJson('/share/' . $shareLink->token); + + // Now try creating another share link as otherUser + + $secondLinkCreationResponse = $this->actingAs($otherUser); + + $secondLinkCreationResponse = $this->postJson('/share/' . $list->list_id); + + $secondLinkCreationResponse->assertStatus(403); + + // Ensure chosen permissions (default from factory) were NOT properly set + $this->assertDatabaseMissing('shared_links', [ + 'shopping_list_id' => $list->list_id, + 'can_update' => 0, + 'can_delete' => 0 + ]); + } + + public function testVerifyShareLinkUpdateAllowedButNotDelete() { + $user = User::factory()->create(); + $otherUser = User::factory()->create(); + $list = ShoppingList::factory()->create([ + 'user_id' => $user->user_id, + 'name' => 'Test Shopping List' + ]); + $shareLink = SharedLink::factory()->create([ + 'shopping_list_id' => $list->list_id, + 'can_update' => 1 + ]); + + $response = $this->actingAs($otherUser); + + // Verify the first link with otherUser to give them permissions + $response = $this->getJson('/share/' . $shareLink->token); + + // Now try CRUD operations with Controllers but DELETE should fail + + // Title update + $titleUpdateResponse = $this->actingAs($otherUser); + $titleUpdateResponse = $this->putJson('/shopping-lists/' . $list->list_id, [ + 'name' => 'foo2', + ]); + $titleUpdateResponse->assertStatus(200); + + // Item creation + $listItemCreationResponse = $this->actingAs($otherUser); + $listItemCreationResponse = $this->postJson('/grocery-items', [ + 'name' => 'Bananas', + 'quantity' => 10, + 'is_food' => true, + 'shopping_list_id' => $list->list_id, + ]); + + $listItemCreationResponse->assertStatus(201); + $listItemCreationResponseJson = $listItemCreationResponse->json(); + + // Delete an item (fabricate via Factory) + // TBD = To Be Deleted + $listItemTBD = GroceryItem::factory()->create([ + 'shopping_list_id' => $list->list_id + ]); + + $listItemTBDResponse = $this->actingAs($otherUser); + $listItemTBDResponse = $this->deleteJson('/grocery-items/' . $listItemTBD->item_id); + $listItemTBDResponse->assertStatus(200); + $this->assertDatabaseMissing('grocery_items', [ + 'shopping_list_id', $list->list_id, + 'item_id' => $listItemTBD->item_id + ]); + + // Deletion which should fail + $listDeleteResponse = $this->actingAs($otherUser); + $listDeleteResponse = $this->deleteJson('/shopping-lists/' . $list->list_id); + + $listDeleteResponse->assertStatus(403); + + // Ensure content was preserved in all tables involved + $this->assertDatabaseHas('shopping_lists', [ + 'list_id' => $list->list_id, + 'user_id' => $user->user_id + ]); + + $this->assertDatabaseHas('grocery_items', [ + 'shopping_list_id' => $list->list_id, + 'item_id' => $listItemCreationResponseJson['item_id'] + ]); + } + + public function testVerifyShareLinkDeleteAllowedButNotUpdate() { + $user = User::factory()->create(); + $otherUser = User::factory()->create(); + $list = ShoppingList::factory()->create([ + 'user_id' => $user->user_id, + 'name' => 'Test Shopping List' + ]); + $shareLink = SharedLink::factory()->create([ + 'shopping_list_id' => $list->list_id, + 'can_delete' => 1 + ]); + + $response = $this->actingAs($otherUser); + + // Verify the first link with otherUser to give them permissions + $response = $this->getJson('/share/' . $shareLink->token); + + // Now try CRUD operations with Controllers but UPDATE should fail + + // Title update + $titleUpdateResponse = $this->actingAs($otherUser); + $titleUpdateResponse = $this->putJson('/shopping-lists/' . $list->list_id, [ + 'name' => 'foo2', + ]); + $titleUpdateResponse->assertStatus(403); + $this->assertDatabaseMissing('grocery_items', [ + 'name' => 'foo2', + ]); + + // Item creation + $listItemCreationResponse = $this->actingAs($otherUser); + $listItemCreationResponse = $this->postJson('/grocery-items', [ + 'name' => 'Bananas', + 'quantity' => 10, + 'is_food' => true, + 'shopping_list_id' => $list->list_id, + ]); + + $listItemCreationResponse->assertStatus(403); + + // Try to delete an item (fabricate via Factory); THIS REQUEST SHOULD FAIL! + // TBD = To Be Deleted + $listItemTBD = GroceryItem::factory()->create([ + 'shopping_list_id' => $list->list_id, + 'name' => 'stick around' + ]); + + $listItemTBDResponse = $this->actingAs($otherUser); + $listItemTBDResponse = $this->deleteJson('/grocery-items/' . $listItemTBD->item_id); + $listItemTBDResponse->assertStatus(403); + $this->assertDatabaseHas('grocery_items', [ + 'shopping_list_id' => $list->list_id, + 'name' => 'stick around' + ]); + + + // Deletion which should succeed (since we're deleting the list) + $listDeleteResponse = $this->actingAs($otherUser); + $listDeleteResponse = $this->deleteJson('/shopping-lists/' . $list->list_id); + + $listDeleteResponse->assertStatus(200); + + // Ensure content was deleted in all tables involved + $this->assertDatabaseMissing('shopping_lists', [ + 'list_id' => $list->list_id, + 'user_id' => $user->user_id + ]); + + // There should be NO ITEMS REMAINING since the list was deleted + $this->assertDatabaseMissing('grocery_items', [ + 'shopping_list_id' => $list->list_id, + ]); + } + + public function testUnauthorizedUserCannotCreateShareLink(): void + { + $owner = User::factory()->create(); + $unauthorizedUser = User::factory()->create(); + + $list = ShoppingList::factory()->create([ + 'user_id' => $owner->user_id + ]); + + $this->actingAs($unauthorizedUser); + + $response = $this->postJson('/share/' . $list->list_id); + $response->assertStatus(403); + + // Ensure chosen permissions (default from factory) were NOT properly set + $this->assertDatabaseMissing('shared_shopping_list_perms', [ + 'shopping_list_id' => $list->list_id, + 'user_id' => $unauthorizedUser->user_id, + 'can_update' => 0, + 'can_delete' => 0 + ]); + } +} diff --git a/API/tests/Feature/ShoppingListControllerTest.php b/API/tests/Feature/ShoppingListControllerTest.php new file mode 100644 index 0000000..ad1a00c --- /dev/null +++ b/API/tests/Feature/ShoppingListControllerTest.php @@ -0,0 +1,169 @@ +create(); + + // Mock the login by setting a cookie + $response = $this->actingAs($user); + + // Make the post request to create a shopping list + $response = $this->postJson('/shopping-lists', [ + 'name' => 'Test Shopping List', + ]); + + // Assert the response is successful (HTTP 201 created) + $response->assertStatus(201); + + // Assert the response contains the shopping list name + $response->assertJsonFragment([ + 'name' => 'Test Shopping List', + ]); + + // Ensure the shopping list is stored in the database + $this->assertDatabaseHas('shopping_lists', [ + 'name' => 'Test Shopping List', + 'user_id' => $user->user_id, // Make sure the user_id matches + ]); + } + + + public function testGetUserShoppingLists() + { + // Create an authenticated user and a shopping list + $user = User::factory()->create(); + $list = ShoppingList::factory()->create([ + 'user_id' => $user->user_id, + ]); + + // Mock the login by setting a cookie + $response = $this->actingAs($user); + + // Make the get request to fetch shopping lists + $response = $this->getJson('/shopping-lists'); + + // Assert the response is successful (HTTP 200 OK) + $response->assertStatus(200); + + // Assert the shopping list is in the response + $response->assertJsonFragment([ + 'name' => $list->name, + ]); + } + + public function testGetSpecificShoppingList() + { + // Create an authenticated user and a shopping list + $user = User::factory()->create(); + $list = ShoppingList::factory()->create([ + 'user_id' => $user->user_id, + ]); + + // Mock the login by setting a cookie + $response = $this->actingAs($user); + + // Make the get request to fetch a specific shopping list by ID + $response = $this->getJson('/shopping-lists/' . $list->list_id); + + // Assert the response is successful (HTTP 200 OK) + $response->assertStatus(200); + + // Assert the specific shopping list is in the response + $response->assertJsonFragment([ + 'name' => $list->name, + ]); + } + + public function testGetSpecificShoppingListUnauthorized() + { + $user = User::factory()->create(); + $otherUser = User::factory()->create(); + $list = ShoppingList::factory()->create(['user_id' => $otherUser->user_id]); + + $this->actingAs($user) + ->getJson('/shopping-lists/' . $list->list_id) + ->assertStatus(403); + } + + public function testUpdateShoppingList() + { + $user = User::factory()->create(); + $list = ShoppingList::factory()->create(['user_id' => $user->user_id, 'name' => 'foo']); + + $this->actingAs($user) + ->putJson('/shopping-lists/' . $list->list_id, [ + 'name' => 'foobar', + ]) + ->assertStatus(200) + ->assertJsonFragment(['name' => 'foobar']); + + $this->assertDatabaseHas('shopping_lists', [ + 'list_id' => $list->list_id, + 'name' => 'foobar' + ]); + } + + public function testUpdateShoppingListUnauthorized() + { + $user = User::factory()->create(); + $otherUser = User::factory()->create(); + $list = ShoppingList::factory()->create(['user_id' => $otherUser->user_id, 'name' => 'foo']); + + $this->actingAs($user) + ->putJson('/shopping-lists/' . $list->list_id, [ + 'name' => 'foo2', + ]) + ->assertStatus(403); + } + + public function testDeleteShoppingList() + { + // Create an authenticated user and a shopping list + $user = User::factory()->create(); + $list = ShoppingList::factory()->create([ + 'user_id' => $user->user_id, + ]); + + // Mock the login by setting a cookie + $response = $this->actingAs($user); + + // Make the delete request to remove the shopping list + $response = $this->deleteJson('/shopping-lists/' . $list->list_id); + + // Assert the response is successful (HTTP 200 OK) + $response->assertStatus(200); + + // Ensure the shopping list is deleted from the database + $this->assertDatabaseMissing('shopping_lists', [ + 'id' => $list->list_id, + ]); + } + + public function testDeleteShoppingListUnauthorized() + { + $user = User::factory()->create(); + $otherUser = User::factory()->create(); + $list = ShoppingList::factory()->create(['user_id' => $otherUser->user_id, 'name' => 'foo']); + + $this->actingAs($user) + ->deleteJson('/shopping-lists/' . $list->list_id) + ->assertStatus(403); + } + +} diff --git a/API/tests/Unit/ExampleTest.php b/API/tests/Unit/ExampleTest.php deleted file mode 100644 index 5773b0c..0000000 --- a/API/tests/Unit/ExampleTest.php +++ /dev/null @@ -1,16 +0,0 @@ -assertTrue(true); - } -}