@@ -10,7 +10,9 @@ import (
1010 "github.com/stretchr/testify/assert"
1111 "github.com/stretchr/testify/require"
1212 fly "github.com/superfly/fly-go"
13+ "github.com/superfly/fly-go/tokens"
1314 "github.com/superfly/flyctl/internal/command_context"
15+ "github.com/superfly/flyctl/internal/config"
1416 "github.com/superfly/flyctl/internal/flag/flagctx"
1517 "github.com/superfly/flyctl/internal/uiex"
1618 "github.com/superfly/flyctl/internal/uiexutil"
@@ -783,3 +785,148 @@ func TestCreateCommand_RegionValidation(t *testing.T) {
783785 require .NoError (t , err )
784786 assert .False (t , valid , "Should not find invalid region" )
785787}
788+
789+ // Test actual MPG token validation functions
790+ func TestMPGTokenValidation (t * testing.T ) {
791+ t .Run ("detectTokenHasMacaroon with actual contexts" , func (t * testing.T ) {
792+ // Test case 1: Context with no config (should handle gracefully)
793+ emptyCtx := context .Background ()
794+ // This should panic or return false - let's catch the panic
795+ func () {
796+ defer func () {
797+ if r := recover (); r != nil {
798+ // Expected panic due to no config in context
799+ t .Logf ("Expected panic caught: %v" , r )
800+ }
801+ }()
802+ result := detectTokenHasMacaroon (emptyCtx )
803+ // If we get here without panicking, it should return false
804+ assert .False (t , result , "Should return false when config is nil" )
805+ }()
806+
807+ // Test case 2: Context with nil tokens
808+ configWithNilTokens := & config.Config {
809+ Tokens : nil ,
810+ }
811+ ctxWithNilTokens := config .NewContext (context .Background (), configWithNilTokens )
812+ result := detectTokenHasMacaroon (ctxWithNilTokens )
813+ assert .False (t , result , "Should return false when tokens are nil" )
814+
815+ // Test case 3: Context with empty tokens (no macaroons)
816+ emptyTokens := tokens .Parse ("" ) // Parse empty string creates empty tokens
817+ configWithEmptyTokens := & config.Config {
818+ Tokens : emptyTokens ,
819+ }
820+ ctxWithEmptyTokens := config .NewContext (context .Background (), configWithEmptyTokens )
821+ result = detectTokenHasMacaroon (ctxWithEmptyTokens )
822+ assert .False (t , result , "Should return false when no macaroon tokens exist" )
823+
824+ // Test case 4: Context with bearer tokens only (no macaroons)
825+ bearerTokens := tokens .Parse ("some_bearer_token_here" ) // This won't be recognized as macaroon
826+ configWithBearerTokens := & config.Config {
827+ Tokens : bearerTokens ,
828+ }
829+ ctxWithBearerTokens := config .NewContext (context .Background (), configWithBearerTokens )
830+ result = detectTokenHasMacaroon (ctxWithBearerTokens )
831+ assert .False (t , result , "Should return false when only bearer tokens exist" )
832+
833+ // Test case 5: Context with macaroon tokens
834+ macaroonTokens := tokens .Parse ("fm1r_test_macaroon_token,fm2_another_macaroon" ) // fm1r and fm2 prefixes are macaroon tokens
835+ configWithMacaroonTokens := & config.Config {
836+ Tokens : macaroonTokens ,
837+ }
838+ ctxWithMacaroonTokens := config .NewContext (context .Background (), configWithMacaroonTokens )
839+ result = detectTokenHasMacaroon (ctxWithMacaroonTokens )
840+ assert .True (t , result , "Should return true when macaroon tokens exist" )
841+
842+ // Test case 6: Context with mixed tokens (including macaroons)
843+ mixedTokens := tokens .Parse ("bearer_token,fm1a_macaroon_token,oauth_token" )
844+ configWithMixedTokens := & config.Config {
845+ Tokens : mixedTokens ,
846+ }
847+ ctxWithMixedTokens := config .NewContext (context .Background (), configWithMixedTokens )
848+ result = detectTokenHasMacaroon (ctxWithMixedTokens )
849+ assert .True (t , result , "Should return true when macaroon tokens exist among mixed tokens" )
850+ })
851+
852+ t .Run ("validateMPGTokenCompatibility with actual contexts" , func (t * testing.T ) {
853+ // Test case 1: Context with nil tokens - should fail
854+ configWithNilTokens := & config.Config {
855+ Tokens : nil ,
856+ }
857+ ctxWithNilTokens := config .NewContext (context .Background (), configWithNilTokens )
858+ err := validateMPGTokenCompatibility (ctxWithNilTokens )
859+ assert .Error (t , err , "Should return error when no macaroon tokens" )
860+ assert .Contains (t , err .Error (), "MPG commands require updated tokens" )
861+ assert .Contains (t , err .Error (), "flyctl auth logout" )
862+ assert .Contains (t , err .Error (), "flyctl auth login" )
863+
864+ // Test case 2: Context with empty tokens - should fail
865+ emptyTokens := tokens .Parse ("" )
866+ configWithEmptyTokens := & config.Config {
867+ Tokens : emptyTokens ,
868+ }
869+ ctxWithEmptyTokens := config .NewContext (context .Background (), configWithEmptyTokens )
870+ err = validateMPGTokenCompatibility (ctxWithEmptyTokens )
871+ assert .Error (t , err , "Should return error when no macaroon tokens" )
872+ assert .Contains (t , err .Error (), "MPG commands require updated tokens" )
873+
874+ // Test case 3: Context with bearer tokens only - should fail
875+ bearerTokens := tokens .Parse ("some_bearer_token" )
876+ configWithBearerTokens := & config.Config {
877+ Tokens : bearerTokens ,
878+ }
879+ ctxWithBearerTokens := config .NewContext (context .Background (), configWithBearerTokens )
880+ err = validateMPGTokenCompatibility (ctxWithBearerTokens )
881+ assert .Error (t , err , "Should return error when no macaroon tokens" )
882+ assert .Contains (t , err .Error (), "MPG commands require updated tokens" )
883+
884+ // Test case 4: Context with macaroon tokens - should pass
885+ macaroonTokens := tokens .Parse ("fm1r_test_macaroon_token" )
886+ configWithMacaroonTokens := & config.Config {
887+ Tokens : macaroonTokens ,
888+ }
889+ ctxWithMacaroonTokens := config .NewContext (context .Background (), configWithMacaroonTokens )
890+ err = validateMPGTokenCompatibility (ctxWithMacaroonTokens )
891+ assert .NoError (t , err , "Should not return error when macaroon tokens exist" )
892+
893+ // Test case 5: Context with mixed tokens including macaroons - should pass
894+ mixedTokens := tokens .Parse ("bearer_token,fm1a_macaroon_token,oauth_token" )
895+ configWithMixedTokens := & config.Config {
896+ Tokens : mixedTokens ,
897+ }
898+ ctxWithMixedTokens := config .NewContext (context .Background (), configWithMixedTokens )
899+ err = validateMPGTokenCompatibility (ctxWithMixedTokens )
900+ assert .NoError (t , err , "Should not return error when macaroon tokens exist among mixed tokens" )
901+ })
902+
903+ t .Run ("MPG commands reject non-macaroon tokens" , func (t * testing.T ) {
904+ // This test verifies that actual MPG command functions call the validation
905+ // and properly reject contexts without macaroon tokens
906+
907+ // Create a context with bearer tokens only (no macaroons)
908+ bearerTokens := tokens .Parse ("some_bearer_token" )
909+ configWithBearerTokens := & config.Config {
910+ Tokens : bearerTokens ,
911+ }
912+ ctxWithBearerTokens := config .NewContext (context .Background (), configWithBearerTokens )
913+
914+ // Test that the actual run functions would reject this context
915+ // We can't easily test the full run functions due to their dependencies,
916+ // but we can verify the validation function they call would fail
917+ err := validateMPGTokenCompatibility (ctxWithBearerTokens )
918+ assert .Error (t , err , "MPG commands should reject contexts with only bearer tokens" )
919+ assert .Contains (t , err .Error (), "MPG commands require updated tokens" )
920+
921+ // Create a context with macaroon tokens
922+ macaroonTokens := tokens .Parse ("fm1r_macaroon_token" )
923+ configWithMacaroonTokens := & config.Config {
924+ Tokens : macaroonTokens ,
925+ }
926+ ctxWithMacaroonTokens := config .NewContext (context .Background (), configWithMacaroonTokens )
927+
928+ // Test that the validation would pass for macaroon tokens
929+ err = validateMPGTokenCompatibility (ctxWithMacaroonTokens )
930+ assert .NoError (t , err , "MPG commands should accept contexts with macaroon tokens" )
931+ })
932+ }
0 commit comments