Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chat Features: Create, Get, List, Update #219

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
10 changes: 8 additions & 2 deletions internal/test/testing.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,13 +100,14 @@ type Test struct {
AccessPackageResourceRoleClient *msgraph.AccessPackageResourceRoleClient
AccessPackageResourceRoleScopeClient *msgraph.AccessPackageResourceRoleScopeClient
AdministrativeUnitsClient *msgraph.AdministrativeUnitsClient
AppRoleAssignedToClient *msgraph.AppRoleAssignedToClient
ApplicationTemplatesClient *msgraph.ApplicationTemplatesClient
ApplicationsClient *msgraph.ApplicationsClient
AppRoleAssignedToClient *msgraph.AppRoleAssignedToClient
AttributeSetClient *msgraph.AttributeSetClient
AuthenticationMethodsClient *msgraph.AuthenticationMethodsClient
AuthenticationStrengthPoliciesClient *msgraph.AuthenticationStrengthPoliciesClient
B2CUserFlowClient *msgraph.B2CUserFlowClient
ChatClient *msgraph.ChatClient
ClaimsMappingPolicyClient *msgraph.ClaimsMappingPolicyClient
ConditionalAccessPoliciesClient *msgraph.ConditionalAccessPoliciesClient
ConnectedOrganizationClient *msgraph.ConnectedOrganizationClient
Expand Down Expand Up @@ -135,8 +136,8 @@ type Test struct {
RoleAssignmentsClient *msgraph.RoleAssignmentsClient
RoleDefinitionsClient *msgraph.RoleDefinitionsClient
RoleEligibilityScheduleRequestClient *msgraph.RoleEligibilityScheduleRequestClient
RoleManagementPolicyClient *msgraph.RoleManagementPolicyClient
RoleManagementPolicyAssignmentClient *msgraph.RoleManagementPolicyAssignmentClient
RoleManagementPolicyClient *msgraph.RoleManagementPolicyClient
RoleManagementPolicyRuleClient *msgraph.RoleManagementPolicyRuleClient
SchemaExtensionsClient *msgraph.SchemaExtensionsClient
ServicePrincipalsAppRoleAssignmentsClient *msgraph.AppRoleAssignmentsClient
Expand Down Expand Up @@ -282,6 +283,11 @@ func NewTest(t *testing.T) (c *Test) {
c.B2CUserFlowClient.BaseClient.Endpoint = *endpoint
c.B2CUserFlowClient.BaseClient.RetryableClient.RetryMax = retry

c.ChatClient = msgraph.NewChatClient()
c.ChatClient.BaseClient.Authorizer = c.Connections["default"].Authorizer
c.ChatClient.BaseClient.Endpoint = *endpoint
c.ChatClient.BaseClient.RetryableClient.RetryMax = retry

c.ClaimsMappingPolicyClient = msgraph.NewClaimsMappingPolicyClient()
c.ClaimsMappingPolicyClient.BaseClient.Authorizer = c.Connections["default"].Authorizer
c.ClaimsMappingPolicyClient.BaseClient.Endpoint = *endpoint
Expand Down
176 changes: 176 additions & 0 deletions msgraph/chat.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
package msgraph

import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"

"github.com/hashicorp/go-azure-sdk/sdk/odata"
)

const (
ShortTypeChat string = "chat"
ShortTypeConversationMember string = "aadUserConversationMember"
TypeChat string = "#microsoft.graph.chat"
TypeConversationMember string = "#microsoft.graph.aadUserConversationMember"
)

type ChatClient struct {
BaseClient Client
}

func NewChatClient() *ChatClient {
return &ChatClient{
BaseClient: NewClient(Version10),
}
}

// Create creates a new chat.
func (c *ChatClient) Create(ctx context.Context, chat Chat) (*Chat, int, error) {
body, err := json.Marshal(chat)
if err != nil {
return nil, 0, fmt.Errorf("json.Marshal(): %v", err)
}

retryFunc := func(resp *http.Response, o *odata.OData) bool {
if resp != nil && o != nil && o.Error != nil {
if resp.StatusCode == http.StatusForbidden {
return o.Error.Match("One or more members cannot be added to the thread roster")
}
}
return false
}

resp, status, _, err := c.BaseClient.Post(ctx, PostHttpRequestInput{
Body: body,
ConsistencyFailureFunc: retryFunc,
OData: odata.Query{
Metadata: odata.MetadataFull,
},
ValidStatusCodes: []int{http.StatusCreated},
Uri: Uri{
Entity: "/chats",
},
})

if err != nil {
return nil, status, fmt.Errorf("ChatsClient.BaseClient.Post(): %v", err)
}

defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, status, fmt.Errorf("io.ReadAll(): %v", err)
}

var newChat Chat
if err := json.Unmarshal(respBody, &newChat); err != nil {
return nil, status, fmt.Errorf("json.Unmarshal(): %v", err)
}

return &newChat, status, nil
}

// Get retrieves a chat.
func (c *ChatClient) Get(ctx context.Context, id string, query odata.Query) (*Chat, int, error) {
query.Metadata = odata.MetadataFull

resp, status, _, err := c.BaseClient.Get(ctx, GetHttpRequestInput{
ConsistencyFailureFunc: RetryOn404ConsistencyFailureFunc,
OData: query,
ValidStatusCodes: []int{http.StatusOK},
Uri: Uri{
Entity: fmt.Sprintf("/chats/%s", id),
},
})
if err != nil {
return nil, status, fmt.Errorf("ChatsClient.BaseClient.Get(): %v", err)
}

defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, status, fmt.Errorf("io.ReadAll(): %v", err)
}

var chat Chat
if err := json.Unmarshal(respBody, &chat); err != nil {
return nil, status, fmt.Errorf("json.Unmarshal(): %v", err)
}

return &chat, status, nil
}

