@@ -2262,3 +2262,157 @@ async fn provider_create_from_existing_vertex_config_only_reports_missing_vertex
22622262 . contains_key( "vertex-config-only" )
22632263 ) ;
22642264}
2265+
2266+ #[ tokio:: test]
2267+ async fn provider_create_from_gcloud_adc_with_config_keys ( ) {
2268+ let ts = run_server ( ) . await ;
2269+
2270+ // Write a valid authorized_user ADC file.
2271+ let adc_content = serde_json:: json!( {
2272+ "type" : "authorized_user" ,
2273+ "client_id" : "test-client-id.apps.googleusercontent.com" ,
2274+ "client_secret" : "test-client-secret" ,
2275+ "refresh_token" : "1//test-refresh-token"
2276+ } ) ;
2277+ let adc_file = tempfile:: NamedTempFile :: new ( ) . unwrap ( ) ;
2278+ serde_json:: to_writer ( & adc_file, & adc_content) . unwrap ( ) ;
2279+ let adc_path = adc_file. path ( ) . to_str ( ) . unwrap ( ) . to_string ( ) ;
2280+ let _guard = EnvVarGuard :: set ( & [ ( "GOOGLE_APPLICATION_CREDENTIALS" , & adc_path) ] ) ;
2281+
2282+ run:: provider_create (
2283+ & ts. endpoint ,
2284+ "vertex-with-config" ,
2285+ "google-vertex-ai" ,
2286+ false ,
2287+ & [ ] , // no explicit credentials; ADC flow
2288+ true , // from_gcloud_adc
2289+ & [
2290+ "VERTEX_AI_PROJECT_ID=my-gcp-project" . to_string ( ) ,
2291+ "VERTEX_AI_REGION=us-east1" . to_string ( ) ,
2292+ ] ,
2293+ & ts. tls ,
2294+ )
2295+ . await
2296+ . expect ( "provider_create with --from-gcloud-adc and --config keys should succeed" ) ;
2297+
2298+ // Verify provider was created with the config keys.
2299+ let providers = ts. state . providers . lock ( ) . await ;
2300+ let provider = providers
2301+ . get ( "vertex-with-config" )
2302+ . expect ( "provider should be stored after create" ) ;
2303+ assert_eq ! ( provider. r#type, "google-vertex-ai" ) ;
2304+ assert_eq ! (
2305+ provider
2306+ . config
2307+ . get( "VERTEX_AI_PROJECT_ID" )
2308+ . map( String :: as_str) ,
2309+ Some ( "my-gcp-project" ) ,
2310+ "VERTEX_AI_PROJECT_ID must be stored in provider config"
2311+ ) ;
2312+ assert_eq ! (
2313+ provider. config. get( "VERTEX_AI_REGION" ) . map( String :: as_str) ,
2314+ Some ( "us-east1" ) ,
2315+ "VERTEX_AI_REGION must be stored in provider config"
2316+ ) ;
2317+ drop ( providers) ;
2318+
2319+ // Verify configure_provider_refresh was called (ADC flow always configures refresh).
2320+ let refresh_requests = ts. state . refresh_requests . lock ( ) . await . clone ( ) ;
2321+ assert_eq ! (
2322+ refresh_requests. len( ) ,
2323+ 1 ,
2324+ "exactly one refresh configure call expected"
2325+ ) ;
2326+ assert_eq ! (
2327+ refresh_requests[ 0 ] ,
2328+ ProviderRefreshRequestLog :: Configure {
2329+ provider_name: "vertex-with-config" . to_string( ) ,
2330+ credential_key: "GOOGLE_VERTEX_AI_TOKEN" . to_string( ) ,
2331+ expires_at_ms: None ,
2332+ }
2333+ ) ;
2334+ }
2335+
2336+ #[ tokio:: test]
2337+ async fn provider_create_from_gcloud_adc_missing_refresh_token ( ) {
2338+ let ts = run_server ( ) . await ;
2339+
2340+ // ADC file is valid authorized_user type but missing refresh_token.
2341+ let adc_content = serde_json:: json!( {
2342+ "type" : "authorized_user" ,
2343+ "client_id" : "test-client-id.apps.googleusercontent.com" ,
2344+ "client_secret" : "test-client-secret"
2345+ } ) ;
2346+ let adc_file = tempfile:: NamedTempFile :: new ( ) . unwrap ( ) ;
2347+ serde_json:: to_writer ( & adc_file, & adc_content) . unwrap ( ) ;
2348+ let adc_path = adc_file. path ( ) . to_str ( ) . unwrap ( ) . to_string ( ) ;
2349+ let _guard = EnvVarGuard :: set ( & [ ( "GOOGLE_APPLICATION_CREDENTIALS" , & adc_path) ] ) ;
2350+
2351+ let err = run:: provider_create (
2352+ & ts. endpoint ,
2353+ "vertex-missing-refresh" ,
2354+ "google-vertex-ai" ,
2355+ false ,
2356+ & [ ] ,
2357+ true ,
2358+ & [ ] ,
2359+ & ts. tls ,
2360+ )
2361+ . await
2362+ . expect_err ( "missing refresh_token should produce an error" ) ;
2363+
2364+ let err_msg = err. to_string ( ) ;
2365+ assert ! (
2366+ err_msg. contains( "refresh_token" ) ,
2367+ "error must mention 'refresh_token', got: {err_msg}"
2368+ ) ;
2369+
2370+ // No provider should have been created.
2371+ let providers = ts. state . providers . lock ( ) . await ;
2372+ assert ! (
2373+ providers. is_empty( ) ,
2374+ "no provider must be created when ADC validation fails"
2375+ ) ;
2376+ }
2377+
2378+ #[ tokio:: test]
2379+ async fn provider_create_from_gcloud_adc_missing_client_secret ( ) {
2380+ let ts = run_server ( ) . await ;
2381+
2382+ // ADC file is valid authorized_user type but missing client_secret.
2383+ let adc_content = serde_json:: json!( {
2384+ "type" : "authorized_user" ,
2385+ "client_id" : "test-client-id.apps.googleusercontent.com" ,
2386+ "refresh_token" : "1//test-refresh-token"
2387+ } ) ;
2388+ let adc_file = tempfile:: NamedTempFile :: new ( ) . unwrap ( ) ;
2389+ serde_json:: to_writer ( & adc_file, & adc_content) . unwrap ( ) ;
2390+ let adc_path = adc_file. path ( ) . to_str ( ) . unwrap ( ) . to_string ( ) ;
2391+ let _guard = EnvVarGuard :: set ( & [ ( "GOOGLE_APPLICATION_CREDENTIALS" , & adc_path) ] ) ;
2392+
2393+ let err = run:: provider_create (
2394+ & ts. endpoint ,
2395+ "vertex-missing-secret" ,
2396+ "google-vertex-ai" ,
2397+ false ,
2398+ & [ ] ,
2399+ true ,
2400+ & [ ] ,
2401+ & ts. tls ,
2402+ )
2403+ . await
2404+ . expect_err ( "missing client_secret should produce an error" ) ;
2405+
2406+ let err_msg = err. to_string ( ) ;
2407+ assert ! (
2408+ err_msg. contains( "client_secret" ) ,
2409+ "error must mention 'client_secret', got: {err_msg}"
2410+ ) ;
2411+
2412+ // No provider should have been created.
2413+ let providers = ts. state . providers . lock ( ) . await ;
2414+ assert ! (
2415+ providers. is_empty( ) ,
2416+ "no provider must be created when ADC validation fails"
2417+ ) ;
2418+ }
0 commit comments