Repository: andygrunwald/go-jira
Pull request #585 (2022-10-19 19:00:38)

Cloud/User: Update User struct
Thanks goes to @t-junjie for his early work in
https://github.com/andygrunwald/go-jira/pull/412

Fix #412


Patch:
diff --git a/cloud/examples/basic_auth/main.go b/cloud/examples/basic_auth/main.go
index ab37284..bafa6a0 100644
--- a/cloud/examples/basic_auth/main.go
+++ b/cloud/examples/basic_auth/main.go
@@ -21,7 +21,7 @@ func main() {
 		panic(err)
 	}
 
-	u, _, err := client.User.GetSelf(context.Background())
+	u, _, err := client.User.GetCurrentUser(context.Background())
 	if err != nil {
 		panic(err)
 	}
diff --git a/cloud/user.go b/cloud/user.go
index 8fa8536..3453a72 100644
--- a/cloud/user.go
+++ b/cloud/user.go
@@ -14,19 +14,20 @@ type UserService service
 
 // User represents a Jira user.
 type User struct {
-	Self            string     `json:"self,omitempty" structs:"self,omitempty"`
-	AccountID       string     `json:"accountId,omitempty" structs:"accountId,omitempty"`
-	AccountType     string     `json:"accountType,omitempty" structs:"accountType,omitempty"`
-	Name            string     `json:"name,omitempty" structs:"name,omitempty"`
-	Key             string     `json:"key,omitempty" structs:"key,omitempty"`
-	Password        string     `json:"-"`
-	EmailAddress    string     `json:"emailAddress,omitempty" structs:"emailAddress,omitempty"`
-	AvatarUrls      AvatarUrls `json:"avatarUrls,omitempty" structs:"avatarUrls,omitempty"`
-	DisplayName     string     `json:"displayName,omitempty" structs:"displayName,omitempty"`
-	Active          bool       `json:"active,omitempty" structs:"active,omitempty"`
-	TimeZone        string     `json:"timeZone,omitempty" structs:"timeZone,omitempty"`
-	Locale          string     `json:"locale,omitempty" structs:"locale,omitempty"`
-	ApplicationKeys []string   `json:"applicationKeys,omitempty" structs:"applicationKeys,omitempty"`
+	Self             string           `json:"self,omitempty" structs:"self,omitempty"`
+	AccountID        string           `json:"accountId,omitempty" structs:"accountId,omitempty"`
+	AccountType      string           `json:"accountType,omitempty" structs:"accountType,omitempty"`
+	Name             string           `json:"name,omitempty" structs:"name,omitempty"`
+	Key              string           `json:"key,omitempty" structs:"key,omitempty"`
+	Password         string           `json:"-"`
+	EmailAddress     string           `json:"emailAddress,omitempty" structs:"emailAddress,omitempty"`
+	AvatarUrls       AvatarUrls       `json:"avatarUrls,omitempty" structs:"avatarUrls,omitempty"`
+	DisplayName      string           `json:"displayName,omitempty" structs:"displayName,omitempty"`
+	Active           bool             `json:"active,omitempty" structs:"active,omitempty"`
+	TimeZone         string           `json:"timeZone,omitempty" structs:"timeZone,omitempty"`
+	Locale           string           `json:"locale,omitempty" structs:"locale,omitempty"`
+	Groups           UserGroups       `json:"groups,omitempty" structs:"groups,omitempty"`
+	ApplicationRoles ApplicationRoles `json:"applicationRoles,omitempty" structs:"applicationRoles,omitempty"`
 }
 
 // UserGroup represents the group list
@@ -35,6 +36,37 @@ type UserGroup struct {
 	Name string `json:"name,omitempty" structs:"name,omitempty"`
 }
 
