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).
This commit is contained in:
2026-08-29 16:47:17 -05:00
parent 5bbb307b12
commit 5b95ac3c7e
2 changed files with 36 additions and 4 deletions
+9 -4
View File
@@ -117,11 +117,16 @@ func New(baseURL, apiKey, apiUsername string) (*Client, error) {
}, nil }, nil
} }
// NewFromEnv builds a client from DISCOURSE_URL, DISCOURSE_API_KEY and // NewFromEnv builds a client from DISCOURSE_URL, the API key
// DISCOURSE_API_USERNAME (default "system"). The intended source is a // (DISCOURSE_API_KEY, falling back to the shorter DISCOURSE_KEY alias)
// 0600 env file (see env.example), sourced before the process starts. // 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) { 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. // String renders the client for logs: base url + acting username only.
+27
View File
@@ -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) { func TestCurrentUser(t *testing.T) {
f := newFake(t) f := newFake(t)
c := testClient(t, f) c := testClient(t, f)