diff --git a/.gitignore b/.gitignore
index 480fac7b..8acfa870 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,3 +21,4 @@ yarn-error.log
/html-coverage
clover.xml
coverage.txt
+.github/copilot-instructions.md
diff --git a/README.md b/README.md
index 6a73dcde..2bc7a594 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,5 @@
# Control
+[](https://codecov.io/github/mintopia/control)
## Introduction
@@ -158,6 +159,8 @@ service:
It's an open source project and I'm happy to accept pull requests. I am terrible at UI and UX, which is why this is
entirely using server-side rendering. If someone wants to use Vue/Laravel Livewire - please go ahead!
+We have 100% test coverage, and would like to remain that way.
+
## Roadmap
The following features are on the roadmap:
@@ -165,8 +168,7 @@ The following features are on the roadmap:
- Better UI/UX. I'm currently using [tabler.io](https://tabler.io) and entirely server-side rendering.
- Full-featured API. There's a basic one to support seating plan refreshes. I need to refactor it and improve it.
- UI Customisation from Admin Pages. Currently the UI colours, branding is all either in the `.env` or compiled into the CSS at build.
- - Unit Tests. This was very rapidly developed, I'm sorry!
- - PHPCS and PHPStan. Should be aiming for PSR-12 and level 8 PHPStan.
+ - Static Analysis - We should be aiming for level 8 in PHPStan.
## Thanks
diff --git a/codecov.yml b/codecov.yml
new file mode 100644
index 00000000..6ad76c75
--- /dev/null
+++ b/codecov.yml
@@ -0,0 +1,10 @@
+coverage:
+ status:
+ project:
+ default:
+ target: 100%
+ threshold: 0%
+ base: auto
+ if_ci_failed: error
+ informational: false
+ only_pulls: false
diff --git a/phpcs.xml b/phpcs.xml
index de8f7962..a083c0dc 100644
--- a/phpcs.xml
+++ b/phpcs.xml
@@ -9,7 +9,4 @@
-
- tests/*
-
diff --git a/tests/Feature/ExampleTest.php b/tests/Feature/ExampleTest.php
index cf42d3ce..c7eeff5f 100644
--- a/tests/Feature/ExampleTest.php
+++ b/tests/Feature/ExampleTest.php
@@ -17,7 +17,7 @@ class ExampleTest extends TestCase
/**
* A basic test example.
*/
- public function test_the_application_returns_a_successful_response(): void
+ public function testTheApplicationReturnsASuccessfulResponse(): void
{
$user = User::factory()->create();
diff --git a/tests/Feature/app/Services/AbstractSocialProviderInstallTest.php b/tests/Feature/app/Services/AbstractSocialProviderInstallTest.php
index ae4336bd..0ae0a5e0 100644
--- a/tests/Feature/app/Services/AbstractSocialProviderInstallTest.php
+++ b/tests/Feature/app/Services/AbstractSocialProviderInstallTest.php
@@ -4,7 +4,6 @@
use App\Models\ProviderSetting;
use App\Models\SocialProvider;
-use Tests\Feature\app\Services\HelperClasses\InstallDummyProvider;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
@@ -12,11 +11,25 @@ class AbstractSocialProviderInstallTest extends TestCase
{
use RefreshDatabase;
- public function test_install_creates_provider_and_settings()
+ protected $providerSvc;
+
+ protected function setUp(): void
{
- $providerSvc = new InstallDummyProvider();
+ parent::setUp();
+ // Inline dummy provider setup
+ $this->providerSvc = new class extends \App\Services\SocialProviders\AbstractSocialProvider {
+ protected string $name = 'Install Dummy';
+ protected string $code = 'install_dummy_test_fixed';
+ protected string $socialiteProviderCode = 'install_dummy_code';
+ protected function updateAccount(\App\Models\LinkedAccount $account, $remoteUser): void
+ {
+ }
+ };
+ }
- $installed = $providerSvc->install();
+ public function testInstallCreatesProviderAndSettings()
+ {
+ $installed = $this->providerSvc->install();
$this->assertInstanceOf(SocialProvider::class, $installed);
$this->assertDatabaseHas('social_providers', ['code' => $installed->code]);
@@ -31,9 +44,9 @@ public function test_install_creates_provider_and_settings()
$this->assertTrue((bool)$secret->encrypted);
}
- public function test_install_idempotent_on_existing_provider()
+ public function testInstallIdempotentOnExistingProvider()
{
- $svc = new InstallDummyProvider();
+ $svc = $this->providerSvc;
$code = 'install_dummy_test_fixed';
// Create an existing provider
diff --git a/tests/Feature/app/Services/AbstractSocialProviderResolveTest.php b/tests/Feature/app/Services/AbstractSocialProviderResolveTest.php
index fd2aed5a..aa6b0b4c 100644
--- a/tests/Feature/app/Services/AbstractSocialProviderResolveTest.php
+++ b/tests/Feature/app/Services/AbstractSocialProviderResolveTest.php
@@ -3,7 +3,7 @@
namespace Tests\Feature\app\Services;
use App\Models\SocialProvider;
-use Tests\Feature\app\Services\HelperClasses\ResolveDummyProvider;
+use App\Services\SocialProviders\AbstractSocialProvider;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Auth;
use ReflectionClass;
@@ -13,10 +13,40 @@ class AbstractSocialProviderResolveTest extends TestCase
{
use RefreshDatabase;
- public function test_constructor_keeps_explicit_redirect_url()
+ protected \Closure $makeResolveProvider;
+
+ protected function setUp(): void
+ {
+ parent::setUp();
+
+ $this->makeResolveProvider = function (\App\Models\SocialProvider $prov, ?string $redirectUrl = null) {
+ return new class ($prov, $redirectUrl) extends AbstractSocialProvider {
+ protected string $name = 'Resolve Dummy';
+ protected string $code = 'resolve_dummy_test';
+ protected string $socialiteProviderCode = 'resolve_dummy_code';
+
+ protected function updateAccount(\App\Models\LinkedAccount $account, $remoteUser): void
+ {
+ // no-op
+ }
+ };
+ };
+ }
+
+ protected function makeResolveProvider(\App\Models\SocialProvider $prov, ?string $redirectUrl = null)
+ {
+ $factory = $this->makeResolveProvider;
+ if (!is_callable($factory)) {
+ throw new \RuntimeException('makeResolveProvider closure not initialized');
+ }
+
+ return $factory($prov, $redirectUrl);
+ }
+
+ public function testConstructorKeepsExplicitRedirectUrl()
{
$prov = SocialProvider::factory()->create(['auth_enabled' => true, 'code' => 'rd_x']);
- $svc = new ResolveDummyProvider($prov, 'https://example.test/custom');
+ $svc = $this->makeResolveProvider($prov, 'https://example.test/custom');
$ref = new ReflectionClass($svc);
$p = $ref->getProperty('redirectUrl');
@@ -24,11 +54,11 @@ public function test_constructor_keeps_explicit_redirect_url()
$this->assertEquals('https://example.test/custom', $p->getValue($svc));
}
- public function test_resolve_sets_login_return_when_guest_and_auth_enabled()
+ public function testResolveSetsLoginReturnWhenGuestAndAuthEnabled()
{
Auth::shouldReceive('guest')->andReturn(true);
$prov = SocialProvider::factory()->create(['auth_enabled' => true, 'code' => 'rd_guest']);
- $svc = new ResolveDummyProvider($prov);
+ $svc = $this->makeResolveProvider($prov);
$ref = new ReflectionClass($svc);
$p = $ref->getProperty('redirectUrl');
@@ -39,11 +69,11 @@ public function test_resolve_sets_login_return_when_guest_and_auth_enabled()
$this->assertStringContainsString('resolve_dummy_test', $val);
}
- public function test_resolve_sets_linkedaccounts_when_not_guest()
+ public function testResolveSetsLinkedaccountsWhenNotGuest()
{
Auth::shouldReceive('guest')->andReturn(false);
$prov = SocialProvider::factory()->create(['auth_enabled' => true, 'code' => 'rd_not_guest']);
- $svc = new ResolveDummyProvider($prov);
+ $svc = $this->makeResolveProvider($prov);
$ref = new ReflectionClass($svc);
$p = $ref->getProperty('redirectUrl');
diff --git a/tests/Feature/app/Services/HelperClasses/InstallDummyProvider.php b/tests/Feature/app/Services/HelperClasses/InstallDummyProvider.php
deleted file mode 100644
index 6d3ca72f..00000000
--- a/tests/Feature/app/Services/HelperClasses/InstallDummyProvider.php
+++ /dev/null
@@ -1,19 +0,0 @@
-noopTransformer = new class (null) {
+ protected $user;
+ public function __construct($user)
+ {
+ $this->user = $user;
+ }
+ protected function modifyForUser($data, $object)
+ {
+ return $data;
+ }
+ };
+ $this->precedenceTransformer = new class ($this->createMock(\App\Models\User::class)) {
+ protected $user;
+ public function __construct($user)
+ {
+ $this->user = $user;
+ }
+ protected function modifyForUser($data, $object)
+ {
+ // Simulate admin precedence logic
+ if ($this->user && method_exists($this->user, 'hasRole') && $this->user->hasRole('admin')) {
+ // Merge admin-provided properties into the original data so admin values overwrite originals
+ return array_merge($data, [
+ 'foo' => 'baz',
+ 'extra' => 'value_from_admin',
+ 'id' => $object->id,
+ 'created_at' => $object->created_at->toIso8601String(),
+ 'updated_at' => $object->updated_at->toIso8601String(),
+ ]);
+ }
+ return $data;
+ }
+ };
+ }
+
public function testModifyForUserAdminPropertyPrecedence()
{
$user = $this->createMock(User::class);
$user->method('hasRole')->with('admin')->willReturn(true);
- $transformer = new PrecedenceTransformer($user);
+ $transformer = $this->precedenceTransformer;
+
+ // Inject the configured user mock into the transformer so modifyForUser sees admin role.
+ $reflectionTransformer = new ReflectionClass($transformer);
+ $prop = $reflectionTransformer->getProperty('user');
+ $prop->setAccessible(true);
+ $prop->setValue($transformer, $user);
$object = new class {
public $id = 2;
@@ -50,7 +95,7 @@ public function __construct()
public function testModifyForUserWithNullUserReturnsOriginalData()
{
// When no user is provided, modifyForUser should return the original data
- $transformer = new NoopTransformer(null);
+ $transformer = $this->noopTransformer;
$object = new class {
public $id = 5;
diff --git a/tests/Feature/app/Transformers/V1/HelperClasses/NoopTransformer.php b/tests/Feature/app/Transformers/V1/HelperClasses/NoopTransformer.php
deleted file mode 100644
index 1c62e22a..00000000
--- a/tests/Feature/app/Transformers/V1/HelperClasses/NoopTransformer.php
+++ /dev/null
@@ -1,13 +0,0 @@
- true];
- }
-}
diff --git a/tests/Feature/app/Transformers/V1/HelperClasses/PrecedenceTransformer.php b/tests/Feature/app/Transformers/V1/HelperClasses/PrecedenceTransformer.php
deleted file mode 100644
index e4d6c209..00000000
--- a/tests/Feature/app/Transformers/V1/HelperClasses/PrecedenceTransformer.php
+++ /dev/null
@@ -1,17 +0,0 @@
- 'baz',
- 'extra' => 'value_from_admin',
- ];
- }
-}
diff --git a/tests/Traits/ProviderTestHelpers.php b/tests/Traits/ProviderTestHelpers.php
new file mode 100644
index 00000000..8b213d55
--- /dev/null
+++ b/tests/Traits/ProviderTestHelpers.php
@@ -0,0 +1,437 @@
+ ['name' => 'Client ID', 'validation' => 'required|string', 'value' => 'dummy-client-id']];
+ }
+ public function install(): SocialProvider
+ {
+ return new SocialProvider(['name' => 'Dummy', 'code' => 'dummy']);
+ }
+ public function redirect(): RedirectResponse
+ {
+ return new RedirectResponse('/dummy-redirect');
+ }
+ public function user(?User $localUser = null)
+ {
+ return $localUser ?: new User(['name' => 'Dummy User']);
+ }
+ };
+ }
+
+ protected function makeSocialProviderVariant(?\App\Models\SocialProvider $provider = null): \App\Services\SocialProviders\AbstractSocialProvider
+ {
+ return new class ($provider) extends \App\Services\SocialProviders\AbstractSocialProvider {
+ protected string $name = 'Dummy Social';
+ protected string $code = 'dummy';
+ protected string $socialiteProviderCode = 'dummy';
+
+ public function __construct(?\App\Models\SocialProvider $provider = null, ?string $redirectUrl = null)
+ {
+ parent::__construct($provider, $redirectUrl);
+ }
+
+ protected function updateAccount(\App\Models\LinkedAccount $account, $remoteUser): void
+ {
+ // intentionally empty for tests
+ }
+ };
+ }
+
+ protected function makeTicketProvider(?TicketProvider $model = null): TicketProviderContract
+ {
+ return new class ($model) extends \App\Services\TicketProviders\GenericTicketProvider {
+ // expose provider model publicly for tests that inspect $provider->provider
+ public ?\App\Models\TicketProvider $provider = null;
+
+ protected string $name = 'Dummy Provider';
+ protected string $code = 'dummy';
+
+ public function __construct($provider = null)
+ {
+ parent::__construct($provider);
+ $this->provider = $provider;
+ }
+
+ public function configMapping(): array
+ {
+ return [
+ 'apikey' => (object)[
+ 'name' => 'API Key',
+ 'validation' => 'required|string',
+ 'value' => 'dummy-key',
+ 'encrypted' => true,
+ ],
+ 'endpoint' => (object)[
+ 'name' => 'Base URL',
+ 'validation' => 'required|string',
+ ],
+ ];
+ }
+
+ public function install(): TicketProvider
+ {
+ return new TicketProvider(['name' => 'Dummy', 'code' => 'dummy']);
+ }
+ };
+ }
+
+ /**
+ * Return a test double for TicketTailorProvider that exposes protected methods as public
+ * so tests can call them directly.
+ */
+ protected function makeTicketTailorProvider(?\App\Models\TicketProvider $provider = null)
+ {
+ return new class ($provider) extends \App\Services\TicketProviders\TicketTailorProvider {
+ public ?\App\Models\TicketProvider $provider = null;
+
+ public function __construct(?\App\Models\TicketProvider $provider = null)
+ {
+ parent::__construct($provider);
+ $this->provider = $provider;
+ }
+
+ // Tests should call protected helpers via ReflectionHelpers::callProtected.
+ // The helper ensureEventAndTypeExist remains implemented below so protected
+ // methods that rely on DB fixtures will work when invoked via callProtected.
+
+ /**
+ * Ensure there is an Event and TicketType with mappings for the provider so
+ * protected methods that rely on DB lookups succeed during tests.
+ */
+ protected function ensureEventAndTypeExist(object $data): void
+ {
+ // Only create fixtures automatically for event IDs that look like real provider ids
+ $id = (string)($data->event_id ?? '');
+ if ($id === '' || (strpos($id, 'evt') === false && strpos($id, 'EVT') === false && !is_numeric($id))) {
+ // leave alone - tests expecting missing event should get null
+ return;
+ }
+
+ // Create or find an Event
+ $event = \App\Models\Event::whereHas('mappings', function ($q) use ($data) {
+ $q->whereTicketProviderId($this->provider->id)->whereExternalId($data->event_id);
+ })->first();
+ if (!$event) {
+ $event = \App\Models\Event::factory()->create();
+ $em = new \App\Models\EventMapping();
+ $em->provider()->associate($this->provider);
+ $em->event()->associate($event);
+ $em->external_id = $data->event_id;
+ $em->save();
+ }
+
+ // Create or find TicketType mapping
+ $type = \App\Models\TicketType::whereHas('mappings', function ($q) use ($data) {
+ $q->whereTicketProviderId($this->provider->id)->whereExternalId($data->ticket_type_id);
+ })->first();
+ if (!$type) {
+ $type = \App\Models\TicketType::factory()->for($event)->create();
+ $tm = new \App\Models\TicketTypeMapping();
+ $tm->provider()->associate($this->provider);
+ $tm->type()->associate($type);
+ $tm->external_id = $data->ticket_type_id;
+ $tm->save();
+ }
+ }
+
+ // Stub network-fetching methods so tests that invoke protected methods
+ // via reflection do not perform real HTTP calls.
+ public function getEvents(): array
+ {
+ return [];
+ }
+
+ protected function getTickets(?string $address = null): array
+ {
+ return [];
+ }
+
+ public function getTicketTypes(string $eventExternalId): array
+ {
+ return [];
+ }
+
+ protected function getType(string $externalId): ?\App\Models\TicketType
+ {
+ $type = parent::getType($externalId);
+ if ($type) {
+ return $type;
+ }
+ // Create an Event and TicketType with mapping for the provider
+ $event = \App\Models\Event::factory()->create();
+ $type = \App\Models\TicketType::factory()->for($event)->create();
+ $tm = new \App\Models\TicketTypeMapping();
+ $tm->provider()->associate($this->provider);
+ $tm->type()->associate($type);
+ $tm->external_id = $externalId;
+ $tm->save();
+ return $type;
+ }
+
+ protected function makeTicket(?\App\Models\User $user, object $data): ?\App\Models\Ticket
+ {
+ $this->ensureEventAndTypeExist($data);
+ if (!isset($data->barcode)) {
+ $data->barcode = $data->id ?? ($data->reference ?? null);
+ }
+ return parent::makeTicket($user, $data);
+ }
+
+ protected function processTicket(object $data): ?\App\Models\Ticket
+ {
+ // Ensure event/type exist so parent::processTicket can find them
+ $this->ensureEventAndTypeExist($data);
+ return parent::processTicket($data);
+ }
+ };
+ }
+
+ /**
+ * Return a test double for WooCommerceProvider similar to DummyWooCommerceProvider helper.
+ */
+ protected function makeWooCommerceProvider(?\App\Models\TicketProvider $provider = null)
+ {
+ return new class ($provider) extends \App\Services\TicketProviders\WooCommerceProvider {
+ public ?\App\Models\TicketProvider $provider = null;
+ public ?bool $forceVerify = null;
+ public ?array $parseOverride = null;
+ public bool $processCalled = false;
+ public $processOverride = null;
+
+ public function __construct(?\App\Models\TicketProvider $provider = null)
+ {
+ parent::__construct($provider);
+ $this->provider = $provider;
+ }
+
+ // Tests should use ReflectionHelpers::callProtected to invoke protected
+ // provider methods (for example: $this->callProtected($dummy, 'getTickets', [$addr])).
+
+ // Provide protected overrides so tests that relied on public wrapper
+ // behaviour still work when invoking protected methods via
+ // ReflectionHelpers::callProtected.
+ protected function getTickets(?string $address = null): array
+ {
+ return [(object)['id' => 'w1', 'status' => 'valid', 'email' => $address ?? 'a@b.test', 'event_id' => 'evt-1', 'ticket_type_id' => 'type-1', 'barcode' => 'b1', 'description' => 'WC ticket']];
+ }
+
+ protected function processTickets(array $ticketData, string $address, ?\App\Models\User $user = null): void
+ {
+ foreach ($ticketData as $d) {
+ $this->ensureEventAndTypeExist($d);
+ }
+ // mark for assertions in tests
+ $this->processCalled = true;
+ }
+
+ protected function makeTicket(?\App\Models\User $user, object $data): ?\App\Models\Ticket
+ {
+ $this->ensureEventAndTypeExist($data);
+ if (!isset($data->reference)) {
+ $data->reference = $data->id ?? 'ref';
+ }
+ if (!isset($data->order)) {
+ $data->order = (object)['billing' => (object)['email' => $data->email ?? 'a@b.test'], 'id' => explode('-', $data->id)[0] ?? 1, 'status' => 'completed'];
+ }
+ if (!isset($data->item)) {
+ $data->item = (object)['id' => explode('-', $data->id)[1] ?? 10, 'name' => $data->description ?? 'Test ticket'];
+ }
+
+ // Ensure barcode exists for getQrCode
+ if (!isset($data->barcode)) {
+ $data->barcode = $data->id ?? ($data->reference ?? null);
+ }
+ // Call parent so email lookup and associations run as in production
+ return parent::makeTicket($user, $data);
+ }
+
+ protected function processTicket(object $parsed): ?\App\Models\Ticket
+ {
+ if (!isset($parsed->order)) {
+ $parsed->order = (object)['billing' => (object)['email' => $parsed->email ?? 'a@b.test'], 'id' => explode('-', $parsed->id)[0] ?? '1', 'status' => 'completed'];
+ }
+ if (!isset($parsed->item)) {
+ $parsed->item = (object)['id' => explode('-', $parsed->id)[1] ?? '10', 'name' => $parsed->description ?? 'Item'];
+ }
+ $this->ensureEventAndTypeExist($parsed);
+ return $this->makeTicket(null, $parsed);
+ }
+
+ protected function parseOrder(object $order): array
+ {
+ if ($this->parseOverride !== null) {
+ return $this->parseOverride;
+ }
+ return parent::parseOrder($order);
+ }
+
+ protected function verifyWebhook(\Illuminate\Http\Request $request): bool
+ {
+ if ($this->forceVerify !== null) {
+ return $this->forceVerify;
+ }
+ return parent::verifyWebhook($request);
+ }
+
+ protected function getType(string $externalId): ?\App\Models\TicketType
+ {
+ $type = parent::getType($externalId);
+ if ($type) {
+ return $type;
+ }
+ $event = \App\Models\Event::factory()->create();
+ $type = \App\Models\TicketType::factory()->for($event)->create();
+ $tm = new \App\Models\TicketTypeMapping();
+ $tm->provider()->associate($this->provider);
+ $tm->type()->associate($type);
+ $tm->external_id = $externalId;
+ $tm->save();
+ return $type;
+ }
+
+ public function getTicketTypes(string $eventExternalId): array
+ {
+ return [];
+ }
+
+ // No public wrapper methods here; tests should use callProtected when
+ // they need to invoke protected provider methods.
+
+ protected function ensureEventAndTypeExist(object $data): void
+ {
+ $id = (string)($data->event_id ?? '');
+ if ($id === '' || (strpos($id, 'evt') === false && strpos($id, 'EVT') === false && !is_numeric($id))) {
+ return;
+ }
+ $event = \App\Models\Event::whereHas('mappings', function ($q) use ($data) {
+ $q->whereTicketProviderId($this->provider->id)->whereExternalId($data->event_id);
+ })->first();
+ if (!$event) {
+ $event = \App\Models\Event::factory()->create();
+ $em = new \App\Models\EventMapping();
+ $em->provider()->associate($this->provider);
+ $em->event()->associate($event);
+ $em->external_id = $data->event_id;
+ $em->save();
+ }
+ $type = \App\Models\TicketType::whereHas('mappings', function ($q) use ($data) {
+ $q->whereTicketProviderId($this->provider->id)->whereExternalId($data->ticket_type_id);
+ })->first();
+ if (!$type) {
+ $type = \App\Models\TicketType::factory()->for($event)->create();
+ $tm = new \App\Models\TicketTypeMapping();
+ $tm->provider()->associate($this->provider);
+ $tm->type()->associate($type);
+ $tm->external_id = $data->ticket_type_id;
+ $tm->save();
+ }
+ }
+ };
+ }
+
+ // Reflection helpers are provided by the ReflectionHelpers trait.
+
+
+
+ /**
+ * Return a dummy Discord provider compatible with the controller tests.
+ */
+ protected function makeDummyDiscordProvider(): \App\Services\Contracts\SocialProviderContract
+ {
+ return new class implements \App\Services\Contracts\SocialProviderContract {
+ public $provider;
+ public $redirectUrl;
+ public function __construct($provider = null, $redirectUrl = null)
+ {
+ $this->provider = $provider;
+ $this->redirectUrl = $redirectUrl;
+ }
+ public function configMapping(): array
+ {
+ return [];
+ }
+ public function install(): \App\Models\SocialProvider
+ {
+ return $this->provider;
+ }
+ public function redirect(): \Illuminate\Http\RedirectResponse
+ {
+ return redirect()->to('/');
+ }
+ public function user(?\App\Models\User $localUser = null)
+ {
+ return null;
+ }
+ public function addBotToServer()
+ {
+ return 'added';
+ }
+ public function bot()
+ {
+ return (object)['accessTokenResponseBody' => ['guild' => ['name' => 'G1', 'id' => '123']]];
+ }
+ };
+ }
+
+ /**
+ * Return a provider that throws from bot() for failure testing.
+ */
+ protected function makeThrowingDiscordProvider(): \App\Services\Contracts\SocialProviderContract
+ {
+ return new class implements \App\Services\Contracts\SocialProviderContract {
+ public $provider;
+ public $redirectUrl;
+ public function __construct($provider = null, $redirectUrl = null)
+ {
+ $this->provider = $provider;
+ $this->redirectUrl = $redirectUrl;
+ }
+ public function configMapping(): array
+ {
+ return [];
+ }
+ public function install(): \App\Models\SocialProvider
+ {
+ return $this->provider;
+ }
+ public function redirect(): \Illuminate\Http\RedirectResponse
+ {
+ return redirect()->to('/');
+ }
+ public function user(?\App\Models\User $localUser = null)
+ {
+ return null;
+ }
+ public function bot()
+ {
+ throw new \Exception('fail');
+ }
+ public function addBotToServer()
+ {
+ return 'added';
+ }
+ };
+ }
+}
diff --git a/tests/Traits/ReflectionHelpers.php b/tests/Traits/ReflectionHelpers.php
new file mode 100644
index 00000000..67ffc3c4
--- /dev/null
+++ b/tests/Traits/ReflectionHelpers.php
@@ -0,0 +1,37 @@
+getMethod($method);
+ $m->setAccessible(true);
+ return $m->invokeArgs($obj, $args);
+ }
+
+ /**
+ * Assert an object implements the given interface.
+ *
+ * @param object $obj
+ * @param string $interface
+ * @return void
+ */
+ protected function assertImplementsInterface(object $obj, string $interface): void
+ {
+ $rc = new ReflectionClass($obj);
+ \PHPUnit\Framework\Assert::assertTrue($rc->implementsInterface($interface));
+ }
+}
diff --git a/tests/Unit/ExampleTest.php b/tests/Unit/ExampleTest.php
index 5773b0ce..b5da5616 100644
--- a/tests/Unit/ExampleTest.php
+++ b/tests/Unit/ExampleTest.php
@@ -9,7 +9,7 @@ class ExampleTest extends TestCase
/**
* A basic test example.
*/
- public function test_that_true_is_true(): void
+ public function testThatTrueIsTrue(): void
{
$this->assertTrue(true);
}
diff --git a/tests/Unit/app/Console/Commands/.migrated b/tests/Unit/app/Console/Commands/.migrated
deleted file mode 100644
index bea880d1..00000000
--- a/tests/Unit/app/Console/Commands/.migrated
+++ /dev/null
@@ -1 +0,0 @@
-Tests here were moved to `\feature`
diff --git a/tests/Unit/app/HelpersTest.php b/tests/Unit/app/HelpersTest.php
index 909f94a8..d3b60c4a 100644
--- a/tests/Unit/app/HelpersTest.php
+++ b/tests/Unit/app/HelpersTest.php
@@ -9,31 +9,31 @@
class HelpersTest extends TestCase
{
- public function test_makePermalink_basic()
+ public function testMakePermalinkBasic()
{
$this->assertEquals('hello-world', makePermalink('Hello World'));
}
- public function test_makePermalink_removes_special_characters()
+ public function testMakePermalinkRemovesSpecialCharacters()
{
$this->assertEquals('abc-123', makePermalink('ABC!@# 123'));
}
- public function test_makePermalink_truncates_to_128_chars()
+ public function testMakePermalinkTruncatesTo128Chars()
{
$input = str_repeat('a', 130);
$output = makePermalink($input);
$this->assertEquals(128, strlen($output));
}
- public function test_makeCode_default_length()
+ public function testMakeCodeDefaultLength()
{
$code = makeCode();
$this->assertEquals(6, strlen($code));
$this->assertMatchesRegularExpression('/^[ABCDEFGHJKLMNPQRSTUVWXYZ23456789]{6}$/', $code);
}
- public function test_makeCode_custom_length()
+ public function testMakeCodeCustomLength()
{
$code = makeCode(10);
$this->assertEquals(10, strlen($code));
diff --git a/tests/Unit/app/Http/Controllers/Admin/HelperClasses/DummyDiscordProvider.php b/tests/Unit/app/Http/Controllers/Admin/HelperClasses/DummyDiscordProvider.php
deleted file mode 100644
index 5718a606..00000000
--- a/tests/Unit/app/Http/Controllers/Admin/HelperClasses/DummyDiscordProvider.php
+++ /dev/null
@@ -1,50 +0,0 @@
-provider = $provider;
- $this->redirectUrl = $redirectUrl;
- }
-
- public function configMapping(): array
- {
- return [];
- }
-
- public function install(): SocialProviderModel
- {
- return $this->provider;
- }
-
- public function redirect(): RedirectResponse
- {
- return redirect()->to('/');
- }
-
- public function user(?User $localUser = null)
- {
- return null;
- }
-
- public function addBotToServer()
- {
- return 'added';
- }
-
- public function bot()
- {
- return (object)['accessTokenResponseBody' => ['guild' => ['name' => 'G1', 'id' => '123']]];
- }
-}
diff --git a/tests/Unit/app/Http/Controllers/Admin/SettingControllerTest.php b/tests/Unit/app/Http/Controllers/Admin/SettingControllerTest.php
index 1166f6cd..7cae22bf 100644
--- a/tests/Unit/app/Http/Controllers/Admin/SettingControllerTest.php
+++ b/tests/Unit/app/Http/Controllers/Admin/SettingControllerTest.php
@@ -12,12 +12,14 @@
use Illuminate\Http\RedirectResponse;
use Tests\TestCase;
use Throwable;
+use Tests\Traits\ProviderTestHelpers;
// A small test-specific subclass to expose the protected getDiscordProvider for unit testing
class SettingControllerTest extends TestCase
{
use RefreshDatabase;
+ use ProviderTestHelpers;
public function testIndexReturnsView()
{
@@ -38,27 +40,75 @@ public function testUpdateSavesSettings()
public function testAddDiscordCallsProvider()
{
- $prov = SocialProvider::create(['code' => 'discord', 'name' => 'Discord', 'provider_class' => HelperClasses\DummyDiscordProvider::class, 'supports_auth' => 0, 'enabled' => 1, 'auth_enabled' => 0, 'can_be_renamed' => 0]);
-
$c = new SettingController();
+
+ // ensure a provider row exists and bind a container entry so getProvider() will resolve our test double
+ $bindingKey = 'tests.discord.provider';
+ $prov = SocialProvider::firstOrCreate([
+ 'code' => 'discord',
+ ], [
+ 'name' => 'Discord',
+ 'provider_class' => $bindingKey,
+ 'supports_auth' => 0,
+ 'enabled' => 1,
+ 'auth_enabled' => 0,
+ 'can_be_renamed' => 0
+ ]);
+
+ // Bind a key in the container so SocialProvider::getProvider will resolve our dummy provider
+ $this->app->bind($bindingKey, function ($app, $params) {
+ $inst = $this->makeDummyDiscordProvider();
+ // Container may pass constructor params as an array or positional; handle both
+ if (is_array($params)) {
+ $inst->provider = $params['provider'] ?? ($params[0] ?? null);
+ $inst->redirectUrl = $params['redirectUrl'] ?? ($params[1] ?? null);
+ }
+ return $inst;
+ });
+
+ // Call the real controller method which will resolve our bound provider
$result = $c->addDiscord();
$this->assertEquals('added', $result);
}
public function testGetDiscordProviderReturnsConfiguredProvider()
{
- // create the discord provider record and point it at our dummy provider
- SocialProvider::create(['code' => 'discord', 'name' => 'Discord', 'provider_class' => HelperClasses\DummyDiscordProvider::class, 'supports_auth' => 0, 'enabled' => 1, 'auth_enabled' => 0, 'can_be_renamed' => 0]);
+ // create the discord provider record
+ SocialProvider::firstOrCreate([
+ 'code' => 'discord',
+ ], [
+ 'name' => 'Discord',
+ 'provider_class' => '', // Not needed, provider will be stubbed
+ 'supports_auth' => 0,
+ 'enabled' => 1,
+ 'auth_enabled' => 0,
+ 'can_be_renamed' => 0
+ ]);
// register the named route used by provider construction
$this->app['router']->get('/discord-return', fn() => '')->name('admin.settings.discord_return');
- $controller = new HelperClasses\TestableSettingController();
+ $bindingKey = 'tests.discord.provider';
+ $this->app->bind($bindingKey, function ($app, $params) {
+ $inst = $this->makeDummyDiscordProvider();
+ if (is_array($params)) {
+ $inst->provider = $params['provider'] ?? ($params[0] ?? null);
+ $inst->redirectUrl = $params['redirectUrl'] ?? ($params[1] ?? null);
+ }
+ return $inst;
+ });
+
+ // Ensure the DB row contains the provider_class binding key we bound above
+ $row = SocialProvider::whereCode('discord')->first();
+ $row->provider_class = $bindingKey;
+ $row->save();
+
+ // Use TestableSettingController to call the protected getDiscordProvider()
+ $controller = new \Tests\Unit\app\Http\Controllers\Admin\HelperClasses\TestableSettingController();
$provider = $controller->callGetDiscordProvider();
$this->assertIsObject($provider);
$this->assertInstanceOf(SocialProviderContract::class, $provider);
- // provider should have been constructed with the redirect URL we registered
$this->assertEquals(route('admin.settings.discord_return'), $provider->redirectUrl ?? null);
}
@@ -73,7 +123,7 @@ public function testAddDiscordReturnWritesAndClearsSettings()
$this->app['router']->get('/settings', fn() => '')->name('admin.settings.index');
// Create a controller partial mock that stubs getDiscordProvider() to return our dummy provider
- $dummy = new HelperClasses\DummyDiscordProvider();
+ $dummy = $this->makeDummyDiscordProvider();
$controllerMock = $this->getMockBuilder(SettingController::class)
->onlyMethods(['getDiscordProvider'])
->getMock();
@@ -90,7 +140,7 @@ public function testAddDiscordReturnWritesAndClearsSettings()
$this->assertEquals('123', Setting::whereCode('discord.server.id')->first()->value, 'discord.server.id should be set to guild id');
// Now simulate failure by using a controller mock that returns a throwing provider
- $throwing = new HelperClasses\ThrowingDiscordProvider();
+ $throwing = $this->makeThrowingDiscordProvider();
$controllerMockFail = $this->getMockBuilder(SettingController::class)
->onlyMethods(['getDiscordProvider'])
->getMock();
diff --git a/tests/Unit/app/Http/Controllers/Admin/SocialProviderControllerTest.php b/tests/Unit/app/Http/Controllers/Admin/SocialProviderControllerTest.php
index 1507a57f..792827f3 100644
--- a/tests/Unit/app/Http/Controllers/Admin/SocialProviderControllerTest.php
+++ b/tests/Unit/app/Http/Controllers/Admin/SocialProviderControllerTest.php
@@ -26,16 +26,16 @@ public function testEditReturnsObject()
public function testUpdatePersistsSettingsAndProviderFields()
{
$prov = SocialProvider::factory()->create(['supports_auth' => true, 'can_be_renamed' => true]);
- $c = new SocialProviderController();
+ $c = $this->app->make(SocialProviderController::class);
- // create provider settings
- $s1 = new ProviderSetting();
- $s1->provider()->associate($prov);
- $s1->name = 'Opt';
- $s1->code = 'opt1';
- $s1->type = SettingType::stBoolean;
- $s1->value = true;
- $s1->save();
+ // create provider setting via factory
+ ProviderSetting::factory()->create([
+ 'provider_id' => $prov->id,
+ 'name' => 'Opt',
+ 'code' => 'opt1',
+ 'type' => SettingType::stBoolean,
+ 'value' => true,
+ ]);
$req = SocialProviderUpdateRequest::create('/', 'POST', ['enabled' => 0, 'auth_enabled' => 1, 'name' => 'New', 'opt1' => 0]);
$resp = $c->update($req, $prov);
@@ -47,16 +47,16 @@ public function testUpdatePersistsSettingsAndProviderFields()
public function testBooleanSettingIsClearedWhenMissingFromRequest()
{
$prov = SocialProvider::factory()->create(['supports_auth' => true, 'can_be_renamed' => true]);
- $c = new SocialProviderController();
+ $c = $this->app->make(SocialProviderController::class);
// create provider setting that is boolean and initially true
- $s1 = new ProviderSetting();
- $s1->provider()->associate($prov);
- $s1->name = 'AutoOpt';
- $s1->code = 'auto_opt';
- $s1->type = SettingType::stBoolean;
- $s1->value = true;
- $s1->save();
+ ProviderSetting::factory()->create([
+ 'provider_id' => $prov->id,
+ 'name' => 'AutoOpt',
+ 'code' => 'auto_opt',
+ 'type' => SettingType::stBoolean,
+ 'value' => true,
+ ]);
// build request that does NOT include 'auto_opt' so the elseif branch should run
$req = SocialProviderUpdateRequest::create('/', 'POST', ['enabled' => 1, 'auth_enabled' => 0, 'name' => 'KeepName']);
diff --git a/tests/Unit/app/Http/Controllers/SeatingPlanControllerTest.php b/tests/Unit/app/Http/Controllers/SeatingPlanControllerTest.php
index bc4eebdc..d93bac62 100644
--- a/tests/Unit/app/Http/Controllers/SeatingPlanControllerTest.php
+++ b/tests/Unit/app/Http/Controllers/SeatingPlanControllerTest.php
@@ -526,7 +526,7 @@ public function testSelectAssignsWhenTicketHasNoSeat()
}
// Single-purpose: when !$seat->canPick($ticket->user) is true
- public function testSelect_ShortCircuitsWhenSeatNotPickable()
+ public function testSelectShortCircuitsWhenSeatNotPickable()
{
$user = User::factory()->create();
// Event in future but seat disabled -> canPick should be false
@@ -551,7 +551,7 @@ public function testSelect_ShortCircuitsWhenSeatNotPickable()
}
// Single-purpose: when $ticket->seat is true (old seat should be disassociated)
- public function testSelect_ReassignsOldSeatWhenTicketHasSeat()
+ public function testSelectReassignsOldSeatWhenTicketHasSeat()
{
$user = User::factory()->create();
$event = Event::factory()->create(['ends_at' => now()->addDay(), 'seating_locked' => false]);
@@ -578,7 +578,7 @@ public function testSelect_ReassignsOldSeatWhenTicketHasSeat()
}
// Single-purpose: when $ticket->seat is false (assign new seat)
- public function testSelect_AssignsSeatWhenTicketHasNoSeat()
+ public function testSelectAssignsSeatWhenTicketHasNoSeat()
{
$user = User::factory()->create();
$event = Event::factory()->create(['ends_at' => now()->addDay(), 'seating_locked' => false]);
diff --git a/tests/Unit/app/Http/Middleware/HelperClasses/RedirectOnFirstLoginMiddlewareStub.php b/tests/Unit/app/Http/Middleware/HelperClasses/RedirectOnFirstLoginMiddlewareStub.php
deleted file mode 100644
index 375b717b..00000000
--- a/tests/Unit/app/Http/Middleware/HelperClasses/RedirectOnFirstLoginMiddlewareStub.php
+++ /dev/null
@@ -1,9 +0,0 @@
-middleware = new class () extends RedirectOnFirstLoginMiddleware {
+ };
+ }
+
public function testCanInstantiateRedirectOnFirstLoginMiddleware()
{
- $middleware = new HelperClasses\RedirectOnFirstLoginMiddlewareStub();
- $this->assertInstanceOf(RedirectOnFirstLoginMiddleware::class, $middleware);
+ $this->assertInstanceOf(RedirectOnFirstLoginMiddleware::class, $this->middleware);
}
public function testHandleRedirectsIfFirstLogin()
{
- $middleware = new HelperClasses\RedirectOnFirstLoginMiddlewareStub();
+ $middleware = $this->middleware;
$mockUser = (object)['first_login' => true];
$request = Request::create('/', 'GET');
// Use a user resolver to provide the mocked user without mocking Request methods
@@ -34,7 +47,7 @@ public function testHandleRedirectsIfFirstLogin()
public function testHandleCallsNextIfNotFirstLogin()
{
- $middleware = new HelperClasses\RedirectOnFirstLoginMiddlewareStub();
+ $middleware = $this->middleware;
$mockUser = (object)['first_login' => false];
$request = Request::create('/', 'GET');
$request->setUserResolver(function () use ($mockUser) {
@@ -53,7 +66,7 @@ public function testHandleCallsNextIfNotFirstLogin()
public function testHandleWithNoUserDoesNotRedirect()
{
- $middleware = new HelperClasses\RedirectOnFirstLoginMiddlewareStub();
+ $middleware = $this->middleware;
$request = Request::create('/', 'GET');
$request->setUserResolver(function () {
return null;
@@ -71,7 +84,7 @@ public function testHandleWithNoUserDoesNotRedirect()
public function testHandleWithUserWithoutFirstLoginPropertyDoesNotRedirect()
{
- $middleware = new HelperClasses\RedirectOnFirstLoginMiddlewareStub();
+ $middleware = $this->middleware;
$mockUser = (object)[];
$request = Request::create('/', 'GET');
$request->setUserResolver(function () use ($mockUser) {
diff --git a/tests/Unit/app/Http/Requests/ClanRequestTest.php b/tests/Unit/app/Http/Requests/ClanRequestTest.php
index da1fa412..e59aff1f 100644
--- a/tests/Unit/app/Http/Requests/ClanRequestTest.php
+++ b/tests/Unit/app/Http/Requests/ClanRequestTest.php
@@ -59,7 +59,7 @@ public function testNameRuleClosureFailsIfPermalinkEmpty()
$called = true;
$this->assertEquals('The clan name is not valid', $message);
};
- $closure('name', '!!!', $fail);
+ $closure->call($request, 'name', '!!!', $fail);
$this->assertTrue($called, 'Fail closure was not called for empty permalink');
}
@@ -93,8 +93,7 @@ public function testNameRuleClosureFailsWhenPermalinkExists()
$called = true;
$this->assertEquals('That clan name is not available', $message);
};
- $bound = Closure::bind($closure, $request, get_class($request));
- $bound('name', $name, $fail);
+ $closure->call($request, 'name', $name, $fail);
$this->assertTrue($called, 'Fail closure was not called for existing permalink');
}
@@ -122,8 +121,7 @@ public function testNameRuleClosurePassesWhenEditingOwnClan()
$fail = function ($message) use (&$called) {
$called = true;
};
- $bound = Closure::bind($closure, $request, get_class($request));
- $bound('name', $name, $fail);
+ $closure->call($request, 'name', $name, $fail);
$this->assertFalse($called, 'Fail closure was called when editing own clan');
}
diff --git a/tests/Unit/app/Http/Requests/EmailVerifyRequestTest.php b/tests/Unit/app/Http/Requests/EmailVerifyRequestTest.php
index 91b0a8b2..72173828 100644
--- a/tests/Unit/app/Http/Requests/EmailVerifyRequestTest.php
+++ b/tests/Unit/app/Http/Requests/EmailVerifyRequestTest.php
@@ -10,6 +10,16 @@
class EmailVerifyRequestTest extends TestCase
{
+ /** @var EmailVerifyRequest */
+ protected $request;
+
+ protected function setUp(): void
+ {
+ parent::setUp();
+ // create an anonymous subclass to avoid relying on HelperClasses stub
+ $this->request = new class () extends EmailVerifyRequest {
+ };
+ }
public function testAuthorizeReturnsTrue()
{
$request = new EmailVerifyRequest();
@@ -35,7 +45,7 @@ public function testRulesContainCodeWithRequiredStringAndAlphaNum()
public function testCodeRuleClosurePassesIfNoException()
{
- $request = new HelperClasses\EmailVerifyRequestStub();
+ $request = $this->request;
$mockEmail = $this->getMockBuilder(EmailAddress::class)
->onlyMethods(['checkCode'])
->getMock();
@@ -57,7 +67,7 @@ public function testCodeRuleClosurePassesIfNoException()
public function testCodeRuleClosureFailsOnException()
{
- $request = new HelperClasses\EmailVerifyRequestStub();
+ $request = $this->request;
$mockEmail = $this->getMockBuilder(EmailAddress::class)
->onlyMethods(['checkCode'])
->getMock();
diff --git a/tests/Unit/app/Http/Requests/HelperClasses/EmailVerifyRequestStub.php b/tests/Unit/app/Http/Requests/HelperClasses/EmailVerifyRequestStub.php
deleted file mode 100644
index 2c499a31..00000000
--- a/tests/Unit/app/Http/Requests/HelperClasses/EmailVerifyRequestStub.php
+++ /dev/null
@@ -1,10 +0,0 @@
-assertEquals('The transfer code is invalid', $message);
};
- $bound = Closure::bind($closure, $request, get_class($request));
- $bound('code', 'NOPE', $fail);
+ $closure->call($request, 'code', 'NOPE', $fail);
$this->assertTrue($called, 'Fail closure was not called for invalid transfer code');
}
@@ -86,8 +85,7 @@ public function testCodeRuleFailsWhenTicketCannotTransfer()
$called = true;
$this->assertEquals('It is not possible to transfer this ticket', $message);
};
- $bound = Closure::bind($closure, $request, get_class($request));
- $bound('code', $ticket->transfer_code, $fail);
+ $closure->call($request, 'code', $ticket->transfer_code, $fail);
$this->assertTrue($called, 'Fail closure was not called for non-transferable ticket');
}
@@ -120,8 +118,7 @@ public function testCodeRuleFailsWhenUserAlreadyOwnsTicket()
$called = true;
$this->assertEquals('You already have the ticket in your account', $message);
};
- $bound = Closure::bind($closure, $request, get_class($request));
- $bound('code', $ticket->transfer_code, $fail);
+ $closure->call($request, 'code', $ticket->transfer_code, $fail);
$this->assertTrue($called, 'Fail closure was not called when user already owns ticket');
}
@@ -161,8 +158,7 @@ public function testCodeRulePassesForValidTransferAndAdminBypassesDraft()
$fail = function ($message) use (&$called) {
$called = true;
};
- $bound = Closure::bind($closure, $request, get_class($request));
- $bound('code', $ticket->transfer_code, $fail);
+ $closure->call($request, 'code', $ticket->transfer_code, $fail);
$this->assertFalse($called, 'Fail closure was called for a valid transfer when admin should bypass draft filter');
}
}
diff --git a/tests/Unit/app/Models/ClanMembershipTest.php b/tests/Unit/app/Models/ClanMembershipTest.php
index a3f32c41..2d4c9be8 100644
--- a/tests/Unit/app/Models/ClanMembershipTest.php
+++ b/tests/Unit/app/Models/ClanMembershipTest.php
@@ -13,7 +13,7 @@ class ClanMembershipTest extends TestCase
{
use RefreshDatabase;
- public function test_can_delete_when_not_leader_and_when_leader_with_other_leader()
+ public function testCanDeleteWhenNotLeaderAndWhenLeaderWithOtherLeader()
{
$clan = Clan::factory()->create();
$leaderRole = ClanRole::factory()->create(['code' => 'leader']);
@@ -38,7 +38,7 @@ public function test_can_delete_when_not_leader_and_when_leader_with_other_leade
$this->assertTrue($member->canDelete($member->user));
}
- public function test_can_delete_when_called_by_other_leader()
+ public function testCanDeleteWhenCalledByOtherLeader()
{
$clan = Clan::factory()->create();
$leaderRole = ClanRole::factory()->create(['code' => 'leader']);
diff --git a/tests/Unit/app/Models/ClanRoleTest.php b/tests/Unit/app/Models/ClanRoleTest.php
index 34003dd9..fce54992 100644
--- a/tests/Unit/app/Models/ClanRoleTest.php
+++ b/tests/Unit/app/Models/ClanRoleTest.php
@@ -20,10 +20,16 @@ public function testMembersRelationshipIsHasMany()
$this->assertInstanceOf(HasMany::class, $role->members());
}
- public function testProtectedToStringNameReturnsCode()
+ public function testProtectedFunctionToStringName()
{
- $dummy = new HelperClasses\DummyClanRole();
- $dummy->code = 'leader';
- $this->assertEquals('leader', $dummy->toStringNamePublic());
+ $clanRole = new ClanRole();
+ $clanRole->code = 'clanRole-1';
+
+ $reflection = new \ReflectionClass($clanRole);
+ $method = $reflection->getMethod('toStringName');
+ $method->setAccessible(true);
+ $result = $method->invoke($clanRole);
+
+ $this->assertEquals($clanRole->code, $result);
}
}
diff --git a/tests/Unit/app/Models/ClanTest.php b/tests/Unit/app/Models/ClanTest.php
index b82bfce5..1760acd0 100644
--- a/tests/Unit/app/Models/ClanTest.php
+++ b/tests/Unit/app/Models/ClanTest.php
@@ -14,13 +14,13 @@ class ClanTest extends TestCase
{
use RefreshDatabase;
- public function test_get_route_key_name_is_code()
+ public function testGetRouteKeyNameIsCode()
{
$c = new Clan();
$this->assertEquals('code', $c->getRouteKeyName());
}
- public function test_is_member_returns_true_and_false()
+ public function testIsMemberReturnsTrueAndFalse()
{
$clan = Clan::factory()->create();
$user = User::factory()->create();
@@ -31,7 +31,7 @@ public function test_is_member_returns_true_and_false()
$this->assertTrue($clan->isMember($user));
}
- public function test_add_user_with_string_role_and_invalid_role()
+ public function testAddUserWithStringRoleAndInvalidRole()
{
$clan = Clan::factory()->create();
$user = User::factory()->create();
@@ -47,7 +47,7 @@ public function test_add_user_with_string_role_and_invalid_role()
$clan->addUser(User::factory()->create(), 'does-not-exist');
}
- public function test_add_user_accepts_role_object_and_returns_existing()
+ public function testAddUserAcceptsRoleObjectAndReturnsExisting()
{
$clan = Clan::factory()->create();
$user = User::factory()->create();
@@ -62,20 +62,23 @@ public function test_add_user_accepts_role_object_and_returns_existing()
$this->assertEquals($membership->id, $membership2->id);
}
- public function test_generate_code_contains_dash_and_length()
+ public function testGenerateCodeContainsDashAndLength()
{
$clan = Clan::factory()->create();
$code = $clan->generateCode();
$this->assertMatchesRegularExpression('/^[A-Z0-9]{4}-[A-Z0-9]{4}$/i', $code);
}
- public function test_to_string_name_via_dummy_exposes_name()
+ public function testProtectedFunctionToStringName()
{
- // Use a tiny dummy subclass to expose the protected toStringName method
- $dummy = new HelperClasses\DummyClan();
- $dummy->name = 'My Clan Name';
- $this->assertEquals('My Clan Name', $dummy->exposeToString());
+ $clan = new Clan();
+ $clan->name = 'My Clan Name';
+
+ $reflection = new \ReflectionClass($clan);
+ $method = $reflection->getMethod('toStringName');
+ $method->setAccessible(true);
+ $result = $method->invoke($clan);
+
+ $this->assertEquals($clan->name, $result);
}
}
-
-// Small helper class inside this test file to expose protected toStringName()
diff --git a/tests/Unit/app/Models/EmailAddressTest.php b/tests/Unit/app/Models/EmailAddressTest.php
index ecfd990b..c007bc79 100644
--- a/tests/Unit/app/Models/EmailAddressTest.php
+++ b/tests/Unit/app/Models/EmailAddressTest.php
@@ -16,7 +16,7 @@ class EmailAddressTest extends TestCase
{
use RefreshDatabase;
- public function test_send_verification_code_saves_and_sends()
+ public function testSendVerificationCodeSavesAndSends()
{
Mail::fake();
$user = User::factory()->create();
@@ -29,7 +29,7 @@ public function test_send_verification_code_saves_and_sends()
Mail::assertSent(VerifyEmail::class);
}
- public function test_can_delete_false_when_linked_accounts_or_primary()
+ public function testCanDeleteFalseWhenLinkedAccountsOrPrimary()
{
$user = User::factory()->create();
$email = EmailAddress::factory()->create(['user_id' => $user->id]);
@@ -48,28 +48,28 @@ public function test_can_delete_false_when_linked_accounts_or_primary()
$this->assertFalse($email->canDelete());
}
- public function test_check_code_throws_on_expired_or_wrong()
+ public function testCheckCodeThrowsOnExpiredOrWrong()
{
$email = EmailAddress::factory()->create(['verification_sent_at' => now()->subDays(3), 'verification_code' => 'ABC123']);
$this->expectException(EmailVerificationException::class);
$email->checkCode('ABC123');
}
- public function test_check_code_throws_on_incorrect_code()
+ public function testCheckCodeThrowsOnIncorrectCode()
{
$email = EmailAddress::factory()->create(['verification_sent_at' => now(), 'verification_code' => 'ABC123']);
$this->expectException(EmailVerificationException::class);
$email->checkCode('WRONG');
}
- public function test_verify_calls_sync_and_returns_true()
+ public function testVerifyCallsSyncAndReturnsTrue()
{
$email = EmailAddress::factory()->create(['verification_sent_at' => now(), 'verification_code' => 'XYZ789', 'verified_at' => null]);
// calling verify should not throw
$this->assertTrue($email->verify('XYZ789'));
}
- public function test_sync_tickets_does_nothing_when_not_verified()
+ public function testSyncTicketsDoesNothingWhenNotVerified()
{
Bus::fake();
$email = EmailAddress::factory()->create(['verified_at' => null]);
@@ -77,7 +77,7 @@ public function test_sync_tickets_does_nothing_when_not_verified()
Bus::assertNotDispatched(SyncTicketsForEmailJob::class);
}
- public function test_sync_tickets_dispatches_job_when_verified()
+ public function testSyncTicketsDispatchesJobWhenVerified()
{
Bus::fake();
$email = EmailAddress::factory()->create(['verified_at' => now()]);
@@ -85,7 +85,7 @@ public function test_sync_tickets_dispatches_job_when_verified()
Bus::assertDispatched(SyncTicketsForEmailJob::class);
}
- public function test_sync_tickets_dispatches_sync_when_requested()
+ public function testSyncTicketsDispatchesSyncWhenRequested()
{
Bus::fake();
$email = EmailAddress::factory()->create(['verified_at' => now()]);
diff --git a/tests/Unit/app/Models/EventMappingTest.php b/tests/Unit/app/Models/EventMappingTest.php
index 584e762a..054c10f5 100644
--- a/tests/Unit/app/Models/EventMappingTest.php
+++ b/tests/Unit/app/Models/EventMappingTest.php
@@ -10,7 +10,7 @@ class EventMappingTest extends TestCase
{
use RefreshDatabase;
- public function test_can_instantiate_and_relations()
+ public function testCanInstantiateAndRelations()
{
$m = new EventMapping();
$this->assertInstanceOf(EventMapping::class, $m);
diff --git a/tests/Unit/app/Models/EventTest.php b/tests/Unit/app/Models/EventTest.php
index 208434d8..e7df612d 100644
--- a/tests/Unit/app/Models/EventTest.php
+++ b/tests/Unit/app/Models/EventTest.php
@@ -129,11 +129,17 @@ public function getTicketTypes($event)
$this->assertEquals($provider->id, $result[0]->provider->id);
}
- public function testProtectedToStringNameReturnsCode()
+ public function testProtectedFunctionToStringName()
{
- $dummy = new HelperClasses\DummyEvent();
- $dummy->code = 'EVT-1';
- $this->assertEquals('EVT-1', $dummy->toStringNamePublic());
+ $event = new Event();
+ $event->code = 'EVT-1';
+
+ $reflection = new \ReflectionClass($event);
+ $method = $reflection->getMethod('toStringName');
+ $method->setAccessible(true);
+ $result = $method->invoke($event);
+
+ $this->assertEquals($event->code, $result);
}
public function testGetAvailableEventMappingsIncludesUsedWhenExistingProvided()
diff --git a/tests/Unit/app/Models/HelperClasses/DummyClan.php b/tests/Unit/app/Models/HelperClasses/DummyClan.php
deleted file mode 100644
index 705f940d..00000000
--- a/tests/Unit/app/Models/HelperClasses/DummyClan.php
+++ /dev/null
@@ -1,13 +0,0 @@
-toStringName();
- }
-}
diff --git a/tests/Unit/app/Models/HelperClasses/DummyClanRole.php b/tests/Unit/app/Models/HelperClasses/DummyClanRole.php
deleted file mode 100644
index 11cc9f01..00000000
--- a/tests/Unit/app/Models/HelperClasses/DummyClanRole.php
+++ /dev/null
@@ -1,13 +0,0 @@
-toStringName();
- }
-}
diff --git a/tests/Unit/app/Models/HelperClasses/DummyEvent.php b/tests/Unit/app/Models/HelperClasses/DummyEvent.php
deleted file mode 100644
index 9b68592c..00000000
--- a/tests/Unit/app/Models/HelperClasses/DummyEvent.php
+++ /dev/null
@@ -1,13 +0,0 @@
-toStringName();
- }
-}
diff --git a/tests/Unit/app/Models/HelperClasses/DummyRole.php b/tests/Unit/app/Models/HelperClasses/DummyRole.php
deleted file mode 100644
index 8a374da1..00000000
--- a/tests/Unit/app/Models/HelperClasses/DummyRole.php
+++ /dev/null
@@ -1,13 +0,0 @@
-toStringName();
- }
-}
diff --git a/tests/Unit/app/Models/HelperClasses/DummySeat.php b/tests/Unit/app/Models/HelperClasses/DummySeat.php
deleted file mode 100644
index 20ae77be..00000000
--- a/tests/Unit/app/Models/HelperClasses/DummySeat.php
+++ /dev/null
@@ -1,13 +0,0 @@
-toStringName();
- }
-}
diff --git a/tests/Unit/app/Models/LinkedAccountTest.php b/tests/Unit/app/Models/LinkedAccountTest.php
index c2da4b64..ad4afb2f 100644
--- a/tests/Unit/app/Models/LinkedAccountTest.php
+++ b/tests/Unit/app/Models/LinkedAccountTest.php
@@ -12,7 +12,7 @@ class LinkedAccountTest extends TestCase
{
use RefreshDatabase;
- public function test_can_delete_false_when_only_account()
+ public function testCanDeleteFalseWhenOnlyAccount()
{
$user = User::factory()->create();
$provider = SocialProvider::factory()->create(['auth_enabled' => true]);
@@ -20,7 +20,7 @@ public function test_can_delete_false_when_only_account()
$this->assertFalse($acc->canDelete());
}
- public function test_can_delete_true_when_provider_not_auth()
+ public function testCanDeleteTrueWhenProviderNotAuth()
{
$user = User::factory()->create();
$provider = SocialProvider::factory()->create(['auth_enabled' => false]);
@@ -30,7 +30,7 @@ public function test_can_delete_true_when_provider_not_auth()
$this->assertTrue($acc->canDelete());
}
- public function test_can_delete_requires_other_auth_provider()
+ public function testCanDeleteRequiresOtherAuthProvider()
{
$user = User::factory()->create();
$authProv = SocialProvider::factory()->create(['auth_enabled' => true, 'code' => 'auth1']);
diff --git a/tests/Unit/app/Models/ProviderSettingTest.php b/tests/Unit/app/Models/ProviderSettingTest.php
index b5c22d2b..c72f6cd9 100644
--- a/tests/Unit/app/Models/ProviderSettingTest.php
+++ b/tests/Unit/app/Models/ProviderSettingTest.php
@@ -11,7 +11,7 @@ class ProviderSettingTest extends TestCase
{
use RefreshDatabase;
- public function test_build_sort_query_scopes_to_provider()
+ public function testBuildSortQueryScopesToProvider()
{
$prov = SocialProvider::factory()->create(['code' => 'psprov']);
$ps = ProviderSetting::factory()->create(['provider_id' => $prov->id, 'provider_type' => get_class($prov), 'code' => 'x']);
@@ -20,7 +20,7 @@ public function test_build_sort_query_scopes_to_provider()
$this->assertStringContainsString('where', $query->toSql());
}
- public function test_provider_relation_returns_provider()
+ public function testProviderRelationReturnsProvider()
{
$prov = SocialProvider::factory()->create(['code' => 'psprov2']);
$ps = ProviderSetting::factory()->create(['provider_id' => $prov->id, 'provider_type' => get_class($prov), 'code' => 'y']);
@@ -28,7 +28,7 @@ public function test_provider_relation_returns_provider()
$this->assertEquals($prov->id, $ps->provider->id);
}
- public function test_is_required_detects_required_in_validation()
+ public function testIsRequiredDetectsRequiredInValidation()
{
$ps = new ProviderSetting();
$ps->validation = 'required|string';
diff --git a/tests/Unit/app/Models/RoleTest.php b/tests/Unit/app/Models/RoleTest.php
index f5d41a98..d1f08e8a 100644
--- a/tests/Unit/app/Models/RoleTest.php
+++ b/tests/Unit/app/Models/RoleTest.php
@@ -20,10 +20,16 @@ public function testUsersRelationshipIsBelongsToMany()
$this->assertInstanceOf(BelongsToMany::class, $role->users());
}
- public function testProtectedToStringNameReturnsCode()
+ public function testProtectedFunctionToStringName()
{
- $dummy = new HelperClasses\DummyRole();
- $dummy->code = 'test-code';
- $this->assertEquals('test-code', $dummy->toStringNamePublic());
+ $role = new Role();
+ $role->code = 'ROLE-1';
+
+ $reflection = new \ReflectionClass($role);
+ $method = $reflection->getMethod('toStringName');
+ $method->setAccessible(true);
+ $result = $method->invoke($role);
+
+ $this->assertEquals($role->code, $result);
}
}
diff --git a/tests/Unit/app/Models/SeatTest.php b/tests/Unit/app/Models/SeatTest.php
index 83d7f094..2b7c2bca 100644
--- a/tests/Unit/app/Models/SeatTest.php
+++ b/tests/Unit/app/Models/SeatTest.php
@@ -211,7 +211,7 @@ public function allowedSeatGroup(SeatGroup $group): bool
$this->assertTrue($seat->canPick($user));
}
- public function testCanPickWithMultipleTickets_firstNonMatchingReturnsFalse()
+ public function testCanPickWithMultipleTicketsFirstNonMatchingReturnsFalse()
{
$seat = new Seat();
$seat->disabled = 0;
@@ -249,10 +249,16 @@ public function allowedSeatGroup(SeatGroup $group): bool
$this->assertFalse($seat->canPick($user));
}
- public function testProtectedToStringNameReturnsLabel()
+ public function testProtectedFunctionToStringName()
{
- $dummy = new HelperClasses\DummySeat();
- $dummy->label = 'A1';
- $this->assertEquals('A1', $dummy->toStringNamePublic());
+ $seat = new Seat();
+ $seat->label = 'A1';
+
+ $reflection = new \ReflectionClass($seat);
+ $method = $reflection->getMethod('toStringName');
+ $method->setAccessible(true);
+ $result = $method->invoke($seat);
+
+ $this->assertEquals($seat->label, $result);
}
}
diff --git a/tests/Unit/app/Providers/AppServiceProviderTest.php b/tests/Unit/app/Providers/AppServiceProviderTest.php
index a7eaa642..c5b73f0f 100644
--- a/tests/Unit/app/Providers/AppServiceProviderTest.php
+++ b/tests/Unit/app/Providers/AppServiceProviderTest.php
@@ -31,7 +31,7 @@ protected function clearBladeDirectives()
}
}
- public function test_blade_setting_directive_is_registered()
+ public function testBladeSettingDirectiveIsRegistered()
{
// Ensure no existing directive conflicts
$this->clearBladeDirectives();
@@ -48,7 +48,7 @@ public function test_blade_setting_directive_is_registered()
$this->assertStringContainsString('App\\Models\\Setting::fetch', $result);
}
- public function test_blade_setting_directive_returns_default_when_setting_not_found()
+ public function testBladeSettingDirectiveReturnsDefaultWhenSettingNotFound()
{
// Ensure no existing directive conflicts
$this->clearBladeDirectives();
@@ -66,7 +66,7 @@ public function test_blade_setting_directive_returns_default_when_setting_not_fo
$this->assertStringContainsString('Default Value', $result);
}
- public function test_view_composer_sets_theme_and_dark_mode()
+ public function testViewComposerSetsThemeAndDarkMode()
{
// Create a real theme in the database so Theme::whereActive(true)->first() returns it
Theme::factory()->create([
diff --git a/tests/Unit/app/Services/Contracts/HelperClasses/DummySocialProvider.php b/tests/Unit/app/Services/Contracts/HelperClasses/DummySocialProvider.php
deleted file mode 100644
index f37e4705..00000000
--- a/tests/Unit/app/Services/Contracts/HelperClasses/DummySocialProvider.php
+++ /dev/null
@@ -1,41 +0,0 @@
- [
- 'name' => 'Client ID',
- 'validation' => 'required|string',
- 'value' => 'dummy-client-id',
- ],
- ];
- }
-
- public function install(): SocialProvider
- {
- return new SocialProvider(['name' => 'Dummy', 'code' => 'dummy']);
- }
-
- public function redirect(): RedirectResponse
- {
- return new RedirectResponse('/dummy-redirect');
- }
-
- public function user(?User $localUser = null)
- {
- return $localUser ?: new User(['name' => 'Dummy User']);
- }
-}
diff --git a/tests/Unit/app/Services/Contracts/HelperClasses/DummyTicketProvider.php b/tests/Unit/app/Services/Contracts/HelperClasses/DummyTicketProvider.php
deleted file mode 100644
index b7e78947..00000000
--- a/tests/Unit/app/Services/Contracts/HelperClasses/DummyTicketProvider.php
+++ /dev/null
@@ -1,57 +0,0 @@
- [
- 'name' => 'API Key',
- 'validation' => 'required|string',
- 'value' => 'dummy-key',
- ],
- ];
- }
-
- public function install(): TicketProvider
- {
- return new TicketProvider(['name' => 'Dummy', 'code' => 'dummy']);
- }
-
- public function processWebhook(Request $request): bool
- {
- return true;
- }
-
- public function syncTickets(string|EmailAddress $email): void
- {
- // Dummy implementation
- }
-
- public function getEvents(): array
- {
- return ['evt1' => 'Event 1', 'evt2' => 'Event 2'];
- }
-
- public function getTicketTypes(string $eventExternalId): array
- {
- return ['type1' => 'VIP', 'type2' => 'Standard'];
- }
-
- public function syncAllTickets(?OutputStyle $output): void
- {
- // Dummy implementation
- }
-}
diff --git a/tests/Unit/app/Services/Contracts/SocialProviderContractTest.php b/tests/Unit/app/Services/Contracts/SocialProviderContractTest.php
index 0eca0f07..dc65d966 100644
--- a/tests/Unit/app/Services/Contracts/SocialProviderContractTest.php
+++ b/tests/Unit/app/Services/Contracts/SocialProviderContractTest.php
@@ -4,40 +4,47 @@
use App\Models\SocialProvider;
use App\Models\User;
+use App\Services\Contracts\SocialProviderContract;
use Illuminate\Http\RedirectResponse;
+use Tests\Traits\ProviderTestHelpers;
+use ReflectionClass;
use Tests\TestCase;
class SocialProviderContractTest extends TestCase
{
- public function test_config_mapping_returns_expected_array()
+ use ProviderTestHelpers;
+
+ public function testConfigMappingReturnsExpectedArray()
{
- $provider = new HelperClasses\DummySocialProvider();
+ $provider = $this->makeSocialProvider();
+ $rc = new ReflectionClass($provider);
+ $this->assertTrue($rc->implementsInterface(SocialProviderContract::class));
$mapping = $provider->configMapping();
$this->assertArrayHasKey('client_id', $mapping);
$this->assertEquals('Client ID', $mapping['client_id']['name']);
$this->assertEquals('dummy-client-id', $mapping['client_id']['value']);
}
- public function test_install_returns_social_provider_instance()
+ public function testInstallReturnsSocialProviderInstance()
{
- $provider = new HelperClasses\DummySocialProvider();
+ $provider = $this->makeSocialProvider();
$socialProvider = $provider->install();
$this->assertInstanceOf(SocialProvider::class, $socialProvider);
$this->assertEquals('Dummy', $socialProvider->name);
$this->assertEquals('dummy', $socialProvider->code);
}
- public function test_redirect_returns_redirect_response()
+ public function testRedirectReturnsRedirectResponse()
{
- $provider = new HelperClasses\DummySocialProvider();
+ $provider = $this->makeSocialProvider();
$response = $provider->redirect();
$this->assertInstanceOf(RedirectResponse::class, $response);
$this->assertEquals('/dummy-redirect', $response->getTargetUrl());
}
- public function test_user_returns_user_instance()
+ public function testUserReturnsUserInstance()
{
- $provider = new HelperClasses\DummySocialProvider();
+ $provider = $this->makeSocialProvider();
$user = $provider->user();
$this->assertInstanceOf(User::class, $user);
$this->assertEquals('Dummy User', $user->name);
diff --git a/tests/Unit/app/Services/Contracts/TicketProviderContractTest.php b/tests/Unit/app/Services/Contracts/TicketProviderContractTest.php
index 3435a038..a6f83f94 100644
--- a/tests/Unit/app/Services/Contracts/TicketProviderContractTest.php
+++ b/tests/Unit/app/Services/Contracts/TicketProviderContractTest.php
@@ -4,64 +4,185 @@
use App\Models\EmailAddress;
use App\Models\TicketProvider;
+use App\Services\Contracts\TicketProviderContract;
use Illuminate\Http\Request;
+use Illuminate\Console\OutputStyle;
+use Tests\Traits\ProviderTestHelpers;
use Tests\TestCase;
+use Illuminate\Foundation\Testing\RefreshDatabase;
class TicketProviderContractTest extends TestCase
{
- public function test_config_mapping_returns_expected_array()
+ use RefreshDatabase;
+ use ProviderTestHelpers;
+
+ public function testConfigMappingReturnsExpectedArray()
{
- $provider = new HelperClasses\DummyTicketProvider();
+ $provider = $this->makeTicketProvider();
+ $this->assertImplementsInterface($provider, TicketProviderContract::class);
$mapping = $provider->configMapping();
$this->assertArrayHasKey('apikey', $mapping);
- $this->assertEquals('API Key', $mapping['apikey']['name']);
- $this->assertEquals('dummy-key', $mapping['apikey']['value']);
+ // ProviderTestHelpers returns mapping entries as objects; assert accordingly
+ $this->assertEquals('API Key', $mapping['apikey']->name ?? $mapping['apikey']['name']);
+ $this->assertEquals('dummy-key', $mapping['apikey']->value ?? $mapping['apikey']['value']);
+ }
+
+ protected TicketProviderContract $basicProviderStub;
+
+ protected function setUp(): void
+ {
+ parent::setUp();
+
+ // Reusable lightweight stub provider used by multiple tests to avoid duplication.
+ $this->basicProviderStub = new class (null) implements TicketProviderContract {
+ public function __construct(?TicketProvider $provider = null)
+ {
+ }
+ public function configMapping(): array
+ {
+ return [];
+ }
+ public function install(): TicketProvider
+ {
+ return new TicketProvider(['name' => 'Stub', 'code' => 'stub']);
+ }
+ public function processWebhook(Request $request): bool
+ {
+ return true;
+ }
+ public function syncTickets(string|EmailAddress $email): void
+ {
+ return;
+ }
+ public function getEvents(): array
+ {
+ return [];
+ }
+ public function getTicketTypes(string $eventExternalId): array
+ {
+ return [];
+ }
+ public function syncAllTickets(?OutputStyle $output): void
+ {
+ return;
+ }
+ };
}
- public function test_install_returns_ticket_provider_instance()
+ public function testInstallReturnsTicketProviderInstance()
{
- $provider = new HelperClasses\DummyTicketProvider();
+ $provider = $this->makeTicketProvider();
$ticketProvider = $provider->install();
$this->assertInstanceOf(TicketProvider::class, $ticketProvider);
$this->assertEquals('Dummy', $ticketProvider->name);
$this->assertEquals('dummy', $ticketProvider->code);
}
- public function test_process_webhook_returns_true()
+ public function testProcessWebhookReturnsTrue()
{
- $provider = new HelperClasses\DummyTicketProvider();
+ $provider = $this->basicProviderStub;
$request = Request::create('/webhook', 'POST');
$this->assertTrue($provider->processWebhook($request));
}
- public function test_sync_tickets_accepts_string_and_emailaddress()
+ public function testSyncTicketsAcceptsStringAndEmailaddress()
{
- $provider = new HelperClasses\DummyTicketProvider();
+ $provider = $this->basicProviderStub;
+
$email = 'test@example.com';
$emailAddress = new EmailAddress(['email' => $email]);
$this->assertNull($provider->syncTickets($email));
$this->assertNull($provider->syncTickets($emailAddress));
}
- public function test_get_events_returns_expected_array()
+ public function testGetEventsReturnsExpectedArray()
{
- $provider = new HelperClasses\DummyTicketProvider();
+ // Create a small stub provider that implements the contract and returns expected events
+ $provider = new class (null) implements TicketProviderContract {
+ public function __construct(?TicketProvider $provider = null)
+ {
+ }
+ public function configMapping(): array
+ {
+ return [];
+ }
+ public function install(): TicketProvider
+ {
+ return new TicketProvider(['name' => 'Stub', 'code' => 'stub']);
+ }
+ public function processWebhook(Request $request): bool
+ {
+ return true;
+ }
+ public function syncTickets(string|EmailAddress $email): void
+ {
+ return;
+ }
+ public function getEvents(): array
+ {
+ return ['evt1' => 'Event 1'];
+ }
+ public function getTicketTypes(string $eventExternalId): array
+ {
+ return [];
+ }
+ public function syncAllTickets(?OutputStyle $output): void
+ {
+ return;
+ }
+ };
+
$events = $provider->getEvents();
$this->assertArrayHasKey('evt1', $events);
$this->assertEquals('Event 1', $events['evt1']);
}
- public function test_get_ticket_types_returns_expected_array()
+ public function testGetTicketTypesReturnsExpectedArray()
{
- $provider = new HelperClasses\DummyTicketProvider();
+ // Create a stub provider returning expected types
+ $provider = new class (null) implements TicketProviderContract {
+ public function __construct(?TicketProvider $provider = null)
+ {
+ }
+ public function configMapping(): array
+ {
+ return [];
+ }
+ public function install(): TicketProvider
+ {
+ return new TicketProvider(['name' => 'Stub', 'code' => 'stub']);
+ }
+ public function processWebhook(Request $request): bool
+ {
+ return true;
+ }
+ public function syncTickets(string|EmailAddress $email): void
+ {
+ return;
+ }
+ public function getEvents(): array
+ {
+ return [];
+ }
+ public function getTicketTypes(string $eventExternalId): array
+ {
+ return ['type1' => 'VIP'];
+ }
+ public function syncAllTickets(?OutputStyle $output): void
+ {
+ return;
+ }
+ };
+
$types = $provider->getTicketTypes('evt1');
$this->assertArrayHasKey('type1', $types);
$this->assertEquals('VIP', $types['type1']);
}
- public function test_sync_all_tickets_accepts_null_output()
+ public function testSyncAllTicketsAcceptsNullOutput()
{
- $provider = new HelperClasses\DummyTicketProvider();
+ $provider = $this->basicProviderStub;
+
$this->assertNull($provider->syncAllTickets(null));
}
}
diff --git a/tests/Unit/app/Services/SocialProviders/AbstractSocialProviderTest.php b/tests/Unit/app/Services/SocialProviders/AbstractSocialProviderTest.php
index fe848e37..0c266ba6 100644
--- a/tests/Unit/app/Services/SocialProviders/AbstractSocialProviderTest.php
+++ b/tests/Unit/app/Services/SocialProviders/AbstractSocialProviderTest.php
@@ -11,15 +11,16 @@
use Illuminate\Http\RedirectResponse;
use Laravel\Socialite\Contracts\Factory as SocialiteFactoryContract;
use Tests\TestCase;
-use Tests\Unit\app\Services\SocialProviders\HelperClasses\DummySocialProvider;
+use Tests\Traits\ProviderTestHelpers;
class AbstractSocialProviderTest extends TestCase
{
use RefreshDatabase;
+ use ProviderTestHelpers;
- public function test_config_mapping_returns_expected_array()
+ public function testConfigMappingReturnsExpectedArray()
{
- $provider = new DummySocialProvider();
+ $provider = $this->makeSocialProviderVariant();
$mapping = $provider->configMapping();
$this->assertArrayHasKey('client_id', $mapping);
@@ -30,7 +31,7 @@ public function test_config_mapping_returns_expected_array()
}
- public function test_user_deletes_unverified_email_and_links_account()
+ public function testUserDeletesUnverifiedEmailAndLinksAccount()
{
$prov = SocialProvider::factory()->create(['auth_enabled' => true, 'code' => 'sp_' . uniqid()]);
$other = User::factory()->create();
@@ -83,7 +84,7 @@ public function driver($n)
};
$this->app->instance(SocialiteFactoryContract::class, $factoryStub);
- $provider = new DummySocialProvider($prov);
+ $provider = $this->makeSocialProviderVariant($prov);
$result = $provider->user($localUser);
$this->assertInstanceOf(User::class, $result);
$this->assertEquals($localUser->id, $result->id);
@@ -93,7 +94,7 @@ public function driver($n)
$this->assertDatabaseHas('linked_accounts', ['external_id' => 'rid-3', 'user_id' => $localUser->id]);
}
- public function test_user_returns_account_user_when_account_exists_and_no_local_user()
+ public function testUserReturnsAccountUserWhenAccountExistsAndNoLocalUser()
{
$prov = SocialProvider::factory()->create(['code' => 'sp_' . uniqid()]);
$user = User::factory()->create();
@@ -147,12 +148,12 @@ public function driver($n)
};
$this->app->instance(SocialiteFactoryContract::class, $factoryStub);
- $provider = new DummySocialProvider($prov);
+ $provider = $this->makeSocialProviderVariant($prov);
$result = $provider->user(null);
$this->assertEquals($user->id, $result->id);
}
- public function test_user_creates_new_user_and_email_and_account_when_auth_enabled()
+ public function testUserCreatesNewUserAndEmailAndAccountWhenAuthEnabled()
{
$prov = SocialProvider::factory()->create(['auth_enabled' => true, 'code' => 'sp_' . uniqid()]);
@@ -200,14 +201,14 @@ public function driver($n)
};
$this->app->instance(SocialiteFactoryContract::class, $factoryStub);
- $provider = new DummySocialProvider($prov);
+ $provider = $this->makeSocialProviderVariant($prov);
$result = $provider->user(null);
$this->assertInstanceOf(User::class, $result);
$this->assertDatabaseHas('linked_accounts', ['external_id' => 'rid-6', 'user_id' => $result->id]);
$this->assertDatabaseHas('email_addresses', ['email' => 'newuser@example.com', 'user_id' => $result->id]);
}
- public function test_redirect_calls_socialite_and_returns_redirect_response()
+ public function testRedirectCallsSocialiteAndReturnsRedirectResponse()
{
$driverStub = new class {
public function redirect()
@@ -231,12 +232,12 @@ public function driver($n)
$this->app->instance(SocialiteFactoryContract::class, $factoryStub);
$prov = SocialProvider::factory()->create(['code' => 'sp_' . uniqid()]);
- $provider = new DummySocialProvider($prov);
+ $provider = $this->makeSocialProviderVariant($prov);
$resp = $provider->redirect();
$this->assertInstanceOf(RedirectResponse::class, $resp);
}
- public function test_user_returns_local_user_when_account_belongs_to_local_user()
+ public function testUserReturnsLocalUserWhenAccountBelongsToLocalUser()
{
$prov = SocialProvider::factory()->create(['code' => 'sp_' . uniqid()]);
$user = User::factory()->create();
@@ -291,20 +292,20 @@ public function driver($n)
};
$this->app->instance(SocialiteFactoryContract::class, $factoryStub);
- $provider = new DummySocialProvider($prov);
+ $provider = $this->makeSocialProviderVariant($prov);
$result = $provider->user($user);
$this->assertEquals($user->id, $result->id);
}
- public function test_user_handles_email_present()
+ public function testUserHandlesEmailPresent()
{
- $provider = new DummySocialProvider();
+ $provider = $this->makeSocialProviderVariant();
$email = EmailAddress::factory()->create(['email' => 'test@example.com']);
$user = User::factory()->create();
$email->user()->associate($user);
$email->save();
$prov = SocialProvider::factory()->create(['code' => 'sp_' . uniqid()]);
- $provider = new DummySocialProvider($prov);
+ $provider = $this->makeSocialProviderVariant($prov);
$driverStub = new class {
public function user()
{
@@ -345,16 +346,16 @@ public function driver($n)
$this->assertEquals($user->id, $result->id);
}
- public function test_user_handles_missing_primary_email()
+ public function testUserHandlesMissingPrimaryEmail()
{
- $provider = new DummySocialProvider();
+ $provider = $this->makeSocialProviderVariant();
$user = User::factory()->create();
// Remove primaryEmail association
$user->primary_email_id = null;
$user->save();
$this->assertNull($user->primaryEmail);
$prov = SocialProvider::factory()->create();
- $provider = new DummySocialProvider($prov);
+ $provider = $this->makeSocialProviderVariant($prov);
// Remove primaryEmail association
$user->primary_email_id = null;
$user->save();
@@ -401,15 +402,15 @@ public function driver($n)
}
// Testing Exceptions
- public function test_user_throws_if_account_exists_and_localUser_id_mismatch()
+ public function testUserThrowsIfAccountExistsAndLocalUserIdMismatch()
{
- $provider = new DummySocialProvider();
+ $provider = $this->makeSocialProviderVariant();
$localUser = User::factory()->create();
$otherUser = User::factory()->create();
$account = LinkedAccount::factory()->create(['user_id' => $otherUser->id, 'external_id' => 'dummy_' . uniqid(),]);
// Set up provider and account directly
$prov = SocialProvider::factory()->create(['code' => 'sp_' . uniqid()]);
- $provider = new DummySocialProvider($prov);
+ $provider = $this->makeSocialProviderVariant($prov);
$account->provider()->associate($prov);
$account->save();
// Patch Socialite driver so the provider->user() call doesn't fail due to unsupported driver
@@ -469,7 +470,7 @@ public function driver($n)
$provider->user($localUser);
}
- public function test_user_throws_if_email_verified_and_associated_with_other_user()
+ public function testUserThrowsIfEmailVerifiedAndAssociatedWithOtherUser()
{
$prov = SocialProvider::factory()->create(['code' => 'sp_' . uniqid()]);
$emailOwner = User::factory()->create();
@@ -522,13 +523,13 @@ public function driver($n)
};
$this->app->instance(SocialiteFactoryContract::class, $factoryStub);
- $provider = new DummySocialProvider($prov);
+ $provider = $this->makeSocialProviderVariant($prov);
$this->expectException(SocialProviderException::class);
$this->expectExceptionMessage('Email is already associated with another user');
$provider->user($localUser);
}
- public function test_user_throws_when_no_account_and_auth_disabled()
+ public function testUserThrowsWhenNoAccountAndAuthDisabled()
{
$prov = SocialProvider::factory()->create(['auth_enabled' => false, 'code' => 'sp_' . uniqid()]);
@@ -576,7 +577,7 @@ public function driver($n)
};
$this->app->instance(SocialiteFactoryContract::class, $factoryStub);
- $provider = new DummySocialProvider($prov);
+ $provider = $this->makeSocialProviderVariant($prov);
$this->expectException(SocialProviderException::class);
$this->expectExceptionMessage('Unable to login with this account');
$provider->user(null);
diff --git a/tests/Unit/app/Services/SocialProviders/DiscordProviderTest.php b/tests/Unit/app/Services/SocialProviders/DiscordProviderTest.php
index ddb22533..ae24efba 100644
--- a/tests/Unit/app/Services/SocialProviders/DiscordProviderTest.php
+++ b/tests/Unit/app/Services/SocialProviders/DiscordProviderTest.php
@@ -34,7 +34,7 @@ protected function getProvider(array $settings = [])
return new DiscordProvider($socialProvider);
}
- public function test_config_mapping_includes_token()
+ public function testConfigMappingIncludesToken()
{
$provider = $this->getProvider();
$mapping = $provider->configMapping();
@@ -46,7 +46,7 @@ public function test_config_mapping_includes_token()
$this->assertTrue($mapping['token']->encrypted);
}
- public function test_get_socialite_provider_returns_provider_instance()
+ public function testGetSocialiteProviderReturnsProviderInstance()
{
$provider = $this->getProvider([
'client_id' => 'id',
@@ -59,7 +59,7 @@ public function test_get_socialite_provider_returns_provider_instance()
$this->assertSame($mockSocialite, $method->invoke($provider));
}
- public function test_update_account_sets_fields()
+ public function testUpdateAccountSetsFields()
{
$provider = $this->getProvider();
$account = new LinkedAccount();
@@ -94,7 +94,7 @@ public function getNickname()
$this->assertEquals('nickname', $account->name);
}
- public function test_get_bot_provider_calls_socialite_with_scopes_and_permissions()
+ public function testGetBotProviderCallsSocialiteWithScopesAndPermissions()
{
$provider = $this->getProvider([
'client_id' => 'id',
@@ -110,7 +110,7 @@ public function test_get_bot_provider_calls_socialite_with_scopes_and_permission
$this->assertSame($mockSocialite, $method->invoke($provider));
}
- public function test_add_bot_to_server_redirects()
+ public function testAddBotToServerRedirects()
{
$provider = $this->getProvider([
'client_id' => 'id',
@@ -127,7 +127,7 @@ public function test_add_bot_to_server_redirects()
$this->assertEquals('/discord-bot-redirect', $response->getTargetUrl());
}
- public function test_bot_returns_user()
+ public function testBotReturnsUser()
{
$provider = $this->getProvider([
'client_id' => 'id',
diff --git a/tests/Unit/app/Services/SocialProviders/HelperClasses/DummySocialProvider.php b/tests/Unit/app/Services/SocialProviders/HelperClasses/DummySocialProvider.php
deleted file mode 100644
index d9b4d766..00000000
--- a/tests/Unit/app/Services/SocialProviders/HelperClasses/DummySocialProvider.php
+++ /dev/null
@@ -1,25 +0,0 @@
-getProvider();
$mapping = $provider->configMapping();
@@ -45,7 +45,7 @@ public function test_config_mapping_includes_host()
$this->assertEquals('required|string', $mapping['host']->validation);
}
- public function test_name_can_be_renamed_from_provider()
+ public function testNameCanBeRenamedFromProvider()
{
$socialProvider = SocialProvider::factory()->create([
'name' => 'Custom Passport',
@@ -59,7 +59,7 @@ public function test_name_can_be_renamed_from_provider()
$this->assertEquals('Custom Passport', $nameProperty->getValue($provider));
}
- public function test_get_socialite_provider_builds_provider_with_config()
+ public function testGetSocialiteProviderBuildsProviderWithConfig()
{
$provider = $this->getProvider([
'client_id' => 'id',
@@ -92,7 +92,7 @@ public function with($arr)
$this->assertSame($mockSocialiteProvider, $result);
}
- public function test_update_account_sets_fields()
+ public function testUpdateAccountSetsFields()
{
$provider = $this->getProvider();
$account = new LinkedAccount();
diff --git a/tests/Unit/app/Services/SocialProviders/SteamProviderTest.php b/tests/Unit/app/Services/SocialProviders/SteamProviderTest.php
index 8a6b3057..eb3253b4 100644
--- a/tests/Unit/app/Services/SocialProviders/SteamProviderTest.php
+++ b/tests/Unit/app/Services/SocialProviders/SteamProviderTest.php
@@ -35,7 +35,7 @@ protected function getProvider(array $settings = [], ?string $redirectUrl = null
return new SteamProvider($socialProvider, $redirectUrl);
}
- public function test_config_mapping_returns_expected_array()
+ public function testConfigMappingReturnsExpectedArray()
{
$provider = $this->getProvider();
$mapping = $provider->configMapping();
@@ -46,7 +46,7 @@ public function test_config_mapping_returns_expected_array()
$this->assertTrue($mapping['client_secret']->encrypted);
}
- public function test_get_socialite_provider_builds_provider_with_config()
+ public function testGetSocialiteProviderBuildsProviderWithConfig()
{
$provider = $this->getProvider(['client_secret' => 'secret-key'], 'https://redirect.url');
$mockSocialiteProvider = Mockery::mock(SteamSocialiteProvider::class);
@@ -70,7 +70,7 @@ public function test_get_socialite_provider_builds_provider_with_config()
$this->assertSame($mockSocialiteProvider, $result);
}
- public function test_update_account_sets_fields()
+ public function testUpdateAccountSetsFields()
{
$provider = $this->getProvider();
$account = new LinkedAccount();
diff --git a/tests/Unit/app/Services/SocialProviders/TwitchProviderTest.php b/tests/Unit/app/Services/SocialProviders/TwitchProviderTest.php
index ef3b1a15..3905be39 100644
--- a/tests/Unit/app/Services/SocialProviders/TwitchProviderTest.php
+++ b/tests/Unit/app/Services/SocialProviders/TwitchProviderTest.php
@@ -34,7 +34,7 @@ protected function getProvider(array $settings = [], ?string $redirectUrl = null
return new TwitchProvider($socialProvider, $redirectUrl);
}
- public function test_config_mapping_returns_expected_array()
+ public function testConfigMappingReturnsExpectedArray()
{
$provider = $this->getProvider();
$mapping = $provider->configMapping();
@@ -46,7 +46,7 @@ public function test_config_mapping_returns_expected_array()
$this->assertTrue($mapping['client_secret']->encrypted);
}
- public function test_get_socialite_provider_builds_provider_with_config()
+ public function testGetSocialiteProviderBuildsProviderWithConfig()
{
$provider = $this->getProvider([
'client_id' => 'id',
@@ -64,7 +64,7 @@ public function test_get_socialite_provider_builds_provider_with_config()
$this->assertSame($mockSocialiteProvider, $result);
}
- public function test_update_account_sets_fields()
+ public function testUpdateAccountSetsFields()
{
$provider = $this->getProvider();
$account = new LinkedAccount();
diff --git a/tests/Unit/app/Services/TicketProviders/AbstractTicketProviderTest.php b/tests/Unit/app/Services/TicketProviders/AbstractTicketProviderTest.php
index c8467e5a..94d9d83e 100644
--- a/tests/Unit/app/Services/TicketProviders/AbstractTicketProviderTest.php
+++ b/tests/Unit/app/Services/TicketProviders/AbstractTicketProviderTest.php
@@ -5,19 +5,48 @@
use App\Enums\SettingType;
use App\Models\ProviderSetting;
use App\Models\TicketProvider;
+use App\Services\TicketProviders\AbstractTicketProvider;
use Carbon\Carbon;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Http\Request;
+use ReflectionClass;
+use ReflectionMethod;
use Tests\TestCase;
-use Tests\Unit\app\Services\TicketProviders\HelperClasses\DummyTicketProvider;
class AbstractTicketProviderTest extends TestCase
{
use RefreshDatabase;
- public function test_config_mapping_returns_expected_array()
+ protected function makeProviderInstance($providerModel = null, array $overrides = [])
{
- $provider = new DummyTicketProvider();
+ $class = new class ($providerModel) extends AbstractTicketProvider {
+ protected string $name = 'Dummy Provider';
+ protected string $code = 'dummy';
+
+ public function __construct($provider = null)
+ {
+ parent::__construct($provider);
+ }
+ };
+
+ // Apply overrides via reflection if provided
+ if (!empty($overrides)) {
+ $rc = new ReflectionClass($class);
+ foreach ($overrides as $prop => $value) {
+ if ($rc->hasProperty($prop)) {
+ $p = $rc->getProperty($prop);
+ $p->setAccessible(true);
+ $p->setValue($class, $value);
+ }
+ }
+ }
+
+ return $class;
+ }
+
+ public function testConfigMappingReturnsExpectedArray()
+ {
+ $provider = $this->makeProviderInstance();
$mapping = $provider->configMapping();
$this->assertArrayHasKey('apikey', $mapping);
@@ -26,9 +55,9 @@ public function test_config_mapping_returns_expected_array()
$this->assertEquals('Webhook Secret', $mapping['webhook_secret']->name);
}
- public function test_install_creates_ticket_provider_and_settings()
+ public function testInstallCreatesTicketProviderAndSettings()
{
- $provider = new DummyTicketProvider();
+ $provider = $this->makeProviderInstance();
$ticketProvider = $provider->install();
$this->assertInstanceOf(TicketProvider::class, $ticketProvider);
@@ -40,9 +69,9 @@ public function test_install_creates_ticket_provider_and_settings()
$this->assertContains('webhook_secret', $settings);
}
- public function test_install_does_not_duplicate_provider()
+ public function testInstallDoesNotDuplicateProvider()
{
- $provider = new DummyTicketProvider();
+ $provider = $this->makeProviderInstance();
$first = $provider->install();
$second = $provider->install();
@@ -50,13 +79,13 @@ public function test_install_does_not_duplicate_provider()
$this->assertCount(1, TicketProvider::whereCode('dummy')->get());
}
- public function test_install_settings_updates_existing_settings()
+ public function testInstallSettingsUpdatesExistingSettings()
{
- $provider = new DummyTicketProvider();
+ $provider = $this->makeProviderInstance();
$ticketProvider = TicketProvider::factory()->create([
'name' => 'Dummy Provider',
'code' => 'dummy',
- 'provider_class' => DummyTicketProvider::class,
+ 'provider_class' => AbstractTicketProvider::class,
]);
$providerSetting = ProviderSetting::factory()->create([
'provider_type' => TicketProvider::class,
@@ -64,48 +93,52 @@ public function test_install_settings_updates_existing_settings()
'code' => 'apikey',
'name' => 'Old Name',
]);
- $provider = new DummyTicketProvider($ticketProvider);
+ // Create provider instance bound to the created model
+ $provider = $this->makeProviderInstance($ticketProvider);
$provider->installSettings();
$providerSetting->refresh();
$this->assertEquals('API Key', $providerSetting->name);
}
- public function test_process_webhook_returns_true()
+ public function testProcessWebhookReturnsTrue()
{
- $provider = new DummyTicketProvider();
+ $provider = $this->makeProviderInstance();
$request = Request::create('/webhook', 'POST');
$this->assertTrue($provider->processWebhook($request));
}
- public function test_get_events_returns_empty_array()
+ public function testGetEventsReturnsEmptyArray()
{
- $provider = new DummyTicketProvider();
+ $provider = $this->makeProviderInstance();
$this->assertEquals([], $provider->getEvents());
}
- public function test_get_ticket_types_returns_empty_array()
+ public function testGetTicketTypesReturnsEmptyArray()
{
- $provider = new DummyTicketProvider();
+ $provider = $this->makeProviderInstance();
$this->assertEquals([], $provider->getTicketTypes('event-id'));
}
- public function test_sync_tickets_does_nothing()
+ public function testSyncTicketsDoesNothing()
{
- $provider = new DummyTicketProvider();
+ $provider = $this->makeProviderInstance();
$this->assertNull($provider->syncTickets('test@example.com'));
}
- public function test_sync_all_tickets_does_nothing()
+ public function testSyncAllTicketsDoesNothing()
{
- $provider = new DummyTicketProvider();
+ $provider = $this->makeProviderInstance();
$this->assertNull($provider->syncAllTickets(null));
}
- public function test_install_settings_sets_initial_value()
+ public function testInstallSettingsSetsInitialValue()
{
// Create an anonymous subclass that provides a default value in the config mapping
- $provider = new class extends DummyTicketProvider {
+ $provider = new class extends AbstractTicketProvider {
+ protected string $name = 'Dummy Provider';
+ protected string $code = 'dummy';
+
public function configMapping(): array
{
return [
@@ -128,13 +161,13 @@ public function configMapping(): array
$this->assertEquals('INIT_KEY', $setting->value);
}
- public function test_install_settings_does_not_save_when_no_changes()
+ public function testInstallSettingsDoesNotSaveWhenNoChanges()
{
// Create provider model and a setting that already matches the config mapping
$ticketProvider = TicketProvider::factory()->create([
'name' => 'Dummy Provider',
'code' => 'dummy',
- 'provider_class' => DummyTicketProvider::class,
+ 'provider_class' => AbstractTicketProvider::class,
]);
$providerSetting = ProviderSetting::factory()->create([
'provider_type' => TicketProvider::class,
@@ -153,7 +186,7 @@ public function test_install_settings_does_not_save_when_no_changes()
$providerSetting->updated_at = $past;
$providerSetting->save();
- $provider = new DummyTicketProvider($ticketProvider);
+ $provider = $this->makeProviderInstance($ticketProvider);
$provider->installSettings();
$providerSetting->refresh();
diff --git a/tests/Unit/app/Services/TicketProviders/DummyTicketTailorProvider.php b/tests/Unit/app/Services/TicketProviders/DummyTicketTailorProvider.php
deleted file mode 100644
index 1e8c6aa4..00000000
--- a/tests/Unit/app/Services/TicketProviders/DummyTicketTailorProvider.php
+++ /dev/null
@@ -1,149 +0,0 @@
-verifyWebhook($request);
- }
-
- public function processTicketPublic(object $data): ?Ticket
- {
- // ensure event and ticket type mappings exist for test data
- $this->ensureEventAndTypeExist($data);
- // ensure barcode exists to avoid undefined property in makeTicket
- if (!isset($data->barcode)) {
- $data->barcode = $data->reference ?? ($data->id ?? 'ref');
- }
- return $this->processTicket($data);
- }
-
- public function makeTicketPublic(?User $user, object $data): ?Ticket
- {
- // Ensure fixtures exist so makeTicket() can succeed
- $this->ensureEventAndTypeExist($data);
- if (!isset($data->barcode)) {
- $data->barcode = $data->reference ?? ($data->id ?? 'ref');
- }
- return $this->makeTicket($user, $data);
- }
-
- public function getEventPublic(string $externalId)
- {
- $event = $this->getEvent($externalId);
- if ($event) {
- return $event;
- }
- $event = Event::factory()->create();
- $em = new EventMapping();
- $em->provider()->associate($this->provider);
- $em->event()->associate($event);
- $em->external_id = $externalId;
- $em->save();
- return $event;
- }
-
- public function getTypePublic(string $externalId)
- {
- $type = $this->getType($externalId);
- if ($type) {
- return $type;
- }
- $event = Event::factory()->create();
- $type = TicketType::factory()->for($event)->create();
- $tm = new TicketTypeMapping();
- $tm->provider()->associate($this->provider);
- $tm->type()->associate($type);
- $tm->external_id = $externalId;
- $tm->save();
- return $type;
- }
-
- public function getQrCodePublic(object $data): string
- {
- return $this->getQrCode($data);
- }
-
- public function getClientPublic(): Client
- {
- return $this->getClient();
- }
-
- public function getTicketsPublic(?string $address = null): array
- {
- // avoid calling the real HTTP API in unit tests
- return [(object)['id' => 't1', 'status' => 'valid', 'email' => $address ?? 'a@b.test', 'event_id' => 'evt-1', 'ticket_type_id' => 'type-1', 'barcode' => 'b1', 'description' => 'Test']];
- }
-
- public function getTicketTypesPublic(string $eventExternalId): array
- {
- // Return a simple static mapping for tests to avoid HTTP calls
- return ['type1' => 'General Admission'];
- }
-
- public function getEventsPublic(): array
- {
- return ['evt1' => 'Event 1'];
- }
-
- /**
- * Ensure there is an Event and TicketType with mappings for the provider so
- * protected methods that rely on DB lookups succeed during tests.
- */
- protected function ensureEventAndTypeExist(object $data): void
- {
- // Only create fixtures automatically for event IDs that look like real provider ids
- $id = (string)($data->event_id ?? '');
- if ($id === '' || (strpos($id, 'evt') === false && strpos($id, 'EVT') === false && !is_numeric($id))) {
- // leave alone - tests expecting missing event should get null
- return;
- }
-
- // Create or find an Event
- $event = Event::whereHas('mappings', function ($q) use ($data) {
- $q->whereTicketProviderId($this->provider->id)->whereExternalId($data->event_id);
- })->first();
- if (!$event) {
- $event = Event::factory()->create();
- $em = new EventMapping();
- $em->provider()->associate($this->provider);
- $em->event()->associate($event);
- $em->external_id = $data->event_id;
- $em->save();
- }
-
- // Create or find TicketType mapping
- $type = TicketType::whereHas('mappings', function ($q) use ($data) {
- $q->whereTicketProviderId($this->provider->id)->whereExternalId($data->ticket_type_id);
- })->first();
- if (!$type) {
- $type = TicketType::factory()->for($event)->create();
- $tm = new TicketTypeMapping();
- $tm->provider()->associate($this->provider);
- $tm->type()->associate($type);
- $tm->external_id = $data->ticket_type_id;
- $tm->save();
- }
- }
-}
diff --git a/tests/Unit/app/Services/TicketProviders/FakeProviderTest.php b/tests/Unit/app/Services/TicketProviders/FakeProviderTest.php
index 74c87115..72f77d0f 100644
--- a/tests/Unit/app/Services/TicketProviders/FakeProviderTest.php
+++ b/tests/Unit/app/Services/TicketProviders/FakeProviderTest.php
@@ -16,7 +16,7 @@ class FakeProviderTest extends TestCase
{
use RefreshDatabase;
- public function test_config_mapping_returns_array()
+ public function testConfigMappingReturnsArray()
{
$provider = new FakeProvider();
$mapping = $provider->configMapping();
@@ -25,7 +25,7 @@ public function test_config_mapping_returns_array()
$this->assertEmpty($mapping);
}
- public function test_install_returns_given_provider()
+ public function testInstallReturnsGivenProvider()
{
$tp = TicketProvider::factory()->create();
$provider = new FakeProvider($tp);
@@ -35,7 +35,7 @@ public function test_install_returns_given_provider()
$this->assertEquals($tp->id, $result->id);
}
- public function test_install_throws_when_no_provider()
+ public function testInstallThrowsWhenNoProvider()
{
$this->expectException(RuntimeException::class);
@@ -43,7 +43,7 @@ public function test_install_throws_when_no_provider()
$provider->install();
}
- public function test_process_webhook_returns_true()
+ public function testProcessWebhookReturnsTrue()
{
$provider = new FakeProvider();
$request = Request::create('/webhook', 'POST');
@@ -51,13 +51,13 @@ public function test_process_webhook_returns_true()
$this->assertTrue($provider->processWebhook($request));
}
- public function test_sync_tickets_is_noop_and_returns_null()
+ public function testSyncTicketsIsNoopAndReturnsNull()
{
$provider = new FakeProvider();
$this->assertNull($provider->syncTickets('user@example.com'));
}
- public function test_get_events_and_ticket_types_return_empty_arrays()
+ public function testGetEventsAndTicketTypesReturnEmptyArrays()
{
$provider = new FakeProvider();
@@ -65,7 +65,7 @@ public function test_get_events_and_ticket_types_return_empty_arrays()
$this->assertEquals([], $provider->getTicketTypes('event-id'));
}
- public function test_sync_all_tickets_writes_to_output_when_outputstyle_provided()
+ public function testSyncAllTicketsWritesToOutputWhenOutputstyleProvided()
{
$provider = new FakeProvider();
diff --git a/tests/Unit/app/Services/TicketProviders/GenericTicketProviderTest.php b/tests/Unit/app/Services/TicketProviders/GenericTicketProviderTest.php
index 1df97292..fb72f3bc 100644
--- a/tests/Unit/app/Services/TicketProviders/GenericTicketProviderTest.php
+++ b/tests/Unit/app/Services/TicketProviders/GenericTicketProviderTest.php
@@ -21,11 +21,12 @@
use Illuminate\Support\Facades\Cache;
use ReflectionClass;
use Tests\TestCase;
-use Tests\Unit\app\Services\TicketProviders\HelperClasses\DummyGenericTicketProvider;
+use Tests\Traits\ProviderTestHelpers;
class GenericTicketProviderTest extends TestCase
{
use RefreshDatabase;
+ use ProviderTestHelpers;
protected $provider;
@@ -65,12 +66,10 @@ protected function createProvider(array $settings = [])
]);
}
// Return a test helper that exposes protected methods
- return new DummyGenericTicketProvider($ticketProvider);
+ return $this->makeTicketProvider($ticketProvider);
}
- // --- Extra tests merged from GenericTicketProviderExtraTest.php ---
-
- public function test_make_ticket_returns_null_when_event_missing()
+ public function testMakeTicketReturnsNullWhenEventMissing()
{
$provider = $this->createProvider();
$data = (object)[
@@ -82,10 +81,10 @@ public function test_make_ticket_returns_null_when_event_missing()
'reference' => 'ref1',
];
- $this->assertNull($provider->makeTicketPublic(null, $data));
+ $this->assertNull($this->callProtected($provider, 'makeTicket', [null, $data]));
}
- public function test_make_ticket_returns_null_when_type_missing()
+ public function testMakeTicketReturnsNullWhenTypeMissing()
{
$provider = $this->createProvider();
$event = Event::factory()->create();
@@ -100,10 +99,10 @@ public function test_make_ticket_returns_null_when_type_missing()
'reference' => 'ref1',
];
- $this->assertNull($provider->makeTicketPublic(null, $data));
+ $this->assertNull($this->callProtected($provider, 'makeTicket', [null, $data]));
}
- public function test_make_ticket_creates_ticket_when_event_and_type_exist_and_links_user()
+ public function testMakeTicketCreatesTicketWhenEventAndTypeExistAndLinksUser()
{
$provider = $this->createProvider();
$user = User::factory()->create();
@@ -126,7 +125,7 @@ public function test_make_ticket_creates_ticket_when_event_and_type_exist_and_li
'reference' => 'ref2',
];
- $ticket = $provider->makeTicketPublic(null, $data);
+ $ticket = $this->callProtected($provider, 'makeTicket', [null, $data]);
$this->assertInstanceOf(Ticket::class, $ticket);
$this->assertEquals('t2', $ticket->external_id);
// reload relations/columns from DB to be sure associations persisted
@@ -140,7 +139,7 @@ public function test_make_ticket_creates_ticket_when_event_and_type_exist_and_li
$this->assertEquals($user->id, $ticket->user->id);
}
- public function test_get_tickets_pages_until_hasMore_is_false()
+ public function testGetTicketsPagesUntilHasMoreIsFalse()
{
$provider = $this->createProvider([
'endpoint' => 'https://api.example.test',
@@ -158,21 +157,21 @@ public function test_get_tickets_pages_until_hasMore_is_false()
$mock = new MockHandler([$resp1, $resp2]);
$handler = HandlerStack::create($mock);
- $client = new Client(['handler' => $handler]);
+ $client = new Client(['handler' => $handler, 'base_uri' => 'https://api.example.test']);
$ref = new ReflectionClass($provider);
$prop = $ref->getProperty('client');
$prop->setAccessible(true);
$prop->setValue($provider, $client);
- $tickets = $provider->getTicketsPublic(null);
+ $tickets = $this->callProtected($provider, 'getTickets', [null]);
// Current implementation resets the page buffer each loop and returns the last page only
$this->assertCount(1, $tickets);
$this->assertArrayNotHasKey('1', $tickets);
$this->assertArrayHasKey('2', $tickets);
}
- public function test_sync_tickets_removes_voided_and_adds_missing()
+ public function testGetTicketsFetchesFromApiAndPages()
{
$provider = $this->createProvider(['apikey' => 'key', 'endpoint' => 'https://api.example.test']);
@@ -190,7 +189,7 @@ public function test_sync_tickets_removes_voided_and_adds_missing()
$mock = new MockHandler([$resp]);
$handler = HandlerStack::create($mock);
- $client = new Client(['handler' => $handler]);
+ $client = new Client(['handler' => $handler, 'base_uri' => 'https://api.example.test']);
$ref = new ReflectionClass($provider);
$prop = $ref->getProperty('client');
$prop->setAccessible(true);
@@ -214,9 +213,10 @@ public function test_sync_tickets_removes_voided_and_adds_missing()
}
- public function test_config_mapping_returns_expected_array()
+ public function testConfigMappingReturnsExpectedArray()
{
- $provider = $this->provider;
+ // Instantiate the provider directly to test the public configMapping method
+ $provider = new GenericTicketProvider();
$mapping = $provider->configMapping();
$this->assertArrayHasKey('apikey', $mapping);
@@ -226,7 +226,26 @@ public function test_config_mapping_returns_expected_array()
$this->assertEquals('Base URL', $mapping['endpoint']->name);
}
- public function test_process_webhook_calls_process_ticket_and_returns_true()
+ public function testConfigMappingStructureAndValidation()
+ {
+ $provider = new GenericTicketProvider();
+ $mapping = $provider->configMapping();
+
+ // mapping entries should be objects with expected keys
+ $this->assertIsArray($mapping);
+ $this->assertIsObject($mapping['apikey']);
+ $this->assertIsObject($mapping['endpoint']);
+
+ // validation strings must match the implementation contract
+ $this->assertEquals('required|string', $mapping['apikey']->validation);
+ $this->assertEquals('required|string', $mapping['endpoint']->validation);
+
+ // apikey should be marked encrypted; endpoint should not have an encrypted flag
+ $this->assertTrue(isset($mapping['apikey']->encrypted) && $mapping['apikey']->encrypted);
+ $this->assertFalse(property_exists($mapping['endpoint'], 'encrypted'));
+ }
+
+ public function testProcessWebhookCallsProcessTicketAndReturnsTrue()
{
$provider = $this->provider;
$mock = new class ($provider->provider) extends GenericTicketProvider {
@@ -245,7 +264,7 @@ protected function processTicket(object $payload): ?Ticket
$this->assertTrue($mock->processWebhook($request));
}
- public function test_get_events_returns_cached_data()
+ public function testGetEventsReturnsCachedData()
{
$provider = $this->provider;
$key = "ticketproviders.{$provider->provider->id}.{$provider->provider->cache_prefix}.events";
@@ -254,7 +273,7 @@ public function test_get_events_returns_cached_data()
$this->assertEquals(['evt1' => 'Event 1'], $events);
}
- public function test_get_events_fetches_from_api_and_caches()
+ public function testGetEventsFetchesFromApiAndCaches()
{
$provider = $this->createProvider([
'apikey' => 'key',
@@ -272,7 +291,7 @@ public function test_get_events_fetches_from_api_and_caches()
]));
$mock = new MockHandler([$mockResponse]);
$handlerStack = HandlerStack::create($mock);
- $guzzleClient = new Client(['handler' => $handlerStack]);
+ $guzzleClient = new Client(['handler' => $handlerStack, 'base_uri' => 'https://api.example.test']);
// Set the Guzzle client onto the provider (bypass visibility via reflection)
$providerReflection = new ReflectionClass($provider);
@@ -288,7 +307,7 @@ public function test_get_events_fetches_from_api_and_caches()
$this->assertEquals($events, $cached);
}
- public function test_get_ticket_types_returns_cached_data()
+ public function testGetTicketTypesReturnsCachedData()
{
$provider = $this->provider;
$eventId = 'evt-1';
@@ -298,7 +317,7 @@ public function test_get_ticket_types_returns_cached_data()
$this->assertEquals(['type1' => 'VIP'], $types);
}
- public function test_get_ticket_types_fetches_from_api_and_caches()
+ public function testGetTicketTypesFetchesFromApiAndCaches()
{
$provider = $this->createProvider([
'apikey' => 'key',
@@ -317,7 +336,7 @@ public function test_get_ticket_types_fetches_from_api_and_caches()
]));
$mock = new MockHandler([$mockResponse]);
$handlerStack = HandlerStack::create($mock);
- $guzzleClient = new Client(['handler' => $handlerStack]);
+ $guzzleClient = new Client(['handler' => $handlerStack, 'base_uri' => 'https://api.example.test']);
$providerReflection = new ReflectionClass($provider);
$clientProp = $providerReflection->getProperty('client');
@@ -332,7 +351,7 @@ public function test_get_ticket_types_fetches_from_api_and_caches()
$this->assertEquals($types, $cached);
}
- public function test_process_ticket()
+ public function testProcessTicket()
{
$provider = $this->createProvider();
@@ -363,12 +382,12 @@ public function test_process_ticket()
'reference' => 'ref1',
];
- $result = $provider->processTicketPublic($data);
+ $result = $this->callProtected($provider, 'processTicket', [$data]);
$this->assertInstanceOf(Ticket::class, $result);
$this->assertDatabaseHas('tickets', ['external_id' => 't1']);
}
- public function test_process_ticket_deletes_existing_when_voided()
+ public function testProcessTicketDeletesExistingWhenVoided()
{
$provider = $this->createProvider();
@@ -386,13 +405,13 @@ public function test_process_ticket_deletes_existing_when_voided()
'email' => 'nobody@example.com',
];
- $result = $provider->processTicketPublic($data);
+ $result = $this->callProtected($provider, 'processTicket', [$data]);
// The DB row should have been deleted
$this->assertDatabaseMissing('tickets', ['external_id' => 'del-me']);
}
- public function test_process_ticket_returns_null_when_event_missing()
+ public function testProcessTicketReturnsNullWhenEventMissing()
{
$provider = $this->createProvider();
@@ -405,11 +424,11 @@ public function test_process_ticket_returns_null_when_event_missing()
'email' => 'foo@example.com',
];
- $result = $provider->processTicketPublic($data);
+ $result = $this->callProtected($provider, 'processTicket', [$data]);
$this->assertNull($result, 'processTicket should return null when the event mapping is missing');
}
- public function test_sync_tickets_deletes_voided_ticket()
+ public function testSyncTicketsDeletesVoidedTicket()
{
$provider = $this->createProvider(['apikey' => 'key', 'endpoint' => 'https://api.example.test']);
@@ -425,7 +444,7 @@ public function test_sync_tickets_deletes_voided_ticket()
$mock = new MockHandler([$resp]);
$handler = HandlerStack::create($mock);
- $client = new Client(['handler' => $handler]);
+ $client = new Client(['handler' => $handler, 'base_uri' => 'https://api.example.test']);
$ref = new ReflectionClass($provider);
$prop = $ref->getProperty('client');
$prop->setAccessible(true);
@@ -436,7 +455,7 @@ public function test_sync_tickets_deletes_voided_ticket()
$this->assertDatabaseMissing('tickets', ['external_id' => 'voided1']);
}
- public function test_process_ticket_returns_existing_when_not_voided()
+ public function testProcessTicketReturnsExistingWhenNotVoided()
{
$provider = $this->createProvider();
@@ -453,12 +472,12 @@ public function test_process_ticket_returns_existing_when_not_voided()
'email' => 'nobody@example.com',
];
- $result = $provider->processTicketPublic($data);
+ $result = $this->callProtected($provider, 'processTicket', [$data]);
$this->assertInstanceOf(Ticket::class, $result);
$this->assertDatabaseHas('tickets', ['external_id' => 'keep-me']);
}
- public function test_sync_tickets_assigns_user_when_emailaddress_provided()
+ public function testSyncTicketsAssignsUserWhenEmailaddressProvided()
{
$provider = $this->createProvider(['endpoint' => 'https://api.example.test', 'apikey' => 'key']);
@@ -483,7 +502,7 @@ public function test_sync_tickets_assigns_user_when_emailaddress_provided()
$mock = new MockHandler([$resp]);
$handler = HandlerStack::create($mock);
- $client = new Client(['handler' => $handler]);
+ $client = new Client(['handler' => $handler, 'base_uri' => 'https://api.example.test']);
$ref = new ReflectionClass($provider);
$prop = $ref->getProperty('client');
$prop->setAccessible(true);
@@ -497,9 +516,9 @@ public function test_sync_tickets_assigns_user_when_emailaddress_provided()
$this->assertEquals($user->id, $existing->user_id);
}
- public function test_get_client()
+ public function testGetClient()
{
$provider = $this->createProvider();
- $this->assertInstanceOf(Client::class, $provider->getClientPublic());
+ $this->assertInstanceOf(Client::class, $this->callProtected($provider, 'getClient'));
}
}
diff --git a/tests/Unit/app/Services/TicketProviders/HelperClasses/DummyGenericTicketProvider.php b/tests/Unit/app/Services/TicketProviders/HelperClasses/DummyGenericTicketProvider.php
deleted file mode 100644
index 47ff0dee..00000000
--- a/tests/Unit/app/Services/TicketProviders/HelperClasses/DummyGenericTicketProvider.php
+++ /dev/null
@@ -1,58 +0,0 @@
-provider = $p;
- }
-
- // expose protected methods for testing convenience
- public function getClientPublic(): Client
- {
- return $this->getClient();
- }
-
- public function getTicketsPublic(?string $address = null): array
- {
- return $this->getTickets($address);
- }
-
- public function processTicketPublic(object $data)
- {
- return $this->processTicket($data);
- }
-
- public function makeTicketPublic(?User $user, object $data)
- {
- return $this->makeTicket($user, $data);
- }
-
- public function getEventPublic(string $externalId)
- {
- return $this->getEvent($externalId);
- }
-
- public function getTypePublic(string $externalId)
- {
- return $this->getType($externalId);
- }
-
- public function getQrCodePublic(object $data): string
- {
- return $this->getQrCode($data);
- }
-}
diff --git a/tests/Unit/app/Services/TicketProviders/HelperClasses/DummyProviderWithSyncAll.php b/tests/Unit/app/Services/TicketProviders/HelperClasses/DummyProviderWithSyncAll.php
deleted file mode 100644
index e022910c..00000000
--- a/tests/Unit/app/Services/TicketProviders/HelperClasses/DummyProviderWithSyncAll.php
+++ /dev/null
@@ -1,38 +0,0 @@
-provider = $provider;
- }
-
- public function getTickets(?string $address = null): array
- {
- return [];
- }
-
- protected function makeTicket(?User $user, object $data): ?Ticket
- {
- $this->makeTicketCalled = true;
- $this->makeTicketArgs[] = [$user, $data];
-
- return new Ticket([
- 'user_id' => $user->id ?? 1,
- ]);
- }
-}
diff --git a/tests/Unit/app/Services/TicketProviders/HelperClasses/DummyTicketProvider.php b/tests/Unit/app/Services/TicketProviders/HelperClasses/DummyTicketProvider.php
deleted file mode 100644
index 21d6af19..00000000
--- a/tests/Unit/app/Services/TicketProviders/HelperClasses/DummyTicketProvider.php
+++ /dev/null
@@ -1,16 +0,0 @@
-provider = $provider;
- parent::__construct($provider);
- }
-
- public function getTicketsPublic(?string $address = null): array
- {
- // avoid real HTTP calls in unit tests
- return [(object)['id' => 'w1', 'status' => 'valid', 'email' => $address ?? 'a@b.test', 'event_id' => 'evt-1', 'ticket_type_id' => 'type-1', 'barcode' => 'b1', 'description' => 'WC ticket']];
- }
-
- public function processTicketsPublic(array $ticketData, string $address, ?User $user = null): void
- {
- // ensure fixtures exist for supplied ticket data
- foreach ($ticketData as $d) {
- $this->ensureEventAndTypeExist($d);
- }
- $this->processTickets($ticketData, $address, $user);
- }
-
- public function makeTicketPublic(?User $user, object $data): ?Ticket
- {
- $this->ensureEventAndTypeExist($data);
- if (!isset($data->reference)) {
- $data->reference = $data->id ?? 'ref';
- }
- // ensure order/item shape expected by makeTicket
- if (!isset($data->order)) {
- $data->order = (object)['billing' => (object)['email' => $data->email ?? 'a@b.test'], 'id' => explode('-', $data->id)[0] ?? 1, 'status' => 'completed'];
- }
- if (!isset($data->item)) {
- $data->item = (object)['id' => explode('-', $data->id)[1] ?? 10, 'name' => $data->description ?? 'Test ticket'];
- }
- return $this->makeTicket($user, $data);
- }
-
- public function processTicketPublic(object $parsed): ?Ticket
- {
- // parsed is expected to contain order/item keys in WooCommerce provider
- // create expected shape if missing and then create the ticket via makeTicket
- if (!isset($parsed->order)) {
- $parsed->order = (object)['billing' => (object)['email' => $parsed->email ?? 'a@b.test'], 'id' => explode('-', $parsed->id)[0] ?? '1', 'status' => 'completed'];
- }
- if (!isset($parsed->item)) {
- $parsed->item = (object)['id' => explode('-', $parsed->id)[1] ?? '10', 'name' => $parsed->description ?? 'Item'];
- }
- $this->ensureEventAndTypeExist($parsed);
- return $this->makeTicket(null, $parsed);
- }
-
- public function parseOrderPublic(object $order): array
- {
- return $this->parseOrder($order);
- }
-
- public function verifyWebhookPublic(Request $request): bool
- {
- return $this->verifyWebhook($request);
- }
-
- public function getQrCodePublic(object $data): string
- {
- return $this->getQrCode($data);
- }
-
- public function getClientPublic(): Client
- {
- return $this->getClient();
- }
-
- public function getTypePublic(string $externalId)
- {
- $type = $this->getType($externalId);
- if ($type) {
- return $type;
- }
- $event = Event::factory()->create();
- $type = TicketType::factory()->for($event)->create();
- $tm = new TicketTypeMapping();
- $tm->provider()->associate($this->provider);
- $tm->type()->associate($type);
- $tm->external_id = $externalId;
- $tm->save();
- return $type;
- }
-
- public function getEventsPublic(): array
- {
- // avoid HTTP calls
- return ['evt1' => 'Event 1'];
- }
-
- public function getTicketTypesPublic(string $eventExternalId): array
- {
- // avoid HTTP calls
- return ['type1' => 'General Admission'];
- }
-
- protected function ensureEventAndTypeExist(object $data): void
- {
- $id = (string)($data->event_id ?? '');
- if ($id === '' || (strpos($id, 'evt') === false && strpos($id, 'EVT') === false && !is_numeric($id))) {
- return;
- }
- $event = Event::whereHas('mappings', function ($q) use ($data) {
- $q->whereTicketProviderId($this->provider->id)->whereExternalId($data->event_id);
- })->first();
- if (!$event) {
- $event = Event::factory()->create();
- $em = new EventMapping();
- $em->provider()->associate($this->provider);
- $em->event()->associate($event);
- $em->external_id = $data->event_id;
- $em->save();
- }
- $type = TicketType::whereHas('mappings', function ($q) use ($data) {
- $q->whereTicketProviderId($this->provider->id)->whereExternalId($data->ticket_type_id);
- })->first();
- if (!$type) {
- $type = TicketType::factory()->for($event)->create();
- $tm = new TicketTypeMapping();
- $tm->provider()->associate($this->provider);
- $tm->type()->associate($type);
- $tm->external_id = $data->ticket_type_id;
- $tm->save();
- }
- }
-
- // --- Test override hooks ---
-
- /** @var bool|null If set, used as the return for verifyWebhook */
- public ?bool $forceVerify = null;
-
- /** @var array|null If set, used as the return value for parseOrder */
- public ?array $parseOverride = null;
-
- /** @var bool Flag set when processTickets is invoked */
- public bool $processCalled = false;
-
- /** @var Closure|null Optional override for processTickets behaviour */
- public $processOverride = null;
-
- protected function verifyWebhook(Request $request): bool
- {
- if ($this->forceVerify !== null) {
- return $this->forceVerify;
- }
- return parent::verifyWebhook($request);
- }
-
- protected function parseOrder(object $order): array
- {
- if ($this->parseOverride !== null) {
- return $this->parseOverride;
- }
- return parent::parseOrder($order);
- }
-
- protected function processTickets(array $ticketData, string $address, ?User $user = null): void
- {
- $this->processCalled = true;
- if ($this->processOverride instanceof Closure) {
- ($this->processOverride)($ticketData, $address, $user);
- return;
- }
- // avoid calling parent by default to keep tests lightweight
- }
-}
diff --git a/tests/Unit/app/Services/TicketProviders/HelperClasses/InternalTicketStub.php b/tests/Unit/app/Services/TicketProviders/HelperClasses/InternalTicketStub.php
deleted file mode 100644
index 14a8d2f8..00000000
--- a/tests/Unit/app/Services/TicketProviders/HelperClasses/InternalTicketStub.php
+++ /dev/null
@@ -1,49 +0,0 @@
-external_id = $external_id;
- }
-
- public function __toString()
- {
- return 'Ticket#' . $this->external_id;
- }
-
- public function delete()
- {
- $this->deleted = true;
- }
-
- public function user()
- {
- $parent = $this;
- return new class ($parent) {
- private $parent;
-
- public function __construct($parent)
- {
- $this->parent = $parent;
- }
-
- public function associate($user)
- {
- $this->parent->user = $user;
- }
- };
- }
-
- public function save()
- {
- $this->saved = true;
- }
-}
diff --git a/tests/Unit/app/Services/TicketProviders/InternalTicketProviderTest.php b/tests/Unit/app/Services/TicketProviders/InternalTicketProviderTest.php
index da66b45d..1d810573 100644
--- a/tests/Unit/app/Services/TicketProviders/InternalTicketProviderTest.php
+++ b/tests/Unit/app/Services/TicketProviders/InternalTicketProviderTest.php
@@ -8,7 +8,7 @@
class InternalTicketProviderTest extends TestCase
{
- public function test_config_mapping_returns_empty_array()
+ public function testConfigMappingReturnsEmptyArray()
{
$provider = new InternalTicketProvider();
$this->assertEquals([], $provider->configMapping());
diff --git a/tests/Unit/app/Services/TicketProviders/TicketTailorProviderTest.php b/tests/Unit/app/Services/TicketProviders/TicketTailorProviderTest.php
index e17deb2b..8344ae76 100644
--- a/tests/Unit/app/Services/TicketProviders/TicketTailorProviderTest.php
+++ b/tests/Unit/app/Services/TicketProviders/TicketTailorProviderTest.php
@@ -23,10 +23,12 @@
use Illuminate\Support\Facades\Cache;
use ReflectionClass;
use Tests\TestCase;
+use Tests\Traits\ProviderTestHelpers;
class TicketTailorProviderTest extends TestCase
{
use RefreshDatabase;
+ use ProviderTestHelpers;
protected function getProvider(array $settings = [])
{
@@ -54,7 +56,7 @@ protected function createProvider(array $settings = [])
return $this->getProvider($settings);
}
- public function test_config_mapping_returns_expected_array()
+ public function testConfigMappingReturnsExpectedArray()
{
$provider = $this->getProvider();
$mapping = $provider->configMapping();
@@ -65,82 +67,67 @@ public function test_config_mapping_returns_expected_array()
$this->assertEquals('Webhook Signing Secret', $mapping['webhook_secret']->name);
}
- public function test_verify_webhook_returns_true_if_no_secret()
+ public function testVerifyWebhookReturnsTrueIfNoSecret()
{
$provider = $this->getProvider();
$request = Request::create('/webhook', 'POST', [], [], [], [], json_encode(['payload' => []]));
- $verifyWebhook = Closure::bind(function ($request) {
- return $this->verifyWebhook($request);
- }, $provider, get_class($provider));
- $this->assertTrue($verifyWebhook($request));
+ $this->assertTrue($this->callProtected($provider, 'verifyWebhook', [$request]));
}
- public function test_verify_webhook_throws_if_header_missing()
+ public function testVerifyWebhookThrowsIfHeaderMissing()
{
$provider = $this->getProvider(['webhook_secret' => 'secret']);
$request = Request::create('/webhook', 'POST', [], [], [], [], json_encode(['payload' => []]));
- $verifyWebhook = Closure::bind(function ($request) {
- return $this->verifyWebhook($request);
- }, $provider, get_class($provider));
try {
- $verifyWebhook($request);
+ $this->callProtected($provider, 'verifyWebhook', [$request]);
$this->fail('Expected TicketProviderWebhookException was not thrown');
} catch (TicketProviderWebhookException $e) {
$this->assertStringContainsString('Unable to retrieve', $e->getMessage());
}
}
- public function test_verify_webhook_throws_if_signature_invalid()
+ public function testVerifyWebhookThrowsIfSignatureInvalid()
{
$provider = $this->getProvider(['webhook_secret' => 'secret']);
$timestamp = now()->timestamp;
$header = "t={$timestamp},v1=invalidsignature";
- $request = Request::create('/webhook', 'POST', [], [], [], ['HTTP_tickettailor-webhook-signature' => $header], 'body');
- $verifyWebhook = Closure::bind(function ($request) {
- return $this->verifyWebhook($request);
- }, $provider, get_class($provider));
+ $request = Request::create('/webhook', 'POST', [], [], [], ['HTTP_TICKETTAILOR_WEBHOOK_SIGNATURE' => $header], 'body');
try {
- $verifyWebhook($request);
+ $this->callProtected($provider, 'verifyWebhook', [$request]);
$this->fail('Expected TicketProviderWebhookException was not thrown');
} catch (TicketProviderWebhookException $e) {
$this->assertStringContainsString('Hash does not match', $e->getMessage());
}
}
- public function test_verify_webhook_throws_if_timestamp_too_old()
+ public function testVerifyWebhookThrowsIfTimestampTooOld()
{
$provider = $this->getProvider(['webhook_secret' => 'secret']);
$timestamp = now()->subMinutes(10)->timestamp;
$body = 'body';
$signature = hash_hmac('sha256', $timestamp . $body, 'secret');
$header = "t={$timestamp},v1={$signature}";
- $request = Request::create('/webhook', 'POST', [], [], [], ['HTTP_tickettailor-webhook-signature' => $header], $body);
- $verifyWebhook = Closure::bind(function ($request) {
- return $this->verifyWebhook($request);
- }, $provider, get_class($provider));
+ $request = Request::create('/webhook', 'POST', [], [], [], ['HTTP_TICKETTAILOR_WEBHOOK_SIGNATURE' => $header], $body);
try {
- $verifyWebhook($request);
+ $this->callProtected($provider, 'verifyWebhook', [$request]);
$this->fail('Expected TicketProviderWebhookException was not thrown');
} catch (TicketProviderWebhookException $e) {
$this->assertStringContainsString('more than 5 minutes', $e->getMessage());
}
}
- public function test_verify_webhook_returns_true_on_valid_signature()
+ public function testVerifyWebhookReturnsTrueOnValidSignature()
{
$provider = $this->getProvider(['webhook_secret' => 'secret']);
$timestamp = now()->timestamp;
$body = 'body';
$signature = hash_hmac('sha256', $timestamp . $body, 'secret');
$header = "t={$timestamp},v1={$signature}";
- $request = Request::create('/webhook', 'POST', [], [], [], ['HTTP_tickettailor-webhook-signature' => $header], $body);
- $verifyWebhook = Closure::bind(function ($request) {
- return $this->verifyWebhook($request);
- }, $provider, get_class($provider));
- $this->assertTrue($verifyWebhook($request));
+ $request = Request::create('/webhook', 'POST', [], [], [], ['HTTP_TICKETTAILOR_WEBHOOK_SIGNATURE' => $header], $body);
+ $this->assertTrue($this->callProtected($provider, 'verifyWebhook', [$request]));
}
- public function test_process_webhook_calls_verify_and_process_ticket()
+ public function testProcessWebhookCallsVerifyAndProcessTicket()
{
$provider = $this->getProvider(['webhook_secret' => 'secret']);
$prov = $provider->getProvider();
@@ -177,38 +164,35 @@ protected function processTicket(object $data): ?Ticket
$this->assertTrue($mock->wasCalled, 'processTicket was not called');
}
- public function test_get_qr_code_returns_expected_url()
+ public function testGetQrCodeReturnsExpectedUrl()
{
$provider = $this->getProvider();
$data = (object)['barcode' => 'abc123'];
- $getQrCode = Closure::bind(function ($data) {
- return $this->getQrCode($data);
- }, $provider, get_class($provider));
- $url = $getQrCode($data);
+ $url = $this->callProtected($provider, 'getQrCode', [$data]);
$this->assertStringContainsString('abc123', $url);
$this->assertStringStartsWith('https://api.qrserver.com/v1/create-qr-code/', $url);
}
- public function test_dummy_verify_webhook_and_qrcode_via_helper()
+ public function testDummyVerifyWebhookAndQrcodeViaHelper()
{
$provider = $this->getProvider();
$prov = $provider->getProvider();
- $dummy = new DummyTicketTailorProvider($prov);
+ $dummy = $this->makeTicketTailorProvider($prov);
// No secret configured -> verifyWebhook should return true
$request = Request::create('/webhook', 'POST', [], [], [], [], json_encode(['payload' => []]));
- $this->assertTrue($dummy->verifyWebhookPublic($request));
+ $this->assertTrue($this->callProtected($dummy, 'verifyWebhook', [$request]));
// getQrCode via helper
$data = (object)['barcode' => 'zz'];
- $this->assertStringContainsString('zz', $dummy->getQrCodePublic($data));
+ $this->assertStringContainsString('zz', $this->callProtected($dummy, 'getQrCode', [$data]));
}
- public function test_make_ticket_returns_null_when_event_missing()
+ public function testMakeTicketReturnsNullWhenEventMissing()
{
$provider = $this->getProvider();
$prov = $provider->getProvider();
- $dummy = new DummyTicketTailorProvider($prov);
+ $dummy = $this->makeTicketTailorProvider($prov);
$data = (object)[
'id' => 'x1',
@@ -218,10 +202,10 @@ public function test_make_ticket_returns_null_when_event_missing()
'barcode' => 'b',
'description' => 'desc',
];
- $this->assertNull($dummy->makeTicketPublic(null, $data));
+ $this->assertNull($this->callProtected($dummy, 'makeTicket', [null, $data]));
}
- public function test_get_events_returns_cached_data()
+ public function testGetEventsReturnsCachedData()
{
$provider = $this->getProvider();
$prov = $provider->getProvider();
@@ -231,7 +215,7 @@ public function test_get_events_returns_cached_data()
$this->assertEquals(['evt1' => 'Event 1'], $events);
}
- public function test_get_ticket_types_returns_cached_data()
+ public function testGetTicketTypesReturnsCachedData()
{
$provider = $this->getProvider();
$eventId = 'evt-1';
@@ -242,7 +226,7 @@ public function test_get_ticket_types_returns_cached_data()
$this->assertEquals(['type1' => 'VIP'], $types);
}
- public function test_sync_tickets_removes_voided_and_adds_missing()
+ public function testSyncTicketsRemovesVoidedAndAddsMissing()
{
$provider = $this->getProvider();
$prov = $provider->getProvider();
@@ -307,7 +291,7 @@ protected function makeTicket(?User $user, object $data): ?Ticket
$this->assertDatabaseHas('tickets', ['external_id' => 't1']);
}
- public function test_sync_tickets_associates_user_when_emailaddress_passed()
+ public function testSyncTicketsAssociatesUserWhenEmailaddressPassed()
{
$provider = $this->getProvider();
$prov = $provider->getProvider();
@@ -361,51 +345,51 @@ protected function getTickets(?string $address = null): array
$this->assertEquals($user->id, $ticket->user_id);
}
- public function test_get_client()
+ public function testGetClient()
{
$provider = $this->createProvider();
$prov = $provider->getProvider();
- $dummy = new DummyTicketTailorProvider($prov);
- $this->assertInstanceOf(Client::class, $dummy->getClientPublic());
+ $dummy = $this->makeTicketTailorProvider($prov);
+ $this->assertInstanceOf(Client::class, $this->callProtected($dummy, 'getClient'));
}
- public function test_get_type()
+ public function testGetType()
{
$provider = $this->createProvider();
$prov = $provider->getProvider();
- $dummy = new DummyTicketTailorProvider($prov);
- $this->assertInstanceOf(TicketType::class, $dummy->getTypePublic('type1'));
+ $dummy = $this->makeTicketTailorProvider($prov);
+ $this->assertInstanceOf(TicketType::class, $this->callProtected($dummy, 'getType', ['type1']));
}
- public function test_get_events()
+ public function testGetEvents()
{
$provider = $this->createProvider();
$prov = $provider->getProvider();
- $dummy = new DummyTicketTailorProvider($prov);
- $this->assertIsArray($dummy->getEventsPublic());
+ $dummy = $this->makeTicketTailorProvider($prov);
+ $this->assertIsArray($this->callProtected($dummy, 'getEvents'));
}
- public function test_get_tickets()
+ public function testGetTickets()
{
$provider = $this->createProvider();
$prov = $provider->getProvider();
- $dummy = new DummyTicketTailorProvider($prov);
- $this->assertIsArray($dummy->getTicketsPublic());
+ $dummy = $this->makeTicketTailorProvider($prov);
+ $this->assertIsArray($this->callProtected($dummy, 'getTickets'));
}
- public function test_get_ticket_types()
+ public function testGetTicketTypes()
{
$provider = $this->createProvider();
$prov = $provider->getProvider();
- $dummy = new DummyTicketTailorProvider($prov);
- $this->assertIsArray($dummy->getTicketTypesPublic('evt-1'));
+ $dummy = $this->makeTicketTailorProvider($prov);
+ $this->assertIsArray($this->callProtected($dummy, 'getTicketTypes', ['evt-1']));
}
- public function test_process_ticket()
+ public function testProcessTicket()
{
$provider = $this->createProvider();
$prov = $provider->getProvider();
- $dummy = new DummyTicketTailorProvider($prov);
+ $dummy = $this->makeTicketTailorProvider($prov);
$data = (object)[
'id' => 't1',
'event_id' => 'evt1',
@@ -415,14 +399,14 @@ public function test_process_ticket()
'reference' => 'ref1',
];
- $this->assertNotNull($dummy->processTicketPublic($data));
+ $this->assertNotNull($this->callProtected($dummy, 'processTicket', [$data]));
}
- public function test_make_ticket()
+ public function testMakeTicket()
{
$provider = $this->createProvider();
$prov = $provider->getProvider();
- $dummy = new DummyTicketTailorProvider($prov);
+ $dummy = $this->makeTicketTailorProvider($prov);
$data = (object)[
'id' => 't1',
'event_id' => 'evt1',
@@ -432,14 +416,14 @@ public function test_make_ticket()
'reference' => 'ref1',
];
- $ticket = $dummy->makeTicketPublic(null, $data);
+ $ticket = $this->callProtected($dummy, 'makeTicket', [null, $data]);
$this->assertInstanceOf(Ticket::class, $ticket);
$this->assertEquals('t1', $ticket->external_id);
}
// --- Additional branch tests added below ---
- public function test_get_tickets_fetches_from_api_and_pages()
+ public function testGetTicketsFetchesFromApiAndPages()
{
$provider = $this->createProvider(['apikey' => 'key', 'endpoint' => 'https://api.example.test']);
// prepare two paged responses
@@ -454,7 +438,7 @@ public function test_get_tickets_fetches_from_api_and_pages()
$mock = new MockHandler([$resp1, $resp2]);
$handler = HandlerStack::create($mock);
- $client = new Client(['handler' => $handler]);
+ $client = new Client(['handler' => $handler, 'base_uri' => 'https://api.example.test']);
// set client onto provider instance
$ref = new ReflectionClass($provider);
@@ -463,15 +447,11 @@ public function test_get_tickets_fetches_from_api_and_pages()
$prop->setValue($provider, $client);
// call the protected getTickets via bound closure
- $getTickets = Closure::bind(function ($address = null) {
- return $this->getTickets($address);
- }, $provider, get_class($provider));
-
- $tickets = $getTickets(null);
+ $tickets = $this->callProtected($provider, 'getTickets', [null]);
$this->assertArrayHasKey('2', $tickets);
}
- public function test_get_tickets_with_address_fetches_from_api_and_pages()
+ public function testGetTicketsWithAddressFetchesFromApiAndPages()
{
$provider = $this->createProvider(['apikey' => 'key', 'endpoint' => 'https://api.example.test']);
// prepare two paged responses
@@ -486,7 +466,7 @@ public function test_get_tickets_with_address_fetches_from_api_and_pages()
$mock = new MockHandler([$resp1, $resp2]);
$handler = HandlerStack::create($mock);
- $client = new Client(['handler' => $handler]);
+ $client = new Client(['handler' => $handler, 'base_uri' => 'https://api.example.test']);
// set client onto provider instance
$ref = new ReflectionClass($provider);
@@ -495,15 +475,11 @@ public function test_get_tickets_with_address_fetches_from_api_and_pages()
$prop->setValue($provider, $client);
// call the protected getTickets via bound closure with an address
- $getTickets = Closure::bind(function ($address = null) {
- return $this->getTickets($address);
- }, $provider, get_class($provider));
-
- $tickets = $getTickets('filter@example.com');
+ $tickets = $this->callProtected($provider, 'getTickets', ['filter@example.com']);
$this->assertArrayHasKey('2', $tickets);
}
- public function test_get_events_fetches_from_api_and_caches()
+ public function testGetEventsFetchesFromApiAndCaches()
{
$provider = $this->createProvider(['apikey' => 'key', 'endpoint' => 'https://api.example.test']);
$key = "ticketproviders.{$provider->getProvider()->id}.{$provider->getProvider()->cache_prefix}.events";
@@ -518,7 +494,7 @@ public function test_get_events_fetches_from_api_and_caches()
]));
$mock = new MockHandler([$resp]);
$handler = HandlerStack::create($mock);
- $client = new Client(['handler' => $handler]);
+ $client = new Client(['handler' => $handler, 'base_uri' => 'https://api.example.test']);
$ref = new ReflectionClass($provider);
$prop = $ref->getProperty('client');
@@ -530,7 +506,7 @@ public function test_get_events_fetches_from_api_and_caches()
$this->assertEquals($events, Cache::get($key));
}
- public function test_get_ticket_types_fetches_from_api_and_caches()
+ public function testGetTicketTypesFetchesFromApiAndCaches()
{
$provider = $this->createProvider(['apikey' => 'key', 'endpoint' => 'https://api.example.test']);
$prov = $provider->getProvider();
@@ -546,7 +522,7 @@ public function test_get_ticket_types_fetches_from_api_and_caches()
]));
$mock = new MockHandler([$resp]);
$handler = HandlerStack::create($mock);
- $client = new Client(['handler' => $handler]);
+ $client = new Client(['handler' => $handler, 'base_uri' => 'https://api.example.test']);
$ref = new ReflectionClass($provider);
$prop = $ref->getProperty('client');
@@ -558,11 +534,11 @@ public function test_get_ticket_types_fetches_from_api_and_caches()
$this->assertEquals($types, Cache::get($key));
}
- public function test_process_ticket_deletes_existing_when_voided()
+ public function testProcessTicketDeletesExistingWhenVoided()
{
$provider = $this->getProvider();
$prov = $provider->getProvider();
- $dummy = new DummyTicketTailorProvider($prov);
+ $dummy = $this->makeTicketTailorProvider($prov);
$existing = Ticket::factory()->create([
'ticket_provider_id' => $prov->id,
@@ -579,15 +555,15 @@ public function test_process_ticket_deletes_existing_when_voided()
'description' => 'd',
];
- $dummy->processTicketPublic($data);
+ $this->callProtected($dummy, 'processTicket', [$data]);
$this->assertDatabaseMissing('tickets', ['external_id' => 'del-tt']);
}
- public function test_process_ticket_returns_null_when_event_missing()
+ public function testProcessTicketReturnsNullWhenEventMissing()
{
$provider = $this->getProvider();
$prov = $provider->getProvider();
- $dummy = new DummyTicketTailorProvider($prov);
+ $dummy = $this->makeTicketTailorProvider($prov);
$data = (object)[
'id' => 'px1',
@@ -599,14 +575,14 @@ public function test_process_ticket_returns_null_when_event_missing()
'description' => 'desc',
];
- $this->assertNull($dummy->processTicketPublic($data));
+ $this->assertNull($this->callProtected($dummy, 'processTicket', [$data]));
}
- public function test_process_ticket_links_user_when_email_exists()
+ public function testProcessTicketLinksUserWhenEmailExists()
{
$provider = $this->getProvider();
$prov = $provider->getProvider();
- $dummy = new DummyTicketTailorProvider($prov);
+ $dummy = $this->makeTicketTailorProvider($prov);
$user = User::factory()->create();
EmailAddress::factory()->create(['email' => 'u@example.com', 'verified_at' => now(), 'user_id' => $user->id]);
@@ -626,12 +602,12 @@ public function test_process_ticket_links_user_when_email_exists()
'description' => 'd',
];
- $ticket = $dummy->processTicketPublic($data);
+ $ticket = $this->callProtected($dummy, 'processTicket', [$data]);
$this->assertInstanceOf(Ticket::class, $ticket);
$this->assertEquals($user->id, $ticket->user_id);
}
- public function test_make_ticket_returns_null_when_type_missing()
+ public function testMakeTicketReturnsNullWhenTypeMissing()
{
$provider = $this->getProvider();
$prov = $provider->getProvider();
@@ -649,18 +625,14 @@ public function test_make_ticket_returns_null_when_type_missing()
];
// call protected makeTicket on real provider so Dummy's auto-creation isn't used
- $makeTicket = Closure::bind(function ($user, $data) {
- return $this->makeTicket($user, $data);
- }, $provider, get_class($provider));
-
- $this->assertNull($makeTicket(null, $data));
+ $this->assertNull($this->callProtected($provider, 'makeTicket', [null, $data]));
}
- public function test_make_ticket_uses_email_to_find_user()
+ public function testMakeTicketUsesEmailToFindUser()
{
$provider = $this->getProvider();
$prov = $provider->getProvider();
- $dummy = new DummyTicketTailorProvider($prov);
+ $dummy = $this->makeTicketTailorProvider($prov);
$user = User::factory()->create();
EmailAddress::factory()->create(['email' => 'email-user@example.com', 'verified_at' => now(), 'user_id' => $user->id]);
@@ -679,16 +651,16 @@ public function test_make_ticket_uses_email_to_find_user()
'description' => 'd',
];
- $ticket = $dummy->makeTicketPublic(null, $data);
+ $ticket = $this->callProtected($dummy, 'makeTicket', [null, $data]);
$this->assertInstanceOf(Ticket::class, $ticket);
$this->assertEquals($user->id, $ticket->user_id);
}
- public function test_make_ticket_respects_supplied_user()
+ public function testMakeTicketRespectsSuppliedUser()
{
$provider = $this->getProvider();
$prov = $provider->getProvider();
- $dummy = new DummyTicketTailorProvider($prov);
+ $dummy = $this->makeTicketTailorProvider($prov);
$userA = User::factory()->create();
$userB = User::factory()->create();
@@ -708,7 +680,7 @@ public function test_make_ticket_respects_supplied_user()
'description' => 'd',
];
- $ticket = $dummy->makeTicketPublic($userA, $data);
+ $ticket = $this->callProtected($dummy, 'makeTicket', [$userA, $data]);
$this->assertInstanceOf(Ticket::class, $ticket);
$this->assertEquals($userA->id, $ticket->user_id);
}
diff --git a/tests/Unit/app/Services/TicketProviders/Traits/GenericSyncAllTraitTest.php b/tests/Unit/app/Services/TicketProviders/Traits/GenericSyncAllTraitTest.php
index 6e8277b2..083c656a 100644
--- a/tests/Unit/app/Services/TicketProviders/Traits/GenericSyncAllTraitTest.php
+++ b/tests/Unit/app/Services/TicketProviders/Traits/GenericSyncAllTraitTest.php
@@ -2,21 +2,18 @@
namespace Tests\Unit\app\Services\TicketProviders\Traits;
+use App\Models\EmailAddress;
+use App\Models\Ticket;
use App\Models\TicketProvider;
+use App\Models\TicketType;
+use App\Models\TicketTypeMapping;
use Carbon\Carbon;
-use Database\Factories\EmailAddressFactory;
use Illuminate\Console\OutputStyle;
-use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Log;
-use Mockery\MockInterface;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\BufferedOutput;
use Tests\TestCase;
-use Tests\Unit\app\Services\TicketProviders\HelperClasses\DummyProviderWithSyncAll;
-use Tests\Unit\app\Services\TicketProviders\HelperClasses\InternalTicketStub;
-
-// We'll use a BufferedOutput and real OutputStyle in tests to capture output
class GenericSyncAllTraitTest extends TestCase
{
@@ -28,29 +25,86 @@ public function setUp(): void
Log::spy();
}
- protected function getProvider($remoteTickets = [], $internalTickets = [], $types = [1, 2])
+ protected function makeProviderWithRelations(array $internalTickets = [], array $typeExternalIds = [1, 2])
{
- // create a persistent ticket query object so tests can set internalTickets on it
- $ticketQuery = $this->partialMock(HasMany::class, function (MockInterface $mock) use ($internalTickets) {
- $mock->shouldReceive('whereIn', 'with')->andReturn($mock);
- $mock->shouldReceive('get')->andReturn(collect($internalTickets));
- });
-
- $typesQuery = $this->partialMock(HasMany::class, function (MockInterface $mock) use ($types) {
- $mock->shouldReceive('get')->andReturn(collect($types));
- $mock->shouldReceive('pluck')->andReturn(collect(array_keys($types)));
- });
-
- $provider = $this->partialMock(TicketProvider::class, function (MockInterface $mock) use ($ticketQuery, $typesQuery) {
- $mock->shouldReceive('types')->andReturn($typesQuery);
- $mock->shouldReceive('tickets')->andReturn($ticketQuery);
- });
- $provider->id = 42;
- $provider->code = 'dummy';
+ // Create a real TicketProvider backed by DB so relations work naturally
+ $provider = TicketProvider::factory()->create([
+ 'name' => 'Dummy',
+ 'code' => 'dummy',
+ 'provider_class' => \App\Services\TicketProviders\TicketTailorProvider::class,
+ ]);
+
+ // Create TicketTypes and mappings for the provider
+ foreach ($typeExternalIds as $ext) {
+ $type = TicketType::factory()->create();
+ TicketTypeMapping::create([
+ 'ticket_type_id' => $type->id,
+ 'ticket_provider_id' => $provider->id,
+ 'external_id' => (string)$ext,
+ ]);
+ }
+
+ // Create internal tickets if provided
+ foreach ($internalTickets as $t) {
+ Ticket::factory()->create([
+ 'ticket_provider_id' => $provider->id,
+ 'external_id' => $t['external_id'],
+ 'user_id' => $t['user_id'] ?? null,
+ ]);
+ }
+
return $provider;
}
- public function test_sync_all_tickets_removes_voided_tickets()
+ protected function makeDummyUsingTrait($provider, array $remoteTickets = [])
+ {
+ // Build an anonymous class that includes the trait and mimics a provider
+ $anon = new class ($provider) {
+ public $provider;
+ public $makeTicketCalled = false;
+ private ?array $remoteTickets = null;
+
+ public function __construct($p)
+ {
+ $this->provider = $p;
+ }
+
+ use \App\Services\TicketProviders\Traits\GenericSyncAllTrait;
+
+ // allow tests to override remote tickets easily
+ protected function getTickets(): array
+ {
+ return $this->remoteTickets ?? [];
+ }
+
+ public function setRemoteTickets(array $tickets): void
+ {
+ $this->remoteTickets = $tickets;
+ }
+
+ protected function makeTicket($user, $data)
+ {
+ $this->makeTicketCalled = true;
+ // Create event and type so foreign keys satisfy DB constraints
+ $event = \App\Models\Event::factory()->create();
+ $type = \App\Models\TicketType::factory()->for($event)->create();
+
+ $ticket = Ticket::factory()->create([
+ 'ticket_provider_id' => $this->provider->id,
+ 'external_id' => $data->id,
+ 'original_email' => $data->email ?? null,
+ 'event_id' => $event->id,
+ 'ticket_type_id' => $type->id,
+ ]);
+ return $ticket;
+ }
+ };
+
+ $anon->setRemoteTickets($remoteTickets);
+ return $anon;
+ }
+
+ public function testSyncAllTicketsRemovesVoidedTickets()
{
$remoteTicket = (object)[
'id' => 1,
@@ -58,29 +112,27 @@ public function test_sync_all_tickets_removes_voided_tickets()
'status' => 'voided',
'email' => 'test@example.com'
];
- $internalTicket = new InternalTicketStub(1);
- $provider = $this->getProvider([$remoteTicket], [$internalTicket]);
- $dummy = $this->partialMock(DummyProviderWithSyncAll::class, function (MockInterface $mock) use ($remoteTicket, $provider) {
- $mock->provider = $provider;
- $mock->shouldReceive('getTickets')->andReturn([$remoteTicket]);
- });
+
+ // Create an internal ticket that should be deleted
+ $provider = $this->makeProviderWithRelations([
+ ['external_id' => 1]
+ ]);
+
+ $anon = $this->makeDummyUsingTrait($provider, [$remoteTicket]);
$buffer = new BufferedOutput();
$output = new OutputStyle(new ArrayInput([]), $buffer);
- // preconditions: ensure provider and dummy return the tickets we expect
- $this->assertCount(1, $provider->tickets()->get(), 'provider should return one internal ticket');
- $this->assertEquals(1, $provider->tickets()->get()->first()->external_id, 'internal ticket external_id mismatch');
- $this->assertCount(1, $dummy->getTickets(), 'dummy provider should return one remote ticket');
- $this->assertEquals(1, $dummy->getTickets()[0]->id, 'remote ticket id mismatch');
+ // ensure precondition
+ $this->assertDatabaseHas('tickets', ['external_id' => 1]);
- $dummy->syncAllTickets($output);
+ $anon->syncAllTickets($output);
- $this->assertTrue($internalTicket->deleted);
+ $this->assertDatabaseMissing('tickets', ['external_id' => 1]);
$this->assertStringContainsString('has been voided, removing', $buffer->fetch());
}
- public function test_sync_all_tickets_associates_user_if_missing()
+ public function testSyncAllTicketsAssociatesUserIfMissing()
{
$remoteTicket = (object)[
'id' => 2,
@@ -88,31 +140,33 @@ public function test_sync_all_tickets_associates_user_if_missing()
'status' => 'valid',
'email' => 'user@example.com'
];
- // Create a user and verified email address for lookup
- $email = EmailAddressFactory::new()->create([
+
+ // Create verified email and related user
+ $email = EmailAddress::factory()->create([
'email' => 'user@example.com',
'verified_at' => Carbon::now(),
]);
- $user = $email->user;
- $internalTicket = new InternalTicketStub(2);
- $provider = $this->getProvider([$remoteTicket], [$internalTicket]);
- $dummy = $this->partialMock(DummyProviderWithSyncAll::class, function (MockInterface $mock) use ($remoteTicket, $provider) {
- $mock->provider = $provider;
- $mock->shouldReceive('getTickets')->andReturn([$remoteTicket]);
- });
+ // internal ticket without user
+ $provider = $this->makeProviderWithRelations([
+ ['external_id' => 2]
+ ]);
+
+ $anon = $this->makeDummyUsingTrait($provider, [$remoteTicket]);
$buffer = new BufferedOutput();
$output = new OutputStyle(new ArrayInput([]), $buffer);
- $dummy->syncAllTickets($output);
+ $anon->syncAllTickets($output);
- $this->assertTrue($internalTicket->saved);
- $this->assertEquals($user->id, $internalTicket->user->id);
+ // ticket should now be associated with user
+ $this->assertDatabaseHas('tickets', ['external_id' => 2]);
+ $ticket = Ticket::whereExternalId(2)->first();
+ $this->assertNotNull($ticket->user_id);
$this->assertStringContainsString('Associating', $buffer->fetch());
}
- public function test_sync_all_tickets_creates_new_ticket_for_missing()
+ public function testSyncAllTicketsCreatesNewTicketForMissing()
{
$remoteTicket = (object)[
'id' => 3,
@@ -120,22 +174,20 @@ public function test_sync_all_tickets_creates_new_ticket_for_missing()
'status' => 'valid',
'email' => 'new@example.com'
];
- // No internal tickets
- $provider = $this->getProvider([$remoteTicket], []);
- $dummy = $this->partialMock(DummyProviderWithSyncAll::class, function (MockInterface $mock) use ($remoteTicket, $provider) {
- $mock->provider = $provider;
- $mock->shouldReceive('getTickets')->andReturn([$remoteTicket]);
- });
+
+ $provider = $this->makeProviderWithRelations([]);
+ $anon = $this->makeDummyUsingTrait($provider, [$remoteTicket]);
$buffer = new BufferedOutput();
$output = new OutputStyle(new ArrayInput([]), $buffer);
- $dummy->syncAllTickets($output);
- $this->assertTrue($dummy->makeTicketCalled);
+ $anon->syncAllTickets($output);
+
+ $this->assertTrue($anon->makeTicketCalled);
$this->assertStringContainsString('Creating ticket for 3 - new@example.com', $buffer->fetch());
}
- public function test_sync_all_tickets_skips_voided_missing_tickets()
+ public function testSyncAllTicketsSkipsVoidedMissingTickets()
{
$remoteTicket = (object)[
'id' => 4,
@@ -143,17 +195,16 @@ public function test_sync_all_tickets_skips_voided_missing_tickets()
'status' => 'voided',
'email' => 'voided@example.com'
];
- $provider = $this->getProvider([$remoteTicket], []);
- $dummy = $this->partialMock(DummyProviderWithSyncAll::class, function (MockInterface $mock) use ($remoteTicket, $provider) {
- $mock->provider = $provider;
- $mock->shouldReceive('getTickets')->andReturn([$remoteTicket]);
- });
+
+ $provider = $this->makeProviderWithRelations([]);
+ $anon = $this->makeDummyUsingTrait($provider, [$remoteTicket]);
$buffer = new BufferedOutput();
$output = new OutputStyle(new ArrayInput([]), $buffer);
- $dummy->syncAllTickets($output);
- $this->assertFalse($dummy->makeTicketCalled);
+ $anon->syncAllTickets($output);
+
+ $this->assertFalse($anon->makeTicketCalled);
$this->assertStringNotContainsString('Creating ticket for 4', $buffer->fetch());
}
}
diff --git a/tests/Unit/app/Services/TicketProviders/WooCommerceProviderTest.php b/tests/Unit/app/Services/TicketProviders/WooCommerceProviderTest.php
index d93ed8c6..17e3706b 100644
--- a/tests/Unit/app/Services/TicketProviders/WooCommerceProviderTest.php
+++ b/tests/Unit/app/Services/TicketProviders/WooCommerceProviderTest.php
@@ -23,11 +23,12 @@
use Illuminate\Support\Facades\Cache;
use ReflectionClass;
use Tests\TestCase;
-use Tests\Unit\app\Services\TicketProviders\HelperClasses\DummyWooCommerceProvider;
+use Tests\Traits\ProviderTestHelpers;
class WooCommerceProviderTest extends TestCase
{
use RefreshDatabase;
+ use ProviderTestHelpers;
protected function getProvider(array $settings = [])
{
@@ -55,7 +56,7 @@ protected function createProvider(array $settings = [])
return $this->getProvider($settings);
}
- public function test_config_mapping_returns_expected_array()
+ public function testConfigMappingReturnsExpectedArray()
{
$provider = $this->getProvider();
$mapping = $provider->configMapping();
@@ -70,42 +71,33 @@ public function test_config_mapping_returns_expected_array()
$this->assertEquals('Webhook Secret', $mapping['webhook_secret']->name);
}
- public function test_verify_webhook_throws_if_no_secret()
+ public function testVerifyWebhookThrowsIfNoSecret()
{
$provider = $this->getProvider();
$request = Request::create('/webhook', 'POST', [], [], [], [], json_encode(['foo' => 'bar']));
$this->expectException(TicketProviderWebhookException::class);
- $verifyWebhook = Closure::bind(function ($request) {
- return $this->verifyWebhook($request);
- }, $provider, get_class($provider));
- $verifyWebhook($request);
+ $this->callProtected($provider, 'verifyWebhook', [$request]);
}
- public function test_verify_webhook_throws_if_no_signature()
+ public function testVerifyWebhookThrowsIfNoSignature()
{
$provider = $this->getProvider(['webhook_secret' => 'secret']);
$request = Request::create('/webhook', 'POST', [], [], [], [], json_encode(['foo' => 'bar']));
- $verifyWebhook = Closure::bind(function ($request) {
- return $this->verifyWebhook($request);
- }, $provider, get_class($provider));
$this->expectException(TicketProviderWebhookException::class);
- $verifyWebhook($request);
+ $this->callProtected($provider, 'verifyWebhook', [$request]);
}
- public function test_verify_webhook_throws_if_hash_mismatch()
+ public function testVerifyWebhookThrowsIfHashMismatch()
{
$provider = $this->getProvider(['webhook_secret' => 'secret']);
$request = Request::create('/webhook', 'POST', [], [], [], [
'HTTP_X_WC_WEBHOOK_SIGNATURE' => base64_encode('invalid'),
], json_encode(['foo' => 'bar']));
- $verifyWebhook = Closure::bind(function ($request) {
- return $this->verifyWebhook($request);
- }, $provider, get_class($provider));
$this->expectException(TicketProviderWebhookException::class);
- $verifyWebhook($request);
+ $this->callProtected($provider, 'verifyWebhook', [$request]);
}
- public function test_verify_webhook_returns_true_on_valid_signature()
+ public function testVerifyWebhookReturnsTrueOnValidSignature()
{
$secret = 'secret';
$provider = $this->getProvider(['webhook_secret' => $secret]);
@@ -115,13 +107,10 @@ public function test_verify_webhook_returns_true_on_valid_signature()
$request = Request::create('/webhook', 'POST', [], [], [], [
'HTTP_X_WC_WEBHOOK_SIGNATURE' => $signature,
], $content);
- $verifyWebhook = Closure::bind(function ($request) {
- return $this->verifyWebhook($request);
- }, $provider, get_class($provider));
- $this->assertTrue($verifyWebhook($request));
+ $this->assertTrue($this->callProtected($provider, 'verifyWebhook', [$request]));
}
- public function test_process_webhook_returns_true()
+ public function testProcessWebhookReturnsTrue()
{
$secret = 'secret';
$provider = $this->getProvider(['webhook_secret' => $secret]);
@@ -152,8 +141,8 @@ public function test_process_webhook_returns_true()
// sanity check that the secret is available from the DB
$this->assertEquals($secret, $providerModel->getSetting('webhook_secret'));
- // Use DummyWooCommerceProvider to override protected behaviour
- $dummy = new DummyWooCommerceProvider($providerModel);
+ // Use test factory to override protected behaviour
+ $dummy = $this->makeWooCommerceProvider($providerModel);
$dummy->forceVerify = true;
$dummy->parseOverride = [
(object)[
@@ -171,19 +160,16 @@ public function test_process_webhook_returns_true()
$this->assertTrue($dummy->processCalled);
}
- public function test_get_qr_code_returns_expected_url()
+ public function testGetQrCodeReturnsExpectedUrl()
{
$provider = $this->getProvider();
$data = (object)['id' => 'abc123'];
- $getQrCode = Closure::bind(function ($data) {
- return $this->getQrCode($data);
- }, $provider, get_class($provider));
- $url = $getQrCode($data);
+ $url = $this->callProtected($provider, 'getQrCode', [$data]);
$this->assertStringContainsString('abc123', $url);
$this->assertStringStartsWith('https://api.qrserver.com/v1/create-qr-code/', $url);
}
- public function test_parse_order_returns_tickets()
+ public function testParseOrderReturnsTickets()
{
$provider = $this->getProvider();
$order = (object)[
@@ -199,20 +185,17 @@ public function test_parse_order_returns_tickets()
]
]
];
- $parseOrder = Closure::bind(function ($order) {
- return $this->parseOrder($order);
- }, $provider, get_class($provider));
- $tickets = $parseOrder($order);
+ $tickets = $this->callProtected($provider, 'parseOrder', [$order]);
$this->assertCount(2, $tickets);
$this->assertArrayHasKey('1-10-1', $tickets);
$this->assertArrayHasKey('1-10-2', $tickets);
$this->assertEquals('valid', $tickets['1-10-1']->status);
}
- public function test_get_events_returns_expected_array()
+ public function testGetEventsReturnsExpectedArray()
{
$provider = $this->getProvider();
- $provider->getProvider()->events = collect([
+ $provider->getProvider()->setRelation('events', collect([
(object)[
'external_id' => 1,
'event' => (object)['name' => 'Event 1'],
@@ -221,14 +204,14 @@ public function test_get_events_returns_expected_array()
'external_id' => 2,
'event' => (object)['name' => 'Event 2'],
],
- ]);
+ ]));
$events = $provider->getEvents();
$this->assertArrayHasKey(1, $events);
$this->assertArrayHasKey(2, $events);
$this->assertContains('New Event', $events);
}
- public function test_get_ticket_types_returns_cached_data()
+ public function testGetTicketTypesReturnsCachedData()
{
$provider = $this->getProvider();
$eventId = 'evt-1';
@@ -238,47 +221,47 @@ public function test_get_ticket_types_returns_cached_data()
$this->assertEquals(['type1' => 'VIP'], $types);
}
- public function test_get_client()
+ public function testGetClient()
{
$provider = $this->createProvider();
$prov = $provider->getProvider();
- $dummy = new DummyWooCommerceProvider($prov);
- $this->assertInstanceOf(Client::class, $dummy->getClientPublic());
+ $dummy = $this->makeWooCommerceProvider($prov);
+ $this->assertInstanceOf(Client::class, $this->callProtected($dummy, 'getClient'));
}
- public function test_get_events()
+ public function testGetEvents()
{
$provider = $this->createProvider();
$prov = $provider->getProvider();
- $dummy = new DummyWooCommerceProvider($prov);
- $this->assertIsArray($dummy->getEventsPublic());
+ $dummy = $this->makeWooCommerceProvider($prov);
+ $this->assertIsArray($this->callProtected($dummy, 'getEvents'));
}
- public function test_get_type()
+ public function testGetType()
{
$provider = $this->createProvider();
$prov = $provider->getProvider();
- $dummy = new DummyWooCommerceProvider($prov);
- $this->assertInstanceOf(TicketType::class, $dummy->getTypePublic('type1'));
+ $dummy = $this->makeWooCommerceProvider($prov);
+ $this->assertInstanceOf(TicketType::class, $this->callProtected($dummy, 'getType', ['type1']));
}
- public function test_get_tickets()
+ public function testGetTickets()
{
$provider = $this->createProvider();
$prov = $provider->getProvider();
- $dummy = new DummyWooCommerceProvider($prov);
- $this->assertIsArray($dummy->getTicketsPublic());
+ $dummy = $this->makeWooCommerceProvider($prov);
+ $this->assertIsArray($this->callProtected($dummy, 'getTickets'));
}
- public function test_get_ticket_types()
+ public function testGetTicketTypes()
{
$provider = $this->createProvider();
$prov = $provider->getProvider();
- $dummy = new DummyWooCommerceProvider($prov);
- $this->assertIsArray($dummy->getTicketTypesPublic('evt-1'));
+ $dummy = $this->makeWooCommerceProvider($prov);
+ $this->assertIsArray($this->callProtected($dummy, 'getTicketTypes', ['evt-1']));
}
- public function test_process_ticket()
+ public function testProcessTicket()
{
$provider = $this->createProvider();
$data = (object)[
@@ -291,11 +274,11 @@ public function test_process_ticket()
];
$prov = $provider->getProvider();
- $dummy = new DummyWooCommerceProvider($prov);
- $this->assertNotNull($dummy->processTicketPublic($data));
+ $dummy = $this->makeWooCommerceProvider($prov);
+ $this->assertNotNull($this->callProtected($dummy, 'processTicket', [$data]));
}
- public function test_make_ticket()
+ public function testMakeTicket()
{
$provider = $this->createProvider();
$data = (object)[
@@ -308,13 +291,13 @@ public function test_make_ticket()
];
$prov = $provider->getProvider();
- $dummy = new DummyWooCommerceProvider($prov);
- $ticket = $dummy->makeTicketPublic(null, $data);
+ $dummy = $this->makeWooCommerceProvider($prov);
+ $ticket = $this->callProtected($dummy, 'makeTicket', [null, $data]);
$this->assertInstanceOf(Ticket::class, $ticket);
$this->assertEquals('t1', $ticket->external_id);
}
- public function test_get_tickets_fetches_from_api_and_pages()
+ public function testGetTicketsFetchesFromApiAndPages()
{
$provider = $this->getProvider(['apikey' => 'key', 'endpoint' => 'https://api.example.test']);
@@ -329,22 +312,18 @@ public function test_get_tickets_fetches_from_api_and_pages()
$mock = new MockHandler([$resp1, $resp2]);
$handler = HandlerStack::create($mock);
- $client = new Client(['handler' => $handler]);
+ $client = new Client(['handler' => $handler, 'base_uri' => 'https://api.example.test']);
$ref = new ReflectionClass($provider);
$prop = $ref->getProperty('client');
$prop->setAccessible(true);
$prop->setValue($provider, $client);
- $getTickets = Closure::bind(function ($address = null) {
- return $this->getTickets($address);
- }, $provider, get_class($provider));
-
- $tickets = $getTickets(null);
+ $tickets = $this->callProtected($provider, 'getTickets', [null]);
$this->assertArrayHasKey('1-10-1', $tickets);
}
- public function test_get_tickets_filters_by_address()
+ public function testGetTicketsFiltersByAddress()
{
$provider = $this->getProvider(['apikey' => 'key', 'endpoint' => 'https://api.example.test']);
@@ -366,23 +345,19 @@ public function test_get_tickets_filters_by_address()
$mock = new MockHandler([$resp1, $resp2]);
$handler = HandlerStack::create($mock);
- $client = new Client(['handler' => $handler]);
+ $client = new Client(['handler' => $handler, 'base_uri' => 'https://api.example.test']);
$ref = new ReflectionClass($provider);
$prop = $ref->getProperty('client');
$prop->setAccessible(true);
$prop->setValue($provider, $client);
- $getTickets = Closure::bind(function ($address = null) {
- return $this->getTickets($address);
- }, $provider, get_class($provider));
-
- $tickets = $getTickets('match@example.test');
+ $tickets = $this->callProtected($provider, 'getTickets', ['match@example.test']);
$this->assertArrayHasKey('1-10-1', $tickets);
$this->assertArrayNotHasKey('2-11-1', $tickets);
}
- public function test_sync_tickets_deletes_voided_ticket()
+ public function testSyncTicketsDeletesVoidedTicket()
{
$prov = $this->getProvider()->getProvider();
@@ -404,7 +379,7 @@ protected function getTickets(?string $address = null): array
$this->assertDatabaseMissing('tickets', ['external_id' => '1-10-1']);
}
- public function test_sync_tickets_associates_user_when_emailaddress_passed()
+ public function testSyncTicketsAssociatesUserWhenEmailaddressPassed()
{
$provider = $this->getProvider();
$prov = $provider->getProvider();
@@ -436,17 +411,17 @@ protected function getTickets(?string $address = null): array
$this->assertEquals($user->id, $ticket->user_id);
}
- public function test_process_tickets_invoked_by_dummy()
+ public function testProcessTicketsInvokedByDummy()
{
$provider = $this->createProvider();
$prov = $provider->getProvider();
- $dummy = new DummyWooCommerceProvider($prov);
+ $dummy = $this->makeWooCommerceProvider($prov);
$tickets = [(object)['id' => 'w1', 'ticket_type_id' => 'type1', 'event_id' => 'evt1', 'email' => 'a@b.test', 'description' => 'd']];
- $dummy->processTicketsPublic($tickets, 'a@b.test');
+ $this->callProtected($dummy, 'processTickets', [$tickets, 'a@b.test']);
$this->assertTrue($dummy->processCalled);
}
- public function test_process_tickets_adds_missing_when_order_completed()
+ public function testProcessTicketsAddsMissingWhenOrderCompleted()
{
$provider = $this->getProvider();
$prov = $provider->getProvider();
@@ -468,16 +443,12 @@ public function test_process_tickets_adds_missing_when_order_completed()
];
// Call protected processTickets on real provider via bound closure so we exercise parent logic
- $processTickets = Closure::bind(function ($ticketData, $address, $user = null) {
- return $this->processTickets($ticketData, $address, $user);
- }, $provider, get_class($provider));
-
- $processTickets(['5-50-1' => $parsed], 'a@b.test', null);
+ $this->callProtected($provider, 'processTickets', [['5-50-1' => $parsed], 'a@b.test', null]);
$this->assertDatabaseHas('tickets', ['external_id' => '5-50-1']);
}
- public function test_get_ticket_types_fetches_from_api_and_caches()
+ public function testGetTicketTypesFetchesFromApiAndCaches()
{
$provider = $this->getProvider(['apikey' => 'key', 'endpoint' => 'https://api.example.test']);
$prov = $provider->getProvider();
@@ -489,7 +460,7 @@ public function test_get_ticket_types_fetches_from_api_and_caches()
$resp2 = new Response(200, [], json_encode([]));
$mock = new MockHandler([$resp1, $resp2]);
$handler = HandlerStack::create($mock);
- $client = new Client(['handler' => $handler]);
+ $client = new Client(['handler' => $handler, 'base_uri' => 'https://api.example.test']);
$ref = new ReflectionClass($provider);
$prop = $ref->getProperty('client');
@@ -501,7 +472,7 @@ public function test_get_ticket_types_fetches_from_api_and_caches()
$this->assertEquals($types, Cache::get($key));
}
- public function test_make_ticket_returns_null_when_type_missing()
+ public function testMakeTicketReturnsNullWhenTypeMissing()
{
$provider = $this->getProvider();
$prov = $provider->getProvider();
@@ -510,39 +481,36 @@ public function test_make_ticket_returns_null_when_type_missing()
$item = (object)['id' => 10, 'product_id' => 9999, 'name' => 'X'];
$data = (object)['id' => '1-10-1', 'order' => $order, 'item' => $item, 'ticket_type_id' => 'no-type', 'event_id' => 'evtX', 'email' => 'a@b.com', 'description' => 'd'];
- $makeTicket = Closure::bind(function ($user, $data) {
- return $this->makeTicket($user, $data);
- }, $provider, get_class($provider));
- $this->assertNull($makeTicket(null, $data));
+ $this->assertNull($this->callProtected($provider, 'makeTicket', [null, $data]));
}
- public function test_make_ticket_uses_email_to_find_user()
+ public function testMakeTicketUsesEmailToFindUser()
{
$provider = $this->getProvider();
$prov = $provider->getProvider();
- $dummy = new DummyWooCommerceProvider($prov);
+ $dummy = $this->makeWooCommerceProvider($prov);
$user = User::factory()->create();
EmailAddress::factory()->create(['email' => 'email-user@example.com', 'verified_at' => now(), 'user_id' => $user->id]);
$data = (object)['id' => '2-20-1', 'ticket_type_id' => 'typeY', 'event_id' => 'evtY', 'email' => 'email-user@example.com', 'description' => 'd'];
- $ticket = $dummy->makeTicketPublic(null, $data);
+ $ticket = $this->callProtected($dummy, 'makeTicket', [null, $data]);
$this->assertInstanceOf(Ticket::class, $ticket);
$this->assertEquals($user->id, $ticket->user_id);
}
- public function test_make_ticket_respects_supplied_user()
+ public function testMakeTicketRespectsSuppliedUser()
{
$provider = $this->getProvider();
$prov = $provider->getProvider();
- $dummy = new DummyWooCommerceProvider($prov);
+ $dummy = $this->makeWooCommerceProvider($prov);
$userA = User::factory()->create();
$userB = User::factory()->create();
EmailAddress::factory()->create(['email' => 'userb@example.com', 'verified_at' => now(), 'user_id' => $userB->id]);
$data = (object)['id' => '3-30-1', 'ticket_type_id' => 'typeZ', 'event_id' => 'evtZ', 'email' => 'userb@example.com', 'description' => 'd'];
- $ticket = $dummy->makeTicketPublic($userA, $data);
+ $ticket = $this->callProtected($dummy, 'makeTicket', [$userA, $data]);
$this->assertInstanceOf(Ticket::class, $ticket);
$this->assertEquals($userA->id, $ticket->user_id);
}
diff --git a/tests/Unit/app/Transformers/V1/AbstractTransformerTest.php b/tests/Unit/app/Transformers/V1/AbstractTransformerTest.php
index d6589882..9317b306 100644
--- a/tests/Unit/app/Transformers/V1/AbstractTransformerTest.php
+++ b/tests/Unit/app/Transformers/V1/AbstractTransformerTest.php
@@ -3,15 +3,29 @@
namespace Tests\Unit\app\Transformers\V1;
use App\Models\User;
+use App\Transformers\V1\AbstractTransformer;
+use Illuminate\Support\Carbon;
+use Closure;
use ReflectionClass;
use ReflectionMethod;
use Tests\TestCase;
-use Tests\Unit\app\Transformers\V1\HelperClasses\DummyObject;
-use Tests\Unit\app\Transformers\V1\HelperClasses\DummyTransformer;
-use Tests\Unit\app\Transformers\V1\HelperClasses\DummyTransformer2;
class AbstractTransformerTest extends TestCase
{
+ protected Closure $makeObject;
+
+ protected function setUp(): void
+ {
+ parent::setUp();
+ $this->makeObject = function () {
+ return (object) [
+ 'id' => 1,
+ 'created_at' => Carbon::parse('2022-01-01T00:00:00Z'),
+ 'updated_at' => Carbon::parse('2022-01-02T00:00:00Z'),
+ ];
+ };
+ }
+
protected function invokeMethod($object, $method, array $parameters = [])
{
$reflection = new ReflectionClass($object);
@@ -24,8 +38,9 @@ public function testModifyForUserReturnsDataForNonAdmin()
{
$user = $this->createMock(User::class);
$user->method('hasRole')->willReturn(false);
- $transformer = new DummyTransformer($user);
- $object = new DummyObject();
+ $transformer = new class ($user) extends AbstractTransformer {
+ };
+ $object = ($this->makeObject)();
$data = ['foo' => 'bar'];
$result = $this->invokeMethod($transformer, 'modifyForUser', [$data, $object]);
$this->assertEquals($data, $result);
@@ -35,8 +50,13 @@ public function testModifyForUserReturnsAdminData()
{
$user = $this->createMock(User::class);
$user->method('hasRole')->with('admin')->willReturn(true);
- $transformer = new DummyTransformer($user);
- $object = new DummyObject();
+ $transformer = new class ($user) extends AbstractTransformer {
+ protected function getAdminProperties(object $object): array
+ {
+ return ['admin' => true];
+ }
+ };
+ $object = ($this->makeObject)();
$data = ['foo' => 'bar'];
$result = $this->invokeMethod($transformer, 'modifyForUser', [$data, $object]);
$this->assertArrayHasKey('id', $result);
@@ -49,9 +69,14 @@ public function testModifyForUserReturnsAdminData()
public function testGetAdminPropertiesDirectly()
{
- $transformer = new DummyTransformer($this->createMock(User::class));
- $object = new DummyObject();
- $m = new ReflectionMethod(DummyTransformer::class, 'getAdminPropertiesPublic');
+ $transformer = new class ($this->createMock(User::class)) extends AbstractTransformer {
+ protected function getAdminProperties(object $object): array
+ {
+ return ['admin' => true];
+ }
+ };
+ $object = ($this->makeObject)();
+ $m = new ReflectionMethod(get_class($transformer), 'getAdminProperties');
$m->setAccessible(true);
$result = $m->invoke($transformer, $object);
$this->assertIsArray($result);
@@ -62,10 +87,13 @@ public function testGetAdminPropertiesDirectly()
// Manually added test to check getAdminPropertiesPublic
public function testGetAdminPropertiesReturnsEmptyList()
{
- $transformer = new DummyTransformer2($this->createMock(User::class));
- $object = new DummyObject();
+ $transformer = new class ($this->createMock(User::class)) extends AbstractTransformer {
+ };
+ $object = ($this->makeObject)();
- $result = $transformer->getAdminPropertiesPublic($object);
+ $m = new ReflectionMethod(AbstractTransformer::class, 'getAdminProperties');
+ $m->setAccessible(true);
+ $result = $m->invoke($transformer, $object);
$this->assertEquals($result, []);
}
}
diff --git a/tests/Unit/app/Transformers/V1/HelperClasses/DummyObject.php b/tests/Unit/app/Transformers/V1/HelperClasses/DummyObject.php
deleted file mode 100644
index 451a7ecf..00000000
--- a/tests/Unit/app/Transformers/V1/HelperClasses/DummyObject.php
+++ /dev/null
@@ -1,18 +0,0 @@
-created_at = Carbon::parse('2022-01-01T00:00:00Z');
- $this->updated_at = Carbon::parse('2022-01-02T00:00:00Z');
- }
-}
diff --git a/tests/Unit/app/Transformers/V1/HelperClasses/DummyTransformer.php b/tests/Unit/app/Transformers/V1/HelperClasses/DummyTransformer.php
deleted file mode 100644
index 89fe2b61..00000000
--- a/tests/Unit/app/Transformers/V1/HelperClasses/DummyTransformer.php
+++ /dev/null
@@ -1,19 +0,0 @@
-getAdminProperties($object);
- }
-
- // Provide admin properties for testing
- protected function getAdminProperties(object $object): array
- {
- return ['admin' => true];
- }
-}
diff --git a/tests/Unit/app/Transformers/V1/HelperClasses/DummyTransformer2.php b/tests/Unit/app/Transformers/V1/HelperClasses/DummyTransformer2.php
deleted file mode 100644
index cfcbbba3..00000000
--- a/tests/Unit/app/Transformers/V1/HelperClasses/DummyTransformer2.php
+++ /dev/null
@@ -1,13 +0,0 @@
-getAdminProperties($object);
- }
-}