+// Groups is a wrapper for UserGroup
+type UserGroups struct {
+	Size  int         `json:"size,omitempty" structs:"size,omitempty"`
+	Items []UserGroup `json:"items,omitempty" structs:"items,omitempty"`
+}
+
+// ApplicationRoles is a wrapper for ApplicationRole
+type ApplicationRoles struct {
+	Size  int               `json:"size,omitempty" structs:"size,omitempty"`
+	Items []ApplicationRole `json:"items,omitempty" structs:"items,omitempty"`
+}
+
+// ApplicationRole represents a role assigned to a user
+type ApplicationRole struct {
+	Key                  string   `json:"key"`
+	Groups               []string `json:"groups"`
+	Name                 string   `json:"name"`
+	DefaultGroups        []string `json:"defaultGroups"`
+	SelectedByDefault    bool     `json:"selectedByDefault"`
+	Defined              bool     `json:"defined"`
+	NumberOfSeats        int      `json:"numberOfSeats"`
+	RemainingSeats       int      `json:"remainingSeats"`
+	UserCount            int      `json:"userCount"`
+	UserCountDescription string   `json:"userCountDescription"`
+	HasUnlimitedSeats    bool     `json:"hasUnlimitedSeats"`
+	Platform             bool     `json:"platform"`
+
+	// Key `groupDetails` missing - https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-application-roles/#api-rest-api-3-applicationrole-key-get
+	// Key `defaultGroupsDetails` missing - https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-application-roles/#api-rest-api-3-applicationrole-key-get
+}
+
 type userSearchParam struct {
 	name  string
 	value string
@@ -158,23 +190,22 @@ func (s *UserService) GetGroups(ctx context.Context, accountId string) (*[]UserG
 	return userGroups, resp, nil
 }
 
-// GetSelf information about the current logged-in user
-//
-// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-myself-get
+// GetCurrentUser returns details for the current user.
 //
-// TODO Double check this method if this works as expected, is using the latest API and the response is complete
-// This double check effort is done for v2 - Remove this two lines if this is completed.
-func (s *UserService) GetSelf(ctx context.Context) (*User, *Response, error) {
-	const apiEndpoint = "rest/api/2/myself"
+// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-myself/#api-rest-api-3-myself-get
+func (s *UserService) GetCurrentUser(ctx context.Context) (*User, *Response, error) {
+	const apiEndpoint = "rest/api/3/myself"
 	req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil)
 	if err != nil {
 		return nil, nil, err
 	}
+
 	var user User
 	resp, err := s.client.Do(req, &user)
 	if err != nil {
 		return nil, resp, NewJiraError(resp, err)
 	}
+
 	return &user, resp, nil
 }
 


Test patch:
diff --git a/cloud/user_test.go b/cloud/user_test.go
index 192688e..7e5fdb2 100644
--- a/cloud/user_test.go
+++ b/cloud/user_test.go
@@ -61,16 +61,89 @@ func TestUserService_Create(t *testing.T) {
 		testRequestURL(t, r, "/rest/api/2/user")
 
 		w.WriteHeader(http.StatusCreated)
-		fmt.Fprint(w, `{"name":"charlie","password":"abracadabra","emailAddress":"charlie@atlassian.com",
-        "displayName":"Charlie of Atlassian","applicationKeys":["jira-core"]}`)
+		fmt.Fprint(w, `
+		{
+			"name": "charlie",
+			"password": "abracadabra",
+			"emailAddress": "charlie@atlassian.com",
+			"displayName": "Charlie of Atlassian",
+			"applicationRoles": {
+				"size": 1,
+				"max-results": 1,
+				"items": [{
+					"key": "jira-software",
+					"groups": [
+						"jira-software-users",
+						"jira-testers"
+					],
+					"name": "Jira Software",
+					"defaultGroups": [
+						"jira-software-users"
+					],
+					"selectedByDefault": false,
+					"defined": false,
+					"numberOfSeats": 10,
+					"remainingSeats": 5,
+					"userCount": 5,
+					"userCountDescription": "5 developers",
+					"hasUnlimitedSeats": false,
+					"platform": false
+				}]
+			},
+			"groups": {
+				"size": 2,
+				"max-results": 2,
+				"items": [{
+						"name": "jira-core",
+						"self": "jira-core"
+					},
+					{
+						"name": "jira-test",
+						"self": "jira-test"
+					}
+				]
+			}
+		}
+		`)
 	})
 
 	u := &User{
-		Name:            "charlie",
-		Password:        "abracadabra",
-		EmailAddress:    "charlie@atlassian.com",
-		DisplayName:     "Charlie of Atlassian",
-		ApplicationKeys: []string{"jira-core"},
+		Name:         "charlie",
+		Password:     "abracadabra",
+		EmailAddress: "charlie@atlassian.com",
+		DisplayName:  "Charlie of Atlassian",
+		Groups: UserGroups{
+			Size: 2,
+			Items: []UserGroup{
+				{
+					Name: "jira-core",
+					Self: "jira-core",
+				},
+				{
+					Name: "jira-test",
+					Self: "jira-test",
+				},
+			},
+		},
+		ApplicationRoles: ApplicationRoles{
+			Size: 1,
+			Items: []ApplicationRole{
+				{
+					Key:                  "jira-software",
+					Groups:               []string{"jira-software-users", "jira-testers"},
+					Name:                 "Jira Software",
+					DefaultGroups:        []string{"jira-software-users"},
+					SelectedByDefault:    false,
+					Defined:              false,
+					NumberOfSeats:        10,
+					RemainingSeats:       5,
+					UserCount:            5,
+					UserCountDescription: "5 developers",
+					HasUnlimitedSeats:    false,
+					Platform:             false,
+				},
+			},
+		},
 	}
 
 	if user, _, err := testClient.User.Create(context.Background(), u); err != nil {
@@ -118,30 +191,48 @@ func TestUserService_GetGroups(t *testing.T) {
 	}
 }
 
