diff --git a/teamcity/group.go b/teamcity/group.go index 87d35d0..f2f637f 100644 --- a/teamcity/group.go +++ b/teamcity/group.go @@ -9,9 +9,17 @@ import ( // Group is the model for group entities in TeamCity type Group struct { - Key string `json:"key,omitempty" xml:"key"` - Description string `json:"description,omitempty" xml:"description"` - Name string `json:"name,omitempty" xml:"name"` + Key string `json:"key,omitempty" xml:"key"` + Description string `json:"description,omitempty" xml:"description"` + Name string `json:"name,omitempty" xml:"name"` + Roles *roleAssignmentsJSON `json:"roles,omitempty" xml:"roles"` + Properties *Properties `json:"properties,omitempty" xml:"properties"` +} + +// GroupList is the model for group list in TeamCity +type GroupList struct { + Count int `json:"count,omitempty" xml:"count"` + Items []Group `json:"group,omitempty" xml:"group"` } // NewGroup returns an instance of a Group. A non-empty Key and Name is required. @@ -62,9 +70,17 @@ func (s *GroupService) Create(group *Group) (*Group, error) { // GetByKey - Get a group by its group key func (s *GroupService) GetByKey(key string) (*Group, error) { + return s.getByLocator(LocatorKey(key)) +} + +// GetByName - Get a group by its group name +func (s *GroupService) GetByName(name string) (*Group, error) { + return s.getByLocator(LocatorName(name)) +} + +func (s *GroupService) getByLocator(locator Locator) (*Group, error) { var out Group - locator := LocatorKey(key).String() - err := s.restHelper.get(locator, &out, "group") + err := s.restHelper.get(locator.String(), &out, "group") if err != nil { return nil, err } @@ -78,3 +94,21 @@ func (s *GroupService) Delete(key string) error { err := s.restHelper.delete(locator, "group") return err } + +// List - List of groups in range [offset:limit) +func (s *GroupService) List(offset, limit int) (*GroupList, error) { + var out GroupList + err := s.restHelper.get("", &out, "group", buildQueryLocator( + LocatorStart(offset), + LocatorCount(limit), + )) + if err != nil { + return nil, err + } + return &out, nil +} + +// ListAll returns all groups +func (s *GroupService) ListAll() (*GroupList, error) { + return s.List(0, -1) +} diff --git a/teamcity/group_test.go b/teamcity/group_test.go index 38b76fa..8c3eb6e 100644 --- a/teamcity/group_test.go +++ b/teamcity/group_test.go @@ -1,6 +1,7 @@ package teamcity_test import ( + "fmt" "testing" "github.com/cvbarros/go-teamcity/teamcity" @@ -42,6 +43,24 @@ func TestGroup_GetByKey(t *testing.T) { assert.Equal(t, newGroup.Description, actual.Description) } +func TestGroup_GetByName(t *testing.T) { + newGroup, _ := teamcity.NewGroup("TESTGROUPKEY2", "Test Group Name 2", "Test Group Description 2") + client := setup() + client.Groups.Create(newGroup) + + actual, err := client.Groups.GetByName(newGroup.Name) + + require.NoError(t, err) + require.NotNil(t, actual) + require.NotEmpty(t, actual.Key) + + cleanUpGroup(t, client, actual.Key) + + assert.Equal(t, newGroup.Key, actual.Key) + assert.Equal(t, newGroup.Name, actual.Name) + assert.Equal(t, newGroup.Description, actual.Description) +} + func TestGroup_Delete(t *testing.T) { newGroup, _ := teamcity.NewGroup("TESTGROUPKEY", "Test Group Name", "Test Group Description") client := setup() @@ -58,6 +77,41 @@ func TestGroup_Delete(t *testing.T) { assert.Contains(t, err.Error(), "404") } +func TestGroup_List(t *testing.T) { + groups := []*teamcity.Group{} + client := setup() + + groupListBefore, err := client.Groups.ListAll() + require.NoError(t, err) + for i := 0; i < 5; i++ { + group, err := teamcity.NewGroup( + fmt.Sprint("TESTGROUPLIST", i), + fmt.Sprint("Test Group List ", i), + fmt.Sprint("Test Group Description List ", i), + ) + require.NoError(t, err) + groups = append(groups, group) + client.Groups.Create(group) + } + groupList, err := client.Groups.ListAll() + + require.NoError(t, err) + assert.Equal(t, groupListBefore.Count+5, groupList.Count) + + for _, group := range groupList.Items { + if group.Key == "ALL_USERS_GROUP" { + continue + } + _, err := client.Groups.GetByKey(group.Key) + require.NoError(t, err) + + cleanUpGroup(t, client, group.Key) + + _, err = client.Groups.GetByKey(group.Key) + require.Error(t, err) + } +} + func cleanUpGroup(t *testing.T, client *teamcity.Client, key string) { client.Groups.Delete(key) } diff --git a/teamcity/locator.go b/teamcity/locator.go index 6c1e61b..b4549f9 100644 --- a/teamcity/locator.go +++ b/teamcity/locator.go @@ -16,14 +16,19 @@ func LocatorID(id string) Locator { //LocatorIDInt creates a locator for a Project/BuildType by Id where the Id's an integer func LocatorIDInt(id int) Locator { - return Locator(url.QueryEscape("id:") + fmt.Sprintf("%d", id)) + return Locator(url.QueryEscape("id:") + fmt.Sprint(id)) } -//LocatorName creates a locator for Project/BuildType by Name +//LocatorName creates a locator for User/Project/BuildType by Name func LocatorName(name string) Locator { return Locator(url.QueryEscape("name:") + url.PathEscape(name)) } +//LocatorUsername creates a locator for User by Username +func LocatorUsername(name string) Locator { + return Locator(url.QueryEscape("username:") + url.PathEscape(name)) +} + //LocatorKey creates a locator for Group by Key func LocatorKey(key string) Locator { return Locator(url.QueryEscape("key:") + url.PathEscape(key)) @@ -34,6 +39,16 @@ func LocatorType(id string) Locator { return Locator(url.QueryEscape("type:") + id) } +//LocatorStart creates a locator to set offset +func LocatorStart(start int) Locator { + return Locator(url.QueryEscape("start:") + fmt.Sprint(start)) +} + +//LocatorCount creates a locator to set number of answers +func LocatorCount(count int) Locator { + return Locator(url.QueryEscape("count:") + fmt.Sprint(count)) +} + func (l Locator) String() string { return string(l) } diff --git a/teamcity/query_locator.go b/teamcity/query_locator.go new file mode 100644 index 0000000..60ffa73 --- /dev/null +++ b/teamcity/query_locator.go @@ -0,0 +1,17 @@ +package teamcity + +func buildQueryLocator(locators ...Locator) *queryStruct { + locatorQuery := "" + if len(locators) > 1 { + for _, locator := range locators[1:] { + locatorQuery += "," + locator.String() + } + } + if len(locators) >= 1 { + locatorQuery = locators[0].String() + locatorQuery + } + return &queryStruct{ + key: "locator", + value: locatorQuery, + } +} diff --git a/teamcity/query_locator_test.go b/teamcity/query_locator_test.go new file mode 100644 index 0000000..3040000 --- /dev/null +++ b/teamcity/query_locator_test.go @@ -0,0 +1,22 @@ +package teamcity + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestBuildQueryLocator_Empty(t *testing.T) { + q := buildQueryLocator() + require.Equal(t, "", q.value) +} + +func TestBuildQueryLocator_OneElement(t *testing.T) { + q := buildQueryLocator(LocatorStart(0)) + require.Equal(t, "locator=start%3A0", q.String()) +} + +func TestBuildQueryLocator_TwoElements(t *testing.T) { + q := buildQueryLocator(LocatorStart(0), LocatorCount(1)) + require.Equal(t, "locator=start%3A0,count%3A1", q.String()) +} diff --git a/teamcity/rest_helper.go b/teamcity/rest_helper.go index f098107..dd6ac86 100644 --- a/teamcity/rest_helper.go +++ b/teamcity/rest_helper.go @@ -20,6 +20,14 @@ type restHelper struct { sling *sling.Sling } +type queryStruct struct { + key, value string +} + +func (q *queryStruct) String() string { + return fmt.Sprintf("%s=%s", q.key, q.value) +} + func newRestHelper(httpClient *http.Client) *restHelper { return newRestHelperWithSling(httpClient, nil) } @@ -54,8 +62,13 @@ func (r *restHelper) getCustom(path string, out interface{}, resourceDescription return r.handleRestError(bodyBytes, response.StatusCode, "GET", resourceDescription) } -func (r *restHelper) get(path string, out interface{}, resourceDescription string) error { +func (r *restHelper) get(path string, out interface{}, resourceDescription string, query ...*queryStruct) error { request, _ := r.sling.New().Get(path).Request() + for _, q := range query { + if q != nil { + request.URL.Query().Set(q.key, q.value) + } + } response, err := r.httpClient.Do(request) if err != nil { return err diff --git a/teamcity/teamcity.go b/teamcity/teamcity.go index ca1002a..8d3812e 100644 --- a/teamcity/teamcity.go +++ b/teamcity/teamcity.go @@ -61,13 +61,15 @@ type Client struct { commonBase *sling.Sling - AgentPools *AgentPoolsService - Projects *ProjectService - BuildTypes *BuildTypeService - Server *ServerService - VcsRoots *VcsRootService - Groups *GroupService - RoleAssignments *RoleAssignmentService + AgentPools *AgentPoolsService + Projects *ProjectService + BuildTypes *BuildTypeService + Server *ServerService + VcsRoots *VcsRootService + Groups *GroupService + Users *UserService + UserGroupMemberShip *UserGroupMemberShipService + RoleAssignments *RoleAssignmentService } func NewClient(auth Auth, httpClient *http.Client) (*Client, error) { @@ -107,16 +109,18 @@ func newClientInstance(auth Auth, address string, httpClient *http.Client) (*Cli } return &Client{ - address: address, - HTTPClient: httpClient, - commonBase: sharedClient, - AgentPools: newAgentPoolsService(sharedClient.New(), httpClient), - Projects: newProjectService(sharedClient.New(), httpClient), - BuildTypes: newBuildTypeService(sharedClient.New(), httpClient), - Server: newServerService(sharedClient.New()), - VcsRoots: newVcsRootService(sharedClient.New(), httpClient), - Groups: newGroupService(sharedClient.New(), httpClient), - RoleAssignments: newRoleAssignmentService(sharedClient.New(), httpClient), + address: address, + HTTPClient: httpClient, + commonBase: sharedClient, + AgentPools: newAgentPoolsService(sharedClient.New(), httpClient), + Projects: newProjectService(sharedClient.New(), httpClient), + BuildTypes: newBuildTypeService(sharedClient.New(), httpClient), + Server: newServerService(sharedClient.New()), + VcsRoots: newVcsRootService(sharedClient.New(), httpClient), + Groups: newGroupService(sharedClient.New(), httpClient), + Users: newUserService(sharedClient.New(), httpClient), + UserGroupMemberShip: newUserGroupMembershipService(sharedClient.New(), httpClient), + RoleAssignments: newRoleAssignmentService(sharedClient.New(), httpClient), }, nil } diff --git a/teamcity/user.go b/teamcity/user.go new file mode 100644 index 0000000..abc4491 --- /dev/null +++ b/teamcity/user.go @@ -0,0 +1,161 @@ +package teamcity + +import ( + "fmt" + "net/http" + + "github.com/dghubble/sling" +) + +// User is the model for User entities in TeamCity +type User struct { + Username string `json:"username,omitempty" xml:"username"` + Name string `json:"name,omitempty" xml:"name"` + ID int `json:"id,omitempty" xml:"id"` + Email string `json:"email,omitempty" xml:"email"` + Properties *Properties `json:"properties,omitempty" xml:"properties"` + Roles *roleAssignmentsJSON `json:"roles,omitempty" xml:"roles"` +} + +// UserList contains list of users +type UserList struct { + Count int `json:"count,omitempty" xml:"count"` + Items []User `json:"user,omitempty" xml:"user"` +} + +// NewUser returns an instance of a User. A non-empty Username, Name and Email is required. +func NewUser(username string, name string, email string) (*User, error) { + if username == "" { + return nil, fmt.Errorf("Key is required") + } + + if name == "" { + return nil, fmt.Errorf("Name is required") + } + + if email == "" { + return nil, fmt.Errorf("Email is required") + } + + return &User{ + Username: username, + Name: name, + Email: email, + }, nil +} + +// UserService has operations for handling Users +type UserService struct { + sling *sling.Sling + httpClient *http.Client + restHelper *restHelper +} + +func newUserService(base *sling.Sling, httpClient *http.Client) *UserService { + sling := base.Path("users/") + return &UserService{ + httpClient: httpClient, + sling: sling, + restHelper: newRestHelperWithSling(httpClient, sling), + } +} + +// Create - Creates a new User +func (s *UserService) Create(user *User) (*User, error) { + var created User + err := s.restHelper.post("", user, &created, "User") + + if err != nil { + return nil, err + } + + return &created, nil +} + +// GetByID - Get a User by its User ID +func (s *UserService) GetByID(ID int) (*User, error) { + return s.getByLocator(LocatorID(fmt.Sprint(ID))) +} + +// GetByUsername - Get a User by its User Username +func (s *UserService) GetByUsername(username string) (*User, error) { + return s.getByLocator(LocatorUsername(username)) +} + +// GetByName - Get a User by its User Name +func (s *UserService) GetByName(name string) (*User, error) { + return s.getByLocator(LocatorName(name)) +} + +func (s *UserService) getByLocator(locator Locator) (*User, error) { + var out User + err := s.restHelper.get(locator.String(), &out, "User") + if err != nil { + return nil, err + } + + return &out, err +} + +// DeleteByID - Deletes a User by its User ID +func (s *UserService) DeleteByID(id int) error { + return s.deleteByLocator(LocatorID(fmt.Sprint(id))) +} + +// DeleteByName - Deletes a User by its User Name +func (s *UserService) DeleteByName(name string) error { + return s.deleteByLocator(LocatorName(name)) +} + +// DeleteByUsername - Deletes a User by its User Username +func (s *UserService) DeleteByUsername(username string) error { + return s.deleteByLocator(LocatorUsername(username)) +} + +func (s *UserService) deleteByLocator(locator Locator) error { + err := s.restHelper.delete(locator.String(), "User") + return err +} + +// List - Get list of users in range [offset:limit) +func (s *UserService) List(offset, limit int) (*UserList, error) { + var out UserList + err := s.restHelper.get("", &out, "Users", buildQueryLocator( + LocatorStart(offset), + LocatorCount(limit), + )) + if err != nil { + return nil, err + } + return &out, err +} + +// ListAll returns list of all users +func (s *UserService) ListAll() (*UserList, error) { + return s.List(0, -1) +} + +// GroupAddByID - Add User with userID to Group with groupKey +func (s *UserService) GroupAddByID(userID int, groupKey string) (*Group, error) { + return s.groupAddByKey(LocatorID(fmt.Sprint(userID)), groupKey) +} + +// GroupAddByUsername - Add User with username to Group with groupKey +func (s *UserService) GroupAddByUsername(username, groupKey string) (*Group, error) { + return s.groupAddByKey(LocatorUsername(username), groupKey) +} + +// GroupAddByName - Add User with name to Group with groupKey +func (s *UserService) GroupAddByName(userName, groupKey string) (*Group, error) { + return s.groupAddByKey(LocatorName(userName), groupKey) +} + +func (s *UserService) groupAddByKey(locator Locator, groupKey string) (*Group, error) { + var out Group + + err := s.restHelper.post(fmt.Sprintf("%s/groups", locator), Group{Key: groupKey}, &out, "User") + if err != nil { + return nil, err + } + return &out, nil +} diff --git a/teamcity/user_group_member_ship_test.go b/teamcity/user_group_member_ship_test.go new file mode 100644 index 0000000..8e9e12c --- /dev/null +++ b/teamcity/user_group_member_ship_test.go @@ -0,0 +1,97 @@ +package teamcity_test + +import ( + "testing" + + "github.com/cvbarros/go-teamcity/teamcity" + "github.com/stretchr/testify/require" +) + +func TestUserGroupMemberShip_IsGroupMember(t *testing.T) { + client := setup() + admin, err := client.Users.GetByUsername("admin") + require.NoError(t, err) + groupKey := "ALL_USERS_GROUP" + + isMember, err := client.UserGroupMemberShip.IsGroupMemberByID(admin.ID, groupKey) + require.NoError(t, err) + require.True(t, isMember) + + isMember, err = client.UserGroupMemberShip.IsGroupMemberByID(admin.ID, "INVALID_GROUP") + require.NoError(t, err) + require.False(t, isMember) + + isMember, err = client.UserGroupMemberShip.IsGroupMemberByUsername("INVALIDUSERNAME", groupKey) + require.NoError(t, err) + require.False(t, isMember) + +} + +func TestUserGropMemberShip_GetGroupMembers(t *testing.T) { + client := setup() + + newGroup, _ := teamcity.NewGroup("TESTGROUPMEMBER", "Test Group Member", "") + actualGroup, err := client.Groups.Create(newGroup) + require.NoError(t, err) + + newUser, _ := teamcity.NewUser("testusermember", "Test User Member", "test@member.com") + actualUser, err := client.Users.Create(newUser) + require.NoError(t, err) + + actualGroup, err = client.Users.GroupAddByID(actualUser.ID, actualGroup.Key) + require.NoError(t, err) + require.NotNil(t, actualGroup) + + memberList, err := client.UserGroupMemberShip.GetGroupMembersListAllByKey(actualGroup.Key) + require.NoError(t, err) + require.NotEmpty(t, memberList.Items) + require.Equal(t, memberList.Count, 1) + require.Equal(t, actualUser.ID, memberList.Items[0].ID) + + require.NoError(t, client.UserGroupMemberShip.GroupDeleteMemberByID(actualUser.ID, actualGroup.Key)) + + memberList, err = client.UserGroupMemberShip.GetGroupMembersListAllByKey(actualGroup.Key) + require.NoError(t, err) + require.Empty(t, memberList.Items) + require.Equal(t, memberList.Count, 0) + + cleanUpGroup(t, client, newGroup.Key) + cleanUpUser(t, client, actualUser.ID) + + require.NoError(t, err) + require.NotNil(t, actualGroup) +} +func TestUserGropMemberShip_GetUserGroups(t *testing.T) { + client := setup() + + newGroup, _ := teamcity.NewGroup("TESTGUSERGROUPS", "Test User groups", "") + actualGroup, err := client.Groups.Create(newGroup) + require.NoError(t, err) + + newUser, _ := teamcity.NewUser("testusergroups", "Test User Groups", "testgroups@member.com") + actualUser, err := client.Users.Create(newUser) + require.NoError(t, err) + + actualGroup, err = client.Users.GroupAddByID(actualUser.ID, actualGroup.Key) + require.NoError(t, err) + require.NotNil(t, actualGroup) + + groupsList, err := client.UserGroupMemberShip.GetUserGroupsListAllByID(actualUser.ID) + require.NoError(t, err) + require.NotEmpty(t, groupsList.Items) + // first is ALL_USERS_GROUP + require.Equal(t, groupsList.Count, 2) + require.Equal(t, actualGroup.Key, groupsList.Items[1].Key) + + require.NoError(t, client.UserGroupMemberShip.GroupDeleteMemberByID(actualUser.ID, actualGroup.Key)) + + groupsList, err = client.UserGroupMemberShip.GetUserGroupsListAllByID(actualUser.ID) + require.NoError(t, err) + require.Equal(t, groupsList.Count, 1) + + cleanUpGroup(t, client, newGroup.Key) + cleanUpUser(t, client, actualUser.ID) + + require.NoError(t, err) + require.NotNil(t, actualGroup) +} diff --git a/teamcity/user_group_membership.go b/teamcity/user_group_membership.go new file mode 100644 index 0000000..472438b --- /dev/null +++ b/teamcity/user_group_membership.go @@ -0,0 +1,156 @@ +package teamcity + +import ( + "fmt" + "net/http" + "strings" + + "github.com/dghubble/sling" +) + +// UserGroupMemberShipService has operations for handling UserGroupMemberships +type UserGroupMemberShipService struct { + // sling *sling.Sling + httpClient *http.Client + userRestHelper *restHelper + groupRestHelper *restHelper +} + +func newUserGroupMembershipService(base *sling.Sling, httpClient *http.Client) *UserGroupMemberShipService { + return &UserGroupMemberShipService{ + httpClient: httpClient, + // sling: sling, + userRestHelper: newRestHelperWithSling(httpClient, base.New().Path("users/")), + groupRestHelper: newRestHelperWithSling(httpClient, base.New().Path("userGroups/")), + } +} + +// GroupAddByID - Add User with UserID to Group with groupKey +func (s *UserGroupMemberShipService) GroupAddByID(userID int, groupKey string) (*Group, error) { + return s.groupAddByKey(LocatorID(fmt.Sprint(userID)), groupKey) +} + +// GroupAddByUsername - Add User with Username to Group with groupKey +func (s *UserGroupMemberShipService) GroupAddByUsername(username, groupKey string) (*Group, error) { + return s.groupAddByKey(LocatorUsername(username), groupKey) +} + +// GroupAddByName - Add User with name to Group with groupKey +func (s *UserGroupMemberShipService) GroupAddByName(name, groupKey string) (*Group, error) { + return s.groupAddByKey(LocatorName(name), groupKey) +} + +func (s *UserGroupMemberShipService) groupAddByKey(locator Locator, groupKey string) (*Group, error) { + var out Group + + err := s.userRestHelper.post(fmt.Sprintf("%s/groups", locator), Group{Key: groupKey}, &out, "UserGroupMemberShip") + if err != nil { + return nil, err + } + return &out, nil +} + +// GroupDeleteMemberByID - Delete User with UserID from the Group with groupKey +func (s *UserGroupMemberShipService) GroupDeleteMemberByID(userID int, groupKey string) error { + return s.groupDeleteMemberByKey(LocatorID(fmt.Sprint(userID)), groupKey) +} + +// GroupDeleteMemberByUsername - Delete User with username from the Group with groupKey +func (s *UserGroupMemberShipService) GroupDeleteMemberByUsername(username, groupKey string) error { + return s.groupDeleteMemberByKey(LocatorUsername(username), groupKey) +} + +// GroupDeleteMemberByName - Delete User with name from the Group with groupKey +func (s *UserGroupMemberShipService) GroupDeleteMemberByName(name, groupKey string) error { + return s.groupDeleteMemberByKey(LocatorName(name), groupKey) +} + +func (s *UserGroupMemberShipService) groupDeleteMemberByKey(locator Locator, groupKey string) error { + err := s.userRestHelper.delete(fmt.Sprintf("%s/groups/%s", locator, groupKey), "UserGroupMemberShip") + if err != nil { + return err + } + return nil +} + +// IsGroupMemberByID - checks the UserGroupMembership's group membership by ID +func (s *UserGroupMemberShipService) IsGroupMemberByID(id int, groupKey string) (bool, error) { + return s.isGroupMemberByLocator(LocatorIDInt(id), groupKey) +} + +// IsGroupMemberByUsername - checks the User's group membership by Username +func (s *UserGroupMemberShipService) IsGroupMemberByUsername(username, groupKey string) (bool, error) { + return s.isGroupMemberByLocator(LocatorUsername(username), groupKey) +} + +// IsGroupMemberByName - checks the User's group membership by Name +func (s *UserGroupMemberShipService) IsGroupMemberByName(name, key string) (bool, error) { + return s.isGroupMemberByLocator(LocatorName(name), key) +} + +func (s *UserGroupMemberShipService) isGroupMemberByLocator(locator Locator, groupKey string) (bool, error) { + var out Group + err := s.userRestHelper.get(fmt.Sprintf("%s/groups/%s", locator, LocatorKey(groupKey)), &out, "UserGroupMemberShip") + if err != nil { + strErr := err.Error() + if strings.Contains(strErr, "status code: 404") { + return false, nil + } + return false, err + } + return true, nil +} + +func (s *UserGroupMemberShipService) GetUserGroupsListByUsername(username string, offset, limit int) (*GroupList, error) { + return s.getUserGroupsList(LocatorUsername(username), offset, limit) +} + +func (s *UserGroupMemberShipService) GetUserGroupsListAllByUsername(username string) (*GroupList, error) { + return s.getUserGroupsList(LocatorUsername(username), 0, -1) +} + +func (s *UserGroupMemberShipService) GetUserGroupsListByID(id, offset, limit int) (*GroupList, error) { + return s.getUserGroupsList(LocatorIDInt(id), offset, limit) +} +func (s *UserGroupMemberShipService) GetUserGroupsListAllByID(id int) (*GroupList, error) { + return s.getUserGroupsList(LocatorIDInt(id), 0, -1) +} +func (s *UserGroupMemberShipService) getUserGroupsList(locator Locator, offset, limit int) (*GroupList, error) { + var out GroupList + err := s.userRestHelper.get(fmt.Sprintf("%s/groups/", locator), &out, "UserGroupMemberShip", + buildQueryLocator( + LocatorStart(offset), + LocatorCount(limit), + )) + if err != nil { + return nil, err + } + return &out, nil +} + +func (s *UserGroupMemberShipService) GetGroupMembersListByName(groupName string, offset, limit int) (*UserList, error) { + return s.getGroupMembersList(LocatorName(groupName), offset, limit) +} +func (s *UserGroupMemberShipService) GetGroupMembersListAllByName(groupName string) (*UserList, error) { + return s.getGroupMembersList(LocatorName(groupName), 0, -1) +} +func (s *UserGroupMemberShipService) GetGroupMembersListByKey(groupKey string, offset, limit int) (*UserList, error) { + return s.getGroupMembersList(LocatorKey(groupKey), offset, limit) +} +func (s *UserGroupMemberShipService) GetGroupMembersListAllByKey(groupKey string) (*UserList, error) { + return s.getGroupMembersList(LocatorKey(groupKey), 0, -1) +} +func (s *UserGroupMemberShipService) getGroupMembersList(locator Locator, offset, limit int) (*UserList, error) { + var out struct { + Users UserList + } + err := s.groupRestHelper.get(locator.String(), &out, "UserGroupMemberShip list members", + buildQueryLocator( + LocatorStart(offset), + LocatorCount(limit), + )) + if err != nil { + return nil, err + } + return &out.Users, nil +} diff --git a/teamcity/user_test.go b/teamcity/user_test.go new file mode 100644 index 0000000..571b308 --- /dev/null +++ b/teamcity/user_test.go @@ -0,0 +1,129 @@ +package teamcity_test + +import ( + "fmt" + "testing" + + "github.com/cvbarros/go-teamcity/teamcity" + "github.com/stretchr/testify/require" +) + +func TestUser_Create(t *testing.T) { + newUser, _ := teamcity.NewUser("username", "First Middle Last", "test@test.test") + client := setup() + actual, err := client.Users.Create(newUser) + + require.NoError(t, err) + require.NotNil(t, actual) + require.NotEmpty(t, actual.Name) + require.NotEmpty(t, actual.Username) + + cleanUpUser(t, client, actual.ID) + + require.NotEqual(t, newUser.ID, actual.ID) + require.Equal(t, newUser.Name, actual.Name) + require.Equal(t, newUser.Email, actual.Email) +} + +func TestUser_Get(t *testing.T) { + newUser, _ := teamcity.NewUser("usernameGet", "First Middle Last", "test@test.test") + + client := setup() + actual, err := client.Users.Create(newUser) + + require.NoError(t, err) + require.NotNil(t, actual) + require.NotEmpty(t, actual.Name) + require.NotEmpty(t, actual.Username) + require.NotZero(t, actual.ID) + + userByUsername, err := client.Users.GetByUsername(newUser.Username) + + require.NoError(t, err) + require.NotNil(t, userByUsername) + require.NotEmpty(t, userByUsername.Name) + require.NotEmpty(t, userByUsername.Username) + require.NotZero(t, userByUsername.ID) + + userByName, err := client.Users.GetByName(newUser.Name) + + require.NoError(t, err) + require.NotNil(t, userByName) + require.NotEmpty(t, userByName.Name) + require.NotEmpty(t, userByName.Username) + require.NotZero(t, userByName.ID) + + userByID, err := client.Users.GetByID(actual.ID) + + require.NoError(t, err) + require.NotNil(t, userByID) + require.NotEmpty(t, userByID.Name) + require.NotEmpty(t, userByID.Username) + require.NotZero(t, userByID.ID) + + cleanUpUser(t, client, actual.ID) + + require.NotEqual(t, newUser.ID, actual.ID) + require.Equal(t, newUser.Name, actual.Name) + require.Equal(t, newUser.Email, actual.Email) + + require.Equal(t, actual, userByID) + require.Equal(t, actual, userByUsername) +} + +func TestUser_Delete(t *testing.T) { + newUser, _ := teamcity.NewUser("usernameDel", "First Middle Last", "test@test.test") + client := setup() + actual, err := client.Users.Create(newUser) + + err = client.Users.DeleteByID(actual.ID) + + require.NoError(t, err) + + _, err = client.Users.GetByUsername(newUser.Username) + + // User is deleted, so expect error, and message to contain 404 (NOT FOUND) + require.Error(t, err) + require.Contains(t, err.Error(), "404") +} + +func TestUser_List(t *testing.T) { + users := []*teamcity.User{} + client := setup() + + for i := 0; i < 5; i++ { + user, err := teamcity.NewUser( + fmt.Sprint("TESTUSERNAME", i), + fmt.Sprint("Test User List ", i), + fmt.Sprint("Test User Description List ", i), + ) + require.NoError(t, err) + users = append(users, user) + _, err = client.Users.Create(user) + require.NoError(t, err) + } + userList, err := client.Users.List(0, 0) + + require.NoError(t, err) + require.Equal(t, 5, userList.Count-1) + + for _, user := range userList.Items { + if user.Username == "admin" { + continue + } + actual, err := client.Users.GetByUsername(user.Username) + require.NoError(t, err) + + cleanUpUser(t, client, actual.ID) + + _, err = client.Users.GetByName(user.Name) + require.Error(t, err) + } + userList, err = client.Users.List(0, 0) + require.NoError(t, err) + require.Equal(t, 1, userList.Count) +} + +func cleanUpUser(t *testing.T, client *teamcity.Client, id int) { + client.Users.DeleteByID(id) +}