@@ -2,8 +2,10 @@ import {
22 AppSchema ,
33 CurrentAppConfiguration ,
44 LegacyAppConfiguration ,
5+ TemplateConfigSchema ,
56 getAppScopes ,
67 getAppScopesArray ,
8+ getTemplateScopesArray ,
79 getUIExtensionRendererVersion ,
810 isCurrentAppSchema ,
911 isLegacyAppSchema ,
@@ -233,6 +235,94 @@ describe('getAppScopesArray', () => {
233235 } )
234236} )
235237
238+ describe ( 'TemplateConfigSchema' , ( ) => {
239+ test ( 'parses config with legacy scopes format' , ( ) => {
240+ const config = { scopes : 'read_products,write_products' }
241+ const result = TemplateConfigSchema . parse ( config )
242+ expect ( result . scopes ) . toEqual ( 'read_products,write_products' )
243+ } )
244+
245+ test ( 'parses config with access_scopes format' , ( ) => {
246+ const config = { access_scopes : { scopes : 'read_products,write_products' } }
247+ const result = TemplateConfigSchema . parse ( config )
248+ expect ( result . access_scopes ?. scopes ) . toEqual ( 'read_products,write_products' )
249+ } )
250+
251+ test ( 'preserves extra keys like metafields via passthrough' , ( ) => {
252+ const config = {
253+ scopes : 'write_products' ,
254+ product : {
255+ metafields : {
256+ app : {
257+ demo_info : {
258+ type : 'single_line_text_field' ,
259+ name : 'Demo Source Info' ,
260+ } ,
261+ } ,
262+ } ,
263+ } ,
264+ webhooks : {
265+ api_version : '2025-07' ,
266+ subscriptions : [ { uri : '/webhooks' , topics : [ 'app/uninstalled' ] } ] ,
267+ } ,
268+ }
269+ const result = TemplateConfigSchema . parse ( config )
270+ expect ( result . product ) . toEqual ( config . product )
271+ expect ( result . webhooks ) . toEqual ( config . webhooks )
272+ } )
273+
274+ test ( 'parses empty config' , ( ) => {
275+ const config = { }
276+ const result = TemplateConfigSchema . parse ( config )
277+ expect ( result ) . toEqual ( { } )
278+ } )
279+ } )
280+
281+ describe ( 'getTemplateScopesArray' , ( ) => {
282+ test ( 'returns scopes from legacy format' , ( ) => {
283+ const config = { scopes : 'read_themes,write_products' }
284+ expect ( getTemplateScopesArray ( config ) ) . toEqual ( [ 'read_themes' , 'write_products' ] )
285+ } )
286+
287+ test ( 'returns scopes from access_scopes format' , ( ) => {
288+ const config = { access_scopes : { scopes : 'read_themes,write_products' } }
289+ expect ( getTemplateScopesArray ( config ) ) . toEqual ( [ 'read_themes' , 'write_products' ] )
290+ } )
291+
292+ test ( 'trims whitespace from scopes and sorts' , ( ) => {
293+ const config = { scopes : ' write_products , read_themes ' }
294+ expect ( getTemplateScopesArray ( config ) ) . toEqual ( [ 'read_themes' , 'write_products' ] )
295+ } )
296+
297+ test ( 'includes empty strings from consecutive commas (caller should handle)' , ( ) => {
298+ const config = { scopes : 'read_themes,write_products' }
299+ expect ( getTemplateScopesArray ( config ) ) . toEqual ( [ 'read_themes' , 'write_products' ] )
300+ } )
301+
302+ test ( 'returns empty array when no scopes defined' , ( ) => {
303+ const config = { }
304+ expect ( getTemplateScopesArray ( config ) ) . toEqual ( [ ] )
305+ } )
306+
307+ test ( 'returns empty array when scopes is empty string' , ( ) => {
308+ const config = { scopes : '' }
309+ expect ( getTemplateScopesArray ( config ) ) . toEqual ( [ ] )
310+ } )
311+
312+ test ( 'returns empty array when access_scopes.scopes is empty' , ( ) => {
313+ const config = { access_scopes : { scopes : '' } }
314+ expect ( getTemplateScopesArray ( config ) ) . toEqual ( [ ] )
315+ } )
316+
317+ test ( 'prefers legacy scopes over access_scopes when both present' , ( ) => {
318+ const config = {
319+ scopes : 'read_themes' ,
320+ access_scopes : { scopes : 'write_products' } ,
321+ }
322+ expect ( getTemplateScopesArray ( config ) ) . toEqual ( [ 'read_themes' ] )
323+ } )
324+ } )
325+
236326describe ( 'preDeployValidation' , ( ) => {
237327 test ( 'throws an error when app-specific webhooks are used with legacy install flow' , async ( ) => {
238328 // Given
0 commit comments