From 5b95ac3c7ec9f362a2dd7e7aa2801e7d38afd4fc Mon Sep 17 00:00:00 2001 From: reachableceo Date: Sat, 29 Aug 2026 16:47:17 -0500 Subject: [PATCH] feat: accept DISCOURSE_KEY as env alias for the API key NewFromEnv now falls back to DISCOURSE_KEY when DISCOURSE_API_KEY is unset (the explicit name still wins), matching the shorter DISCOURSE_URL/DISCOURSE_KEY convention used across the MOPAC fleet scripts. Behavioral test proves both the alias and the precedence against the fake server. Part of Redmine 507 (Discourse Go client). --- discourse.go | 13 +++++++++---- discourse_test.go | 27 +++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/discourse.go b/discourse.go index 9be1d6a..23f07ea 100644 --- a/discourse.go +++ b/discourse.go @@ -117,11 +117,16 @@ func New(baseURL, apiKey, apiUsername string) (*Client, error) { }, nil } -// NewFromEnv builds a client from DISCOURSE_URL, DISCOURSE_API_KEY and -// DISCOURSE_API_USERNAME (default "system"). The intended source is a -// 0600 env file (see env.example), sourced before the process starts. +// NewFromEnv builds a client from DISCOURSE_URL, the API key +// (DISCOURSE_API_KEY, falling back to the shorter DISCOURSE_KEY alias) +// and DISCOURSE_API_USERNAME (default "system"). The intended source is +// a 0600 env file (see env.example), sourced before the process starts. func NewFromEnv() (*Client, error) { - return New(os.Getenv("DISCOURSE_URL"), os.Getenv("DISCOURSE_API_KEY"), os.Getenv("DISCOURSE_API_USERNAME")) + key := os.Getenv("DISCOURSE_API_KEY") + if key == "" { + key = os.Getenv("DISCOURSE_KEY") + } + return New(os.Getenv("DISCOURSE_URL"), key, os.Getenv("DISCOURSE_API_USERNAME")) } // String renders the client for logs: base url + acting username only. diff --git a/discourse_test.go b/discourse_test.go index 221e341..e33b207 100644 --- a/discourse_test.go +++ b/discourse_test.go @@ -341,6 +341,33 @@ func TestNewRejectsBadInput(t *testing.T) { } } +func TestNewFromEnvKeyAlias(t *testing.T) { + f := newFake(t) + // alias alone works: DISCOURSE_KEY is accepted for the API key + t.Setenv("DISCOURSE_URL", f.srv.URL) + t.Setenv("DISCOURSE_API_USERNAME", "system") + t.Setenv("DISCOURSE_API_KEY", "") + t.Setenv("DISCOURSE_KEY", testKey) + c, err := NewFromEnv() + if err != nil { + t.Fatalf("NewFromEnv with DISCOURSE_KEY: %v", err) + } + if _, err := c.CurrentUser(context.Background()); err != nil { + t.Fatalf("alias key must authenticate against the fake: %v", err) + } + // explicit DISCOURSE_API_KEY wins over the alias (fake 403s any other key) + t.Setenv("DISCOURSE_API_KEY", "not-the-key") + t.Setenv("DISCOURSE_KEY", testKey) + c, err = NewFromEnv() + if err != nil { + t.Fatalf("NewFromEnv: %v", err) + } + _, err = c.CurrentUser(context.Background()) + if !errors.Is(err, ErrForbidden) { + t.Fatalf("primary key must take precedence over the alias, got %v", err) + } +} + func TestCurrentUser(t *testing.T) { f := newFake(t) c := testClient(t, f)