@@ -681,4 +681,167 @@ mod tests {
681681 let contents = tokio:: fs:: read_to_string ( path) . await . unwrap ( ) ;
682682 assert ! ( contents. contains( "\" schema_version\" : 1" ) ) ;
683683 }
684+
685+ #[ test]
686+ fn token_set_not_expiring_when_no_expiry ( ) {
687+ let token_set = TokenSet {
688+ access_token : "token" . into ( ) ,
689+ refresh_token : None ,
690+ id_token : None ,
691+ expires_at : None ,
692+ token_type : None ,
693+ scope : None ,
694+ } ;
695+ assert ! ( !token_set. is_expiring_within( Duration :: from_secs( 3600 ) ) ) ;
696+ }
697+
698+ #[ test]
699+ fn auth_profile_new_token ( ) {
700+ let profile = AuthProfile :: new_token ( "anthropic" , "default" , "sk-abc" . into ( ) ) ;
701+ assert_eq ! ( profile. provider, "anthropic" ) ;
702+ assert_eq ! ( profile. profile_name, "default" ) ;
703+ assert_eq ! ( profile. kind, AuthProfileKind :: Token ) ;
704+ assert_eq ! ( profile. token. as_deref( ) , Some ( "sk-abc" ) ) ;
705+ assert ! ( profile. token_set. is_none( ) ) ;
706+ }
707+
708+ #[ test]
709+ fn auth_profile_new_oauth ( ) {
710+ let ts = TokenSet {
711+ access_token : "access" . into ( ) ,
712+ refresh_token : Some ( "refresh" . into ( ) ) ,
713+ id_token : None ,
714+ expires_at : None ,
715+ token_type : None ,
716+ scope : None ,
717+ } ;
718+ let profile = AuthProfile :: new_oauth ( "openai" , "work" , ts) ;
719+ assert_eq ! ( profile. kind, AuthProfileKind :: OAuth ) ;
720+ assert ! ( profile. token_set. is_some( ) ) ;
721+ assert ! ( profile. token. is_none( ) ) ;
722+ }
723+
724+ #[ test]
725+ fn auth_profiles_data_default ( ) {
726+ let data = AuthProfilesData :: default ( ) ;
727+ assert_eq ! ( data. schema_version, CURRENT_SCHEMA_VERSION ) ;
728+ assert ! ( data. profiles. is_empty( ) ) ;
729+ assert ! ( data. active_profiles. is_empty( ) ) ;
730+ }
731+
732+ #[ test]
733+ fn remove_nonexistent_profile_returns_false ( ) {
734+ let tmp = TempDir :: new ( ) . unwrap ( ) ;
735+ let store = AuthProfilesStore :: new ( tmp. path ( ) , false ) ;
736+ let result = store. remove_profile ( "nonexistent:id" ) . unwrap ( ) ;
737+ assert ! ( !result) ;
738+ }
739+
740+ #[ test]
741+ fn remove_existing_profile_returns_true ( ) {
742+ let tmp = TempDir :: new ( ) . unwrap ( ) ;
743+ let store = AuthProfilesStore :: new ( tmp. path ( ) , false ) ;
744+ let profile = AuthProfile :: new_token ( "test" , "default" , "tok" . into ( ) ) ;
745+ let id = profile. id . clone ( ) ;
746+ store. upsert_profile ( profile, true ) . unwrap ( ) ;
747+
748+ let removed = store. remove_profile ( & id) . unwrap ( ) ;
749+ assert ! ( removed) ;
750+
751+ let data = store. load ( ) . unwrap ( ) ;
752+ assert ! ( !data. profiles. contains_key( & id) ) ;
753+ assert ! ( !data. active_profiles. values( ) . any( |v| v == & id) ) ;
754+ }
755+
756+ #[ test]
757+ fn set_active_profile_errors_for_missing_profile ( ) {
758+ let tmp = TempDir :: new ( ) . unwrap ( ) ;
759+ let store = AuthProfilesStore :: new ( tmp. path ( ) , false ) ;
760+ let err = store
761+ . set_active_profile ( "openai" , "missing:id" )
762+ . unwrap_err ( ) ;
763+ assert ! ( err. to_string( ) . contains( "not found" ) ) ;
764+ }
765+
766+ #[ test]
767+ fn set_active_profile_succeeds_for_existing_profile ( ) {
768+ let tmp = TempDir :: new ( ) . unwrap ( ) ;
769+ let store = AuthProfilesStore :: new ( tmp. path ( ) , false ) ;
770+ let profile = AuthProfile :: new_token ( "openai" , "prod" , "tok" . into ( ) ) ;
771+ let id = profile. id . clone ( ) ;
772+ store. upsert_profile ( profile, false ) . unwrap ( ) ;
773+
774+ store. set_active_profile ( "openai" , & id) . unwrap ( ) ;
775+ let data = store. load ( ) . unwrap ( ) ;
776+ assert_eq ! ( data. active_profiles. get( "openai" ) , Some ( & id) ) ;
777+ }
778+
779+ #[ test]
780+ fn clear_active_profile ( ) {
781+ let tmp = TempDir :: new ( ) . unwrap ( ) ;
782+ let store = AuthProfilesStore :: new ( tmp. path ( ) , false ) ;
783+ let profile = AuthProfile :: new_token ( "openai" , "prod" , "tok" . into ( ) ) ;
784+ store. upsert_profile ( profile, true ) . unwrap ( ) ;
785+
786+ store. clear_active_profile ( "openai" ) . unwrap ( ) ;
787+ let data = store. load ( ) . unwrap ( ) ;
788+ assert ! ( data. active_profiles. get( "openai" ) . is_none( ) ) ;
789+ }
790+
791+ #[ test]
792+ fn update_profile_modifies_in_place ( ) {
793+ let tmp = TempDir :: new ( ) . unwrap ( ) ;
794+ let store = AuthProfilesStore :: new ( tmp. path ( ) , false ) ;
795+ let profile = AuthProfile :: new_token ( "openai" , "prod" , "tok" . into ( ) ) ;
796+ let id = profile. id . clone ( ) ;
797+ store. upsert_profile ( profile, false ) . unwrap ( ) ;
798+
799+ let updated = store
800+ . update_profile ( & id, |p| {
801+ p. metadata . insert ( "env" . into ( ) , "staging" . into ( ) ) ;
802+ Ok ( ( ) )
803+ } )
804+ . unwrap ( ) ;
805+ assert_eq ! (
806+ updated. metadata. get( "env" ) . map( |s| s. as_str( ) ) ,
807+ Some ( "staging" )
808+ ) ;
809+ }
810+
811+ #[ test]
812+ fn update_profile_errors_for_missing_id ( ) {
813+ let tmp = TempDir :: new ( ) . unwrap ( ) ;
814+ let store = AuthProfilesStore :: new ( tmp. path ( ) , false ) ;
815+ let err = store. update_profile ( "missing:id" , |_| Ok ( ( ) ) ) . unwrap_err ( ) ;
816+ assert ! ( err. to_string( ) . contains( "not found" ) ) ;
817+ }
818+
819+ #[ test]
820+ fn upsert_preserves_created_at_on_update ( ) {
821+ let tmp = TempDir :: new ( ) . unwrap ( ) ;
822+ let store = AuthProfilesStore :: new ( tmp. path ( ) , false ) ;
823+ let profile = AuthProfile :: new_token ( "openai" , "prod" , "tok1" . into ( ) ) ;
824+ let id = profile. id . clone ( ) ;
825+ let created = profile. created_at ;
826+ store. upsert_profile ( profile, false ) . unwrap ( ) ;
827+
828+ std:: thread:: sleep ( Duration :: from_millis ( 10 ) ) ;
829+ let updated = AuthProfile :: new_token ( "openai" , "prod" , "tok2" . into ( ) ) ;
830+ store. upsert_profile ( updated, false ) . unwrap ( ) ;
831+
832+ let data = store. load ( ) . unwrap ( ) ;
833+ let loaded = data. profiles . get ( & id) . unwrap ( ) ;
834+ assert_eq ! ( loaded. created_at, created) ;
835+ }
836+
837+ #[ test]
838+ fn auth_profile_kind_serde_roundtrip ( ) {
839+ let json = serde_json:: to_string ( & AuthProfileKind :: OAuth ) . unwrap ( ) ;
840+ assert_eq ! ( json, "\" o-auth\" " ) ; // kebab-case
841+ let back: AuthProfileKind = serde_json:: from_str ( & json) . unwrap ( ) ;
842+ assert_eq ! ( back, AuthProfileKind :: OAuth ) ;
843+
844+ let json = serde_json:: to_string ( & AuthProfileKind :: Token ) . unwrap ( ) ;
845+ assert_eq ! ( json, "\" token\" " ) ;
846+ }
684847}
0 commit comments