@@ -3,6 +3,7 @@ import {ensureThemeStore} from './theme-store.js'
33import { describe , vi , expect , test , beforeEach } from 'vitest'
44import { Config , Flags } from '@oclif/core'
55import { AdminSession , ensureAuthenticatedThemes } from '@shopify/cli-kit/node/session'
6+ import { loadAdminSessionFromStoreAuth } from '@shopify/store'
67import { loadEnvironment } from '@shopify/cli-kit/node/environments'
78import { fileExistsSync } from '@shopify/cli-kit/node/fs'
89import { AbortError } from '@shopify/cli-kit/node/error'
@@ -14,6 +15,7 @@ import {hashString} from '@shopify/cli-kit/node/crypto'
1415import type { Writable } from 'stream'
1516
1617vi . mock ( '@shopify/cli-kit/node/session' )
18+ vi . mock ( '@shopify/store' , ( ) => ( { loadAdminSessionFromStoreAuth : vi . fn ( ) } ) )
1719vi . mock ( '@shopify/cli-kit/node/environments' )
1820vi . mock ( '@shopify/cli-kit/node/ui' )
1921vi . mock ( '@shopify/cli-kit/node/metadata' , ( ) => ( {
@@ -180,6 +182,9 @@ describe('ThemeCommand', () => {
180182 }
181183 vi . mocked ( ensureThemeStore ) . mockReturnValue ( 'test-store.myshopify.com' )
182184 vi . mocked ( ensureAuthenticatedThemes ) . mockResolvedValue ( mockSession )
185+ vi . mocked ( loadAdminSessionFromStoreAuth ) . mockRejectedValue (
186+ new AbortError ( 'No stored app authentication found for test-store.myshopify.com.' ) ,
187+ )
183188 vi . mocked ( fileExistsSync ) . mockReturnValue ( true )
184189 } )
185190
@@ -244,6 +249,45 @@ describe('ThemeCommand', () => {
244249 expect ( sensitiveMetadata ) . toContainEqual ( { store_fqdn : mockSession . storeFqdn } )
245250 } )
246251
252+ test ( 'uses a matching store auth cache session when no password is provided' , async ( ) => {
253+ const storeAuthSession = { token : 'shpat_preview_token' , storeFqdn : 'test-store.myshopify.com' }
254+ vi . mocked ( loadAdminSessionFromStoreAuth ) . mockResolvedValue ( { adminSession : storeAuthSession , session : { } as any } )
255+
256+ await CommandConfig . load ( )
257+ const command = new TestThemeCommand ( [ ] , CommandConfig )
258+
259+ await command . run ( )
260+
261+ expect ( loadAdminSessionFromStoreAuth ) . toHaveBeenCalledWith ( 'test-store.myshopify.com' )
262+ expect ( ensureAuthenticatedThemes ) . not . toHaveBeenCalled ( )
263+ expect ( command . commandCalls [ 0 ] ) . toMatchObject ( { session : storeAuthSession } )
264+ } )
265+
266+ test ( 'uses the password flag instead of a matching store auth cache session' , async ( ) => {
267+ const storeAuthSession = { token : 'shpat_preview_token' , storeFqdn : 'test-store.myshopify.com' }
268+ vi . mocked ( loadAdminSessionFromStoreAuth ) . mockResolvedValue ( { adminSession : storeAuthSession , session : { } as any } )
269+
270+ await CommandConfig . load ( )
271+ const command = new TestThemeCommand ( [ '--password' , 'shptka_password' ] , CommandConfig )
272+
273+ await command . run ( )
274+
275+ expect ( loadAdminSessionFromStoreAuth ) . not . toHaveBeenCalled ( )
276+ expect ( ensureAuthenticatedThemes ) . toHaveBeenCalledWith ( 'test-store.myshopify.com' , 'shptka_password' )
277+ expect ( command . commandCalls [ 0 ] ) . toMatchObject ( { session : mockSession } )
278+ } )
279+
280+ test ( 'falls back to theme authentication when no matching store auth cache session exists' , async ( ) => {
281+ await CommandConfig . load ( )
282+ const command = new TestThemeCommand ( [ ] , CommandConfig )
283+
284+ await command . run ( )
285+
286+ expect ( loadAdminSessionFromStoreAuth ) . toHaveBeenCalledWith ( 'test-store.myshopify.com' )
287+ expect ( ensureAuthenticatedThemes ) . toHaveBeenCalledWith ( 'test-store.myshopify.com' , undefined )
288+ expect ( command . commandCalls [ 0 ] ) . toMatchObject ( { session : mockSession } )
289+ } )
290+
247291 test ( 'single environment provided but not found in TOML - throws AbortError' , async ( ) => {
248292 // Given
249293 vi . mocked ( loadEnvironment ) . mockResolvedValue ( undefined )
@@ -839,6 +883,60 @@ describe('ThemeCommand', () => {
839883 expect ( liveEnvFlags ?. [ 'no-color' ] ) . toEqual ( true )
840884 } )
841885
886+ test ( 'multiple environment commands accept missing password when a store auth cache session exists' , async ( ) => {
887+ const storeAuthSession = { token : 'shpat_preview_token' , storeFqdn : 'store1.myshopify.com' }
888+ vi . mocked ( loadEnvironment )
889+ . mockResolvedValueOnce ( { store : 'store1.myshopify.com' , path : '/home/path/to/theme1' } )
890+ . mockResolvedValueOnce ( { store : 'store2.myshopify.com' , password : 'password2' , path : '/home/path/to/theme2' } )
891+ vi . mocked ( loadAdminSessionFromStoreAuth ) . mockResolvedValue ( { adminSession : storeAuthSession , session : { } as any } )
892+ vi . mocked ( renderConfirmationPrompt ) . mockResolvedValue ( true )
893+ vi . mocked ( renderConcurrent ) . mockImplementation ( async ( { processes} ) => {
894+ for ( const process of processes ) {
895+ // eslint-disable-next-line no-await-in-loop
896+ await process . action ( { } as Writable , { } as Writable , { } as any )
897+ }
898+ } )
899+ vi . mocked ( ensureThemeStore ) . mockImplementation ( ( options : any ) => options . store )
900+
901+ await CommandConfig . load ( )
902+ const command = new TestThemeCommandWithPathFlag (
903+ [ '--environment' , 'preview' , '--environment' , 'another-preview' ] ,
904+ CommandConfig ,
905+ )
906+
907+ await command . run ( )
908+
909+ expect ( renderWarning ) . not . toHaveBeenCalled ( )
910+ expect ( loadAdminSessionFromStoreAuth ) . toHaveBeenCalledWith ( 'store1.myshopify.com' )
911+ expect ( ensureAuthenticatedThemes ) . toHaveBeenCalledWith ( 'store2.myshopify.com' , 'password2' )
912+ expect ( command . commandCalls ) . toEqual (
913+ expect . arrayContaining ( [ expect . objectContaining ( { session : storeAuthSession } ) ] ) ,
914+ )
915+ } )
916+
917+ test ( 'multiple environment commands still require password when no store auth cache session exists' , async ( ) => {
918+ vi . mocked ( loadEnvironment )
919+ . mockResolvedValueOnce ( { store : 'store1.myshopify.com' , path : '/home/path/to/theme1' } )
920+ . mockResolvedValueOnce ( { store : 'store2.myshopify.com' , password : 'password2' , path : '/home/path/to/theme2' } )
921+ vi . mocked ( renderConcurrent ) . mockResolvedValue ( undefined )
922+
923+ await CommandConfig . load ( )
924+ const command = new TestThemeCommandWithPathFlag (
925+ [ '--environment' , 'preview' , '--environment' , 'another-preview' ] ,
926+ CommandConfig ,
927+ )
928+
929+ await command . run ( )
930+
931+ expect ( renderWarning ) . toHaveBeenCalledWith (
932+ expect . objectContaining ( {
933+ body : [ 'Missing required flags in environment configuration for preview:' , { list : { items : [ 'password' ] } } ] ,
934+ } ) ,
935+ )
936+ expect ( renderConcurrent ) . not . toHaveBeenCalled ( )
937+ expect ( ensureAuthenticatedThemes ) . not . toHaveBeenCalled ( )
938+ } )
939+
842940 test ( 'commands will only create a session object if the password flag is supported' , async ( ) => {
843941 // Given
844942 vi . mocked ( loadEnvironment )
0 commit comments