diff --git a/.env.example b/.env.example index 78b02bd..89b69b3 100644 --- a/.env.example +++ b/.env.example @@ -4,6 +4,12 @@ APP_KEY= APP_DEBUG=true APP_URL=http://localhost +NFTCDN_DOMAIN=mynftcdndomain +NFTCDN_KEY=yournftcdndomainhere= +ALLOW_REGISTRATION=true +ALLOW_PASSWORD_CHANGE=true +VITE_APP_ENV=local + LOG_CHANNEL=stack LOG_DEPRECATIONS_CHANNEL=null LOG_LEVEL=debug @@ -13,7 +19,7 @@ DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=gatekeepermetamorphosis DB_USERNAME=root -DB_PASSWORD= +DB_PASSWORD=rootpassword BROADCAST_DRIVER=log CACHE_DRIVER=file diff --git a/.env.testing b/.env.testing new file mode 100644 index 0000000..f4972ad --- /dev/null +++ b/.env.testing @@ -0,0 +1,67 @@ +APP_NAME=GateKeeper +APP_ENV=testing +APP_KEY=base64:ScbTyDAmfxPaqXuNfzEA5zJ9iPxyC97lwXw5Wi8OyNw= +APP_DEBUG=true +APP_URL=http://localhost +APP_SERVICE=gatekeeper.app + +NFTCDN_DOMAIN=yournftcdndomain +NFTCDN_KEY=yournftcdnkeyhere= +ALLOW_REGISTRATION=false +ALLOW_PASSWORD_CHANGE=false +VITE_APP_ENV=testing + +LOG_CHANNEL=stack +LOG_DEPRECATIONS_CHANNEL=null +LOG_LEVEL=debug + +DB_CONNECTION=sqlite +DB_DATABASE=:memory: +DB_HOST=localhost +DB_PORT=3306 +DB_USERNAME=root +DB_PASSWORD=rootpassword + +BROADCAST_DRIVER=log +CACHE_DRIVER=file +FILESYSTEM_DISK=local +QUEUE_CONNECTION=sync +SESSION_DRIVER=database +SESSION_LIFETIME=120 + +MEMCACHED_HOST=127.0.0.1 + +REDIS_HOST=127.0.0.1 +REDIS_PASSWORD=null +REDIS_PORT=6379 + +MAIL_MAILER=smtp +MAIL_HOST=mailpit +MAIL_PORT=1025 +MAIL_USERNAME=null +MAIL_PASSWORD=null +MAIL_ENCRYPTION=null +MAIL_FROM_ADDRESS="hello@example.com" +MAIL_FROM_NAME="${APP_NAME}" + +AWS_ACCESS_KEY_ID= +AWS_SECRET_ACCESS_KEY= +AWS_DEFAULT_REGION=us-east-1 +AWS_BUCKET= +AWS_USE_PATH_STYLE_ENDPOINT=false + +PUSHER_APP_ID= +PUSHER_APP_KEY= +PUSHER_APP_SECRET= +PUSHER_HOST= +PUSHER_PORT=443 +PUSHER_SCHEME=https +PUSHER_APP_CLUSTER=mt1 + +VITE_APP_NAME="${APP_NAME}" +VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}" +VITE_PUSHER_HOST="${PUSHER_HOST}" +VITE_PUSHER_PORT="${PUSHER_PORT}" +VITE_PUSHER_SCHEME="${PUSHER_SCHEME}" +VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" +VITE_DEV_SERVER_URL=http://localhost:5173 diff --git a/.github/workflows/ci-staging.yml b/.github/workflows/ci-staging.yml new file mode 100644 index 0000000..9779467 --- /dev/null +++ b/.github/workflows/ci-staging.yml @@ -0,0 +1,44 @@ +name: CI (Staging PR) +on: + pull_request: + branches: + - staging +jobs: + php-tests: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 8.3 + tools: composer:v2 + coverage: none + + - name: Install PHP dependencies (with dev) + run: composer install --no-interaction --prefer-dist --optimize-autoloader + + - name: Prepare .env for tests + run: | + cp .env.example .env + php artisan key:generate + env: + APP_ENV: testing + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: 'npm' + + - name: Install frontend deps & build + run: | + npm ci + npm run build + + # If you have tests, this is where they run + - name: Run Laravel test suite + run: php artisan test diff --git a/app/Http/Controllers/CheckinController.php b/app/Http/Controllers/CheckinController.php index 945ac9a..8e65f5e 100644 --- a/app/Http/Controllers/CheckinController.php +++ b/app/Http/Controllers/CheckinController.php @@ -48,7 +48,7 @@ public function store(StoreCheckinRequest $request, string $event_uuid): JsonRes // throughout an event return response()->json([ 'message' => 'Ticket has already been checked in!' - ], 401); + ], 400); } // Since we've arrive here it is probably safe to insert the "checkin" diff --git a/app/Http/Controllers/CheckoutController.php b/app/Http/Controllers/CheckoutController.php index 0a1f984..bac62fb 100644 --- a/app/Http/Controllers/CheckoutController.php +++ b/app/Http/Controllers/CheckoutController.php @@ -43,7 +43,7 @@ public function store(StoreCheckoutRequest $request, string $event_uuid): JsonRe // then we can't very well add a new checkout can we? return response()->json([ 'message' => 'Ticket has not been checked in!' - ], 401); + ], 400); } // Since we've arrive here it is probably safe to insert the "checkin" diff --git a/app/Http/Controllers/EventCheckinController.php b/app/Http/Controllers/EventCheckinController.php index 5484187..1356eb0 100644 --- a/app/Http/Controllers/EventCheckinController.php +++ b/app/Http/Controllers/EventCheckinController.php @@ -26,22 +26,6 @@ public function index(Request $request) compact('team', 'events')); } - /** - * Show the form for creating a new resource. - */ - public function create() - { - // - } - - /** - * Store a newly created resource in storage. - */ - public function store(Request $request) - { - // - } - /** * Display the specified resource. */ @@ -54,28 +38,4 @@ public function show(Request $request, string $event_uuid) ->render($request, 'ScanTickets/Show', compact('event')); } - - /** - * Show the form for editing the specified resource. - */ - public function edit(string $id) - { - // - } - - /** - * Update the specified resource in storage. - */ - public function update(Request $request, string $id) - { - // - } - - /** - * Remove the specified resource from storage. - */ - public function destroy(string $id) - { - // - } } diff --git a/app/Http/Controllers/EventPolicyController.php b/app/Http/Controllers/EventPolicyController.php index 8c0bcfc..c410e78 100644 --- a/app/Http/Controllers/EventPolicyController.php +++ b/app/Http/Controllers/EventPolicyController.php @@ -9,20 +9,6 @@ class EventPolicyController extends Controller { - /** - * Display a listing of the resource. - */ - public function index(Event $event) { - // - } - - /** - * Show the form for creating a new resource. - */ - public function create(Event $event) { - // - } - /** * Store a newly created resource in storage. */ @@ -48,27 +34,6 @@ public function store(Request $request, string $eventUUID) { return back(303); } - /** - * Display the specified resource. - */ - public function show(Event $event, Policy $policy) { - // - } - - /** - * Show the form for editing the specified resource. - */ - public function edit(Event $event, Policy $policy) { - // - } - - /** - * Update the specified resource in storage. - */ - public function update(Request $request, Event $event, Policy $policy) { - // - } - /** * Remove the specified resource from storage. */ diff --git a/app/Http/Controllers/TicketController.php b/app/Http/Controllers/TicketController.php index a3595e8..d75fbfa 100644 --- a/app/Http/Controllers/TicketController.php +++ b/app/Http/Controllers/TicketController.php @@ -1,190 +1,181 @@ validated(); + + $event = Event::where('uuid', $details['event_uuid']) + ->firstOrFail(); + + if (!$event->isticketingactive()) { + return response()->json([ + 'message' => 'Sorry, ticketing is closed for this event.' + ], 409); + } + + $policy = $event->policies() + ->where('hash', $details['policy_id']) + ->firstOrFail(); + + $ticket = Ticket::where('event_id', $event->id) + ->where('policy_id', $policy->id) + ->where('asset_id', $details['asset_id']) + ->where('stake_key', $details['stake_key']) + ->latest('id') + ->first(); + + if ($ticket) { + /** + * TODO: Check if the existing ticket is already checked in or not + * If the ticket is already checked in, we need to throw an error + * + * TODO: Check if the existing ticket's signBy is expired + * If the ticket's signBy is expired then we should delete it and create + * a new one + */ + } - /** - * Display a listing of the resource. - */ - public function index() - { - // + if (!$ticket) { + // Generate a new ticket + $ticket = new Ticket; + $ticket->fill([ + ...$details, + 'event_id' => $event->id, + 'policy_id' => $policy->id, + 'signature_nonce' => Str::uuid() + ->getBytes() + ]); + + $ticket->save(); } - /** - * Store a newly created resource in storage. - */ - public function store(StoreTicketRequest $request) - { - $details = $request->validated(); - - $event = Event::where('uuid', $details['event_uuid']) - ->firstOrFail(); - - if (!$event->isticketingactive()) { - return response()->json([ - 'message' => 'Sorry, ticketing is closed for this event.' - ], 409); - } - - $policy = $event->policies() - ->where('hash', $details['policy_id']) - ->firstOrFail(); - - - $ticket = Ticket::where('event_id', $event->id) - ->where('policy_id', $policy->id) - ->where('asset_id', $details['asset_id']) - ->where('stake_key', $details['stake_key']) - ->latest('id') - ->first(); - - if ($ticket) { - /** - * TODO: Check if the existing ticket is already checked in or not - * If the ticket is already checked in, we need to throw an error - * - * TODO: Check if the existing ticket's signBy is expired - * If the ticket's signBy is expired then we should delete it and create - * a new one - */ - } - - if (!$ticket) { - // Generate a new ticket - $ticket = new Ticket; - $ticket->fill([ - ...$details, - 'event_id' => $event->id, - 'policy_id' => $policy->id, - 'signature_nonce' => Str::uuid() - ->getBytes() - ]); - - $ticket->save(); - } - - $signature_json = $ticket->generate_signing_json(); - - - // Uuid::fromBytes($this->signature_nonce) - // ->toString() - - return [ - 'id' => Uuid::fromBytes($ticket->signature_nonce) - ->toString(), - 'nonce' => bin2hex($signature_json), - ]; + $signature_json = $ticket->generate_signing_json(); + return [ + 'id' => Uuid::fromBytes($ticket->signature_nonce) + ->toString(), + 'nonce' => bin2hex($signature_json), + ]; + + } + + /** + * Update the specified resource in storage. + */ + public function update(UpdateTicketRequest $request, string $ticket_nonce): mixed + { + $details = $request->validated(); + + $event = Event::where('uuid', $details['event_uuid']) + ->firstOrFail(); + + if (!$event->isticketingactive()) { + return false; } - /** - * Display the specified resource. - */ - public function show(Ticket $ticket) - { - // + $policy = $event->policies() + ->where('hash', $details['policy_id']) + ->firstOrFail(); + + $ticket = Ticket::where('event_id', $event->id) + ->where('policy_id', $policy->id) + ->where('asset_id', $details['asset_id']) + ->where('stake_key', $details['stake_key']) + ->where('signature_nonce', Uuid::fromString($ticket_nonce) + ->getBytes()) + ->firstOrFail(); + + + if (!empty($details['signature']['txn'])) { +// $details['signatureMethod'] = true; +// +// $valid_signature = SignTxn::execute([ +// 'tx_hex' => $details['signature']['txn'], +// 'stake_key' => $ticket->stake_key, +// 'nonce' => bin2hex($ticket->generate_signing_json()), +// ]) +// ->body(); + return response()->json([ + 'message' => 'Sorry, dummy transction validation is disabled' + ], 401); + } else { + $details['signatureMethod'] = false; + + $verification_request = new VerificationRequest( + signatureCbor: $details['signature']['signature'], + signatureKey: $details['signature']['key'], + challengeHex: bin2hex($ticket->generate_signing_json()), + expectedSignerStakeAddress: $ticket->stake_key, + networkMode: 1 + ); + + $verifier = CIP8Verifier::create(); + $result = $verifier->verify($verification_request); + + $valid_signature = $result->isValid; + +// $valid_signature = SignData::execute([ +// 'signature' => $details['signature']['signature'], +// 'key' => $details['signature']['key'], +// 'payload' => bin2hex($ticket->generate_signing_json()), +// 'stake_address' => $ticket->stake_key, +// 'network_mode' => 'mainnet' +// ]) +// ->body(); } - /** - * Update the specified resource in storage. - */ - public function update(UpdateTicketRequest $request, string $ticket_nonce): mixed - { - $details = $request->validated(); - - $event = Event::where('uuid', $details['event_uuid']) - ->firstOrFail(); - - if (!$event->isticketingactive()) { - return false; - } - - $policy = $event->policies() - ->where('hash', $details['policy_id']) - ->firstOrFail(); - - $ticket = Ticket::where('event_id', $event->id) - ->where('policy_id', $policy->id) - ->where('asset_id', $details['asset_id']) - ->where('stake_key', $details['stake_key']) - ->where('signature_nonce', Uuid::fromString($ticket_nonce) - ->getBytes()) - ->firstOrFail(); - - - if (!empty($details['signature']['txn'])) { - $details['signatureMethod'] = true; - - $valid_signature = SignTxn::execute([ - 'tx_hex' => $details['signature']['txn'], - 'stake_key' => $ticket->stake_key, - 'nonce' => bin2hex($ticket->generate_signing_json()), - ]) - ->body(); - } else { - $details['signatureMethod'] = false; - - $valid_signature = SignData::execute([ - 'signature' => $details['signature']['signature'], - 'key' => $details['signature']['key'], - 'payload' => bin2hex($ticket->generate_signing_json()), - 'stake_address' => $ticket->stake_key, - 'network_mode' => 'mainnet' - ]) - ->body(); - } - - $details['valid_signature'] = $valid_signature; - - if (!$valid_signature) { - return response()->json([ - 'message' => 'Sorry, that signature is invalid!' - ], 401); - } - - // Validate that the user holds the asset here... - // By doing it so far down in the process we minimize the number of - // false-requests we need to make to Blockfrost, Koios or another data - // provider. - - if (empty($ticket->ticket_nonce)) { - $ticket->ticket_nonce = Uuid::uuid4() - ->getBytes(); - $ticket->signature = json_encode($details['signature']); - $ticket->save(); - } - - $ticket->removeOldAttempts(); - - $ticket_nonce = Uuid::fromBytes($ticket->ticket_nonce) - ->toString(); - - return [ - 'qr_value' => $ticket->asset_id . '|' . $ticket_nonce, - 'security_code' => $ticket_nonce, - ]; - -// return $details; + $details['valid_signature'] = $valid_signature; + + if (!$valid_signature) { + return response()->json([ + 'message' => 'Sorry, that signature is invalid!' + ], 401); } - /** - * Remove the specified resource from storage. - */ - public function destroy(Ticket $ticket) - { - // + // Validate that the user holds the asset here... + // By doing it so far down in the process we minimize the number of + // false-requests we need to make to Blockfrost, Koios or another data + // provider. + + if (empty($ticket->ticket_nonce)) { + $ticket->ticket_nonce = Uuid::uuid4() + ->getBytes(); + $ticket->signature = json_encode($details['signature']); + $ticket->save(); } + + $ticket->removeOldAttempts(); + + $ticket_nonce = Uuid::fromBytes($ticket->ticket_nonce) + ->toString(); + + return [ + 'qr_value' => $ticket->asset_id . '|' . $ticket_nonce, + 'security_code' => $ticket_nonce, + ]; } +} diff --git a/app/Http/Requests/UpdateTicketRequest.php b/app/Http/Requests/UpdateTicketRequest.php index 094038d..91d04ac 100644 --- a/app/Http/Requests/UpdateTicketRequest.php +++ b/app/Http/Requests/UpdateTicketRequest.php @@ -2,6 +2,7 @@ namespace App\Http\Requests; + use Illuminate\Contracts\Validation\ValidationRule; use Illuminate\Foundation\Http\FormRequest; class UpdateTicketRequest extends FormRequest @@ -17,7 +18,7 @@ public function authorize(): bool /** * Get the validation rules that apply to the request. * - * @return array|string> + * @return array|string> */ public function rules(): array { diff --git a/app/Models/Event.php b/app/Models/Event.php index a1a9c56..4ec2b3b 100644 --- a/app/Models/Event.php +++ b/app/Models/Event.php @@ -84,8 +84,6 @@ public function attendance(): Collection ...$this->checkins, ...$this->checkouts ]); -// return $this->checkins -// ->merge($this->checkouts); } public function policies(): BelongsToMany diff --git a/app/Models/Policy.php b/app/Models/Policy.php index 790cc7d..1cd46ec 100644 --- a/app/Models/Policy.php +++ b/app/Models/Policy.php @@ -1,36 +1,47 @@ belongsToMany(Event::class); + } - public function events(): BelongsToMany - { - return $this->belongsToMany(Event::class); - } + public function team(): BelongsTo + { + return $this->belongsTo(Team::class); + } + public function user(): BelongsTo + { + return $this->belongsTo(User::class); } + +} diff --git a/app/Models/Team.php b/app/Models/Team.php index 2b6fce8..9028eec 100644 --- a/app/Models/Team.php +++ b/app/Models/Team.php @@ -1,73 +1,73 @@ - */ - protected $casts = [ - 'personal_team' => 'boolean', - ]; + /** + * The attributes that should be cast. + * + * @var array + */ + protected $casts = [ + 'personal_team' => 'boolean', + ]; - /** - * The attributes that are mass assignable. - * - * @var array - */ - protected $fillable = [ - 'name', - 'personal_team', - ]; + /** + * The attributes that are mass assignable. + * + * @var array + */ + protected $fillable = [ + 'name', + 'personal_team', + ]; - protected $hidden = [ - 'created_at', - 'updated_at', - ]; + protected $hidden = [ + 'created_at', + 'updated_at', + ]; - /*protected $visible = [ - 'id', - 'name', - 'profile_photo_url' - ];*/ + /** + * The event map for the model. + * + * @var array + */ + protected $dispatchesEvents = [ + 'created' => TeamCreated::class, + 'updated' => TeamUpdated::class, + 'deleted' => TeamDeleted::class, + ]; - /** - * The event map for the model. - * - * @var array - */ - protected $dispatchesEvents = [ - 'created' => TeamCreated::class, - 'updated' => TeamUpdated::class, - 'deleted' => TeamDeleted::class, - ]; + protected $appends = [ + 'profile_photo_url', + ]; - protected $appends = [ - 'profile_photo_url', - ]; + public function policies(): HasMany + { + return $this->hasMany(Policy::class); + } - public function policies(): HasMany - { - return $this->hasMany(Policy::class); - } + public function events(): HasMany + { + return $this->hasMany(Event::class); + } - public function events(): HasMany - { - return $this->hasMany(Event::class); - } + public function owner(): BelongsTo + { + return $this->belongsTo(User::class, 'user_id'); } +} diff --git a/app/Models/User.php b/app/Models/User.php index 58f2f6f..7f9afce 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -1,81 +1,86 @@ - */ - protected $fillable = [ - 'name', 'email', 'password', - ]; + /** + * The attributes that are mass assignable. + * + * @var array + */ + protected $fillable = [ + 'name', 'email', 'password', + ]; - /** - * The attributes that should be hidden for serialization. - * - * @var array - */ - protected $hidden = [ - 'password', - 'remember_token', - 'two_factor_recovery_codes', - 'two_factor_secret', - 'created_at', - 'updated_at', - 'email_verified_at', - 'two_factor_confirmed_at' - ]; + /** + * The attributes that should be hidden for serialization. + * + * @var array + */ + protected $hidden = [ + 'password', + 'remember_token', + 'two_factor_recovery_codes', + 'two_factor_secret', + 'created_at', + 'updated_at', + 'email_verified_at', + 'two_factor_confirmed_at' + ]; - /** - * The attributes that should be cast. - * - * @var array - */ - protected $casts = [ - 'email_verified_at' => 'datetime', - ]; + /** + * The attributes that should be cast. + * + * @var array + */ + protected $casts = [ + 'email_verified_at' => 'datetime', + ]; - /** - * The accessors to append to the model's array form. - * - * @var array - */ - protected $appends = [ - 'profile_photo_url', - ]; + /** + * The accessors to append to the model's array form. + * + * @var array + */ + protected $appends = [ + 'profile_photo_url', + ]; - public function events(): HasMany - { - return $this->hasMany(Event::class); - } + public function events(): HasMany + { + return $this->hasMany(Event::class); + } - public function checkins(): HasMany - { - return $this->hasMany(Checkin::class); - } + public function checkins(): HasMany + { + return $this->hasMany(Checkin::class); + } - public function checkouts(): HasMany - { - return $this->HasMany(Checkout::class); - } + public function checkouts(): HasMany + { + return $this->HasMany(Checkout::class); + } + + public function policies(): HasMany + { + return $this->hasMany(Policy::class); } +} diff --git a/composer.json b/composer.json index d5384d5..550875b 100644 --- a/composer.json +++ b/composer.json @@ -9,17 +9,18 @@ "license": "MIT", "require": { "php": "^8.1", + "cardano-php/cip8-verifier": "^1.0", "guzzlehttp/guzzle": "^7.2", "hammerstone/sidecar": "^0.6.1", "inertiajs/inertia-laravel": "^0.6.8", + "knuckleswtf/scribe": "^5.5", "laravel/framework": "^10.10", "laravel/jetstream": "^4.2", "laravel/sanctum": "^3.3", "laravel/tinker": "^2.8", "laravel/vapor-core": "^2.37", "spatie/laravel-permission": "^6.3", - "tightenco/ziggy": "^1.0", - "knuckleswtf/scribe": "^5.5" + "tightenco/ziggy": "^1.0" }, "require-dev": { "fakerphp/faker": "^1.9.1", diff --git a/composer.lock b/composer.lock index 91dee6d..5b6831c 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "fbf31c0df12e2d743ca97cd011c1706a", + "content-hash": "e3b6bccf3ba55f6dc91406608829884a", "packages": [ { "name": "aws/aws-crt-php", @@ -341,6 +341,57 @@ ], "time": "2023-12-11T17:09:12+00:00" }, + { + "name": "cardano-php/cip8-verifier", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/cardano-php/cip8-verifier.git", + "reference": "e78b0b0c10a452399ebf0adbf53e192ef42acfa2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cardano-php/cip8-verifier/zipball/e78b0b0c10a452399ebf0adbf53e192ef42acfa2", + "reference": "e78b0b0c10a452399ebf0adbf53e192ef42acfa2", + "shasum": "" + }, + "require": { + "ext-sodium": "*", + "php": ">=8.0", + "spomky-labs/cbor-php": "^3.1" + }, + "require-dev": { + "pestphp/pest": "^3.8", + "phpstan/phpstan": "^1.10" + }, + "type": "library", + "autoload": { + "psr-4": { + "CardanoPhp\\CIP8Verifier\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Latheesan Kanesamoorthy" + } + ], + "description": "A PHP library to verify Cardano CIP-8 signed messages.", + "keywords": [ + "cardano", + "cip8", + "signature", + "verification" + ], + "support": { + "issues": "https://github.com/cardano-php/cip8-verifier/issues", + "source": "https://github.com/cardano-php/cip8-verifier/tree/v1.0.0" + }, + "time": "2025-07-31T03:42:53+00:00" + }, { "name": "dasprid/enum", "version": "1.0.7", @@ -4936,6 +4987,77 @@ ], "time": "2025-11-03T20:16:13+00:00" }, + { + "name": "spomky-labs/cbor-php", + "version": "3.2.2", + "source": { + "type": "git", + "url": "https://github.com/Spomky-Labs/cbor-php.git", + "reference": "2a5fb86aacfe1004611370ead6caa2bfc88435d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Spomky-Labs/cbor-php/zipball/2a5fb86aacfe1004611370ead6caa2bfc88435d0", + "reference": "2a5fb86aacfe1004611370ead6caa2bfc88435d0", + "shasum": "" + }, + "require": { + "brick/math": "^0.9|^0.10|^0.11|^0.12|^0.13|^0.14", + "ext-mbstring": "*", + "php": ">=8.0" + }, + "require-dev": { + "ext-json": "*", + "roave/security-advisories": "dev-latest", + "symfony/error-handler": "^6.4|^7.1|^8.0", + "symfony/var-dumper": "^6.4|^7.1|^8.0" + }, + "suggest": { + "ext-bcmath": "GMP or BCMath extensions will drastically improve the library performance. BCMath extension needed to handle the Big Float and Decimal Fraction Tags", + "ext-gmp": "GMP or BCMath extensions will drastically improve the library performance" + }, + "type": "library", + "autoload": { + "psr-4": { + "CBOR\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Florent Morselli", + "homepage": "https://github.com/Spomky" + }, + { + "name": "All contributors", + "homepage": "https://github.com/Spomky-Labs/cbor-php/contributors" + } + ], + "description": "CBOR Encoder/Decoder for PHP", + "keywords": [ + "Concise Binary Object Representation", + "RFC7049", + "cbor" + ], + "support": { + "issues": "https://github.com/Spomky-Labs/cbor-php/issues", + "source": "https://github.com/Spomky-Labs/cbor-php/tree/3.2.2" + }, + "funding": [ + { + "url": "https://github.com/Spomky", + "type": "github" + }, + { + "url": "https://www.patreon.com/FlorentMorselli", + "type": "patreon" + } + ], + "time": "2025-11-13T13:00:34+00:00" + }, { "name": "symfony/console", "version": "v6.4.27", diff --git a/database/factories/CheckinFactory.php b/database/factories/CheckinFactory.php new file mode 100644 index 0000000..8272219 --- /dev/null +++ b/database/factories/CheckinFactory.php @@ -0,0 +1,27 @@ + + */ +class CheckinFactory extends Factory +{ + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + 'ticket_id' => Ticket::factory(), + 'user_id' => User::factory(), + ]; + } +} diff --git a/database/factories/CheckoutFactory.php b/database/factories/CheckoutFactory.php new file mode 100644 index 0000000..9067bb8 --- /dev/null +++ b/database/factories/CheckoutFactory.php @@ -0,0 +1,26 @@ + + */ +class CheckoutFactory extends Factory +{ + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + 'user_id' => User::factory(), + 'ticket_id' => Ticket::factory(), + ]; + } +} diff --git a/database/factories/TicketFactory.php b/database/factories/TicketFactory.php index 2b550ab..2b41d53 100644 --- a/database/factories/TicketFactory.php +++ b/database/factories/TicketFactory.php @@ -19,8 +19,8 @@ public function definition(): array return [ 'event_id' => Event::factory(), 'policy_id' => Policy::factory(), - 'asset_id' => $this->faker->bothify(str_repeat('#', 40)), // adjust length as needed - 'stake_key' => $this->faker->bothify(str_repeat('#', 40)), + 'asset_id' => $this->faker->bothify(str_repeat('#', 32)), // adjust length as needed + 'stake_key' => $this->faker->bothify(str_repeat('#', 32)), 'signature_nonce' => random_bytes(16), // binary(16) 'ticket_nonce' => random_bytes(16), // binary(16), nullable in schema but we'll fill it 'signature' => null, diff --git a/phpunit.xml b/phpunit.xml index 7aedc2e..08c683b 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -21,7 +21,6 @@ - diff --git a/postcss.config.js b/postcss.config.js index 49c0612..6658cd9 100644 --- a/postcss.config.js +++ b/postcss.config.js @@ -1,6 +1,5 @@ export default { plugins: { - tailwindcss: {}, autoprefixer: {}, }, }; diff --git a/tests/Feature/CheckinControllerTest.php b/tests/Feature/CheckinControllerTest.php new file mode 100644 index 0000000..90d1297 --- /dev/null +++ b/tests/Feature/CheckinControllerTest.php @@ -0,0 +1,58 @@ +user = User::factory()->create(); + $this->event = Event::factory()->create(); + $this->ticket = Ticket::factory()->create([ + 'event_id' => $this->event->id + ]); + } + + protected function doCheckIn() + { + return $this->actingAs($this->user)->post(route('event.check-in.store', ['event' => $this->event->uuid]), [ + 'asset_id' => $this->ticket->asset_id, + 'ticket_code' => Uuid::fromBytes($this->ticket->ticket_nonce)->toString(), + ]); + } + + /** @test */ + public function test_can_checkin_ticket() + { + $this->setup_test(); + $response = $this->doCheckIn(); + + $response->assertStatus(200); + } + + /** @test */ + public function test_checkin_fails_if_ticket_already_checked_in() + { + $this->setup_test(); + $response = $this->doCheckIn(); + + $response->assertStatus(200); + + $response = $this->doCheckIn(); + + // Ensure we get a 400 response because the ticket is already checked in + $response->assertStatus(400); // 400 is typically used for invalid requests + $response->assertJson(['message' => 'Ticket has already been checked in!']); + } + +} diff --git a/tests/Feature/CheckoutControllerTest.php b/tests/Feature/CheckoutControllerTest.php new file mode 100644 index 0000000..81611b8 --- /dev/null +++ b/tests/Feature/CheckoutControllerTest.php @@ -0,0 +1,63 @@ +user = User::factory()->create(); + $this->event = Event::factory()->create(); + $this->ticket = Ticket::factory()->create([ + 'event_id' => $this->event->id + ]); + } + + protected function doCheckIn() + { + return $this->actingAs($this->user)->post(route('event.check-in.store', ['event' => $this->event->uuid]), [ + 'asset_id' => $this->ticket->asset_id, + 'ticket_code' => Uuid::fromBytes($this->ticket->ticket_nonce)->toString(), + ]); + } + + protected function doCheckOut() + { + return $this->actingAs($this->user)->post(route('event.check-out.store', ['event' => $this->event->uuid]), [ + 'asset_id' => $this->ticket->asset_id, + 'ticket_code' => Uuid::fromBytes($this->ticket->ticket_nonce)->toString(), + ]); + } + + /** @test */ + public function cannot_checkout_ticket_that_is_not_checked_in() + { + $this->setup_test(); + $response = $this->doCheckOut(); + +// dd($response); + + $response->assertStatus(400); + $response->assertJson(['message' => 'Ticket has not been checked in!']); + } + + /** @test */ + public function can_checkout_ticket_if_checked_in() + { + $this->setup_test(); + $response = $this->doCheckIn(); + + $response->assertStatus(200); + + $response = $this->doCheckOut(); + + $response->assertStatus(200); + } + +} diff --git a/tests/Unit,Features/Controller/CreateNewUserTest.php b/tests/Feature/CreateNewUserTest.php similarity index 92% rename from tests/Unit,Features/Controller/CreateNewUserTest.php rename to tests/Feature/CreateNewUserTest.php index 48b751f..13f60c2 100644 --- a/tests/Unit,Features/Controller/CreateNewUserTest.php +++ b/tests/Feature/CreateNewUserTest.php @@ -1,13 +1,12 @@ actingAs($user); $payload = [ - 'uuid' => (string) \Str::uuid(), + 'uuid' => (string)\Str::uuid(), 'team_id' => $user->currentTeam->id, 'user_id' => $user->id, 'name' => 'Test Event', @@ -32,6 +30,7 @@ public function user_can_store_new_event() 'location' => 'Test City', 'event_start' => '10:00', 'event_end' => '12:00', + 'is_public' => true, ]; $response = $this->post(route('manage-event.store'), $payload); @@ -39,6 +38,7 @@ public function user_can_store_new_event() $this->assertDatabaseHas('events', [ 'name' => 'Test Event', + 'user_id' => $user->id, 'team_id' => $user->currentTeam->id, ]); } @@ -47,7 +47,7 @@ public function user_can_store_new_event() public function user_can_update_event() { $user = User::factory()->withPersonalTeam()->create(); - $this->actingAs($user); + $this->withSession([])->actingAs($user); $event = Event::factory()->create([ 'team_id' => $user->currentTeam->id, @@ -69,7 +69,6 @@ public function user_can_update_event() $response = $this->put(route('manage-event.update', $event->uuid), $payload); $response->assertRedirect(route('manage-event.edit', $event->uuid)); - $this->assertDatabaseHas('events', [ 'uuid' => $event->uuid, 'name' => 'Updated Event Name', diff --git a/tests/Feature/EventCheckinControllerTest.php b/tests/Feature/EventCheckinControllerTest.php new file mode 100644 index 0000000..eb2d730 --- /dev/null +++ b/tests/Feature/EventCheckinControllerTest.php @@ -0,0 +1,72 @@ +create(); + $team = Team::factory()->for($user, 'owner')->create(); + + $events = Event::factory() + ->count(3) + ->for($team) + ->for($user) + ->create(); + + $this->actingAs($user); + + $response = $this->get(route('scan-tickets.index')); + $response->assertOk(); + $response->assertInertia(fn(Assert $page) => $page + ->component('ScanTickets/Index') + ->where('team.id', $team->id) + ->where('team.name', $team->name) + ->has('events', 3) + ->has('events.0', fn(Assert $event) => $event + ->hasAll([ + 'uuid', + 'name', + 'event_date', + ]) + ) + ); + } + + /** @test */ + public function show_displays_single_event() + { + $user = User::factory()->create(); + $team = Team::factory()->for($user, 'owner')->create(); + + $events = Event::factory() + ->count(3) + ->for($team) + ->for($user) + ->create(); + + $this->actingAs($user); + + $event = $events->random(); + + $response = $this->get(route('scan-tickets.show', ['scan_ticket' => $event->uuid])); + $response->assertOk(); + $response->assertInertia(fn(Assert $page) => $page + ->component('ScanTickets/Show') + ->has('event') + ->where('event.uuid', $event->uuid) + ); + } +} diff --git a/tests/Unit,Features/Controller/EventControllerTest.php b/tests/Feature/EventControllerTest.php similarity index 58% rename from tests/Unit,Features/Controller/EventControllerTest.php rename to tests/Feature/EventControllerTest.php index e17769f..84adac2 100644 --- a/tests/Unit,Features/Controller/EventControllerTest.php +++ b/tests/Feature/EventControllerTest.php @@ -6,16 +6,16 @@ use App\Models\User; use Illuminate\Foundation\Testing\RefreshDatabase; use Tests\TestCase; -use App\Models\Policy; +use Inertia\Testing\AssertableInertia as Assert; class EventControllerTest extends TestCase { use RefreshDatabase; /** @test */ - public function test_create_event() + public function test_user_can_create_event() { - $user = User::factory()->create(); + $user = User::factory()->withPersonalTeam()->create(); $this->actingAs($user); $eventData = [ @@ -24,24 +24,28 @@ public function test_create_event() 'start_date_time' => '2025-06-01 09:00:00', 'end_date_time' => '2025-06-01 17:00:00', 'location' => 'Test Venue TBA', + 'nonce_valid_for_minutes' => 15, + 'hodl_asset' => 1, ]; $response = $this->post(route('manage-event.store'), $eventData); - $response->assertStatus(302); - $this->assertDatabaseHas('events', ['name' => 'New Event']); + $this->assertDatabaseHas('events', ['name' => 'Gatekeeper Test Event']); } /** @test */ - public function test_view_event() + public function test_owner_can_view_manage_event_page() { - $event = Event::factory()->create(); $user = User::factory()->create(); + $event = Event::factory()->for($user)->create(); $this->actingAs($user); $response = $this->get(route('manage-event.show', $event->uuid)); $response->assertStatus(200); - $response->assertViewIs('ManageEvent/Show'); + $response->assertInertia(fn(Assert $page) => $page + ->component('ManageEvent/Show') + ->has('event') + ->where('event.uuid', $event->uuid)); } } diff --git a/tests/Feature/EventPolicyControllerTest.php b/tests/Feature/EventPolicyControllerTest.php new file mode 100644 index 0000000..40fbf00 --- /dev/null +++ b/tests/Feature/EventPolicyControllerTest.php @@ -0,0 +1,47 @@ +user = User::factory()->withPersonalTeam()->create(); + $this->event = Event::factory(['user_id' => $this->user->id, 'team_id' => $this->user->currentTeam->id])->create(); + $this->policy = Policy::factory(['user_id' => $this->user->id, 'team_id' => $this->user->currentTeam->id])->create(); + } + + /** @test */ + public function it_can_attach_policy_to_event() + { + $this->setup_test(); + + $this->actingAs($this->user); + + $response = $this->post(route('event.policy.store', [$this->event->uuid]), ['policy_id' => $this->policy->id]); + + $response->assertStatus(303); + } + + /** @test */ + public function it_can_detach_policy_from_event() + { + $this->setup_test(); + + $this->actingAs($this->user); + + $response = $this->delete(route('event.policy.destroy', [$this->event->uuid, $this->policy->id])); + + $response->assertStatus(303); + + } +} diff --git a/tests/Unit,Features/Controller/EventPublicViewTest.php b/tests/Feature/EventPublicViewTest.php similarity index 100% rename from tests/Unit,Features/Controller/EventPublicViewTest.php rename to tests/Feature/EventPublicViewTest.php diff --git a/tests/Unit,Features/Controller/ImageControllerTest.php b/tests/Feature/ImageControllerTest.php similarity index 100% rename from tests/Unit,Features/Controller/ImageControllerTest.php rename to tests/Feature/ImageControllerTest.php diff --git a/tests/Unit,Features/Controller/ResetUserPasswordTest.php b/tests/Feature/ResetUserPasswordTest.php similarity index 94% rename from tests/Unit,Features/Controller/ResetUserPasswordTest.php rename to tests/Feature/ResetUserPasswordTest.php index 2c05141..51c098b 100644 --- a/tests/Unit,Features/Controller/ResetUserPasswordTest.php +++ b/tests/Feature/ResetUserPasswordTest.php @@ -1,6 +1,6 @@ withPersonalTeam()->create(); + $team = $user->currentTeam; + + $this->actingAs($user); + +// $this->withoutExceptionHandling(); + + $response = $this->get(route('teams.show', $team)); + +// $response->dump(); + + $response->assertOk(); + + $response->assertInertia(fn (Assert $page) => $page + ->has('team') + ->where('team.user_id', $user->id) + ); + } +} diff --git a/tests/Unit,Features/Controller/TeamPolicyControllerTest.php b/tests/Feature/TeamPolicyControllerTest.php similarity index 52% rename from tests/Unit,Features/Controller/TeamPolicyControllerTest.php rename to tests/Feature/TeamPolicyControllerTest.php index e2e4e64..e819f52 100644 --- a/tests/Unit,Features/Controller/TeamPolicyControllerTest.php +++ b/tests/Feature/TeamPolicyControllerTest.php @@ -2,27 +2,30 @@ namespace Tests\Feature; -use App\Models\Team; +use App\Models\User; use Illuminate\Foundation\Testing\RefreshDatabase; use Tests\TestCase; -use App\Models\Policy; class TeamPolicyControllerTest extends TestCase { use RefreshDatabase; + /** @test */ public function test_store_creates_policy_for_team() { - $team = Team::factory()->create(); + $user = User::factory()->withPersonalTeam()->create(); + $team = $user->currentTeam; + + $this->actingAs($user); $data = [ - 'name' => 'Test Policy', - 'description' => 'Sample description', - // Add all required fields of StorePolicyRequest validation here + 'name' => 'Team Policy Test Policy', + 'hash' => bin2hex(random_bytes(28)), + 'team_id' => $team->id, + 'user_id' => $team->user_id, ]; - $response = $this->post(route('teams.policies.store', $team), $data); - + $response = $this->post(route('team.policy.store', $team), $data); $response->assertStatus(303); $this->assertDatabaseHas('policies', [ 'team_id' => $team->id, diff --git a/tests/Feature/TicketControllerTest.php b/tests/Feature/TicketControllerTest.php new file mode 100644 index 0000000..38e555a --- /dev/null +++ b/tests/Feature/TicketControllerTest.php @@ -0,0 +1,121 @@ + now()->subDay(1), + 'end_date_time' => now()->addDay(1), + ])->create(); + + $event->policies()->save(Policy::factory(['hash' => self::$POLICY_ID])->create()); + + $response = $this->post(route('ticket.store'), [ + 'event_uuid' => $event->uuid, + 'policy_id' => self::$POLICY_ID, + 'asset_id' => self::$ASSET_ID, + 'stake_key' => self::$STAKE_KEY, + ]); + + + $response->assertOk(); + $response->assertJsonStructure(['id', 'nonce']); + } + + /** @test */ + public function cannot_create_ticket_challenge_before_ticketing_window() + { + $event = Event::factory([ + 'start_date_time' => now()->addDay(2), + 'end_date_time' => now()->addDay(3), + ])->create(); + + $event->policies()->save(Policy::factory(['hash' => self::$POLICY_ID])->create()); + + $response = $this->post(route('ticket.store'), [ + 'event_uuid' => $event->uuid, + 'policy_id' => self::$POLICY_ID, + 'asset_id' => bin2hex(random_bytes(16)), + 'stake_key' => self::$STAKE_KEY, + ]); + + $response->assertStatus(409); + $response->assertJson(["message" => "Sorry, ticketing is closed for this event."]); + } + + /** @test */ + public function cannot_create_ticket_challenge_after_ticketing_window() + { + $event = Event::factory([ + 'start_date_time' => now()->subDay(3), + 'end_date_time' => now()->subDay(1), + ])->create(); + + $event->policies()->save(Policy::factory(['hash' => self::$POLICY_ID])->create()); + + $response = $this->post(route('ticket.store'), [ + 'event_uuid' => $event->uuid, + 'policy_id' => self::$POLICY_ID, + 'asset_id' => bin2hex(random_bytes(16)), + 'stake_key' => self::$STAKE_KEY, + ]); + + $response->assertStatus(409); + $response->assertJson(["message" => "Sorry, ticketing is closed for this event."]); + } + + /** @test */ + public function validate_signature_and_return_qr_value() + { + $event = Event::factory([ + 'name' => 'Cardano Community Meetup', + 'uuid' => '36e1e81e-fe27-4967-8288-46eaed7da7b0', + 'start_date_time' => now()->subDay(1), + 'end_date_time' => now()->addDay(1), + ])->create(); + + $policy = Policy::factory(['hash' => self::$POLICY_ID])->create(); + + $event->policies()->save($policy); + + $ticket = Ticket::factory([ + 'asset_id' => self::$ASSET_ID, + 'stake_key' => self::$STAKE_KEY, + 'signature_nonce' => Uuid::fromString('e19801fb-649f-41e5-b509-469c63935582')->getBytes(), + 'created_at' => '2025-11-18 22:07:42', + 'updated_at' => '2025-11-18 22:07:42' + ])->for($event)->for($policy)->create(); + + $response = $this->put(route('ticket.update', [Uuid::fromBytes($ticket->signature_nonce)->toString()]), [ + 'event_uuid' => '36e1e81e-fe27-4967-8288-46eaed7da7b0', + 'policy_id' => '40fa2aa67258b4ce7b5782f74831d46a84c59a0ff0c28262fab21728', + 'asset_id' => '436c61794e6174696f6e35393835', + 'stake_key' => 'stake1uxekfkqgs4ye2wnf38e7x8uy006wvleq3etu8t35gqmdmnq5v4rks', + 'nonce' => '7b2261737365744964223a2234333663363137393465363137343639366636653335333933383335222c226576656e744964223a2233366531653831652d666532372d343936372d383238382d343665616564376461376230222c226576656e744e616d65223a2243617264616e6f20436f6d6d756e697479204d6565747570222c22706f6c6963794964223a223430666132616136373235386234636537623537383266373438333164343661383463353961306666306332383236326661623231373238222c227369676e4279223a22323032352d31312d31385432323a32323a34322b30303a3030222c227374616b654b6579223a227374616b65317578656b666b71677334796532776e66333865377838757930303677766c6571336574753874333567716d646d6e71357634726b73222c227469636b65744964223a2265313938303166622d363439662d343165352d623530392d343639633633393335353832222c2274797065223a22476174654b65657065725469636b6574222c2276657273696f6e223a22312e302e30227d', + 'signature' => [ + 'key' => 'a401010327200621582073b46827a5032a0d31f12aa6e58dd4b5dc9b98e77ebd1cee852f31598c65cfca', + 'signature' => '84582aa201276761646472657373581de1b364d8088549953a6989f3e31f847bf4e67f208e57c3ae344036ddcca166686173686564f45901947b2261737365744964223a2234333663363137393465363137343639366636653335333933383335222c226576656e744964223a2233366531653831652d666532372d343936372d383238382d343665616564376461376230222c226576656e744e616d65223a2243617264616e6f20436f6d6d756e697479204d6565747570222c22706f6c6963794964223a223430666132616136373235386234636537623537383266373438333164343661383463353961306666306332383236326661623231373238222c227369676e4279223a22323032352d31312d31385432323a32323a34322b30303a3030222c227374616b654b6579223a227374616b65317578656b666b71677334796532776e66333865377838757930303677766c6571336574753874333567716d646d6e71357634726b73222c227469636b65744964223a2265313938303166622d363439662d343165352d623530392d343639633633393335353832222c2274797065223a22476174654b65657065725469636b6574222c2276657273696f6e223a22312e302e30227d5840311d6519a24127b9dbdf9b2c8f7e097f79231d3711c55f32ed73ab2b647a7f30aff63a4f59922943eb01669cf3b3d042ddd768c90c66f3e8b4f308c643a4ec0b' + ] + ]); + + $response->assertOk(); + $response->assertJsonStructure(['qr_value', 'security_code']); + } +} diff --git a/tests/Feature/UpdateUserPasswordTest.php b/tests/Feature/UpdateUserPasswordTest.php new file mode 100644 index 0000000..1420c6e --- /dev/null +++ b/tests/Feature/UpdateUserPasswordTest.php @@ -0,0 +1,44 @@ +create(); + + $user->password = Hash::make('password'); + $user->save(); + $user->refresh(); + + $this->assertTrue( + Hash::check('password', $user->password), + 'Sanity check failed: stored password is not "password".' + ); + + $input = [ + 'current_password' => 'password', + 'password' => 'this is my password!', + 'password_confirmation' => 'this is my password!', + ]; + + $this->actingAs($user, 'web'); + + $action = new UpdateUserPassword(); + $action->update($user, $input); + + $user->refresh(); + + $this->assertTrue( + Hash::check($input['password'], $user->password), + 'New password was not saved correctly.' + ); + } +} diff --git a/tests/Unit,Features/Controller/UpdateUserProfileInformationTest.php b/tests/Feature/UpdateUserProfileInformationTest.php similarity index 83% rename from tests/Unit,Features/Controller/UpdateUserProfileInformationTest.php rename to tests/Feature/UpdateUserProfileInformationTest.php index ac1db12..aa15a57 100644 --- a/tests/Unit,Features/Controller/UpdateUserProfileInformationTest.php +++ b/tests/Feature/UpdateUserProfileInformationTest.php @@ -1,15 +1,13 @@ loadEnvironmentFrom('.env.testing'); - - $app->make(Kernel::class)->bootstrap(); - - return $app; + $this->seed(DemoEventSeeder::class); } } diff --git a/tests/Unit,Features/Controller/CheckinControllerTest.php b/tests/Unit,Features/Controller/CheckinControllerTest.php deleted file mode 100644 index b3fb7f1..0000000 --- a/tests/Unit,Features/Controller/CheckinControllerTest.php +++ /dev/null @@ -1,47 +0,0 @@ -post(route('event.check-in.store', ['event' => 1]), [ - 'ticket_id' => 1, - 'user_id' => 1, - ]); - - $response->assertStatus(302); - } - - /** @test */ - public function test_checkin_ticket_already_checked_in() - { - // Create a test event and ticket - $event = Event::factory()->create(); - $ticket = Ticket::factory()->create([ - 'event_id' => $event->id, - 'status' => 'checked_in', - ]); - - // Make the POST request to the check-in route - $response = $this->post(route('event.check-in.store', ['event' => $event->uuid]), [ - 'ticket_code' => $ticket->ticket_nonce, - 'asset_id' => $ticket->asset_id, - ]); - - // Ensure we get a 400 response because the ticket is already checked in - $response->assertStatus(400); // 400 is typically used for invalid requests - $response->assertJson(['message' => 'Ticket has already been checked in!']); - } - -} diff --git a/tests/Unit,Features/Controller/CheckoutControllerTest.php b/tests/Unit,Features/Controller/CheckoutControllerTest.php deleted file mode 100644 index 9735297..0000000 --- a/tests/Unit,Features/Controller/CheckoutControllerTest.php +++ /dev/null @@ -1,30 +0,0 @@ -post(route('event.check-out.store', ['event' => 1]), [ - 'ticket_id' => 1, - 'user_id' => 1, - ]); - - $response->assertStatus(302); - } - - /** @test */ - public function checkout_ticket_not_checked_in() - { - $response = $this->post(route('event.check-out.store', ['event' => 1]), [ - 'ticket_id' => 1, - 'user_id' => 1, - ]); - - $response->assertStatus(400); // Assuming 400 for non-checked-in ticket - } -} diff --git a/tests/Unit,Features/Controller/EventCheckinControllerTest.php b/tests/Unit,Features/Controller/EventCheckinControllerTest.php deleted file mode 100644 index 709ec59..0000000 --- a/tests/Unit,Features/Controller/EventCheckinControllerTest.php +++ /dev/null @@ -1,27 +0,0 @@ -get(route('event.check-in.index', ['event' => 1])); - - $response->assertStatus(200); - $response->assertViewHas('events'); - } - - /** @test */ - public function show_displays_single_event() - { - $response = $this->get(route('event.check-in.show', ['event' => 1])); - - $response->assertStatus(200); - $response->assertViewHas('event'); - } -} diff --git a/tests/Unit,Features/Controller/EventPolicyControllerTest.php b/tests/Unit,Features/Controller/EventPolicyControllerTest.php deleted file mode 100644 index 2f944ac..0000000 --- a/tests/Unit,Features/Controller/EventPolicyControllerTest.php +++ /dev/null @@ -1,32 +0,0 @@ -create(); - $policy = Policy::factory()->create(); - - $response = $this->post(route('event.policy.store', [$event, $policy])); - - $response->assertRedirect(route('event.show', $event)); - } - - /** @test */ - public function it_can_detach_policy_from_event() - { - $event = Event::factory()->create(); - $policy = Policy::factory()->create(); - - $response = $this->delete(route('event.policy.destroy', [$event, $policy])); - - $response->assertRedirect(route('event.show', $event)); - } -} diff --git a/tests/Unit,Features/Controller/PolicyControllerTest.php b/tests/Unit,Features/Controller/PolicyControllerTest.php deleted file mode 100644 index 0f4e645..0000000 --- a/tests/Unit,Features/Controller/PolicyControllerTest.php +++ /dev/null @@ -1,39 +0,0 @@ -get(route('policy.index')); - - $response->assertStatus(200); - } - - /** @test */ - public function store_creates_policy() - { - $response = $this->post(route('policy.store'), [ - 'name' => 'Test Policy', - ]); - - $response->assertRedirect(route('policy.index')); - $this->assertDatabaseHas('policies', ['name' => 'Test Policy']); - } - - /** @test */ - public function show_returns_policy() - { - $policy = Policy::factory()->create(); - - $response = $this->get(route('policy.show', $policy)); - - $response->assertStatus(200); - $response->assertViewHas('policy'); - } -} diff --git a/tests/Unit,Features/Controller/TeamControllerTest.php b/tests/Unit,Features/Controller/TeamControllerTest.php deleted file mode 100644 index 1317ffa..0000000 --- a/tests/Unit,Features/Controller/TeamControllerTest.php +++ /dev/null @@ -1,21 +0,0 @@ -create(); - - $response = $this->get(route('teams.show', $team->id)); - - $response->assertStatus(200); - $response->assertSessionHas('flash.banner', "You're looking at a team pal!"); - $response->assertSee($team->name); - } -} diff --git a/tests/Unit,Features/Controller/TicketControllerTest.php b/tests/Unit,Features/Controller/TicketControllerTest.php deleted file mode 100644 index 3d32b06..0000000 --- a/tests/Unit,Features/Controller/TicketControllerTest.php +++ /dev/null @@ -1,32 +0,0 @@ -post(route('ticket.store'), [ - 'asset_id' => 'asset123', - 'stake_key' => 'stake123', - ]); - - $response->assertStatus(201); - $response->assertJsonStructure(['nonce']); - } - - /** @test */ - public function update_validates_signature_and_returns_qr_value() - { - $response = $this->put(route('ticket.update', ['ticket' => 1]), [ - 'signature' => 'valid-signature', - ]); - - $response->assertStatus(200); - $response->assertJsonStructure(['qr']); - } -} diff --git a/tests/Unit,Features/Controller/UpdateUserPasswordTest.php b/tests/Unit,Features/Controller/UpdateUserPasswordTest.php deleted file mode 100644 index 891e4e5..0000000 --- a/tests/Unit,Features/Controller/UpdateUserPasswordTest.php +++ /dev/null @@ -1,34 +0,0 @@ - ['required', 'string'], - 'password' => ['required', 'string', new PasswordRule, 'confirmed'], - ])->after(function ($validator) use ($user, $input) { - if (! Hash::check($input['current_password'], $user->password)) { - $validator->errors()->add( - 'current_password', - __('The provided password does not match your current password.') - ); - } - })->validate(); - - $user->forceFill([ - 'password' => Hash::make($input['password']), - ])->save(); - } -} diff --git a/tests/Unit,Features/model/EventTest.php b/tests/Unit,Features/model/EventTest.php deleted file mode 100644 index c3b7cbe..0000000 --- a/tests/Unit,Features/model/EventTest.php +++ /dev/null @@ -1,24 +0,0 @@ -create(['event_date' => now()->addDays(1)]); - - $this->assertTrue($event->isTicketingActive()); - - $event->event_date = now()->subDays(1); - - $this->assertFalse($event->isTicketingActive()); - } -} diff --git a/tests/Unit,Features/model/CheckinTest.php b/tests/Unit/CheckinTest.php similarity index 93% rename from tests/Unit,Features/model/CheckinTest.php rename to tests/Unit/CheckinTest.php index f62d3ca..f24a719 100644 --- a/tests/Unit,Features/model/CheckinTest.php +++ b/tests/Unit/CheckinTest.php @@ -1,11 +1,10 @@ artisan('migrate:fresh --seed'); // Test if the necessary data is seeded $this->assertDatabaseHas('users', [ - 'email' => 'admin@example.com', + 'email' => 'demo@example.com', ]); } } diff --git a/tests/Unit/EventTest.php b/tests/Unit/EventTest.php new file mode 100644 index 0000000..bf1dd1f --- /dev/null +++ b/tests/Unit/EventTest.php @@ -0,0 +1,30 @@ +create(['start_date_time' => now()->subDays(1), 'end_date_time' => now()->addDays(1)]); + $this->assertTrue($event->isTicketingActive()); + $event->start_date_time = now()->addDays(1); + $this->assertFalse($event->isTicketingActive()); + } + + /** @test */ + public function event_ticketing_active_when_end_date_greater_than_now() + { + $event = Event::factory()->create(['start_date_time' => now()->subDays(2), 'end_date_time' => now()->addDays(1)]); + $this->assertTrue($event->isTicketingActive()); + $event->end_date_time = now()->subDays(1); + $this->assertFalse($event->isTicketingActive()); + } +}