-func TestUserService_GetSelf(t *testing.T) {
+func TestUserService_GetCurrentUser(t *testing.T) {
 	setup()
 	defer teardown()
-	testMux.HandleFunc("/rest/api/2/myself", func(w http.ResponseWriter, r *http.Request) {
+	testMux.HandleFunc("/rest/api/3/myself", func(w http.ResponseWriter, r *http.Request) {
 		testMethod(t, r, http.MethodGet)
-		testRequestURL(t, r, "/rest/api/2/myself")
+		testRequestURL(t, r, "/rest/api/3/myself")
 
 		w.WriteHeader(http.StatusCreated)
-		fmt.Fprint(w, `{"self":"http://www.example.com/jira/rest/api/2/user?accountId=000000000000000000000000","key":"fred",
-        "name":"fred","emailAddress":"fred@example.com","avatarUrls":{"48x48":"http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred",
-        "24x24":"http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred","16x16":"http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred",
-        "32x32":"http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred"},"displayName":"Fred F. User","active":true,"timeZone":"Australia/Sydney","groups":{"size":3,"items":[
-        {"name":"jira-user","self":"http://www.example.com/jira/rest/api/2/group?groupname=jira-user"},{"name":"jira-admin",
-        "self":"http://www.example.com/jira/rest/api/2/group?groupname=jira-admin"},{"name":"important","self":"http://www.example.com/jira/rest/api/2/group?groupname=important"
-        }]},"applicationRoles":{"size":1,"items":[]},"expand":"groups,applicationRoles"}`)
+		fmt.Fprint(w, `{
+			"self": "https://your-domain.atlassian.net/rest/api/3/user?accountId=5b10a2844c20165700ede21g",
+			"key": "",
+			"accountId": "5b10a2844c20165700ede21g",
+			"accountType": "atlassian",
+			"name": "",
+			"emailAddress": "mia@example.com",
+			"avatarUrls": {
+			  "48x48": "https://avatar-management--avatars.server-location.prod.public.atl-paas.net/initials/MK-5.png?size=48&s=48",
+			  "24x24": "https://avatar-management--avatars.server-location.prod.public.atl-paas.net/initials/MK-5.png?size=24&s=24",
+			  "16x16": "https://avatar-management--avatars.server-location.prod.public.atl-paas.net/initials/MK-5.png?size=16&s=16",
+			  "32x32": "https://avatar-management--avatars.server-location.prod.public.atl-paas.net/initials/MK-5.png?size=32&s=32"
+			},
+			"displayName": "Mia Krystof",
+			"active": true,
+			"timeZone": "Australia/Sydney",
+			"groups": {
+			  "size": 3,
+			  "items": []
+			},
+			"applicationRoles": {
+			  "size": 1,
+			  "items": []
+			}
+		  }`)
 	})
 
-	if user, _, err := testClient.User.GetSelf(context.Background()); err != nil {
+	if user, _, err := testClient.User.GetCurrentUser(context.Background()); err != nil {
 		t.Errorf("Error given: %s", err)
+
 	} else if user == nil {
 		t.Error("Expected user groups. []UserGroup is nil")
-	} else if user.Name != "fred" ||
-		!user.Active ||
-		user.DisplayName != "Fred F. User" {
+
+	} else if user.EmailAddress != "mia@example.com" || !user.Active || user.DisplayName != "Mia Krystof" {
 		t.Errorf("User JSON deserialized incorrectly")
 	}
 }