diff --git a/app/Data/Discord/GuildData.php b/app/Data/Discord/GuildData.php index 978b51c..f9d2362 100644 --- a/app/Data/Discord/GuildData.php +++ b/app/Data/Discord/GuildData.php @@ -11,9 +11,7 @@ class GuildData extends Data { public function __construct( - public readonly ?string $identity_guild_id, - public readonly ?string $identity_enabled, - public readonly ?string $tag, - public readonly ?string $badge, + public readonly string $id, + public readonly string $owner_id, ) {} } diff --git a/app/Data/Discord/RoleData.php b/app/Data/Discord/RoleData.php new file mode 100644 index 0000000..524c682 --- /dev/null +++ b/app/Data/Discord/RoleData.php @@ -0,0 +1,17 @@ +roles()->first(fn ($role) => $role['id'] === $reactionRole->role_id); + $role = $discordRepository->roles()?->first(fn ($role) => $role->id === $reactionRole->role_id); return new self( $reactionRole->id, @@ -40,7 +40,7 @@ public static function fromFaq(ReactionRole $reactionRole): self $reactionRole->role_id, $reactionRole->created_at, $reactionRole->updated_at, - $role ? $role['name'] : 'role-not-found', + $role->name ?? 'role-not-found', DiscordMessageRule::$discordChannelLinkBase.config('services.discord.server_id').'/'.$reactionRole->channel_id.'/'.$reactionRole->message_id, ); } diff --git a/app/Data/Requests/CreateApplicationQuestionAnswerRequest.php b/app/Data/Requests/CreateApplicationQuestionAnswerRequest.php index 43ebb04..ebc310a 100644 --- a/app/Data/Requests/CreateApplicationQuestionAnswerRequest.php +++ b/app/Data/Requests/CreateApplicationQuestionAnswerRequest.php @@ -23,6 +23,6 @@ public function __construct( public static function authorize( #[CurrentUser] User $user, ): bool { - return $user->can('applicationQuestionAnswer.create'); + return $user->can('applicationAnswerQuestion.create'); } } diff --git a/app/Data/Requests/CreatePermissionRequest.php b/app/Data/Requests/CreatePermissionRequest.php index b20834f..4e7423d 100644 --- a/app/Data/Requests/CreatePermissionRequest.php +++ b/app/Data/Requests/CreatePermissionRequest.php @@ -48,6 +48,12 @@ class CreatePermissionRequest extends Data 'serverContent' => [ 'resend', ], + 'ticket' => [ + 'read-own', + ], + 'ticketTranscript' => [ + 'read-own', + ], ]; public function __construct( @@ -60,7 +66,7 @@ public function __construct( public static function authorize( #[CurrentUser] User $user, ): bool { - return $user->can('permission.read'); + return $user->can('permission.create'); } /** diff --git a/app/Data/Requests/DeleteApplicationQuestionAnswerRequest.php b/app/Data/Requests/DeleteApplicationQuestionAnswerRequest.php index 4d27c38..acb3c68 100644 --- a/app/Data/Requests/DeleteApplicationQuestionAnswerRequest.php +++ b/app/Data/Requests/DeleteApplicationQuestionAnswerRequest.php @@ -13,6 +13,6 @@ public function __construct() {} public static function authorize( #[CurrentUser] User $user, ): bool { - return $user->can('applicationQuestionAnswer.delete'); + return $user->can('applicationAnswerQuestion.delete'); } } diff --git a/app/Data/Requests/ReadApplicationQuestionAnswerRequest.php b/app/Data/Requests/ReadApplicationQuestionAnswerRequest.php index bb46393..64ce195 100644 --- a/app/Data/Requests/ReadApplicationQuestionAnswerRequest.php +++ b/app/Data/Requests/ReadApplicationQuestionAnswerRequest.php @@ -13,6 +13,6 @@ public function __construct() {} public static function authorize( #[CurrentUser] User $user, ): bool { - return $user->can('applicationQuestionAnswer.read'); + return $user->can('applicationAnswerQuestion.read'); } } diff --git a/app/Data/Requests/ReadTicketRequest.php b/app/Data/Requests/ReadTicketRequest.php index cd12826..3dbb797 100644 --- a/app/Data/Requests/ReadTicketRequest.php +++ b/app/Data/Requests/ReadTicketRequest.php @@ -13,6 +13,6 @@ public function __construct() {} public static function authorize( #[CurrentUser] User $user, ): bool { - return $user->can('ticket.read'); + return $user->canany(['ticket.read', 'ticket.read-own']); } } diff --git a/app/Data/Requests/SetupTicketConfigRequest.php b/app/Data/Requests/SetupTicketConfigRequest.php index 1884150..bf39431 100644 --- a/app/Data/Requests/SetupTicketConfigRequest.php +++ b/app/Data/Requests/SetupTicketConfigRequest.php @@ -24,6 +24,6 @@ public function __construct( public static function authorize( #[CurrentUser] User $user, ): bool { - return $user->can('ticketConfig.setup'); + return $user->can('ticketConfig.create'); } } diff --git a/app/Data/Requests/UpdateApplicationQuestionAnswerRequest.php b/app/Data/Requests/UpdateApplicationQuestionAnswerRequest.php index 4e28233..982bd4c 100644 --- a/app/Data/Requests/UpdateApplicationQuestionAnswerRequest.php +++ b/app/Data/Requests/UpdateApplicationQuestionAnswerRequest.php @@ -24,6 +24,6 @@ public function __construct( public static function authorize( #[CurrentUser] User $user, ): bool { - return $user->can('applicationQuestionAnswer.update'); + return $user->can('applicationAnswerQuestion.update'); } } diff --git a/app/Data/Requests/UpdateReactionRoleRequest.php b/app/Data/Requests/UpdateReactionRoleRequest.php index 7c0d58c..987f563 100644 --- a/app/Data/Requests/UpdateReactionRoleRequest.php +++ b/app/Data/Requests/UpdateReactionRoleRequest.php @@ -7,9 +7,11 @@ use App\Rules\EmojiRule; use App\Rules\RoleRule; use Illuminate\Container\Attributes\CurrentUser; +use Spatie\LaravelData\Attributes\MergeValidationRules; use Spatie\LaravelData\Data; use Spatie\LaravelData\Optional; +#[MergeValidationRules] class UpdateReactionRoleRequest extends Data { public function __construct( @@ -30,9 +32,9 @@ public static function authorize( public static function rules(): array { return [ - 'message_link' => ['required', 'string', new DiscordMessageRule], - 'emoji' => ['required', 'string', new EmojiRule], - 'role_id' => ['required', 'string', new RoleRule], + 'message_link' => [new DiscordMessageRule], + 'emoji' => [new EmojiRule], + 'role_id' => [new RoleRule], ]; } } diff --git a/app/Data/TicketButtonPingRoleData.php b/app/Data/TicketButtonPingRoleData.php index 2d565fe..e7b361d 100644 --- a/app/Data/TicketButtonPingRoleData.php +++ b/app/Data/TicketButtonPingRoleData.php @@ -25,13 +25,13 @@ public function __construct( public static function fromTicketButtonPingRole(TicketButtonPingRole $ticketButtonPingRole): self { $discordRepository = new DiscordRepository; - $role = $discordRepository->roles()->first(fn ($role) => $role['id'] === $ticketButtonPingRole->role_id); + $role = $discordRepository->roles()?->first(fn ($role) => $role->id === $ticketButtonPingRole->role_id); return new self( $ticketButtonPingRole->id, $ticketButtonPingRole->ticket_button_id, $ticketButtonPingRole->role_id, - $role ? $role['name'] : 'role-not-found', + $role->name ?? 'role-not-found', $ticketButtonPingRole->created_at, $ticketButtonPingRole->updated_at, ); diff --git a/app/Data/TicketTeamRoleData.php b/app/Data/TicketTeamRoleData.php index d612c9a..424b6e2 100644 --- a/app/Data/TicketTeamRoleData.php +++ b/app/Data/TicketTeamRoleData.php @@ -26,13 +26,13 @@ public static function fromTicketTeamRole(TicketTeamRole $ticketTeamRole): self { $discordRepository = new DiscordRepository; - $role = $discordRepository->roles()->first(fn ($role) => $role['id'] === $ticketTeamRole->role_id); + $role = $discordRepository->roles()?->first(fn ($role) => $role->id === $ticketTeamRole->role_id); return new self( $ticketTeamRole->id, $ticketTeamRole->ticket_team_id, $ticketTeamRole->role_id, - $role ? $role['name'] : 'role-not-found', + $role->name ?? 'role-not-found', $ticketTeamRole->created_at, $ticketTeamRole->updated_at, ); diff --git a/app/Data/UserData.php b/app/Data/UserData.php index 50f32dc..658bb29 100644 --- a/app/Data/UserData.php +++ b/app/Data/UserData.php @@ -5,6 +5,7 @@ namespace App\Data; use App\Models\User; +use App\Repositories\DiscordRepository; use Illuminate\Support\Collection; use Spatie\LaravelData\Data; use Spatie\TypeScriptTransformer\Attributes\LiteralTypeScriptType; @@ -25,15 +26,17 @@ public function __construct( public readonly Collection $permissions, ) {} - public static function fromUser(User $user, bool $isOwner = false): self + public static function fromUser(User $user): self { + $guild = new DiscordRepository()->guild(); + return new self( $user->id, $user->discord_id, $user->nickname, $user->name, $user->avatar, - $isOwner, + $guild && $guild->owner_id === $user->discord_id, $user->getPermissionsViaRoles()->pluck('name'), ); } diff --git a/app/Http/Controllers/ApplicationController.php b/app/Http/Controllers/ApplicationController.php index c145b65..b1de810 100644 --- a/app/Http/Controllers/ApplicationController.php +++ b/app/Http/Controllers/ApplicationController.php @@ -313,10 +313,6 @@ public function update(UpdateApplicationRequest $request, Application $applicati */ public function destroy(DeleteApplicationRequest $request, Application $application): bool { - if (! request()->user()?->can('application.delete')) { - abort(403); - } - return $application->delete() ?? false; } diff --git a/app/Http/Controllers/ApplicationQuestionAnswerController.php b/app/Http/Controllers/ApplicationQuestionAnswerController.php index 43e6449..546e654 100644 --- a/app/Http/Controllers/ApplicationQuestionAnswerController.php +++ b/app/Http/Controllers/ApplicationQuestionAnswerController.php @@ -93,10 +93,6 @@ public function update(UpdateApplicationQuestionAnswerRequest $request, Applicat */ public function destroy(DeleteApplicationQuestionAnswerRequest $request, ApplicationQuestionAnswer $applicationQuestionAnswer): bool { - if (! request()->user()?->can('applicationQuestionAnswer.delete')) { - abort(403); - } - return $applicationQuestionAnswer->delete() ?? false; } } diff --git a/app/Http/Controllers/DiscordController.php b/app/Http/Controllers/DiscordController.php index 2cb62e5..6b5afda 100644 --- a/app/Http/Controllers/DiscordController.php +++ b/app/Http/Controllers/DiscordController.php @@ -34,9 +34,9 @@ public function callback(Request $request): JsonResponse abort(404); } - if (! in_array(config('services.discord.required_role'), $json['roles'])) { - abort(404); - } + // if (! in_array(config('services.discord.required_role'), $json['roles'])) { + // abort(404); + // } $user = User::updateOrCreate([ 'discord_id' => $user->id, diff --git a/app/Http/Controllers/MeController.php b/app/Http/Controllers/MeController.php index 797c3ec..74eb999 100644 --- a/app/Http/Controllers/MeController.php +++ b/app/Http/Controllers/MeController.php @@ -28,30 +28,29 @@ public function __invoke(Request $request): JsonResponse $discordGuildUser = $this->discordRepository->currentUser(); - $guild = $this->discordRepository->guild(); - - if (! isset($discordGuildUser['roles'])) { + if (! $discordGuildUser?->roles) { Cache::forget('user-'.$user->id); Auth::guard('web')->logout(); abort(403, 'You oauth2 token expired. Please login with Discord'); } - if (! in_array(config('services.discord.required_role'), $discordGuildUser['roles'])) { - Cache::forget('user-'.$user->id); - Auth::guard('web')->logout(); + $userRoles = collect($discordGuildUser->roles); - abort(403, 'You do not have the required permissions.'); + $everyoneRole = $this->discordRepository->everyoneRole(); + if ($everyoneRole) { + $userRoles->push($everyoneRole->id); } - $roles = Role::whereIn('name', $discordGuildUser['roles'])->get()->pluck('name'); + $roles = Role::whereIn('name', $userRoles)->get()->pluck('name'); + $userData = UserData::from($user); - if ($guild['owner_id'] === $user->discord_id) { + if ($userData->is_owner) { $roles->push('Owner'); } $user->syncRoles($roles); - return response()->json(UserData::from($user, $guild['owner_id'] === $user->discord_id)); + return response()->json($userData); } } diff --git a/app/Http/Controllers/ServerContentMessageController.php b/app/Http/Controllers/ServerContentMessageController.php index 8dc4ca6..2d4d307 100644 --- a/app/Http/Controllers/ServerContentMessageController.php +++ b/app/Http/Controllers/ServerContentMessageController.php @@ -11,10 +11,6 @@ class ServerContentMessageController extends Controller { public function index(ReadServerContentMessageRequest $request): ?ServerContentMessageData { - if (! request()->user()?->can('serverContentMessage.read')) { - abort(403); - } - $messages = ServerContentMessage::where('server_id', config('services.discord.server_id'))->first(); return $messages ? ServerContentMessageData::from($messages) : null; diff --git a/app/Http/Controllers/TicketController.php b/app/Http/Controllers/TicketController.php index 0fe30be..0cb4e45 100644 --- a/app/Http/Controllers/TicketController.php +++ b/app/Http/Controllers/TicketController.php @@ -9,7 +9,9 @@ use App\Enums\TicketState; use App\Models\Ticket; use App\Models\TicketButton; +use App\Models\User; use App\Repositories\TicketRepository; +use Illuminate\Container\Attributes\CurrentUser; use Illuminate\Support\Facades\Http; use Spatie\LaravelData\DataCollection; use Spatie\LaravelData\PaginatedDataCollection; @@ -27,16 +29,23 @@ public function __construct( * * @return PaginatedDataCollection|DataCollection */ - public function index(ReadTicketRequest $request): PaginatedDataCollection|DataCollection - { - $tickets = QueryBuilder::for(Ticket::class) + public function index( + #[CurrentUser] User $user, + ReadTicketRequest $request + ): PaginatedDataCollection|DataCollection { + $ticketsQuery = QueryBuilder::for(Ticket::class) ->allowedIncludes(['ticketButton.ticketTeam.ticketTeamRoles', 'ticketTranscripts']) ->allowedSorts('created_at') ->allowedFilters([ AllowedFilter::exact('id'), AllowedFilter::exact('state'), - ]) - ->getOrPaginate(); + ]); + + if ($user->cannot('ticket.read')) { + $ticketsQuery->where('created_by_discord_user_id', $user->discord_id); + } + + $tickets = $ticketsQuery->getOrPaginate(); if (request()->has('full')) { return TicketData::collect($tickets, DataCollection::class)->wrap('data'); diff --git a/app/Repositories/ApplicationSubmissionRepository.php b/app/Repositories/ApplicationSubmissionRepository.php index ae2c54a..02116be 100644 --- a/app/Repositories/ApplicationSubmissionRepository.php +++ b/app/Repositories/ApplicationSubmissionRepository.php @@ -434,12 +434,13 @@ private function chunkTextBySpace(int $limit, string $text): array private function getAcceptActionRow(ApplicationSubmission $applicationSubmission): ?ActionRowData { - if ($applicationSubmission->state !== ApplicationSubmissionState::Pending) { + if ($applicationSubmission->state !== ApplicationSubmissionState::Pending || + ! $applicationSubmission->application) { return null; } /** @var Collection $options */ - $options = StringCollectorOptionData::collect($applicationSubmission->application?->acceptedResponses()->limit(25)->get() ?? []); + $options = StringCollectorOptionData::collect($applicationSubmission->application->acceptedResponses()->limit(25)->get()); if ($options->isEmpty()) { return null; @@ -457,12 +458,13 @@ private function getAcceptActionRow(ApplicationSubmission $applicationSubmission private function getDenyActionRow(ApplicationSubmission $applicationSubmission): ?ActionRowData { - if ($applicationSubmission->state !== ApplicationSubmissionState::Pending) { + if ($applicationSubmission->state !== ApplicationSubmissionState::Pending || + ! $applicationSubmission->application) { return null; } /** @var Collection $options */ - $options = StringCollectorOptionData::collect($applicationSubmission->application?->deniedResponses()->limit(25)->get() ?? []); + $options = StringCollectorOptionData::collect($applicationSubmission->application->deniedResponses()->limit(25)->get()); if ($options->isEmpty()) { return null; diff --git a/app/Repositories/DiscordRepository.php b/app/Repositories/DiscordRepository.php index 2a46e88..211376f 100644 --- a/app/Repositories/DiscordRepository.php +++ b/app/Repositories/DiscordRepository.php @@ -2,7 +2,9 @@ namespace App\Repositories; +use App\Data\Discord\GuildData; use App\Data\Discord\MemberData; +use App\Data\Discord\RoleData; use App\Data\Discord\UserData; use Illuminate\Support\Collection; use Illuminate\Support\Facades\Cache; @@ -12,20 +14,39 @@ class DiscordRepository { /** - * @return Collection> + * @return ?Collection */ - public function roles(): Collection + public function roles(): ?Collection { - /** - * @var array> - */ $roles = Cache::remember('discord-'.config('services.discord.server_id').'-roles', 300, function () { $response = Http::discordBot()->get('/guilds/'.config('services.discord.server_id').'/roles'); return $response->json(); }); + try { + $roleData = RoleData::collect($roles, Collection::class); + } catch (\Exception $e) { + Log::error('Could not parse user into RoleData:', [ + 'error' => $e, + 'roles' => $roles, + ]); + $roleData = null; + } - return collect($roles); + return $roleData; + } + + public function everyoneRole(): ?RoleData + { + $everyoneRole = $this->roles()?->firstWhere('name', '@everyone'); + if (! $everyoneRole) { + return null; + } + + return Cache::remember( + 'discord-'.config('services.discord.server_id').'-role-everyone', 60 * 60 * 24 * 7, // save for 7 days + fn () => $everyoneRole + ); } /** @@ -36,13 +57,13 @@ public function channels(): Collection /** * @var array> */ - $roles = Cache::remember('discord-'.config('services.discord.server_id').'-channels', 300, function () { + $channels = Cache::remember('discord-'.config('services.discord.server_id').'-channels', 300, function () { $response = Http::discordBot()->get('/guilds/'.config('services.discord.server_id').'/channels'); return $response->json(); }); - return collect($roles); + return collect($channels); } /** @@ -67,10 +88,7 @@ public function categories(): Collection return $textChannels; } - /** - * @return Collection - */ - public function guild(): Collection + public function guild(): ?GuildData { /** * @var array> @@ -81,13 +99,20 @@ public function guild(): Collection return $response->json(); }); - return collect($guild); + try { + $guildData = GuildData::from($guild); + } catch (\Exception $e) { + Log::error('Could not parse guild into GuildData:', [ + 'error' => $e, + 'guild' => $guild, + ]); + $guildData = null; + } + + return $guildData; } - /** - * @return Collection - */ - public function currentUser(): Collection + public function currentUser(): ?MemberData { /** * @var array> @@ -98,7 +123,17 @@ public function currentUser(): Collection return $response->json(); }); - return collect($currentUser); + try { + $currentUserData = MemberData::from($currentUser); + } catch (\Exception $e) { + Log::error('Could not parse currentUser into MemberData:', [ + 'error' => $e, + 'currentUser' => $currentUser, + ]); + $currentUserData = null; + } + + return $currentUserData; } public function getUserById(string $userId): ?UserData diff --git a/app/Repositories/TicketRepository.php b/app/Repositories/TicketRepository.php index 3cb0b16..c56348d 100644 --- a/app/Repositories/TicketRepository.php +++ b/app/Repositories/TicketRepository.php @@ -32,11 +32,7 @@ public function getChannelName(Ticket $ticket): string public function createForButton(TicketButton $ticketButton, CreateTicketRequest $request): Ticket { - $roles = $this->discordRepository->roles(); - /** - * @var array{id:string} $everyoneRole - */ - $everyoneRole = $roles->firstWhere('name', '@everyone'); + $everyoneRole = $this->discordRepository->everyoneRole(); $ticket = Ticket::create([ 'ticket_button_id' => $request->ticket_button_id, @@ -67,7 +63,7 @@ public function createForButton(TicketButton $ticketButton, CreateTicketRequest 'parent_id' => $ticketConfig->category_id, 'permission_overwrites' => [ [ - 'id' => $everyoneRole['id'], // everyone + 'id' => $everyoneRole?->id, // everyone 'type' => 0, // role 'deny' => 1 << 10, // view channel permission ], @@ -156,15 +152,61 @@ public function sendTranscript(Ticket $ticket): bool */ $ticketConfig = TicketConfig::where('guild_id', $guildId)->first(); - $buttons = collect([ - ButtonData::link( - 'Transcript', - config('services.frontend.base_url').'/ticket/transcript/'.$ticket->id, - ), + $buttons = collect([$this->getTranscriptButton($ticket)]); + $response = Http::discordBot()->post('/channels/'.$ticketConfig->transcript_channel_id.'/messages', [ + 'embeds' => collect([$this->getTranscriptEmbed($ticket)]), + 'components' => [ + new ActionRowData(components: $buttons), + ], + ]); + + $this->sendTranscriptToMember($ticket); + + return $response->ok(); + } + + private function sendTranscriptToMember(Ticket $ticket): bool + { + $createdByMember = $this->discordRepository->getGuildMemberById($ticket->created_by_discord_user_id); + if (! $createdByMember?->roles) { + return false; + } + + $teamRoleIds = $ticket->ticketButton?->ticketTeam?->ticketTeamRoles->pluck('role_id'); + $intersect = $teamRoleIds?->intersect($createdByMember->roles); + if (! $intersect?->isEmpty()) { + return false; + } + + $channelResponse = Http::discordBot()->post('/users/@me/channels', ['recipient_id' => $ticket->created_by_discord_user_id]); + if ($channelResponse->failed()) { + return false; + } + + $channel = $channelResponse->json(); + $buttons = collect([$this->getTranscriptButton($ticket)]); + $channelResponse = Http::discordBot()->post('/channels/'.$channel['id'].'/messages', [ + 'embeds' => collect([$this->getTranscriptEmbed($ticket)]), + 'components' => [ + new ActionRowData(components: $buttons), + ], ]); - $embed = new EmbedData( - title: 'Ticket Closes', + return $channelResponse->ok(); + } + + private function getTranscriptButton(Ticket $ticket): ButtonData + { + return ButtonData::link( + 'Transcript', + config('services.frontend.base_url').'/ticket/transcript/'.$ticket->id, + ); + } + + private function getTranscriptEmbed(Ticket $ticket): EmbedData + { + return new EmbedData( + title: 'Ticket Closed', color: (string) hexdec('22e629'), // Green fields: collect([ new FieldsData( @@ -203,13 +245,5 @@ public function sendTranscript(Ticket $ticket): bool ), ]), ); - $response = Http::discordBot()->post('/channels/'.$ticketConfig->transcript_channel_id.'/messages', [ - 'embeds' => collect([$embed]), - 'components' => [ - new ActionRowData(components: $buttons), - ], - ]); - - return $response->ok(); } } diff --git a/config/database.php b/config/database.php index 125949e..bd39a59 100644 --- a/config/database.php +++ b/config/database.php @@ -58,7 +58,7 @@ 'strict' => true, 'engine' => null, 'options' => extension_loaded('pdo_mysql') ? array_filter([ - PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), + (defined('Pdo\Mysql::ATTR_SSL_CA') ? Pdo\Mysql::ATTR_SSL_CA : PDO::MYSQL_ATTR_SSL_CA) => env('MYSQL_ATTR_SSL_CA'), ]) : [], ], @@ -78,7 +78,7 @@ 'strict' => true, 'engine' => null, 'options' => extension_loaded('pdo_mysql') ? array_filter([ - PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), + (defined('Pdo\Mysql::ATTR_SSL_CA') ? Pdo\Mysql::ATTR_SSL_CA : PDO::MYSQL_ATTR_SSL_CA) => env('MYSQL_ATTR_SSL_CA'), ]) : [], ], diff --git a/database/factories/ApplicationFactory.php b/database/factories/ApplicationFactory.php index f14fd4f..e71f863 100644 --- a/database/factories/ApplicationFactory.php +++ b/database/factories/ApplicationFactory.php @@ -2,6 +2,7 @@ namespace Database\Factories; +use App\Enums\DiscordButton; use Illuminate\Database\Eloquent\Factories\Factory; /** @@ -27,4 +28,18 @@ public function definition(): array 'completion_message' => fake()->sentence(2), ]; } + + public function withEmbed(): static + { + return $this->state(function (array $attributes) { + return [ + 'embed_title' => fake()->sentence(), + 'embed_description' => fake()->text(), + 'embed_color' => fake()->hexColor(), + 'embed_channel_id' => (string) fake()->numberBetween(100000000000000000, 999999999999999999), + 'embed_button_color' => fake()->randomElement(DiscordButton::cases()), + 'embed_button_text' => fake()->word(), + ]; + }); + } } diff --git a/database/factories/ServerContentFactory.php b/database/factories/ServerContentFactory.php index 56fb635..efa2d9a 100644 --- a/database/factories/ServerContentFactory.php +++ b/database/factories/ServerContentFactory.php @@ -24,4 +24,31 @@ public function definition(): array 'is_active' => fake()->boolean(), ]; } + + public function recommended(): static + { + return $this->state(function (array $attributes) { + return [ + 'is_recommended' => true, + ]; + }); + } + + public function notRecommended(): static + { + return $this->state(function (array $attributes) { + return [ + 'is_recommended' => false, + ]; + }); + } + + public function active(): static + { + return $this->state(function (array $attributes) { + return [ + 'is_active' => true, + ]; + }); + } } diff --git a/database/migrations/2025_12_25_233535_add_ticket_read_own_permission.php b/database/migrations/2025_12_25_233535_add_ticket_read_own_permission.php new file mode 100644 index 0000000..345b4f1 --- /dev/null +++ b/database/migrations/2025_12_25_233535_add_ticket_read_own_permission.php @@ -0,0 +1,25 @@ + 'ticket.read-own']); + Permission::create(['name' => 'ticketTranscript.read-own']); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Permission::where(['name' => 'ticket.read-own'])->delete(); + Permission::where(['name' => 'ticketTranscript.read-own'])->delete(); + } +}; diff --git a/docker-compose.yml b/docker-compose.yml index cb6374d..191ba02 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,6 +9,16 @@ services: MARIADB_DATABASE: ${DB_DATABASE} MARIADB_ROOT_PASSWORD: ${DB_PASSWORD} + mariadb-test: + image: mariadb:10.6.18 + ports: + - '3307:3306' + environment: + MARIADB_USER: ${DB_USERNAME} + MARIADB_PASSWORD: ${DB_PASSWORD} + MARIADB_DATABASE: ${DB_DATABASE} + MARIADB_ROOT_PASSWORD: ${DB_PASSWORD} + app: build: ./ profiles: [windows] diff --git a/phpunit.xml b/phpunit.xml index 4e154be..a2f42e5 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -19,6 +19,7 @@ + diff --git a/tests/Feature/Http/Controllers/ApplicationControllerTest.php b/tests/Feature/Http/Controllers/ApplicationControllerTest.php index 6f61273..9fc23ac 100644 --- a/tests/Feature/Http/Controllers/ApplicationControllerTest.php +++ b/tests/Feature/Http/Controllers/ApplicationControllerTest.php @@ -1,104 +1,226 @@ create(); - $user = User::factory()->owner()->create(); +use function PHPUnit\Framework\assertEquals; +use function PHPUnit\Framework\assertFalse; - $this->actingAs($user) - ->get(route('application.index')) - ->assertOk() - ->assertJsonCount(1, 'data') - ->assertJsonPath('data.0.id', $application->id); +pest()->use(CrudPermissionTrait::class); + +describe('read operations', function () { + test('read permission', function () { + Application::factory()->create(); + $this->assertReadPermissions('application.index', 'application.read'); + }); + + test('owner can read application', function () { + $application = Application::factory()->create(); + $user = User::factory()->owner()->create(); + + $this->actingAs($user) + ->get(route('application.index')) + ->assertOk() + ->assertJsonCount(1, 'data') + ->assertJsonPath('data.0.id', $application->id); + }); }); -test('can create application', function () { - $user = User::factory()->owner()->create(); - $data = [ - 'name' => 'name', - 'is_active' => true, - 'log_channel' => 'log_channel', - 'accept_message' => 'accept_message', - 'deny_message' => 'deny_message', - 'confirmation_message' => 'confirmation_message', - 'completion_message' => 'completion_message', - 'restricted_role_ids' => ['1', '2'], - 'accepted_role_ids' => ['3', '4'], - 'denied_role_ids' => ['5', '6'], - 'ping_role_ids' => ['7', '8'], - 'accept_removal_role_ids' => ['9', '10'], - 'deny_removal_role_ids' => ['11', '12'], - 'pending_role_ids' => ['13', '14'], - 'required_role_ids' => ['15', '16'], - ]; - $validationData = collect($data)->except('restricted_role_ids', 'accepted_role_ids', 'denied_role_ids', 'ping_role_ids', 'accept_removal_role_ids', 'deny_removal_role_ids', 'pending_role_ids', 'required_role_ids')->toArray(); - - $this->actingAs($user) - ->postJson(route('application.store'), $data) - ->assertCreated() - ->assertJson(['data' => $validationData]); - - $this->assertDatabaseHas('applications', $validationData); - - expect(Application::count())->toBe(1); - expect(Application::first()->restrictedRoles->map(fn ($restrictedRole) => $restrictedRole->role_id)->toArray())->toBe(['1', '2']); - expect(Application::first()->acceptedRoles->map(fn ($acceptedRole) => $acceptedRole->role_id)->toArray())->toBe(['3', '4']); - expect(Application::first()->deniedRoles->map(fn ($deniedRole) => $deniedRole->role_id)->toArray())->toBe(['5', '6']); - expect(Application::first()->pingRoles->map(fn ($pingRole) => $pingRole->role_id)->toArray())->toBe(['7', '8']); - expect(Application::first()->acceptRemovalRoles->map(fn ($acceptRemovalRole) => $acceptRemovalRole->role_id)->toArray())->toBe(['9', '10']); - expect(Application::first()->denyRemovalRoles->map(fn ($denyRemovalRole) => $denyRemovalRole->role_id)->toArray())->toBe(['11', '12']); - expect(Application::first()->pendingRoles->map(fn ($pendingRole) => $pendingRole->role_id)->toArray())->toBe(['13', '14']); - expect(Application::first()->requiredRoles->map(fn ($requiredRole) => $requiredRole->role_id)->toArray())->toBe(['15', '16']); +describe('create operations', function () { + test('create permission', function () { + $data = [ + 'name' => 'name', + 'is_active' => true, + 'log_channel' => 'log_channel', + 'accept_message' => 'accept_message', + 'deny_message' => 'deny_message', + 'confirmation_message' => 'confirmation_message', + 'completion_message' => 'completion_message', + ]; + $this->assertCreatePermissions('application.store', 'application.create', $data, Application::class); + }); + + test('can create application', function () { + $user = User::factory()->owner()->create(); + $data = [ + 'name' => 'name', + 'is_active' => true, + 'log_channel' => 'log_channel', + 'accept_message' => 'accept_message', + 'deny_message' => 'deny_message', + 'confirmation_message' => 'confirmation_message', + 'completion_message' => 'completion_message', + 'activity_channel' => 'activity_channel', + 'restricted_role_ids' => ['1', '2'], + 'accepted_role_ids' => ['3', '4'], + 'denied_role_ids' => ['5', '6'], + 'ping_role_ids' => ['7', '8'], + 'accept_removal_role_ids' => ['9', '10'], + 'deny_removal_role_ids' => ['11', '12'], + 'pending_role_ids' => ['13', '14'], + 'required_role_ids' => ['15', '16'], + 'embed_channel_id' => 'embed_channel_id', + 'embed_title' => 'embed_title', + 'embed_description' => 'embed_description', + 'embed_color' => '#123123', + 'embed_button_text' => 'embed_button_text', + 'embed_button_color' => DiscordButton::Primary->value, + ]; + $validationData = collect($data) + ->except('restricted_role_ids', 'accepted_role_ids', 'denied_role_ids', 'ping_role_ids', 'accept_removal_role_ids', 'deny_removal_role_ids', 'pending_role_ids', 'required_role_ids') + ->toArray(); + + $this->actingAs($user) + ->postJson(route('application.store'), $data) + ->assertCreated() + ->assertJson(['data' => $validationData]); + + $this->assertDatabaseHas('applications', $validationData); + + expect(Application::count())->toBe(1); + expect(Application::first()->restrictedRoles->map(fn ($restrictedRole) => $restrictedRole->role_id)->toArray())->toBe(['1', '2']); + expect(Application::first()->acceptedRoles->map(fn ($acceptedRole) => $acceptedRole->role_id)->toArray())->toBe(['3', '4']); + expect(Application::first()->deniedRoles->map(fn ($deniedRole) => $deniedRole->role_id)->toArray())->toBe(['5', '6']); + expect(Application::first()->pingRoles->map(fn ($pingRole) => $pingRole->role_id)->toArray())->toBe(['7', '8']); + expect(Application::first()->acceptRemovalRoles->map(fn ($acceptRemovalRole) => $acceptRemovalRole->role_id)->toArray())->toBe(['9', '10']); + expect(Application::first()->denyRemovalRoles->map(fn ($denyRemovalRole) => $denyRemovalRole->role_id)->toArray())->toBe(['11', '12']); + expect(Application::first()->pendingRoles->map(fn ($pendingRole) => $pendingRole->role_id)->toArray())->toBe(['13', '14']); + expect(Application::first()->requiredRoles->map(fn ($requiredRole) => $requiredRole->role_id)->toArray())->toBe(['15', '16']); + }); }); -test('can update application', function () { - $user = User::factory()->owner()->create(); - $application = Application::factory()->create(); - $data = [ - 'name' => 'name', - 'is_active' => true, - 'log_channel' => 'log_channel', - 'accept_message' => 'accept_message', - 'deny_message' => 'deny_message', - 'confirmation_message' => 'confirmation_message', - 'completion_message' => 'completion_message', - 'restricted_role_ids' => ['1', '2'], - 'accepted_role_ids' => ['3', '4'], - 'denied_role_ids' => ['5', '6'], - 'ping_role_ids' => ['7', '8'], - 'accept_removal_role_ids' => ['9', '10'], - 'deny_removal_role_ids' => ['11', '12'], - 'pending_role_ids' => ['13', '14'], - 'required_role_ids' => ['15', '16'], - ]; - $validationData = collect($data)->except('restricted_role_ids', 'accepted_role_ids', 'denied_role_ids', 'ping_role_ids', 'accept_removal_role_ids', 'deny_removal_role_ids', 'pending_role_ids', 'required_role_ids')->toArray(); - - $this->actingAs($user) - ->patchJson(route('application.update', $application), $data) - ->assertOk() - ->assertJson(['data' => $validationData]); - - $this->assertDatabaseHas('applications', $validationData); - - expect($application->restrictedRoles->map(fn ($restrictedRole) => $restrictedRole->role_id)->toArray())->toBe(['1', '2']); - expect($application->acceptedRoles->map(fn ($acceptedRole) => $acceptedRole->role_id)->toArray())->toBe(['3', '4']); - expect($application->deniedRoles->map(fn ($deniedRole) => $deniedRole->role_id)->toArray())->toBe(['5', '6']); - expect($application->pingRoles->map(fn ($pingRole) => $pingRole->role_id)->toArray())->toBe(['7', '8']); - expect($application->acceptRemovalRoles->map(fn ($acceptRemovalRole) => $acceptRemovalRole->role_id)->toArray())->toBe(['9', '10']); - expect($application->denyRemovalRoles->map(fn ($denyRemovalRole) => $denyRemovalRole->role_id)->toArray())->toBe(['11', '12']); - expect($application->pendingRoles->map(fn ($pendingRole) => $pendingRole->role_id)->toArray())->toBe(['13', '14']); - expect($application->requiredRoles->map(fn ($requiredRole) => $requiredRole->role_id)->toArray())->toBe(['15', '16']); +describe('update operations', function () { + test('update permission', function () { + $application = Application::factory()->create(); + $data = [ + 'name' => 'Test', + ]; + $this->assertUpdatePermissions('application.update', 'application.update', $application, $data, Application::class); + }); + + test('can update application', function () { + $user = User::factory()->owner()->create(); + $application = Application::factory()->create(); + $data = [ + 'name' => 'name', + 'is_active' => true, + 'log_channel' => 'log_channel', + 'accept_message' => 'accept_message', + 'deny_message' => 'deny_message', + 'confirmation_message' => 'confirmation_message', + 'completion_message' => 'completion_message', + 'activity_channel' => 'activity_channel', + 'restricted_role_ids' => ['1', '2'], + 'accepted_role_ids' => ['3', '4'], + 'denied_role_ids' => ['5', '6'], + 'ping_role_ids' => ['7', '8'], + 'accept_removal_role_ids' => ['9', '10'], + 'deny_removal_role_ids' => ['11', '12'], + 'pending_role_ids' => ['13', '14'], + 'required_role_ids' => ['15', '16'], + 'embed_channel_id' => 'embed_channel_id', + 'embed_title' => 'embed_title', + 'embed_description' => 'embed_description', + 'embed_color' => '#123123', + 'embed_button_text' => 'embed_button_text', + 'embed_button_color' => DiscordButton::Primary->value, + ]; + $validationData = collect($data) + ->except('restricted_role_ids', 'accepted_role_ids', 'denied_role_ids', 'ping_role_ids', 'accept_removal_role_ids', 'deny_removal_role_ids', 'pending_role_ids', 'required_role_ids') + ->toArray(); + + $this->actingAs($user) + ->patchJson(route('application.update', $application), $data) + ->assertOk() + ->assertJson(['data' => $validationData]); + + $this->assertDatabaseHas('applications', $validationData); + + expect($application->restrictedRoles->map(fn ($restrictedRole) => $restrictedRole->role_id)->toArray())->toBe(['1', '2']); + expect($application->acceptedRoles->map(fn ($acceptedRole) => $acceptedRole->role_id)->toArray())->toBe(['3', '4']); + expect($application->deniedRoles->map(fn ($deniedRole) => $deniedRole->role_id)->toArray())->toBe(['5', '6']); + expect($application->pingRoles->map(fn ($pingRole) => $pingRole->role_id)->toArray())->toBe(['7', '8']); + expect($application->acceptRemovalRoles->map(fn ($acceptRemovalRole) => $acceptRemovalRole->role_id)->toArray())->toBe(['9', '10']); + expect($application->denyRemovalRoles->map(fn ($denyRemovalRole) => $denyRemovalRole->role_id)->toArray())->toBe(['11', '12']); + expect($application->pendingRoles->map(fn ($pendingRole) => $pendingRole->role_id)->toArray())->toBe(['13', '14']); + expect($application->requiredRoles->map(fn ($requiredRole) => $requiredRole->role_id)->toArray())->toBe(['15', '16']); + }); }); -test('can delete application', function () { - $user = User::factory()->owner()->create(); - $application = Application::factory()->create(); +describe('delete operations', function () { + test('delete permission', function () { + $application = Application::factory()->create(); + $this->assertDeletePermissions('application.destroy', 'application.delete', $application, Application::class, true); + }); + + test('can delete application', function () { + $user = User::factory()->owner()->create(); + $application = Application::factory()->create(); + + $this->actingAs($user) + ->deleteJson(route('application.destroy', $application)) + ->assertOk(); + + $this->assertSoftDeleted('applications', ['id' => $application->id]); + }); +}); + +describe('send button operations', function () { + test('send button permission', function () { + $application = Application::factory()->create(); + $user = User::factory()->create(); + $route = 'application.sendButton'; + $permission = 'application.update'; + assertFalse($user->can($permission)); + + $this->actingAs($user) + ->get(route($route, $application)) + ->assertForbidden(); + + $user->givePermissionTo($permission); + $this->actingAs($user) + ->get(route($route, $application)) + ->assertOk(); + }); + + test('can send application embed', function () { + Http::fake(); + $application = Application::factory()->withEmbed()->create(); + $user = User::factory()->owner()->create(); + $response = $this->actingAs($user) + ->get(route('application.sendButton', $application)) + ->assertOk(); + + assertEquals(1, $response->getContent()); + + Http::assertSent(fn ($request) => $request->data()['embeds'][0]->title === $application->embed_title && + $request->data()['embeds'][0]->description === $application->embed_description && + $request->data()['components'][0]->components[0]->style === $application->embed_button_color + ); + }); + + test('can not send application embed with missing embed attribute', function (string $attribute) { + Http::fake(); + $application = Application::factory()->create([ + $attribute => null, + ]); + $user = User::factory()->owner()->create(); + + $response = $this->actingAs($user) + ->get(route('application.sendButton', $application)) + ->assertOk(); - $this->actingAs($user) - ->deleteJson(route('application.destroy', $application)) - ->assertOk(); + assertEquals('', $response->getContent()); - $this->assertSoftDeleted('applications', ['id' => $application->id]); + Http::assertNothingSent(); + })->with([ + 'embed_title', + 'embed_description', + 'embed_color', + 'embed_channel_id', + 'embed_button_color', + 'embed_button_text', + ]); }); diff --git a/tests/Feature/Http/Controllers/ApplicationQuestionAnswerControllerTest.php b/tests/Feature/Http/Controllers/ApplicationQuestionAnswerControllerTest.php index aac9860..1c1531f 100644 --- a/tests/Feature/Http/Controllers/ApplicationQuestionAnswerControllerTest.php +++ b/tests/Feature/Http/Controllers/ApplicationQuestionAnswerControllerTest.php @@ -5,64 +5,106 @@ use App\Models\ApplicationQuestionAnswer; use App\Models\ApplicationSubmission; use App\Models\User; +use Tests\Traits\CrudPermissionTrait; -test('auth user can get application question answer', function () { - $applicationQuestion = ApplicationQuestionAnswer::factory()->create(); - $user = User::factory()->owner()->create(); +pest()->use(CrudPermissionTrait::class); - $this->actingAs($user) - ->get(route('application-question-answer.index')) - ->assertOk() - ->assertJsonCount(1, 'data') - ->assertJsonPath('data.0.id', $applicationQuestion->id); +describe('read operations', function () { + test('read permission', function () { + ApplicationQuestionAnswer::factory()->create(); + $this->assertReadPermissions('application-question-answer.index', 'applicationAnswerQuestion.read'); + }); + + test('can read application question answer', function () { + $applicationQuestion = ApplicationQuestionAnswer::factory()->create(); + $user = User::factory()->owner()->create(); + + $this->actingAs($user) + ->get(route('application-question-answer.index')) + ->assertOk() + ->assertJsonCount(1, 'data') + ->assertJsonPath('data.0.id', $applicationQuestion->id); + }); }); -test('can create application question answer', function () { - $user = User::factory()->owner()->create(); - $applicationQuestion = ApplicationQuestion::factory()->create(); - $applicationSubmission = ApplicationSubmission::factory()->create([ - 'state' => ApplicationSubmissionState::Pending, - ]); - $data = [ - 'application_question_id' => $applicationQuestion->id, - 'application_submission_id' => $applicationSubmission->id, - 'answer' => 'Test', - ]; - - $this->actingAs($user) - ->postJson(route('application-question-answer.store'), $data) - ->assertCreated() - ->assertJson(['data' => $data]); - - $this->assertDatabaseHas('application_question_answers', $data); +describe('create operations', function () { + test('create permission', function () { + $applicationQuestion = ApplicationQuestion::factory()->create(); + $applicationSubmission = ApplicationSubmission::factory()->create([ + 'state' => ApplicationSubmissionState::Pending, + ]); + $data = [ + 'application_question_id' => $applicationQuestion->id, + 'application_submission_id' => $applicationSubmission->id, + 'answer' => 'Test', + ]; + $this->assertCreatePermissions('application-question-answer.store', 'applicationAnswerQuestion.create', $data, ApplicationQuestionAnswer::class); + }); + + test('can create application question answer', function () { + $user = User::factory()->owner()->create(); + $applicationQuestion = ApplicationQuestion::factory()->create(); + $applicationSubmission = ApplicationSubmission::factory()->create([ + 'state' => ApplicationSubmissionState::Pending, + ]); + $data = [ + 'application_question_id' => $applicationQuestion->id, + 'application_submission_id' => $applicationSubmission->id, + 'answer' => 'Test', + ]; + + $this->actingAs($user) + ->postJson(route('application-question-answer.store'), $data) + ->assertCreated() + ->assertJson(['data' => $data]); + + $this->assertDatabaseHas('application_question_answers', $data); + }); }); -test('can update application question answer', function () { - $user = User::factory()->owner()->create(); - $applicationQuestionAnswer = ApplicationQuestionAnswer::factory()->create(); - $applicationQuestion = ApplicationQuestion::factory()->create(); - $applicationSubmission = ApplicationSubmission::factory()->create(); - $data = [ - 'application_question_id' => $applicationQuestion->id, - 'application_submission_id' => $applicationSubmission->id, - 'answer' => 'Test', - ]; - - $this->actingAs($user) - ->patchJson(route('application-question-answer.update', $applicationQuestionAnswer), $data) - ->assertOk() - ->assertJson(['data' => $data]); - - $this->assertDatabaseHas('application_question_answers', $data); +describe('update operations', function () { + test('update permission', function () { + $applicationQuestionAnswer = ApplicationQuestionAnswer::factory()->create(); + $data = [ + 'answer' => 'Test', + ]; + $this->assertUpdatePermissions('application-question-answer.update', 'applicationAnswerQuestion.update', $applicationQuestionAnswer, $data, ApplicationQuestionAnswer::class); + }); + + test('can update application question answer', function () { + $user = User::factory()->owner()->create(); + $applicationQuestionAnswer = ApplicationQuestionAnswer::factory()->create(); + $applicationQuestion = ApplicationQuestion::factory()->create(); + $applicationSubmission = ApplicationSubmission::factory()->create(); + $data = [ + 'application_question_id' => $applicationQuestion->id, + 'application_submission_id' => $applicationSubmission->id, + 'answer' => 'Test', + 'attachments' => 'attachments', + ]; + + $this->actingAs($user) + ->patchJson(route('application-question-answer.update', $applicationQuestionAnswer), $data) + ->assertOk() + ->assertJson(['data' => $data]); + + $this->assertDatabaseHas('application_question_answers', $data); + }); }); -test('can delete application question answer', function () { - $user = User::factory()->owner()->create(); - $applicationQuestionAnswer = ApplicationQuestionAnswer::factory()->create(); +describe('delete operations', function () { + test('delete permission', function () { + $applicationQuestionAnswer = ApplicationQuestionAnswer::factory()->create(); + $this->assertDeletePermissions('application-question-answer.destroy', 'applicationAnswerQuestion.delete', $applicationQuestionAnswer, ApplicationQuestionAnswer::class); + }); + test('can delete application question answer', function () { + $user = User::factory()->owner()->create(); + $applicationQuestionAnswer = ApplicationQuestionAnswer::factory()->create(); - $this->actingAs($user) - ->deleteJson(route('application-question-answer.destroy', $applicationQuestionAnswer)) - ->assertOk(); + $this->actingAs($user) + ->deleteJson(route('application-question-answer.destroy', $applicationQuestionAnswer)) + ->assertOk(); - $this->assertDatabaseMissing('application_question_answers', $applicationQuestionAnswer->toArray()); + $this->assertDatabaseMissing('application_question_answers', $applicationQuestionAnswer->toArray()); + }); }); diff --git a/tests/Feature/Http/Controllers/ApplicationQuestionControllerTest.php b/tests/Feature/Http/Controllers/ApplicationQuestionControllerTest.php index b9c0966..bb5c0d8 100644 --- a/tests/Feature/Http/Controllers/ApplicationQuestionControllerTest.php +++ b/tests/Feature/Http/Controllers/ApplicationQuestionControllerTest.php @@ -3,63 +3,102 @@ use App\Models\Application; use App\Models\ApplicationQuestion; use App\Models\User; +use Tests\Traits\CrudPermissionTrait; -test('auth user can get application question', function () { - $applicationQuestion = ApplicationQuestion::factory()->create(); - $user = User::factory()->owner()->create(); +pest()->use(CrudPermissionTrait::class); - $this->actingAs($user) - ->get(route('application-question.index')) - ->assertOk() - ->assertJsonCount(1, 'data') - ->assertJsonPath('data.0.id', $applicationQuestion->id); +describe('read operations', function () { + test('read permission', function () { + ApplicationQuestion::factory()->create(); + $this->assertReadPermissions('application-question.index', 'applicationQuestion.read'); + }); + test('can read application question', function () { + $applicationQuestion = ApplicationQuestion::factory()->create(); + $user = User::factory()->owner()->create(); + + $this->actingAs($user) + ->get(route('application-question.index')) + ->assertOk() + ->assertJsonCount(1, 'data') + ->assertJsonPath('data.0.id', $applicationQuestion->id); + }); }); -test('can create application question', function () { - $user = User::factory()->owner()->create(); - $application = Application::factory()->create(); - $data = [ - 'question' => 'Test', - 'order' => 1, - 'is_active' => true, - 'application_id' => $application->id, - ]; - - $this->actingAs($user) - ->postJson(route('application-question.store'), $data) - ->assertCreated() - ->assertJson(['data' => $data]); - - $this->assertDatabaseHas('application_questions', $data); +describe('create operations', function () { + test('create permission', function () { + $application = Application::factory()->create(); + $data = [ + 'question' => 'Test', + 'order' => 1, + 'is_active' => true, + 'application_id' => $application->id, + ]; + $this->assertCreatePermissions('application-question.store', 'applicationQuestion.create', $data, ApplicationQuestion::class); + }); + + test('can create application question', function () { + $user = User::factory()->owner()->create(); + $application = Application::factory()->create(); + $data = [ + 'question' => 'Test', + 'order' => 1, + 'is_active' => true, + 'application_id' => $application->id, + ]; + + $this->actingAs($user) + ->postJson(route('application-question.store'), $data) + ->assertCreated() + ->assertJson(['data' => $data]); + + $this->assertDatabaseHas('application_questions', $data); + }); }); -test('can update application question', function () { - $user = User::factory()->owner()->create(); - $applicationQuestion = ApplicationQuestion::factory()->create(); - $application = Application::factory()->create(); - $data = [ - 'question' => 'Test', - 'order' => 1, - 'is_active' => true, - 'application_id' => $application->id, - ]; - - $this->actingAs($user) - ->patchJson(route('application-question.update', $applicationQuestion), $data) - ->assertOk() - ->assertJson(['data' => $data]); - - $this->assertDatabaseHas('application_questions', $data); +describe('update operations', function () { + test('update permission', function () { + $applicationQuestion = ApplicationQuestion::factory()->create(); + $data = [ + 'question' => 'Test', + ]; + $this->assertUpdatePermissions('application-question.update', 'applicationQuestion.update', $applicationQuestion, $data, ApplicationQuestion::class); + }); + + test('can update application question', function () { + $user = User::factory()->owner()->create(); + $applicationQuestion = ApplicationQuestion::factory()->create(); + $application = Application::factory()->create(); + $data = [ + 'question' => 'Test', + 'order' => 1, + 'is_active' => true, + 'application_id' => $application->id, + ]; + + $this->actingAs($user) + ->patchJson(route('application-question.update', $applicationQuestion), $data) + ->assertOk() + ->assertJson(['data' => $data]); + + $this->assertDatabaseHas('application_questions', $data); + }); }); -test('can delete application question', function () { - $user = User::factory()->owner()->create(); - $applicationQuestion = ApplicationQuestion::factory()->create(); +describe('delete operations', function () { + test('delete permission', function () { + $applicationQuestion = ApplicationQuestion::factory()->create(); + $this->assertDeletePermissions('application-question.destroy', 'applicationQuestion.delete', $applicationQuestion, ApplicationQuestion::class, true); + }); + + test('can delete application question', function () { + $user = User::factory()->owner()->create(); + $applicationQuestion = ApplicationQuestion::factory()->create(); - $this->actingAs($user) - ->deleteJson(route('application-question.destroy', $applicationQuestion)) - ->assertOk(); + $this->actingAs($user) + ->deleteJson(route('application-question.destroy', $applicationQuestion)) + ->assertOk(); - $this->assertSoftDeleted('application_questions', ['id' => $applicationQuestion->id]); + $this->assertSoftDeleted('application_questions', ['id' => $applicationQuestion->id]); + }); }); diff --git a/tests/Feature/Http/Controllers/ApplicationResponseControllerTest.php b/tests/Feature/Http/Controllers/ApplicationResponseControllerTest.php index be1231f..84df9ff 100644 --- a/tests/Feature/Http/Controllers/ApplicationResponseControllerTest.php +++ b/tests/Feature/Http/Controllers/ApplicationResponseControllerTest.php @@ -4,63 +4,102 @@ use App\Models\Application; use App\Models\ApplicationResponse; use App\Models\User; +use Tests\Traits\CrudPermissionTrait; -test('auth user can get application response', function () { - $application = ApplicationResponse::factory()->create(); - $user = User::factory()->owner()->create(); +pest()->use(CrudPermissionTrait::class); - $this->actingAs($user) - ->get(route('application-response.index')) - ->assertOk() - ->assertJsonCount(1, 'data') - ->assertJsonPath('data.0.id', $application->id); +describe('read operations', function () { + test('read permission', function () { + ApplicationResponse::factory()->create(); + $this->assertReadPermissions('application-response.index', 'applicationResponse.read'); + }); + test('can read application response', function () { + $application = ApplicationResponse::factory()->create(); + $user = User::factory()->owner()->create(); + + $this->actingAs($user) + ->get(route('application-response.index')) + ->assertOk() + ->assertJsonCount(1, 'data') + ->assertJsonPath('data.0.id', $application->id); + }); }); -test('can create application response', function () { - $user = User::factory()->owner()->create(); - $application = Application::factory()->create(); - $data = [ - 'type' => ApplicationResponseType::Accepted->value, - 'name' => 'Test', - 'response' => 'Test', - 'application_id' => $application->id, - ]; - - $this->actingAs($user) - ->postJson(route('application-response.store'), $data) - ->assertCreated() - ->assertJson(['data' => $data]); - - $this->assertDatabaseHas('application_responses', $data); +describe('create operations', function () { + test('create permission', function () { + $application = Application::factory()->create(); + $data = [ + 'type' => ApplicationResponseType::Accepted->value, + 'name' => 'Test', + 'response' => 'Test', + 'application_id' => $application->id, + ]; + $this->assertCreatePermissions('application-response.store', 'applicationResponse.create', $data, ApplicationResponse::class); + }); + + test('can create application response', function () { + $user = User::factory()->owner()->create(); + $application = Application::factory()->create(); + $data = [ + 'type' => ApplicationResponseType::Accepted->value, + 'name' => 'Test', + 'response' => 'Test', + 'application_id' => $application->id, + ]; + + $this->actingAs($user) + ->postJson(route('application-response.store'), $data) + ->assertCreated() + ->assertJson(['data' => $data]); + + $this->assertDatabaseHas('application_responses', $data); + }); }); -test('can update application response', function () { - $user = User::factory()->owner()->create(); - $applicationResponse = ApplicationResponse::factory()->create(); - $application = Application::factory()->create(); - $data = [ - 'type' => ApplicationResponseType::Accepted->value, - 'name' => 'Test', - 'response' => 'Test', - 'application_id' => $application->id, - ]; - - $this->actingAs($user) - ->patchJson(route('application-response.update', $applicationResponse), $data) - ->assertOk() - ->assertJson(['data' => $data]); - - $this->assertDatabaseHas('application_responses', $data); +describe('update operations', function () { + test('update permission', function () { + $applicationResponse = ApplicationResponse::factory()->create(); + $data = [ + 'name' => 'Test', + ]; + $this->assertUpdatePermissions('application-response.update', 'applicationResponse.update', $applicationResponse, $data, ApplicationResponse::class); + }); + + test('can update application response', function () { + $user = User::factory()->owner()->create(); + $applicationResponse = ApplicationResponse::factory()->create(); + $application = Application::factory()->create(); + $data = [ + 'type' => ApplicationResponseType::Accepted->value, + 'name' => 'Test', + 'response' => 'Test', + 'application_id' => $application->id, + ]; + + $this->actingAs($user) + ->patchJson(route('application-response.update', $applicationResponse), $data) + ->assertOk() + ->assertJson(['data' => $data]); + + $this->assertDatabaseHas('application_responses', $data); + }); }); -test('can delete application response', function () { - $user = User::factory()->owner()->create(); - $applicationResponse = ApplicationResponse::factory()->create(); +describe('delete operations', function () { + test('delete permission', function () { + $applicationResponse = ApplicationResponse::factory()->create(); + $this->assertDeletePermissions('application-response.destroy', 'applicationResponse.delete', $applicationResponse, ApplicationResponse::class, true); + }); + + test('can delete application response', function () { + $user = User::factory()->owner()->create(); + $applicationResponse = ApplicationResponse::factory()->create(); - $this->actingAs($user) - ->deleteJson(route('application-response.destroy', $applicationResponse)) - ->assertOk(); + $this->actingAs($user) + ->deleteJson(route('application-response.destroy', $applicationResponse)) + ->assertOk(); - $this->assertSoftDeleted('application_responses', ['id' => $applicationResponse->id]); + $this->assertSoftDeleted('application_responses', ['id' => $applicationResponse->id]); + }); }); diff --git a/tests/Feature/Http/Controllers/ApplicationSubmissionControllerTest.php b/tests/Feature/Http/Controllers/ApplicationSubmissionControllerTest.php index c381a67..fc7ed30 100644 --- a/tests/Feature/Http/Controllers/ApplicationSubmissionControllerTest.php +++ b/tests/Feature/Http/Controllers/ApplicationSubmissionControllerTest.php @@ -1,36 +1,52 @@ use(CrudPermissionTrait::class); beforeEach(function () { Http::fake([ - config('services.discord.api_url').'/guilds/*' => Http::response([ - 'flags' => 1, - 'nick' => '', - 'pending' => false, - 'premium_since' => '', - 'roles' => [], - 'unusual_dm_activity_until' => '', - 'mute' => false, - 'deaf' => false, - 'user' => [ - 'id' => 123, - 'username' => 'test', - 'global_name' => 'test', - 'discriminator' => '', - 'public_flags' => 1, - 'flags' => 1, - 'accent_color' => 1, - 'banner_color' => '', - ], - 'joined_at' => now()->toDateTimeString(), - ]), + config('services.discord.api_url').'/guilds/*' => Http::response(new MemberData( + avatar: null, + banner: null, + communication_disabled_until: null, + flags: 1, + joined_at: now()->toDateTimeString(), + nick: '', + pending: false, + premium_since: '', + roles: [], + unusual_dm_activity_until: '', + user: new UserData( + id: 123, + username: 'test', + avatar: null, + global_name: 'test', + discriminator: '', + public_flags: 1, + flags: 1, + accent_color: 1, + banner: null, + clan: null, + primary_guild: null, + ), + mute: false, + deaf: false, + )->toArray()), ]); Http::fake([ + config('services.discord.api_url').'/users/@me/channels' => Http::response([ + 'id' => '123', + ]), config('services.discord.api_url').'/channels/*' => Http::response([ 'id' => '123', 'channel_id' => '123', @@ -38,68 +54,146 @@ ]); }); -test('auth user can get application submission', function () { - $applicationSubmission = ApplicationSubmission::factory()->create(); - $user = User::factory()->owner()->create(); +describe('read operations', function () { + test('read permission', function () { + ApplicationSubmission::factory()->create(); + $this->assertReadPermissions('application-submission.index', 'applicationSubmission.read'); + }); + + test('auth user can get application submission', function () { + $applicationSubmission = ApplicationSubmission::factory()->create(); + $user = User::factory()->owner()->create(); + + $this->actingAs($user) + ->get(route('application-submission.index')) + ->assertOk() + ->assertJsonCount(1, 'data') + ->assertJsonPath('data.0.id', $applicationSubmission->id); + }); +}); + +describe('create operations', function () { + test('create permission', function () { + $applicationResponse = ApplicationResponse::factory()->create(); + $application = Application::factory()->create(); + $data = [ + 'discord_id' => '123123123123123123', + 'submitted_at' => '2024-12-24 12:00:00', + 'application_response_id' => $applicationResponse->id, + 'state' => ApplicationSubmissionState::Pending->value, + 'custom_response' => 'Test', + 'handled_by' => '123123123123123123', + 'application_id' => $application->id, + ]; + $this->assertCreatePermissions('application-submission.store', 'applicationSubmission.create', $data, ApplicationSubmission::class); + }); + + test('can create application submission', function () { + $user = User::factory()->owner()->create(); + $applicationResponse = ApplicationResponse::factory()->create(); + $application = Application::factory()->create(); + $data = [ + 'discord_id' => '123123123123123123', + 'submitted_at' => '2024-12-24 12:00:00', + 'application_response_id' => $applicationResponse->id, + 'state' => ApplicationSubmissionState::Pending->value, + 'custom_response' => 'Test', + 'handled_by' => '123123123123123123', + 'application_id' => $application->id, + ]; + + $this->actingAs($user) + ->postJson(route('application-submission.store'), $data) + ->assertCreated() + ->assertJson(['data' => collect($data)->except('submitted_at')->toArray()]); - $this->actingAs($user) - ->get(route('application-submission.index')) - ->assertOk() - ->assertJsonCount(1, 'data') - ->assertJsonPath('data.0.id', $applicationSubmission->id); + $this->assertDatabaseHas('application_submissions', $data); + }); }); -test('can create application submission', function () { - $user = User::factory()->owner()->create(); - $applicationResponse = ApplicationResponse::factory()->create(); - $application = Application::factory()->create(); - $data = [ - 'discord_id' => '123123123123123123', - 'submitted_at' => '2024-12-24 12:00:00', - 'application_response_id' => $applicationResponse->id, - 'state' => ApplicationSubmissionState::Pending->value, - 'custom_response' => 'Test', - 'handled_by' => '123123123123123123', - 'application_id' => $application->id, - ]; - - $this->actingAs($user) - ->postJson(route('application-submission.store'), $data) - ->assertCreated() - ->assertJson(['data' => collect($data)->except('submitted_at')->toArray()]); - - $this->assertDatabaseHas('application_submissions', $data); +describe('update operations', function () { + test('update permission', function () { + $applicationSubmission = ApplicationSubmission::factory()->create(); + $data = [ + 'discord_id' => '123123123123123123', + ]; + $this->assertUpdatePermissions('application-submission.update', 'applicationSubmission.update', $applicationSubmission, $data, ApplicationSubmission::class); + }); + + test('can update application submission', function () { + $user = User::factory()->owner()->create(); + $applicationSubmission = ApplicationSubmission::factory()->create(); + $applicationResponse = ApplicationResponse::factory()->create(); + $application = Application::factory()->create(); + $data = [ + 'discord_id' => '123123123123123123', + 'submitted_at' => '2024-12-24 12:00:00', + 'application_response_id' => $applicationResponse->id, + 'state' => ApplicationSubmissionState::Pending->value, + 'custom_response' => 'Test', + 'handled_by' => '123123123123123123', + 'application_id' => $application->id, + ]; + + $this->actingAs($user) + ->patchJson(route('application-submission.update', $applicationSubmission), $data) + ->assertOk() + ->assertJson(['data' => collect($data)->except('submitted_at')->toArray()]); + + $this->assertDatabaseHas('application_submissions', $data); + }); }); -test('can update application submission', function () { - $user = User::factory()->owner()->create(); - $applicationSubmission = ApplicationSubmission::factory()->create(); - $applicationResponse = ApplicationResponse::factory()->create(); - $application = Application::factory()->create(); - $data = [ - 'discord_id' => '123123123123123123', - 'submitted_at' => '2024-12-24 12:00:00', - 'application_response_id' => $applicationResponse->id, - 'state' => ApplicationSubmissionState::Pending->value, - 'custom_response' => 'Test', - 'handled_by' => '123123123123123123', - 'application_id' => $application->id, - ]; - - $this->actingAs($user) - ->patchJson(route('application-submission.update', $applicationSubmission), $data) - ->assertOk() - ->assertJson(['data' => collect($data)->except('submitted_at')->toArray()]); - - $this->assertDatabaseHas('application_submissions', $data); +describe('delete operations', function () { + test('delete permission', function () { + $applicationSubmission = ApplicationSubmission::factory()->create(); + $this->assertDeletePermissions('application-submission.destroy', 'applicationSubmission.delete', $applicationSubmission, ApplicationSubmission::class); + }); + + test('can delete application submission', function () { + $user = User::factory()->owner()->create(); + $applicationSubmission = ApplicationSubmission::factory()->create(); + + $this->actingAs($user) + ->deleteJson(route('application-submission.destroy', $applicationSubmission)) + ->assertOk(); + $this->assertDatabaseMissing('application_submissions', collect($applicationSubmission)->except('application')->toArray()); + }); }); -test('can delete application submission', function () { - $user = User::factory()->owner()->create(); - $applicationSubmission = ApplicationSubmission::factory()->create(); +describe('history operations', function () { + test('history permission', function () { + $permission = 'applicationSubmission.read'; + $route = 'application-submission.history'; + $user = User::factory()->create(); + $applicationSubmission = ApplicationSubmission::factory()->create(); + + assertFalse($user->can($permission)); + + $this->actingAs($user) + ->get(route($route, $applicationSubmission)) + ->assertForbidden(); + + $user->givePermissionTo($permission); + + $this->actingAs($user) + ->get(route($route, $applicationSubmission)) + ->assertOk(); + }); + + test('can read submission history', function () { + $user = User::factory()->owner()->create(); + $applicationSubmission1 = ApplicationSubmission::factory()->create(); + $applicationSubmission2 = ApplicationSubmission::factory()->create(['discord_id' => $applicationSubmission1->discord_id]); + $applicationSubmission3 = ApplicationSubmission::factory()->create(); + + $this->actingAs($user) + ->get(route('application-submission.history', $applicationSubmission1)) + ->assertOk() + ->assertJsonCount(2, 'data') + ->assertJsonPath('data.0.id', $applicationSubmission1->id) + ->assertJsonPath('data.1.id', $applicationSubmission2->id) + ->assertJsonMissing(['id' => $applicationSubmission3->id]); - $this->actingAs($user) - ->deleteJson(route('application-submission.destroy', $applicationSubmission)) - ->assertOk(); - $this->assertDatabaseMissing('application_submissions', collect($applicationSubmission)->except('application')->toArray()); + }); }); diff --git a/tests/Feature/Http/Controllers/BotTokenControllerTest.php b/tests/Feature/Http/Controllers/BotTokenControllerTest.php index 36af56b..d0ef7e8 100644 --- a/tests/Feature/Http/Controllers/BotTokenControllerTest.php +++ b/tests/Feature/Http/Controllers/BotTokenControllerTest.php @@ -2,7 +2,9 @@ use App\Models\User; -test('auth user can get bot token', function () { +use function PHPUnit\Framework\assertFalse; + +test('can read bot token', function () { $user = User::factory()->owner()->create(); $this->assertDatabaseMissing('users', ['name' => 'Discord Bot']); @@ -28,8 +30,9 @@ $this->assertEquals(1, $botUser->tokens()->count()); }); -test('none owner user can not get bot token', function () { +test('can not read without permission', function () { $user = User::factory()->create(); + assertFalse($user->can('botToken.read')); $this->assertDatabaseMissing('users', ['name' => 'Discord Bot']); diff --git a/tests/Feature/Http/Controllers/FaqControllerTest.php b/tests/Feature/Http/Controllers/FaqControllerTest.php index 859b7b7..7577a5b 100644 --- a/tests/Feature/Http/Controllers/FaqControllerTest.php +++ b/tests/Feature/Http/Controllers/FaqControllerTest.php @@ -2,57 +2,93 @@ use App\Models\Faq; use App\Models\User; +use Tests\Traits\CrudPermissionTrait; -test('auth user can get faqs', function () { - $faq = Faq::factory()->create(); - $user = User::factory()->owner()->create(); +pest()->use(CrudPermissionTrait::class); - $this->actingAs($user) - ->get(route('faq.index')) - ->assertOk() - ->assertJsonCount(1, 'data') - ->assertJsonPath('data.0.id', $faq->id); +describe('read operations', function () { + test('read permission', function () { + Faq::factory()->create(); + $this->assertReadPermissions('faq.index', 'faq.read'); + }); + test('can read faqs', function () { + $faq = Faq::factory()->create(); + $user = User::factory()->owner()->create(); + + $this->actingAs($user) + ->get(route('faq.index')) + ->assertOk() + ->assertJsonCount(1, 'data') + ->assertJsonPath('data.0.id', $faq->id); + }); }); -test('can create faq', function () { - $user = User::factory()->owner()->create(); - $data = [ - 'question' => 'Is this a test?', - 'answer' => 'Yes, this is a test!', - ]; +describe('create operations', function () { + test('create permission', function () { + $data = [ + 'question' => 'Is this a test?', + 'answer' => 'Yes, this is a test!', + ]; + $this->assertCreatePermissions('faq.store', 'faq.create', $data, Faq::class); + }); + + test('can create faq', function () { + $user = User::factory()->owner()->create(); + $data = [ + 'question' => 'Is this a test?', + 'answer' => 'Yes, this is a test!', + ]; - $this->actingAs($user) - ->postJson(route('faq.store'), $data) - ->assertCreated() - ->assertJson(['data' => $data]); + $this->actingAs($user) + ->postJson(route('faq.store'), $data) + ->assertCreated() + ->assertJson(['data' => $data]); - $this->assertDatabaseHas('faqs', $data); + $this->assertDatabaseHas('faqs', $data); + }); }); -test('can update faq', function () { - $user = User::factory()->owner()->create(); - $faq = Faq::factory()->create(); - $data = [ - 'question' => 'Is this a test?', - 'answer' => 'Yes, this is a test!', - ]; +describe('update operations', function () { + test('update permission', function () { + $faq = Faq::factory()->create(); + $data = [ + 'question' => 'Is this a test?', + ]; + $this->assertUpdatePermissions('faq.update', 'faq.update', $faq, $data, Faq::class); + }); - $this->actingAs($user) - ->patchJson(route('faq.update', $faq), $data) - ->assertOk() - ->assertJson(['data' => $data]); + test('can update faq', function () { + $user = User::factory()->owner()->create(); + $faq = Faq::factory()->create(); + $data = [ + 'question' => 'Is this a test?', + 'answer' => 'Yes, this is a test!', + ]; - $this->assertDatabaseHas('faqs', $data); + $this->actingAs($user) + ->patchJson(route('faq.update', $faq), $data) + ->assertOk() + ->assertJson(['data' => $data]); + + $this->assertDatabaseHas('faqs', $data); + }); }); -test('can delete faq', function () { - $user = User::factory()->owner()->create(); - $faq = Faq::factory()->create(); +describe('update operations', function () { + test('delete permission', function () { + $faq = Faq::factory()->create(); + $this->assertDeletePermissions('faq.destroy', 'faq.delete', $faq, Faq::class); + }); + + test('can delete faq', function () { + $user = User::factory()->owner()->create(); + $faq = Faq::factory()->create(); - $this->actingAs($user) - ->deleteJson(route('faq.destroy', $faq)) - ->assertOk(); + $this->actingAs($user) + ->deleteJson(route('faq.destroy', $faq)) + ->assertOk(); - $this->assertDatabaseMissing('faqs', $faq->toArray()); + $this->assertDatabaseMissing('faqs', $faq->toArray()); + }); }); diff --git a/tests/Feature/Http/Controllers/PermissionControllerTest.php b/tests/Feature/Http/Controllers/PermissionControllerTest.php index fdd9827..ea51338 100644 --- a/tests/Feature/Http/Controllers/PermissionControllerTest.php +++ b/tests/Feature/Http/Controllers/PermissionControllerTest.php @@ -5,117 +5,129 @@ use Spatie\Permission\Models\Permission; use Spatie\Permission\Models\Role; -test('owner can get templates', function () { - $user = User::factory()->owner()->create(); - - $response = $this->actingAs($user) - ->get(route('permission.template')) - ->assertOk() - ->assertJsonStructure([ - '*' => [], - ]); - - $data = $response->json(); - expect($data)->toHaveKeys(CreatePermissionRequest::$models); - foreach (CreatePermissionRequest::$models as $model) { - expect($data[$model])->toContain(...CreatePermissionRequest::$operations); - } - - foreach (CreatePermissionRequest::$specialPermissions as $model => $specialPermissions) { - expect($data[$model])->toContain(...$specialPermissions); - } +use function PHPUnit\Framework\assertFalse; + +describe('read template operations', function () { + test('can read templates', function () { + $user = User::factory()->owner()->create(); + + $response = $this->actingAs($user) + ->get(route('permission.template')) + ->assertOk() + ->assertJsonStructure([ + '*' => [], + ]); + + $data = $response->json(); + expect($data)->toHaveKeys(CreatePermissionRequest::$models); + foreach (CreatePermissionRequest::$models as $model) { + expect($data[$model])->toContain(...CreatePermissionRequest::$operations); + } + + foreach (CreatePermissionRequest::$specialPermissions as $model => $specialPermissions) { + expect($data[$model])->toContain(...$specialPermissions); + } + }); + + test('can not read without permission', function () { + $user = User::factory()->create(); + assertFalse($user->can('permission.read')); + + $this->actingAs($user) + ->get(route('permission.template')) + ->assertForbidden(); + }); }); -test('none owner can not get templates', function () { - $user = User::factory()->create(); - - $this->actingAs($user) - ->get(route('permission.template')) - ->assertForbidden(); -}); - -test('owner can get permissions', function () { - $user = User::factory()->owner()->create(); - $role = Role::create(['name' => 'TestRole']); - $permission = Permission::where(['name' => 'faq.create'])->first(); - $role->givePermissionTo($permission); - - $this->actingAs($user) - ->get(route('permission.index')) - ->assertOk() - ->assertJsonFragment([ - 'role' => 'TestRole', - 'permissions' => ['faq.create'], - ]); -}); - -test('none owner can not get permissions', function () { - $user = User::factory()->create(); - $role = Role::create(['name' => 'TestRole']); - $permission = Permission::where(['name' => 'faq.create'])->first(); - $role->givePermissionTo($permission); - - $this->actingAs($user) - ->get(route('permission.index')) - ->assertForbidden(); +describe('read permission operations', function () { + test('owner can get permissions', function () { + $user = User::factory()->owner()->create(); + $role = Role::create(['name' => 'TestRole']); + $permission = Permission::where(['name' => 'faq.create'])->first(); + $role->givePermissionTo($permission); + + $this->actingAs($user) + ->get(route('permission.index')) + ->assertOk() + ->assertJsonFragment([ + 'role' => 'TestRole', + 'permissions' => ['faq.create'], + ]); + }); + + test('can not get permissions', function () { + $user = User::factory()->create(); + assertFalse($user->can('permission.read')); + + $role = Role::create(['name' => 'TestRole']); + $permission = Permission::where(['name' => 'faq.create'])->first(); + $role->givePermissionTo($permission); + + $this->actingAs($user) + ->get(route('permission.index')) + ->assertForbidden(); + }); }); -test('owner can create permissions', function () { - $user = User::factory()->owner()->create(); - $data = [ - [ - 'role' => 'Tester', - 'permissions' => [ - 'faq' => ['create' => true, 'read' => false], - 'rule' => ['read' => true], - 'serverContent' => ['resend' => true], +describe('create permission operations', function () { + test('owner can create permissions', function () { + $user = User::factory()->owner()->create(); + $data = [ + [ + 'role' => 'Tester', + 'permissions' => [ + 'faq' => ['create' => true, 'read' => false], + 'rule' => ['read' => true], + 'serverContent' => ['resend' => true], + ], ], - ], - [ - 'role' => 'Testing', - 'permissions' => [ - 'faq' => ['create' => true], + [ + 'role' => 'Testing', + 'permissions' => [ + 'faq' => ['create' => true], + ], ], - ], - ]; - - $this->actingAs($user) - ->post(route('permission.store'), ['permissions' => $data]) - ->assertOk(); - - $this->assertDatabaseHas('roles', ['name' => 'Tester']); - $this->assertDatabaseHas('roles', ['name' => 'Testing']); - - $managerRole = Role::where(['name' => 'Tester'])->first(); - expect($managerRole->hasPermissionTo('faq.create'))->toBeTrue(); - expect($managerRole->hasPermissionTo('rule.read'))->toBeTrue(); - expect($managerRole->hasPermissionTo('faq.read'))->toBeFalse(); - - $managerRole = Role::where(['name' => 'Testing'])->first(); - expect($managerRole->hasPermissionTo('faq.create'))->toBeTrue(); - expect($managerRole->hasPermissionTo('rule.read'))->toBeFalse(); - expect($managerRole->hasPermissionTo('faq.read'))->toBeFalse(); -}); - -test('none owner can not create permissions', function () { - $user = User::factory()->create(); - $data = [ - [ - 'role' => 'Tester', - 'permissions' => [ - 'faq' => ['create' => true, 'read' => false], - 'rule' => ['read' => true], + ]; + + $this->actingAs($user) + ->post(route('permission.store'), ['permissions' => $data]) + ->assertOk(); + + $this->assertDatabaseHas('roles', ['name' => 'Tester']); + $this->assertDatabaseHas('roles', ['name' => 'Testing']); + + $managerRole = Role::where(['name' => 'Tester'])->first(); + expect($managerRole->hasPermissionTo('faq.create'))->toBeTrue(); + expect($managerRole->hasPermissionTo('rule.read'))->toBeTrue(); + expect($managerRole->hasPermissionTo('faq.read'))->toBeFalse(); + + $managerRole = Role::where(['name' => 'Testing'])->first(); + expect($managerRole->hasPermissionTo('faq.create'))->toBeTrue(); + expect($managerRole->hasPermissionTo('rule.read'))->toBeFalse(); + expect($managerRole->hasPermissionTo('faq.read'))->toBeFalse(); + }); + + test('none owner can not create permissions', function () { + $user = User::factory()->create(); + assertFalse($user->can('permission.create')); + $data = [ + [ + 'role' => 'Tester', + 'permissions' => [ + 'faq' => ['create' => true, 'read' => false], + 'rule' => ['read' => true], + ], ], - ], - [ - 'role' => 'Testing', - 'permissions' => [ - 'faq' => ['create' => true], + [ + 'role' => 'Testing', + 'permissions' => [ + 'faq' => ['create' => true], + ], ], - ], - ]; + ]; - $this->actingAs($user) - ->post(route('permission.store'), ['permissions' => $data]) - ->assertForbidden(); + $this->actingAs($user) + ->post(route('permission.store'), ['permissions' => $data]) + ->assertForbidden(); + }); }); diff --git a/tests/Feature/Http/Controllers/ReactionRoleControllerTest.php b/tests/Feature/Http/Controllers/ReactionRoleControllerTest.php index 24cf8bc..6671104 100644 --- a/tests/Feature/Http/Controllers/ReactionRoleControllerTest.php +++ b/tests/Feature/Http/Controllers/ReactionRoleControllerTest.php @@ -3,23 +3,11 @@ use App\Models\ReactionRole; use App\Models\User; use Illuminate\Support\Facades\Http; +use Tests\Traits\CrudPermissionTrait; -test('auth user can get reaction roles', function () { - Http::fake([ - config('services.discord.api_url').'/guilds/*/roles' => Http::response([]), - ]); - - $reactionRole = ReactionRole::factory()->create(); - $user = User::factory()->owner()->create(); - - $this->actingAs($user) - ->get(route('reaction-role.index')) - ->assertOk() - ->assertJsonCount(1, 'data') - ->assertJsonPath('data.0.id', $reactionRole->id); -}); +pest()->use(CrudPermissionTrait::class); -test('can create rule', function () { +beforeEach(function () { config(['services.discord.server_id' => '123']); Http::fake([ config('services.discord.api_url').'/guilds/*/roles/*' => Http::response([]), @@ -27,68 +15,109 @@ config('services.discord.api_url').'/channels/*' => Http::response([]), config('services.discord.api_url').'/guilds/*/emojis' => Http::response([['id' => '123']]), ]); - - $user = User::factory()->owner()->create(); - $data = [ - 'message_link' => 'https://discord.com/channels/123/456/789', - 'emoji' => '', - 'role_id' => '1234', - ]; - - $this->actingAs($user) - ->postJson(route('reaction-role.store'), $data) - ->assertCreated() - ->assertJson(['data' => $data]); - - $this->assertDatabaseHas('reaction_roles', [ - 'channel_id' => '456', - 'message_id' => '789', - 'emoji' => '', - 'role_id' => '1234', - ]); }); -test('can update rule', function () { - config(['services.discord.server_id' => '123']); - Http::fake([ - config('services.discord.api_url').'/guilds/*/roles/*' => Http::response([]), - config('services.discord.api_url').'/guilds/*/roles' => Http::response([]), - config('services.discord.api_url').'/channels/*' => Http::response([]), - config('services.discord.api_url').'/guilds/*/emojis' => Http::response([['id' => '123']]), - ]); - - $reactionRole = ReactionRole::factory()->create(); - $user = User::factory()->owner()->create(); - $data = [ - 'message_link' => 'https://discord.com/channels/123/456/789', - 'emoji' => '', - 'role_id' => '1234', - ]; - - $this->actingAs($user) - ->patchJson(route('reaction-role.update', $reactionRole), $data) - ->assertOk() - ->assertJson(['data' => $data]); - - $this->assertDatabaseHas('reaction_roles', [ - 'channel_id' => '456', - 'message_id' => '789', - 'emoji' => '', - 'role_id' => '1234', - ]); +describe('read operations', function () { + test('read permission', function () { + ReactionRole::factory()->create(); + $this->assertReadPermissions('reaction-role.index', 'reactionRole.read'); + }); + + test('auth user can get reaction roles', function () { + $reactionRole = ReactionRole::factory()->create(); + $user = User::factory()->owner()->create(); + + $this->actingAs($user) + ->get(route('reaction-role.index')) + ->assertOk() + ->assertJsonCount(1, 'data') + ->assertJsonPath('data.0.id', $reactionRole->id); + }); }); -test('can delete rule', function () { - Http::fake([ - config('services.discord.api_url').'/channels/*' => Http::response([]), - ]); - - $user = User::factory()->owner()->create(); - $reactionRole = ReactionRole::factory()->create(); +describe('create operations', function () { + test('create permission', function () { + $data = [ + 'message_link' => 'https://discord.com/channels/123/456/789', + 'emoji' => '', + 'role_id' => '1234', + ]; + $assertData = [ + 'channel_id' => '456', + 'message_id' => '789', + 'emoji' => '', + 'role_id' => '1234', + ]; + $this->assertCreatePermissions('reaction-role.store', 'reactionRole.create', $data, ReactionRole::class, $assertData); + }); + + test('can create rule', function () { + $user = User::factory()->owner()->create(); + $data = [ + 'message_link' => 'https://discord.com/channels/123/456/789', + 'emoji' => '', + 'role_id' => '1234', + ]; + + $this->actingAs($user) + ->postJson(route('reaction-role.store'), $data) + ->assertCreated() + ->assertJson(['data' => $data]); + + $this->assertDatabaseHas('reaction_roles', [ + 'channel_id' => '456', + 'message_id' => '789', + 'emoji' => '', + 'role_id' => '1234', + ]); + }); +}); - $this->actingAs($user) - ->deleteJson(route('reaction-role.destroy', $reactionRole)) - ->assertOk(); +describe('update operations', function () { + test('update permission', function () { + $reactionRole = ReactionRole::factory()->create(); + $data = [ + 'role_id' => '1234', + ]; + $this->assertUpdatePermissions('reaction-role.update', 'reactionRole.update', $reactionRole, $data, ReactionRole::class); + }); + + test('can update rule', function () { + $reactionRole = ReactionRole::factory()->create(); + $user = User::factory()->owner()->create(); + $data = [ + 'message_link' => 'https://discord.com/channels/123/456/789', + 'emoji' => '', + 'role_id' => '1234', + ]; + + $this->actingAs($user) + ->patchJson(route('reaction-role.update', $reactionRole), $data) + ->assertOk() + ->assertJson(['data' => $data]); + + $this->assertDatabaseHas('reaction_roles', [ + 'channel_id' => '456', + 'message_id' => '789', + 'emoji' => '', + 'role_id' => '1234', + ]); + }); +}); - $this->assertDatabaseMissing('reaction_roles', $reactionRole->toArray()); +describe('delete operations', function () { + test('delete permission', function () { + $reactionRole = ReactionRole::factory()->create(); + $this->assertDeletePermissions('reaction-role.destroy', 'reactionRole.delete', $reactionRole, ReactionRole::class); + }); + test('can delete rule', function () { + $user = User::factory()->owner()->create(); + $reactionRole = ReactionRole::factory()->create(); + + $this->actingAs($user) + ->deleteJson(route('reaction-role.destroy', $reactionRole)) + ->assertOk(); + + $this->assertDatabaseMissing('reaction_roles', $reactionRole->toArray()); + }); }); diff --git a/tests/Feature/Http/Controllers/RuleControllerTest.php b/tests/Feature/Http/Controllers/RuleControllerTest.php index 8578dd0..c2bec06 100644 --- a/tests/Feature/Http/Controllers/RuleControllerTest.php +++ b/tests/Feature/Http/Controllers/RuleControllerTest.php @@ -2,58 +2,96 @@ use App\Models\Rule; use App\Models\User; +use Tests\Traits\CrudPermissionTrait; -test('auth user can get rules', function () { - $rule = Rule::factory()->create(); - $user = User::factory()->owner()->create(); +pest()->use(CrudPermissionTrait::class); - $this->actingAs($user) - ->get(route('rule.index')) - ->assertOk() - ->assertJsonCount(1, 'data') - ->assertJsonPath('data.0.id', $rule->id); +describe('read operations', function () { + test('read permission', function () { + Rule::factory()->create(); + $this->assertReadPermissions('rule.index', 'rule.read'); + }); + + test('can read rules', function () { + $rule = Rule::factory()->create(); + $user = User::factory()->owner()->create(); + + $this->actingAs($user) + ->get(route('rule.index')) + ->assertOk() + ->assertJsonCount(1, 'data') + ->assertJsonPath('data.0.id', $rule->id); + }); }); -test('can create rule', function () { - $user = User::factory()->owner()->create(); - $data = [ - 'number' => 1, - 'name' => 'Testing', - 'rule' => 'This has to be tested!', - ]; +describe('create operations', function () { + test('create permission', function () { + $data = [ + 'number' => 1, + 'name' => 'Testing', + 'rule' => 'This has to be tested!', + ]; + $this->assertCreatePermissions('rule.store', 'rule.create', $data, Rule::class); + }); - $this->actingAs($user) - ->postJson(route('rule.store'), $data) - ->assertCreated() - ->assertJson(['data' => $data]); + test('can create rule', function () { + $user = User::factory()->owner()->create(); + $data = [ + 'number' => 1, + 'name' => 'Testing', + 'rule' => 'This has to be tested!', + ]; - $this->assertDatabaseHas('rules', $data); + $this->actingAs($user) + ->postJson(route('rule.store'), $data) + ->assertCreated() + ->assertJson(['data' => $data]); + + $this->assertDatabaseHas('rules', $data); + }); }); -test('can update rule', function () { - $user = User::factory()->owner()->create(); - $rule = Rule::factory()->create(); - $data = [ - 'number' => 1, - 'name' => 'Testing', - 'rule' => 'This has to be tested!', - ]; - - $this->actingAs($user) - ->patchJson(route('rule.update', $rule), $data) - ->assertOk() - ->assertJson(['data' => $data]); - - $this->assertDatabaseHas('rules', $data); +describe('update operations', function () { + test('update permission', function () { + $rule = Rule::factory()->create(); + $data = [ + 'number' => 1, + ]; + $this->assertUpdatePermissions('rule.update', 'rule.update', $rule, $data, Rule::class); + }); + + test('can update rule', function () { + $user = User::factory()->owner()->create(); + $rule = Rule::factory()->create(); + $data = [ + 'number' => 1, + 'name' => 'Testing', + 'rule' => 'This has to be tested!', + ]; + + $this->actingAs($user) + ->patchJson(route('rule.update', $rule), $data) + ->assertOk() + ->assertJson(['data' => $data]); + + $this->assertDatabaseHas('rules', $data); + }); }); -test('can delete rule', function () { - $user = User::factory()->owner()->create(); - $rule = Rule::factory()->create(); +describe('delete operations', function () { + test('delete permission', function () { + $rule = Rule::factory()->create(); + $this->assertDeletePermissions('rule.destroy', 'rule.delete', $rule, Rule::class); + }); + + test('can delete rule', function () { + $user = User::factory()->owner()->create(); + $rule = Rule::factory()->create(); - $this->actingAs($user) - ->deleteJson(route('rule.destroy', $rule)) - ->assertOk(); + $this->actingAs($user) + ->deleteJson(route('rule.destroy', $rule)) + ->assertOk(); - $this->assertDatabaseMissing('rules', $rule->toArray()); + $this->assertDatabaseMissing('rules', $rule->toArray()); + }); }); diff --git a/tests/Feature/Http/Controllers/ServerContentControllerTest.php b/tests/Feature/Http/Controllers/ServerContentControllerTest.php index a2dda63..cc0c951 100644 --- a/tests/Feature/Http/Controllers/ServerContentControllerTest.php +++ b/tests/Feature/Http/Controllers/ServerContentControllerTest.php @@ -1,63 +1,181 @@ create(); - $user = User::factory()->owner()->create(); +use function PHPUnit\Framework\assertEquals; +use function PHPUnit\Framework\assertFalse; +use function PHPUnit\Framework\assertTrue; - $this->actingAs($user) - ->get(route('server-content.index')) - ->assertOk() - ->assertJsonCount(1, 'data') - ->assertJsonPath('data.0.id', $serverContent->id); +pest()->use(CrudPermissionTrait::class); + +beforeEach(function () { + $this->serverId = '123'; + config(['services.discord.server_id' => $this->serverId]); }); -test('can create server content', function () { - $user = User::factory()->owner()->create(); - $data = [ - 'name' => 'Test', - 'url' => 'https://example.com', - 'description' => 'Test Content', - 'is_recommended' => true, - 'is_active' => true, - ]; - - $this->actingAs($user) - ->postJson(route('server-content.store'), $data) - ->assertCreated() - ->assertJson(['data' => $data]); - - $this->assertDatabaseHas('server_contents', $data); +describe('read operations', function () { + test('read permission', function () { + ServerContent::factory()->create(); + $this->assertReadPermissions('server-content.index', 'serverContent.read'); + }); + + test('auth user can get server contents', function () { + $serverContent = ServerContent::factory()->create(); + $user = User::factory()->owner()->create(); + + $this->actingAs($user) + ->get(route('server-content.index')) + ->assertOk() + ->assertJsonCount(1, 'data') + ->assertJsonPath('data.0.id', $serverContent->id); + }); }); -test('can update server content', function () { - $user = User::factory()->owner()->create(); - $serverContent = ServerContent::factory()->create(); - $data = [ - 'name' => 'Test', - 'url' => 'https://example.com', - 'description' => 'Test Content', - 'is_recommended' => true, - 'is_active' => true, - ]; - - $this->actingAs($user) - ->patchJson(route('server-content.update', $serverContent), $data) - ->assertOk() - ->assertJson(['data' => $data]); - - $this->assertDatabaseHas('server_contents', $data); +describe('create operations', function () { + test('create permission', function () { + $data = [ + 'name' => 'Test', + 'url' => 'https://example.com', + 'description' => 'Test Content', + 'is_recommended' => true, + 'is_active' => true, + ]; + $this->assertCreatePermissions('server-content.store', 'serverContent.create', $data, ServerContent::class); + }); + + test('can create server content', function () { + $user = User::factory()->owner()->create(); + $data = [ + 'name' => 'Test', + 'url' => 'https://example.com', + 'description' => 'Test Content', + 'is_recommended' => true, + 'is_active' => true, + ]; + + $this->actingAs($user) + ->postJson(route('server-content.store'), $data) + ->assertCreated() + ->assertJson(['data' => $data]); + + $this->assertDatabaseHas('server_contents', $data); + }); }); -test('can delete server content', function () { - $user = User::factory()->owner()->create(); - $serverContent = ServerContent::factory()->create(); +describe('update operations', function () { + test('update permission', function () { + $serverContent = ServerContent::factory()->create(); + $data = [ + 'name' => 'Test', + ]; + $this->assertUpdatePermissions('server-content.update', 'serverContent.update', $serverContent, $data, ServerContent::class); + }); + + test('can update server content', function () { + $user = User::factory()->owner()->create(); + $serverContent = ServerContent::factory()->create(); + $data = [ + 'name' => 'Test', + 'url' => 'https://example.com', + 'description' => 'Test Content', + 'is_recommended' => true, + 'is_active' => true, + ]; + + $this->actingAs($user) + ->patchJson(route('server-content.update', $serverContent), $data) + ->assertOk() + ->assertJson(['data' => $data]); + + $this->assertDatabaseHas('server_contents', $data); + }); +}); + +describe('delete operations', function () { + test('delete permission', function () { + $serverContent = ServerContent::factory()->create(); + $this->assertDeletePermissions('server-content.destroy', 'serverContent.delete', $serverContent, ServerContent::class); + }); + + test('can delete server content', function () { + $user = User::factory()->owner()->create(); + $serverContent = ServerContent::factory()->create(); + + $this->actingAs($user) + ->deleteJson(route('server-content.destroy', $serverContent)) + ->assertOk(); + + $this->assertDatabaseMissing('server_contents', $serverContent->toArray()); + }); +}); + +describe('resend operations', function () { + test('resend permission', function () { + Http::fake(); + $user = User::factory()->create(); + ServerContentMessage::create([ + 'server_id' => $this->serverId, + 'heading' => 'heading', + 'not_recommended' => 'not_recommended', + 'recommended' => 'recommended', + ]); + + $route = 'server-content.resend'; + $permission = 'serverContent.resend'; + $data = [ + 'channel_id' => '321', + ]; + + assertFalse($user->can($permission)); + + $this->actingAs($user) + ->postJson(route($route, $data)) + ->assertForbidden(); + + $user->givePermissionTo($permission); + $this->actingAs($user) + ->postJson(route($route, $data)) + ->assertOk(); + }); + + test('does not resend server content without messages', function () { + $user = User::factory()->owner()->create(); + assertEquals(0, ServerContentMessage::where('server_id', $this->serverId)->count()); + + $this->actingAs($user) + ->postJson(route('server-content.resend'), [ + 'channel_id' => '321', + ]) + ->assertStatus(400); + }); + + test('resend server content', function () { + Http::fake(); + $user = User::factory()->owner()->create(); + $serverContentMessage = ServerContentMessage::create([ + 'server_id' => $this->serverId, + 'heading' => 'heading', + 'not_recommended' => 'not_recommended', + 'recommended' => 'recommended', + ]); + ServerContent::factory()->active()->recommended()->create(); + ServerContent::factory()->active()->notRecommended()->create(); + + $this->actingAs($user) + ->postJson(route('server-content.resend'), [ + 'channel_id' => '321', + ]) + ->assertOk(); + + Http::assertSentCount(3); - $this->actingAs($user) - ->deleteJson(route('server-content.destroy', $serverContent)) - ->assertOk(); + $contents = collect(Http::recorded())->map(fn ($record) => $record[0]['content']); - $this->assertDatabaseMissing('server_contents', $serverContent->toArray()); + assertTrue(str_contains($contents[0], $serverContentMessage->heading)); + assertTrue(str_contains($contents[1], $serverContentMessage->not_recommended)); + assertTrue(str_contains($contents[2], $serverContentMessage->recommended)); + }); }); diff --git a/tests/Feature/Http/Controllers/ServerContentMessageControllerTest.php b/tests/Feature/Http/Controllers/ServerContentMessageControllerTest.php new file mode 100644 index 0000000..8bc3403 --- /dev/null +++ b/tests/Feature/Http/Controllers/ServerContentMessageControllerTest.php @@ -0,0 +1,127 @@ +serverId = '100000000000000000'; + config(['services.discord.server_id' => $this->serverId]); +}); + +describe('read operations', function () { + test('read permission', function () { + $user = User::factory()->create(); + $route = 'server-content-message.index'; + $permission = 'serverContentMessage.read'; + + assertFalse($user->can($permission)); + + $this->actingAs($user) + ->get(route($route)) + ->assertForbidden(); + + $user->givePermissionTo($permission); + $this->actingAs($user) + ->get(route($route)) + ->assertOk(); + }); + + test('can read empty message', function () { + $user = User::factory()->owner()->create(); + + $response = $this->actingAs($user) + ->get(route('server-content-message.index')) + ->assertOk(); + assertEquals('', $response->getContent()); + }); + + test('can read message', function () { + $user = User::factory()->owner()->create(); + new ServerContentMessage([ + 'heading' => 'heading', + 'not_recommended' => 'not_recommended', + 'recommended' => 'recommended', + 'server_id' => $this->serverId, + ])->save(); + + $this->actingAs($user) + ->get(route('server-content-message.index')) + ->assertOk() + ->assertJson([ + 'heading' => 'heading', + 'not_recommended' => 'not_recommended', + 'recommended' => 'recommended', + ]); + }); +}); + +describe('upsert operations', function () { + test('upsert permission', function () { + $user = User::factory()->create(); + $data = [ + 'heading' => 'heading', + 'not_recommended' => 'not_recommended', + 'recommended' => 'recommended', + ]; + $route = 'server-content-message.store'; + $permission = 'serverContentMessage.create'; + + assertFalse($user->can($permission)); + + $this->actingAs($user) + ->postJson(route($route), $data) + ->assertForbidden(); + + $user->givePermissionTo($permission); + $this->actingAs($user) + ->postJson(route($route), $data) + ->assertCreated(); + }); + + test('can create', function () { + $user = User::factory()->owner()->create(); + $data = [ + 'heading' => 'heading', + 'not_recommended' => 'not_recommended', + 'recommended' => 'recommended', + ]; + + $this->actingAs($user) + ->postJson(route('server-content-message.store'), $data) + ->assertCreated() + ->assertJson($data); + $this->assertDatabaseHas(ServerContentMessage::class, $data); + }); + + test('can update', function () { + $user = User::factory()->owner()->create(); + new ServerContentMessage([ + 'heading' => 'heading', + 'not_recommended' => 'not_recommended', + 'recommended' => 'recommended', + 'server_id' => $this->serverId, + ])->save(); + + $data = [ + 'heading' => 'Update', + 'not_recommended' => 'Update', + 'recommended' => 'Update', + ]; + + $this->actingAs($user) + ->postJson(route('server-content-message.store'), $data) + ->assertCreated() + ->assertJson($data); + + $this->assertDatabaseHas(ServerContentMessage::class, $data); + + $this->assertDatabaseMissing(ServerContentMessage::class, [ + 'heading' => 'heading', + 'not_recommended' => 'not_recommended', + 'recommended' => 'recommended', + ]); + }); +}); diff --git a/tests/Feature/Http/Controllers/TicketButtonControllerTest.php b/tests/Feature/Http/Controllers/TicketButtonControllerTest.php index 3f65282..da7de08 100644 --- a/tests/Feature/Http/Controllers/TicketButtonControllerTest.php +++ b/tests/Feature/Http/Controllers/TicketButtonControllerTest.php @@ -5,78 +5,124 @@ use App\Models\TicketPanel; use App\Models\TicketTeam; use App\Models\User; +use Tests\Traits\CrudPermissionTrait; -test('auth user can get ticket buttons', function () { - $ticketButton = TicketButton::factory()->create(); - $user = User::factory()->owner()->create(); +pest()->use(CrudPermissionTrait::class); - $this->actingAs($user) - ->get(route('button.index')) - ->assertOk() - ->assertJsonCount(1, 'data') - ->assertJsonPath('data.0.id', $ticketButton->id); +describe('read operations', function () { + test('read permission', function () { + TicketButton::factory()->create(); + $this->assertReadPermissions('button.index', 'ticketButton.read'); + }); + + test('can read ticket buttons', function () { + $ticketButton = TicketButton::factory()->create(); + $user = User::factory()->owner()->create(); + + $this->actingAs($user) + ->get(route('button.index')) + ->assertOk() + ->assertJsonCount(1, 'data') + ->assertJsonPath('data.0.id', $ticketButton->id); + }); }); -test('can create ticket buttons', function () { - $user = User::factory()->owner()->create(); - $panel = TicketPanel::factory()->create(); - $team = TicketTeam::factory()->create(); - $data = [ - 'ticket_team_id' => $team->id, - 'ticket_panel_id' => $panel->id, - 'text' => 'Test', - 'color' => DiscordButton::Success->value, - 'initial_message' => 'Test', - 'emoji' => '⚠️', - 'naming_scheme' => '%id%-Channel', - 'disabled' => false, - 'ticket_button_ping_role_ids' => ['123', '456'], - ]; - - $this->actingAs($user) - ->postJson(route('button.store'), $data) - ->assertCreated() - ->assertJson(['data' => collect($data)->except('ticket_button_ping_role_ids')->toArray()]); - - $this->assertDatabaseHas('ticket_buttons', collect($data)->except('ticket_button_ping_role_ids')->toArray()); - - expect(TicketButton::count())->toBe(1); - expect(TicketButton::first()->ticketButtonPingRoles->map(fn ($ticketButtonPingRole) => $ticketButtonPingRole->role_id)->toArray())->toBe(['123', '456']); +describe('create operations', function () { + test('create permission', function () { + $panel = TicketPanel::factory()->create(); + $team = TicketTeam::factory()->create(); + $data = [ + 'ticket_team_id' => $team->id, + 'ticket_panel_id' => $panel->id, + 'text' => 'Test', + 'color' => DiscordButton::Success->value, + 'initial_message' => 'Test', + 'emoji' => '⚠️', + 'naming_scheme' => '%id%-Channel', + 'disabled' => false, + 'ticket_button_ping_role_ids' => ['123', '456'], + ]; + $this->assertCreatePermissions('button.store', 'ticketButton.create', $data, TicketButton::class, collect($data)->except('ticket_button_ping_role_ids')->toArray()); + }); + + test('can create ticket buttons', function () { + $user = User::factory()->owner()->create(); + $panel = TicketPanel::factory()->create(); + $team = TicketTeam::factory()->create(); + $data = [ + 'ticket_team_id' => $team->id, + 'ticket_panel_id' => $panel->id, + 'text' => 'Test', + 'color' => DiscordButton::Success->value, + 'initial_message' => 'Test', + 'emoji' => '⚠️', + 'naming_scheme' => '%id%-Channel', + 'disabled' => false, + 'ticket_button_ping_role_ids' => ['123', '456'], + ]; + + $this->actingAs($user) + ->postJson(route('button.store'), $data) + ->assertCreated() + ->assertJson(['data' => collect($data)->except('ticket_button_ping_role_ids')->toArray()]); + + $this->assertDatabaseHas('ticket_buttons', collect($data)->except('ticket_button_ping_role_ids')->toArray()); + + expect(TicketButton::count())->toBe(1); + expect(TicketButton::first()->ticketButtonPingRoles->map(fn ($ticketButtonPingRole) => $ticketButtonPingRole->role_id)->toArray())->toBe(['123', '456']); + }); }); -test('can update ticket buttons', function () { - $user = User::factory()->owner()->create(); - $ticketButton = TicketButton::factory()->create(); - $panel = TicketPanel::factory()->create(); - $team = TicketTeam::factory()->create(); - $data = [ - 'ticket_team_id' => $team->id, - 'ticket_panel_id' => $panel->id, - 'text' => 'Test', - 'color' => DiscordButton::Success->value, - 'initial_message' => 'Test', - 'emoji' => '⚠️', - 'naming_scheme' => '%id%-Channel', - 'disabled' => false, - 'ticket_button_ping_role_ids' => ['123', '456'], - ]; - - $this->actingAs($user) - ->patchJson(route('button.update', $ticketButton), $data) - ->assertOk() - ->assertJson(['data' => collect($data)->except('ticket_button_ping_role_ids')->toArray()]); - - $this->assertDatabaseHas('ticket_buttons', collect($data)->except('ticket_button_ping_role_ids')->toArray()); - expect($ticketButton->ticketButtonPingRoles->map(fn ($ticketButtonPingRole) => $ticketButtonPingRole->role_id)->toArray())->toBe(['123', '456']); +describe('update operations', function () { + test('update permission', function () { + $ticketButton = TicketButton::factory()->create(); + $data = [ + 'text' => 'Test', + ]; + $this->assertUpdatePermissions('button.update', 'ticketButton.update', $ticketButton, $data, TicketButton::class); + }); + + test('can update ticket buttons', function () { + $user = User::factory()->owner()->create(); + $ticketButton = TicketButton::factory()->create(); + $panel = TicketPanel::factory()->create(); + $team = TicketTeam::factory()->create(); + $data = [ + 'ticket_team_id' => $team->id, + 'ticket_panel_id' => $panel->id, + 'text' => 'Test', + 'color' => DiscordButton::Success->value, + 'initial_message' => 'Test', + 'emoji' => '⚠️', + 'naming_scheme' => '%id%-Channel', + 'disabled' => false, + 'ticket_button_ping_role_ids' => ['123', '456'], + ]; + + $this->actingAs($user) + ->patchJson(route('button.update', $ticketButton), $data) + ->assertOk() + ->assertJson(['data' => collect($data)->except('ticket_button_ping_role_ids')->toArray()]); + + $this->assertDatabaseHas('ticket_buttons', collect($data)->except('ticket_button_ping_role_ids')->toArray()); + expect($ticketButton->ticketButtonPingRoles->map(fn ($ticketButtonPingRole) => $ticketButtonPingRole->role_id)->toArray())->toBe(['123', '456']); + }); }); -test('can delete ticket buttons', function () { - $user = User::factory()->owner()->create(); - $ticketButton = TicketButton::factory()->create(); +describe('delete operations', function () { + test('delete permission', function () { + $ticketButton = TicketButton::factory()->create(); + $this->assertDeletePermissions('button.destroy', 'ticketButton.delete', $ticketButton, TicketButton::class); + }); + + test('can delete ticket buttons', function () { + $user = User::factory()->owner()->create(); + $ticketButton = TicketButton::factory()->create(); - $this->actingAs($user) - ->deleteJson(route('button.destroy', $ticketButton)) - ->assertOk(); + $this->actingAs($user) + ->deleteJson(route('button.destroy', $ticketButton)) + ->assertOk(); - $this->assertDatabaseMissing('ticket_buttons', $ticketButton->toArray()); + $this->assertDatabaseMissing('ticket_buttons', $ticketButton->toArray()); + }); }); diff --git a/tests/Feature/Http/Controllers/TicketConfigControllerTest.php b/tests/Feature/Http/Controllers/TicketConfigControllerTest.php index 4fb3b65..64cfcad 100644 --- a/tests/Feature/Http/Controllers/TicketConfigControllerTest.php +++ b/tests/Feature/Http/Controllers/TicketConfigControllerTest.php @@ -2,49 +2,111 @@ use App\Models\TicketConfig; use App\Models\User; +use Tests\Traits\CrudPermissionTrait; -test('auth user can get ticket configs', function () { - $ticketConfig = TicketConfig::factory()->create(); - config(['services.discord.server_id' => $ticketConfig->guild_id]); - $user = User::factory()->owner()->create(); +use function PHPUnit\Framework\assertFalse; - $this->actingAs($user) - ->get(route('config.index')) - ->assertOk() - ->assertJsonPath('data.id', $ticketConfig->id); +pest()->use(CrudPermissionTrait::class); + +describe('read operations', function () { + test('read permission', function () { + TicketConfig::factory()->create(); + $this->assertReadPermissions('config.index', 'ticketConfig.read'); + }); + + test('can read ticket configs', function () { + $ticketConfig = TicketConfig::factory()->create(); + config(['services.discord.server_id' => $ticketConfig->guild_id]); + $user = User::factory()->owner()->create(); + + $this->actingAs($user) + ->get(route('config.index')) + ->assertOk() + ->assertJsonPath('data.id', $ticketConfig->id); + }); }); -test('can create ticket config', function () { - $user = User::factory()->owner()->create(); - config(['services.discord.server_id' => '100000000000000000']); - $data = [ - 'guild_id' => '100000000000000000', - 'category_id' => '100000000000000001', - 'transcript_channel_id' => '100000000000000002', - ]; - - $this->actingAs($user) - ->postJson(route('config.store'), $data) - ->assertCreated() - ->assertJson(['data' => $data]); - - $this->assertDatabaseHas('ticket_configs', $data); +describe('create operations', function () { + test('create permission', function () { + config(['services.discord.server_id' => '100000000000000000']); + $data = [ + 'guild_id' => '100000000000000000', + 'category_id' => '100000000000000001', + 'transcript_channel_id' => '100000000000000002', + ]; + $this->assertCreatePermissions('config.store', 'ticketConfig.create', $data, TicketConfig::class); + }); + + test('can create ticket config', function () { + $user = User::factory()->owner()->create(); + config(['services.discord.server_id' => '100000000000000000']); + $data = [ + 'guild_id' => '100000000000000000', + 'category_id' => '100000000000000001', + 'transcript_channel_id' => '100000000000000002', + ]; + + $this->actingAs($user) + ->postJson(route('config.store'), $data) + ->assertCreated() + ->assertJson(['data' => $data]); + + $this->assertDatabaseHas('ticket_configs', $data); + }); }); -test('can update ticket config', function () { - $user = User::factory()->owner()->create(); - $ticketConfig = TicketConfig::factory()->create(); - config(['services.discord.server_id' => $ticketConfig->guild_id]); - $data = [ - 'guild_id' => $ticketConfig->guild_id, - 'category_id' => '100000000000000001', - 'transcript_channel_id' => '100000000000000002', - ]; - - $this->actingAs($user) - ->postJson(route('config.store'), $data) - ->assertCreated() - ->assertJson(['data' => $data]); - - $this->assertDatabaseHas('ticket_configs', $data); +describe('update operations', function () { + test('create permission', function () { + $ticketConfig = TicketConfig::factory()->create(); + config(['services.discord.server_id' => $ticketConfig->guild_id]); + $data = [ + 'guild_id' => $ticketConfig->guild_id, + 'category_id' => '100000000000000001', + 'transcript_channel_id' => '100000000000000002', + ]; + $this->assertCreatePermissions('config.store', 'ticketConfig.create', $data, TicketConfig::class); + }); + test('can update ticket config', function () { + $user = User::factory()->owner()->create(); + $ticketConfig = TicketConfig::factory()->create(); + config(['services.discord.server_id' => $ticketConfig->guild_id]); + $data = [ + 'guild_id' => $ticketConfig->guild_id, + 'category_id' => '100000000000000001', + 'transcript_channel_id' => '100000000000000002', + ]; + + $this->actingAs($user) + ->postJson(route('config.store'), $data) + ->assertCreated() + ->assertJson(['data' => $data]); + + $this->assertDatabaseHas('ticket_configs', $data); + }); +}); +describe('setup operations', function () { + test('setup permission', function () { + Http::fake(); + $user = User::factory()->create(); + + $route = 'config.setup'; + $permission = 'ticketConfig.create'; + $data = [ + 'category_id' => '123', + 'transcript_channel_id' => '123', + 'create_channel_id' => '123', + 'guild_id' => '123', + ]; + + assertFalse($user->can($permission)); + + $this->actingAs($user) + ->postJson(route($route, $data)) + ->assertForbidden(); + + $user->givePermissionTo($permission); + $this->actingAs($user) + ->postJson(route($route, $data)) + ->assertOk(); + }); }); diff --git a/tests/Feature/Http/Controllers/TicketControllerTest.php b/tests/Feature/Http/Controllers/TicketControllerTest.php index 90f1e19..a63d62f 100644 --- a/tests/Feature/Http/Controllers/TicketControllerTest.php +++ b/tests/Feature/Http/Controllers/TicketControllerTest.php @@ -6,35 +6,13 @@ use App\Models\TicketConfig; use App\Models\User; use Illuminate\Support\Facades\Http; +use Tests\Traits\CrudPermissionTrait; -test('auth user can get tickets', function () { - Http::fake([ - config('services.discord.api_url').'/users/*' => Http::response([]), - ]); - - $ticket = Ticket::factory()->create(); - $user = User::factory()->owner()->create(); +use function PHPUnit\Framework\assertFalse; - $this->actingAs($user) - ->get(route('ticket.index')) - ->assertOk() - ->assertJson(['data' => [['id' => $ticket->id]]]); -}); - -test('none super user can not create tickets', function () { - $user = User::factory()->create(); - $button = TicketButton::factory()->create(); - $data = [ - 'ticket_button_id' => $button->id, - 'created_by_discord_user_id' => '100000000000000000', - ]; - - $this->actingAs($user) - ->postJson(route('ticket.store'), $data) - ->assertForbidden(); -}); +pest()->use(CrudPermissionTrait::class); -test('can create tickets', function () { +beforeEach(function () { config(['services.discord.server_id' => '100000000000000000']); TicketConfig::factory()->create(['guild_id' => '100000000000000000']); @@ -47,54 +25,125 @@ ]]), config('services.discord.api_url').'/guilds/*/channels' => Http::response(['id' => '123'], 201), config('services.discord.api_url').'/channels/*' => Http::response(['id' => '456']), + config('services.discord.api_url').'/channels/*' => Http::response([]), + config('services.discord.api_url').'/guilds/*/members/*' => Http::response(['id' => '123']), ]); +}); + +describe('read operations', function () { + test('read permission', function () { + Ticket::factory()->create(); + $this->assertReadPermissions('ticket.index', 'ticket.read'); + $this->assertReadPermissions('ticket.index', 'ticket.read-own'); + }); + + test('can read tickets', function () { + $ticket = Ticket::factory()->create(); + $user = User::factory()->owner()->create(); + + $this->actingAs($user) + ->get(route('ticket.index')) + ->assertOk() + ->assertJson(['data' => [['id' => $ticket->id]]]); + }); + + test('can read own tickets', function () { + $user = User::factory()->create(); + $ticket1 = Ticket::factory()->create(['created_by_discord_user_id' => '123']); + $ticket2 = Ticket::factory()->create(['created_by_discord_user_id' => $user->discord_id]); + $user->givePermissionTo('ticket.read-own'); + + $this->actingAs($user) + ->get(route('ticket.index')) + ->assertOk() + ->assertJsonCount(1, 'data') + ->assertJsonPath('data.0.id', $ticket2->id) + ->assertJsonMissing(['id' => $ticket1->id]); + }); +}); - $user = User::factory()->bot()->create(); - $button = TicketButton::factory()->create(); - $data = [ - 'ticket_button_id' => $button->id, - 'created_by_discord_user_id' => '100000000000000000', - ]; - - $this->actingAs($user) - ->postJson(route('ticket.store'), $data) - ->assertCreated() - ->assertJson(['data' => [ +describe('create operations', function () { + test('create permission', function () { + $button = TicketButton::factory()->create(); + $data = [ + 'ticket_button_id' => $button->id, + 'created_by_discord_user_id' => '100000000000000000', + ]; + $this->assertCreatePermissions('ticket.store', 'ticket.create', $data, Ticket::class); + }); + + test('can create tickets', function () { + $user = User::factory()->bot()->create(); + $button = TicketButton::factory()->create(); + $data = [ + 'ticket_button_id' => $button->id, + 'created_by_discord_user_id' => '100000000000000000', + ]; + + $this->actingAs($user) + ->postJson(route('ticket.store'), $data) + ->assertCreated() + ->assertJson(['data' => [ + ...$data, + 'channel_id' => '123', + 'state' => TicketState::Open->value, + ]]); + + $this->assertDatabaseHas('tickets', [ ...$data, 'channel_id' => '123', 'state' => TicketState::Open->value, - ]]); - - $this->assertDatabaseHas('tickets', [ - ...$data, - 'channel_id' => '123', - 'state' => TicketState::Open->value, - 'closed_by_discord_user_id' => null, - 'closed_reason' => null, - ]); + 'closed_by_discord_user_id' => null, + 'closed_reason' => null, + ]); + }); }); -test('can close tickets', function () { - Http::fake([ - config('services.discord.api_url').'/channels/*' => Http::response([]), - ]); +describe('close operations', function () { + test('close permission', function () { + $user = User::factory()->create(); + $ticket = Ticket::factory()->create(); + $data = [ + 'closed_by_discord_user_id' => '100000000000000001', + 'closed_reason' => 'Test', + ]; - config(['services.discord.server_id' => '100000000000000000']); - TicketConfig::factory()->create(['guild_id' => '100000000000000000']); - $user = User::factory()->owner()->create(); - $ticket = Ticket::factory()->create(); + $route = 'ticket.close'; + $permission = 'ticket.update'; + $table = Ticket::class; - $data = [ - 'closed_by_discord_user_id' => '100000000000000001', - 'closed_reason' => 'Test', - ]; + assertFalse($user->can($permission)); - $this->actingAs($user) - ->postJson(route('ticket.close', $ticket), $data) - ->assertOk(); + $this->actingAs($user) + ->postJson(route($route, $ticket), $data) + ->assertForbidden(); - $this->assertDatabaseHas('tickets', [ - 'id' => $ticket->id, - ...$data, - ]); + $this->assertDatabaseMissing($table, $data); + + $user->givePermissionTo($permission); + $this->actingAs($user) + ->postJson(route($route, $ticket), $data) + ->assertOk(); + + $this->assertDatabaseHas($table, $data); + }); + + test('can close tickets', function () { + $user = User::factory()->owner()->create(); + $ticket = Ticket::factory()->create(); + + $data = [ + 'closed_by_discord_user_id' => '100000000000000001', + 'closed_reason' => 'Test', + ]; + + $this->actingAs($user) + ->postJson(route('ticket.close', $ticket), $data) + ->assertOk(); + + $this->assertDatabaseHas('tickets', [ + 'id' => $ticket->id, + ...$data, + ]); + }); }); diff --git a/tests/Feature/Http/Controllers/TicketPanelControllerTest.php b/tests/Feature/Http/Controllers/TicketPanelControllerTest.php index 09d68ff..e9ed1cd 100644 --- a/tests/Feature/Http/Controllers/TicketPanelControllerTest.php +++ b/tests/Feature/Http/Controllers/TicketPanelControllerTest.php @@ -3,85 +3,138 @@ use App\Models\TicketPanel; use App\Models\User; use Illuminate\Support\Facades\Http; +use Tests\Traits\CrudPermissionTrait; -test('auth user can get ticket panels', function () { - Http::fake([ - config('services.discord.api_url').'/guilds/*/channels' => Http::response([]), - ]); +use function PHPUnit\Framework\assertFalse; - $ticketPanel = TicketPanel::factory()->create(); - $user = User::factory()->owner()->create(); - - $this->actingAs($user) - ->get(route('panel.index')) - ->assertOk() - ->assertJsonCount(1, 'data') - ->assertJsonPath('data.0.id', $ticketPanel->id); -}); +pest()->use(CrudPermissionTrait::class); -test('can create ticket panel', function () { +beforeEach(function () { Http::fake([ config('services.discord.api_url').'/guilds/*/channels' => Http::response([]), + config('services.discord.api_url').'/channels/*' => Http::response([]), ]); +}); - $user = User::factory()->owner()->create(); - $data = [ - 'title' => 'Test', - 'message' => 'Test', - 'embed_color' => '#012345', - 'channel_id' => '100000000000000000', - ]; +describe('read operations', function () { + test('read permission', function () { + TicketPanel::factory()->create(); + $this->assertReadPermissions('panel.index', 'ticketPanel.read'); + }); + + test('can read ticket panels', function () { + $ticketPanel = TicketPanel::factory()->create(); + $user = User::factory()->owner()->create(); + + $this->actingAs($user) + ->get(route('panel.index')) + ->assertOk() + ->assertJsonCount(1, 'data') + ->assertJsonPath('data.0.id', $ticketPanel->id); + }); +}); - $this->actingAs($user) - ->postJson(route('panel.store'), $data) - ->assertCreated() - ->assertJson(['data' => $data]); +describe('create operations', function () { + test('create permission', function () { + $data = [ + 'title' => 'Test', + 'message' => 'Test', + 'embed_color' => '#012345', + 'channel_id' => '100000000000000000', + ]; + $this->assertCreatePermissions('panel.store', 'ticketPanel.create', $data, TicketPanel::class); + }); + + test('can create ticket panel', function () { + $user = User::factory()->owner()->create(); + $data = [ + 'title' => 'Test', + 'message' => 'Test', + 'embed_color' => '#012345', + 'channel_id' => '100000000000000000', + ]; + + $this->actingAs($user) + ->postJson(route('panel.store'), $data) + ->assertCreated() + ->assertJson(['data' => $data]); + + $this->assertDatabaseHas('ticket_panels', $data); + }); +}); - $this->assertDatabaseHas('ticket_panels', $data); +describe('update operations', function () { + test('update permission', function () { + $ticketPanel = TicketPanel::factory()->create(); + $data = [ + 'title' => 'Test', + ]; + $this->assertUpdatePermissions('panel.update', 'ticketPanel.update', $ticketPanel, $data, TicketPanel::class); + }); + + test('can update ticket panel', function () { + $user = User::factory()->owner()->create(); + $ticketPanel = TicketPanel::factory()->create(); + $data = [ + 'title' => 'Test', + 'message' => 'Test', + 'embed_color' => '#012345', + 'channel_id' => '100000000000000000', + ]; + + $this->actingAs($user) + ->patchJson(route('panel.update', $ticketPanel), $data) + ->assertOk() + ->assertJson(['data' => $data]); + + $this->assertDatabaseHas('ticket_panels', $data); + }); }); -test('can update ticket panel', function () { - Http::fake([ - config('services.discord.api_url').'/guilds/*/channels' => Http::response([]), - ]); +describe('delete operations', function () { + test('delete permission', function () { + $ticketPanel = TicketPanel::factory()->create(); + $this->assertDeletePermissions('panel.destroy', 'ticketPanel.delete', $ticketPanel, TicketPanel::class); + }); + + test('can delete ticket panel', function () { + $user = User::factory()->owner()->create(); + $ticketPanel = TicketPanel::factory()->create(); - $user = User::factory()->owner()->create(); - $ticketPanel = TicketPanel::factory()->create(); - $data = [ - 'title' => 'Test', - 'message' => 'Test', - 'embed_color' => '#012345', - 'channel_id' => '100000000000000000', - ]; - - $this->actingAs($user) - ->patchJson(route('panel.update', $ticketPanel), $data) - ->assertOk() - ->assertJson(['data' => $data]); - - $this->assertDatabaseHas('ticket_panels', $data); + $this->actingAs($user) + ->deleteJson(route('panel.destroy', $ticketPanel)) + ->assertOk(); + + $this->assertDatabaseMissing('ticket_panels', $ticketPanel->toArray()); + }); }); -test('can delete ticket panel', function () { - $user = User::factory()->owner()->create(); - $ticketPanel = TicketPanel::factory()->create(); +describe('send operations', function () { + test('send permission', function () { + $user = User::factory()->create(); + $ticketPanel = TicketPanel::factory()->create(); - $this->actingAs($user) - ->deleteJson(route('panel.destroy', $ticketPanel)) - ->assertOk(); + $route = 'panel.send'; + $permission = 'ticketPanel.create'; - $this->assertDatabaseMissing('ticket_panels', $ticketPanel->toArray()); -}); + assertFalse($user->can($permission)); -test('can send ticket panel', function () { - Http::fake([ - config('services.discord.api_url').'/channels/*' => Http::response([]), - ]); + $this->actingAs($user) + ->postJson(route($route, $ticketPanel)) + ->assertForbidden(); + + $user->givePermissionTo($permission); + $this->actingAs($user) + ->postJson(route($route, $ticketPanel)) + ->assertOk(); + }); - $user = User::factory()->owner()->create(); - $ticketPanel = TicketPanel::factory()->create(); + test('can send ticket panel', function () { + $user = User::factory()->owner()->create(); + $ticketPanel = TicketPanel::factory()->create(); - $this->actingAs($user) - ->postJson(route('panel.send', $ticketPanel)) - ->assertOk(); + $this->actingAs($user) + ->postJson(route('panel.send', $ticketPanel)) + ->assertOk(); + }); }); diff --git a/tests/Feature/Http/Controllers/TicketTeamControllerTest.php b/tests/Feature/Http/Controllers/TicketTeamControllerTest.php index f723017..bf81057 100644 --- a/tests/Feature/Http/Controllers/TicketTeamControllerTest.php +++ b/tests/Feature/Http/Controllers/TicketTeamControllerTest.php @@ -2,57 +2,94 @@ use App\Models\TicketTeam; use App\Models\User; +use Tests\Traits\CrudPermissionTrait; -test('auth user can get ticket teams', function () { - $ticketTeam = TicketTeam::factory()->create(); - $user = User::factory()->owner()->create(); +pest()->use(CrudPermissionTrait::class); - $this->actingAs($user) - ->get(route('team.index')) - ->assertOk() - ->assertJsonCount(1, 'data') - ->assertJsonPath('data.0.id', $ticketTeam->id); -}); +describe('read operations', function () { + test('read permission', function () { + TicketTeam::factory()->create(); + $this->assertReadPermissions('team.index', 'ticketTeam.read'); + }); + + test('can read ticket teams', function () { + $ticketTeam = TicketTeam::factory()->create(); + $user = User::factory()->owner()->create(); -test('can create ticket team', function () { - $user = User::factory()->owner()->create(); + $this->actingAs($user) + ->get(route('team.index')) + ->assertOk() + ->assertJsonCount(1, 'data') + ->assertJsonPath('data.0.id', $ticketTeam->id); + }); +}); - $this->actingAs($user) - ->postJson(route('team.store'), [ +describe('create operations', function () { + test('create permission', function () { + $data = [ 'name' => 'Test', 'ticket_team_role_ids' => ['123', '456'], - ]) - ->assertCreated() - ->assertJson(['data' => ['name' => 'Test']]); + ]; + $this->assertCreatePermissions('team.store', 'ticketTeam.create', $data, TicketTeam::class, ['name' => 'Test']); + }); - $this->assertDatabaseHas('ticket_teams', ['name' => 'Test']); - expect(TicketTeam::count())->toBe(1); - expect(TicketTeam::first()->ticketTeamRoles->map(fn ($teamRole) => $teamRole->role_id)->toArray())->toBe(['123', '456']); -}); + test('can create ticket team', function () { + $user = User::factory()->owner()->create(); + + $this->actingAs($user) + ->postJson(route('team.store'), [ + 'name' => 'Test', + 'ticket_team_role_ids' => ['123', '456'], + ]) + ->assertCreated() + ->assertJson(['data' => ['name' => 'Test']]); -test('can update ticket team', function () { - $user = User::factory()->owner()->create(); - $ticketTeam = TicketTeam::factory()->create(); + $this->assertDatabaseHas('ticket_teams', ['name' => 'Test']); + expect(TicketTeam::count())->toBe(1); + expect(TicketTeam::first()->ticketTeamRoles->map(fn ($teamRole) => $teamRole->role_id)->toArray())->toBe(['123', '456']); + }); +}); - $this->actingAs($user) - ->patchJson(route('team.update', $ticketTeam), [ +describe('update operations', function () { + test('update permission', function () { + $ticketTeam = TicketTeam::factory()->create(); + $data = [ 'name' => 'Test', - 'ticket_team_role_ids' => ['123', '456'], - ]) - ->assertOk() - ->assertJson(['data' => ['name' => 'Test']]); + ]; + $this->assertUpdatePermissions('team.update', 'ticketTeam.update', $ticketTeam, $data, TicketTeam::class); + }); + + test('can update ticket team', function () { + $user = User::factory()->owner()->create(); + $ticketTeam = TicketTeam::factory()->create(); - $this->assertDatabaseHas('ticket_teams', ['name' => 'Test']); - expect($ticketTeam->ticketTeamRoles->map(fn ($teamRole) => $teamRole->role_id)->toArray())->toBe(['123', '456']); + $this->actingAs($user) + ->patchJson(route('team.update', $ticketTeam), [ + 'name' => 'Test', + 'ticket_team_role_ids' => ['123', '456'], + ]) + ->assertOk() + ->assertJson(['data' => ['name' => 'Test']]); + + $this->assertDatabaseHas('ticket_teams', ['name' => 'Test']); + expect($ticketTeam->ticketTeamRoles->map(fn ($teamRole) => $teamRole->role_id)->toArray())->toBe(['123', '456']); + }); }); -test('can delete ticket team', function () { - $user = User::factory()->owner()->create(); - $ticketTeam = TicketTeam::factory()->create(); +describe('delete operations', function () { + test('delete permission', function () { + $ticketTeam = TicketTeam::factory()->create(); + $this->assertDeletePermissions('team.destroy', 'ticketTeam.delete', $ticketTeam, TicketTeam::class); + }); + + test('can delete ticket team', function () { + $user = User::factory()->owner()->create(); + $ticketTeam = TicketTeam::factory()->create(); - $this->actingAs($user) - ->deleteJson(route('team.destroy', $ticketTeam)) - ->assertOk(); + $this->actingAs($user) + ->deleteJson(route('team.destroy', $ticketTeam)) + ->assertOk(); - $this->assertDatabaseMissing('ticket_teams', $ticketTeam->toArray()); + $this->assertDatabaseMissing('ticket_teams', $ticketTeam->toArray()); + }); }); diff --git a/tests/Feature/Http/Controllers/TicketTranscriptControllerTest.php b/tests/Feature/Http/Controllers/TicketTranscriptControllerTest.php index 5c46616..53eab2a 100644 --- a/tests/Feature/Http/Controllers/TicketTranscriptControllerTest.php +++ b/tests/Feature/Http/Controllers/TicketTranscriptControllerTest.php @@ -5,72 +5,118 @@ use App\Models\User; use Illuminate\Support\Carbon; use Illuminate\Support\Facades\Http; +use Tests\Traits\CrudPermissionTrait; + +use function PHPUnit\Framework\assertFalse; + +pest()->use(CrudPermissionTrait::class); + +beforeEach(function () { + Carbon::setTestNow(); -test('can create ticket transcript', function () { Http::fake([ config('services.discord.api_url').'/users/*' => Http::response([]), ]); +}); - $user = User::factory()->owner()->create(); - $ticket = Ticket::factory()->create(); - $data = [ - 'ticket_id' => $ticket->id, - 'discord_user_id' => '100000000000000000', - 'message_id' => '100000000000000001', - 'message' => 'Hello Test!', - 'attachments' => '[{"id":1}]', - 'embeds' => '[{"id":2}]', - ]; - - $this->actingAs($user) - ->postJson(route('transcript.store'), $data) - ->assertCreated() - ->assertJson(['data' => $data]); - - $this->assertDatabaseHas('ticket_transcripts', $data); +describe('create operations', function () { + test('create permission', function () { + $ticket = Ticket::factory()->create(); + $data = [ + 'ticket_id' => $ticket->id, + 'discord_user_id' => '100000000000000000', + 'message_id' => '100000000000000001', + 'message' => 'Hello Test!', + 'attachments' => '[{"id":1}]', + 'embeds' => '[{"id":2}]', + ]; + $this->assertCreatePermissions('transcript.store', 'ticketTranscript.create', $data, TicketTranscript::class); + }); + test('can create ticket transcript', function () { + $user = User::factory()->owner()->create(); + $ticket = Ticket::factory()->create(); + $data = [ + 'ticket_id' => $ticket->id, + 'discord_user_id' => '100000000000000000', + 'message_id' => '100000000000000001', + 'message' => 'Hello Test!', + 'attachments' => '[{"id":1}]', + 'embeds' => '[{"id":2}]', + ]; + + $this->actingAs($user) + ->postJson(route('transcript.store'), $data) + ->assertCreated() + ->assertJson(['data' => $data]); + + $this->assertDatabaseHas('ticket_transcripts', $data); + }); }); -test('can udpate ticket transcript', function () { - Http::fake([ - config('services.discord.api_url').'/users/*' => Http::response([]), - ]); +describe('update operations', function () { + test('can update ticket transcript', function () { + $ticketTranscript = TicketTranscript::factory()->create(); - $ticketTranscript = TicketTranscript::factory()->create(); - - $user = User::factory()->owner()->create(); - $ticket = Ticket::factory()->create(); - $data = [ - 'ticket_id' => $ticket->id, - 'discord_user_id' => '100000000000000000', - 'message_id' => $ticketTranscript->message_id, - 'message' => 'Hello Test!', - 'attachments' => '[{"id":1}]', - 'embeds' => '[{"id":2}]', - ]; - - $this->actingAs($user) - ->postJson(route('transcript.store'), $data) - ->assertCreated() - ->assertJson(['data' => $data]); - - $this->assertDatabaseHas('ticket_transcripts', [ - 'id' => $ticketTranscript->id, - ...$data, - ]); + $user = User::factory()->owner()->create(); + $ticket = Ticket::factory()->create(); + $data = [ + 'ticket_id' => $ticket->id, + 'discord_user_id' => '100000000000000000', + 'message_id' => $ticketTranscript->message_id, + 'message' => 'Hello Test!', + 'attachments' => '[{"id":1}]', + 'embeds' => '[{"id":2}]', + ]; + + $this->actingAs($user) + ->postJson(route('transcript.store'), $data) + ->assertCreated() + ->assertJson(['data' => $data]); + + $this->assertDatabaseHas('ticket_transcripts', [ + 'id' => $ticketTranscript->id, + ...$data, + ]); + }); }); -test('can delete ticket transcript', function () { - Carbon::setTestNow(); +describe('delete operations', function () { + test('delete permission', function () { + $permission = 'ticketTranscript.delete'; + $route = 'transcript.delete'; + $table = TicketTranscript::class; - $ticketTranscript = TicketTranscript::factory()->create(); - $user = User::factory()->owner()->create(); + $ticketTranscript = TicketTranscript::factory()->create(); + $user = User::factory()->create(); + assertFalse($user->can($permission)); - $this->actingAs($user) - ->deleteJson(route('transcript.delete', $ticketTranscript->message_id)) - ->assertOk(); + $this->actingAs($user) + ->deleteJson(route($route, $ticketTranscript->message_id)) + ->assertForbidden(); + $this->assertDatabaseHas($table, ['id' => $ticketTranscript->id]); - $this->assertDatabaseHas('ticket_transcripts', [ - 'id' => $ticketTranscript->id, - 'deleted_at' => now(), - ]); + $user->givePermissionTo($permission); + $this->actingAs($user) + ->deleteJson(route($route, $ticketTranscript->message_id)) + ->assertOk(); + $this->assertDatabaseHas($table, [ + 'id' => $ticketTranscript->id, + 'deleted_at' => now(), + ]); + }); + + test('can delete ticket transcript', function () { + + $ticketTranscript = TicketTranscript::factory()->create(); + $user = User::factory()->owner()->create(); + + $this->actingAs($user) + ->deleteJson(route('transcript.delete', $ticketTranscript->message_id)) + ->assertOk(); + + $this->assertDatabaseHas('ticket_transcripts', [ + 'id' => $ticketTranscript->id, + 'deleted_at' => now(), + ]); + }); }); diff --git a/tests/Traits/CrudPermissionTrait.php b/tests/Traits/CrudPermissionTrait.php new file mode 100644 index 0000000..57ee357 --- /dev/null +++ b/tests/Traits/CrudPermissionTrait.php @@ -0,0 +1,162 @@ +create(); + $this->assertCannotRead($user, $route, $permission); + $this->assertCanRead($user, $route, $permission); + } + + private function assertCanRead(User $user, string $route, string $permission) + { + $user->givePermissionTo($permission); + + $this->actingAs($user) + ->get(route($route)) + ->assertOk(); + } + + private function assertCannotRead(User $user, string $route, string $permission) + { + assertFalse($user->can($permission)); + + $this->actingAs($user) + ->get(route($route)) + ->assertForbidden(); + } + + protected function assertCreatePermissions(string $route, string $permission, array $data, string $table, ?array $assertData = null) + { + $user = User::factory()->create(); + $this->assertCannotCreate($user, $route, $permission, $data, $table, $assertData); + $this->assertCanCreate($user, $route, $permission, $data, $table, $assertData); + } + + private function assertCanCreate( + User $user, + string $route, + string $permission, + array $data, + string $table, + ?array $assertData + ) { + $user->givePermissionTo($permission); + + $this->actingAs($user) + ->postJson(route($route), $data) + ->assertCreated(); + + $this->assertDatabaseHas($table, $assertData ?? $data); + } + + private function assertCannotCreate( + User $user, + string $route, + string $permission, + array $data, + string $table, + ?array $assertData + ) { + assertFalse($user->can($permission)); + + $this->actingAs($user) + ->postJson(route($route), $data) + ->assertForbidden(); + + $this->assertDatabaseMissing($table, $assertData ?? $data); + } + + protected function assertUpdatePermissions(string $route, string $permission, Model $model, array $data, string $table) + { + $user = User::factory()->create(); + $this->assertCannotUpdate($user, $route, $permission, $model, $data, $table); + $this->assertCanUpdate($user, $route, $permission, $model, $data, $table); + } + + private function assertCanUpdate( + User $user, + string $route, + string $permission, + Model $model, + array $data, + string $table + ) { + $user->givePermissionTo($permission); + + $this->actingAs($user) + ->patchJson(route($route, $model), $data) + ->assertOk(); + + $this->assertDatabaseHas($table, $data); + } + + private function assertCannotUpdate( + User $user, + string $route, + string $permission, + Model $model, + array $data, + string $table + ) { + assertFalse($user->can($permission)); + + $this->actingAs($user) + ->patchJson(route($route, $model), $data) + ->assertForbidden(); + + $this->assertDatabaseMissing($table, $data); + } + + protected function assertDeletePermissions(string $route, string $permission, Model $model, string $table, bool $softDelete = false) + { + $user = User::factory()->create(); + $this->assertCannotDelete($user, $route, $permission, $model, $table); + $this->assertCanDelete($user, $route, $permission, $model, $table, $softDelete); + } + + private function assertCanDelete( + User $user, + string $route, + string $permission, + Model $model, + string $table, + bool $softDelete + ) { + $user->givePermissionTo($permission); + + $this->actingAs($user) + ->deleteJson(route($route, $model)) + ->assertOk(); + + if ($softDelete) { + $this->assertSoftDeleted($table, ['id' => $model->id]); + } else { + $this->assertDatabaseMissing($table, ['id' => $model->id]); + } + } + + private function assertCannotDelete( + User $user, + string $route, + string $permission, + Model $model, + string $table + ) { + assertFalse($user->can($permission)); + + $this->actingAs($user) + ->deleteJson(route($route, $model)) + ->assertForbidden(); + + $this->assertDatabaseHas($table, ['id' => $model->id]); + } +}