@@ -29,6 +29,7 @@ import {ApplicationURLs} from '../../services/dev/urls.js'
2929import { describe , expect , test , vi } from 'vitest'
3030import { inTemporaryDirectory , mkdir , readFile , writeFile } from '@shopify/cli-kit/node/fs'
3131import { joinPath } from '@shopify/cli-kit/node/path'
32+ import { AbortError } from '@shopify/cli-kit/node/error'
3233
3334const CORRECT_CURRENT_APP_SCHEMA : CurrentAppConfiguration = {
3435 path : '' ,
@@ -232,6 +233,134 @@ describe('getAppScopesArray', () => {
232233 } )
233234} )
234235
236+ describe ( 'preDeployValidation' , ( ) => {
237+ test ( 'throws an error when app-specific webhooks are used with legacy install flow' , async ( ) => {
238+ // Given
239+ const configuration : CurrentAppConfiguration = {
240+ ...DEFAULT_CONFIG ,
241+ access_scopes : {
242+ scopes : 'read_orders' ,
243+ use_legacy_install_flow : true ,
244+ } ,
245+ webhooks : {
246+ api_version : '2024-07' ,
247+ subscriptions : [
248+ {
249+ topics : [ 'orders/create' ] ,
250+ uri : 'webhooks' ,
251+ } ,
252+ ] ,
253+ } ,
254+ }
255+ const app = testApp ( { configuration} )
256+
257+ // When/Then
258+ await expect ( app . preDeployValidation ( ) ) . rejects . toThrow (
259+ new AbortError (
260+ 'App-specific webhook subscriptions are not supported when use_legacy_install_flow is enabled.' ,
261+ `To use app-specific webhooks, you need to:
262+ 1. Remove 'use_legacy_install_flow = true' from your configuration
263+ 2. Run 'shopify app deploy' to sync your scopes with the Partner Dashboard
264+
265+ Alternatively, continue using shop-specific webhooks with the legacy install flow.
266+
267+ Learn more: https://shopify.dev/docs/apps/build/authentication-authorization/app-installation` ,
268+ ) ,
269+ )
270+ } )
271+
272+ test ( 'does not throw an error when app-specific webhooks are used without legacy install flow' , async ( ) => {
273+ // Given
274+ const configuration : CurrentAppConfiguration = {
275+ ...DEFAULT_CONFIG ,
276+ access_scopes : {
277+ scopes : 'read_orders' ,
278+ use_legacy_install_flow : false ,
279+ } ,
280+ webhooks : {
281+ api_version : '2024-07' ,
282+ subscriptions : [
283+ {
284+ topics : [ 'orders/create' ] ,
285+ uri : 'webhooks' ,
286+ } ,
287+ ] ,
288+ } ,
289+ }
290+ const app = testApp ( { configuration} )
291+
292+ // When/Then
293+ await expect ( app . preDeployValidation ( ) ) . resolves . not . toThrow ( )
294+ } )
295+
296+ test ( 'does not throw an error when legacy install flow is enabled without app-specific webhooks' , async ( ) => {
297+ // Given
298+ const configuration : CurrentAppConfiguration = {
299+ ...DEFAULT_CONFIG ,
300+ access_scopes : {
301+ scopes : 'read_orders' ,
302+ use_legacy_install_flow : true ,
303+ } ,
304+ webhooks : {
305+ api_version : '2024-07' ,
306+ } ,
307+ }
308+ const app = testApp ( { configuration} )
309+
310+ // When/Then
311+ await expect ( app . preDeployValidation ( ) ) . resolves . not . toThrow ( )
312+ } )
313+
314+ test ( 'does not throw an error when neither app-specific webhooks nor legacy install flow are used' , async ( ) => {
315+ // Given
316+ const configuration : CurrentAppConfiguration = {
317+ ...DEFAULT_CONFIG ,
318+ access_scopes : {
319+ scopes : 'read_orders' ,
320+ } ,
321+ webhooks : {
322+ api_version : '2024-07' ,
323+ } ,
324+ }
325+ const app = testApp ( { configuration} )
326+
327+ // When/Then
328+ await expect ( app . preDeployValidation ( ) ) . resolves . not . toThrow ( )
329+ } )
330+
331+ test ( 'does not throw an error for legacy schema apps' , async ( ) => {
332+ // Given
333+ const configuration : LegacyAppConfiguration = {
334+ ...CORRECT_LEGACY_APP_SCHEMA ,
335+ scopes : 'read_orders' ,
336+ }
337+ const app = testApp ( configuration , 'legacy' )
338+
339+ // When/Then
340+ await expect ( app . preDeployValidation ( ) ) . resolves . not . toThrow ( )
341+ } )
342+
343+ test ( 'handles null/undefined subscriptions safely' , async ( ) => {
344+ // Given
345+ const configuration : CurrentAppConfiguration = {
346+ ...DEFAULT_CONFIG ,
347+ access_scopes : {
348+ scopes : 'read_orders' ,
349+ use_legacy_install_flow : true ,
350+ } ,
351+ webhooks : {
352+ api_version : '2024-07' ,
353+ // Testing edge case
354+ subscriptions : null as any ,
355+ } ,
356+ }
357+ const app = testApp ( { configuration} )
358+
359+ // When/Then
360+ await expect ( app . preDeployValidation ( ) ) . resolves . not . toThrow ( )
361+ } )
362+ } )
363+
235364describe ( 'validateFunctionExtensionsWithUiHandle' , ( ) => {
236365 const generateFunctionConfig = ( { type, uiHandle} : { type ?: string ; uiHandle ?: string } ) : FunctionConfigType => ( {
237366 description : 'description' ,
0 commit comments