// List returns a list of chats as Chat objects.
// To return just a lost of IDs then place the query to be Odata.Query{Select: "id"}.
func (c *ChatClient) List(ctx context.Context, userID string, query odata.Query) (*[]Chat, int, error) {
query.Metadata = odata.MetadataFull

resp, status, _, err := c.BaseClient.Get(ctx, GetHttpRequestInput{
ConsistencyFailureFunc: RetryOn404ConsistencyFailureFunc,
OData: query,
ValidStatusCodes: []int{http.StatusOK},
Uri: Uri{
Entity: fmt.Sprintf("/users/%s/chats", userID),
},
})
if err != nil {
return nil, status, fmt.Errorf("ChatsClient.BaseClient.Get(): %v", err)
}

defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, status, fmt.Errorf("io.ReadAll(): %v", err)
}

var chatList struct {
Value []Chat `json:"value"`
}
if err := json.Unmarshal(respBody, &chatList); err != nil {
return nil, status, fmt.Errorf("json.Unmarshal(): %v", err)
}

return &chatList.Value, status, nil

}

// Update updates a chat.
func (c *ChatClient) Update(ctx context.Context, chat Chat) (int, error) {
body, err := json.Marshal(chat)
if err != nil {
return 0, fmt.Errorf("json.Marshal(): %v", err)
}

_, status, _, err := c.BaseClient.Patch(ctx, PatchHttpRequestInput{
Body: body,
ConsistencyFailureFunc: RetryOn404ConsistencyFailureFunc,
ValidStatusCodes: []int{http.StatusNoContent},
Uri: Uri{
Entity: fmt.Sprintf("/chats/%s", *chat.ID),
},
})
if err != nil {
return status, fmt.Errorf("ChatsClient.BaseClient.Patch(): %v", err)
}

return status, nil
}

// Delete deletes a chat.
func (c *ChatClient) Delete(ctx context.Context, chatId string) (int, error) {
_, status, _, err := c.BaseClient.Delete(ctx, DeleteHttpRequestInput{
ConsistencyFailureFunc: RetryOn404ConsistencyFailureFunc,
ValidStatusCodes: []int{http.StatusNoContent},
Uri: Uri{
Entity: fmt.Sprintf("/chats/%s", chatId),
},
})
if err != nil {
return status, fmt.Errorf("ChatsClient.BaseClient.Delete(): %v", err)
}

return status, nil
}
136 changes: 136 additions & 0 deletions msgraph/chat_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package msgraph_test

import (
"fmt"
"testing"

"github.com/hashicorp/go-azure-sdk/sdk/odata"
"github.com/manicminer/hamilton/internal/test"
"github.com/manicminer/hamilton/internal/utils"
"github.com/manicminer/hamilton/msgraph"
)

