@@ -301,5 +301,247 @@ class ShopSessionStorageTest < ActiveSupport::TestCase
301301 assert_nil session . refresh_token
302302 assert_nil session . refresh_token_expires
303303 end
304+
305+ test "#refresh_token_if_expired! does nothing when token is not expired" do
306+ shop = MockShopInstance . new (
307+ shopify_domain : TEST_SHOPIFY_DOMAIN ,
308+ shopify_token : TEST_SHOPIFY_TOKEN ,
309+ expires_at : 1 . day . from_now ,
310+ refresh_token : "refresh-token" ,
311+ refresh_token_expires_at : 30 . days . from_now ,
312+ available_attributes : [ :shopify_domain , :shopify_token , :expires_at , :refresh_token , :refresh_token_expires_at ] ,
313+ )
314+
315+ ShopifyAPI ::Auth ::RefreshToken . expects ( :refresh_access_token ) . never
316+ shop . expects ( :update! ) . never
317+
318+ shop . refresh_token_if_expired!
319+
320+ # Token should remain unchanged
321+ assert_equal TEST_SHOPIFY_TOKEN , shop . shopify_token
322+ end
323+
324+ test "#refresh_token_if_expired! refreshes when token is expired" do
325+ expired_time = 1 . hour . ago
326+ new_expiry = 1 . day . from_now
327+ new_refresh_token_expiry = 30 . days . from_now
328+
329+ shop = MockShopInstance . new (
330+ shopify_domain : TEST_SHOPIFY_DOMAIN ,
331+ shopify_token : "old-token" ,
332+ expires_at : expired_time ,
333+ refresh_token : "refresh-token" ,
334+ refresh_token_expires_at : 30 . days . from_now ,
335+ available_attributes : [ :shopify_domain , :shopify_token , :expires_at , :refresh_token , :refresh_token_expires_at ] ,
336+ )
337+
338+ # Mock the refresh response
339+ new_session = mock
340+ new_session . stubs ( :access_token ) . returns ( "new-token" )
341+ new_session . stubs ( :expires ) . returns ( new_expiry )
342+ new_session . stubs ( :refresh_token ) . returns ( "new-refresh-token" )
343+ new_session . stubs ( :refresh_token_expires ) . returns ( new_refresh_token_expiry )
344+
345+ ShopifyAPI ::Auth ::RefreshToken . expects ( :refresh_access_token )
346+ . with ( shop : TEST_SHOPIFY_DOMAIN , refresh_token : "refresh-token" )
347+ . returns ( new_session )
348+
349+ shop . refresh_token_if_expired!
350+
351+ # Verify the token was updated
352+ assert_equal "new-token" , shop . shopify_token
353+ assert_equal new_expiry , shop . expires_at
354+ assert_equal "new-refresh-token" , shop . refresh_token
355+ assert_equal new_refresh_token_expiry , shop . refresh_token_expires_at
356+ end
357+
358+ test "#refresh_token_if_expired! raises error when refresh token is expired" do
359+ shop = MockShopInstance . new (
360+ shopify_domain : TEST_SHOPIFY_DOMAIN ,
361+ shopify_token : "old-token" ,
362+ expires_at : 1 . hour . ago ,
363+ refresh_token : "refresh-token" ,
364+ refresh_token_expires_at : 1 . hour . ago ,
365+ available_attributes : [ :shopify_domain , :shopify_token , :expires_at , :refresh_token , :refresh_token_expires_at ] ,
366+ )
367+
368+ ShopifyAPI ::Auth ::RefreshToken . expects ( :refresh_access_token ) . never
369+
370+ assert_raises ( ShopifyApp ::RefreshTokenExpiredError ) do
371+ shop . refresh_token_if_expired!
372+ end
373+ end
374+
375+ test "#refresh_token_if_expired! does nothing when refresh_token column doesn't exist" do
376+ shop = MockShopInstance . new (
377+ shopify_domain : TEST_SHOPIFY_DOMAIN ,
378+ shopify_token : TEST_SHOPIFY_TOKEN ,
379+ expires_at : 1 . hour . ago ,
380+ refresh_token_expires_at : 30 . days . from_now ,
381+ available_attributes : [ :shopify_domain , :shopify_token , :expires_at , :refresh_token_expires_at ] ,
382+ )
383+
384+ ShopifyAPI ::Auth ::RefreshToken . expects ( :refresh_access_token ) . never
385+ shop . expects ( :update! ) . never
386+
387+ shop . refresh_token_if_expired!
388+
389+ assert_equal TEST_SHOPIFY_TOKEN , shop . shopify_token
390+ end
391+
392+ test "#refresh_token_if_expired! does nothing when refresh_token is empty" do
393+ shop = MockShopInstance . new (
394+ shopify_domain : TEST_SHOPIFY_DOMAIN ,
395+ shopify_token : TEST_SHOPIFY_TOKEN ,
396+ expires_at : 1 . hour . ago ,
397+ refresh_token : "" ,
398+ refresh_token_expires_at : 30 . days . from_now ,
399+ available_attributes : [ :shopify_domain , :shopify_token , :expires_at , :refresh_token , :refresh_token_expires_at ] ,
400+ )
401+
402+ ShopifyAPI ::Auth ::RefreshToken . expects ( :refresh_access_token ) . never
403+ shop . expects ( :update! ) . never
404+
405+ shop . refresh_token_if_expired!
406+
407+ assert_equal TEST_SHOPIFY_TOKEN , shop . shopify_token
408+ end
409+
410+ test "#refresh_token_if_expired! does nothing when expires_at column doesn't exist" do
411+ shop = MockShopInstance . new (
412+ shopify_domain : TEST_SHOPIFY_DOMAIN ,
413+ shopify_token : TEST_SHOPIFY_TOKEN ,
414+ refresh_token : "refresh-token" ,
415+ refresh_token_expires_at : 30 . days . from_now ,
416+ available_attributes : [ :shopify_domain , :shopify_token , :refresh_token , :refresh_token_expires_at ] ,
417+ )
418+
419+ ShopifyAPI ::Auth ::RefreshToken . expects ( :refresh_access_token ) . never
420+ shop . expects ( :update! ) . never
421+
422+ shop . refresh_token_if_expired!
423+
424+ assert_equal TEST_SHOPIFY_TOKEN , shop . shopify_token
425+ end
426+
427+ test "#refresh_token_if_expired! does nothing when expires_at is nil" do
428+ shop = MockShopInstance . new (
429+ shopify_domain : TEST_SHOPIFY_DOMAIN ,
430+ shopify_token : TEST_SHOPIFY_TOKEN ,
431+ expires_at : nil ,
432+ refresh_token : "refresh-token" ,
433+ refresh_token_expires_at : 30 . days . from_now ,
434+ available_attributes : [ :shopify_domain , :shopify_token , :expires_at , :refresh_token , :refresh_token_expires_at ] ,
435+ )
436+
437+ ShopifyAPI ::Auth ::RefreshToken . expects ( :refresh_access_token ) . never
438+ shop . expects ( :update! ) . never
439+
440+ shop . refresh_token_if_expired!
441+
442+ assert_equal TEST_SHOPIFY_TOKEN , shop . shopify_token
443+ end
444+
445+ test "#refresh_token_if_expired! does nothing when refresh_token_expires_at column doesn't exist" do
446+ shop = MockShopInstance . new (
447+ shopify_domain : TEST_SHOPIFY_DOMAIN ,
448+ shopify_token : TEST_SHOPIFY_TOKEN ,
449+ expires_at : 1 . hour . ago ,
450+ refresh_token : "refresh-token" ,
451+ available_attributes : [ :shopify_domain , :shopify_token , :expires_at , :refresh_token ] ,
452+ )
453+
454+ ShopifyAPI ::Auth ::RefreshToken . expects ( :refresh_access_token ) . never
455+ shop . expects ( :update! ) . never
456+
457+ shop . refresh_token_if_expired!
458+
459+ assert_equal TEST_SHOPIFY_TOKEN , shop . shopify_token
460+ end
461+
462+ test "#refresh_token_if_expired! does nothing when refresh_token_expires_at is nil" do
463+ shop = MockShopInstance . new (
464+ shopify_domain : TEST_SHOPIFY_DOMAIN ,
465+ shopify_token : TEST_SHOPIFY_TOKEN ,
466+ expires_at : 1 . hour . ago ,
467+ refresh_token : "refresh-token" ,
468+ refresh_token_expires_at : nil ,
469+ available_attributes : [ :shopify_domain , :shopify_token , :expires_at , :refresh_token , :refresh_token_expires_at ] ,
470+ )
471+
472+ ShopifyAPI ::Auth ::RefreshToken . expects ( :refresh_access_token ) . never
473+ shop . expects ( :update! ) . never
474+
475+ shop . refresh_token_if_expired!
476+
477+ assert_equal TEST_SHOPIFY_TOKEN , shop . shopify_token
478+ end
479+
480+ test "#refresh_token_if_expired! handles race condition with double-check" do
481+ expired_time = 1 . hour . ago
482+ refreshed_time = 1 . day . from_now
483+
484+ shop = MockShopInstance . new (
485+ shopify_domain : TEST_SHOPIFY_DOMAIN ,
486+ shopify_token : "old-token" ,
487+ expires_at : expired_time ,
488+ refresh_token : "refresh-token" ,
489+ refresh_token_expires_at : 30 . days . from_now ,
490+ available_attributes : [ :shopify_domain , :shopify_token , :expires_at , :refresh_token , :refresh_token_expires_at ] ,
491+ )
492+
493+ # Simulate another process already refreshed the token
494+ shop . expects ( :reload ) . once . with do
495+ shop . expires_at = refreshed_time
496+ shop . shopify_token = "already-refreshed-token"
497+ true
498+ end . returns ( shop )
499+ ShopifyAPI ::Auth ::RefreshToken . expects ( :refresh_access_token ) . never
500+
501+ shop . refresh_token_if_expired!
502+
503+ assert_equal "already-refreshed-token" , shop . shopify_token
504+ end
505+
506+ test "#with_shopify_session calls refresh_token_if_expired! by default" do
507+ shop = MockShopInstance . new (
508+ shopify_domain : TEST_SHOPIFY_DOMAIN ,
509+ shopify_token : TEST_SHOPIFY_TOKEN ,
510+ available_attributes : [ :shopify_domain , :shopify_token ] ,
511+ )
512+
513+ shop . expects ( :refresh_token_if_expired! ) . once
514+
515+ block_executed = false
516+ shop . with_shopify_session do
517+ block_executed = true
518+ end
519+
520+ assert block_executed , "Block should have been executed"
521+ end
522+
523+ test "#with_shopify_session skips refresh when auto_refresh is false" do
524+ expired_time = 1 . hour . ago
525+
526+ shop = MockShopInstance . new (
527+ shopify_domain : TEST_SHOPIFY_DOMAIN ,
528+ shopify_token : "old-token" ,
529+ expires_at : expired_time ,
530+ refresh_token : "refresh-token" ,
531+ available_attributes : [ :shopify_domain , :shopify_token , :expires_at , :refresh_token ] ,
532+ )
533+
534+ # Should NOT refresh even though token is expired
535+ shop . expects ( :refresh_token_if_expired! ) . never
536+ ShopifyAPI ::Auth ::RefreshToken . expects ( :refresh_access_token ) . never
537+
538+ block_executed = false
539+
540+ shop . with_shopify_session ( auto_refresh : false ) do
541+ block_executed = true
542+ end
543+
544+ assert block_executed , "Block should have been executed"
545+ end
304546 end
305547end
0 commit comments