Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 21 additions & 14 deletions jira/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type Client struct {
baseURL string
username string
token string
domainName string
httpClient *http.Client
}

Expand All @@ -33,23 +34,23 @@ type JiraIssue struct {
Fields map[string]any `json:"fields"`
}

// NewClient creates a new JIRA client with the provided authentication token
func NewClient(baseURL, username, token string) (*Client, error) {
if token == "" {
return nil, errors.New("JIRA token is required")
}

// NewClient creates a new JIRA client with the provided authentication token.
// The token can be empty to support lazy initialization - validation occurs when making API calls.
func NewClient(baseURL, username, token, domainName string) (*Client, error) {
return &Client{
baseURL: strings.TrimSuffix(baseURL, "/"),
username: username,
token: token,
baseURL: strings.TrimSuffix(baseURL, "/"),
username: username,
token: token,
domainName: domainName,
httpClient: &http.Client{
Timeout: 30 * time.Second,
},
}, nil
}

// NewClientFromDomain creates a JIRA client using domain configuration and environment variables
// NewClientFromDomain creates a JIRA client using domain configuration and environment variables.
// The JIRA_TOKEN_{DOMAIN} environment variable is optional to support lazy initialization.
// Token validation occurs when making API calls via GetIssue().
func NewClientFromDomain(dom fdomain.Domain) (*Client, error) {
jiraConfig, err := config.LoadJiraConfig(dom)
if err != nil {
Expand All @@ -58,11 +59,8 @@ func NewClientFromDomain(dom fdomain.Domain) (*Client, error) {

domainNameUpper := strings.ToUpper(dom.Key())
token := os.Getenv("JIRA_TOKEN_" + domainNameUpper)
if token == "" {
return nil, fmt.Errorf("JIRA_TOKEN_%s environment variable is required", domainNameUpper)
}

client, err := NewClient(jiraConfig.Connection.BaseURL, jiraConfig.Connection.Username, token)
client, err := NewClient(jiraConfig.Connection.BaseURL, jiraConfig.Connection.Username, token, domainNameUpper)
if err != nil {
return nil, fmt.Errorf("failed to create JIRA client: %w", err)
}
Expand All @@ -75,6 +73,15 @@ var jiraIssueKeyPattern = regexp.MustCompile(`^[A-Z][A-Z0-9]*-[1-9][0-9]*$`)

// GetIssue fetches a JIRA issue by key. If fields is empty, Jira returns all default fields.
func (c *Client) GetIssue(issueKey string, fields []string) (*JiraIssue, error) {
// Validate token is set before making API call
if c.token == "" {
if c.domainName != "" {
return nil, fmt.Errorf("JIRA token is required but not set. Please set JIRA_TOKEN_%s environment variable", c.domainName)
}

return nil, errors.New("JIRA token is required but not set")
}

// Validate JIRA issue key format
if issueKey == "" {
return nil, errors.New("issue key cannot be empty")
Expand Down
61 changes: 52 additions & 9 deletions jira/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ func TestNewClient(t *testing.T) {
expectError: false,
},
{
name: "empty token should fail",
name: "empty token allowed for lazy initialization",
baseURL: "https://example.atlassian.net",
username: "[email protected]",
token: "",
expectError: true,
expectError: false,
},
{
name: "baseURL with trailing slash should be trimmed",
Expand All @@ -50,7 +50,7 @@ func TestNewClient(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
client, err := NewClient(tt.baseURL, tt.username, tt.token)
client, err := NewClient(tt.baseURL, tt.username, tt.token, "")

if tt.expectError {
require.Error(t, err)
Expand Down Expand Up @@ -156,7 +156,7 @@ func TestClient_GetIssue(t *testing.T) {
defer server.Close()

// Create client
client, err := NewClient(server.URL, "testuser", "testtoken")
client, err := NewClient(server.URL, "testuser", "testtoken", "")
require.NoError(t, err)

tests := []struct {
Expand Down Expand Up @@ -210,7 +210,27 @@ func TestClient_GetIssue_ErrorCases(t *testing.T) {
serverResponse func(w http.ResponseWriter, r *http.Request)
expectError bool
errorContains string
emptyToken bool
domainName string
}{
{
name: "empty token without domain name",
issueKey: "TEST-123",
fields: nil,
expectError: true,
errorContains: "JIRA token is required but not set",
emptyToken: true,
domainName: "",
},
{
name: "empty token with domain name",
issueKey: "TEST-123",
fields: nil,
expectError: true,
errorContains: "Please set JIRA_TOKEN_TESTDOMAIN environment variable",
emptyToken: true,
domainName: "TESTDOMAIN",
},
{
name: "empty issue key",
issueKey: "",
Expand Down Expand Up @@ -288,13 +308,21 @@ func TestClient_GetIssue_ErrorCases(t *testing.T) {

// For validation tests, we don't need a server
if tt.serverResponse == nil {
client, err = NewClient("https://example.com", "testuser", "testtoken")
token := "testtoken"
if tt.emptyToken {
token = ""
}
client, err = NewClient("https://example.com", "testuser", token, tt.domainName)
require.NoError(t, err)
} else {
server := httptest.NewServer(http.HandlerFunc(tt.serverResponse))
defer server.Close()

client, err = NewClient(server.URL, "testuser", "testtoken")
token := "testtoken"
if tt.emptyToken {
token = ""
}
client, err = NewClient(server.URL, "testuser", token, tt.domainName)
require.NoError(t, err)
}

Expand Down Expand Up @@ -353,7 +381,7 @@ func TestNewClientFromDomain(t *testing.T) { //nolint:paralleltest // Cannot use
},
},
{
name: "missing JIRA token",
name: "missing JIRA token allowed for lazy initialization",
setupDomain: func(t *testing.T) fdomain.Domain {
t.Helper()
dom := setupTestDomain(t)
Expand All @@ -365,8 +393,23 @@ func TestNewClientFromDomain(t *testing.T) { //nolint:paralleltest // Cannot use
t.Helper()
// Don't set the token
},
expectError: true,
errorContains: "JIRA_TOKEN_EXEMPLAR environment variable is required",
expectError: false,
validate: func(client *Client) error {
if client.baseURL != "https://example.atlassian.net" {
return fmt.Errorf("expected baseURL 'https://example.atlassian.net', got '%s'", client.baseURL)
}
if client.username != "testuser" {
return fmt.Errorf("expected username 'testuser', got '%s'", client.username)
}
if client.token != "" {
return fmt.Errorf("expected empty token, got '%s'", client.token)
}
if client.domainName != "EXEMPLAR" {
return fmt.Errorf("expected domainName 'EXEMPLAR', got '%s'", client.domainName)
}

return nil
},
},
{
name: "missing config file",
Expand Down
2 changes: 1 addition & 1 deletion jira/converter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ func TestJiraToStruct_ErrorCases(t *testing.T) {
os.Unsetenv("JIRA_TOKEN_EXEMPLAR")
},
expectError: true,
errorContains: "JIRA_TOKEN_EXEMPLAR environment variable is required",
errorContains: "Please set JIRA_TOKEN_EXEMPLAR environment variable",
},
{
name: "missing config file",
Expand Down