From efef463e12e61662de5a39141e48c80fa0414a5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonatan=20M=C3=A4nnchen?= Date: Tue, 16 Jun 2026 18:57:16 +0200 Subject: [PATCH] Apply optional token_refresh_failed fallback in with_api/4 When resolve_api_auth/2 fails with token_refresh_failed and optional is true, fall back to executing the request without credentials, mirroring the existing handling in with_repo/3. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/hex_cli_auth.erl | 3 +++ test/hex_cli_auth_SUITE.erl | 23 +++++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/src/hex_cli_auth.erl b/src/hex_cli_auth.erl index 5c55e8f7..7c2c1016 100644 --- a/src/hex_cli_auth.erl +++ b/src/hex_cli_auth.erl @@ -234,6 +234,9 @@ with_api(Permission, BaseConfig, Fun, Opts) -> {error, no_auth} -> %% auth_inline is false, just return error {error, {auth_error, no_credentials}}; + {error, {auth_error, token_refresh_failed}} when Optional =:= true -> + %% Token refresh failed but auth is optional, fall back to no credentials + execute_optional_with_retry(BaseConfig, Fun, Opts); {error, _} = Error -> Error end. diff --git a/test/hex_cli_auth_SUITE.erl b/test/hex_cli_auth_SUITE.erl index eef610bf..dc2d61d9 100644 --- a/test/hex_cli_auth_SUITE.erl +++ b/test/hex_cli_auth_SUITE.erl @@ -60,6 +60,7 @@ all() -> %% with_api tests - wrapper behavior with_api_optional_test, + with_api_optional_token_refresh_failed_test, with_api_auth_inline_test, with_api_device_auth_test, @@ -731,6 +732,28 @@ with_api_optional_test(_Config) -> ?assertEqual(undefined, Result), ok. +with_api_optional_token_refresh_failed_test(_Config) -> + %% When resolve_api_auth fails with token_refresh_failed and optional is true, + %% fall back to executing the request without credentials. + Now = erlang:system_time(second), + Config = config_with_callbacks(#{ + oauth_tokens => + {ok, #{ + access_token => <<"expired_token">>, + %% Expired with no refresh_token => token_refresh_failed immediately + expires_at => Now - 100 + }} + }), + + Result = hex_cli_auth:with_api( + read, + Config, + fun(Cfg) -> maps:get(api_key, Cfg, undefined) end, + [{optional, true}] + ), + ?assertEqual(undefined, Result), + ok. + with_api_auth_inline_test(_Config) -> %% Test auth_inline => false returns error instead of prompting Config = config_with_callbacks(#{oauth_tokens => error}),