geeViz.eeAuth.tests.test_discovery_and_modes¶
Comprehensive tests for credential auto-discovery and Map.view mode selection. Covers the full matrix:
Credential types: SA via path env, SA via b64 env, OAuth refresh token (authorized_user file), bare
refresh_tokendictQuantities: zero, one, multiple
Auth modes:
auto,proxy,legacySurfaces: Python SDK init (via ensure_started), Map.view() URL builder
Discovery is exercised against a fake-filesystem + env-patched setup so no real GCP credentials are required.
Functions
|
If a user has GOOGLE_CLOUD_PROJECT=earthengine-legacy set (somehow — copy-paste mistake, misguided tutorial), filter that out too — never let the placeholder propagate. |
|
|
EE's shared OAuth-client project numbers (e.g. 764086051850) are listed in |
|
|
|
When the EE credentials file has no refresh_token field (user deleted it), the diagnosis must say so — that's the signal robust_init uses to NOT silently fall through to ADC. |
|
ADC's |
|
When |
|
Falls back to GOOGLE_CLOUD_PROJECT when ee.data state AND ADC don't have a project (i.e. ee.Initialize never called and the user hasn't run |
|
|
The project backfill must NOT clobber an SA's project_id that came from its own JSON. |
|
When ee.data state is empty, ADC has no quota_project_id, AND env vars are unset, fall back to |
Discovers GOOGLE_APPLICATION_CREDENTIALS pointing at an SA JSON. |
|
Discovers ~/.config/earthengine/credentials style refresh token. |
|
Discovers GEE_SERVICE_ACCOUNT_B64 base64-encoded SA. |
|
When |
|
All four sources at once → discovery picks them all up. |
|
Discovers GEE_<NAME>_SERVICE_ACCOUNT for arbitrary names. |
|
overwrite=True replaces existing names. |
|
Already-registered credentials are not overwritten. |
|
|
When credentials are discoverable, auto mode starts the proxy. |
Calling ensure_started twice is safe; second returns same state. |
|
|
mode='auto' returns empty proxy_url so caller falls back to legacy. |
mode='legacy' must not touch state — Map.view() falls through. |
|
mode='proxy' is explicit — fail noisily so users notice. |
|
|
No gcloud installed → return empty, no exception. |
|
|
|
|
|
When the OAuth-no-project failure mode happens, the error message must tell users how to fix it — listing the four remediation paths — rather than dumping a raw HttpError. |
|
When an SA's project_id IS a real project but the SA itself lacks |
The Python SDK init path must use AnonymousCredentials so the SDK doesn't try to attach its own token — the proxy supplies the real one. |
|
When proxy is unavailable, the URL must use the legacy format with the minted token baked in. |
|
Default mode is 'auto'; Map.view() must call ensure_started so discovery + proxy auto-start happens. |
|
When the legacy path runs, a DeprecationWarning must be emitted. |
|
When GEEVIZ_EEAUTH_MODE=proxy and proxy can't start, Map.view() must surface the error rather than silently falling back. |
|
Map.view() must consult GEEVIZ_EEAUTH_MODE to pick auto/proxy/legacy. |
|
A registry can hold a personal-account refresh token AND a service account at the same time. |
|
Authorized_user files don't have project_id, but they often have quota_project_id. |
|
User-supplied project argument wins over what's in the JSON. |
|
|
When proxy is active, |
EE's own |
|
Source-of-truth: both code paths point at the same eeCreds proxy_url, ensuring 'Python SDK + Map viewer' parity. |
|
The defining behavior of the simple flow: call |
|
The detached proxy runs in a separate process, so the project id resolved by the step-4 interactive prompt only reaches THIS process's tenant registry. |
|
|
When ee.Initialize() fails, the interactive fallback must run |
If init fails even after |
|
When step 3a's cached project no longer works (deleted, access revoked, wrong Google account), the cache must be wiped so the interactive fallback offers a fresh prompt instead of a future run silently retrying the same broken value on every import. |
|
mode='proxy' is explicit — if the user demanded the proxy and it can't start, surface the error rather than falling through. |
|
|
Non-interactive callers (daemons, CI) must get a clear RuntimeError when ee.Initialize() fails, NOT a hanging ee.Authenticate() prompt. |
|
|
When the automatic resolution chain fails and the interactive fallback has to prompt for a project id, a typo or an inaccessible-project rejection must not be instantly fatal — a common Colab failure is entering a project the wrong signed-in account can't reach. |
|
|
Fast path: when |
After |
|
eeAuth.robust_init must attempt the proxy via |
|
In Colab, |
|
When the fast-path and the proxy both fail, robust_init must call plain |
|
robust_init must verify each init attempt with a real EE call before declaring success — a silently misconfigured proxy or stale project would otherwise break the next user operation. |
|
|
|
- geeViz.eeAuth.tests.test_discovery_and_modes.test_discover_finds_adc_path()[source]¶
Discovers GOOGLE_APPLICATION_CREDENTIALS pointing at an SA JSON.
- geeViz.eeAuth.tests.test_discovery_and_modes.test_discover_finds_ee_persistent_oauth()[source]¶
Discovers ~/.config/earthengine/credentials style refresh token.
- geeViz.eeAuth.tests.test_discovery_and_modes.test_discover_backfills_project_for_oauth_from_ee_state()[source]¶
When
ee.Initialize(project='X')has been called, discovery must pick X up as the project for OAuth entries — otherwise the proxy forwards EE calls withoutx-goog-user-projectand the upstream falls back toearthengine-legacywhich personal Google accounts can’t use → 403 ‘project not found or deleted’.
- geeViz.eeAuth.tests.test_discovery_and_modes.test_discover_backfills_project_for_oauth_from_env_var()[source]¶
Falls back to GOOGLE_CLOUD_PROJECT when ee.data state AND ADC don’t have a project (i.e. ee.Initialize never called and the user hasn’t run
gcloud auth application-default set-quota-project).
- geeViz.eeAuth.tests.test_discovery_and_modes.test_discover_backfills_project_for_oauth_from_adc()[source]¶
ADC’s
quota_project_idis the source EE itself uses, so it should take priority over env vars / gcloud-config when neither ee.data state nor an explicit env var is set.
- geeViz.eeAuth.tests.test_discovery_and_modes.test_discover_falls_back_to_gcloud_config_when_no_other_hint()[source]¶
When ee.data state is empty, ADC has no quota_project_id, AND env vars are unset, fall back to
gcloud config get-value project. This is the chicken-and-egg scenario:import geeViz.geeViewruns robustInitializer BEFORE the user has calledee.Initialize(project=...), so we have nothing else to read.
- geeViz.eeAuth.tests.test_discovery_and_modes.test_detect_oauth_project_filters_out_earthengine_legacy_placeholder()[source]¶
ee.data._get_state().cloud_api_user_projectdefaults to the literal stringearthengine-legacyBEFORE anyee.Initialize( project=...)runs. That’s the SDK’s internal placeholder for “no real project set” — treating it as a hit would route every request through a consumer personal accounts can’t use → 403.Detection must filter this value out at every step and fall through to ADC / env vars / gcloud config.
- geeViz.eeAuth.tests.test_discovery_and_modes.test_detect_oauth_project_filters_legacy_from_env_vars_too()[source]¶
If a user has GOOGLE_CLOUD_PROJECT=earthengine-legacy set (somehow — copy-paste mistake, misguided tutorial), filter that out too — never let the placeholder propagate.
- geeViz.eeAuth.tests.test_discovery_and_modes.test_detect_oauth_project_filters_sdk_projects()[source]¶
EE’s shared OAuth-client project numbers (e.g. 764086051850) are listed in
ee.oauth.SDK_PROJECTS. They aren’t billable by end users, so any source returning one of them must be filtered the same wayearthengine-legacyis.
- geeViz.eeAuth.tests.test_discovery_and_modes.test_gcloud_default_project_returns_empty_when_unset()[source]¶
gcloud config get-value projectreturning(unset)or empty must produce""from the helper — never the literal string(unset).
- geeViz.eeAuth.tests.test_discovery_and_modes.test_gcloud_default_project_handles_missing_gcloud_silently()[source]¶
No gcloud installed → return empty, no exception.
- geeViz.eeAuth.tests.test_discovery_and_modes.test_initialize_via_proxy_emits_helpful_message_on_earthengine_legacy_403()[source]¶
When the OAuth-no-project failure mode happens, the error message must tell users how to fix it — listing the four remediation paths — rather than dumping a raw HttpError.
- geeViz.eeAuth.tests.test_discovery_and_modes.test_initialize_via_proxy_explains_sa_missing_serviceusage_role()[source]¶
When an SA’s project_id IS a real project but the SA itself lacks
roles/serviceusage.serviceUsageConsumeron it, the error must name the project, point at the missing role, and offer theproject=override as an alternative fix. This is the failure mode users hit when registering an SA whose JSON’s project_id has restrictive IAM.
- geeViz.eeAuth.tests.test_discovery_and_modes.test_proxy_strips_quota_project_on_discovery_path()[source]¶
EE’s own
_cloud_api_utils.build_cloud_resourcestripsquota_project_idfrom credentials before fetching$discovery/rest— the serviceUsage API rejects discovery requests that carry a consumer project. Our proxy must mirror that: when the incoming path contains$discovery/rest, do NOT injectx-goog-user-projecton the forwarded request.Without this, SAs that have full EE perms but lack
serviceusage.serviceUsageConsumer403 on init even though they can do all the real work afterward — that’s the bug the user hit.
- geeViz.eeAuth.tests.test_discovery_and_modes.test_discover_does_not_overwrite_sa_project_with_oauth_hint()[source]¶
The project backfill must NOT clobber an SA’s project_id that came from its own JSON. SA files have authoritative project info; only OAuth entries (which lack project info) should get backfilled.
- geeViz.eeAuth.tests.test_discovery_and_modes.test_discover_finds_env_default_b64()[source]¶
Discovers GEE_SERVICE_ACCOUNT_B64 base64-encoded SA.
- geeViz.eeAuth.tests.test_discovery_and_modes.test_discover_finds_per_tenant_envs()[source]¶
Discovers GEE_<NAME>_SERVICE_ACCOUNT for arbitrary names.
- geeViz.eeAuth.tests.test_discovery_and_modes.test_discover_skips_existing_names_by_default()[source]¶
Already-registered credentials are not overwritten.
- geeViz.eeAuth.tests.test_discovery_and_modes.test_discover_overwrite_replaces_existing()[source]¶
overwrite=True replaces existing names.
- geeViz.eeAuth.tests.test_discovery_and_modes.test_discover_with_nothing_to_find_returns_empty_list()[source]¶
- geeViz.eeAuth.tests.test_discovery_and_modes.test_discover_finds_gcloud_adc_default_file()[source]¶
When
gcloud auth application-default loginhas run, its well-known credentials file at e.g.~/AppData/Roaming/gcloud/application_default_credentials.jsonmust be picked up soMap.view()can spin up the proxy in ADC-only environments. Registered as"adc-default".
- geeViz.eeAuth.tests.test_discovery_and_modes.test_discover_finds_multiple_simultaneously()[source]¶
All four sources at once → discovery picks them all up.
- geeViz.eeAuth.tests.test_discovery_and_modes.test_ensure_started_mode_legacy_does_nothing()[source]¶
mode=’legacy’ must not touch state — Map.view() falls through.
- geeViz.eeAuth.tests.test_discovery_and_modes.test_ensure_started_mode_proxy_raises_when_no_creds()[source]¶
mode=’proxy’ is explicit — fail noisily so users notice.
- geeViz.eeAuth.tests.test_discovery_and_modes.test_ensure_started_mode_auto_falls_back_silently_with_no_creds()[source]¶
mode=’auto’ returns empty proxy_url so caller falls back to legacy.
- geeViz.eeAuth.tests.test_discovery_and_modes.test_ensure_started_auto_discovers_and_would_start_with_creds()[source]¶
When credentials are discoverable, auto mode starts the proxy. Stub the actual port-binding to keep the test hermetic.
- geeViz.eeAuth.tests.test_discovery_and_modes.test_ensure_started_idempotent()[source]¶
Calling ensure_started twice is safe; second returns same state.
- geeViz.eeAuth.tests.test_discovery_and_modes.test_map_view_reads_GEEVIZ_EEAUTH_MODE_env()[source]¶
Map.view() must consult GEEVIZ_EEAUTH_MODE to pick auto/proxy/legacy.
- geeViz.eeAuth.tests.test_discovery_and_modes.test_map_view_calls_ensure_started_in_auto_mode()[source]¶
Default mode is ‘auto’; Map.view() must call ensure_started so discovery + proxy auto-start happens.
- geeViz.eeAuth.tests.test_discovery_and_modes.test_map_view_legacy_emits_deprecation_warning()[source]¶
When the legacy path runs, a DeprecationWarning must be emitted.
- geeViz.eeAuth.tests.test_discovery_and_modes.test_map_view_mode_proxy_propagates_runtime_error()[source]¶
When GEEVIZ_EEAUTH_MODE=proxy and proxy can’t start, Map.view() must surface the error rather than silently falling back.
- geeViz.eeAuth.tests.test_discovery_and_modes.test_mixed_refresh_and_sa_credentials_coexist()[source]¶
A registry can hold a personal-account refresh token AND a service account at the same time. Switching between them works.
- geeViz.eeAuth.tests.test_discovery_and_modes.test_oauth_default_project_from_quota_project_id()[source]¶
Authorized_user files don’t have project_id, but they often have quota_project_id. addCreds(…) should pick it up.
- geeViz.eeAuth.tests.test_discovery_and_modes.test_oauth_with_explicit_project_override()[source]¶
User-supplied project argument wins over what’s in the JSON.
- geeViz.eeAuth.tests.test_discovery_and_modes.test_initialize_via_proxy_uses_anonymous_creds()[source]¶
The Python SDK init path must use AnonymousCredentials so the SDK doesn’t try to attach its own token — the proxy supplies the real one. (Source-of-truth check on the library.)
Source-of-truth: both code paths point at the same eeCreds proxy_url, ensuring ‘Python SDK + Map viewer’ parity.
- geeViz.eeAuth.tests.test_discovery_and_modes.test_proxy_mode_url_registers_upstream_and_keeps_url_clean()[source]¶
When proxy is active,
Map.view()must: - register the upstream proxy URL with the local HTTP server via_set_ee_api_upstreamso it can reverse-proxy /ee-api/*default to an EMPTY query string — the JS viewer’s same-origin
window.location.origin + "/ee-api"default does the workinclude
?tenant=only when there are multiple registered credentials AND the active one isn’t the default (first registered)never include accessToken / accessTokenCreationTime / projectID — the proxy injects auth + x-goog-user-project server-side
- geeViz.eeAuth.tests.test_discovery_and_modes.test_legacy_mode_url_still_uses_accessToken_template()[source]¶
When proxy is unavailable, the URL must use the legacy format with the minted token baked in.
- geeViz.eeAuth.tests.test_discovery_and_modes.test_geeView_robustInitializer_is_thin_pointer_to_eeAuth()[source]¶
geeView.robustInitializermust be a thin delegate togeeViz.eeAuth.robust_init; the real flow lives in eeAuth so it’s usable from any entry point, not just module-import.
- geeViz.eeAuth.tests.test_discovery_and_modes.test_robust_init_tries_eeAuth_proxy_first()[source]¶
eeAuth.robust_init must attempt the proxy via
ensure_startedbefore any other path.
- geeViz.eeAuth.tests.test_discovery_and_modes.test_robust_init_respects_legacy_env()[source]¶
GEEVIZ_EEAUTH_MODE=legacymust skip the proxy attempt.
- geeViz.eeAuth.tests.test_discovery_and_modes.test_robust_init_calls_ee_initialize_with_no_project()[source]¶
The defining behavior of the simple flow: call
ee.Initialize()with NO project arg and let EE’s own resolution chain (credentials.quota_project_id → ADC → env vars) pick the project. No project prompts, no manual ADC fallback handling.
- geeViz.eeAuth.tests.test_discovery_and_modes.test_robust_init_falls_back_to_authenticate_force_true_localhost()[source]¶
When ee.Initialize() fails, the interactive fallback must run
ee.Authenticate(force=True, auth_mode='localhost')— that’s the combination that reliably works for desktop dev.
- geeViz.eeAuth.tests.test_discovery_and_modes.test_robust_init_uses_colab_auth_mode_in_colab()[source]¶
In Colab,
auth_mode='localhost'opens a loopback server on the Colab VM that the user’s browser can’t reach — the flow hangs. The interactive fallback must detect Colab (google.colabinsys.modules) and switch toauth_mode='colab'(silent google.colab.auth flow).
- geeViz.eeAuth.tests.test_discovery_and_modes.test_robust_init_retries_project_prompt_on_failure()[source]¶
When the automatic resolution chain fails and the interactive fallback has to prompt for a project id, a typo or an inaccessible-project rejection must not be instantly fatal — a common Colab failure is entering a project the wrong signed-in account can’t reach. The loop gives the user multiple attempts and only raises when they’re exhausted (or when there is no stdin to read from).
- geeViz.eeAuth.tests.test_discovery_and_modes.test_robust_init_invalidates_stale_cached_project()[source]¶
When step 3a’s cached project no longer works (deleted, access revoked, wrong Google account), the cache must be wiped so the interactive fallback offers a fresh prompt instead of a future run silently retrying the same broken value on every import.
- geeViz.eeAuth.tests.test_discovery_and_modes.test_robust_init_defaults_to_auto_mode_in_colab()[source]¶
The detached proxy runs in a separate process, so the project id resolved by the step-4 interactive prompt only reaches THIS process’s tenant registry. Every
/ee-apirequest would then be signed withoutx-goog-user-projectand EE would return 403. In Colab, default toautomode so the proxy shares this process’s registry andsync_oauth_projectafter the prompt actually takes effect. Users can still opt into detached explicitly viaGEEVIZ_EEAUTH_MODE=detached.
- geeViz.eeAuth.tests.test_discovery_and_modes.test_robust_init_verifies_with_getinfo_call()[source]¶
robust_init must verify each init attempt with a real EE call before declaring success — a silently misconfigured proxy or stale project would otherwise break the next user operation.
- geeViz.eeAuth.tests.test_discovery_and_modes.test_robust_init_syncs_oauth_project_after_init()[source]¶
After
ee.Initialize()succeeds (either auto or after auth), OAuth entries must be synced to the working project — otherwise a subsequentMap.view()would route through the proxy with the stale guess.
- geeViz.eeAuth.tests.test_discovery_and_modes.test_robust_init_propagates_proxy_mode_errors()[source]¶
mode=’proxy’ is explicit — if the user demanded the proxy and it can’t start, surface the error rather than falling through.
- geeViz.eeAuth.tests.test_discovery_and_modes.test_robust_init_final_error_lists_concrete_remedies()[source]¶
If init fails even after
ee.Authenticate(force=True), the error must tell the user exactly which commands to run instead of a generic ‘auth failed’ message.
- geeViz.eeAuth.tests.test_discovery_and_modes.test_diagnose_ee_credentials_reads_refresh_token_and_project()[source]¶
_diagnose_ee_credentialsmust surface what’s in the EE credentials JSON without interpreting it — refresh_token presence and theprojectfield both go through verbatim.
- geeViz.eeAuth.tests.test_discovery_and_modes.test_diagnose_reports_missing_refresh_token()[source]¶
When the EE credentials file has no refresh_token field (user deleted it), the diagnosis must say so — that’s the signal robust_init uses to NOT silently fall through to ADC.
- geeViz.eeAuth.tests.test_discovery_and_modes.test_save_ee_project_to_credentials_file_roundtrip()[source]¶
_save_ee_project_to_credentials_filemust write to the EE credentials JSON’sprojectfield — same placeearthengine set_projectwrites — so next session reads it back.
- geeViz.eeAuth.tests.test_discovery_and_modes.test_robust_init_returns_already_initialized_when_ee_works()[source]¶
Fast path: when
ee.Number(1).getInfo()already succeeds, robust_init must NOT touch anything else.
- geeViz.eeAuth.tests.test_discovery_and_modes.test_robust_init_uses_ee_initialize_auto_resolution()[source]¶
When the fast-path and the proxy both fail, robust_init must call plain
ee.Initialize()(no project arg) and let EE pick the project from credentials’ quota_project_id / ADC. If that succeeds, return source=’ee-auto-init’ with the project EE picked.