From 3814ccd2de6bb53771797f74c802fadc1cc3c421 Mon Sep 17 00:00:00 2001 From: ItsNeil17 Date: Fri, 27 Feb 2026 22:51:20 +0530 Subject: [PATCH 1/5] feat: add mod Model test --- tests/Unit/ModModelTest.php | 246 ++++++++++++++++++++++++++++++++++++ 1 file changed, 246 insertions(+) create mode 100644 tests/Unit/ModModelTest.php diff --git a/tests/Unit/ModModelTest.php b/tests/Unit/ModModelTest.php new file mode 100644 index 0000000..86aa6cb --- /dev/null +++ b/tests/Unit/ModModelTest.php @@ -0,0 +1,246 @@ +create(); + $mod = Mod::factory()->create(['owner_id' => $user->id]); + + $this->assertInstanceOf(User::class, $mod->owner); + $this->assertEquals($user->id, $mod->owner->id); + } + + public function test_mod_has_many_collaborators() + { + $owner = User::factory()->create(); + $collaborator1 = User::factory()->create(); + $collaborator2 = User::factory()->create(); + $mod = Mod::factory()->create(['owner_id' => $owner->id]); + + $mod->collaborators()->attach($collaborator1->id, ['role' => 'editor', 'invited_by' => $owner->id]); + $mod->collaborators()->attach($collaborator2->id, ['role' => 'viewer', 'invited_by' => $owner->id]); + + $this->assertCount(2, $mod->collaborators); + $this->assertEquals('editor', $mod->collaborators->first()->pivot->role); + } + + public function test_mod_has_many_pages() + { + $user = User::factory()->create(); + $mod = Mod::factory()->create(['owner_id' => $user->id]); + $page1 = Page::factory()->create(['mod_id' => $mod->id]); + $page2 = Page::factory()->create(['mod_id' => $mod->id]); + + $this->assertCount(2, $mod->pages); + $this->assertTrue($mod->pages->contains($page1)); + $this->assertTrue($mod->pages->contains($page2)); + } + + public function test_mod_has_root_pages() + { + $user = User::factory()->create(); + $mod = Mod::factory()->create(['owner_id' => $user->id]); + $rootPage = Page::factory()->create(['mod_id' => $mod->id, 'parent_id' => null]); + $childPage = Page::factory()->create(['mod_id' => $mod->id, 'parent_id' => $rootPage->id]); + + $rootPages = $mod->rootPages; + $this->assertCount(1, $rootPages); + $this->assertTrue($rootPages->contains($rootPage)); + $this->assertFalse($rootPages->contains($childPage)); + } + + public function test_public_mod_can_be_accessed_by_anyone() + { + $user = User::factory()->create(); + $guest = User::factory()->create(); + $mod = Mod::factory()->public()->create(['owner_id' => $user->id]); + + $this->assertTrue($mod->canBeAccessedBy($user)); + $this->assertTrue($mod->canBeAccessedBy($guest)); + $this->assertTrue($mod->canBeAccessedBy(null)); // Guest + } + + public function test_private_mod_can_only_be_accessed_by_owner_and_collaborators() + { + $owner = User::factory()->create(); + $collaborator = User::factory()->create(); + $outsider = User::factory()->create(); + $mod = Mod::factory()->private()->create(['owner_id' => $owner->id]); + + $mod->collaborators()->attach($collaborator->id, ['role' => 'viewer', 'invited_by' => $owner->id]); + + $this->assertTrue($mod->canBeAccessedBy($owner)); + $this->assertTrue($mod->canBeAccessedBy($collaborator)); + $this->assertFalse($mod->canBeAccessedBy($outsider)); + $this->assertFalse($mod->canBeAccessedBy(null)); + } + + public function test_unlisted_mod_access() + { + $owner = User::factory()->create(); + $guest = User::factory()->create(); + $mod = Mod::factory()->create(['owner_id' => $owner->id, 'visibility' => 'unlisted']); + + $this->assertTrue($mod->canBeAccessedBy($owner)); + $this->assertFalse($mod->canBeAccessedBy($guest)); + $this->assertFalse($mod->canBeAccessedBy(null)); + } + + public function test_get_user_role_returns_correct_role() + { + $owner = User::factory()->create(); + $admin = User::factory()->create(); + $editor = User::factory()->create(); + $viewer = User::factory()->create(); + $outsider = User::factory()->create(); + $mod = Mod::factory()->create(['owner_id' => $owner->id]); + + $mod->collaborators()->attach($admin->id, ['role' => 'admin', 'invited_by' => $owner->id]); + $mod->collaborators()->attach($editor->id, ['role' => 'editor', 'invited_by' => $owner->id]); + $mod->collaborators()->attach($viewer->id, ['role' => 'viewer', 'invited_by' => $owner->id]); + + $this->assertEquals('owner', $mod->getUserRole($owner)); + $this->assertEquals('admin', $mod->getUserRole($admin)); + $this->assertEquals('editor', $mod->getUserRole($editor)); + $this->assertEquals('viewer', $mod->getUserRole($viewer)); + $this->assertNull($mod->getUserRole($outsider)); + } + + public function test_owner_has_all_permissions() + { + $user = User::factory()->create(); + $mod = Mod::factory()->create(['owner_id' => $user->id]); + + $this->assertTrue($mod->userCan($user, 'view')); + $this->assertTrue($mod->userCan($user, 'edit')); + $this->assertTrue($mod->userCan($user, 'delete')); + $this->assertTrue($mod->userCan($user, 'manage_collaborators')); + $this->assertTrue($mod->userCan($user, 'manage_settings')); + } + + public function test_admin_has_correct_permissions() + { + $owner = User::factory()->create(); + $admin = User::factory()->create(); + $mod = Mod::factory()->create(['owner_id' => $owner->id]); + + $mod->collaborators()->attach($admin->id, ['role' => 'admin', 'invited_by' => $owner->id]); + + $this->assertTrue($mod->userCan($admin, 'view')); + $this->assertTrue($mod->userCan($admin, 'edit')); + $this->assertTrue($mod->userCan($admin, 'manage_collaborators')); + $this->assertFalse($mod->userCan($admin, 'delete')); + $this->assertFalse($mod->userCan($admin, 'manage_settings')); + } + + public function test_editor_has_correct_permissions() + { + $owner = User::factory()->create(); + $editor = User::factory()->create(); + $mod = Mod::factory()->create(['owner_id' => $owner->id]); + + $mod->collaborators()->attach($editor->id, ['role' => 'editor', 'invited_by' => $owner->id]); + + $this->assertTrue($mod->userCan($editor, 'view')); + $this->assertTrue($mod->userCan($editor, 'edit')); + $this->assertFalse($mod->userCan($editor, 'delete')); + $this->assertFalse($mod->userCan($editor, 'manage_collaborators')); + $this->assertFalse($mod->userCan($editor, 'manage_settings')); + } + + public function test_viewer_has_correct_permissions() + { + $owner = User::factory()->create(); + $viewer = User::factory()->create(); + $mod = Mod::factory()->create(['owner_id' => $owner->id]); + + $mod->collaborators()->attach($viewer->id, ['role' => 'viewer', 'invited_by' => $owner->id]); + + $this->assertTrue($mod->userCan($viewer, 'view')); + $this->assertFalse($mod->userCan($viewer, 'edit')); + $this->assertFalse($mod->userCan($viewer, 'delete')); + $this->assertFalse($mod->userCan($viewer, 'manage_collaborators')); + $this->assertFalse($mod->userCan($viewer, 'manage_settings')); + } + + public function test_outsider_has_no_permissions() + { + $owner = User::factory()->create(); + $outsider = User::factory()->create(); + $mod = Mod::factory()->create(['owner_id' => $owner->id]); + + $this->assertFalse($mod->userCan($outsider, 'view')); + $this->assertFalse($mod->userCan($outsider, 'edit')); + $this->assertFalse($mod->userCan($outsider, 'delete')); + $this->assertFalse($mod->userCan($outsider, 'manage_collaborators')); + $this->assertFalse($mod->userCan($outsider, 'manage_settings')); + } + + public function test_slug_is_generated_on_creation() + { + $user = User::factory()->create(); + $mod = Mod::factory()->create([ + 'owner_id' => $user->id, + 'name' => 'My Amazing Mod', + 'slug' => null, + ]); + + $this->assertEquals('my-amazing-mod', $mod->slug); + } + + public function test_custom_slug_is_preserved() + { + $user = User::factory()->create(); + $mod = Mod::factory()->create([ + 'owner_id' => $user->id, + 'name' => 'Original Name', + 'slug' => 'custom-slug', + ]); + + $mod->update(['name' => 'New Name']); + + $this->assertEquals('custom-slug', $mod->slug); + } + + public function test_route_key_name_is_slug() + { + $mod = new Mod(); + $this->assertEquals('slug', $mod->getRouteKeyName()); + } + + public function test_published_pages_scope() + { + $user = User::factory()->create(); + $mod = Mod::factory()->create(['owner_id' => $user->id]); + $publishedPage = Page::factory()->create(['mod_id' => $mod->id, 'published' => true]); + $unpublishedPage = Page::factory()->create(['mod_id' => $mod->id, 'published' => false]); + + $publishedPages = $mod->publishedPages; + $this->assertCount(1, $publishedPages); + $this->assertTrue($publishedPages->contains($publishedPage)); + $this->assertFalse($publishedPages->contains($unpublishedPage)); + } + + public function test_index_page_relationship() + { + $user = User::factory()->create(); + $mod = Mod::factory()->create(['owner_id' => $user->id]); + $indexPage = Page::factory()->create(['mod_id' => $mod->id, 'is_index' => true]); + $regularPage = Page::factory()->create(['mod_id' => $mod->id, 'is_index' => false]); + + $retrievedIndexPage = $mod->indexPage; + $this->assertNotNull($retrievedIndexPage); + $this->assertEquals($indexPage->id, $retrievedIndexPage->id); + } +} From 7d97e837e6b531e5e60b545adb712852208a70a7 Mon Sep 17 00:00:00 2001 From: ItsNeil17 Date: Fri, 27 Feb 2026 22:51:33 +0530 Subject: [PATCH 2/5] feat: add Mod test --- tests/Feature/ModTest.php | 225 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 225 insertions(+) create mode 100644 tests/Feature/ModTest.php diff --git a/tests/Feature/ModTest.php b/tests/Feature/ModTest.php new file mode 100644 index 0000000..8ae38bb --- /dev/null +++ b/tests/Feature/ModTest.php @@ -0,0 +1,225 @@ +create(); + $this->actingAs($user); + + $response = $this->get(route('mods.index')); + $response->assertOk(); + $response->assertInertia(fn ($page) => $page->component('Mods/Index')); + } + + public function test_guest_cannot_view_mods_index() + { + $response = $this->get(route('mods.index')); + $response->assertRedirect(route('login')); + } + + public function test_authenticated_user_can_create_mod() + { + $user = User::factory()->create(); + $this->actingAs($user); + + $modData = [ + 'name' => 'Test Mod', + 'description' => 'A test mod description', + 'visibility' => 'public', + 'storage_driver' => 'local', + ]; + + $response = $this->post(route('mods.store'), $modData); + + $mod = Mod::where('name', 'Test Mod')->first(); + $this->assertNotNull($mod); + $this->assertEquals($user->id, $mod->owner_id); + $this->assertEquals('test-mod', $mod->slug); + + $response->assertRedirect(route('mods.show', $mod)); + } + + public function test_mod_creation_requires_name() + { + $user = User::factory()->create(); + $this->actingAs($user); + + $response = $this->post(route('mods.store'), [ + 'description' => 'A test mod description', + 'visibility' => 'public', + ]); + + $response->assertSessionHasErrors('name'); + } + + public function test_mod_creation_validates_visibility() + { + $user = User::factory()->create(); + $this->actingAs($user); + + $response = $this->post(route('mods.store'), [ + 'name' => 'Test Mod', + 'description' => 'A test mod description', + 'visibility' => 'invalid', + ]); + + $response->assertSessionHasErrors('visibility'); + } + + public function test_user_can_view_their_own_mod() + { + $user = User::factory()->create(); + $mod = Mod::factory()->create(['owner_id' => $user->id]); + $this->actingAs($user); + + $response = $this->get(route('mods.show', $mod)); + $response->assertOk(); + $response->assertInertia(fn ($page) => $page->component('Mods/Show')); + } + + public function test_user_can_view_public_mod() + { + $owner = User::factory()->create(); + $viewer = User::factory()->create(); + $mod = Mod::factory()->public()->create(['owner_id' => $owner->id]); + $this->actingAs($viewer); + + $response = $this->get(route('mods.show', $mod)); + $response->assertOk(); + } + + public function test_user_cannot_view_private_mod_they_dont_have_access_to() + { + $owner = User::factory()->create(); + $viewer = User::factory()->create(); + $mod = Mod::factory()->private()->create(['owner_id' => $owner->id]); + $this->actingAs($viewer); + + $response = $this->get(route('mods.show', $mod)); + $response->assertForbidden(); + } + + public function test_collaborator_can_view_private_mod() + { + $owner = User::factory()->create(); + $collaborator = User::factory()->create(); + $mod = Mod::factory()->private()->create(['owner_id' => $owner->id]); + + // Add collaborator + $mod->collaborators()->attach($collaborator->id, [ + 'role' => 'editor', + 'invited_by' => $owner->id + ]); + + $this->actingAs($collaborator); + + $response = $this->get(route('mods.show', $mod)); + $response->assertOk(); + } + + public function test_owner_can_update_mod() + { + $user = User::factory()->create(); + $mod = Mod::factory()->create(['owner_id' => $user->id]); + $this->actingAs($user); + + $updateData = [ + 'name' => 'Updated Mod Name', + 'description' => 'Updated description', + 'visibility' => 'private', + 'storage_driver' => 'local', // Add required field + ]; + + $response = $this->patch(route('mods.update', $mod), $updateData); + + $mod->refresh(); + $this->assertEquals('Updated Mod Name', $mod->name); + $this->assertEquals('private', $mod->visibility); + + $response->assertRedirect(route('mods.show', $mod)); + } + + public function test_non_owner_cannot_update_mod() + { + $owner = User::factory()->create(); + $user = User::factory()->create(); + $mod = Mod::factory()->create(['owner_id' => $owner->id]); + $this->actingAs($user); + + $response = $this->patch(route('mods.update', $mod), [ + 'name' => 'Updated Name' + ]); + + $response->assertForbidden(); + } + + public function test_owner_can_delete_mod() + { + $user = User::factory()->create(); + $mod = Mod::factory()->create(['owner_id' => $user->id]); + $this->actingAs($user); + + $response = $this->delete(route('mods.destroy', $mod)); + + $this->assertSoftDeleted($mod); + $response->assertRedirect(route('mods.index')); + } + + public function test_non_owner_cannot_delete_mod() + { + $owner = User::factory()->create(); + $user = User::factory()->create(); + $mod = Mod::factory()->create(['owner_id' => $owner->id]); + $this->actingAs($user); + + $response = $this->delete(route('mods.destroy', $mod)); + $response->assertForbidden(); + } + + public function test_guest_can_view_public_mod_page() + { + $owner = User::factory()->create(); + $mod = Mod::factory()->public()->create(['owner_id' => $owner->id]); + + $response = $this->get("/docs/{$mod->slug}"); + $response->assertOk(); + $response->assertInertia(fn ($page) => $page->component('Public/Mod')); + } + + public function test_guest_cannot_view_private_mod_page() + { + $owner = User::factory()->create(); + $mod = Mod::factory()->private()->create(['owner_id' => $owner->id]); + + $response = $this->get("/docs/{$mod->slug}"); + $response->assertNotFound(); // Private mods are not found in public routes + } + + public function test_mod_slug_is_generated_from_name() + { + $user = User::factory()->create(); + $this->actingAs($user); + + $response = $this->post(route('mods.store'), [ + 'name' => 'My Awesome Mod Name', + 'description' => 'Description', + 'visibility' => 'public', + 'storage_driver' => 'local', // Add required field + ]); + + $mod = Mod::where('name', 'My Awesome Mod Name')->first(); + $this->assertNotNull($mod); + $this->assertEquals('my-awesome-mod-name', $mod->slug); + } +} From cb5281d8e961e0ec50436c846264059f2a2bbc20 Mon Sep 17 00:00:00 2001 From: ItsNeil17 Date: Fri, 27 Feb 2026 22:51:38 +0530 Subject: [PATCH 3/5] feat: add page test --- tests/Feature/PageTest.php | 388 +++++++++++++++++++++++++++++++++++++ 1 file changed, 388 insertions(+) create mode 100644 tests/Feature/PageTest.php diff --git a/tests/Feature/PageTest.php b/tests/Feature/PageTest.php new file mode 100644 index 0000000..1e4e8c7 --- /dev/null +++ b/tests/Feature/PageTest.php @@ -0,0 +1,388 @@ +create(); + $mod = Mod::factory()->create(['owner_id' => $user->id]); + $this->actingAs($user); + + $response = $this->get(route('pages.index', $mod)); + $response->assertOk(); + $response->assertInertia(fn ($page) => $page->component('Pages/Index')); + } + + public function test_collaborator_can_view_pages_index() + { + $owner = User::factory()->create(); + $collaborator = User::factory()->create(); + $mod = Mod::factory()->create(['owner_id' => $owner->id]); + + $mod->collaborators()->attach($collaborator->id, [ + 'role' => 'editor', + 'invited_by' => $owner->id + ]); + + $this->actingAs($collaborator); + + $response = $this->get(route('pages.index', $mod)); + $response->assertOk(); + } + + public function test_unauthorized_user_cannot_view_pages_index() + { + $owner = User::factory()->create(); + $user = User::factory()->create(); + $mod = Mod::factory()->private()->create(['owner_id' => $owner->id]); + $this->actingAs($user); + + $response = $this->get(route('pages.index', $mod)); + $response->assertForbidden(); + } + + public function test_owner_can_create_page() + { + $user = User::factory()->create(); + $mod = Mod::factory()->create(['owner_id' => $user->id]); + $this->actingAs($user); + + $pageData = [ + 'title' => 'Test Page', + 'content' => 'This is test content.', + 'parent_id' => null, + 'is_index' => false, + 'published' => true, + ]; + + $response = $this->post(route('pages.store', $mod), $pageData); + + if ($response->status() === 422) { + $this->fail('Validation failed: ' . json_encode($response->json())); + } elseif ($response->status() !== 302) { + $this->fail('Expected redirect (302) but got status: ' . $response->status()); + } + + $page = Page::where('title', 'Test Page')->first(); + $this->assertNotNull($page); + $this->assertEquals($mod->id, $page->mod_id); + $this->assertEquals($user->id, $page->created_by); + $this->assertEquals('test-page', $page->slug); + + $response->assertRedirect(route('pages.show', [$mod, $page])); + } + + public function test_editor_can_create_page() + { + $owner = User::factory()->create(); + $editor = User::factory()->create(); + $mod = Mod::factory()->create(['owner_id' => $owner->id]); + + $mod->collaborators()->attach($editor->id, [ + 'role' => 'editor', + 'invited_by' => $owner->id + ]); + + $this->actingAs($editor); + + $pageData = [ + 'title' => 'Editor Page', + 'content' => 'This is test content.', + 'parent_id' => null, + 'is_index' => false, + 'published' => true, + ]; + + + $response = $this->post(route('pages.store', $mod), $pageData); + + $page = Page::where('title', 'Editor Page')->first(); + $this->assertNotNull($page); + $this->assertEquals($editor->id, $page->created_by); + } + + public function test_viewer_cannot_create_page() + { + $owner = User::factory()->create(); + $viewer = User::factory()->create(); + $mod = Mod::factory()->create(['owner_id' => $owner->id]); + + $mod->collaborators()->attach($viewer->id, [ + 'role' => 'viewer', + 'invited_by' => $owner->id + ]); + + $this->actingAs($viewer); + + $response = $this->post(route('pages.store', $mod), [ + 'title' => 'Viewer Page', + 'content' => 'Should not work.', + ]); + + $response->assertForbidden(); + } + + public function test_page_creation_requires_title() + { + $user = User::factory()->create(); + $mod = Mod::factory()->create(['owner_id' => $user->id]); + $this->actingAs($user); + + $response = $this->post(route('pages.store', $mod), [ + 'content' => 'Content without title', + ]); + + $response->assertSessionHasErrors('title'); + } + + public function test_user_can_view_page() + { + $user = User::factory()->create(); + $mod = Mod::factory()->create(['owner_id' => $user->id]); + $page = Page::factory()->create(['mod_id' => $mod->id, 'published' => true]); + $this->actingAs($user); + + $response = $this->get(route('pages.show', [$mod, $page])); + $response->assertOk(); + $response->assertInertia(fn ($page) => $page->component('Pages/Show')); + } + + public function test_guest_can_view_published_page_in_public_mod() + { + $user = User::factory()->create(); + $mod = Mod::factory()->public()->create(['owner_id' => $user->id]); + $page = Page::factory()->create(['mod_id' => $mod->id, 'published' => true]); + + $response = $this->get("/docs/{$mod->slug}/{$page->slug}"); + $response->assertOk(); + $response->assertInertia(fn ($page) => $page->component('Public/Page')); + } + + public function test_guest_cannot_view_unpublished_page() + { + $user = User::factory()->create(); + $mod = Mod::factory()->public()->create(['owner_id' => $user->id]); + $page = Page::factory()->create(['mod_id' => $mod->id, 'published' => false]); + + $response = $this->get("/docs/{$mod->slug}/{$page->slug}"); + $response->assertNotFound(); + } + + public function test_owner_can_view_unpublished_page() + { + $user = User::factory()->create(); + $mod = Mod::factory()->create(['owner_id' => $user->id]); + $page = Page::factory()->create(['mod_id' => $mod->id, 'published' => false]); + $this->actingAs($user); + + $response = $this->get(route('pages.show', [$mod, $page])); + $response->assertOk(); + } + + public function test_owner_can_update_page() + { + $user = User::factory()->create(); + $mod = Mod::factory()->create(['owner_id' => $user->id]); + $page = Page::factory()->create(['mod_id' => $mod->id]); + $this->actingAs($user); + + $updateData = [ + 'title' => 'Updated Page Title', + 'content' => 'Updated content.', + 'parent_id' => $page->parent_id, + 'is_index' => $page->is_index, + 'published' => $page->published, + ]; + + $response = $this->patch(route('pages.update', [$mod, $page]), $updateData); + + $page->refresh(); + $this->assertEquals('Updated Page Title', $page->title); + $this->assertEquals('Updated content.', $page->content); + $this->assertEquals($user->id, $page->updated_by); + + $response->assertRedirect(route('pages.show', [$mod, $page])); + } + + public function test_editor_can_update_page() + { + $owner = User::factory()->create(); + $editor = User::factory()->create(); + $mod = Mod::factory()->create(['owner_id' => $owner->id]); + $page = Page::factory()->create(['mod_id' => $mod->id]); + + $mod->collaborators()->attach($editor->id, [ + 'role' => 'editor', + 'invited_by' => $owner->id + ]); + + $this->actingAs($editor); + + $response = $this->patch(route('pages.update', [$mod, $page]), [ + 'title' => 'Editor Updated', + 'content' => $page->content, + 'parent_id' => $page->parent_id, + 'is_index' => $page->is_index, + 'published' => $page->published, + ]); + + $page->refresh(); + $this->assertEquals('Editor Updated', $page->title); + $this->assertEquals($editor->id, $page->updated_by); + } + + public function test_viewer_cannot_update_page() + { + $owner = User::factory()->create(); + $viewer = User::factory()->create(); + $mod = Mod::factory()->create(['owner_id' => $owner->id]); + $page = Page::factory()->create(['mod_id' => $mod->id]); + + $mod->collaborators()->attach($viewer->id, [ + 'role' => 'viewer', + 'invited_by' => $owner->id + ]); + + $this->actingAs($viewer); + + $response = $this->patch(route('pages.update', [$mod, $page]), [ + 'title' => 'Should not update', + ]); + + $response->assertForbidden(); + } + + public function test_owner_can_delete_page() + { + $user = User::factory()->create(); + $mod = Mod::factory()->create(['owner_id' => $user->id]); + $page = Page::factory()->create(['mod_id' => $mod->id]); + $this->actingAs($user); + + $response = $this->delete(route('pages.destroy', [$mod, $page])); + + $this->assertSoftDeleted($page); + $response->assertRedirect(route('mods.show', $mod)); + } + + public function test_editor_can_delete_page() + { + $owner = User::factory()->create(); + $editor = User::factory()->create(); + $mod = Mod::factory()->create(['owner_id' => $owner->id]); + $page = Page::factory()->create(['mod_id' => $mod->id]); + + $mod->collaborators()->attach($editor->id, [ + 'role' => 'editor', + 'invited_by' => $owner->id + ]); + + $this->actingAs($editor); + + $response = $this->delete(route('pages.destroy', [$mod, $page])); + $this->assertSoftDeleted($page); + } + + public function test_can_create_child_page() + { + $user = User::factory()->create(); + $mod = Mod::factory()->create(['owner_id' => $user->id]); + $parentPage = Page::factory()->create(['mod_id' => $mod->id]); + $this->actingAs($user); + + $pageData = [ + 'title' => 'Child Page', + 'content' => 'Child content.', + 'parent_id' => $parentPage->id, + 'published' => true, + ]; + + $response = $this->post(route('pages.store', $mod), $pageData); + + $childPage = Page::where('title', 'Child Page')->first(); + $this->assertNotNull($childPage); + $this->assertEquals($parentPage->id, $childPage->parent_id); + } + + public function test_can_reorder_pages() + { + $user = User::factory()->create(); + $mod = Mod::factory()->create(['owner_id' => $user->id]); + $page1 = Page::factory()->create(['mod_id' => $mod->id, 'order_index' => 1]); + $page2 = Page::factory()->create(['mod_id' => $mod->id, 'order_index' => 2]); + $this->actingAs($user); + + $response = $this->post(route('pages.reorder', $mod), [ + 'pages' => [ + ['id' => $page2->id, 'parent_id' => null, 'order_index' => 1], + ['id' => $page1->id, 'parent_id' => null, 'order_index' => 2], + ] + ]); + + $response->assertRedirect(); + + $page1->refresh(); + $page2->refresh(); + $this->assertEquals(2, $page1->order_index); + $this->assertEquals(1, $page2->order_index); + } + + public function test_can_autosave_page() + { + $user = User::factory()->create(); + $mod = Mod::factory()->create(['owner_id' => $user->id]); + $page = Page::factory()->create(['mod_id' => $mod->id]); + $this->actingAs($user); + + $response = $this->post(route('pages.autosave', [$mod, $page]), [ + 'content' => 'Autosaved content', + ]); + + $response->assertOk(); + } + public function test_page_slug_is_generated_from_title() + { + $user = User::factory()->create(); + $mod = Mod::factory()->create(['owner_id' => $user->id]); + $this->actingAs($user); + + $response = $this->post(route('pages.store', $mod), [ + 'title' => 'My Amazing Page Title', + 'content' => 'Content', + 'parent_id' => null, + 'is_index' => false, + 'published' => true, + ]); + $page = Page::where('title', 'My Amazing Page Title')->first(); + $this->assertEquals('my-amazing-page-title', $page->slug); + } + + public function test_page_validates_parent_belongs_to_same_mod() + { + $user = User::factory()->create(); + $mod1 = Mod::factory()->create(['owner_id' => $user->id]); + $mod2 = Mod::factory()->create(['owner_id' => $user->id]); + $parentPage = Page::factory()->create(['mod_id' => $mod2->id]); + $this->actingAs($user); + + $response = $this->post(route('pages.store', $mod1), [ + 'title' => 'Invalid Parent Page', + 'content' => 'Content', + 'parent_id' => $parentPage->id, + ]); + + $response->assertStatus(422); + } +} From c5570b550dc95864d2e6d53c81ba2d59c2eda7f7 Mon Sep 17 00:00:00 2001 From: ItsNeil17 Date: Fri, 27 Feb 2026 22:51:44 +0530 Subject: [PATCH 4/5] feat: add Mod Collaborator test --- app/Http/Controllers/ModController.php | 49 ++- routes/web.php | 2 +- tests/Feature/ModCollaboratorTest.php | 422 +++++++++++++++++++++++++ 3 files changed, 464 insertions(+), 9 deletions(-) create mode 100644 tests/Feature/ModCollaboratorTest.php diff --git a/app/Http/Controllers/ModController.php b/app/Http/Controllers/ModController.php index 69d8be8..56cf6d1 100644 --- a/app/Http/Controllers/ModController.php +++ b/app/Http/Controllers/ModController.php @@ -217,18 +217,24 @@ public function addCollaborator(Request $request, Mod $mod) } $validated = $request->validate([ - 'username' => 'required|string|exists:users,username', + 'username' => 'required|string', 'role' => 'required|in:admin,editor,viewer', ]); - $collaborator = User::where('username', $validated['username'])->firstOrFail(); + $collaborator = User::where('username', $validated['username']) + ->orWhere('email', $validated['username']) + ->first(); + + if (!$collaborator) { + return back()->withErrors(['email' => 'User not found.']); + } if ($mod->collaborators()->where('user_id', $collaborator->id)->exists()) { - return back()->withErrors(['username' => 'User is already a collaborator.']); + return back()->withErrors(['email' => 'User is already a collaborator.']); } if ($mod->owner_id === $collaborator->id) { - return back()->withErrors(['username' => 'Owner cannot be added as collaborator.']); + return back()->withErrors(['email' => 'Owner cannot be added as collaborator.']); } $existingInvitation = ModInvitation::where('mod_id', $mod->id) @@ -238,11 +244,17 @@ public function addCollaborator(Request $request, Mod $mod) ->first(); if ($existingInvitation) { - return back()->withErrors(['username' => 'User already has a pending invitation.']); + return back()->withErrors(['email' => 'User already has a pending invitation.']); } - $invitation = ModInvitation::createInvitation($mod, $collaborator, $user, $validated['role']); + // Add collaborator directly to mod_users table + $mod->collaborators()->attach($collaborator->id, [ + 'role' => $validated['role'], + 'invited_by' => $user->id + ]); + // Also create and send invitation email + $invitation = ModInvitation::createInvitation($mod, $collaborator, $user, $validated['role']); $inviteUrl = route('invitations.show', ['token' => $invitation->token]); try { @@ -256,8 +268,7 @@ public function addCollaborator(Request $request, Mod $mod) return back()->with('success', "Invitation sent to {$collaborator->name}!"); } catch (\Exception $e) { - $invitation->delete(); - return back()->withErrors(['email' => 'Failed to send invitation email. Please try again.']); + return back()->with('success', "Collaborator added successfully! (Email notification failed to send)"); } } @@ -268,10 +279,24 @@ public function removeCollaborator(Mod $mod, User $collaborator) { $user = Auth::user(); + // Allow users to remove themselves + if ($user->id === $collaborator->id) { + $mod->collaborators()->detach($collaborator->id); + return back()->with('success', 'You have left the mod successfully!'); + } + if (!$mod->userCan($user, 'manage_collaborators')) { abort(403); } + $currentUserRole = $mod->getUserRole($user); + $targetUserRole = $mod->getUserRole($collaborator); + + // Admins cannot remove other admins (only owners can) + if ($currentUserRole === 'admin' && $targetUserRole === 'admin') { + abort(403); + } + $mod->collaborators()->detach($collaborator->id); return back()->with('success', 'Collaborator removed successfully!'); @@ -292,6 +317,14 @@ public function updateCollaboratorRole(Request $request, Mod $mod, User $collabo 'role' => 'required|in:admin,editor,viewer', ]); + $currentUserRole = $mod->getUserRole($user); + $targetRole = $validated['role']; + + // Only owners can promote to admin + if ($targetRole === 'admin' && $currentUserRole !== 'owner') { + abort(403); + } + $mod->collaborators()->updateExistingPivot($collaborator->id, [ 'role' => $validated['role'] ]); diff --git a/routes/web.php b/routes/web.php index 5640a62..6365e42 100644 --- a/routes/web.php +++ b/routes/web.php @@ -58,6 +58,7 @@ Route::get('mods/{mod:slug}/pages', [PageController::class, 'index'])->name('pages.index'); Route::get('mods/{mod:slug}/pages/create', [PageController::class, 'create'])->name('pages.create'); + Route::get('mods/{mod:slug}/pages/search', [PageController::class, 'search'])->name('pages.search'); Route::post('mods/{mod:slug}/pages', [PageController::class, 'store'])->name('pages.store'); Route::get('mods/{mod:slug}/pages/{page:slug}', [PageController::class, 'show'])->name('pages.show'); Route::get('mods/{mod:slug}/pages/{page:slug}/edit', [PageController::class, 'edit'])->name('pages.edit'); @@ -66,7 +67,6 @@ Route::post('mods/{mod:slug}/pages/reorder', [PageController::class, 'updateOrder'])->name('pages.reorder'); Route::post('mods/{mod:slug}/pages/{page:slug}/autosave', [PageController::class, 'autoSave'])->name('pages.autosave'); - Route::get('mods/{mod:slug}/pages/search', [PageController::class, 'search'])->name('pages.search'); Route::get('mods/{mod:slug}/files', [FileController::class, 'index'])->name('files.index'); Route::post('mods/{mod:slug}/files', [FileController::class, 'store'])->name('files.store'); diff --git a/tests/Feature/ModCollaboratorTest.php b/tests/Feature/ModCollaboratorTest.php new file mode 100644 index 0000000..25473ef --- /dev/null +++ b/tests/Feature/ModCollaboratorTest.php @@ -0,0 +1,422 @@ +create(); + $mod = Mod::factory()->create(['owner_id' => $user->id]); + $this->actingAs($user); + + $response = $this->get(route('mods.collaborators.index', $mod)); + $response->assertOk(); + $response->assertInertia(fn ($page) => $page->component('Mods/ManageCollaborators')); + } + + public function test_admin_can_view_collaborators_page() + { + $owner = User::factory()->create(); + $admin = User::factory()->create(); + $mod = Mod::factory()->create(['owner_id' => $owner->id]); + + $mod->collaborators()->attach($admin->id, [ + 'role' => 'admin', + 'invited_by' => $owner->id + ]); + + $this->actingAs($admin); + + $response = $this->get(route('mods.collaborators.index', $mod)); + $response->assertOk(); + } + + public function test_editor_cannot_view_collaborators_page() + { + $owner = User::factory()->create(); + $editor = User::factory()->create(); + $mod = Mod::factory()->create(['owner_id' => $owner->id]); + + $mod->collaborators()->attach($editor->id, [ + 'role' => 'editor', + 'invited_by' => $owner->id + ]); + + $this->actingAs($editor); + + $response = $this->get(route('mods.collaborators.index', $mod)); + $response->assertForbidden(); + } + + public function test_owner_can_add_collaborator_by_email() + { + Mail::fake(); + + $owner = User::factory()->create(); + $collaborator = User::factory()->create(); + $mod = Mod::factory()->create(['owner_id' => $owner->id]); + $this->actingAs($owner); + + $response = $this->post(route('mods.collaborators.store', $mod), [ + 'username' => $collaborator->username, + 'role' => 'editor', + ]); + + $response->assertRedirect(); + $response->assertSessionHas('success'); + + $this->assertDatabaseHas('mod_users', [ + 'mod_id' => $mod->id, + 'user_id' => $collaborator->id, + 'role' => 'editor', + 'invited_by' => $owner->id, + ]); + + Mail::assertSent(CollaboratorInvitation::class); + } + + public function test_owner_can_add_collaborator_by_username() + { + Mail::fake(); + + $owner = User::factory()->create(); + $collaborator = User::factory()->create(['username' => 'testuser']); + $mod = Mod::factory()->create(['owner_id' => $owner->id]); + $this->actingAs($owner); + + $response = $this->post(route('mods.collaborators.store', $mod), [ + 'username' => 'testuser', + 'role' => 'viewer', + ]); + + $response->assertRedirect(); + + $this->assertDatabaseHas('mod_users', [ + 'mod_id' => $mod->id, + 'user_id' => $collaborator->id, + 'role' => 'viewer', + ]); + } + + public function test_cannot_add_nonexistent_user() + { + $owner = User::factory()->create(); + $mod = Mod::factory()->create(['owner_id' => $owner->id]); + $this->actingAs($owner); + + $response = $this->post(route('mods.collaborators.store', $mod), [ + 'username' => 'nonexistent@example.com', + 'role' => 'editor', + ]); + + $response->assertSessionHasErrors('email'); + } + + public function test_cannot_add_owner_as_collaborator() + { + $owner = User::factory()->create(); + $mod = Mod::factory()->create(['owner_id' => $owner->id]); + $this->actingAs($owner); + + $response = $this->post(route('mods.collaborators.store', $mod), [ + 'username' => $owner->email, + 'role' => 'editor', + ]); + + $response->assertSessionHasErrors('email'); + } + + public function test_cannot_add_existing_collaborator() + { + $owner = User::factory()->create(); + $collaborator = User::factory()->create(); + $mod = Mod::factory()->create(['owner_id' => $owner->id]); + + $mod->collaborators()->attach($collaborator->id, [ + 'role' => 'editor', + 'invited_by' => $owner->id + ]); + + $this->actingAs($owner); + + $response = $this->post(route('mods.collaborators.store', $mod), [ + 'username' => $collaborator->email, + 'role' => 'admin', + ]); + + $response->assertSessionHasErrors('email'); + } + + public function test_role_validation() + { + $owner = User::factory()->create(); + $collaborator = User::factory()->create(); + $mod = Mod::factory()->create(['owner_id' => $owner->id]); + $this->actingAs($owner); + + $response = $this->post(route('mods.collaborators.store', $mod), [ + 'username' => $collaborator->email, + 'role' => 'invalid_role', + ]); + + $response->assertSessionHasErrors('role'); + } + + public function test_owner_can_update_collaborator_role() + { + $owner = User::factory()->create(); + $collaborator = User::factory()->create(); + $mod = Mod::factory()->create(['owner_id' => $owner->id]); + + $mod->collaborators()->attach($collaborator->id, [ + 'role' => 'viewer', + 'invited_by' => $owner->id + ]); + + $this->actingAs($owner); + + $response = $this->patch(route('mods.collaborators.update', [$mod, $collaborator]), [ + 'role' => 'editor', + ]); + + $response->assertRedirect(); + + $this->assertDatabaseHas('mod_users', [ + 'mod_id' => $mod->id, + 'user_id' => $collaborator->id, + 'role' => 'editor', + ]); + } + + public function test_admin_can_update_collaborator_role_but_not_to_admin() + { + $owner = User::factory()->create(); + $admin = User::factory()->create(); + $editor = User::factory()->create(); + $mod = Mod::factory()->create(['owner_id' => $owner->id]); + + $mod->collaborators()->attach($admin->id, [ + 'role' => 'admin', + 'invited_by' => $owner->id + ]); + $mod->collaborators()->attach($editor->id, [ + 'role' => 'editor', + 'invited_by' => $owner->id + ]); + + $this->actingAs($admin); + + // Admin should be able to downgrade editor to viewer + $response = $this->patch(route('mods.collaborators.update', [$mod, $editor]), [ + 'role' => 'viewer', + ]); + $response->assertRedirect(); + + // But admin should not be able to promote to admin + $response = $this->patch(route('mods.collaborators.update', [$mod, $editor]), [ + 'role' => 'admin', + ]); + $response->assertForbidden(); + } + + public function test_editor_cannot_update_collaborator_role() + { + $owner = User::factory()->create(); + $editor = User::factory()->create(); + $viewer = User::factory()->create(); + $mod = Mod::factory()->create(['owner_id' => $owner->id]); + + $mod->collaborators()->attach($editor->id, [ + 'role' => 'editor', + 'invited_by' => $owner->id + ]); + $mod->collaborators()->attach($viewer->id, [ + 'role' => 'viewer', + 'invited_by' => $owner->id + ]); + + $this->actingAs($editor); + + $response = $this->patch(route('mods.collaborators.update', [$mod, $viewer]), [ + 'role' => 'editor', + ]); + + $response->assertForbidden(); + } + + public function test_owner_can_remove_collaborator() + { + $owner = User::factory()->create(); + $collaborator = User::factory()->create(); + $mod = Mod::factory()->create(['owner_id' => $owner->id]); + + $mod->collaborators()->attach($collaborator->id, [ + 'role' => 'editor', + 'invited_by' => $owner->id + ]); + + $this->actingAs($owner); + + $response = $this->delete(route('mods.collaborators.destroy', [$mod, $collaborator])); + + $response->assertRedirect(); + + $this->assertDatabaseMissing('mod_users', [ + 'mod_id' => $mod->id, + 'user_id' => $collaborator->id, + ]); + } + + public function test_admin_can_remove_non_admin_collaborator() + { + $owner = User::factory()->create(); + $admin = User::factory()->create(); + $editor = User::factory()->create(); + $mod = Mod::factory()->create(['owner_id' => $owner->id]); + + $mod->collaborators()->attach($admin->id, [ + 'role' => 'admin', + 'invited_by' => $owner->id + ]); + $mod->collaborators()->attach($editor->id, [ + 'role' => 'editor', + 'invited_by' => $owner->id + ]); + + $this->actingAs($admin); + + $response = $this->delete(route('mods.collaborators.destroy', [$mod, $editor])); + $response->assertRedirect(); + } + + public function test_admin_cannot_remove_other_admin() + { + $owner = User::factory()->create(); + $admin1 = User::factory()->create(); + $admin2 = User::factory()->create(); + $mod = Mod::factory()->create(['owner_id' => $owner->id]); + + $mod->collaborators()->attach($admin1->id, [ + 'role' => 'admin', + 'invited_by' => $owner->id + ]); + $mod->collaborators()->attach($admin2->id, [ + 'role' => 'admin', + 'invited_by' => $owner->id + ]); + + $this->actingAs($admin1); + + $response = $this->delete(route('mods.collaborators.destroy', [$mod, $admin2])); + $response->assertForbidden(); + } + + public function test_collaborator_can_remove_themselves() + { + $owner = User::factory()->create(); + $collaborator = User::factory()->create(); + $mod = Mod::factory()->create(['owner_id' => $owner->id]); + + $mod->collaborators()->attach($collaborator->id, [ + 'role' => 'editor', + 'invited_by' => $owner->id + ]); + + $this->actingAs($collaborator); + + $response = $this->delete(route('mods.collaborators.destroy', [$mod, $collaborator])); + + $response->assertRedirect(); + + $this->assertDatabaseMissing('mod_users', [ + 'mod_id' => $mod->id, + 'user_id' => $collaborator->id, + ]); + } + + public function test_can_show_invitation_page() + { + $owner = User::factory()->create(); + $mod = Mod::factory()->create(['owner_id' => $owner->id]); + $invitee = User::factory()->create(['email' => 'invited@example.com']); + $this->actingAs($invitee); + + $invitation = ModInvitation::create([ + 'mod_id' => $mod->id, + 'user_id' => $invitee->id, + 'email' => $invitee->email, + 'role' => 'editor', + 'token' => 'invitation-token', + 'invited_by' => $owner->id, + 'expires_at' => now()->addDays(7), + ]); + + $response = $this->get(route('invitations.show', $invitation->token)); + $response->assertOk(); + $response->assertInertia(fn ($page) => $page->component('Invitations/Accept')); + } + + public function test_can_accept_invitation() + { + $owner = User::factory()->create(); + $invitee = User::factory()->create(['email' => 'invited@example.com']); + $mod = Mod::factory()->create(['owner_id' => $owner->id]); + + $invitation = ModInvitation::create([ + 'mod_id' => $mod->id, + 'user_id' => $invitee->id, + 'email' => 'invited@example.com', + 'role' => 'editor', + 'token' => 'invitation-token', + 'invited_by' => $owner->id, + 'expires_at' => now()->addDays(7), + ]); + + $this->actingAs($invitee); + + $response = $this->post(route('invitations.accept', $invitation->token)); + + $response->assertRedirect(route('mods.show', $mod)); + + $this->assertDatabaseHas('mod_users', [ + 'mod_id' => $mod->id, + 'user_id' => $invitee->id, + 'role' => 'editor', + ]); + + } + + + public function test_guest_cannot_accept_invitation() + { + $owner = User::factory()->create(); + $mod = Mod::factory()->create(['owner_id' => $owner->id]); + $invitee = User::factory()->create(['email' => 'invited@example.com']); + + $invitation = ModInvitation::create([ + 'mod_id' => $mod->id, + 'user_id' => $invitee->id, + 'email' => $invitee->email, + 'role' => 'editor', + 'token' => 'invitation-token', + 'invited_by' => $owner->id, + 'expires_at' => now()->addDays(7), + ]); + + $response = $this->post(route('invitations.accept', $invitation->token)); + $response->assertRedirect(route('login')); + } +} From 360f12c55ce98158d9cbe9613bdea16cc2b82e9c Mon Sep 17 00:00:00 2001 From: ItsNeil17 Date: Fri, 27 Feb 2026 22:54:17 +0530 Subject: [PATCH 5/5] chore: lint Signed-off-by: ItsNeil17 --- app/Http/Controllers/FileController.php | 34 ++++++------- app/Http/Controllers/ModController.php | 44 ++++++++--------- app/Http/Controllers/PageController.php | 49 +++++++++---------- .../Settings/ProfileController.php | 8 +-- .../TwoFactorAuthenticationController.php | 1 - app/Mail/CollaboratorInvitation.php | 1 - app/Models/File.php | 2 +- app/Models/Mod.php | 9 ++-- app/Models/ModInvitation.php | 2 +- app/Models/ModUser.php | 1 - app/Models/Page.php | 4 +- app/Models/User.php | 8 +-- database/factories/ModFactory.php | 2 +- database/factories/PageFactory.php | 7 +-- database/seeders/DatabaseSeeder.php | 1 - routes/web.php | 2 +- tests/Feature/ModCollaboratorTest.php | 30 ++++++------ tests/Feature/ModTest.php | 4 +- tests/Feature/PageTest.php | 21 ++++---- tests/Unit/ModModelTest.php | 2 +- 20 files changed, 112 insertions(+), 120 deletions(-) diff --git a/app/Http/Controllers/FileController.php b/app/Http/Controllers/FileController.php index e7ab4ae..b8fdd37 100644 --- a/app/Http/Controllers/FileController.php +++ b/app/Http/Controllers/FileController.php @@ -7,14 +7,12 @@ use App\Models\Page; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; -use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Storage; use Illuminate\Support\Str; use Inertia\Inertia; class FileController extends Controller { - /** * Display files for a mod. */ @@ -22,7 +20,7 @@ public function index(Mod $mod) { $user = Auth::user(); - if (!$mod->canBeAccessedBy($user)) { + if (! $mod->canBeAccessedBy($user)) { abort(403); } @@ -64,7 +62,7 @@ public function store(Request $request, Mod $mod) { $user = Auth::user(); - if (!$mod->userCan($user, 'edit')) { + if (! $mod->userCan($user, 'edit')) { abort(403); } $request->validate([ @@ -88,7 +86,7 @@ public function store(Request $request, Mod $mod) foreach ($uploadedFiles as $uploadedFile) { $originalName = $uploadedFile->getClientOriginalName(); $extension = $uploadedFile->getClientOriginalExtension(); - $filename = Str::uuid() . '.' . $extension; + $filename = Str::uuid().'.'.$extension; $path = "mods/{$mod->id}/files/{$filename}"; @@ -103,7 +101,7 @@ public function store(Request $request, Mod $mod) 'path' => $path, 'mime_type' => $uploadedFile->getMimeType(), 'size' => $uploadedFile->getSize(), - 'storage_driver' => "local", // hard coded local for now + 'storage_driver' => 'local', // hard coded local for now 'uploaded_by' => $user->id, ]); $url = Storage::disk('public')->url($path); @@ -122,11 +120,11 @@ public function store(Request $request, Mod $mod) return response()->json([ 'success' => true, 'files' => $uploadedFileData, - 'message' => count($uploadedFiles) . ' file(s) uploaded successfully!' + 'message' => count($uploadedFiles).' file(s) uploaded successfully!', ]); } - return redirect()->back()->with('success', count($uploadedFiles) . ' file(s) uploaded successfully!'); + return redirect()->back()->with('success', count($uploadedFiles).' file(s) uploaded successfully!'); } /** @@ -140,7 +138,7 @@ public function show(Mod $mod, File $file) abort(404); } - if (!$mod->canBeAccessedBy($user)) { + if (! $mod->canBeAccessedBy($user)) { abort(403); } @@ -164,13 +162,13 @@ public function download(Mod $mod, File $file) abort(404); } - if (!$mod->canBeAccessedBy($user)) { + if (! $mod->canBeAccessedBy($user)) { abort(403); } $disk = 'public'; - if (!Storage::disk($disk)->exists($file->path)) { + if (! Storage::disk($disk)->exists($file->path)) { abort(404, 'File not found on storage.'); } @@ -188,7 +186,7 @@ public function destroy(Mod $mod, File $file) abort(404); } - if (!$mod->userCan($user, 'edit')) { + if (! $mod->userCan($user, 'edit')) { abort(403); } @@ -208,7 +206,7 @@ public function quickUpload(Request $request, Mod $mod) { $user = Auth::user(); - if (!$mod->userCan($user, 'edit')) { + if (! $mod->userCan($user, 'edit')) { abort(403); } @@ -221,16 +219,16 @@ public function quickUpload(Request $request, Mod $mod) $allowedMimes = [ 'image/jpeg', 'image/png', 'image/gif', 'image/webp', 'application/pdf', 'text/plain', 'text/markdown', - 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' + 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', ]; - if (!in_array($uploadedFile->getMimeType(), $allowedMimes)) { + if (! in_array($uploadedFile->getMimeType(), $allowedMimes)) { return response()->json(['error' => 'File type not allowed.'], 422); } $originalName = $uploadedFile->getClientOriginalName(); $extension = $uploadedFile->getClientOriginalExtension(); - $filename = Str::uuid() . '.' . $extension; + $filename = Str::uuid().'.'.$extension; $path = "mods/{$mod->id}/files/{$filename}"; $disk = 'public'; @@ -260,7 +258,7 @@ public function quickUpload(Request $request, Mod $mod) 'size' => $file->human_size, 'is_image' => $file->isImage(), ], - 'message' => 'File uploaded successfully!' + 'message' => 'File uploaded successfully!', ]); } @@ -275,7 +273,7 @@ public function getPageFiles(Mod $mod, Page $page) abort(404); } - if (!$mod->canBeAccessedBy($user)) { + if (! $mod->canBeAccessedBy($user)) { abort(403); } diff --git a/app/Http/Controllers/ModController.php b/app/Http/Controllers/ModController.php index 56cf6d1..443b386 100644 --- a/app/Http/Controllers/ModController.php +++ b/app/Http/Controllers/ModController.php @@ -14,7 +14,6 @@ class ModController extends Controller { - /** * Display a listing of user's mods. */ @@ -22,7 +21,7 @@ public function index() { $user = Auth::user(); - if (!$user) { + if (! $user) { return redirect()->route('login'); } @@ -69,7 +68,7 @@ public function store(Request $request) $counter = 1; while (Mod::where('slug', $slug)->exists()) { - $slug = $originalSlug . '-' . $counter; + $slug = $originalSlug.'-'.$counter; $counter++; } @@ -93,7 +92,7 @@ public function show(Mod $mod) { $user = Auth::user(); - if ($mod->visibility === 'private' && !$mod->canBeAccessedBy($user)) { + if ($mod->visibility === 'private' && ! $mod->canBeAccessedBy($user)) { abort(403); } @@ -103,7 +102,7 @@ public function show(Mod $mod) 'rootPages' => function ($query) { $query->published()->with('publishedChildren'); }, - 'indexPage' + 'indexPage', ]); $userRole = $user ? $mod->getUserRole($user) : null; @@ -123,7 +122,7 @@ public function edit(Mod $mod) { $user = Auth::user(); - if (!$mod->userCan($user, 'manage_settings')) { + if (! $mod->userCan($user, 'manage_settings')) { abort(403); } @@ -139,7 +138,7 @@ public function update(Request $request, Mod $mod) { $user = Auth::user(); - if (!$mod->userCan($user, 'manage_settings')) { + if (! $mod->userCan($user, 'manage_settings')) { abort(403); } @@ -156,7 +155,7 @@ public function update(Request $request, Mod $mod) $counter = 1; while (Mod::where('slug', $slug)->where('id', '!=', $mod->id)->exists()) { - $slug = $originalSlug . '-' . $counter; + $slug = $originalSlug.'-'.$counter; $counter++; } @@ -193,7 +192,7 @@ public function manageCollaborators(Mod $mod) { $user = Auth::user(); - if (!$mod->userCan($user, 'manage_collaborators')) { + if (! $mod->userCan($user, 'manage_collaborators')) { abort(403); } @@ -212,7 +211,7 @@ public function addCollaborator(Request $request, Mod $mod) { $user = Auth::user(); - if (!$mod->userCan($user, 'manage_collaborators')) { + if (! $mod->userCan($user, 'manage_collaborators')) { abort(403); } @@ -225,7 +224,7 @@ public function addCollaborator(Request $request, Mod $mod) ->orWhere('email', $validated['username']) ->first(); - if (!$collaborator) { + if (! $collaborator) { return back()->withErrors(['email' => 'User not found.']); } @@ -250,7 +249,7 @@ public function addCollaborator(Request $request, Mod $mod) // Add collaborator directly to mod_users table $mod->collaborators()->attach($collaborator->id, [ 'role' => $validated['role'], - 'invited_by' => $user->id + 'invited_by' => $user->id, ]); // Also create and send invitation email @@ -268,7 +267,7 @@ public function addCollaborator(Request $request, Mod $mod) return back()->with('success', "Invitation sent to {$collaborator->name}!"); } catch (\Exception $e) { - return back()->with('success', "Collaborator added successfully! (Email notification failed to send)"); + return back()->with('success', 'Collaborator added successfully! (Email notification failed to send)'); } } @@ -282,10 +281,11 @@ public function removeCollaborator(Mod $mod, User $collaborator) // Allow users to remove themselves if ($user->id === $collaborator->id) { $mod->collaborators()->detach($collaborator->id); + return back()->with('success', 'You have left the mod successfully!'); } - if (!$mod->userCan($user, 'manage_collaborators')) { + if (! $mod->userCan($user, 'manage_collaborators')) { abort(403); } @@ -309,7 +309,7 @@ public function updateCollaboratorRole(Request $request, Mod $mod, User $collabo { $user = Auth::user(); - if (!$mod->userCan($user, 'manage_collaborators')) { + if (! $mod->userCan($user, 'manage_collaborators')) { abort(403); } @@ -326,7 +326,7 @@ public function updateCollaboratorRole(Request $request, Mod $mod, User $collabo } $mod->collaborators()->updateExistingPivot($collaborator->id, [ - 'role' => $validated['role'] + 'role' => $validated['role'], ]); return back()->with('success', 'Collaborator role updated successfully!'); @@ -345,7 +345,7 @@ public function publicShow($slug) $rootPages = $mod->pages() ->whereNull('parent_id') ->where('published', true) - ->with(['children' => function($query) { + ->with(['children' => function ($query) { $query->where('published', true)->orderBy('order_index'); }]) ->orderBy('order_index') @@ -358,7 +358,7 @@ public function publicShow($slug) return Inertia::render('Public/Mod', [ 'mod' => array_merge($mod->toArray(), [ - 'root_pages' => $rootPages->map(function($page) { + 'root_pages' => $rootPages->map(function ($page) { return [ 'id' => $page->id, 'title' => $page->title, @@ -366,7 +366,7 @@ public function publicShow($slug) 'content' => substr($page->content ?? '', 0, 200), 'published' => $page->published, 'updated_at' => $page->updated_at, - 'children' => $page->children->map(function($child) { + 'children' => $page->children->map(function ($child) { return [ 'id' => $child->id, 'title' => $child->title, @@ -408,10 +408,10 @@ public function showInvitation(string $token) } $user = Auth::user(); - if (!$user || $user->id !== $invitation->user_id) { + if (! $user || $user->id !== $invitation->user_id) { return Inertia::render('Invitations/Login', [ 'invitation' => $invitation, - 'needsLogin' => !$user, + 'needsLogin' => ! $user, 'wrongUser' => $user && $user->id !== $invitation->user_id, ]); } @@ -432,7 +432,7 @@ public function acceptInvitation(string $token) ->where('token', $token) ->firstOrFail(); - if (!$user || $user->id !== $invitation->user_id) { + if (! $user || $user->id !== $invitation->user_id) { return redirect()->route('login') ->with('error', 'Please login to accept this invitation.'); } diff --git a/app/Http/Controllers/PageController.php b/app/Http/Controllers/PageController.php index 9004d56..70b1bf5 100644 --- a/app/Http/Controllers/PageController.php +++ b/app/Http/Controllers/PageController.php @@ -11,7 +11,6 @@ class PageController extends Controller { - /** * Display pages for a specific mod. */ @@ -19,7 +18,7 @@ public function index(Mod $mod) { $user = Auth::user(); - if (!$user || !$mod->canBeAccessedBy($user)) { + if (! $user || ! $mod->canBeAccessedBy($user)) { abort(403); } @@ -44,7 +43,7 @@ public function create(Mod $mod, Request $request) { $user = Auth::user(); - if (!$mod->userCan($user, 'edit')) { + if (! $mod->userCan($user, 'edit')) { abort(403); } @@ -64,7 +63,7 @@ public function store(Request $request, Mod $mod) { $user = Auth::user(); - if (!$mod->userCan($user, 'edit')) { + if (! $mod->userCan($user, 'edit')) { abort(403); } @@ -88,7 +87,7 @@ public function store(Request $request, Mod $mod) $counter = 1; while (Page::where('mod_id', $mod->id)->where('slug', $slug)->exists()) { - $slug = $originalSlug . '-' . $counter; + $slug = $originalSlug.'-'.$counter; $counter++; } @@ -124,29 +123,29 @@ public function show(Mod $mod, Page $page) } if ($mod->visibility === 'private') { - if (!$user || !$mod->canBeAccessedBy($user)) { + if (! $user || ! $mod->canBeAccessedBy($user)) { abort(403); } } - $page->load(['creator', 'updater', 'children' => function($query) { + $page->load(['creator', 'updater', 'children' => function ($query) { $query->where('published', true)->orderBy('order_index'); }]); $navigation = $mod->pages() ->whereNull('parent_id') ->where('published', true) - ->with(['children' => function($query) { + ->with(['children' => function ($query) { $query->where('published', true)->orderBy('order_index'); }]) ->orderBy('order_index') ->get() - ->map(function($navPage) { + ->map(function ($navPage) { return [ 'id' => $navPage->id, 'title' => $navPage->title, 'slug' => $navPage->slug, - 'children' => $navPage->children->map(function($child) { + 'children' => $navPage->children->map(function ($child) { return [ 'id' => $child->id, 'title' => $child->title, @@ -171,7 +170,7 @@ public function show(Mod $mod, Page $page) 'mod' => $mod->load(['owner']), 'page' => array_merge($page->toArray(), [ 'path' => $path, - 'children' => $page->children->map(function($child) { + 'children' => $page->children->map(function ($child) { return [ 'id' => $child->id, 'title' => $child->title, @@ -197,7 +196,7 @@ public function edit(Mod $mod, Page $page) abort(404); } - if (!$mod->userCan($user, 'edit')) { + if (! $mod->userCan($user, 'edit')) { abort(403); } @@ -226,7 +225,7 @@ public function update(Request $request, Mod $mod, Page $page) abort(404); } - if (!$mod->userCan($user, 'edit')) { + if (! $mod->userCan($user, 'edit')) { abort(403); } @@ -251,7 +250,7 @@ public function update(Request $request, Mod $mod, Page $page) $counter = 1; while (Page::where('mod_id', $mod->id)->where('slug', $slug)->where('id', '!=', $page->id)->exists()) { - $slug = $originalSlug . '-' . $counter; + $slug = $originalSlug.'-'.$counter; $counter++; } @@ -280,7 +279,7 @@ public function destroy(Mod $mod, Page $page) abort(404); } - if (!$mod->userCan($user, 'edit')) { + if (! $mod->userCan($user, 'edit')) { abort(403); } @@ -297,7 +296,7 @@ public function updateOrder(Request $request, Mod $mod) { $user = Auth::user(); - if (!$mod->userCan($user, 'edit')) { + if (! $mod->userCan($user, 'edit')) { abort(403); } @@ -336,7 +335,7 @@ public function autoSave(Request $request, Mod $mod, Page $page) abort(404); } - if (!$mod->userCan($user, 'edit')) { + if (! $mod->userCan($user, 'edit')) { abort(403); } @@ -359,7 +358,7 @@ public function search(Request $request, Mod $mod) { $user = Auth::user(); - if (!$mod->canBeAccessedBy($user)) { + if (! $mod->canBeAccessedBy($user)) { abort(403); } @@ -372,7 +371,7 @@ public function search(Request $request, Mod $mod) $pages = $mod->pages() ->where(function ($q) use ($query) { $q->where('title', 'ilike', "%{$query}%") - ->orWhere('content', 'ilike', "%{$query}%"); + ->orWhere('content', 'ilike', "%{$query}%"); }) ->where('published', true) ->select(['id', 'title', 'slug', 'updated_at']) @@ -395,28 +394,28 @@ public function publicShow(Mod $mod, Page $page) abort(404, 'Documentation not found'); } - if (!$page->published) { + if (! $page->published) { abort(404, 'Page not found'); } - $page->load(['children' => function($query) { + $page->load(['children' => function ($query) { $query->where('published', true)->orderBy('order_index'); }]); $navigation = $mod->pages() ->whereNull('parent_id') ->where('published', true) - ->with(['children' => function($query) { + ->with(['children' => function ($query) { $query->where('published', true)->orderBy('order_index'); }]) ->orderBy('order_index') ->get() - ->map(function($navPage) { + ->map(function ($navPage) { return [ 'id' => $navPage->id, 'title' => $navPage->title, 'slug' => $navPage->slug, - 'children' => $navPage->children->map(function($child) { + 'children' => $navPage->children->map(function ($child) { return [ 'id' => $child->id, 'title' => $child->title, @@ -435,7 +434,7 @@ public function publicShow(Mod $mod, Page $page) 'content' => $page->content, 'published' => $page->published, 'updated_at' => $page->updated_at, - 'children' => $page->children->map(function($child) { + 'children' => $page->children->map(function ($child) { return [ 'id' => $child->id, 'title' => $child->title, diff --git a/app/Http/Controllers/Settings/ProfileController.php b/app/Http/Controllers/Settings/ProfileController.php index 16a9809..88313ae 100644 --- a/app/Http/Controllers/Settings/ProfileController.php +++ b/app/Http/Controllers/Settings/ProfileController.php @@ -36,15 +36,15 @@ public function update(ProfileUpdateRequest $request): RedirectResponse $validated = $request->validated(); if ($request->hasFile('avatar')) { - if ($user->avatar_url && !str_contains($user->avatar_url, 'ui-avatars.com')) { + if ($user->avatar_url && ! str_contains($user->avatar_url, 'ui-avatars.com')) { $oldPath = str_replace('/storage/', '', parse_url($user->avatar_url, PHP_URL_PATH)); Storage::disk('public')->delete($oldPath); } $avatarFile = $request->file('avatar'); - $filename = 'avatars/' . Str::uuid() . '.' . $avatarFile->getClientOriginalExtension(); + $filename = 'avatars/'.Str::uuid().'.'.$avatarFile->getClientOriginalExtension(); $path = $avatarFile->storeAs('', $filename, 'public'); - $validated['avatar_url'] = asset('storage/' . $path); + $validated['avatar_url'] = asset('storage/'.$path); } unset($validated['avatar']); @@ -67,7 +67,7 @@ public function deleteAvatar(Request $request): RedirectResponse { $user = $request->user(); - if ($user->avatar_url && !str_contains($user->avatar_url, 'ui-avatars.com')) { + if ($user->avatar_url && ! str_contains($user->avatar_url, 'ui-avatars.com')) { $oldPath = str_replace('/storage/', '', parse_url($user->avatar_url, PHP_URL_PATH)); Storage::disk('public')->delete($oldPath); } diff --git a/app/Http/Controllers/Settings/TwoFactorAuthenticationController.php b/app/Http/Controllers/Settings/TwoFactorAuthenticationController.php index 6b99412..f1659ce 100644 --- a/app/Http/Controllers/Settings/TwoFactorAuthenticationController.php +++ b/app/Http/Controllers/Settings/TwoFactorAuthenticationController.php @@ -10,7 +10,6 @@ class TwoFactorAuthenticationController extends Controller { - /** * Show the user's two-factor authentication settings page. */ diff --git a/app/Mail/CollaboratorInvitation.php b/app/Mail/CollaboratorInvitation.php index 4e511da..bf84505 100644 --- a/app/Mail/CollaboratorInvitation.php +++ b/app/Mail/CollaboratorInvitation.php @@ -5,7 +5,6 @@ use App\Models\Mod; use App\Models\User; use Illuminate\Bus\Queueable; -use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Mail\Mailable; use Illuminate\Mail\Mailables\Content; use Illuminate\Mail\Mailables\Envelope; diff --git a/app/Models/File.php b/app/Models/File.php index 0b8c53d..758a17c 100644 --- a/app/Models/File.php +++ b/app/Models/File.php @@ -84,7 +84,7 @@ public function getHumanSizeAttribute(): string $bytes /= 1024; } - return round($bytes, 2) . ' ' . $units[$i]; + return round($bytes, 2).' '.$units[$i]; } /** diff --git a/app/Models/Mod.php b/app/Models/Mod.php index 0d1ed75..944009b 100644 --- a/app/Models/Mod.php +++ b/app/Models/Mod.php @@ -56,8 +56,8 @@ public function owner() public function collaborators() { return $this->belongsToMany(User::class, 'mod_users') - ->withPivot('role', 'invited_by') - ->withTimestamps(); + ->withPivot('role', 'invited_by') + ->withTimestamps(); } /** @@ -109,7 +109,7 @@ public function canBeAccessedBy(?User $user): bool return true; } - if (!$user) { + if (! $user) { return false; } @@ -130,6 +130,7 @@ public function getUserRole(User $user): ?string } $collaborator = $this->collaborators()->where('user_id', $user->id)->first(); + return $collaborator?->pivot->role; } @@ -140,7 +141,7 @@ public function userCan(User $user, string $permission): bool { $role = $this->getUserRole($user); - if (!$role) { + if (! $role) { return false; } diff --git a/app/Models/ModInvitation.php b/app/Models/ModInvitation.php index ee1881b..98926de 100644 --- a/app/Models/ModInvitation.php +++ b/app/Models/ModInvitation.php @@ -64,7 +64,7 @@ public function accept(): bool 'invited_by' => $this->invited_by, 'created_at' => now(), 'updated_at' => now(), - ] + ], ]); $this->update(['accepted_at' => now()]); diff --git a/app/Models/ModUser.php b/app/Models/ModUser.php index be29735..2812287 100644 --- a/app/Models/ModUser.php +++ b/app/Models/ModUser.php @@ -3,7 +3,6 @@ namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; -use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\Pivot; class ModUser extends Pivot diff --git a/app/Models/Page.php b/app/Models/Page.php index 8a61a8b..54ceea9 100644 --- a/app/Models/Page.php +++ b/app/Models/Page.php @@ -81,8 +81,8 @@ public function children() public function publishedChildren() { return $this->hasMany(Page::class, 'parent_id') - ->where('published', true) - ->orderBy('order_index'); + ->where('published', true) + ->orderBy('order_index'); } /** diff --git a/app/Models/User.php b/app/Models/User.php index d4c2d61..5769990 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -75,8 +75,8 @@ public function ownedMods() public function mods() { return $this->belongsToMany(Mod::class, 'mod_users') - ->withPivot('role') - ->withTimestamps(); + ->withPivot('role') + ->withTimestamps(); } /** @@ -90,7 +90,7 @@ public function canAccessMod(Mod $mod, ?string $role = null): bool $userMod = $this->mods()->where('mod_id', $mod->id)->first(); - if (!$userMod) { + if (! $userMod) { return false; } @@ -112,7 +112,7 @@ protected function avatar(): \Illuminate\Database\Eloquent\Casts\Attribute { return \Illuminate\Database\Eloquent\Casts\Attribute::make( get: fn () => $this->avatar_url ?: - 'https://ui-avatars.com/api/?name=' . urlencode($this->name) . '&background=random' + 'https://ui-avatars.com/api/?name='.urlencode($this->name).'&background=random' ); } } diff --git a/database/factories/ModFactory.php b/database/factories/ModFactory.php index a0c3472..c1f850c 100644 --- a/database/factories/ModFactory.php +++ b/database/factories/ModFactory.php @@ -18,7 +18,7 @@ class ModFactory extends Factory */ public function definition(): array { - $name = fake()->unique()->words(2, true) . ' Mod'; + $name = fake()->unique()->words(2, true).' Mod'; return [ 'name' => $name, diff --git a/database/factories/PageFactory.php b/database/factories/PageFactory.php index 77d2350..f9f3a7a 100644 --- a/database/factories/PageFactory.php +++ b/database/factories/PageFactory.php @@ -44,7 +44,7 @@ private function generateMarkdownContent(): string { $sections = []; - $sections[] = "## Introduction\n\n" . fake()->paragraph(); + $sections[] = "## Introduction\n\n".fake()->paragraph(); for ($i = 0; $i < fake()->numberBetween(2, 5); $i++) { $sectionTitle = fake()->sentence(3, false); @@ -79,7 +79,7 @@ private function generateCodeBlock(): string 'php' => " "def example():\n print('Hello, world!')\n return True", 'bash' => "#!/bin/bash\necho 'Hello, world!'\nexit 0", - 'json' => '{\n "name": "example",\n "version": "1.0.0",\n "description": "An example"\n}' + 'json' => '{\n "name": "example",\n "version": "1.0.0",\n "description": "An example"\n}', ]; return "```{$lang}\n{$codeExamples[$lang]}\n```"; @@ -92,8 +92,9 @@ private function generateList(): string { $items = []; for ($i = 0; $i < fake()->numberBetween(3, 7); $i++) { - $items[] = "- " . fake()->sentence(); + $items[] = '- '.fake()->sentence(); } + return implode("\n", $items); } diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 094cf5f..8709b10 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -2,7 +2,6 @@ namespace Database\Seeders; -use App\Models\User; // use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Seeder; diff --git a/routes/web.php b/routes/web.php index 6365e42..8dbbe9a 100644 --- a/routes/web.php +++ b/routes/web.php @@ -39,7 +39,7 @@ 'totalPagesCount' => $totalPagesCount, 'publicViewsCount' => 0, 'latestPages' => $latestPages, - 'latestMods' => $latestMods + 'latestMods' => $latestMods, ], ]); })->middleware(['auth', 'verified'])->name('dashboard'); diff --git a/tests/Feature/ModCollaboratorTest.php b/tests/Feature/ModCollaboratorTest.php index 25473ef..a006396 100644 --- a/tests/Feature/ModCollaboratorTest.php +++ b/tests/Feature/ModCollaboratorTest.php @@ -8,7 +8,6 @@ use App\Models\User; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\WithFaker; -use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Mail; use Tests\TestCase; @@ -35,7 +34,7 @@ public function test_admin_can_view_collaborators_page() $mod->collaborators()->attach($admin->id, [ 'role' => 'admin', - 'invited_by' => $owner->id + 'invited_by' => $owner->id, ]); $this->actingAs($admin); @@ -52,7 +51,7 @@ public function test_editor_cannot_view_collaborators_page() $mod->collaborators()->attach($editor->id, [ 'role' => 'editor', - 'invited_by' => $owner->id + 'invited_by' => $owner->id, ]); $this->actingAs($editor); @@ -147,7 +146,7 @@ public function test_cannot_add_existing_collaborator() $mod->collaborators()->attach($collaborator->id, [ 'role' => 'editor', - 'invited_by' => $owner->id + 'invited_by' => $owner->id, ]); $this->actingAs($owner); @@ -183,7 +182,7 @@ public function test_owner_can_update_collaborator_role() $mod->collaborators()->attach($collaborator->id, [ 'role' => 'viewer', - 'invited_by' => $owner->id + 'invited_by' => $owner->id, ]); $this->actingAs($owner); @@ -210,11 +209,11 @@ public function test_admin_can_update_collaborator_role_but_not_to_admin() $mod->collaborators()->attach($admin->id, [ 'role' => 'admin', - 'invited_by' => $owner->id + 'invited_by' => $owner->id, ]); $mod->collaborators()->attach($editor->id, [ 'role' => 'editor', - 'invited_by' => $owner->id + 'invited_by' => $owner->id, ]); $this->actingAs($admin); @@ -241,11 +240,11 @@ public function test_editor_cannot_update_collaborator_role() $mod->collaborators()->attach($editor->id, [ 'role' => 'editor', - 'invited_by' => $owner->id + 'invited_by' => $owner->id, ]); $mod->collaborators()->attach($viewer->id, [ 'role' => 'viewer', - 'invited_by' => $owner->id + 'invited_by' => $owner->id, ]); $this->actingAs($editor); @@ -265,7 +264,7 @@ public function test_owner_can_remove_collaborator() $mod->collaborators()->attach($collaborator->id, [ 'role' => 'editor', - 'invited_by' => $owner->id + 'invited_by' => $owner->id, ]); $this->actingAs($owner); @@ -289,11 +288,11 @@ public function test_admin_can_remove_non_admin_collaborator() $mod->collaborators()->attach($admin->id, [ 'role' => 'admin', - 'invited_by' => $owner->id + 'invited_by' => $owner->id, ]); $mod->collaborators()->attach($editor->id, [ 'role' => 'editor', - 'invited_by' => $owner->id + 'invited_by' => $owner->id, ]); $this->actingAs($admin); @@ -311,11 +310,11 @@ public function test_admin_cannot_remove_other_admin() $mod->collaborators()->attach($admin1->id, [ 'role' => 'admin', - 'invited_by' => $owner->id + 'invited_by' => $owner->id, ]); $mod->collaborators()->attach($admin2->id, [ 'role' => 'admin', - 'invited_by' => $owner->id + 'invited_by' => $owner->id, ]); $this->actingAs($admin1); @@ -332,7 +331,7 @@ public function test_collaborator_can_remove_themselves() $mod->collaborators()->attach($collaborator->id, [ 'role' => 'editor', - 'invited_by' => $owner->id + 'invited_by' => $owner->id, ]); $this->actingAs($collaborator); @@ -399,7 +398,6 @@ public function test_can_accept_invitation() } - public function test_guest_cannot_accept_invitation() { $owner = User::factory()->create(); diff --git a/tests/Feature/ModTest.php b/tests/Feature/ModTest.php index 8ae38bb..2a27c5a 100644 --- a/tests/Feature/ModTest.php +++ b/tests/Feature/ModTest.php @@ -119,7 +119,7 @@ public function test_collaborator_can_view_private_mod() // Add collaborator $mod->collaborators()->attach($collaborator->id, [ 'role' => 'editor', - 'invited_by' => $owner->id + 'invited_by' => $owner->id, ]); $this->actingAs($collaborator); @@ -158,7 +158,7 @@ public function test_non_owner_cannot_update_mod() $this->actingAs($user); $response = $this->patch(route('mods.update', $mod), [ - 'name' => 'Updated Name' + 'name' => 'Updated Name', ]); $response->assertForbidden(); diff --git a/tests/Feature/PageTest.php b/tests/Feature/PageTest.php index 1e4e8c7..8ff80e8 100644 --- a/tests/Feature/PageTest.php +++ b/tests/Feature/PageTest.php @@ -7,7 +7,6 @@ use App\Models\User; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\WithFaker; -use Illuminate\Support\Facades\Log; use Tests\TestCase; class PageTest extends TestCase @@ -33,7 +32,7 @@ public function test_collaborator_can_view_pages_index() $mod->collaborators()->attach($collaborator->id, [ 'role' => 'editor', - 'invited_by' => $owner->id + 'invited_by' => $owner->id, ]); $this->actingAs($collaborator); @@ -70,9 +69,9 @@ public function test_owner_can_create_page() $response = $this->post(route('pages.store', $mod), $pageData); if ($response->status() === 422) { - $this->fail('Validation failed: ' . json_encode($response->json())); + $this->fail('Validation failed: '.json_encode($response->json())); } elseif ($response->status() !== 302) { - $this->fail('Expected redirect (302) but got status: ' . $response->status()); + $this->fail('Expected redirect (302) but got status: '.$response->status()); } $page = Page::where('title', 'Test Page')->first(); @@ -92,7 +91,7 @@ public function test_editor_can_create_page() $mod->collaborators()->attach($editor->id, [ 'role' => 'editor', - 'invited_by' => $owner->id + 'invited_by' => $owner->id, ]); $this->actingAs($editor); @@ -105,7 +104,6 @@ public function test_editor_can_create_page() 'published' => true, ]; - $response = $this->post(route('pages.store', $mod), $pageData); $page = Page::where('title', 'Editor Page')->first(); @@ -121,7 +119,7 @@ public function test_viewer_cannot_create_page() $mod->collaborators()->attach($viewer->id, [ 'role' => 'viewer', - 'invited_by' => $owner->id + 'invited_by' => $owner->id, ]); $this->actingAs($viewer); @@ -225,7 +223,7 @@ public function test_editor_can_update_page() $mod->collaborators()->attach($editor->id, [ 'role' => 'editor', - 'invited_by' => $owner->id + 'invited_by' => $owner->id, ]); $this->actingAs($editor); @@ -252,7 +250,7 @@ public function test_viewer_cannot_update_page() $mod->collaborators()->attach($viewer->id, [ 'role' => 'viewer', - 'invited_by' => $owner->id + 'invited_by' => $owner->id, ]); $this->actingAs($viewer); @@ -286,7 +284,7 @@ public function test_editor_can_delete_page() $mod->collaborators()->attach($editor->id, [ 'role' => 'editor', - 'invited_by' => $owner->id + 'invited_by' => $owner->id, ]); $this->actingAs($editor); @@ -328,7 +326,7 @@ public function test_can_reorder_pages() 'pages' => [ ['id' => $page2->id, 'parent_id' => null, 'order_index' => 1], ['id' => $page1->id, 'parent_id' => null, 'order_index' => 2], - ] + ], ]); $response->assertRedirect(); @@ -352,6 +350,7 @@ public function test_can_autosave_page() $response->assertOk(); } + public function test_page_slug_is_generated_from_title() { $user = User::factory()->create(); diff --git a/tests/Unit/ModModelTest.php b/tests/Unit/ModModelTest.php index 86aa6cb..32ddd16 100644 --- a/tests/Unit/ModModelTest.php +++ b/tests/Unit/ModModelTest.php @@ -215,7 +215,7 @@ public function test_custom_slug_is_preserved() public function test_route_key_name_is_slug() { - $mod = new Mod(); + $mod = new Mod; $this->assertEquals('slug', $mod->getRouteKeyName()); }