func TestChatClient(t *testing.T) {
c := test.NewTest(t)
defer c.CancelFunc()

self := testDirectoryObjectsClient_Get(t, c, c.Claims.ObjectId)

// To create a chat two users need to be assigned to the chat.
// An owner needs to be assigned
user1 := testUsersClient_Create(t, c, msgraph.User{
AccountEnabled: utils.BoolPtr(true),
DisplayName: utils.StringPtr("test-user1"),
MailNickname: utils.StringPtr(fmt.Sprintf("test-user1-%s", c.RandomString)),
UserPrincipalName: utils.StringPtr(fmt.Sprintf("test-user1-%s@%s", c.RandomString, c.Connections["default"].DomainName)),
PasswordProfile: &msgraph.UserPasswordProfile{
Password: utils.StringPtr(fmt.Sprintf("IrPa55w0rd%s", c.RandomString)),
},
})
defer testUsersClient_Delete(t, c, *user1.ID())

user2 := testUsersClient_Create(t, c, msgraph.User{
AccountEnabled: utils.BoolPtr(true),
DisplayName: utils.StringPtr("test-user2"),
MailNickname: utils.StringPtr(fmt.Sprintf("test-user2-%s", c.RandomString)),
UserPrincipalName: utils.StringPtr(fmt.Sprintf("test-user2-%s@%s", c.RandomString, c.Connections["default"].DomainName)),
PasswordProfile: &msgraph.UserPasswordProfile{
Password: utils.StringPtr(fmt.Sprintf("IrPa55w0rd%s", c.RandomString)),
},
})
defer testUsersClient_Delete(t, c, *user2.ID())

// Check that a group chat and a OneOnOne chat can be created
newChat := msgraph.Chat{
Topic: utils.StringPtr(fmt.Sprintf("test-chat-%s", c.RandomString)),
ChatType: utils.StringPtr(msgraph.ChatTypeGroup),
Members: &[]msgraph.ConversationMember{
{
ODataType: utils.StringPtr(msgraph.TypeConversationMember),
User: utils.StringPtr(fmt.Sprintf("https://graph.microsoft.com/v1.0/users('%s')", *user1.Id)),
Roles: &[]string{"owner"},
},
{
ODataType: utils.StringPtr(msgraph.TypeConversationMember),
User: utils.StringPtr(fmt.Sprintf("https://graph.microsoft.com/v1.0/users('%s')", *user2.Id)),
Roles: &[]string{"owner"},
},
{
ODataType: utils.StringPtr(msgraph.TypeConversationMember),
User: utils.StringPtr(fmt.Sprintf("https://graph.microsoft.com/v1.0/users('%s')", *self.Id)),
Roles: &[]string{"owner"},
},
},
}

chat := testChatClient_Create(t, c, newChat)
defer testChatClient_Delete(t, c, *chat.ID)
testChatClient_Get(t, c, *chat.ID)
testChatClient_List(t, c, *self.Id)

chat.Topic = utils.StringPtr(fmt.Sprintf("test-chat-archived-%s", c.RandomString))
chat.Viewpoint.IsHidden = utils.BoolPtr(true)
testChatClient_Update(t, c, *chat)
}

func testChatClient_Create(t *testing.T, c *test.Test, newChat msgraph.Chat) (chat *msgraph.Chat) {
chat, status, err := c.ChatClient.Create(c.Context, newChat)
if err != nil {
t.Fatalf("ChatClient.Create(): %v", err)
}
if status < 200 || status >= 300 {
t.Fatalf("ChatClient.Create(): invalid status: %d", status)
}
if chat == nil {
t.Fatal("ChatClient.Create(): chat was nil")
}
return
}

func testChatClient_Get(t *testing.T, c *test.Test, id string) (chat *msgraph.Chat) {
chat, status, err := c.ChatClient.Get(c.Context, id, odata.Query{})
if err != nil {
t.Fatalf("ChatClient.Get(): %v", err)
}
if status < 200 || status >= 300 {
t.Fatalf("ChatClient.Get(): invalid status: %d", status)
}
if chat == nil {
t.Fatal("ChatClient.Get(): chat was nil")
}
return
}

func testChatClient_List(t *testing.T, c *test.Test, userID string) (chats *[]msgraph.Chat) {
chats, _, err := c.ChatClient.List(c.Context, userID, odata.Query{Top: 10})
if err != nil {
t.Fatalf("ChatClient.List(): %v", err)
}
if chats == nil {
t.Fatal("ChatClient.List(): chats was nil")
}
return
}

func testChatClient_Update(t *testing.T, c *test.Test, chat msgraph.Chat) (updatedChat *msgraph.Chat) {
chat.Topic = utils.StringPtr(fmt.Sprintf("test-chat-%s", c.RandomString))
status, err := c.ChatClient.Update(c.Context, chat)
if err != nil {
t.Fatalf("ChatClient.Update(): %v", err)
}
if status < 200 || status >= 300 {
t.Fatalf("ChatClient.Update(): invalid status: %d", status)
}
return
}

func testChatClient_Delete(t *testing.T, c *test.Test, chatId string) {
status, err := c.ChatClient.Delete(c.Context, chatId)
if err != nil {
t.Fatalf("ChatClient.Delete(): %v", err)
}
if status < 200 || status >= 300 {
t.Fatalf("ChatClient.Delete(): invalid status: %d", status)
}
return

Check failure on line 135 in msgraph/chat_test.go

View workflow job for this annotation

GitHub Actions / golint

S1023: redundant `return` statement (gosimple)
}
Loading
Loading