diff --git a/.idea/laravel-shopify.iml b/.idea/laravel-shopify.iml
index f39d0417..b0819070 100644
--- a/.idea/laravel-shopify.iml
+++ b/.idea/laravel-shopify.iml
@@ -137,6 +137,14 @@
+
+
+
+
+
+
+
+
diff --git a/.idea/php.xml b/.idea/php.xml
index f8b21236..0bf0b935 100644
--- a/.idea/php.xml
+++ b/.idea/php.xml
@@ -1,5 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
@@ -139,9 +151,17 @@
+
+
+
+
+
+
+
+
-
+
@@ -149,9 +169,16 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index ddbac182..9fc87217 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -23,7 +23,7 @@
src/Exceptions/
src/Objects/Enums/
src/resources/
- src/Messaging/Events/AppLoggedIn.php
+ src/Messaging/Events/
src/ShopifyAppProvider.php
diff --git a/src/Actions/ActivatePlan.php b/src/Actions/ActivatePlan.php
index 8b957567..72cb5493 100644
--- a/src/Actions/ActivatePlan.php
+++ b/src/Actions/ActivatePlan.php
@@ -8,6 +8,7 @@
use Osiset\ShopifyApp\Contracts\Objects\Values\PlanId;
use Osiset\ShopifyApp\Contracts\Queries\Plan as IPlanQuery;
use Osiset\ShopifyApp\Contracts\Queries\Shop as IShopQuery;
+use Osiset\ShopifyApp\Messaging\Events\PlanActivatedEvent;
use Osiset\ShopifyApp\Objects\Enums\ChargeStatus;
use Osiset\ShopifyApp\Objects\Enums\ChargeType;
use Osiset\ShopifyApp\Objects\Enums\PlanType;
@@ -142,6 +143,8 @@ public function __invoke(ShopId $shopId, PlanId $planId, ChargeReference $charge
$charge = $this->chargeCommand->make($transfer);
$this->shopCommand->setToPlan($shopId, $planId);
+ event(new PlanActivatedEvent($shop, $plan, $charge));
+
return $charge;
}
}
diff --git a/src/Actions/AuthenticateShop.php b/src/Actions/AuthenticateShop.php
index 93ad11d2..c2d8767c 100644
--- a/src/Actions/AuthenticateShop.php
+++ b/src/Actions/AuthenticateShop.php
@@ -4,6 +4,7 @@
use Illuminate\Http\Request;
use Osiset\ShopifyApp\Contracts\ApiHelper as IApiHelper;
+use Osiset\ShopifyApp\Messaging\Events\AppInstalledEvent;
use Osiset\ShopifyApp\Objects\Values\ShopDomain;
use Osiset\ShopifyApp\Util;
@@ -109,6 +110,8 @@ public function __invoke(Request $request): array
call_user_func($this->dispatchWebhooksAction, $result['shop_id'], false);
call_user_func($this->afterAuthorizeAction, $result['shop_id']);
+ event(new AppInstalledEvent($result['shop_id']));
+
return [$result, true];
}
}
diff --git a/src/Http/Middleware/IframeProtection.php b/src/Http/Middleware/IframeProtection.php
index fb297530..e03bacd3 100644
--- a/src/Http/Middleware/IframeProtection.php
+++ b/src/Http/Middleware/IframeProtection.php
@@ -7,6 +7,7 @@
use Illuminate\Support\Facades\Cache;
use Osiset\ShopifyApp\Contracts\Queries\Shop as IShopQuery;
use Osiset\ShopifyApp\Objects\Values\ShopDomain;
+use Osiset\ShopifyApp\Util;
/**
* Responsibility for protection against clickjaking
@@ -44,6 +45,7 @@ public function __construct(
public function handle(Request $request, Closure $next)
{
$response = $next($request);
+ $ancestors = Util::getShopifyConfig('iframe_ancestors');
$shop = Cache::remember(
'frame-ancestors_'.$request->get('shop'),
@@ -57,9 +59,15 @@ function () use ($request) {
? $shop->name
: '*.myshopify.com';
+ $iframeAncestors = "frame-ancestors https://$domain https://admin.shopify.com";
+
+ if (!blank($ancestors)) {
+ $iframeAncestors .= ' '.$ancestors;
+ }
+
$response->headers->set(
'Content-Security-Policy',
- "frame-ancestors https://$domain https://admin.shopify.com"
+ $iframeAncestors
);
return $response;
diff --git a/src/Http/Middleware/VerifyShopify.php b/src/Http/Middleware/VerifyShopify.php
index b8ac15b3..b7d43e60 100644
--- a/src/Http/Middleware/VerifyShopify.php
+++ b/src/Http/Middleware/VerifyShopify.php
@@ -83,7 +83,7 @@ public function __construct(
* @param Request $request The request object.
* @param Closure $next The next action.
*
- * @throws SignatureVerificationException If HMAC verification fails.
+ * @throws SignatureVerificationException|HttpException If HMAC verification fails.
*
* @return mixed
*/
@@ -102,9 +102,12 @@ public function handle(Request $request, Closure $next)
}
if (!Util::useNativeAppBridge()) {
- $storeResult = !$this->isApiRequest($request) && $this->checkPreviousInstallation($request);
+ $shop = $this->getShopIfAlreadyInstalled($request);
+ $storeResult = !$this->isApiRequest($request) && $shop;
if ($storeResult) {
+ $this->loginFromShop($shop);
+
return $next($request);
}
}
@@ -512,4 +515,36 @@ protected function checkPreviousInstallation(Request $request): bool
return $shop && $shop->password && ! $shop->trashed();
}
+
+ /**
+ * Get shop model if there is a store record in the database.
+ *
+ * @param Request $request The request object.
+ *
+ * @return ?ShopModel
+ */
+ protected function getShopIfAlreadyInstalled(Request $request): ?ShopModel
+ {
+ $shop = $this->shopQuery->getByDomain(ShopDomain::fromRequest($request), [], true);
+
+ return $shop && $shop->password && ! $shop->trashed() ? $shop : null;
+ }
+
+ /**
+ * Login and validate store
+ *
+ * @param ShopModel $shop
+ *
+ * @return void
+ */
+ protected function loginFromShop(ShopModel $shop): void
+ {
+ // Override auth guard
+ if (($guard = Util::getShopifyConfig('shop_auth_guard'))) {
+ $this->auth->setDefaultDriver($guard);
+ }
+
+ // All is well, login the shop
+ $this->auth->login($shop);
+ }
}
diff --git a/src/Messaging/Events/AppInstalledEvent.php b/src/Messaging/Events/AppInstalledEvent.php
new file mode 100644
index 00000000..cef4e658
--- /dev/null
+++ b/src/Messaging/Events/AppInstalledEvent.php
@@ -0,0 +1,35 @@
+shopId = $shop_id;
+ }
+}
diff --git a/src/Messaging/Events/AppUninstalledEvent.php b/src/Messaging/Events/AppUninstalledEvent.php
new file mode 100644
index 00000000..cb02e57b
--- /dev/null
+++ b/src/Messaging/Events/AppUninstalledEvent.php
@@ -0,0 +1,35 @@
+shop = $shop;
+ }
+}
diff --git a/src/Messaging/Events/PlanActivatedEvent.php b/src/Messaging/Events/PlanActivatedEvent.php
new file mode 100644
index 00000000..8e3b8180
--- /dev/null
+++ b/src/Messaging/Events/PlanActivatedEvent.php
@@ -0,0 +1,53 @@
+shop = $shop;
+ $this->plan = $plan;
+ $this->chargeId = $chargeId;
+ }
+}
diff --git a/src/Messaging/Events/ShopAuthenticatedEvent.php b/src/Messaging/Events/ShopAuthenticatedEvent.php
new file mode 100644
index 00000000..3cd16b18
--- /dev/null
+++ b/src/Messaging/Events/ShopAuthenticatedEvent.php
@@ -0,0 +1,35 @@
+shopId = $shopId;
+ }
+}
diff --git a/src/Messaging/Events/ShopDeletedEvent.php b/src/Messaging/Events/ShopDeletedEvent.php
new file mode 100644
index 00000000..f901dd1f
--- /dev/null
+++ b/src/Messaging/Events/ShopDeletedEvent.php
@@ -0,0 +1,35 @@
+shop = $shop;
+ }
+}
diff --git a/src/Messaging/Jobs/AppUninstalledJob.php b/src/Messaging/Jobs/AppUninstalledJob.php
index c973e69c..047758a6 100644
--- a/src/Messaging/Jobs/AppUninstalledJob.php
+++ b/src/Messaging/Jobs/AppUninstalledJob.php
@@ -10,6 +10,7 @@
use Osiset\ShopifyApp\Actions\CancelCurrentPlan;
use Osiset\ShopifyApp\Contracts\Commands\Shop as IShopCommand;
use Osiset\ShopifyApp\Contracts\Queries\Shop as IShopQuery;
+use Osiset\ShopifyApp\Messaging\Events\AppUninstalledEvent;
use Osiset\ShopifyApp\Objects\Values\ShopDomain;
use Osiset\ShopifyApp\Util;
use stdClass;
@@ -89,6 +90,8 @@ public function handle(
// Soft delete the shop.
$shopCommand->softDelete($shopId);
+ event(new AppUninstalledEvent($shop));
+
return true;
}
}
diff --git a/src/ShopifyAppProvider.php b/src/ShopifyAppProvider.php
index 2e95706b..0102d220 100644
--- a/src/ShopifyAppProvider.php
+++ b/src/ShopifyAppProvider.php
@@ -5,6 +5,7 @@
use Illuminate\Routing\Redirector;
use Illuminate\Routing\UrlGenerator;
use Illuminate\Support\Facades\Blade;
+use Illuminate\Support\Facades\Event;
use Illuminate\Support\ServiceProvider;
use Osiset\ShopifyApp\Actions\ActivatePlan as ActivatePlanAction;
use Osiset\ShopifyApp\Actions\ActivateUsageCharge as ActivateUsageChargeAction;
@@ -70,6 +71,10 @@ public function boot()
$this->bootMiddlewares();
$this->bootMacros();
$this->bootDirectives();
+
+ if (version_compare($this->app->version(), '8.0.0', '<')) {
+ $this->registerEvents();
+ }
}
/**
@@ -86,6 +91,13 @@ public function register()
WebhookJobMakeCommand::class,
]);
+ if (version_compare($this->app->version(), '8.0.0', '>=')) {
+ $this->booting(function () {
+ $this->registerEvents();
+ });
+ }
+
+
// Services (start)
$this->app->bind(IApiHelper::class, function () {
return new ApiHelper();
@@ -346,4 +358,15 @@ private function bootDirectives(): void
{
Blade::directive('sessionToken', new SessionToken());
}
+
+ private function registerEvents(): void
+ {
+ $events = Util::getShopifyConfig('listen');
+
+ foreach ($events as $event => $listeners) {
+ foreach (array_unique($listeners, SORT_REGULAR) as $listener) {
+ Event::listen($event, $listener);
+ }
+ }
+ }
}
diff --git a/src/Traits/AuthController.php b/src/Traits/AuthController.php
index 4476151f..8f2865f2 100644
--- a/src/Traits/AuthController.php
+++ b/src/Traits/AuthController.php
@@ -11,6 +11,7 @@
use Osiset\ShopifyApp\Exceptions\MissingAuthUrlException;
use Osiset\ShopifyApp\Exceptions\MissingShopDomainException;
use Osiset\ShopifyApp\Exceptions\SignatureVerificationException;
+use Osiset\ShopifyApp\Messaging\Events\ShopAuthenticatedEvent;
use Osiset\ShopifyApp\Objects\Values\ShopDomain;
use Osiset\ShopifyApp\Util;
@@ -57,6 +58,8 @@ public function authenticate(Request $request, AuthenticateShop $authShop)
$shopDomain = $shopDomain->toNative();
$shopOrigin = $shopDomain ?? $request->user()->name;
+ event(new ShopAuthenticatedEvent($result['shop_id']));
+
return View::make(
'shopify-app::auth.fullpage_redirect',
[
diff --git a/src/Traits/ShopModel.php b/src/Traits/ShopModel.php
index 57545cc8..5d8c7cae 100644
--- a/src/Traits/ShopModel.php
+++ b/src/Traits/ShopModel.php
@@ -11,6 +11,7 @@
use Osiset\ShopifyApp\Contracts\Objects\Values\AccessToken as AccessTokenValue;
use Osiset\ShopifyApp\Contracts\Objects\Values\ShopDomain as ShopDomainValue;
use Osiset\ShopifyApp\Contracts\Objects\Values\ShopId as ShopIdValue;
+use Osiset\ShopifyApp\Messaging\Events\ShopDeletedEvent;
use Osiset\ShopifyApp\Objects\Values\AccessToken;
use Osiset\ShopifyApp\Objects\Values\SessionContext;
use Osiset\ShopifyApp\Objects\Values\ShopDomain;
@@ -51,6 +52,10 @@ trait ShopModel
protected static function bootShopModel(): void
{
static::addGlobalScope(new Namespacing());
+
+ static::deleted(function ($shop) {
+ event(new ShopDeletedEvent($shop));
+ });
}
/**
diff --git a/src/Util.php b/src/Util.php
index 67a5a3e1..293bf7cb 100644
--- a/src/Util.php
+++ b/src/Util.php
@@ -17,7 +17,7 @@ class Util
/**
* HMAC creation helper.
*
- * @param array $opts The options for building the HMAC.
+ * @param array $opts The options for building the HMAC.
* @param string $secret The app secret key.
*
* @return Hmac
@@ -61,7 +61,7 @@ public static function createHmac(array $opts, string $secret): Hmac
* See: https://github.com/rack/rack/blob/f9ad97fd69a6b3616d0a99e6bedcfb9de2f81f6c/lib/rack/query_parser.rb#L36
*
* @param string $queryString The query string.
- * @param string|null $delimiter The delimiter.
+ * @param string|null $delimiter The delimiter.
*
* @return mixed
*/
@@ -77,7 +77,7 @@ public static function parseQueryString(string $queryString, string $delimiter =
);
foreach ($split as $part) {
- if (! $part) {
+ if (!$part) {
continue;
}
@@ -135,7 +135,7 @@ public static function base64UrlDecode($data)
/**
* Checks if the route should be registered or not.
*
- * @param string $routeToCheck The route name to check.
+ * @param string $routeToCheck The route name to check.
* @param bool|array $routesToExclude The routes which are to be excluded.
*
* @return bool
@@ -158,8 +158,8 @@ public static function registerPackageRoute(string $routeToCheck, $routesToExclu
* Used as a helper function so it is accessible in Blade.
* The second param of `shop` is important for `config_api_callback`.
*
- * @param string $key The key to lookup.
- * @param mixed $shop The shop domain (string, ShopDomain, etc).
+ * @param string $key The key to lookup.
+ * @param mixed $shop The shop domain (string, ShopDomain, etc).
*
* @return mixed
*/
@@ -207,8 +207,8 @@ public static function getShopifyConfig(string $key, $shop = null)
public static function getGraphQLWebhookTopic(string $topic): string
{
return Str::of($topic)
- ->upper()
- ->replaceMatches('/[^A-Z_]/', '_');
+ ->upper()
+ ->replaceMatches('/[^A-Z_]/', '_');
}
@@ -246,4 +246,11 @@ public static function useNativeAppBridge(): bool
return !$frontendEngine->isSame($reactEngine);
}
+
+ public static function hasAppLegacySupport(string $feature): bool
+ {
+ $legacySupports = self::getShopifyConfig('app_legacy_supports') ?? [];
+
+ return (bool) Arr::get($legacySupports, $feature, true);
+ }
}
diff --git a/src/resources/config/shopify-app.php b/src/resources/config/shopify-app.php
index ad53f005..cda82baf 100644
--- a/src/resources/config/shopify-app.php
+++ b/src/resources/config/shopify-app.php
@@ -330,6 +330,42 @@
'billing_redirect' => env('SHOPIFY_BILLING_REDIRECT', '/billing/process'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Enable legacy support for features
+ |--------------------------------------------------------------------------
+ |
+ */
+ 'app_legacy_supports' => [
+ 'after_authenticate_job' => true,
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Register listeners to the events
+ |--------------------------------------------------------------------------
+ |
+ */
+
+ 'listen' => [
+ \Osiset\ShopifyApp\Messaging\Events\AppInstalledEvent::class => [
+ // \App\Listeners\MyListener::class,
+ ],
+ \Osiset\ShopifyApp\Messaging\Events\ShopAuthenticatedEvent::class => [
+ // \App\Listeners\MyListener::class,
+ ],
+ \Osiset\ShopifyApp\Messaging\Events\ShopDeletedEvent::class => [
+ // \App\Listeners\MyListener::class,
+ ],
+ \Osiset\ShopifyApp\Messaging\Events\AppUninstalledEvent::class => [
+ // \App\Listeners\MyListener::class,
+ ],
+ \Osiset\ShopifyApp\Messaging\Events\PlanActivatedEvent::class => [
+ // \App\Listeners\MyListener::class,
+ ],
+ ],
+
/*
|--------------------------------------------------------------------------
| Shopify Webhooks
@@ -392,8 +428,13 @@
| This, like webhooks and scripttag jobs, will fire every time a shop
| authenticates, not just once.
|
+ |
*/
+ /*
+ * @deprecated This will be removed in the next major version.
+ * @see
+ */
'after_authenticate_job' => [
/*
[
@@ -498,26 +539,26 @@
*/
'theme_support' => [
- /**
+ /*
* Specify the name of the template the app will integrate with
*/
'templates' => ['product', 'collection', 'index'],
- /**
+ /*
* Interval for caching the request: minutes, seconds, hours, days, etc.
*/
'cache_interval' => 'hours',
- /**
+ /*
* Cache duration
*/
'cache_duration' => '12',
- /**
+ /*
* At which levels of theme support the use of "theme app extension" is not available
* and script tags will be installed.
* Available levels: FULL, PARTIAL, UNSUPPORTED.
*/
'unacceptable_levels' => [
- Osiset\ShopifyApp\Objects\Enums\ThemeSupportLevel::UNSUPPORTED
- ]
+ Osiset\ShopifyApp\Objects\Enums\ThemeSupportLevel::UNSUPPORTED,
+ ],
],
/*
@@ -542,4 +583,6 @@
|
*/
'frontend_engine' => env('SHOPIFY_FRONTEND_ENGINE', 'BLADE'),
+
+ 'iframe_ancestors' => '',
];
diff --git a/tests/Actions/ActivatePlanTest.php b/tests/Actions/ActivatePlanTest.php
index be6fb66f..97bb7c54 100644
--- a/tests/Actions/ActivatePlanTest.php
+++ b/tests/Actions/ActivatePlanTest.php
@@ -2,7 +2,9 @@
namespace Osiset\ShopifyApp\Test\Actions;
+use Illuminate\Support\Facades\Event;
use Osiset\ShopifyApp\Actions\ActivatePlan;
+use Osiset\ShopifyApp\Messaging\Events\PlanActivatedEvent;
use Osiset\ShopifyApp\Objects\Values\ChargeId;
use Osiset\ShopifyApp\Objects\Values\ChargeReference;
use Osiset\ShopifyApp\Storage\Models\Charge;
@@ -27,6 +29,7 @@ public function setUp(): void
public function testRunRecurring(): void
{
+ Event::fake();
// Create a plan
$plan = factory(Util::getShopifyConfig('models.plan', Plan::class))->states('type_recurring')->create();
@@ -56,10 +59,12 @@ public function testRunRecurring(): void
);
$this->assertInstanceOf(ChargeId::class, $result);
+ Event::assertDispatched(PlanActivatedEvent::class);
}
public function testRunOnetime(): void
{
+ Event::fake();
// Create a plan
$plan = factory(Util::getShopifyConfig('models.plan', Plan::class))->states('type_onetime')->create();
@@ -89,6 +94,7 @@ public function testRunOnetime(): void
);
$this->assertInstanceOf(ChargeId::class, $result);
+ Event::assertDispatched(PlanActivatedEvent::class);
}
//TODO we need to test for both myshopify and admin hosts
diff --git a/tests/Actions/AuthenticateShopTest.php b/tests/Actions/AuthenticateShopTest.php
index 49fcb4de..f5699be6 100644
--- a/tests/Actions/AuthenticateShopTest.php
+++ b/tests/Actions/AuthenticateShopTest.php
@@ -2,8 +2,10 @@
namespace Osiset\ShopifyApp\Test\Actions;
+use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Request;
use Osiset\ShopifyApp\Actions\AuthenticateShop;
+use Osiset\ShopifyApp\Messaging\Events\AppInstalledEvent;
use Osiset\ShopifyApp\Test\Stubs\Api as ApiStub;
use Osiset\ShopifyApp\Test\TestCase;
@@ -88,6 +90,7 @@ public function testShouldGoToAuthRedirectForInvalidHmac(): void
public function testRuns(): void
{
+ Event::fake();
// Build request
$currentRequest = Request::instance();
$newRequest = $currentRequest->duplicate(
@@ -121,5 +124,6 @@ public function testRuns(): void
[, $status] = call_user_func($this->action, $newRequest);
$this->assertTrue($status);
+ Event::assertDispatched(AppInstalledEvent::class);
}
}
diff --git a/tests/Http/Middleware/IframeProtectionTest.php b/tests/Http/Middleware/IframeProtectionTest.php
index a02448db..14bba2d8 100644
--- a/tests/Http/Middleware/IframeProtectionTest.php
+++ b/tests/Http/Middleware/IframeProtectionTest.php
@@ -63,4 +63,28 @@ public function testIframeProtectionWithUnauthorizedShop(): void
$this->assertNotEmpty($currentHeader);
$this->assertEquals($expectedHeader, $currentHeader);
}
+
+ public function testIframeProtectionWithExistingAncestorsInConfig(): void
+ {
+ $shop = factory($this->model)->create();
+ $this->auth->login($shop);
+ $this->app['config']->set('shopify-app.iframe_ancestors', 'https://example.com');
+
+ $domain = auth()->user()->name;
+ $expectedHeader = "frame-ancestors https://$domain https://admin.shopify.com https://example.com";
+
+ $request = new Request();
+ $shopQueryStub = $this->createStub(ShopQuery::class);
+ $shopQueryStub->method('getByDomain')->willReturn($shop);
+ $next = function () {
+ return new Response('Test Response');
+ };
+
+ $middleware = new IframeProtection($shopQueryStub);
+ $response = $middleware->handle($request, $next);
+ $currentHeader = $response->headers->get('content-security-policy');
+
+ $this->assertNotEmpty($currentHeader);
+ $this->assertEquals($expectedHeader, $currentHeader);
+ }
}
diff --git a/tests/Http/Middleware/VerifyShopifyTest.php b/tests/Http/Middleware/VerifyShopifyTest.php
index 047584c7..79869ed2 100644
--- a/tests/Http/Middleware/VerifyShopifyTest.php
+++ b/tests/Http/Middleware/VerifyShopifyTest.php
@@ -317,4 +317,36 @@ public function testTokenProcessingAndMissMatchingShops(): void
$this->expectException(HttpException::class);
$this->runMiddleware(VerifyShopify::class, $newRequest);
}
+
+ public function testNotNativeAppbridgeWithTokenProcessingAndLoginShop(): void
+ {
+ // Create a shop that matches the token from buildToken
+ factory($this->model)->create(['name' => 'shop-name.myshopify.com']);
+ $this->app['config']->set('shopify-app.frontend_engine', 'REACT');
+
+ // Setup the request
+ $currentRequest = Request::instance();
+ $newRequest = $currentRequest->duplicate(
+ // Query Params
+ [
+ 'shop' => 'shop-name.myshopify.com',
+ ],
+ // Request Params
+ null,
+ // Attributes
+ null,
+ // Cookies
+ null,
+ // Files
+ null,
+ // Server vars
+ [
+ 'HTTP_Authorization' => "Bearer {$this->buildToken()}",
+ ]
+ );
+
+ // Run the middleware
+ $result = $this->runMiddleware(VerifyShopify::class, $newRequest);
+ $this->assertTrue($result[0]);
+ }
}
diff --git a/tests/Messaging/Jobs/AppUninstalledTest.php b/tests/Messaging/Jobs/AppUninstalledTest.php
index f4882360..e4250563 100644
--- a/tests/Messaging/Jobs/AppUninstalledTest.php
+++ b/tests/Messaging/Jobs/AppUninstalledTest.php
@@ -2,6 +2,8 @@
namespace Osiset\ShopifyApp\Test\Messaging\Jobs;
+use Illuminate\Support\Facades\Event;
+use Osiset\ShopifyApp\Messaging\Events\AppUninstalledEvent;
use Osiset\ShopifyApp\Messaging\Jobs\AppUninstalledJob;
use Osiset\ShopifyApp\Objects\Enums\ChargeStatus;
use Osiset\ShopifyApp\Storage\Models\Charge;
@@ -32,6 +34,7 @@ public function testJobSoftDeletesShopAndCharges(): void
$this->assertNotNull($shop->plan);
$this->assertNotEmpty($shop->password);
+ Event::fake();
// Run the job
AppUninstalledJob::dispatchSync(
$shop->getDomain()->toNative(),
@@ -46,5 +49,6 @@ public function testJobSoftDeletesShopAndCharges(): void
$this->assertFalse($shop->hasCharges());
$this->assertNull($shop->plan);
$this->assertEmpty($shop->password);
+ Event::assertDispatched(AppUninstalledEvent::class);
}
}
diff --git a/tests/Traits/AuthControllerTest.php b/tests/Traits/AuthControllerTest.php
index 9988ffe3..a435c7c9 100644
--- a/tests/Traits/AuthControllerTest.php
+++ b/tests/Traits/AuthControllerTest.php
@@ -3,7 +3,10 @@
namespace Osiset\ShopifyApp\Test\Traits;
use Illuminate\Http\Response;
+use Illuminate\Support\Facades\Event;
+use Illuminate\Support\Facades\Request;
use Osiset\ShopifyApp\Exceptions\MissingShopDomainException;
+use Osiset\ShopifyApp\Messaging\Events\ShopAuthenticatedEvent;
use Osiset\ShopifyApp\Test\Stubs\Api as ApiStub;
use Osiset\ShopifyApp\Test\TestCase;
use Osiset\ShopifyApp\Util;
@@ -20,6 +23,7 @@ public function setUp(): void
public function testAuthRedirectsToShopifyWhenNoCode(): void
{
+ Event::fake();
// Run the request
$response = $this->call('post', '/authenticate', ['shop' => 'example.myshopify.com']);
@@ -29,6 +33,8 @@ public function testAuthRedirectsToShopifyWhenNoCode(): void
'authUrl',
'https://example.myshopify.com/admin/oauth/authorize?client_id='.Util::getShopifyConfig('api_key').'&scope=read_products%2Cwrite_products%2Cread_themes&redirect_uri=https%3A%2F%2Flocalhost%2Fauthenticate'
);
+
+ Event::assertDispatched(ShopAuthenticatedEvent::class);
}
public function testAuthAcceptsShopWithCode(): void
diff --git a/tests/UtilTest.php b/tests/UtilTest.php
index 13cff9aa..e88ee5b8 100644
--- a/tests/UtilTest.php
+++ b/tests/UtilTest.php
@@ -123,4 +123,15 @@ public function testUseNativeAppBridgeIsFalse(): void
$this->assertFalse($result);
}
+
+ public function testHasAppLegacySupport(): void
+ {
+ $supportedFeatures = $this->app['config']->get('shopify-app.app_legacy_supports', []);
+ foreach ($supportedFeatures as $feature => $val) {
+ $this->assertSame(
+ $val,
+ Util::hasAppLegacySupport($feature)
+ );
+ }
+ }
}