From 5e44e9330fa7d551f7aabe251bde7846b4e4e2e9 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Sun, 21 Dec 2025 12:06:45 +0100 Subject: [PATCH 01/99] feat: implemented better json decoding function --- go/go.mod | 1 + go/go.sum | 8 ++- .../json-client/decoders/decode-msTeam.go | 25 +++++++++ .../json-client/decoders/decode-params.go | 27 ++++++++++ go/internal/json-client/requests-teams.go | 52 +++++++++++++++++++ 5 files changed, 111 insertions(+), 2 deletions(-) create mode 100644 go/internal/json-client/decoders/decode-msTeam.go create mode 100644 go/internal/json-client/decoders/decode-params.go create mode 100644 go/internal/json-client/requests-teams.go diff --git a/go/go.mod b/go/go.mod index 75df08b..7e695aa 100644 --- a/go/go.mod +++ b/go/go.mod @@ -5,6 +5,7 @@ go 1.25.5 require ( github.com/microsoft/kiota-abstractions-go v1.9.3 github.com/microsoftgraph/msgraph-sdk-go v1.90.0 + github.com/mitchellh/mapstructure v1.5.0 github.com/pzsp-teams/lib v0.0.0-20251213104555-e41d3adf599f ) diff --git a/go/go.sum b/go/go.sum index e345e5c..6e98158 100644 --- a/go/go.sum +++ b/go/go.sum @@ -23,6 +23,8 @@ github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeD github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/keybase/go-keychain v0.0.0-20230523030712-b5615109f100 h1:rG3VnJUnAWyiv7qYmmdOdSapzz6HM+zb9/uRFr0T5EM= @@ -51,18 +53,20 @@ github.com/microsoftgraph/msgraph-sdk-go v1.90.0 h1:ygVeWfGB8TMO4rTFxtrYueZmj1mL github.com/microsoftgraph/msgraph-sdk-go v1.90.0/go.mod h1:UdZWxbZiFvjPug9DYayD90JNiHjXyNRA39lEpcy3Kms= github.com/microsoftgraph/msgraph-sdk-go-core v1.4.0 h1:0SrIoFl7TQnMRrsi5TFaeNe0q8KO5lRzRp4GSCCL2So= github.com/microsoftgraph/msgraph-sdk-go-core v1.4.0/go.mod h1:A1iXs+vjsRjzANxF6UeKv2ACExG7fqTwHHbwh1FL+EE= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pzsp-teams/lib v0.0.0-20251208073120-ca1606f2b27d h1:hFdPrkbnUngxrGdMkPVZpvY76T6QgnXo87u9wzXgNWY= -github.com/pzsp-teams/lib v0.0.0-20251208073120-ca1606f2b27d/go.mod h1:uGrgoO+KmBc55Le/AeysA0d24HOCQHO55Uw5Z7naQo4= github.com/pzsp-teams/lib v0.0.0-20251213104555-e41d3adf599f h1:tmXKJEWYl0f/H4n91wnfW1asgkHbftKgX5nbpafNHmU= github.com/pzsp-teams/lib v0.0.0-20251213104555-e41d3adf599f/go.mod h1:EPtQVPz+HYZNsSpcoQ3QWLbMEz1O2qeIH5ojlYJLEic= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/std-uritemplate/std-uritemplate/go/v2 v2.0.3 h1:7hth9376EoQEd1hH4lAp3vnaLP2UMyxuMMghLKzDHyU= github.com/std-uritemplate/std-uritemplate/go/v2 v2.0.3/go.mod h1:Z5KcoM0YLC7INlNhEezeIZ0TZNYf7WSNO0Lvah4DSeQ= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/zalando/go-keyring v0.2.6 h1:r7Yc3+H+Ux0+M72zacZoItR3UDxeWfKTcabvkI8ua9s= diff --git a/go/internal/json-client/decoders/decode-msTeam.go b/go/internal/json-client/decoders/decode-msTeam.go new file mode 100644 index 0000000..c67986d --- /dev/null +++ b/go/internal/json-client/decoders/decode-msTeam.go @@ -0,0 +1,25 @@ +package decoders + +import( + msmodels "github.com/microsoftgraph/msgraph-sdk-go/models" +) + +type TeamPatchDTO struct { + DisplayName *string `json:"displayName"` + Description *string `json:"description"` + // Dodaj inne pola, które chcesz aktualizować... +} + +func GetMSTeam(patch *TeamPatchDTO) *msmodels.Team { + team := msmodels.NewTeam() + + if patch.DisplayName != nil { + team.SetDisplayName(patch.DisplayName) + } + if patch.Description != nil { + team.SetDescription(patch.Description) + } + // Ustaw inne pola w podobny sposób... + + return team +} \ No newline at end of file diff --git a/go/internal/json-client/decoders/decode-params.go b/go/internal/json-client/decoders/decode-params.go new file mode 100644 index 0000000..6651123 --- /dev/null +++ b/go/internal/json-client/decoders/decode-params.go @@ -0,0 +1,27 @@ +package decoders + +import ( + "github.com/mitchellh/mapstructure" +) + +func DecodeParams[T any](p interface{}) (*T, error) { + var result T + + config := &mapstructure.DecoderConfig{ + Metadata: nil, + Result: &result, + TagName: "json", + WeaklyTypedInput: true, + } + + decoder, err := mapstructure.NewDecoder(config) + if err != nil { + return nil, err + } + + if err := decoder.Decode(p); err != nil { + return nil, err + } + + return &result, nil +} \ No newline at end of file diff --git a/go/internal/json-client/requests-teams.go b/go/internal/json-client/requests-teams.go new file mode 100644 index 0000000..a6898a8 --- /dev/null +++ b/go/internal/json-client/requests-teams.go @@ -0,0 +1,52 @@ +package jsonclient + +import ( + "context" + + "github.com/pzsp-teams/lib-python/internal/decoders" +) + +type getTeamParams struct { + TeamRef string `json:"teamRef"` +} + +func (jsonclient *TeamsJSONClient) Get(p map[string]interface{}) (interface{}, error) { + params, err := decoders.DecodeParams[getTeamParams](p) + if err != nil { + return nil, err + } + team, err := jsonclient.client.Teams.Get(context.TODO(), params.TeamRef) + if err != nil { + return nil, err + } else { + return team, nil + } +} + +func (jsonclient *TeamsJSONClient) ListMyJoined() (interface{}, error) { + teams, err := jsonclient.client.Teams.ListMyJoined(context.TODO()) + if err != nil { + return nil, err + } else { + return teams, nil + } +} + +type updateTeamParams struct { + TeamRef string `json:"teamRef"` + TeamPatch decoders.TeamPatchDTO `json:"team"` +} + +func (jsonclient *TeamsJSONClient) Update(p map[string]interface{}) (interface{}, error) { + params, err := decoders.DecodeParams[updateTeamParams](p) + msTeam := decoders.GetMSTeam(¶ms.TeamPatch) + if err != nil { + return nil, err + } + updatedTeam, err := jsonclient.client.Teams.Update(context.TODO(), params.TeamRef, msTeam) + if err != nil { + return nil, err + } else { + return updatedTeam, nil + } +} From 177141e9b18402b430658933d9aab056ba502c15 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Sun, 21 Dec 2025 12:14:08 +0100 Subject: [PATCH 02/99] feat: written Teams service in go bridge --- go/internal/json-client/requests-teams.go | 112 ++++++++++++++++++++-- 1 file changed, 105 insertions(+), 7 deletions(-) diff --git a/go/internal/json-client/requests-teams.go b/go/internal/json-client/requests-teams.go index a6898a8..847d52b 100644 --- a/go/internal/json-client/requests-teams.go +++ b/go/internal/json-client/requests-teams.go @@ -3,7 +3,7 @@ package jsonclient import ( "context" - "github.com/pzsp-teams/lib-python/internal/decoders" + "github.com/pzsp-teams/lib-python/internal/json-client/decoders" ) type getTeamParams struct { @@ -18,18 +18,16 @@ func (jsonclient *TeamsJSONClient) Get(p map[string]interface{}) (interface{}, e team, err := jsonclient.client.Teams.Get(context.TODO(), params.TeamRef) if err != nil { return nil, err - } else { - return team, nil } + return team, nil } func (jsonclient *TeamsJSONClient) ListMyJoined() (interface{}, error) { teams, err := jsonclient.client.Teams.ListMyJoined(context.TODO()) if err != nil { return nil, err - } else { - return teams, nil } + return teams, nil } type updateTeamParams struct { @@ -46,7 +44,107 @@ func (jsonclient *TeamsJSONClient) Update(p map[string]interface{}) (interface{} updatedTeam, err := jsonclient.client.Teams.Update(context.TODO(), params.TeamRef, msTeam) if err != nil { return nil, err - } else { - return updatedTeam, nil } + return updatedTeam, nil +} + +type createViaGroupParams struct { + DisplayName string `json:"displayName"` + MailNickname string `json:"mailNickname"` + Visibility string `json:"visibility"` +} + +func (jsonclient *TeamsJSONClient) CreateViaGroup(p map[string]interface{}) (interface{}, error) { + params, err := decoders.DecodeParams[createViaGroupParams](p) + if err != nil { + return nil, err + } + createdTeam, err := jsonclient.client.Teams.CreateViaGroup(context.TODO(), params.DisplayName, params.MailNickname, params.Visibility) + if err != nil { + return nil, err + } + return createdTeam, nil +} + +type createFromTemplateParams struct { + DisplayName string `json:"displayName"` + Description string `json:"description"` + Owners []string `json:"owners"` +} + +func (jsonclient *TeamsJSONClient) CreateFromTemplate(p map[string]interface{}) (interface{}, error) { + params, err := decoders.DecodeParams[createFromTemplateParams](p) + if err != nil { + return nil, err + } + createdTeam, err := jsonclient.client.Teams.CreateFromTemplate(context.TODO(), params.DisplayName, params.Description, params.Owners) + if err != nil { + return nil, err + } + return createdTeam, nil +} + +type archiveTeamParams struct { + TeamRef string `json:"teamRef"` + SpoReadOnlyFromMembers *bool `json:"spoReadOnlyFromMembers,omitempty"` +} + +func (jsonclient *TeamsJSONClient) Archive(p map[string]interface{}) (interface{}, error) { + params, err := decoders.DecodeParams[archiveTeamParams](p) + if err != nil { + return nil, err + } + err = jsonclient.client.Teams.Archive(context.TODO(), params.TeamRef, params.SpoReadOnlyFromMembers) + if err != nil { + return nil, err + } + return "archived", nil +} + +type unarchiveTeamParams struct { + TeamRef string `json:"teamRef"` +} + +func (jsonclient *TeamsJSONClient) Unarchive(p map[string]interface{}) (interface{}, error) { + params, err := decoders.DecodeParams[unarchiveTeamParams](p) + if err != nil { + return nil, err + } + err = jsonclient.client.Teams.Unarchive(context.TODO(), params.TeamRef) + if err != nil { + return nil, err + } + return "unarchived", nil +} + +type deleteTeamParams struct { + TeamRef string `json:"teamRef"` +} + +func (jsonclient *TeamsJSONClient) Delete(p map[string]interface{}) (interface{}, error) { + params, err := decoders.DecodeParams[deleteTeamParams](p) + if err != nil { + return nil, err + } + err = jsonclient.client.Teams.Delete(context.TODO(), params.TeamRef) + if err != nil { + return nil, err + } + return "deleted", nil +} + +type restoreTeamParams struct { + DeletedGroupID string `json:"deletedGroupId"` +} + +func (jsonclient *TeamsJSONClient) RestoreDeleted(p map[string]interface{}) (interface{}, error) { + params, err := decoders.DecodeParams[restoreTeamParams](p) + if err != nil { + return nil, err + } + restoredTeam, err := jsonclient.client.Teams.RestoreDeleted(context.TODO(), params.DeletedGroupID) + if err != nil { + return nil, err + } + return restoredTeam, nil } From 8442b0c8763f9a59a724e8c514b314d9c6a43d91 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Sun, 21 Dec 2025 12:23:56 +0100 Subject: [PATCH 03/99] feat: adjusted update Team patch DTO --- .../json-client/decoders/decode-msTeam.go | 117 +++++++++++++++++- 1 file changed, 113 insertions(+), 4 deletions(-) diff --git a/go/internal/json-client/decoders/decode-msTeam.go b/go/internal/json-client/decoders/decode-msTeam.go index c67986d..3447881 100644 --- a/go/internal/json-client/decoders/decode-msTeam.go +++ b/go/internal/json-client/decoders/decode-msTeam.go @@ -5,9 +5,44 @@ import( ) type TeamPatchDTO struct { - DisplayName *string `json:"displayName"` - Description *string `json:"description"` - // Dodaj inne pola, które chcesz aktualizować... + DisplayName *string `json:"displayName"` + Description *string `json:"description"` + Classification *string `json:"classification"` + Visibility *string `json:"visibility"` + IsArchived *bool `json:"isArchived"` + + MemberSettings *TeamMemberSettingsDTO `json:"memberSettings"` + GuestSettings *TeamGuestSettingsDTO `json:"guestSettings"` + MessagingSettings *TeamMessagingSettingsDTO `json:"messagingSettings"` + FunSettings *TeamFunSettingsDTO `json:"funSettings"` +} + +type TeamMemberSettingsDTO struct { + AllowCreateUpdateChannels *bool `json:"allowCreateUpdateChannels"` + AllowDeleteChannels *bool `json:"allowDeleteChannels"` + AllowAddRemoveApps *bool `json:"allowAddRemoveApps"` + AllowCreateUpdateRemoveTabs *bool `json:"allowCreateUpdateRemoveTabs"` + AllowCreateUpdateRemoveConnectors *bool `json:"allowCreateUpdateRemoveConnectors"` +} + +type TeamGuestSettingsDTO struct { + AllowCreateUpdateChannels *bool `json:"allowCreateUpdateChannels"` + AllowDeleteChannels *bool `json:"allowDeleteChannels"` +} + +type TeamMessagingSettingsDTO struct { + AllowUserEditMessages *bool `json:"allowUserEditMessages"` + AllowUserDeleteMessages *bool `json:"allowUserDeleteMessages"` + AllowOwnerDeleteMessages *bool `json:"allowOwnerDeleteMessages"` + AllowTeamMentions *bool `json:"allowTeamMentions"` + AllowChannelMentions *bool `json:"allowChannelMentions"` +} + +type TeamFunSettingsDTO struct { + AllowGiphy *bool `json:"allowGiphy"` + GiphyContentRating *string `json:"giphyContentRating"` + AllowStickersAndMemes *bool `json:"allowStickersAndMemes"` + AllowCustomMemes *bool `json:"allowCustomMemes"` } func GetMSTeam(patch *TeamPatchDTO) *msmodels.Team { @@ -19,7 +54,81 @@ func GetMSTeam(patch *TeamPatchDTO) *msmodels.Team { if patch.Description != nil { team.SetDescription(patch.Description) } - // Ustaw inne pola w podobny sposób... + if patch.Classification != nil { + team.SetClassification(patch.Classification) + } + if patch.IsArchived != nil { + team.SetIsArchived(patch.IsArchived) + } + if patch.Visibility != nil { + switch *patch.Visibility { + case "Public": + v := msmodels.PUBLIC_TEAMVISIBILITYTYPE + team.SetVisibility(&v) + case "Private": + v := msmodels.PRIVATE_TEAMVISIBILITYTYPE + team.SetVisibility(&v) + case "HiddenMembership": + v := msmodels.HIDDENMEMBERSHIP_TEAMVISIBILITYTYPE + team.SetVisibility(&v) + } + } + + if patch.MemberSettings != nil { + msSettings := msmodels.NewTeamMemberSettings() + p := patch.MemberSettings + if p.AllowCreateUpdateChannels != nil { msSettings.SetAllowCreateUpdateChannels(p.AllowCreateUpdateChannels) } + if p.AllowDeleteChannels != nil { msSettings.SetAllowDeleteChannels(p.AllowDeleteChannels) } + if p.AllowAddRemoveApps != nil { msSettings.SetAllowAddRemoveApps(p.AllowAddRemoveApps) } + if p.AllowCreateUpdateRemoveTabs != nil { msSettings.SetAllowCreateUpdateRemoveTabs(p.AllowCreateUpdateRemoveTabs) } + if p.AllowCreateUpdateRemoveConnectors != nil { msSettings.SetAllowCreateUpdateRemoveConnectors(p.AllowCreateUpdateRemoveConnectors) } + + team.SetMemberSettings(msSettings) + } + + if patch.GuestSettings != nil { + msSettings := msmodels.NewTeamGuestSettings() + p := patch.GuestSettings + if p.AllowCreateUpdateChannels != nil { msSettings.SetAllowCreateUpdateChannels(p.AllowCreateUpdateChannels) } + if p.AllowDeleteChannels != nil { msSettings.SetAllowDeleteChannels(p.AllowDeleteChannels) } + + team.SetGuestSettings(msSettings) + } + + if patch.MessagingSettings != nil { + msSettings := msmodels.NewTeamMessagingSettings() + p := patch.MessagingSettings + if p.AllowUserEditMessages != nil { msSettings.SetAllowUserEditMessages(p.AllowUserEditMessages) } + if p.AllowUserDeleteMessages != nil { msSettings.SetAllowUserDeleteMessages(p.AllowUserDeleteMessages) } + if p.AllowOwnerDeleteMessages != nil { msSettings.SetAllowOwnerDeleteMessages(p.AllowOwnerDeleteMessages) } + if p.AllowTeamMentions != nil { msSettings.SetAllowTeamMentions(p.AllowTeamMentions) } + if p.AllowChannelMentions != nil { msSettings.SetAllowChannelMentions(p.AllowChannelMentions) } + + team.SetMessagingSettings(msSettings) + } + + if patch.FunSettings != nil { + msSettings := msmodels.NewTeamFunSettings() + p := patch.FunSettings + if p.AllowGiphy != nil { msSettings.SetAllowGiphy(p.AllowGiphy) } + if p.GiphyContentRating != nil { + switch *p.GiphyContentRating { + case "moderate": + v := msmodels.MODERATE_GIPHYRATINGTYPE + msSettings.SetGiphyContentRating(&v) + case "strict": + v := msmodels.STRICT_GIPHYRATINGTYPE + msSettings.SetGiphyContentRating(&v) + default: + v := msmodels.MODERATE_GIPHYRATINGTYPE + msSettings.SetGiphyContentRating(&v) + } +} + if p.AllowStickersAndMemes != nil { msSettings.SetAllowStickersAndMemes(p.AllowStickersAndMemes) } + if p.AllowCustomMemes != nil { msSettings.SetAllowCustomMemes(p.AllowCustomMemes) } + + team.SetFunSettings(msSettings) + } return team } \ No newline at end of file From b0dcf5fdad5f883d3d10b83dc78d53205a41461f Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Tue, 23 Dec 2025 11:45:20 +0100 Subject: [PATCH 04/99] refactor: improved go requests handling --- go/bridge/main.go | 19 +++++----- go/internal/json-client/request-map.go | 46 +++++++++++++++++++++++ go/internal/json-client/requests-teams.go | 16 ++++---- 3 files changed, 63 insertions(+), 18 deletions(-) create mode 100644 go/internal/json-client/request-map.go diff --git a/go/bridge/main.go b/go/bridge/main.go index ec5052b..d14276c 100644 --- a/go/bridge/main.go +++ b/go/bridge/main.go @@ -51,19 +51,18 @@ func main() { continue } - switch req.Method { - case "listChannels": - channels, err := client.ListChannels(req.Params) - if err != nil { - respondError(writer, err) - } else { - respondResult(writer, channels) - } - default: + handler, exists := jsonClientLib.Handlers[req.Method] + if !exists { respondError(writer, fmt.Errorf("unknown method")) + continue + } + + result, err := handler(client, req.Params) + if err != nil { + respondError(writer, err) } + respondResult(writer, result) continue } - respondError(writer, fmt.Errorf("unknown request type")) } } diff --git a/go/internal/json-client/request-map.go b/go/internal/json-client/request-map.go new file mode 100644 index 0000000..24d0b65 --- /dev/null +++ b/go/internal/json-client/request-map.go @@ -0,0 +1,46 @@ +package jsonclient + +type HandlerFunc func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) + +var Handlers = map[string]HandlerFunc{ + + "listChannels": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + return c.ListChannels(p) + }, + + "getTeam": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + return c.GetTeam(p) + }, + + "updateTeam": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + return c.UpdateTeam(p) + }, + + "listMyJoined": func(c *TeamsJSONClient, _ map[string]interface{}) (interface{}, error) { + return c.ListMyJoined() + }, + + "createTeamViaGroup": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + return c.CreateTeamViaGroup(p) + }, + + "createTeamFromTemplate": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + return c.CreateTeamFromTemplate(p) + }, + + "archiveTeam": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + return c.ArchiveTeam(p) + }, + + "unarchiveTeam": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + return c.UnarchiveTeam(p) + }, + + "deleteTeam": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + return c.DeleteTeam(p) + }, + + "restoreDeletedTeam": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + return c.RestoreDeletedTeam(p) + }, +} diff --git a/go/internal/json-client/requests-teams.go b/go/internal/json-client/requests-teams.go index 847d52b..0c8a3d4 100644 --- a/go/internal/json-client/requests-teams.go +++ b/go/internal/json-client/requests-teams.go @@ -10,7 +10,7 @@ type getTeamParams struct { TeamRef string `json:"teamRef"` } -func (jsonclient *TeamsJSONClient) Get(p map[string]interface{}) (interface{}, error) { +func (jsonclient *TeamsJSONClient) GetTeam(p map[string]interface{}) (interface{}, error) { params, err := decoders.DecodeParams[getTeamParams](p) if err != nil { return nil, err @@ -35,7 +35,7 @@ type updateTeamParams struct { TeamPatch decoders.TeamPatchDTO `json:"team"` } -func (jsonclient *TeamsJSONClient) Update(p map[string]interface{}) (interface{}, error) { +func (jsonclient *TeamsJSONClient) UpdateTeam(p map[string]interface{}) (interface{}, error) { params, err := decoders.DecodeParams[updateTeamParams](p) msTeam := decoders.GetMSTeam(¶ms.TeamPatch) if err != nil { @@ -54,7 +54,7 @@ type createViaGroupParams struct { Visibility string `json:"visibility"` } -func (jsonclient *TeamsJSONClient) CreateViaGroup(p map[string]interface{}) (interface{}, error) { +func (jsonclient *TeamsJSONClient) CreateTeamViaGroup(p map[string]interface{}) (interface{}, error) { params, err := decoders.DecodeParams[createViaGroupParams](p) if err != nil { return nil, err @@ -72,7 +72,7 @@ type createFromTemplateParams struct { Owners []string `json:"owners"` } -func (jsonclient *TeamsJSONClient) CreateFromTemplate(p map[string]interface{}) (interface{}, error) { +func (jsonclient *TeamsJSONClient) CreateTeamFromTemplate(p map[string]interface{}) (interface{}, error) { params, err := decoders.DecodeParams[createFromTemplateParams](p) if err != nil { return nil, err @@ -89,7 +89,7 @@ type archiveTeamParams struct { SpoReadOnlyFromMembers *bool `json:"spoReadOnlyFromMembers,omitempty"` } -func (jsonclient *TeamsJSONClient) Archive(p map[string]interface{}) (interface{}, error) { +func (jsonclient *TeamsJSONClient) ArchiveTeam(p map[string]interface{}) (interface{}, error) { params, err := decoders.DecodeParams[archiveTeamParams](p) if err != nil { return nil, err @@ -105,7 +105,7 @@ type unarchiveTeamParams struct { TeamRef string `json:"teamRef"` } -func (jsonclient *TeamsJSONClient) Unarchive(p map[string]interface{}) (interface{}, error) { +func (jsonclient *TeamsJSONClient) UnarchiveTeam(p map[string]interface{}) (interface{}, error) { params, err := decoders.DecodeParams[unarchiveTeamParams](p) if err != nil { return nil, err @@ -121,7 +121,7 @@ type deleteTeamParams struct { TeamRef string `json:"teamRef"` } -func (jsonclient *TeamsJSONClient) Delete(p map[string]interface{}) (interface{}, error) { +func (jsonclient *TeamsJSONClient) DeleteTeam(p map[string]interface{}) (interface{}, error) { params, err := decoders.DecodeParams[deleteTeamParams](p) if err != nil { return nil, err @@ -137,7 +137,7 @@ type restoreTeamParams struct { DeletedGroupID string `json:"deletedGroupId"` } -func (jsonclient *TeamsJSONClient) RestoreDeleted(p map[string]interface{}) (interface{}, error) { +func (jsonclient *TeamsJSONClient) RestoreDeletedTeam(p map[string]interface{}) (interface{}, error) { params, err := decoders.DecodeParams[restoreTeamParams](p) if err != nil { return nil, err From be98ae66684539b1d063daabf3f7d8dcc58e66ca Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Tue, 23 Dec 2025 12:03:14 +0100 Subject: [PATCH 05/99] feat: implemented most of teams service in python --- python/teams_lib_pzsp2_z1/services/teams.py | 117 ++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 python/teams_lib_pzsp2_z1/services/teams.py diff --git a/python/teams_lib_pzsp2_z1/services/teams.py b/python/teams_lib_pzsp2_z1/services/teams.py new file mode 100644 index 0000000..249554a --- /dev/null +++ b/python/teams_lib_pzsp2_z1/services/teams.py @@ -0,0 +1,117 @@ +from teams_lib_pzsp2_z1.model.team import Team +from teams_lib_pzsp2_z1.services.base_service import BaseService + + +class TeamsService(BaseService): + def get(self, teamRef: str) -> Team: + response = self.client.execute( + cmd_type="request", + method="getTeam", + params={ + "teamRef": teamRef, + }, + ) + return Team( + ID=response["ID"], + DisplayName=response["DisplayName"], + Description=response["Description"], + IsArchived=(True if response["IsArchived"] else False), + Visibility=response["Visibility"], + ) + + def list_my_joined(self) -> list[Team]: + response = self.client.execute( + cmd_type="request", + method="listMyJoined", + params={}, + ) + return [ + Team( + ID=team["ID"], + DisplayName=team["DisplayName"], + Description=team["Description"], + IsArchived=(True if team["IsArchived"] else False), + Visibility=team["Visibility"], + ) + for team in response + ] + + # def update //TODO + + def create_via_group( + self, display_name: str, mail_nickname: str, visibility: str + ) -> Team: + response = self.client.execute( + cmd_type="request", + method="createTeamViaGroup", + params={ + "displayName": display_name, + "mailNickname": mail_nickname, + "visibility": visibility, + }, + ) + return Team( + ID=response["ID"], + DisplayName=response["DisplayName"], + Description=response["Description"], + IsArchived=(True if response["IsArchived"] else False), + Visibility=response["Visibility"], + ) + + def create_from_template( + self, + display_name: str, + description: str, + owners: list[str], + ) -> str: + response = self.client.execute( + cmd_type="request", + method="createTeamFromTemplate", + params={ + "displayName": display_name, + "description": description, + "owners": owners, + }, + ) + return response["id"] + + def archive(self, teamRef: str, spo_read_only_from_members: bool) -> bool: + response = self.client.execute( + cmd_type="request", + method="archiveTeam", + params={ + "teamRef": teamRef, + "spoReadOnlyFromMembers": spo_read_only_from_members, + }, + ) + return response == "archived" + + def unarchive(self, teamRef: str) -> bool: + response = self.client.execute( + cmd_type="request", + method="unarchiveTeam", + params={ + "teamRef": teamRef, + }, + ) + return response == "unarchived" + + def delete(self, teamRef: str) -> bool: + response = self.client.execute( + cmd_type="request", + method="deleteTeam", + params={ + "teamRef": teamRef, + }, + ) + return response == "deleted" + + def restore_deleted(self, teamRef: str) -> str: + response = self.client.execute( + cmd_type="request", + method="restoreDeletedTeam", + params={ + "teamRef": teamRef, + }, + ) + return response["id"] From 894abbd6109daa06e6405114c6ec3cc166a43d2f Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Tue, 23 Dec 2025 12:13:15 +0100 Subject: [PATCH 06/99] feat: implemented update team function in python --- python/teams_lib_pzsp2_z1/model/team.py | 41 +++++++++++++++++++++ python/teams_lib_pzsp2_z1/services/teams.py | 20 +++++++++- 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/python/teams_lib_pzsp2_z1/model/team.py b/python/teams_lib_pzsp2_z1/model/team.py index 9de6e1b..d53ee4a 100644 --- a/python/teams_lib_pzsp2_z1/model/team.py +++ b/python/teams_lib_pzsp2_z1/model/team.py @@ -8,3 +8,44 @@ class Team: Description: str IsArchived: bool Visibility: str + + +@dataclass +class MSTeamsUpdate: + DisplayName: str | None = None + Description: str | None = None + Classification: str | None = None + Visibility: str | None = None + IsArchived: bool | None = None + MemberSettings: "TeamMemberSettingsDTO | None" = None + GuestSettings: "TeamGuestSettingsDTO | None" = None + MessagingSettings: "TeamMessagingSettingsDTO | None" = None + FunSettings: "TeamFunSettingsDTO | None" = None + + @dataclass + class TeamMemberSettingsDTO: + AllowCreateUpdateChannels: bool | None = None + AllowDeleteChannels: bool | None = None + AllowAddRemoveApps: bool | None = None + AllowCreateUpdateRemoveTabs: bool | None = None + AllowCreateUpdateRemoveConnectors: bool | None = None + + @dataclass + class TeamGuestSettingsDTO: + AllowCreateUpdateChannels: bool | None = None + AllowDeleteChannels: bool | None = None + + @dataclass + class TeamMessagingSettingsDTO: + AllowUserEditMessages: bool | None = None + AllowUserDeleteMessages: bool | None = None + AllowOwnerDeleteMessages: bool | None = None + AllowTeamMentions: bool | None = None + AllowChannelMentions: bool | None = None + + @dataclass + class TeamFunSettingsDTO: + AllowGiphy: bool | None = None + GiphyContentRating: str | None = None + AllowStickersAndMemes: bool | None = None + AllowCustomMemes: bool | None = None diff --git a/python/teams_lib_pzsp2_z1/services/teams.py b/python/teams_lib_pzsp2_z1/services/teams.py index 249554a..599ed16 100644 --- a/python/teams_lib_pzsp2_z1/services/teams.py +++ b/python/teams_lib_pzsp2_z1/services/teams.py @@ -1,4 +1,4 @@ -from teams_lib_pzsp2_z1.model.team import Team +from teams_lib_pzsp2_z1.model.team import MSTeamsUpdate, Team from teams_lib_pzsp2_z1.services.base_service import BaseService @@ -36,7 +36,23 @@ def list_my_joined(self) -> list[Team]: for team in response ] - # def update //TODO + def update(self, teamRef: str, update: MSTeamsUpdate) -> Team: + params = {"teamRef": teamRef} + update_dict = {k: v for k, v in update.__dict__.items() if v is not None} + params["team"] = update_dict + + response = self.client.execute( + cmd_type="request", + method="updateTeam", + params=params, + ) + return Team( + ID=response["ID"], + DisplayName=response["DisplayName"], + Description=response["Description"], + IsArchived=(True if response["IsArchived"] else False), + Visibility=response["Visibility"], + ) def create_via_group( self, display_name: str, mail_nickname: str, visibility: str From 8e48ff76351c08e5638f0e66fa1bf99b5773b655 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Tue, 23 Dec 2025 15:25:22 +0100 Subject: [PATCH 07/99] feat: Written test to validate get teams request --- go/internal/json-client/requests-teams.go | 8 +++ python/teams_lib_pzsp2_z1/client.py | 5 +- python/teams_lib_pzsp2_z1/services/teams.py | 1 + python/tests/fake_server/data.py | 25 +++++++-- python/tests/fake_server/setup.py | 14 ++++- python/tests/test_teams.py | 60 +++++++++++++++++++++ 6 files changed, 106 insertions(+), 7 deletions(-) create mode 100644 python/tests/test_teams.py diff --git a/go/internal/json-client/requests-teams.go b/go/internal/json-client/requests-teams.go index 0c8a3d4..c461bfa 100644 --- a/go/internal/json-client/requests-teams.go +++ b/go/internal/json-client/requests-teams.go @@ -2,6 +2,8 @@ package jsonclient import ( "context" + "fmt" + "os" "github.com/pzsp-teams/lib-python/internal/json-client/decoders" ) @@ -12,6 +14,9 @@ type getTeamParams struct { func (jsonclient *TeamsJSONClient) GetTeam(p map[string]interface{}) (interface{}, error) { params, err := decoders.DecodeParams[getTeamParams](p) + + fmt.Fprintf(os.Stderr, "Decoded params: %+v\n", params) + if err != nil { return nil, err } @@ -19,6 +24,9 @@ func (jsonclient *TeamsJSONClient) GetTeam(p map[string]interface{}) (interface{ if err != nil { return nil, err } + + fmt.Fprintf(os.Stderr, "Received team: %+v\n", team) + return team, nil } diff --git a/python/teams_lib_pzsp2_z1/client.py b/python/teams_lib_pzsp2_z1/client.py index 055ac45..65b994a 100644 --- a/python/teams_lib_pzsp2_z1/client.py +++ b/python/teams_lib_pzsp2_z1/client.py @@ -4,9 +4,11 @@ import subprocess import threading from typing import Any +import sys from teams_lib_pzsp2_z1 import config from teams_lib_pzsp2_z1.services.channels import ChannelsService +from teams_lib_pzsp2_z1.services.teams import TeamsService class TeamsClient: @@ -23,13 +25,14 @@ def __init__( [str(self._binary())], stdin=subprocess.PIPE, stdout=subprocess.PIPE, - stderr=subprocess.DEVNULL, + stderr=sys.stderr, text=True, bufsize=1, ) self.env_path = env_path self.channels = ChannelsService(self) + self.teams = TeamsService(self) if auto_init: self.init_client(cache_enabled, cache_path) diff --git a/python/teams_lib_pzsp2_z1/services/teams.py b/python/teams_lib_pzsp2_z1/services/teams.py index 599ed16..508ae13 100644 --- a/python/teams_lib_pzsp2_z1/services/teams.py +++ b/python/teams_lib_pzsp2_z1/services/teams.py @@ -11,6 +11,7 @@ def get(self, teamRef: str) -> Team: "teamRef": teamRef, }, ) + return Team( ID=response["ID"], DisplayName=response["DisplayName"], diff --git a/python/tests/fake_server/data.py b/python/tests/fake_server/data.py index d917f45..d5e7fb7 100644 --- a/python/tests/fake_server/data.py +++ b/python/tests/fake_server/data.py @@ -14,8 +14,15 @@ def __init__(self) -> FakeServerData: DisplayName="Test Team", Description="A team for testing", IsArchived=False, - Visibility="Private", + Visibility="private", ), + Team( + ID="team-456-def", + DisplayName="Another Team", + Description="Another team for testing", + IsArchived=False, + Visibility="public", + ) ] self.channels = { "team-123-abc": [ @@ -34,7 +41,6 @@ def __init__(self) -> FakeServerData: def get_myJoinedTeams_response(self) -> dict: return { - "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#teams", "value": [ { "id": team.ID, @@ -49,7 +55,6 @@ def get_myJoinedTeams_response(self) -> dict: def get_listChannels_response(self, team_id: str) -> dict: return { - "@odata.context": f"https://graph.microsoft.com/v1.0/$metadata#teams('{team_id}')/channels", "value": [ { "id": channel.ID, @@ -58,4 +63,16 @@ def get_listChannels_response(self, team_id: str) -> dict: } for channel in self.channels.get(team_id, []) ], - } \ No newline at end of file + } + + def get_team_response(self, team_id: str) -> dict | None: + return { + "id": team.ID, + "displayName": team.DisplayName, + "description": team.Description, + "isArchived": team.IsArchived, + "visibility": team.Visibility, + } if (team := next((t for t in self.teams if t.ID == team_id), None)) else None + + + diff --git a/python/tests/fake_server/setup.py b/python/tests/fake_server/setup.py index aa92527..63e2b33 100644 --- a/python/tests/fake_server/setup.py +++ b/python/tests/fake_server/setup.py @@ -7,15 +7,25 @@ def setup_fake_server(httpserver) -> FakeServerData: data = FakeServerData() - # Mock response for listing joined teams httpserver.expect_request( "/v1.0/users/me-token-to-replace/joinedTeams", method="GET" ).respond_with_json(data.get_myJoinedTeams_response()) - # Mock response for listing channels in the fake team httpserver.expect_request( f"/v1.0/teams/{data.teams[0].ID}/channels", method="GET" ).respond_with_json(data.get_listChannels_response(data.teams[0].ID)) + httpserver.expect_request( + f"/v1.0/teams/{data.teams[1].ID}/channels", method="GET" + ).respond_with_json(data.get_listChannels_response(data.teams[1].ID)) + + httpserver.expect_request( + f"/v1.0/teams/{data.teams[0].ID}", method="GET" + ).respond_with_json(data.get_team_response(data.teams[0].ID)) + + httpserver.expect_request( + f"/v1.0/teams/{data.teams[1].ID}", method="GET" + ).respond_with_json(data.get_team_response(data.teams[1].ID)) + return data diff --git a/python/tests/test_teams.py b/python/tests/test_teams.py new file mode 100644 index 0000000..1dea2fb --- /dev/null +++ b/python/tests/test_teams.py @@ -0,0 +1,60 @@ +from teams_lib_pzsp2_z1.client import TeamsClient +from tests.init_fake_client import init_fake_client +from tests.fake_server.setup import setup_fake_server + + +def test_list_my_teams_integration(httpserver): + """ + Integration test: Python -> Go Binary -> Fake HTTP -> Python Mock Server + """ + + data = setup_fake_server(httpserver) + + # Init fake client + client = TeamsClient(auto_init=False) + try: + init_fake_client(client, httpserver.url_for("")) + + teams = client.teams.list_my_joined() + + assert len(teams) == len(data.teams) + + assert teams[0].DisplayName == data.teams[0].DisplayName + assert teams[0].ID == data.teams[0].ID + assert teams[0].Description == data.teams[0].Description + assert teams[0].IsArchived == data.teams[0].IsArchived + assert teams[0].Visibility == data.teams[0].Visibility + + assert teams[1].DisplayName == data.teams[1].DisplayName + assert teams[1].ID == data.teams[1].ID + assert teams[1].Description == data.teams[1].Description + assert teams[1].IsArchived == data.teams[1].IsArchived + assert teams[1].Visibility == data.teams[1].Visibility + + finally: + client.close() + +def test_get_team_integration(httpserver): + """ + Integration test: Python -> Go Binary -> Fake HTTP -> Python Mock Server + """ + + data = setup_fake_server(httpserver) + + # Init fake client + client = TeamsClient(auto_init=False) + try: + init_fake_client(client, httpserver.url_for("")) + + team = client.teams.get(data.teams[0].DisplayName) + + print(data.get_team_response(data.teams[0].ID)) + + assert team.DisplayName == data.teams[0].DisplayName + assert team.ID == data.teams[0].ID + assert team.Description == data.teams[0].Description + assert team.IsArchived == data.teams[0].IsArchived + assert team.Visibility == data.teams[0].Visibility + + finally: + client.close() From f9fd045fd3b938464fe6e46c93edea21266f645f Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Tue, 23 Dec 2025 18:27:14 +0100 Subject: [PATCH 08/99] feat: added test to createTeamViaGroup (torture) --- python/tests/fake_server/data.py | 56 ++++++++++++++++++++ python/tests/fake_server/setup.py | 85 +++++++++++++++++++++++++++---- python/tests/test_teams.py | 26 +++++++++- 3 files changed, 156 insertions(+), 11 deletions(-) diff --git a/python/tests/fake_server/data.py b/python/tests/fake_server/data.py index d5e7fb7..035ae8f 100644 --- a/python/tests/fake_server/data.py +++ b/python/tests/fake_server/data.py @@ -1,12 +1,19 @@ from dataclasses import dataclass from teams_lib_pzsp2_z1.model.team import Team from teams_lib_pzsp2_z1.model.channel import Channel +from dataclasses import field @dataclass class FakeServerData: teams: list[Team] channels: dict[str, list[Channel]] + newGroupID: str + newTeamName: str + newGroupMailNickname: str + newTeamVisibility: str + potentialTeams: list[Team] = field(default_factory=list) + def __init__(self) -> FakeServerData: self.teams = [ Team( @@ -38,6 +45,12 @@ def __init__(self) -> FakeServerData: ), ], } + self.newGroupID = "group-789-ghi" + self.newTeamName = "New Team" + self.newGroupMailNickname = "new-team-nickname" + self.newTeamVisibility = "private" + self.potentialTeams = [] + def get_myJoinedTeams_response(self) -> dict: return { @@ -74,5 +87,48 @@ def get_team_response(self, team_id: str) -> dict | None: "visibility": team.Visibility, } if (team := next((t for t in self.teams if t.ID == team_id), None)) else None + def get_createGroup_response(self, request_json: dict) -> dict: + visibility = request_json.get("visibility").lower() + if visibility not in ("private", "public"): + visibility = "private" + + self.potentialTeams.append( + Team( + ID=self.newGroupID, + DisplayName=request_json.get("displayName"), + Description=request_json.get("description"), + IsArchived=False, + Visibility=visibility, + ) + ) + + return { + "id": self.newGroupID, + "displayName": request_json.get("displayName"), + "description": request_json.get("displayName"), + "visibility": visibility, + "groupTypes": ["Unified"], + "mailEnabled": True, + "mailNickname": request_json.get("mailNickname"), + "securityEnabled": False, + "createdDateTime": "2024-01-01T00:00:00Z", + } + + def get_createTeamViaGroup_response(self, group_id: str) -> dict: + team = next((t for t in self.potentialTeams if t.ID == group_id), None) + if not team: + return {} + + self.teams.append(team) + + return { + "id": team.ID, + "displayName": team.DisplayName, + "description": team.Description, + "isArchived": team.IsArchived, + "visibility": team.Visibility, + } + + diff --git a/python/tests/fake_server/setup.py b/python/tests/fake_server/setup.py index 63e2b33..2b4d72b 100644 --- a/python/tests/fake_server/setup.py +++ b/python/tests/fake_server/setup.py @@ -1,31 +1,96 @@ from tests.fake_server.data import FakeServerData +import json +import re +import sys +from werkzeug.wrappers import Response def setup_fake_server(httpserver) -> FakeServerData: """ Sets up a fake HTTP server to simulate Microsoft Graph API responses. + Includes stdout logging for debugging. """ data = FakeServerData() + def make_log_response(request, data_dict: dict, context_msg: str = "") -> Response: + print(f"\n[MOCK SERVER] 🟢 REQUEST: {request.method} {request.path}", file=sys.stdout) + if context_msg: + print(f"[MOCK SERVER] Context: {context_msg}", file=sys.stdout) + + if request.content_type == "application/json": + try: + req_data = request.json + if req_data: + print(f"[MOCK SERVER] Body: {req_data}", file=sys.stdout) + except Exception as e: + print(f"[MOCK SERVER] ⚠️ Body Error: {e}", file=sys.stdout) + + status_code = 200 if data_dict else 404 + if request.method in ["POST", "PUT"] and data_dict: + status_code = 201 + + print(f"[MOCK SERVER] 👉 Response: {status_code}", file=sys.stdout) + + return Response( + json.dumps(data_dict), + mimetype="application/json", + status=status_code + ) + + # GET /users/me/joinedTeams httpserver.expect_request( "/v1.0/users/me-token-to-replace/joinedTeams", method="GET" - ).respond_with_json(data.get_myJoinedTeams_response()) + ).respond_with_handler(lambda req: make_log_response( + req, data.get_myJoinedTeams_response(), "List Joined Teams" + )) + # GET /teams/{id}/channels httpserver.expect_request( - f"/v1.0/teams/{data.teams[0].ID}/channels", method="GET" - ).respond_with_json(data.get_listChannels_response(data.teams[0].ID)) + re.compile(r"^/v1.0/teams/([^/]+)/channels"), + method="GET" + ).respond_with_handler(lambda req: make_log_response( + req, + data.get_listChannels_response(re.search(r"/teams/([^/]+)/channels", req.path).group(1)), + "List Channels" + )) + # POST /groups httpserver.expect_request( - f"/v1.0/teams/{data.teams[1].ID}/channels", method="GET" - ).respond_with_json(data.get_listChannels_response(data.teams[1].ID)) + "/v1.0/groups", + method="POST" + ).respond_with_handler(lambda req: make_log_response( + req, + data.get_createGroup_response(req.json), + "Create Group" + )) + # PUT /groups/{id}/team httpserver.expect_request( - f"/v1.0/teams/{data.teams[0].ID}", method="GET" - ).respond_with_json(data.get_team_response(data.teams[0].ID)) + re.compile(r"^/v1.0/groups/([^/]+)/team"), + method="PUT" + ).respond_with_handler(lambda req: make_log_response( + req, + data.get_createTeamViaGroup_response(re.search(r"/groups/([^/]+)/team", req.path).group(1)), + "Teamify Group (PUT)" + )) + # GET /teams/{id} httpserver.expect_request( - f"/v1.0/teams/{data.teams[1].ID}", method="GET" - ).respond_with_json(data.get_team_response(data.teams[1].ID)) + re.compile(r"^/v1.0/teams/([^/]+)(?:$|\?)"), + method="GET" + ).respond_with_handler(lambda req: make_log_response( + req, + data.get_team_response(re.search(r"/teams/([^/?]+)", req.path).group(1)), + "Get Team Details" + )) - return data + # Fallback for unmatched routes + httpserver.expect_request(re.compile(".*")).respond_with_handler( + lambda req: Response( + json.dumps({"error": f"Route not matched in Mock: {req.method} {req.path}"}), + status=404, + mimetype="application/json" + ) + ) + return data \ No newline at end of file diff --git a/python/tests/test_teams.py b/python/tests/test_teams.py index 1dea2fb..48419e5 100644 --- a/python/tests/test_teams.py +++ b/python/tests/test_teams.py @@ -41,7 +41,6 @@ def test_get_team_integration(httpserver): data = setup_fake_server(httpserver) - # Init fake client client = TeamsClient(auto_init=False) try: init_fake_client(client, httpserver.url_for("")) @@ -58,3 +57,28 @@ def test_get_team_integration(httpserver): finally: client.close() + + +def test_create_team_via_group_integration(httpserver): + """ + Integration test: Python -> Go Binary -> Fake HTTP -> Python Mock Server + """ + + data = setup_fake_server(httpserver) + + client = TeamsClient(auto_init=False) + try: + init_fake_client(client, httpserver.url_for("")) + + team = client.teams.create_via_group( + display_name=data.newTeamName, + mail_nickname=data.newGroupMailNickname, + visibility=data.newTeamVisibility, + ) + + assert team.ID == data.newGroupID + assert team.DisplayName == data.newTeamName + assert team.Visibility == data.newTeamVisibility + + finally: + client.close() From 463e88e4cfbd37dc2c8c556075027c411f5462be Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Sat, 27 Dec 2025 12:15:28 +0100 Subject: [PATCH 09/99] feat: Implemented test to createTeamFromTemplate --- python/teams_lib_pzsp2_z1/services/teams.py | 2 +- python/tests/fake_server/data.py | 15 ++++++++++++ python/tests/fake_server/setup.py | 10 ++++++++ python/tests/test_teams.py | 26 ++++++++++++++++----- 4 files changed, 46 insertions(+), 7 deletions(-) diff --git a/python/teams_lib_pzsp2_z1/services/teams.py b/python/teams_lib_pzsp2_z1/services/teams.py index 508ae13..a756a2a 100644 --- a/python/teams_lib_pzsp2_z1/services/teams.py +++ b/python/teams_lib_pzsp2_z1/services/teams.py @@ -90,7 +90,7 @@ def create_from_template( "owners": owners, }, ) - return response["id"] + return response def archive(self, teamRef: str, spo_read_only_from_members: bool) -> bool: response = self.client.execute( diff --git a/python/tests/fake_server/data.py b/python/tests/fake_server/data.py index 035ae8f..829227f 100644 --- a/python/tests/fake_server/data.py +++ b/python/tests/fake_server/data.py @@ -129,6 +129,21 @@ def get_createTeamViaGroup_response(self, group_id: str) -> dict: "visibility": team.Visibility, } + def get_createTeamFromTemplate_response(self, request_json: dict) -> dict: + new_team_id = "team-from-template-001" + new_team = Team( + ID=new_team_id, + DisplayName=request_json.get("displayName"), + Description=request_json.get("description"), + IsArchived=False, + Visibility="private", + ) + self.teams.append(new_team) + + return { + "id": new_team_id, + } + diff --git a/python/tests/fake_server/setup.py b/python/tests/fake_server/setup.py index 2b4d72b..d82994b 100644 --- a/python/tests/fake_server/setup.py +++ b/python/tests/fake_server/setup.py @@ -84,6 +84,16 @@ def make_log_response(request, data_dict: dict, context_msg: str = "") -> Respon "Get Team Details" )) + # POST /teams + httpserver.expect_request( + "/v1.0/teams", + method="POST" + ).respond_with_handler(lambda req: make_log_response( + req, + data.get_createTeamFromTemplate_response(req.json), + "Create Team from Template (POST)" + )) + # Fallback for unmatched routes httpserver.expect_request(re.compile(".*")).respond_with_handler( lambda req: Response( diff --git a/python/tests/test_teams.py b/python/tests/test_teams.py index 48419e5..0bf28bc 100644 --- a/python/tests/test_teams.py +++ b/python/tests/test_teams.py @@ -35,9 +35,6 @@ def test_list_my_teams_integration(httpserver): client.close() def test_get_team_integration(httpserver): - """ - Integration test: Python -> Go Binary -> Fake HTTP -> Python Mock Server - """ data = setup_fake_server(httpserver) @@ -60,9 +57,6 @@ def test_get_team_integration(httpserver): def test_create_team_via_group_integration(httpserver): - """ - Integration test: Python -> Go Binary -> Fake HTTP -> Python Mock Server - """ data = setup_fake_server(httpserver) @@ -82,3 +76,23 @@ def test_create_team_via_group_integration(httpserver): finally: client.close() + + +def test_create_team_from_template_integration(httpserver): + + data = setup_fake_server(httpserver) + + client = TeamsClient(auto_init=False) + try: + init_fake_client(client, httpserver.url_for("")) + + msg = client.teams.create_from_template( + display_name=data.newTeamName, + description="A team created from a template", + owners=["user-123-abc"], + ) + + assert msg == "id will be given later (async)" + + finally: + client.close() \ No newline at end of file From 8689e6617fefd690a353587780ff98b9ef671bd6 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Sat, 27 Dec 2025 12:19:09 +0100 Subject: [PATCH 10/99] feat: implemented test to ArchiveTeam --- python/tests/fake_server/data.py | 15 +++++++++++++++ python/tests/fake_server/setup.py | 10 ++++++++++ python/tests/test_teams.py | 19 +++++++++++++++++++ 3 files changed, 44 insertions(+) diff --git a/python/tests/fake_server/data.py b/python/tests/fake_server/data.py index 829227f..e27b20e 100644 --- a/python/tests/fake_server/data.py +++ b/python/tests/fake_server/data.py @@ -29,6 +29,13 @@ def __init__(self) -> FakeServerData: Description="Another team for testing", IsArchived=False, Visibility="public", + ), + Team( + ID="archived-team-789-ghi", + DisplayName="Archived Team", + Description="An archived team for testing", + IsArchived=True, + Visibility="private", ) ] self.channels = { @@ -144,6 +151,14 @@ def get_createTeamFromTemplate_response(self, request_json: dict) -> dict: "id": new_team_id, } + def get_archiveTeam_response(self, team_id: str) -> dict: + team = next((t for t in self.teams if t.ID == team_id), None) + if not team: + return {"success": False} + + team.IsArchived = True + return {"success": True} + diff --git a/python/tests/fake_server/setup.py b/python/tests/fake_server/setup.py index d82994b..3e0d6d8 100644 --- a/python/tests/fake_server/setup.py +++ b/python/tests/fake_server/setup.py @@ -94,6 +94,16 @@ def make_log_response(request, data_dict: dict, context_msg: str = "") -> Respon "Create Team from Template (POST)" )) + # POST /teams/{id}/archive + httpserver.expect_request( + re.compile(r"^/v1.0/teams/([^/]+)/archive"), + method="POST" + ).respond_with_handler(lambda req: make_log_response( + req, + data.get_archiveTeam_response(re.search(r"/teams/([^/]+)/archive", req.path).group(1)), + "Archive Team (POST)" + )) + # Fallback for unmatched routes httpserver.expect_request(re.compile(".*")).respond_with_handler( lambda req: Response( diff --git a/python/tests/test_teams.py b/python/tests/test_teams.py index 0bf28bc..325a1c4 100644 --- a/python/tests/test_teams.py +++ b/python/tests/test_teams.py @@ -94,5 +94,24 @@ def test_create_team_from_template_integration(httpserver): assert msg == "id will be given later (async)" + finally: + client.close() + + +def test_archive_team_integration(httpserver): + + data = setup_fake_server(httpserver) + + client = TeamsClient(auto_init=False) + try: + init_fake_client(client, httpserver.url_for("")) + + success = client.teams.archive( + teamRef=data.teams[0].DisplayName, + spo_read_only_from_members=True, + ) + + assert success is True + finally: client.close() \ No newline at end of file From 1166782557ea6912146a9e002d30ab1ccca984da Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Sat, 27 Dec 2025 12:20:50 +0100 Subject: [PATCH 11/99] feat: Implemented test for UnarchiveTeam integration --- python/tests/fake_server/data.py | 8 ++++++++ python/tests/fake_server/setup.py | 10 ++++++++++ python/tests/test_teams.py | 18 ++++++++++++++++++ 3 files changed, 36 insertions(+) diff --git a/python/tests/fake_server/data.py b/python/tests/fake_server/data.py index e27b20e..17daf30 100644 --- a/python/tests/fake_server/data.py +++ b/python/tests/fake_server/data.py @@ -159,6 +159,14 @@ def get_archiveTeam_response(self, team_id: str) -> dict: team.IsArchived = True return {"success": True} + def get_unarchiveTeam_response(self, team_id: str) -> dict: + team = next((t for t in self.teams if t.ID == team_id), None) + if not team: + return {"success": False} + + team.IsArchived = False + return {"success": True} + diff --git a/python/tests/fake_server/setup.py b/python/tests/fake_server/setup.py index 3e0d6d8..649d43a 100644 --- a/python/tests/fake_server/setup.py +++ b/python/tests/fake_server/setup.py @@ -104,6 +104,16 @@ def make_log_response(request, data_dict: dict, context_msg: str = "") -> Respon "Archive Team (POST)" )) + # POST /teams/{id}/unarchive + httpserver.expect_request( + re.compile(r"^/v1.0/teams/([^/]+)/unarchive"), + method="POST" + ).respond_with_handler(lambda req: make_log_response( + req, + data.get_unarchiveTeam_response(re.search(r"/teams/([^/]+)/unarchive", req.path).group(1)), + "Unarchive Team (POST)" + )) + # Fallback for unmatched routes httpserver.expect_request(re.compile(".*")).respond_with_handler( lambda req: Response( diff --git a/python/tests/test_teams.py b/python/tests/test_teams.py index 325a1c4..5c15d2f 100644 --- a/python/tests/test_teams.py +++ b/python/tests/test_teams.py @@ -113,5 +113,23 @@ def test_archive_team_integration(httpserver): assert success is True + finally: + client.close() + + +def test_unarchive_team_integration(httpserver): + + data = setup_fake_server(httpserver) + + client = TeamsClient(auto_init=False) + try: + init_fake_client(client, httpserver.url_for("")) + + success = client.teams.unarchive( + teamRef=data.teams[2].DisplayName, + ) + + assert success is True + finally: client.close() \ No newline at end of file From 4daa3b590a1d8b8f50ed8af888b4fc9948392925 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Sat, 27 Dec 2025 12:38:29 +0100 Subject: [PATCH 12/99] feat: implemented integration tests for delete and restore Teams --- python/teams_lib_pzsp2_z1/services/teams.py | 6 ++-- python/tests/fake_server/data.py | 25 ++++++++++++++ python/tests/fake_server/setup.py | 20 +++++++++++ python/tests/test_teams.py | 37 +++++++++++++++++++++ 4 files changed, 85 insertions(+), 3 deletions(-) diff --git a/python/teams_lib_pzsp2_z1/services/teams.py b/python/teams_lib_pzsp2_z1/services/teams.py index a756a2a..47602a4 100644 --- a/python/teams_lib_pzsp2_z1/services/teams.py +++ b/python/teams_lib_pzsp2_z1/services/teams.py @@ -123,12 +123,12 @@ def delete(self, teamRef: str) -> bool: ) return response == "deleted" - def restore_deleted(self, teamRef: str) -> str: + def restore_deleted(self, deleted_team_ID: str) -> str: response = self.client.execute( cmd_type="request", method="restoreDeletedTeam", params={ - "teamRef": teamRef, + "deletedGroupID": deleted_team_ID, }, ) - return response["id"] + return response diff --git a/python/tests/fake_server/data.py b/python/tests/fake_server/data.py index 17daf30..6821439 100644 --- a/python/tests/fake_server/data.py +++ b/python/tests/fake_server/data.py @@ -167,6 +167,31 @@ def get_unarchiveTeam_response(self, team_id: str) -> dict: team.IsArchived = False return {"success": True} + def get_deleteTeam_response(self, team_id: str) -> dict: + team = next((t for t in self.teams if t.ID == team_id), None) + if not team: + return {"success": False} + + self.teams.remove(team) + return {"success": True} + + def get_restoreTeam_response(self, team_id: str) -> dict: + archived_team = next((t for t in self.teams if t.ID == team_id and t.IsArchived), None) + if not archived_team: + return {"success": False} + + archived_team.IsArchived = False + return { + "id": archived_team.ID, + "displayName": archived_team.DisplayName, + "description": archived_team.Description, + "isArchived": archived_team.IsArchived, + "visibility": archived_team.Visibility, + "mailNickname": self.newGroupMailNickname + } + + + diff --git a/python/tests/fake_server/setup.py b/python/tests/fake_server/setup.py index 649d43a..0a4df85 100644 --- a/python/tests/fake_server/setup.py +++ b/python/tests/fake_server/setup.py @@ -114,6 +114,26 @@ def make_log_response(request, data_dict: dict, context_msg: str = "") -> Respon "Unarchive Team (POST)" )) + # DELETE /teams/{id}/delete + httpserver.expect_request( + re.compile(r"^/v1.0/groups/([^/]+)"), + method="DELETE" + ).respond_with_handler(lambda req: make_log_response( + req, + data.get_deleteTeam_response(re.search(r"/groups/([^/]+)", req.path).group(1)), + "Delete Team (DELETE)" + )) + + # POST /teams/{id}/restore + httpserver.expect_request( + re.compile(r"^/v1.0/directory/deletedItems/([^/]+)/restore"), + method="POST" + ).respond_with_handler(lambda req: make_log_response( + req, + data.get_restoreTeam_response(re.search(r"/directory/deletedItems/([^/]+)/restore", req.path).group(1)), + "Restore Deleted Team (POST)" + )) + # Fallback for unmatched routes httpserver.expect_request(re.compile(".*")).respond_with_handler( lambda req: Response( diff --git a/python/tests/test_teams.py b/python/tests/test_teams.py index 5c15d2f..6a4e35a 100644 --- a/python/tests/test_teams.py +++ b/python/tests/test_teams.py @@ -131,5 +131,42 @@ def test_unarchive_team_integration(httpserver): assert success is True + finally: + client.close() + + +def test_delete_team_integration(httpserver): + + data = setup_fake_server(httpserver) + + client = TeamsClient(auto_init=False) + try: + init_fake_client(client, httpserver.url_for("")) + + success = client.teams.delete( + teamRef=data.teams[0].DisplayName, + ) + + assert success is True + # assert True is False + + finally: + client.close() + + +def test_restore_deleted_team_integration(httpserver): + + data = setup_fake_server(httpserver) + + client = TeamsClient(auto_init=False) + try: + init_fake_client(client, httpserver.url_for("")) + + msg = client.teams.restore_deleted( + deleted_team_ID=data.teams[2].ID, + ) + + assert msg == data.teams[2].ID + finally: client.close() \ No newline at end of file From fc4284cd348e244fe674228324b16cce7176dba9 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Sat, 27 Dec 2025 12:44:32 +0100 Subject: [PATCH 13/99] feat: added integration test to check error handling --- python/teams_lib_pzsp2_z1/client.py | 2 +- python/tests/test_teams.py | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/python/teams_lib_pzsp2_z1/client.py b/python/teams_lib_pzsp2_z1/client.py index 65b994a..a5f28c0 100644 --- a/python/teams_lib_pzsp2_z1/client.py +++ b/python/teams_lib_pzsp2_z1/client.py @@ -2,9 +2,9 @@ import pathlib import platform import subprocess +import sys import threading from typing import Any -import sys from teams_lib_pzsp2_z1 import config from teams_lib_pzsp2_z1.services.channels import ChannelsService diff --git a/python/tests/test_teams.py b/python/tests/test_teams.py index 6a4e35a..3835eff 100644 --- a/python/tests/test_teams.py +++ b/python/tests/test_teams.py @@ -168,5 +168,23 @@ def test_restore_deleted_team_integration(httpserver): assert msg == data.teams[2].ID + finally: + client.close() + + +def test_error_handling_integration(httpserver): + + data = setup_fake_server(httpserver) + + client = TeamsClient(auto_init=False) + try: + init_fake_client(client, httpserver.url_for("")) + + try: + client.teams.get("non-existent-team") + assert False, "Expected an exception for non-existent team" + except Exception as e: + assert "Go Error" in str(e) + finally: client.close() \ No newline at end of file From ecfaa8724b843a5732b317b1df28fc1e04efaa4b Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Sat, 27 Dec 2025 12:47:42 +0100 Subject: [PATCH 14/99] feat: implemented integration test for UpdateTeam --- python/tests/fake_server/data.py | 20 ++++++++++++++++++++ python/tests/fake_server/setup.py | 13 +++++++++++++ python/tests/test_teams.py | 28 +++++++++++++++++++++++++++- 3 files changed, 60 insertions(+), 1 deletion(-) diff --git a/python/tests/fake_server/data.py b/python/tests/fake_server/data.py index 6821439..3334bba 100644 --- a/python/tests/fake_server/data.py +++ b/python/tests/fake_server/data.py @@ -94,6 +94,26 @@ def get_team_response(self, team_id: str) -> dict | None: "visibility": team.Visibility, } if (team := next((t for t in self.teams if t.ID == team_id), None)) else None + def get_updateTeam_response(self, team_id: str, update_json: dict) -> dict | None: + team = next((t for t in self.teams if t.ID == team_id), None) + if not team: + return None + + if "displayName" in update_json: + team.DisplayName = update_json["displayName"] + if "description" in update_json: + team.Description = update_json["description"] + if "visibility" in update_json: + team.Visibility = update_json["visibility"] + + return { + "id": team.ID, + "displayName": team.DisplayName, + "description": team.Description, + "isArchived": team.IsArchived, + "visibility": team.Visibility, + } + def get_createGroup_response(self, request_json: dict) -> dict: visibility = request_json.get("visibility").lower() if visibility not in ("private", "public"): diff --git a/python/tests/fake_server/setup.py b/python/tests/fake_server/setup.py index 0a4df85..3a996f6 100644 --- a/python/tests/fake_server/setup.py +++ b/python/tests/fake_server/setup.py @@ -84,6 +84,19 @@ def make_log_response(request, data_dict: dict, context_msg: str = "") -> Respon "Get Team Details" )) + # PATCH /teams/{id} + httpserver.expect_request( + re.compile(r"^/v1.0/teams/([^/]+)"), + method="PATCH" + ).respond_with_handler(lambda req: make_log_response( + req, + data.get_updateTeam_response( + re.search(r"/teams/([^/]+)", req.path).group(1), + req.json + ), + "Update Team (PATCH)" + )) + # POST /teams httpserver.expect_request( "/v1.0/teams", diff --git a/python/tests/test_teams.py b/python/tests/test_teams.py index 3835eff..6b1873a 100644 --- a/python/tests/test_teams.py +++ b/python/tests/test_teams.py @@ -1,3 +1,4 @@ +from python.teams_lib_pzsp2_z1.model.team import MSTeamsUpdate from teams_lib_pzsp2_z1.client import TeamsClient from tests.init_fake_client import init_fake_client from tests.fake_server.setup import setup_fake_server @@ -56,6 +57,32 @@ def test_get_team_integration(httpserver): client.close() +def test_update_team_integration(httpserver): + + data = setup_fake_server(httpserver) + + client = TeamsClient(auto_init=False) + try: + init_fake_client(client, httpserver.url_for("")) + + updated_description = "Updated team description" + team = client.teams.update( + teamRef=data.teams[0].DisplayName, + update=MSTeamsUpdate( + Description=updated_description, + ), + ) + + assert team.DisplayName == data.teams[0].DisplayName + assert team.ID == data.teams[0].ID + assert team.Description == updated_description + assert team.IsArchived == data.teams[0].IsArchived + assert team.Visibility == data.teams[0].Visibility + + finally: + client.close() + + def test_create_team_via_group_integration(httpserver): data = setup_fake_server(httpserver) @@ -148,7 +175,6 @@ def test_delete_team_integration(httpserver): ) assert success is True - # assert True is False finally: client.close() From 2b0fdd112757f7873f857ff1dc3fa957f08236c3 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Sat, 27 Dec 2025 15:00:44 +0100 Subject: [PATCH 15/99] feat: implemented part of channels endpoints --- .../decoders/decode-messageBody.go | 33 +++++ go/internal/json-client/requests-channels.go | 113 ++++++++++++++++-- go/internal/json-client/requests-teams.go | 6 - 3 files changed, 137 insertions(+), 15 deletions(-) create mode 100644 go/internal/json-client/decoders/decode-messageBody.go diff --git a/go/internal/json-client/decoders/decode-messageBody.go b/go/internal/json-client/decoders/decode-messageBody.go new file mode 100644 index 0000000..a08afd6 --- /dev/null +++ b/go/internal/json-client/decoders/decode-messageBody.go @@ -0,0 +1,33 @@ +package decoders + +import ( + "github.com/mitchellh/mapstructure" + "github.com/pzsp-teams/lib/models" +) + +type messageBodyDTO struct { + ContentType string `json:"contentType"` + Content string `json:"content"` +} + +func DecodeMessageBody(body *messageBodyDTO) (*models.MessageBody, error) { + var result models.MessageBody + + config := &mapstructure.DecoderConfig{ + Metadata: nil, + Result: &result, + TagName: "json", + WeaklyTypedInput: true, + } + + decoder, err := mapstructure.NewDecoder(config) + if err != nil { + return nil, err + } + + if err := decoder.Decode(body); err != nil { + return nil, err + } + + return &result, nil +} diff --git a/go/internal/json-client/requests-channels.go b/go/internal/json-client/requests-channels.go index cdfb18a..241073f 100644 --- a/go/internal/json-client/requests-channels.go +++ b/go/internal/json-client/requests-channels.go @@ -2,22 +2,117 @@ package jsonclient import ( "context" - "fmt" + + "github.com/pzsp-teams/lib-python/internal/json-client/decoders" + "github.com/pzsp-teams/lib/models" ) -type ListChannelsParams struct { +type listChannelsParams struct { + TeamRef string `json:"teamRef"` +} + +func (jsonclient *TeamsJSONClient) ListChannels(p map[string]interface{}) (interface{}, error) { + params, err := decoders.DecodeParams[listChannelsParams](p) + if err != nil { + return nil, err + } + channels, err := jsonclient.client.Channels.ListChannels(context.TODO(), params.TeamRef) + if err != nil { + return nil, err + } + return channels, nil +} + +type baseChannelParams struct { + TeamRef string `json:"teamRef"` + ChannelRef string `json:"channelRef"` +} + +func (jsonclient *TeamsJSONClient) GetChannel(p map[string]interface{}) (interface{}, error) { + params, err := decoders.DecodeParams[baseChannelParams](p) + if err != nil { + return nil, err + } + channel, err := jsonclient.client.Channels.Get(context.TODO(), params.TeamRef, params.ChannelRef) + if err != nil { + return nil, err + } + return channel, nil +} + +type createChannelParams struct { TeamRef string `json:"teamRef"` + Name string `json:"name"` } -func (jsonclient *TeamsJSONClient) ListChannels(params map[string]interface{}) (interface{}, error) { - teamRef, ok := params["teamRef"].(string) - if !ok || teamRef == "" { - return nil, fmt.Errorf("invalid teamRef parameter") +func (jsonclient *TeamsJSONClient) CreateStandardChannel(p map[string]interface{}) (interface{}, error) { + params, err := decoders.DecodeParams[createChannelParams](p) + if err != nil { + return nil, err } - channels, err := jsonclient.client.Channels.ListChannels(context.TODO(), teamRef) + channel, err := jsonclient.client.Channels.CreateStandardChannel(context.TODO(), params.TeamRef, params.Name) if err != nil { return nil, err - } else { - return channels, nil } + return channel, nil +} + +type createPrivateChannelParams struct { + TeamRef string `json:"teamRef"` + Name string `json:"name"` + MemberRefs []string `json:"memberRefs"` + OwnerRefs []string `json:"ownerRefs"` } + +func (jsonclient *TeamsJSONClient) CreatePrivateChannel(p map[string]interface{}) (interface{}, error) { + params, err := decoders.DecodeParams[createPrivateChannelParams](p) + if err != nil { + return nil, err + } + channel, err := jsonclient.client.Channels.CreatePrivateChannel(context.TODO(), params.TeamRef, params.Name, params.MemberRefs, params.OwnerRefs) + if err != nil { + return nil, err + } + return channel, nil +} + +func (jsonclient *TeamsJSONClient) DeleteChannel(p map[string]interface{}) (interface{}, error) { + params, err := decoders.DecodeParams[baseChannelParams](p) + if err != nil { + return nil, err + } + err = jsonclient.client.Channels.Delete(context.TODO(), params.TeamRef, params.ChannelRef) + if err != nil { + return nil, err + } + return "deleted", nil +} + +type messageBodyDTO struct { + ContentType string `json:"contentType"` + Content string `json:"content"` +} + +type sendMessageToChannelParams struct { + TeamRef string `json:"teamRef"` + ChannelRef string `json:"channelRef"` + Body messageBodyDTO `json:"body"` +} + +func (jsonclient *TeamsJSONClient) SendMessageToChannel(p map[string]interface{}) (interface{}, error) { + params, err := decoders.DecodeParams[sendMessageToChannelParams](p) + if err != nil { + return nil, err + } + body, err := decoders.DecodeParams[models.MessageBody](¶ms.Body) + if err != nil { + return nil, err + } + sentMessage, err := jsonclient.client.Channels.SendMessage(context.TODO(), params.TeamRef, params.ChannelRef, *body) + if err != nil { + return nil, err + } + return sentMessage, nil +} + + diff --git a/go/internal/json-client/requests-teams.go b/go/internal/json-client/requests-teams.go index c461bfa..52b77a3 100644 --- a/go/internal/json-client/requests-teams.go +++ b/go/internal/json-client/requests-teams.go @@ -14,9 +14,6 @@ type getTeamParams struct { func (jsonclient *TeamsJSONClient) GetTeam(p map[string]interface{}) (interface{}, error) { params, err := decoders.DecodeParams[getTeamParams](p) - - fmt.Fprintf(os.Stderr, "Decoded params: %+v\n", params) - if err != nil { return nil, err } @@ -24,9 +21,6 @@ func (jsonclient *TeamsJSONClient) GetTeam(p map[string]interface{}) (interface{ if err != nil { return nil, err } - - fmt.Fprintf(os.Stderr, "Received team: %+v\n", team) - return team, nil } From 41e5dff60b108ab77ef43436428bc7e74dab0723 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Sat, 27 Dec 2025 15:11:07 +0100 Subject: [PATCH 16/99] feat: implemented rest of channel endpoints --- go/internal/json-client/requests-channels.go | 143 +++++++++++++++++++ 1 file changed, 143 insertions(+) diff --git a/go/internal/json-client/requests-channels.go b/go/internal/json-client/requests-channels.go index 241073f..56b72b2 100644 --- a/go/internal/json-client/requests-channels.go +++ b/go/internal/json-client/requests-channels.go @@ -115,4 +115,147 @@ func (jsonclient *TeamsJSONClient) SendMessageToChannel(p map[string]interface{} return sentMessage, nil } +type listMessagesOptionsDTO struct { + Top *int32 `json:"top"` + ExpandReplies bool `json:"expandReplies"` +} + +type listMessagesParams struct { + TeamRef string `json:"teamRef"` + ChannelRef string `json:"channelRef"` + Options listMessagesOptionsDTO `json:"options"` +} + +func (jsonclient *TeamsJSONClient) ListMessagesInChannel(p map[string]interface{}) (interface{}, error) { + params, err := decoders.DecodeParams[listMessagesParams](p) + if err != nil { + return nil, err + } + options, err := decoders.DecodeParams[models.ListMessagesOptions](params.Options) + if err != nil { + return nil, err + } + messages, err := jsonclient.client.Channels.ListMessages(context.TODO(), params.TeamRef, params.ChannelRef, options) + if err != nil { + return nil, err + } + return messages, nil +} + +type getMessageParams struct { + TeamRef string `json:"teamRef"` + ChannelRef string `json:"channelRef"` + MessageID string `json:"messageId"` +} + +func (jsonclient *TeamsJSONClient) GetMessageInChannel(p map[string]interface{}) (interface{}, error) { + params, err := decoders.DecodeParams[getMessageParams](p) + if err != nil { + return nil, err + } + message, err := jsonclient.client.Channels.GetMessage(context.TODO(), params.TeamRef, params.ChannelRef, params.MessageID) + if err != nil { + return nil, err + } + return message, nil +} + +type listRepliesParams struct { + TeamRef string `json:"teamRef"` + ChannelRef string `json:"channelRef"` + MessageID string `json:"messageId"` + Top *int32 `json:"top"` +} + +func (jsonclient *TeamsJSONClient) ListMessageRepliesInChannel(p map[string]interface{}) (interface{}, error) { + params, err := decoders.DecodeParams[listRepliesParams](p) + if err != nil { + return nil, err + } + messages, err := jsonclient.client.Channels.ListReplies(context.TODO(), params.TeamRef, params.ChannelRef, params.MessageID, params.Top) + if err != nil { + return nil, err + } + return messages, nil +} + +type getReplyParams struct { + TeamRef string `json:"teamRef"` + ChannelRef string `json:"channelRef"` + MessageID string `json:"messageId"` + ReplyID string `json:"replyId"` +} + +func (jsonclient *TeamsJSONClient) GetMessageReplyInChannel(p map[string]interface{}) (interface{}, error) { + params, err := decoders.DecodeParams[getReplyParams](p) + if err != nil { + return nil, err + } + message, err := jsonclient.client.Channels.GetReply(context.TODO(), params.TeamRef, params.ChannelRef, params.MessageID, params.ReplyID) + if err != nil { + return nil, err + } + return message, nil +} + +func (jsonclient *TeamsJSONClient) ListChannelMembers(p map[string]interface{}) (interface{}, error) { + params, err := decoders.DecodeParams[baseChannelParams](p) + if err != nil { + return nil, err + } + members, err := jsonclient.client.Channels.ListMembers(context.TODO(), params.TeamRef, params.ChannelRef) + if err != nil { + return nil, err + } + return members, nil +} + +type addOrUpdateMemberToChannelParams struct { + TeamRef string `json:"teamRef"` + ChannelRef string `json:"channelRef"` + UserRef string `json:"userRef"` + IsOwner bool `json:"isOwner"` +} + +func (jsonclient *TeamsJSONClient) AddMemberToChannel(p map[string]interface{}) (interface{}, error) { + params, err := decoders.DecodeParams[addOrUpdateMemberToChannelParams](p) + if err != nil { + return nil, err + } + member, err := jsonclient.client.Channels.AddMember(context.TODO(), params.TeamRef, params.ChannelRef, params.UserRef, params.IsOwner) + if err != nil { + return nil, err + } + return member, nil +} + +func (jsonclient *TeamsJSONClient) UpdateMemberInChannel(p map[string]interface{}) (interface{}, error) { + params, err := decoders.DecodeParams[addOrUpdateMemberToChannelParams](p) + if err != nil { + return nil, err + } + member, err := jsonclient.client.Channels.UpdateMemberRole(context.TODO(), params.TeamRef, params.ChannelRef, params.UserRef, params.IsOwner) + if err != nil { + return nil, err + } + return member, nil +} + +type removeMemberFromChannelParams struct { + TeamRef string `json:"teamRef"` + ChannelRef string `json:"channelRef"` + UserRef string `json:"userRef"` +} + +func (jsonclient *TeamsJSONClient) RemoveMemberFromChannel(p map[string]interface{}) (interface{}, error) { + params, err := decoders.DecodeParams[removeMemberFromChannelParams](p) + if err != nil { + return nil, err + } + err = jsonclient.client.Channels.RemoveMember(context.TODO(), params.TeamRef, params.ChannelRef, params.UserRef) + if err != nil { + return nil, err + } + return "removed", nil +} From 52e89aca73d8feaabaff69ed4b2c253d9de3ff75 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Sat, 27 Dec 2025 15:14:55 +0100 Subject: [PATCH 17/99] feat: added channels endpoints to map --- go/internal/json-client/request-map.go | 62 +++++++++++++++++++++-- go/internal/json-client/requests-teams.go | 2 - 2 files changed, 58 insertions(+), 6 deletions(-) diff --git a/go/internal/json-client/request-map.go b/go/internal/json-client/request-map.go index 24d0b65..b0c9099 100644 --- a/go/internal/json-client/request-map.go +++ b/go/internal/json-client/request-map.go @@ -4,10 +4,7 @@ type HandlerFunc func(c *TeamsJSONClient, p map[string]interface{}) (interface{} var Handlers = map[string]HandlerFunc{ - "listChannels": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { - return c.ListChannels(p) - }, - + // TEAMS "getTeam": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { return c.GetTeam(p) }, @@ -43,4 +40,61 @@ var Handlers = map[string]HandlerFunc{ "restoreDeletedTeam": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { return c.RestoreDeletedTeam(p) }, + + // CHANNELS + "listChannels": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + return c.ListChannels(p) + }, + + "getChannel": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + return c.GetChannel(p) + }, + + "createStandardChannel": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + return c.CreateStandardChannel(p) + }, + + "createPrivateChannel": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + return c.CreatePrivateChannel(p) + }, + + "deleteChannel": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + return c.DeleteChannel(p) + }, + + "listChannelMembers": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + return c.ListChannelMembers(p) + }, + + "addMemberToChannel": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + return c.AddMemberToChannel(p) + }, + + "removeMemberFromChannel": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + return c.RemoveMemberFromChannel(p) + }, + + "updateMemberRoleInChannel": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + return c.UpdateMemberInChannel(p) + }, + + "sendMessageToChannel": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + return c.SendMessageToChannel(p) + }, + + "listMessagesInChannel": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + return c.ListMessagesInChannel(p) + }, + + "getMessageInChannel": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + return c.GetMessageInChannel(p) + }, + + "listMessageRepliesInChannel": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + return c.ListMessageRepliesInChannel(p) + }, + + "getMessageReplyInChannel": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + return c.GetMessageReplyInChannel(p) + }, } diff --git a/go/internal/json-client/requests-teams.go b/go/internal/json-client/requests-teams.go index 52b77a3..0c8a3d4 100644 --- a/go/internal/json-client/requests-teams.go +++ b/go/internal/json-client/requests-teams.go @@ -2,8 +2,6 @@ package jsonclient import ( "context" - "fmt" - "os" "github.com/pzsp-teams/lib-python/internal/json-client/decoders" ) From 76eaec011f7dbad9307ee93c9694fe881db475b0 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Sat, 27 Dec 2025 15:16:26 +0100 Subject: [PATCH 18/99] chore: deleted unnecessary file --- .../decoders/decode-messageBody.go | 33 ------------------- 1 file changed, 33 deletions(-) delete mode 100644 go/internal/json-client/decoders/decode-messageBody.go diff --git a/go/internal/json-client/decoders/decode-messageBody.go b/go/internal/json-client/decoders/decode-messageBody.go deleted file mode 100644 index a08afd6..0000000 --- a/go/internal/json-client/decoders/decode-messageBody.go +++ /dev/null @@ -1,33 +0,0 @@ -package decoders - -import ( - "github.com/mitchellh/mapstructure" - "github.com/pzsp-teams/lib/models" -) - -type messageBodyDTO struct { - ContentType string `json:"contentType"` - Content string `json:"content"` -} - -func DecodeMessageBody(body *messageBodyDTO) (*models.MessageBody, error) { - var result models.MessageBody - - config := &mapstructure.DecoderConfig{ - Metadata: nil, - Result: &result, - TagName: "json", - WeaklyTypedInput: true, - } - - decoder, err := mapstructure.NewDecoder(config) - if err != nil { - return nil, err - } - - if err := decoder.Decode(body); err != nil { - return nil, err - } - - return &result, nil -} From 53de9fad330c95167fe8755bf8c52c27de307f19 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Sat, 27 Dec 2025 15:25:48 +0100 Subject: [PATCH 19/99] feat: added models of member and message in python --- python/teams_lib_pzsp2_z1/model/member.py | 10 ++++++++++ python/teams_lib_pzsp2_z1/model/message.py | 23 ++++++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 python/teams_lib_pzsp2_z1/model/member.py create mode 100644 python/teams_lib_pzsp2_z1/model/message.py diff --git a/python/teams_lib_pzsp2_z1/model/member.py b/python/teams_lib_pzsp2_z1/model/member.py new file mode 100644 index 0000000..4d8b88e --- /dev/null +++ b/python/teams_lib_pzsp2_z1/model/member.py @@ -0,0 +1,10 @@ +from dataclasses import dataclass + + +@dataclass +class Member: + ID: str + UserID: str + DisplayName: str + Role: str + Email: str diff --git a/python/teams_lib_pzsp2_z1/model/message.py b/python/teams_lib_pzsp2_z1/model/message.py new file mode 100644 index 0000000..970cf00 --- /dev/null +++ b/python/teams_lib_pzsp2_z1/model/message.py @@ -0,0 +1,23 @@ +from dataclasses import dataclass +from enum import Enum +from time import datetime + + +class MessageContentType(Enum): + TEXT = "text" + HTML = "html" + +@dataclass +class MessageFrom: + UserID: str + UserDisplayName: str + +@dataclass +class Message: + ID: str + Content: str + ContentType: MessageContentType + CreateDateTime: datetime.datetime + From: MessageFrom + ReplyCount: int + From 9ce9d6311403f426583b9719e2cd8e605cda8f20 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Sun, 28 Dec 2025 18:26:38 +0100 Subject: [PATCH 20/99] feat: Wrapped functions for channels and messages --- python/teams_lib_pzsp2_z1/model/message.py | 22 +- .../teams_lib_pzsp2_z1/services/channels.py | 219 ++++++++++++++++++ 2 files changed, 238 insertions(+), 3 deletions(-) diff --git a/python/teams_lib_pzsp2_z1/model/message.py b/python/teams_lib_pzsp2_z1/model/message.py index 970cf00..2e62ae0 100644 --- a/python/teams_lib_pzsp2_z1/model/message.py +++ b/python/teams_lib_pzsp2_z1/model/message.py @@ -1,6 +1,6 @@ from dataclasses import dataclass +from datetime import datetime from enum import Enum -from time import datetime class MessageContentType(Enum): @@ -10,14 +10,30 @@ class MessageContentType(Enum): @dataclass class MessageFrom: UserID: str - UserDisplayName: str + DisplayName: str @dataclass class Message: ID: str Content: str ContentType: MessageContentType - CreateDateTime: datetime.datetime + CreateDateTime: datetime From: MessageFrom ReplyCount: int +@dataclass +class MessageBody: + ContentType: MessageContentType + Content: str + + def __dict__(self): + return { + "ContentType": self.ContentType.value, + "Content": self.Content, + } + +@dataclass +class ListMessagesOptions: + Top: int | None = None + ExpandReplies: bool = False + diff --git a/python/teams_lib_pzsp2_z1/services/channels.py b/python/teams_lib_pzsp2_z1/services/channels.py index 303e74e..62fb4b2 100644 --- a/python/teams_lib_pzsp2_z1/services/channels.py +++ b/python/teams_lib_pzsp2_z1/services/channels.py @@ -1,4 +1,10 @@ from teams_lib_pzsp2_z1.model.channel import Channel +from teams_lib_pzsp2_z1.model.message import ( + Message, + MessageBody, + MessageContentType, + MessageFrom, +) from teams_lib_pzsp2_z1.services.base_service import BaseService @@ -19,3 +25,216 @@ def list_channels(self, teamRef: str) -> list[Channel]: ) for channel in response ] + + def get(self, teamRef: str, channelRef: str) -> Channel: + response = self.client.execute( + cmd_type="request", + method="getChannel", + params={ + "teamRef": teamRef, + "channelRef": channelRef, + }, + ) + + return Channel( + ID=response["ID"], + Name=response["Name"], + IsGeneral=(True if response["IsGeneral"] else False), + ) + + def create_standard(self, teamRef: str, display_name: str) -> Channel: + response = self.client.execute( + cmd_type="request", + method="createStandardChannel", + params={ + "teamRef": teamRef, + "name": display_name, + }, + ) + + return Channel( + ID=response["ID"], + Name=response["Name"], + IsGeneral=(True if response["IsGeneral"] else False), + ) + + def create_private( + self, + teamRef: str, + display_name: str, + member_refs: list[str], + owner_refs: list[str] + ) -> Channel: + response = self.client.execute( + cmd_type="request", + method="createPrivateChannel", + params={ + "teamRef": teamRef, + "name": display_name, + "memberRefs": member_refs, + "ownerRefs": owner_refs, + }, + ) + + return Channel( + ID=response["ID"], + Name=response["Name"], + IsGeneral=(True if response["IsGeneral"] else False), + ) + + def delete(self, teamRef: str, channelRef: str) -> bool: + response = self.client.execute( + cmd_type="request", + method="deleteChannel", + params={ + "teamRef": teamRef, + "channelRef": channelRef, + }, + ) + return response == "deleted" + + def send_message(self, teamRef: str, channelRef: str, body: MessageBody) -> Message: + response = self.client.execute( + cmd_type="request", + method="sendChannelMessage", + params={ + "teamRef": teamRef, + "channelRef": channelRef, + "body": dict(body), + }, + ) + + return Message( + ID=response["ID"], + Content=response["Content"], + ContentType=MessageContentType(response["ContentType"]), + CreateDateTime=response["CreateDateTime"], + From=MessageFrom( + UserID=response["From"]["UserID"], + DisplayName=response["From"]["DisplayName"], + ), + ReplyCount=response["ReplyCount"], + ) + + def list_messages_in_channel( + self, + teamRef: str, + channelRef: str, + top: int | None = 10, + expand_replies: bool = False, + ): + response = self.client.execute( + cmd_type="request", + method="listChannelMessages", + params={ + "teamRef": teamRef, + "channelRef": channelRef, + "options": { + "top": top, + "expandReplies": expand_replies, + }, + }, + ) + + return [ + Message( + ID=message["ID"], + Content=message["Content"], + ContentType=MessageContentType(message["ContentType"]), + CreateDateTime=message["CreateDateTime"], + From=MessageFrom( + UserID=message["From"]["UserID"], + DisplayName=message["From"]["DisplayName"], + ), + ReplyCount=message["ReplyCount"], + ) + for message in response + ] + + def get_message(self, teamRef: str, channelRef: str, messageID: str) -> Message: + response = self.client.execute( + cmd_type="request", + method="getChannelMessage", + params={ + "teamRef": teamRef, + "channelRef": channelRef, + "messageID": messageID, + }, + ) + + return Message( + ID=response["ID"], + Content=response["Content"], + ContentType=MessageContentType(response["ContentType"]), + CreateDateTime=response["CreateDateTime"], + From=MessageFrom( + UserID=response["From"]["UserID"], + DisplayName=response["From"]["DisplayName"], + ), + ReplyCount=response["ReplyCount"], + ) + + def list_message_replies( + self, + teamRef: str, + channelRef: str, + messageID: str, + top: int | None = 10, + ): + response = self.client.execute( + cmd_type="request", + method="listChannelMessageReplies", + params={ + "teamRef": teamRef, + "channelRef": channelRef, + "messageID": messageID, + "top": top, + }, + ) + + return [ + Message( + ID=message["ID"], + Content=message["Content"], + ContentType=MessageContentType(message["ContentType"]), + CreateDateTime=message["CreateDateTime"], + From=MessageFrom( + UserID=message["From"]["UserID"], + DisplayName=message["From"]["DisplayName"], + ), + ReplyCount=message["ReplyCount"], + ) + for message in response + ] + + def get_message_reply( + self, + teamRef: str, + channelRef: str, + messageID: str, + replyID: str, + ) -> Message: + response = self.client.execute( + cmd_type="request", + method="getChannelMessageReply", + params={ + "teamRef": teamRef, + "channelRef": channelRef, + "messageID": messageID, + "replyID": replyID, + }, + ) + + return Message( + ID=response["ID"], + Content=response["Content"], + ContentType=MessageContentType(response["ContentType"]), + CreateDateTime=response["CreateDateTime"], + From=MessageFrom( + UserID=response["From"]["UserID"], + DisplayName=response["From"]["DisplayName"], + ), + ReplyCount=response["ReplyCount"], + ) + + From 83bc2b9a4219730984fab9a72ee17924b7c5191b Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Sun, 28 Dec 2025 18:32:00 +0100 Subject: [PATCH 21/99] feat: wrapped members management functions --- .../teams_lib_pzsp2_z1/services/channels.py | 54 ++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/python/teams_lib_pzsp2_z1/services/channels.py b/python/teams_lib_pzsp2_z1/services/channels.py index 62fb4b2..a5d2302 100644 --- a/python/teams_lib_pzsp2_z1/services/channels.py +++ b/python/teams_lib_pzsp2_z1/services/channels.py @@ -1,4 +1,5 @@ from teams_lib_pzsp2_z1.model.channel import Channel +from teams_lib_pzsp2_z1.model.member import Member from teams_lib_pzsp2_z1.model.message import ( Message, MessageBody, @@ -237,4 +238,55 @@ def get_message_reply( ReplyCount=response["ReplyCount"], ) - + def list_members(self, teamRef: str, channelRef: str) -> list[Member]: + response = self.client.execute( + cmd_type="request", + method="listChannelMembers", + params={ + "teamRef": teamRef, + "channelRef": channelRef, + }, + ) + return [Member(**member) for member in response] + + def add_member( + self, teamRef: str, channelRef: str, userRef: str, isOwner: bool + ) -> Member: + response = self.client.execute( + cmd_type="request", + method="addChannelMember", + params={ + "teamRef": teamRef, + "channelRef": channelRef, + "userRef": userRef, + "isOwner": isOwner, + }, + ) + return Member(**response) + + def update_member_role( + self, teamRef: str, channelRef: str, userRef: str, isOwner: bool + ) -> Member: + response = self.client.execute( + cmd_type="request", + method="updateChannelMemberRole", + params={ + "teamRef": teamRef, + "channelRef": channelRef, + "userRef": userRef, + "isOwner": isOwner, + }, + ) + return Member(**response) + + def remove_member(self, teamRef: str, channelRef: str, userRef: str) -> bool: + response = self.client.execute( + cmd_type="request", + method="removeChannelMember", + params={ + "teamRef": teamRef, + "channelRef": channelRef, + "userRef": userRef, + }, + ) + return response == "removed" From 2844dec05f4fc224bb0bab929aad43b360ec5638 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Sun, 28 Dec 2025 18:47:23 +0100 Subject: [PATCH 22/99] feat: Added integration test for get_channel --- python/tests/fake_server/data.py | 11 +++++++++++ python/tests/fake_server/setup.py | 13 +++++++++++++ python/tests/test_channels.py | 21 +++++++++++++++++++++ 3 files changed, 45 insertions(+) diff --git a/python/tests/fake_server/data.py b/python/tests/fake_server/data.py index 3334bba..e79d8e8 100644 --- a/python/tests/fake_server/data.py +++ b/python/tests/fake_server/data.py @@ -210,6 +210,17 @@ def get_restoreTeam_response(self, team_id: str) -> dict: "mailNickname": self.newGroupMailNickname } + def get_channel_response(self, team_id: str, channel_id: str) -> dict | None: + channel = next((c for c in self.channels.get(team_id, []) if c.ID == channel_id), None) + if not channel: + return None + + return { + "id": channel.ID, + "displayName": channel.Name, + "isGeneral": channel.IsGeneral, + } + diff --git a/python/tests/fake_server/setup.py b/python/tests/fake_server/setup.py index 3a996f6..e599d1a 100644 --- a/python/tests/fake_server/setup.py +++ b/python/tests/fake_server/setup.py @@ -44,6 +44,19 @@ def make_log_response(request, data_dict: dict, context_msg: str = "") -> Respon req, data.get_myJoinedTeams_response(), "List Joined Teams" )) + # GET /teams/{team_id}/channels/{channel_id} + httpserver.expect_request( + re.compile(r"^/v1.0/teams/([^/]+)/channels/([^/]+)"), + method="GET" + ).respond_with_handler(lambda req: make_log_response( + req, + data.get_channel_response( + re.search(r"/teams/([^/]+)/channels/([^/]+)", req.path).group(1), + re.search(r"/teams/([^/]+)/channels/([^/]+)", req.path).group(2) + ), + "Get Channel Details" + )) + # GET /teams/{id}/channels httpserver.expect_request( re.compile(r"^/v1.0/teams/([^/]+)/channels"), diff --git a/python/tests/test_channels.py b/python/tests/test_channels.py index 121f315..f700c7c 100644 --- a/python/tests/test_channels.py +++ b/python/tests/test_channels.py @@ -29,3 +29,24 @@ def test_list_channels_integration(httpserver): finally: client.close() + + +def test_get_channel_integration(httpserver): + + data = setup_fake_server(httpserver) + + client = TeamsClient(auto_init=False) + try: + init_fake_client(client, httpserver.url_for("")) + + channel = client.channels.get( + teamRef=data.teams[0].DisplayName, + channelRef=data.channels[data.teams[0].ID][1].Name, + ) + + assert channel.Name == data.channels[data.teams[0].ID][1].Name + assert channel.ID == data.channels[data.teams[0].ID][1].ID + assert channel.IsGeneral == data.channels[data.teams[0].ID][1].IsGeneral + + finally: + client.close() From ad8335288287693e7488d029e9c4fb2ed3c1fa95 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Sun, 28 Dec 2025 19:00:49 +0100 Subject: [PATCH 23/99] feat: added test for creating standard channel --- python/tests/fake_server/data.py | 26 ++++++++++++++++++++++++++ python/tests/fake_server/setup.py | 13 +++++++++++++ python/tests/test_channels.py | 21 +++++++++++++++++++++ 3 files changed, 60 insertions(+) diff --git a/python/tests/fake_server/data.py b/python/tests/fake_server/data.py index e79d8e8..946a77c 100644 --- a/python/tests/fake_server/data.py +++ b/python/tests/fake_server/data.py @@ -57,6 +57,8 @@ def __init__(self) -> FakeServerData: self.newGroupMailNickname = "new-team-nickname" self.newTeamVisibility = "private" self.potentialTeams = [] + self.newChannelName = "New Channel" + self.newChannelID = "19:newchannelid@thread.tacv2" def get_myJoinedTeams_response(self) -> dict: @@ -221,6 +223,30 @@ def get_channel_response(self, team_id: str, channel_id: str) -> dict | None: "isGeneral": channel.IsGeneral, } + def get_create_channel_response(self, team_id: str, request_json: dict) -> dict: + display_name = request_json.get("displayName") + description = request_json.get("description", display_name) + + new_channel = Channel( + ID=self.newChannelID, + Name=display_name, + IsGeneral=False, + ) + + if team_id not in self.channels: + self.channels[team_id] = [] + + self.channels[team_id].append(new_channel) + + return { + "id": new_channel.ID, + "displayName": new_channel.Name, + "description": description, + "isGeneral": new_channel.IsGeneral, + "membershipType": "standard" + } + + diff --git a/python/tests/fake_server/setup.py b/python/tests/fake_server/setup.py index e599d1a..0cb0f29 100644 --- a/python/tests/fake_server/setup.py +++ b/python/tests/fake_server/setup.py @@ -160,6 +160,19 @@ def make_log_response(request, data_dict: dict, context_msg: str = "") -> Respon "Restore Deleted Team (POST)" )) + # POST /teams/{id}/channels + httpserver.expect_request( + re.compile(r"^/v1.0/teams/([^/]+)/channels"), + method="POST" + ).respond_with_handler(lambda req: make_log_response( + req, + data.get_create_channel_response( + re.search(r"/teams/([^/]+)/channels", req.path).group(1), + req.json + ), + "Create Channel (POST)" + )) + # Fallback for unmatched routes httpserver.expect_request(re.compile(".*")).respond_with_handler( lambda req: Response( diff --git a/python/tests/test_channels.py b/python/tests/test_channels.py index f700c7c..e9f46f2 100644 --- a/python/tests/test_channels.py +++ b/python/tests/test_channels.py @@ -50,3 +50,24 @@ def test_get_channel_integration(httpserver): finally: client.close() + + +def test_create_standard_channel_integration(httpserver): + + data = setup_fake_server(httpserver) + + client = TeamsClient(auto_init=False) + try: + init_fake_client(client, httpserver.url_for("")) + + channel = client.channels.create_standard( + teamRef=data.teams[0].DisplayName, + display_name=data.newChannelName, + ) + + assert channel.Name == data.newChannelName + assert channel.ID == data.newChannelID + assert channel.IsGeneral is False + + finally: + client.close() \ No newline at end of file From 31b2e3dc36521eb42a002ff959d9763f0b31833a Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Sun, 28 Dec 2025 19:36:02 +0100 Subject: [PATCH 24/99] feat: added tests to getting messages --- python/teams_lib_pzsp2_z1/model/message.py | 7 +- .../teams_lib_pzsp2_z1/services/channels.py | 64 +++++------ python/tests/fake_server/data.py | 104 +++++++++++++++++ python/tests/fake_server/setup.py | 42 +++++++ python/tests/test_channels.py | 107 +++++++++++++++++- 5 files changed, 289 insertions(+), 35 deletions(-) diff --git a/python/teams_lib_pzsp2_z1/model/message.py b/python/teams_lib_pzsp2_z1/model/message.py index 2e62ae0..6bee67d 100644 --- a/python/teams_lib_pzsp2_z1/model/message.py +++ b/python/teams_lib_pzsp2_z1/model/message.py @@ -7,20 +7,23 @@ class MessageContentType(Enum): TEXT = "text" HTML = "html" + @dataclass class MessageFrom: UserID: str DisplayName: str + @dataclass class Message: ID: str Content: str ContentType: MessageContentType - CreateDateTime: datetime + CreatedDateTime: datetime From: MessageFrom ReplyCount: int + @dataclass class MessageBody: ContentType: MessageContentType @@ -32,8 +35,8 @@ def __dict__(self): "Content": self.Content, } + @dataclass class ListMessagesOptions: Top: int | None = None ExpandReplies: bool = False - diff --git a/python/teams_lib_pzsp2_z1/services/channels.py b/python/teams_lib_pzsp2_z1/services/channels.py index a5d2302..11a22ce 100644 --- a/python/teams_lib_pzsp2_z1/services/channels.py +++ b/python/teams_lib_pzsp2_z1/services/channels.py @@ -60,11 +60,11 @@ def create_standard(self, teamRef: str, display_name: str) -> Channel: ) def create_private( - self, - teamRef: str, - display_name: str, - member_refs: list[str], - owner_refs: list[str] + self, + teamRef: str, + display_name: str, + member_refs: list[str], + owner_refs: list[str], ) -> Channel: response = self.client.execute( cmd_type="request", @@ -109,7 +109,7 @@ def send_message(self, teamRef: str, channelRef: str, body: MessageBody) -> Mess ID=response["ID"], Content=response["Content"], ContentType=MessageContentType(response["ContentType"]), - CreateDateTime=response["CreateDateTime"], + CreatedDateTime=response["CreatedDateTime"], From=MessageFrom( UserID=response["From"]["UserID"], DisplayName=response["From"]["DisplayName"], @@ -117,16 +117,16 @@ def send_message(self, teamRef: str, channelRef: str, body: MessageBody) -> Mess ReplyCount=response["ReplyCount"], ) - def list_messages_in_channel( - self, - teamRef: str, - channelRef: str, - top: int | None = 10, - expand_replies: bool = False, + def list_messages( + self, + teamRef: str, + channelRef: str, + top: int | None = 10, + expand_replies: bool = False, ): response = self.client.execute( cmd_type="request", - method="listChannelMessages", + method="listMessagesInChannel", params={ "teamRef": teamRef, "channelRef": channelRef, @@ -142,7 +142,7 @@ def list_messages_in_channel( ID=message["ID"], Content=message["Content"], ContentType=MessageContentType(message["ContentType"]), - CreateDateTime=message["CreateDateTime"], + CreatedDateTime=message["CreatedDateTime"], From=MessageFrom( UserID=message["From"]["UserID"], DisplayName=message["From"]["DisplayName"], @@ -155,7 +155,7 @@ def list_messages_in_channel( def get_message(self, teamRef: str, channelRef: str, messageID: str) -> Message: response = self.client.execute( cmd_type="request", - method="getChannelMessage", + method="getMessageInChannel", params={ "teamRef": teamRef, "channelRef": channelRef, @@ -167,7 +167,7 @@ def get_message(self, teamRef: str, channelRef: str, messageID: str) -> Message: ID=response["ID"], Content=response["Content"], ContentType=MessageContentType(response["ContentType"]), - CreateDateTime=response["CreateDateTime"], + CreatedDateTime=response["CreatedDateTime"], From=MessageFrom( UserID=response["From"]["UserID"], DisplayName=response["From"]["DisplayName"], @@ -176,15 +176,15 @@ def get_message(self, teamRef: str, channelRef: str, messageID: str) -> Message: ) def list_message_replies( - self, - teamRef: str, - channelRef: str, - messageID: str, - top: int | None = 10, + self, + teamRef: str, + channelRef: str, + messageID: str, + top: int | None = 10, ): response = self.client.execute( cmd_type="request", - method="listChannelMessageReplies", + method="listMessageRepliesInChannel", params={ "teamRef": teamRef, "channelRef": channelRef, @@ -198,7 +198,7 @@ def list_message_replies( ID=message["ID"], Content=message["Content"], ContentType=MessageContentType(message["ContentType"]), - CreateDateTime=message["CreateDateTime"], + CreatedDateTime=message["CreatedDateTime"], From=MessageFrom( UserID=message["From"]["UserID"], DisplayName=message["From"]["DisplayName"], @@ -209,15 +209,15 @@ def list_message_replies( ] def get_message_reply( - self, - teamRef: str, - channelRef: str, - messageID: str, - replyID: str, + self, + teamRef: str, + channelRef: str, + messageID: str, + replyID: str, ) -> Message: response = self.client.execute( cmd_type="request", - method="getChannelMessageReply", + method="getMessageReplyInChannel", params={ "teamRef": teamRef, "channelRef": channelRef, @@ -230,7 +230,7 @@ def get_message_reply( ID=response["ID"], Content=response["Content"], ContentType=MessageContentType(response["ContentType"]), - CreateDateTime=response["CreateDateTime"], + CreatedDateTime=response["CreatedDateTime"], From=MessageFrom( UserID=response["From"]["UserID"], DisplayName=response["From"]["DisplayName"], @@ -250,7 +250,7 @@ def list_members(self, teamRef: str, channelRef: str) -> list[Member]: return [Member(**member) for member in response] def add_member( - self, teamRef: str, channelRef: str, userRef: str, isOwner: bool + self, teamRef: str, channelRef: str, userRef: str, isOwner: bool ) -> Member: response = self.client.execute( cmd_type="request", @@ -265,7 +265,7 @@ def add_member( return Member(**response) def update_member_role( - self, teamRef: str, channelRef: str, userRef: str, isOwner: bool + self, teamRef: str, channelRef: str, userRef: str, isOwner: bool ) -> Member: response = self.client.execute( cmd_type="request", diff --git a/python/tests/fake_server/data.py b/python/tests/fake_server/data.py index 946a77c..3d3f990 100644 --- a/python/tests/fake_server/data.py +++ b/python/tests/fake_server/data.py @@ -1,6 +1,7 @@ from dataclasses import dataclass from teams_lib_pzsp2_z1.model.team import Team from teams_lib_pzsp2_z1.model.channel import Channel +from teams_lib_pzsp2_z1.model.message import Message, MessageFrom from dataclasses import field @dataclass @@ -52,6 +53,58 @@ def __init__(self) -> FakeServerData: ), ], } + self.messages = { + "19:123123@thread.tacv2": [ + Message( + ID="msg-001", + Content="Hello, team!", + ContentType="text", + From=MessageFrom( + UserID="user-123-abc", + DisplayName="Alice" + ), + CreatedDateTime="2024-01-01T10:00:00Z", + ReplyCount=0, + ), + Message( + ID="msg-002", + Content="Don't forget the meeting at 3 PM.", + ContentType="text", + From=MessageFrom( + UserID="user-456-def", + DisplayName="Bob" + ), + CreatedDateTime="2024-01-01T11:00:00Z", + ReplyCount=2, + ), + ], + } + self.replies = { + "msg-002": [ + Message( + ID="msg-002-reply-001", + Content="Thanks for the reminder!", + ContentType="text", + From=MessageFrom( + UserID="user-789-ghi", + DisplayName="Charlie" + ), + CreatedDateTime="2024-01-01T12:00:00Z", + ReplyCount=0, + ), + Message( + ID="msg-002-reply-002", + Content="I'll be there.", + ContentType="text", + From=MessageFrom( + UserID="user-123-abc", + DisplayName="Alice" + ), + CreatedDateTime="2024-01-01T12:30:00Z", + ReplyCount=0, + ), + ], + } self.newGroupID = "group-789-ghi" self.newTeamName = "New Team" self.newGroupMailNickname = "new-team-nickname" @@ -246,6 +299,57 @@ def get_create_channel_response(self, team_id: str, request_json: dict) -> dict: "membershipType": "standard" } + def get_delete_channel_response(self, team_id: str, channel_id: str) -> dict: + channel = next((c for c in self.channels.get(team_id, []) if c.ID == channel_id), None) + if not channel: + return {"success": False} + + self.channels[team_id].remove(channel) + return {"success": True} + + def get_list_messages_response(self, team_id: str, channel_id: str) -> dict: + return { + "value": [ + { + "id": message.ID, + "body": { + "content": message.Content, + "contentType": message.ContentType, + }, + "from": { + "user": { + "id": message.From.UserID, + "displayName": message.From.DisplayName, + } + }, + "createdDateTime": message.CreatedDateTime, + "replies": [{"id": f"dummy-reply-{i}"} for i in range(message.ReplyCount)] + } + for message in self.messages.get(channel_id, []) + ], + } + + def get_message_response(self, team_id: str, channel_id: str, message_id: str) -> dict | None: + message = next((m for m in self.messages.get(channel_id, []) if m.ID == message_id), None) + if not message: + return None + + return { + "id": message.ID, + "body": { + "content": message.Content, + "contentType": message.ContentType, + }, + "from": { + "user": { + "id": message.From.UserID, + "displayName": message.From.DisplayName, + } + }, + "createdDateTime": message.CreatedDateTime, + "replies": [{"id": f"dummy-reply-{i}"} for i in range(message.ReplyCount)] + } + diff --git a/python/tests/fake_server/setup.py b/python/tests/fake_server/setup.py index 0cb0f29..fa218ce 100644 --- a/python/tests/fake_server/setup.py +++ b/python/tests/fake_server/setup.py @@ -44,6 +44,33 @@ def make_log_response(request, data_dict: dict, context_msg: str = "") -> Respon req, data.get_myJoinedTeams_response(), "List Joined Teams" )) + # GET /teams/{team_id}/channels/{channel_id}/messages/{message_id} + httpserver.expect_request( + re.compile(r"^/v1.0/teams/([^/]+)/channels/([^/]+)/messages/([^/]+)"), + method="GET" + ).respond_with_handler(lambda req: make_log_response( + req, + data.get_message_response( + re.search(r"/teams/([^/]+)/channels/([^/]+)/messages/([^/]+)", req.path).group(1), + re.search(r"/teams/([^/]+)/channels/([^/]+)/messages/([^/]+)", req.path).group(2), + re.search(r"/teams/([^/]+)/channels/([^/]+)/messages/([^/]+)", req.path).group(3) + ), + "Get Channel Message" + )) + + # GET /teams/{team_id}/channels/{channel_id}/messages + httpserver.expect_request( + re.compile(r"^/v1.0/teams/([^/]+)/channels/([^/]+)/messages"), + method="GET" + ).respond_with_handler(lambda req: make_log_response( + req, + data.get_list_messages_response( + re.search(r"/teams/([^/]+)/channels/([^/]+)/messages", req.path).group(1), + re.search(r"/teams/([^/]+)/channels/([^/]+)/messages", req.path).group(2) + ), + "List Channel Messages" + )) + # GET /teams/{team_id}/channels/{channel_id} httpserver.expect_request( re.compile(r"^/v1.0/teams/([^/]+)/channels/([^/]+)"), @@ -173,6 +200,21 @@ def make_log_response(request, data_dict: dict, context_msg: str = "") -> Respon "Create Channel (POST)" )) + # DELETE /teams/{team_id}/channels/{channel_id} + httpserver.expect_request( + re.compile(r"^/v1.0/teams/([^/]+)/channels/([^/]+)"), + method="DELETE" + ).respond_with_handler(lambda req: make_log_response( + req, + data.get_delete_channel_response( + re.search(r"/teams/([^/]+)/channels/([^/]+)", req.path).group(1), + re.search(r"/teams/([^/]+)/channels/([^/]+)", req.path).group(2) + ), + "Delete Channel (DELETE)" + )) + + + # Fallback for unmatched routes httpserver.expect_request(re.compile(".*")).respond_with_handler( lambda req: Response( diff --git a/python/tests/test_channels.py b/python/tests/test_channels.py index e9f46f2..06524dc 100644 --- a/python/tests/test_channels.py +++ b/python/tests/test_channels.py @@ -1,6 +1,7 @@ from teams_lib_pzsp2_z1.client import TeamsClient from tests.init_fake_client import init_fake_client from tests.fake_server.setup import setup_fake_server +from teams_lib_pzsp2_z1.model.message import MessageContentType def test_list_channels_integration(httpserver): @@ -70,4 +71,108 @@ def test_create_standard_channel_integration(httpserver): assert channel.IsGeneral is False finally: - client.close() \ No newline at end of file + client.close() + + +def test_create_private_channel_integration(httpserver): + + data = setup_fake_server(httpserver) + + client = TeamsClient(auto_init=False) + try: + init_fake_client(client, httpserver.url_for("")) + + channel = client.channels.create_private( + teamRef=data.teams[0].DisplayName, + display_name=data.newChannelName, + member_refs=[], + owner_refs=[], + ) + + assert channel.Name == data.newChannelName + assert channel.ID == data.newChannelID + assert channel.IsGeneral is False + + finally: + client.close() + + +def test_delete_channel_integration(httpserver): + + data = setup_fake_server(httpserver) + + client = TeamsClient(auto_init=False) + try: + init_fake_client(client, httpserver.url_for("")) + + success = client.channels.delete( + teamRef=data.teams[0].DisplayName, + channelRef=data.channels[data.teams[0].ID][1].Name, + ) + + assert success is True + + finally: + client.close() + + +def test_list_messages_integration(httpserver): + + data = setup_fake_server(httpserver) + + client = TeamsClient(auto_init=False) + try: + init_fake_client(client, httpserver.url_for("")) + + messages = client.channels.list_messages( + teamRef=data.teams[0].DisplayName, + channelRef=data.channels[data.teams[0].ID][0].Name, + ) + + assert len(messages) == len(data.messages[data.channels[data.teams[0].ID][0].ID]) + assert messages[0].ID == data.messages[data.channels[data.teams[0].ID][0].ID][0].ID + assert messages[0].Content == data.messages[data.channels[data.teams[0].ID][0].ID][0].Content + assert messages[0].ContentType == MessageContentType(data.messages[data.channels[data.teams[0].ID][0].ID][0].ContentType) + assert messages[0].From.UserID == data.messages[data.channels[data.teams[0].ID][0].ID][0].From.UserID + assert messages[0].From.DisplayName == data.messages[data.channels[data.teams[0].ID][0].ID][0].From.DisplayName + assert messages[0].ReplyCount == data.messages[data.channels[data.teams[0].ID][0].ID][0].ReplyCount + assert messages[0].CreatedDateTime == data.messages[data.channels[data.teams[0].ID][0].ID][0].CreatedDateTime + + assert messages[1].ID == data.messages[data.channels[data.teams[0].ID][0].ID][1].ID + assert messages[1].Content == data.messages[data.channels[data.teams[0].ID][0].ID][1].Content + assert messages[1].ContentType == MessageContentType(data.messages[data.channels[data.teams[0].ID][0].ID][1].ContentType) + assert messages[1].From.UserID == data.messages[data.channels[data.teams[0].ID][0].ID][1].From.UserID + assert messages[1].From.DisplayName == data.messages[data.channels[data.teams[0].ID][0].ID][1].From.DisplayName + assert messages[1].ReplyCount == data.messages[data.channels[data.teams[0].ID][0].ID][1].ReplyCount + assert messages[1].CreatedDateTime == data.messages[data.channels[data.teams[0].ID][0].ID][1].CreatedDateTime + + finally: + client.close() + + +def test_get_message_integration(httpserver): + + data = setup_fake_server(httpserver) + + client = TeamsClient(auto_init=False) + try: + init_fake_client(client, httpserver.url_for("")) + + message = client.channels.get_message( + teamRef=data.teams[0].DisplayName, + channelRef=data.channels[data.teams[0].ID][0].Name, + messageID=data.messages[data.channels[data.teams[0].ID][0].ID][0].ID, + ) + + assert message.ID == data.messages[data.channels[data.teams[0].ID][0].ID][0].ID + assert message.Content == data.messages[data.channels[data.teams[0].ID][0].ID][0].Content + assert message.ContentType == MessageContentType(data.messages[data.channels[data.teams[0].ID][0].ID][0].ContentType) + assert message.From.UserID == data.messages[data.channels[data.teams[0].ID][0].ID][0].From.UserID + assert message.From.DisplayName == data.messages[data.channels[data.teams[0].ID][0].ID][0].From.DisplayName + assert message.ReplyCount == data.messages[data.channels[data.teams[0].ID][0].ID][0].ReplyCount + assert message.CreatedDateTime == data.messages[data.channels[data.teams[0].ID][0].ID][0].CreatedDateTime + + finally: + client.close() + + From 221ad3881c464ffbd62bb397486132542547dbb6 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Mon, 29 Dec 2025 10:55:43 +0100 Subject: [PATCH 25/99] feat: implemented tests for replies --- python/tests/fake_server/data.py | 41 ++++++++++++++++++++ python/tests/fake_server/setup.py | 30 +++++++++++++++ python/tests/test_channels.py | 62 +++++++++++++++++++++++++++++++ 3 files changed, 133 insertions(+) diff --git a/python/tests/fake_server/data.py b/python/tests/fake_server/data.py index 3d3f990..872c5a5 100644 --- a/python/tests/fake_server/data.py +++ b/python/tests/fake_server/data.py @@ -350,6 +350,47 @@ def get_message_response(self, team_id: str, channel_id: str, message_id: str) - "replies": [{"id": f"dummy-reply-{i}"} for i in range(message.ReplyCount)] } + def get_list_replies_response(self, team_id: str, channel_id: str, message_id: str) -> dict: + return { + "value": [ + { + "id": reply.ID, + "body": { + "content": reply.Content, + "contentType": reply.ContentType, + }, + "from": { + "user": { + "id": reply.From.UserID, + "displayName": reply.From.DisplayName, + } + }, + "createdDateTime": reply.CreatedDateTime, + } + for reply in self.replies.get(message_id, []) + ], + } + + def get_reply_response(self, team_id: str, channel_id: str, message_id: str, reply_id: str) -> dict | None: + reply = next((r for r in self.replies.get(message_id, []) if r.ID == reply_id), None) + if not reply: + return None + + return { + "id": reply.ID, + "body": { + "content": reply.Content, + "contentType": reply.ContentType, + }, + "from": { + "user": { + "id": reply.From.UserID, + "displayName": reply.From.DisplayName, + } + }, + "createdDateTime": reply.CreatedDateTime, + } + diff --git a/python/tests/fake_server/setup.py b/python/tests/fake_server/setup.py index fa218ce..2c25da0 100644 --- a/python/tests/fake_server/setup.py +++ b/python/tests/fake_server/setup.py @@ -44,6 +44,36 @@ def make_log_response(request, data_dict: dict, context_msg: str = "") -> Respon req, data.get_myJoinedTeams_response(), "List Joined Teams" )) + + # GET /teams/{team_id}/channels/{channel_id}/messages/{message_id}/replies/{reply_id} + httpserver.expect_request( + re.compile(r"^/v1.0/teams/([^/]+)/channels/([^/]+)/messages/([^/]+)/replies/([^/]+)"), + method="GET" + ).respond_with_handler(lambda req: make_log_response( + req, + data.get_reply_response( + re.search(r"/teams/([^/]+)/channels/([^/]+)/messages/([^/]+)/replies/([^/]+)", req.path).group(1), + re.search(r"/teams/([^/]+)/channels/([^/]+)/messages/([^/]+)/replies/([^/]+)", req.path).group(2), + re.search(r"/teams/([^/]+)/channels/([^/]+)/messages/([^/]+)/replies/([^/]+)", req.path).group(3), + re.search(r"/teams/([^/]+)/channels/([^/]+)/messages/([^/]+)/replies/([^/]+)", req.path).group(4) + ), + "Get Message Reply" + )) + + # GET /teams/{team_id}/channels/{channel_id}/messages/{message_id}/replies + httpserver.expect_request( + re.compile(r"^/v1.0/teams/([^/]+)/channels/([^/]+)/messages/([^/]+)/replies"), + method="GET" + ).respond_with_handler(lambda req: make_log_response( + req, + data.get_list_replies_response( + re.search(r"/teams/([^/]+)/channels/([^/]+)/messages/([^/]+)/replies", req.path).group(1), + re.search(r"/teams/([^/]+)/channels/([^/]+)/messages/([^/]+)/replies", req.path).group(2), + re.search(r"/teams/([^/]+)/channels/([^/]+)/messages/([^/]+)/replies", req.path).group(3) + ), + "List Message Replies" + )) + # GET /teams/{team_id}/channels/{channel_id}/messages/{message_id} httpserver.expect_request( re.compile(r"^/v1.0/teams/([^/]+)/channels/([^/]+)/messages/([^/]+)"), diff --git a/python/tests/test_channels.py b/python/tests/test_channels.py index 06524dc..dcf8196 100644 --- a/python/tests/test_channels.py +++ b/python/tests/test_channels.py @@ -176,3 +176,65 @@ def test_get_message_integration(httpserver): client.close() +def test_list_replies_integration(httpserver): + + data = setup_fake_server(httpserver) + + client = TeamsClient(auto_init=False) + try: + init_fake_client(client, httpserver.url_for("")) + + replies = client.channels.list_message_replies( + teamRef=data.teams[0].DisplayName, + channelRef=data.channels[data.teams[0].ID][0].Name, + messageID=data.messages[data.channels[data.teams[0].ID][0].ID][1].ID, + ) + + assert len(replies) == len(data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID]) + assert replies[0].ID == data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][0].ID + assert replies[0].Content == data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][0].Content + assert replies[0].ContentType == MessageContentType(data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][0].ContentType) + assert replies[0].From.UserID == data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][0].From.UserID + assert replies[0].From.DisplayName == data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][0].From.DisplayName + assert replies[0].CreatedDateTime == data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][0].CreatedDateTime + + assert replies[1].ID == data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][1].ID + assert replies[1].Content == data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][1].Content + assert replies[1].ContentType == MessageContentType(data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][1].ContentType) + assert replies[1].From.UserID == data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][1].From.UserID + assert replies[1].From.DisplayName == data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][1].From.DisplayName + assert replies[1].CreatedDateTime == data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][1].CreatedDateTime + + finally: + client.close() + + +def test_get_reply_integration(httpserver): + + data = setup_fake_server(httpserver) + + client = TeamsClient(auto_init=False) + try: + init_fake_client(client, httpserver.url_for("")) + + reply = client.channels.get_message_reply( + teamRef=data.teams[0].DisplayName, + channelRef=data.channels[data.teams[0].ID][0].Name, + messageID=data.messages[data.channels[data.teams[0].ID][0].ID][1].ID, + replyID=data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][0].ID, + ) + + assert reply.ID == data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][0].ID + assert reply.Content == data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][0].Content + assert reply.ContentType == MessageContentType(data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][0].ContentType) + assert reply.From.UserID == data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][0].From.UserID + assert reply.From.DisplayName == data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][0].From.DisplayName + assert reply.CreatedDateTime == data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][0].CreatedDateTime + + finally: + client.close() + + + + + From e168fc6bd6207b1b747bb1e535dc037bc9664e84 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Mon, 29 Dec 2025 11:04:13 +0100 Subject: [PATCH 26/99] feat: added send message in channel test --- python/teams_lib_pzsp2_z1/model/message.py | 4 +++ .../teams_lib_pzsp2_z1/services/channels.py | 2 +- python/tests/fake_server/data.py | 29 +++++++++++++++++ python/tests/fake_server/setup.py | 14 +++++++++ python/tests/test_channels.py | 31 ++++++++++++++++++- 5 files changed, 78 insertions(+), 2 deletions(-) diff --git a/python/teams_lib_pzsp2_z1/model/message.py b/python/teams_lib_pzsp2_z1/model/message.py index 6bee67d..5ff9a4d 100644 --- a/python/teams_lib_pzsp2_z1/model/message.py +++ b/python/teams_lib_pzsp2_z1/model/message.py @@ -35,6 +35,10 @@ def __dict__(self): "Content": self.Content, } + def __iter__(self): + yield "ContentType", self.ContentType.value + yield "Content", self.Content + @dataclass class ListMessagesOptions: diff --git a/python/teams_lib_pzsp2_z1/services/channels.py b/python/teams_lib_pzsp2_z1/services/channels.py index 11a22ce..0ec951a 100644 --- a/python/teams_lib_pzsp2_z1/services/channels.py +++ b/python/teams_lib_pzsp2_z1/services/channels.py @@ -97,7 +97,7 @@ def delete(self, teamRef: str, channelRef: str) -> bool: def send_message(self, teamRef: str, channelRef: str, body: MessageBody) -> Message: response = self.client.execute( cmd_type="request", - method="sendChannelMessage", + method="sendMessageToChannel", params={ "teamRef": teamRef, "channelRef": channelRef, diff --git a/python/tests/fake_server/data.py b/python/tests/fake_server/data.py index 872c5a5..c14d454 100644 --- a/python/tests/fake_server/data.py +++ b/python/tests/fake_server/data.py @@ -112,6 +112,17 @@ def __init__(self) -> FakeServerData: self.potentialTeams = [] self.newChannelName = "New Channel" self.newChannelID = "19:newchannelid@thread.tacv2" + self.newMessageTemplate = Message( + ID="new-message-id", + Content="This is a new message.", + ContentType="text", + From=MessageFrom( + UserID="user-new-001", + DisplayName="New User" + ), + CreatedDateTime="2024-01-02T10:00:00Z", + ReplyCount=0, + ) def get_myJoinedTeams_response(self) -> dict: @@ -307,6 +318,22 @@ def get_delete_channel_response(self, team_id: str, channel_id: str) -> dict: self.channels[team_id].remove(channel) return {"success": True} + def get_send_message_response(self, team_id: str, channel_id: str, request_json: dict) -> dict: + return { + "id": self.newMessageTemplate.ID, + "body": { + "content": request_json.get("body", {}).get("content"), + "contentType": request_json.get("body", {}).get("contentType"), + }, + "from": { + "user": { + "id": self.newMessageTemplate.From.UserID, + "displayName": self.newMessageTemplate.From.DisplayName, + } + }, + "createdDateTime": self.newMessageTemplate.CreatedDateTime, + } + def get_list_messages_response(self, team_id: str, channel_id: str) -> dict: return { "value": [ @@ -397,3 +424,5 @@ def get_reply_response(self, team_id: str, channel_id: str, message_id: str, rep + + diff --git a/python/tests/fake_server/setup.py b/python/tests/fake_server/setup.py index 2c25da0..e3a0453 100644 --- a/python/tests/fake_server/setup.py +++ b/python/tests/fake_server/setup.py @@ -44,6 +44,20 @@ def make_log_response(request, data_dict: dict, context_msg: str = "") -> Respon req, data.get_myJoinedTeams_response(), "List Joined Teams" )) + #POST /teams/{team_id}/channels/{channel_id}/messages + httpserver.expect_request( + re.compile(r"^/v1.0/teams/([^/]+)/channels/([^/]+)/messages"), + method="POST" + ).respond_with_handler(lambda req: make_log_response( + req, + data.get_send_message_response( + re.search(r"/teams/([^/]+)/channels/([^/]+)/messages", req.path).group(1), + re.search(r"/teams/([^/]+)/channels/([^/]+)/messages", req.path).group(2), + req.json + ), + "Create Channel Message" + )) + # GET /teams/{team_id}/channels/{channel_id}/messages/{message_id}/replies/{reply_id} httpserver.expect_request( diff --git a/python/tests/test_channels.py b/python/tests/test_channels.py index dcf8196..8a6c687 100644 --- a/python/tests/test_channels.py +++ b/python/tests/test_channels.py @@ -1,7 +1,7 @@ from teams_lib_pzsp2_z1.client import TeamsClient from tests.init_fake_client import init_fake_client from tests.fake_server.setup import setup_fake_server -from teams_lib_pzsp2_z1.model.message import MessageContentType +from teams_lib_pzsp2_z1.model.message import MessageContentType, MessageBody def test_list_channels_integration(httpserver): @@ -116,6 +116,35 @@ def test_delete_channel_integration(httpserver): client.close() +def test_send_message_integration(httpserver): + + data = setup_fake_server(httpserver) + + client = TeamsClient(auto_init=False) + try: + init_fake_client(client, httpserver.url_for("")) + + message = client.channels.send_message( + teamRef=data.teams[0].DisplayName, + channelRef=data.channels[data.teams[0].ID][0].Name, + body=MessageBody( + Content=data.newMessageTemplate.Content, + ContentType=MessageContentType.TEXT + ) + ) + + assert message.ID == data.newMessageTemplate.ID + assert message.Content == data.newMessageTemplate.Content + assert message.ContentType == MessageContentType(data.newMessageTemplate.ContentType) + assert message.From.UserID == data.newMessageTemplate.From.UserID + assert message.From.DisplayName == data.newMessageTemplate.From.DisplayName + assert message.ReplyCount == data.newMessageTemplate.ReplyCount + assert message.CreatedDateTime == data.newMessageTemplate.CreatedDateTime + + finally: + client.close() + + def test_list_messages_integration(httpserver): data = setup_fake_server(httpserver) From 7f20b764d28d8c2b74eb51cf77c1c5c07a97d532 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Mon, 29 Dec 2025 11:45:01 +0100 Subject: [PATCH 27/99] feat: added tests to members management --- .../teams_lib_pzsp2_z1/services/channels.py | 6 +- python/tests/fake_server/data.py | 95 ++++++++++++++++ python/tests/fake_server/setup.py | 57 ++++++++++ python/tests/test_channels.py | 107 ++++++++++++++++++ 4 files changed, 262 insertions(+), 3 deletions(-) diff --git a/python/teams_lib_pzsp2_z1/services/channels.py b/python/teams_lib_pzsp2_z1/services/channels.py index 0ec951a..c4c5172 100644 --- a/python/teams_lib_pzsp2_z1/services/channels.py +++ b/python/teams_lib_pzsp2_z1/services/channels.py @@ -254,7 +254,7 @@ def add_member( ) -> Member: response = self.client.execute( cmd_type="request", - method="addChannelMember", + method="addMemberToChannel", params={ "teamRef": teamRef, "channelRef": channelRef, @@ -269,7 +269,7 @@ def update_member_role( ) -> Member: response = self.client.execute( cmd_type="request", - method="updateChannelMemberRole", + method="updateMemberRoleInChannel", params={ "teamRef": teamRef, "channelRef": channelRef, @@ -282,7 +282,7 @@ def update_member_role( def remove_member(self, teamRef: str, channelRef: str, userRef: str) -> bool: response = self.client.execute( cmd_type="request", - method="removeChannelMember", + method="removeMemberFromChannel", params={ "teamRef": teamRef, "channelRef": channelRef, diff --git a/python/tests/fake_server/data.py b/python/tests/fake_server/data.py index c14d454..27e22ca 100644 --- a/python/tests/fake_server/data.py +++ b/python/tests/fake_server/data.py @@ -2,6 +2,7 @@ from teams_lib_pzsp2_z1.model.team import Team from teams_lib_pzsp2_z1.model.channel import Channel from teams_lib_pzsp2_z1.model.message import Message, MessageFrom +from teams_lib_pzsp2_z1.model.member import Member from dataclasses import field @dataclass @@ -105,6 +106,26 @@ def __init__(self) -> FakeServerData: ), ], } + self.members = { + "team-123-abc": { + "19:123123@thread.tacv2": [ + Member( + ID="user-123-abc", + UserID="user-123-abc", + DisplayName="Alice", + Role="owner", + Email="alice@example.com" + ), + Member( + ID="user-456-def", + UserID="user-456-def", + DisplayName="Bob", + Role="member", + Email="bob@example.com" + ), + ], + } + } self.newGroupID = "group-789-ghi" self.newTeamName = "New Team" self.newGroupMailNickname = "new-team-nickname" @@ -123,6 +144,15 @@ def __init__(self) -> FakeServerData: CreatedDateTime="2024-01-02T10:00:00Z", ReplyCount=0, ) + self.newMemberTemplate = Member( + ID="user-new-002", + UserID="new-user-002", + DisplayName="New Member", + Role="owner", + Email="newmember@example.com" + ) + + def get_myJoinedTeams_response(self) -> dict: @@ -418,6 +448,71 @@ def get_reply_response(self, team_id: str, channel_id: str, message_id: str, rep "createdDateTime": reply.CreatedDateTime, } + def get_add_member_response(self, team_id: str, request_json: dict) -> dict: + roles = [] + if self.newMemberTemplate.Role == "owner": + roles = ["owner"] + + return { + "@odata.context": f"https://graph.microsoft.com/v1.0/$metadata#teams('{team_id}')/members/$entity", + "@odata.type": "#microsoft.graph.aadUserConversationMember", + + "id": self.newMemberTemplate.ID, + "roles": roles, + "displayName": self.newMemberTemplate.DisplayName, + "userId": self.newMemberTemplate.UserID, + "email": self.newMemberTemplate.Email, + } + + def get_update_member_role_response(self, team_id: str, channel_id: str, member_id: str, request_json: dict) -> dict | None: + channel_members = self.members.get(team_id, {}).get(channel_id, []) + + member = next((m for m in channel_members if m.ID == member_id), None) + if not member: + return None + + if "roles" in request_json: + member.Role = "owner" if "owner" in request_json["roles"] else "member" + + return { + "@odata.context": f"https://graph.microsoft.com/v1.0/$metadata#teams('{team_id}')/channels('{channel_id}')/members/$entity", + "@odata.type": "#microsoft.graph.aadUserConversationMember", + "id": member.ID, + "userId": member.UserID, + "displayName": member.DisplayName, + "roles": [member.Role] if member.Role == "owner" else [], + "email": member.Email, + } + + def get_list_members_response(self, team_id: str, channel_id: str) -> dict: + return { + "@odata.context": f"https://graph.microsoft.com/v1.0/$metadata#teams('{team_id}')/channels('{channel_id}')/members", + "value": [ + { + "@odata.type": "#microsoft.graph.aadUserConversationMember", + "id": member.ID, + "userId": member.UserID, + "displayName": member.DisplayName, + "roles": [member.Role] if member.Role == "owner" else [], + "email": member.Email, + } + for member in self.members.get(team_id, {}).get(channel_id, []) + ], + } + + def get_remove_member_response(self, team_id: str, channel_id: str, member_id: str) -> dict: + channel_members = self.members.get(team_id, {}).get(channel_id, []) + + member = next((m for m in channel_members if m.ID == member_id), None) + if not member: + return {"success": False} + + channel_members.remove(member) + return {"success": True} + + + + diff --git a/python/tests/fake_server/setup.py b/python/tests/fake_server/setup.py index e3a0453..93e1bb1 100644 --- a/python/tests/fake_server/setup.py +++ b/python/tests/fake_server/setup.py @@ -44,6 +44,63 @@ def make_log_response(request, data_dict: dict, context_msg: str = "") -> Respon req, data.get_myJoinedTeams_response(), "List Joined Teams" )) + #POST /teams/{team_id}/channels/{channel_id}/members + httpserver.expect_request( + re.compile(r"^/v1.0/teams/([^/]+)/channels/([^/]+)/members"), + method="POST" + ).respond_with_handler(lambda req: make_log_response( + req, + data.get_add_member_response( + re.search(r"/teams/([^/]+)/channels/([^/]+)/members", req.path).group(1), + req.json + ), + "Add member to channel" + )) + + + # PATCH /teams/{tid}/channels/{cid}/members/{mid} + httpserver.expect_request( + re.compile(r"^/v1.0/teams/([^/]+)/channels/([^/]+)/members/([^/]+)"), + method="PATCH" + ).respond_with_handler(lambda req: make_log_response( + req, + data.get_update_member_role_response( + re.search(r"/teams/([^/]+)/channels/([^/]+)/members/([^/]+)", req.path).group(1), # team_id + re.search(r"/teams/([^/]+)/channels/([^/]+)/members/([^/]+)", req.path).group(2), # channel_id + re.search(r"/teams/([^/]+)/channels/([^/]+)/members/([^/]+)", req.path).group(3), # member_id + req.json + ), + "Update Member Role" + )) + + # DELETE /teams/{team_id}/channels/{channel_id}/members/{member_id} + httpserver.expect_request( + re.compile(r"^/v1.0/teams/([^/]+)/channels/([^/]+)/members/([^/]+)"), + method="DELETE" + ).respond_with_handler(lambda req: make_log_response( + req, + data.get_remove_member_response( + re.search(r"/teams/([^/]+)/channels/([^/]+)/members/([^/]+)", req.path).group(1), # team_id + re.search(r"/teams/([^/]+)/channels/([^/]+)/members/([^/]+)", req.path).group(2), # channel_id + re.search(r"/teams/([^/]+)/channels/([^/]+)/members/([^/]+)", req.path).group(3) # member_id + ), + "Remove Member from Channel" + )) + + #GET /teams/{team_id}/channels/{channel_id}/members + httpserver.expect_request( + re.compile(r"^/v1.0/teams/([^/]+)/channels/([^/]+)/members"), + method="GET" + ).respond_with_handler(lambda req: make_log_response( + req, + data.get_list_members_response( + re.search(r"/teams/([^/]+)/channels/([^/]+)/members", req.path).group(1), + re.search(r"/teams/([^/]+)/channels/([^/]+)/members", req.path).group(2) + ), + "List channel members" + )) + + #POST /teams/{team_id}/channels/{channel_id}/messages httpserver.expect_request( re.compile(r"^/v1.0/teams/([^/]+)/channels/([^/]+)/messages"), diff --git a/python/tests/test_channels.py b/python/tests/test_channels.py index 8a6c687..fe8e161 100644 --- a/python/tests/test_channels.py +++ b/python/tests/test_channels.py @@ -264,6 +264,113 @@ def test_get_reply_integration(httpserver): client.close() +def test_list_members_integration(httpserver): + data = setup_fake_server(httpserver) + + client = TeamsClient(auto_init=False) + try: + init_fake_client(client, httpserver.url_for("")) + + team_id = data.teams[0].ID + channel = data.channels[team_id][0] + channel_id = channel.ID + + members = client.channels.list_members( + teamRef=data.teams[0].DisplayName, + channelRef=channel.Name, + ) + + expected_members = data.members[team_id][channel_id] + + assert len(members) == len(expected_members) + + assert members[0].UserID == expected_members[0].UserID + assert members[0].DisplayName == expected_members[0].DisplayName + expected_role_0 = "owner" if expected_members[0].Role == "owner" else "" + assert members[0].Role == expected_role_0 + assert members[0].Email == expected_members[0].Email + assert members[0].ID == expected_members[0].ID + + assert members[1].UserID == expected_members[1].UserID + assert members[1].DisplayName == expected_members[1].DisplayName + expected_role_1 = "owner" if expected_members[1].Role == "owner" else "" + assert members[1].Role == expected_role_1 + assert members[1].Email == expected_members[1].Email + assert members[1].ID == expected_members[1].ID + + finally: + client.close() + + +def test_add_member_integration(httpserver): + + data = setup_fake_server(httpserver) + + client = TeamsClient(auto_init=False) + try: + init_fake_client(client, httpserver.url_for("")) + + member = client.channels.add_member( + teamRef=data.teams[0].DisplayName, + channelRef=data.channels[data.teams[0].ID][1].Name, + userRef=data.newMemberTemplate.DisplayName, + isOwner=True if data.newMemberTemplate.Role == "owner" else False, + ) + + assert member.UserID == data.newMemberTemplate.UserID + assert member.DisplayName == data.newMemberTemplate.DisplayName + assert member.Role == data.newMemberTemplate.Role + assert member.Email == data.newMemberTemplate.Email + assert member.ID == data.newMemberTemplate.ID + + finally: + client.close() + + +def test_update_member_role_integration(httpserver): + + data = setup_fake_server(httpserver) + + client = TeamsClient(auto_init=False) + try: + init_fake_client(client, httpserver.url_for("")) + + member = client.channels.update_member_role( + teamRef=data.teams[0].DisplayName, + channelRef=data.channels[data.teams[0].ID][0].Name, + userRef=data.members[data.teams[0].ID][data.channels[data.teams[0].ID][0].ID][1].DisplayName, + isOwner=True + ) + + assert member.UserID == data.members[data.teams[0].ID][data.channels[data.teams[0].ID][0].ID][1].UserID + assert member.DisplayName == data.members[data.teams[0].ID][data.channels[data.teams[0].ID][0].ID][1].DisplayName + assert member.Role == "owner" + assert member.Email == data.members[data.teams[0].ID][data.channels[data.teams[0].ID][0].ID][1].Email + assert member.ID == data.members[data.teams[0].ID][data.channels[data.teams[0].ID][0].ID][1].ID + finally: + client.close() + + +def test_remove_member_integration(httpserver): + + data = setup_fake_server(httpserver) + + client = TeamsClient(auto_init=False) + try: + init_fake_client(client, httpserver.url_for("")) + + success = client.channels.remove_member( + teamRef=data.teams[0].DisplayName, + channelRef=data.channels[data.teams[0].ID][0].Name, + userRef=data.members[data.teams[0].ID][data.channels[data.teams[0].ID][0].ID][1].DisplayName, + ) + + assert success is True + + finally: + client.close() + + From 8267c4e5d2f24ef917e855059d09cee7ef56f311 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Mon, 29 Dec 2025 11:58:23 +0100 Subject: [PATCH 28/99] feat: added chats endpoints to bridge --- go/internal/json-client/requests-chats.go | 100 ++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 go/internal/json-client/requests-chats.go diff --git a/go/internal/json-client/requests-chats.go b/go/internal/json-client/requests-chats.go new file mode 100644 index 0000000..59c60d7 --- /dev/null +++ b/go/internal/json-client/requests-chats.go @@ -0,0 +1,100 @@ +package jsonclient + +import ( + "context" + + "github.com/pzsp-teams/lib-python/internal/json-client/decoders" +) + +type createOneToOneChatParams struct { + OwnerEmail string `json:"ownerEmail"` + RecipientEmail string `json:"recipientEmail"` +} + +func (jsonclient *TeamsJSONClient) CreateOneToOneChat(p map[string]interface{}) (interface{}, error) { + params, err := decoders.DecodeParams[createOneToOneChatParams](p) + if err != nil { + return nil, err + } + chat, err := jsonclient.client.Chats.CreateOneToOne(context.TODO(), params.OwnerEmail, params.RecipientEmail) + if err != nil { + return nil, err + } + return chat, nil +} + +type createGroupChatParams struct { + OwnerEmail string `json:"ownerEmail"` + MemberEmails []string `json:"memberEmails"` + Topic string `json:"topic"` +} + +func (jsonclient *TeamsJSONClient) CreateGroupChat(p map[string]interface{}) (interface{}, error) { + params, err := decoders.DecodeParams[createGroupChatParams](p) + if err != nil { + return nil, err + } + chat, err := jsonclient.client.Chats.CreateGroup(context.TODO(), params.OwnerEmail, params.MemberEmails, params.Topic) + if err != nil { + return nil, err + } + return chat, nil +} + +type sendMessageInChatParams struct { + ChatID string `json:"chatID"` + Content string `json:"content"` +} + +func (jsonclient *TeamsJSONClient) SendMessageInChat(p map[string]interface{}) (interface{}, error) { + params, err := decoders.DecodeParams[sendMessageInChatParams](p) + if err != nil { + return nil, err + } + message, err := jsonclient.client.Chats.SendMessage(context.TODO(), params.ChatID, params.Content) + if err != nil { + return nil, err + } + return message, nil +} + +func (jsonclient *TeamsJSONClient) ListMyChats(_ map[string]interface{}) (interface{}, error) { + chats, err := jsonclient.client.Chats.ListMyJoined(context.TODO()) + if err != nil { + return nil, err + } + return chats, nil +} + +type listMembersInChatParams struct { + ChatID string `json:"chatID"` +} + +func (jsonclient *TeamsJSONClient) ListMembersInChat(p map[string]interface{}) (interface{}, error) { + params, err := decoders.DecodeParams[listMembersInChatParams](p) + if err != nil { + return nil, err + } + members, err := jsonclient.client.Chats.ListMembers(context.TODO(), params.ChatID) + if err != nil { + return nil, err + } + return members, nil +} + +type addMemberToChatParams struct { + ChatID string `json:"chatID"` + Email string `json:"email"` +} + +func (jsonclient *TeamsJSONClient) AddMemberToChat(p map[string]interface{}) (interface{}, error) { + params, err := decoders.DecodeParams[addMemberToChatParams](p) + if err != nil { + return nil, err + } + member, err := jsonclient.client.Chats.AddMember(context.TODO(), params.ChatID, params.Email) + if err != nil { + return nil, err + } + return member, nil +} From be56e37bae93bb7a39b566273a5e57d92d775068 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Mon, 29 Dec 2025 11:59:54 +0100 Subject: [PATCH 29/99] feat: added chat endpoints to request map --- go/internal/json-client/request-map.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/go/internal/json-client/request-map.go b/go/internal/json-client/request-map.go index b0c9099..b91027f 100644 --- a/go/internal/json-client/request-map.go +++ b/go/internal/json-client/request-map.go @@ -97,4 +97,29 @@ var Handlers = map[string]HandlerFunc{ "getMessageReplyInChannel": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { return c.GetMessageReplyInChannel(p) }, + + // CHATS + "createOneOnOneChat": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + return c.CreateOneToOneChat(p) + }, + + "createGroupChat": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + return c.CreateGroupChat(p) + }, + + "sendMessageInChat": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + return c.SendMessageInChat(p) + }, + + "listMyChats": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + return c.ListMyChats(p) + }, + + "listMembersInChat": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + return c.ListMembersInChat(p) + }, + + "addMemberToChat": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + return c.AddMemberToChat(p) + }, } From de296a62b493f935bda018dcf3dce6caf986cd6f Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Mon, 29 Dec 2025 12:06:49 +0100 Subject: [PATCH 30/99] feat: prepared structure for chat service in python --- python/teams_lib_pzsp2_z1/client.py | 2 ++ python/teams_lib_pzsp2_z1/model/chat.py | 15 +++++++++++++++ python/teams_lib_pzsp2_z1/services/chats.py | 12 ++++++++++++ 3 files changed, 29 insertions(+) create mode 100644 python/teams_lib_pzsp2_z1/model/chat.py create mode 100644 python/teams_lib_pzsp2_z1/services/chats.py diff --git a/python/teams_lib_pzsp2_z1/client.py b/python/teams_lib_pzsp2_z1/client.py index a5f28c0..7a0e76c 100644 --- a/python/teams_lib_pzsp2_z1/client.py +++ b/python/teams_lib_pzsp2_z1/client.py @@ -8,6 +8,7 @@ from teams_lib_pzsp2_z1 import config from teams_lib_pzsp2_z1.services.channels import ChannelsService +from teams_lib_pzsp2_z1.services.chats import ChatsService from teams_lib_pzsp2_z1.services.teams import TeamsService @@ -33,6 +34,7 @@ def __init__( self.env_path = env_path self.channels = ChannelsService(self) self.teams = TeamsService(self) + self.chats = ChatsService(self) if auto_init: self.init_client(cache_enabled, cache_path) diff --git a/python/teams_lib_pzsp2_z1/model/chat.py b/python/teams_lib_pzsp2_z1/model/chat.py new file mode 100644 index 0000000..7224aed --- /dev/null +++ b/python/teams_lib_pzsp2_z1/model/chat.py @@ -0,0 +1,15 @@ +from dataclasses import dataclass +from enum import Enum + + +class ChatType(Enum): + ONEONONE = "one-on-one" + GROUP = "group" + + +@dataclass +class Chat: + ID: str + Type: ChatType + IsHidden: bool + Topic: str diff --git a/python/teams_lib_pzsp2_z1/services/chats.py b/python/teams_lib_pzsp2_z1/services/chats.py new file mode 100644 index 0000000..ae55f9e --- /dev/null +++ b/python/teams_lib_pzsp2_z1/services/chats.py @@ -0,0 +1,12 @@ +from teams_lib_pzsp2_z1.model.chat import Chat, ChatType +from teams_lib_pzsp2_z1.model.member import Member +from teams_lib_pzsp2_z1.model.message import ( + Message, + MessageBody, + MessageContentType, + MessageFrom, +) +from teams_lib_pzsp2_z1.services.base_service import BaseService + + +class ChatsService(BaseService): From b64f8829710c34bf63ec694f56251ffde50e0219 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Wed, 31 Dec 2025 13:13:47 +0100 Subject: [PATCH 31/99] feat: resolved chatRef --- go/go.mod | 33 +++--- go/go.sum | 66 ++++++----- .../json-client/decoders/decode-chatRef.go | 28 +++++ .../decoders/decode-messageBody.go | 6 + go/internal/json-client/requests-channels.go | 7 +- go/internal/json-client/requests-chats.go | 112 +++++++++++++----- python/teams_lib_pzsp2_z1/services/chats.py | 31 +++++ 7 files changed, 202 insertions(+), 81 deletions(-) create mode 100644 go/internal/json-client/decoders/decode-chatRef.go create mode 100644 go/internal/json-client/decoders/decode-messageBody.go diff --git a/go/go.mod b/go/go.mod index 7e695aa..a0e5afd 100644 --- a/go/go.mod +++ b/go/go.mod @@ -4,24 +4,25 @@ go 1.25.5 require ( github.com/microsoft/kiota-abstractions-go v1.9.3 - github.com/microsoftgraph/msgraph-sdk-go v1.90.0 + github.com/microsoftgraph/msgraph-sdk-go v1.92.0 github.com/mitchellh/mapstructure v1.5.0 - github.com/pzsp-teams/lib v0.0.0-20251213104555-e41d3adf599f + github.com/pzsp-teams/lib v0.0.0-20251231113700-9da62b291ebb ) require ( - al.essio.dev/pkg/shellescape v1.5.1 // indirect - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.1 // indirect + al.essio.dev/pkg/shellescape v1.6.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 // indirect - github.com/danieljoos/wincred v1.2.2 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/danieljoos/wincred v1.2.3 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/godbus/dbus/v5 v5.1.0 // indirect - github.com/golang-jwt/jwt/v5 v5.2.2 // indirect + github.com/godbus/dbus/v5 v5.2.2 // indirect + github.com/golang-jwt/jwt/v5 v5.3.0 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/keybase/go-keychain v0.0.0-20230523030712-b5615109f100 // indirect + github.com/keybase/go-keychain v0.0.1 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/microsoft/kiota-authentication-azure-go v1.3.1 // indirect github.com/microsoft/kiota-http-go v1.5.4 // indirect @@ -31,13 +32,13 @@ require ( github.com/microsoft/kiota-serialization-text-go v1.1.3 // indirect github.com/microsoftgraph/msgraph-sdk-go-core v1.4.0 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect - github.com/std-uritemplate/std-uritemplate/go/v2 v2.0.3 // indirect + github.com/std-uritemplate/std-uritemplate/go/v2 v2.0.8 // indirect github.com/zalando/go-keyring v0.2.6 // indirect - go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/otel v1.37.0 // indirect - go.opentelemetry.io/otel/metric v1.37.0 // indirect - go.opentelemetry.io/otel/trace v1.37.0 // indirect - golang.org/x/net v0.43.0 // indirect - golang.org/x/sys v0.35.0 // indirect - golang.org/x/text v0.28.0 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/otel v1.39.0 // indirect + go.opentelemetry.io/otel/metric v1.39.0 // indirect + go.opentelemetry.io/otel/trace v1.39.0 // indirect + golang.org/x/net v0.48.0 // indirect + golang.org/x/sys v0.39.0 // indirect + golang.org/x/text v0.32.0 // indirect ) diff --git a/go/go.sum b/go/go.sum index 6e98158..bff219a 100644 --- a/go/go.sum +++ b/go/go.sum @@ -1,15 +1,17 @@ -al.essio.dev/pkg/shellescape v1.5.1 h1:86HrALUujYS/h+GtqoB26SBEdkWfmMI6FubjXlsXyho= -al.essio.dev/pkg/shellescape v1.5.1/go.mod h1:6sIqp7X2P6mThCQ7twERpZTuigpr6KbZWtls1U8I890= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.1 h1:5YTBM8QDVIBN3sxBil89WfdAAqDZbyJTgh688DSxX5w= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.1/go.mod h1:YD5h/ldMsG0XiIw7PdyNhLxaM317eFh5yNLccNfGdyw= +al.essio.dev/pkg/shellescape v1.6.0 h1:NxFcEqzFSEVCGN2yq7Huv/9hyCEGVa/TncnOOBBeXHA= +al.essio.dev/pkg/shellescape v1.6.0/go.mod h1:6sIqp7X2P6mThCQ7twERpZTuigpr6KbZWtls1U8I890= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0 h1:JXg2dwJUmPB9JmtVmdEB16APJ7jurfbY5jnfXpJoRMc= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0/go.mod h1:YD5h/ldMsG0XiIw7PdyNhLxaM317eFh5yNLccNfGdyw= github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA= github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs= github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk= -github.com/danieljoos/wincred v1.2.2 h1:774zMFJrqaeYCK2W57BgAem/MLi6mtSE47MB6BOJ0i0= -github.com/danieljoos/wincred v1.2.2/go.mod h1:w7w4Utbrz8lqeMbDAK0lkNJUv5sAOkFi7nd/ogr0Uh8= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/danieljoos/wincred v1.2.3 h1:v7dZC2x32Ut3nEfRH+vhoZGvN72+dQ/snVXo/vMFLdQ= +github.com/danieljoos/wincred v1.2.3/go.mod h1:6qqX0WNrS4RzPZ1tnroDzq9kY3fu1KwE7MRLQK4X0bs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -17,18 +19,18 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= -github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= -github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ= +github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c= +github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= +github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/keybase/go-keychain v0.0.0-20230523030712-b5615109f100 h1:rG3VnJUnAWyiv7qYmmdOdSapzz6HM+zb9/uRFr0T5EM= -github.com/keybase/go-keychain v0.0.0-20230523030712-b5615109f100/go.mod h1:qDHUvIjGZJUtdPtuP4WMu5/U4aVWbFw1MhlkJqCGmCQ= +github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU= +github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -49,8 +51,8 @@ github.com/microsoft/kiota-serialization-multipart-go v1.1.2 h1:1pUyA1QgIeKslQwb github.com/microsoft/kiota-serialization-multipart-go v1.1.2/go.mod h1:j2K7ZyYErloDu7Kuuk993DsvfoP7LPWvAo7rfDpdPio= github.com/microsoft/kiota-serialization-text-go v1.1.3 h1:8z7Cebn0YAAr++xswVgfdxZjnAZ4GOB9O7XP4+r5r/M= github.com/microsoft/kiota-serialization-text-go v1.1.3/go.mod h1:NDSvz4A3QalGMjNboKKQI9wR+8k+ih8UuagNmzIRgTQ= -github.com/microsoftgraph/msgraph-sdk-go v1.90.0 h1:ygVeWfGB8TMO4rTFxtrYueZmj1mLqtDKW5UZ4iJwczU= -github.com/microsoftgraph/msgraph-sdk-go v1.90.0/go.mod h1:UdZWxbZiFvjPug9DYayD90JNiHjXyNRA39lEpcy3Kms= +github.com/microsoftgraph/msgraph-sdk-go v1.92.0 h1:LOTZ5un7tsSki0S072D+TIHsiOIpmGJnDkNps9Em6cw= +github.com/microsoftgraph/msgraph-sdk-go v1.92.0/go.mod h1:sue5+4Z9FCOon6pHgvC1djjybs9ZYB3LZaAGYI1Qcfo= github.com/microsoftgraph/msgraph-sdk-go-core v1.4.0 h1:0SrIoFl7TQnMRrsi5TFaeNe0q8KO5lRzRp4GSCCL2So= github.com/microsoftgraph/msgraph-sdk-go-core v1.4.0/go.mod h1:A1iXs+vjsRjzANxF6UeKv2ACExG7fqTwHHbwh1FL+EE= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= @@ -59,33 +61,33 @@ github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmd github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pzsp-teams/lib v0.0.0-20251213104555-e41d3adf599f h1:tmXKJEWYl0f/H4n91wnfW1asgkHbftKgX5nbpafNHmU= -github.com/pzsp-teams/lib v0.0.0-20251213104555-e41d3adf599f/go.mod h1:EPtQVPz+HYZNsSpcoQ3QWLbMEz1O2qeIH5ojlYJLEic= +github.com/pzsp-teams/lib v0.0.0-20251231113700-9da62b291ebb h1:Bzr1vl4wA1X0g+FIKEn5vBexVJGhP1CztwTCyy7TPlo= +github.com/pzsp-teams/lib v0.0.0-20251231113700-9da62b291ebb/go.mod h1:EPtQVPz+HYZNsSpcoQ3QWLbMEz1O2qeIH5ojlYJLEic= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= -github.com/std-uritemplate/std-uritemplate/go/v2 v2.0.3 h1:7hth9376EoQEd1hH4lAp3vnaLP2UMyxuMMghLKzDHyU= -github.com/std-uritemplate/std-uritemplate/go/v2 v2.0.3/go.mod h1:Z5KcoM0YLC7INlNhEezeIZ0TZNYf7WSNO0Lvah4DSeQ= +github.com/std-uritemplate/std-uritemplate/go/v2 v2.0.8 h1:gMBdYMTHt2mmTdXW8YfvRjRUZ0GhyGV+IqSH9H15bGw= +github.com/std-uritemplate/std-uritemplate/go/v2 v2.0.8/go.mod h1:Z5KcoM0YLC7INlNhEezeIZ0TZNYf7WSNO0Lvah4DSeQ= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/zalando/go-keyring v0.2.6 h1:r7Yc3+H+Ux0+M72zacZoItR3UDxeWfKTcabvkI8ua9s= github.com/zalando/go-keyring v0.2.6/go.mod h1:2TCrxYrbUNYfNS/Kgy/LSrkSQzZ5UPVH85RwfczwvcI= -go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= -go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= -go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= -go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= -go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= -go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= -golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= -golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= +golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= +golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= -golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= -golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= +golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= +golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/go/internal/json-client/decoders/decode-chatRef.go b/go/internal/json-client/decoders/decode-chatRef.go new file mode 100644 index 0000000..bd46c4d --- /dev/null +++ b/go/internal/json-client/decoders/decode-chatRef.go @@ -0,0 +1,28 @@ +package decoders + +import ( + "fmt" + + "github.com/pzsp-teams/lib/chats" +) + +type ChatRefDTO struct { + Ref string `json:"ref"` + Type string `json:"type"` +} + +const ( + ChatTypeGroup = "group" + ChatTypeOneOnOne = "oneOnOne" +) + +func GetChatRef(dto ChatRefDTO) (chats.ChatRef, error) { + switch dto.Type { + case ChatTypeGroup: + return chats.GroupChatRef{Ref: dto.Ref}, nil + case ChatTypeOneOnOne: + return chats.OneOnOneChatRef{Ref: dto.Ref}, nil + default: + return nil, fmt.Errorf("unknown chat type: %s", dto.Type) + } +} diff --git a/go/internal/json-client/decoders/decode-messageBody.go b/go/internal/json-client/decoders/decode-messageBody.go new file mode 100644 index 0000000..d9fc39b --- /dev/null +++ b/go/internal/json-client/decoders/decode-messageBody.go @@ -0,0 +1,6 @@ +package decoders + +type MessageBodyDTO struct { + ContentType string `json:"contentType"` + Content string `json:"content"` +} \ No newline at end of file diff --git a/go/internal/json-client/requests-channels.go b/go/internal/json-client/requests-channels.go index 56b72b2..d0f9659 100644 --- a/go/internal/json-client/requests-channels.go +++ b/go/internal/json-client/requests-channels.go @@ -88,15 +88,10 @@ func (jsonclient *TeamsJSONClient) DeleteChannel(p map[string]interface{}) (inte return "deleted", nil } -type messageBodyDTO struct { - ContentType string `json:"contentType"` - Content string `json:"content"` -} - type sendMessageToChannelParams struct { TeamRef string `json:"teamRef"` ChannelRef string `json:"channelRef"` - Body messageBodyDTO `json:"body"` + Body decoders.MessageBodyDTO `json:"body"` } func (jsonclient *TeamsJSONClient) SendMessageToChannel(p map[string]interface{}) (interface{}, error) { diff --git a/go/internal/json-client/requests-chats.go b/go/internal/json-client/requests-chats.go index 59c60d7..00c2c8d 100644 --- a/go/internal/json-client/requests-chats.go +++ b/go/internal/json-client/requests-chats.go @@ -4,11 +4,12 @@ import ( "context" "github.com/pzsp-teams/lib-python/internal/json-client/decoders" + "github.com/pzsp-teams/lib/chats" + "github.com/pzsp-teams/lib/models" ) type createOneToOneChatParams struct { - OwnerEmail string `json:"ownerEmail"` - RecipientEmail string `json:"recipientEmail"` + RecipientRef string `json:"recipientRef"` } func (jsonclient *TeamsJSONClient) CreateOneToOneChat(p map[string]interface{}) (interface{}, error) { @@ -16,7 +17,7 @@ func (jsonclient *TeamsJSONClient) CreateOneToOneChat(p map[string]interface{}) if err != nil { return nil, err } - chat, err := jsonclient.client.Chats.CreateOneToOne(context.TODO(), params.OwnerEmail, params.RecipientEmail) + chat, err := jsonclient.client.Chats.CreateOneOneOne(context.TODO(), params.RecipientRef) if err != nil { return nil, err } @@ -24,9 +25,9 @@ func (jsonclient *TeamsJSONClient) CreateOneToOneChat(p map[string]interface{}) } type createGroupChatParams struct { - OwnerEmail string `json:"ownerEmail"` - MemberEmails []string `json:"memberEmails"` - Topic string `json:"topic"` + RecipientRefs []string `json:"recipientRefs"` + Topic string `json:"topic"` + IncludeMe bool `json:"includeMe"` } func (jsonclient *TeamsJSONClient) CreateGroupChat(p map[string]interface{}) (interface{}, error) { @@ -34,67 +35,124 @@ func (jsonclient *TeamsJSONClient) CreateGroupChat(p map[string]interface{}) (in if err != nil { return nil, err } - chat, err := jsonclient.client.Chats.CreateGroup(context.TODO(), params.OwnerEmail, params.MemberEmails, params.Topic) + chat, err := jsonclient.client.Chats.CreateGroup(context.TODO(), params.RecipientRefs, params.Topic, params.IncludeMe) if err != nil { return nil, err } return chat, nil } -type sendMessageInChatParams struct { - ChatID string `json:"chatID"` - Content string `json:"content"` +type groupChatMemberParams struct { + GroupChatRef string `json:"groupChatRef"` + UserRef string `json:"userRef"` } -func (jsonclient *TeamsJSONClient) SendMessageInChat(p map[string]interface{}) (interface{}, error) { - params, err := decoders.DecodeParams[sendMessageInChatParams](p) +func (jsonclient *TeamsJSONClient) AddMemberGroupToChat(p map[string]interface{}) (interface{}, error) { + params, err := decoders.DecodeParams[groupChatMemberParams](p) if err != nil { return nil, err } - message, err := jsonclient.client.Chats.SendMessage(context.TODO(), params.ChatID, params.Content) + member, err := jsonclient.client.Chats.AddMemberToGroupChat(context.TODO(), chats.GroupChatRef{Ref: params.GroupChatRef}, params.UserRef) if err != nil { return nil, err } - return message, nil + return member, nil } -func (jsonclient *TeamsJSONClient) ListMyChats(_ map[string]interface{}) (interface{}, error) { - chats, err := jsonclient.client.Chats.ListMyJoined(context.TODO()) +func (jsonclient *TeamsJSONClient) RemoveMemberToGroupChat(p map[string]interface{}) (interface{}, error) { + params, err := decoders.DecodeParams[groupChatMemberParams](p) if err != nil { return nil, err } - return chats, nil + err = jsonclient.client.Chats.RemoveMemberFromGroupChat(context.TODO(), chats.GroupChatRef{Ref: params.GroupChatRef}, params.UserRef) + if err != nil { + return nil, err + } + return "removed", nil } type listMembersInChatParams struct { - ChatID string `json:"chatID"` + GroupChatRef string `json:"groupChatRef"` } -func (jsonclient *TeamsJSONClient) ListMembersInChat(p map[string]interface{}) (interface{}, error) { +func (jsonclient *TeamsJSONClient) ListGroupChatMembers(p map[string]interface{}) (interface{}, error) { params, err := decoders.DecodeParams[listMembersInChatParams](p) if err != nil { return nil, err } - members, err := jsonclient.client.Chats.ListMembers(context.TODO(), params.ChatID) + members, err := jsonclient.client.Chats.ListGroupChatMembers(context.TODO(), chats.GroupChatRef{Ref: params.GroupChatRef}) if err != nil { return nil, err } return members, nil } -type addMemberToChatParams struct { - ChatID string `json:"chatID"` - Email string `json:"email"` +type updateGroupChatTopicParams struct { + GroupChatRef string `json:"groupChatRef"` + Topic string `json:"topic"` } -func (jsonclient *TeamsJSONClient) AddMemberToChat(p map[string]interface{}) (interface{}, error) { - params, err := decoders.DecodeParams[addMemberToChatParams](p) +func (jsonclient *TeamsJSONClient) UpdateGroupChatTopic(p map[string]interface{}) (interface{}, error) { + params, err := decoders.DecodeParams[updateGroupChatTopicParams](p) if err != nil { return nil, err } - member, err := jsonclient.client.Chats.AddMember(context.TODO(), params.ChatID, params.Email) + chat, err := jsonclient.client.Chats.UpdateGroupChatTopic(context.TODO(), chats.GroupChatRef{Ref: params.GroupChatRef}, params.Topic) if err != nil { return nil, err } - return member, nil + return chat, nil +} + +type baseChatParams struct { + ChatRef decoders.ChatRefDTO `json:"chatRef"` +} + +func (jsonclient *TeamsJSONClient) ListMessagesInChat(p map[string]interface{}) (interface{}, error) { + params, err := decoders.DecodeParams[baseChatParams](p) + if err != nil { + return nil, err + } + chatRef, err := decoders.GetChatRef(params.ChatRef) + if err != nil { + return nil, err + } + messages, err := jsonclient.client.Chats.ListMessages(context.TODO(), chatRef) + if err != nil { + return nil, err + } + return messages, nil +} + +type sendMessageInChatParams struct { + ChatRef decoders.ChatRefDTO `json:"chatRef"` + Body decoders.MessageBodyDTO `json:"body"` +} + +func (jsonclient *TeamsJSONClient) SendMessageInChat(p map[string]interface{}) (interface{}, error) { + params, err := decoders.DecodeParams[sendMessageInChatParams](p) + if err != nil { + return nil, err + } + chatRef, err := decoders.GetChatRef(params.ChatRef) + if err != nil { + return nil, err + } + body, err := decoders.DecodeParams[models.MessageBody](¶ms.Body) + if err != nil { + return nil, err + } + message, err := jsonclient.client.Chats.SendMessage(context.TODO(), chatRef, *body) + if err != nil { + return nil, err + } + return message, nil +} + +func (jsonclient *TeamsJSONClient) ListMyChats(_ map[string]interface{}) (interface{}, error) { + chats, err := jsonclient.client.Chats.ListMyJoined(context.TODO()) + if err != nil { + return nil, err + } + return chats, nil } diff --git a/python/teams_lib_pzsp2_z1/services/chats.py b/python/teams_lib_pzsp2_z1/services/chats.py index ae55f9e..baec592 100644 --- a/python/teams_lib_pzsp2_z1/services/chats.py +++ b/python/teams_lib_pzsp2_z1/services/chats.py @@ -10,3 +10,34 @@ class ChatsService(BaseService): + def list_my_joined(self) -> list[Chat]: + response = self.client.execute( + cmd_type="request", + method="listMyJoinedChats", + params={}, + ) + return [ + Chat( + ID=chat["ID"], + Type=ChatType(chat["Type"]), + IsHidden=(True if chat["IsHidden"] else False), + Topic=chat["Topic"], + ) + for chat in response + ] + + def create_one_on_one(self, user_id: str) -> Chat: + response = self.client.execute( + cmd_type="request", + method="createOneOnOneChat", + params={ + "userID": user_id, + }, + ) + + return Chat( + ID=response["ID"], + Type=ChatType(response["Type"]), + IsHidden=(True if response["IsHidden"] else False), + Topic=response["Topic"], + ) \ No newline at end of file From 83c5824f4151220af96ccd606e299cf9873b8011 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Wed, 31 Dec 2025 13:39:14 +0100 Subject: [PATCH 32/99] feat: added chat endpoints to bridge --- .../json-client/decoders/decode-chatRef.go | 2 + go/internal/json-client/requests-chats.go | 143 +++++++++++++++++- 2 files changed, 142 insertions(+), 3 deletions(-) diff --git a/go/internal/json-client/decoders/decode-chatRef.go b/go/internal/json-client/decoders/decode-chatRef.go index bd46c4d..c9c9c0c 100644 --- a/go/internal/json-client/decoders/decode-chatRef.go +++ b/go/internal/json-client/decoders/decode-chatRef.go @@ -26,3 +26,5 @@ func GetChatRef(dto ChatRefDTO) (chats.ChatRef, error) { return nil, fmt.Errorf("unknown chat type: %s", dto.Type) } } + + diff --git a/go/internal/json-client/requests-chats.go b/go/internal/json-client/requests-chats.go index 00c2c8d..1356d7a 100644 --- a/go/internal/json-client/requests-chats.go +++ b/go/internal/json-client/requests-chats.go @@ -2,6 +2,7 @@ package jsonclient import ( "context" + "time" "github.com/pzsp-teams/lib-python/internal/json-client/decoders" "github.com/pzsp-teams/lib/chats" @@ -149,10 +150,146 @@ func (jsonclient *TeamsJSONClient) SendMessageInChat(p map[string]interface{}) ( return message, nil } -func (jsonclient *TeamsJSONClient) ListMyChats(_ map[string]interface{}) (interface{}, error) { - chats, err := jsonclient.client.Chats.ListMyJoined(context.TODO()) +type messageInChatParams struct { + ChatRef decoders.ChatRefDTO `json:"chatRef"` + MessageID string `json:"messageId"` +} + +func (jsonclient *TeamsJSONClient) DeleteMessageInChat(p map[string]interface{}) (interface{}, error) { + params, err := decoders.DecodeParams[messageInChatParams](p) + if err != nil { + return nil, err + } + chatRef, err := decoders.GetChatRef(params.ChatRef) + if err != nil { + return nil, err + } + err = jsonclient.client.Chats.DeleteMessage(context.TODO(), chatRef, params.MessageID) + if err != nil { + return nil, err + } + return "deleted", nil +} + +func (jsonclient *TeamsJSONClient) GetMessageInChat(p map[string]interface{}) (interface{}, error) { + params, err := decoders.DecodeParams[messageInChatParams](p) + if err != nil { + return nil, err + } + chatRef, err := decoders.GetChatRef(params.ChatRef) + if err != nil { + return nil, err + } + message, err := jsonclient.client.Chats.GetMessage(context.TODO(), chatRef, params.MessageID) + if err != nil { + return nil, err + } + return message, nil +} + +type ListMyChatsParams struct { + ChatType string `json:"chatType"` +} + +func (jsonclient *TeamsJSONClient) ListMyChats(p map[string]interface{}) (interface{}, error) { + params, err := decoders.DecodeParams[ListMyChatsParams](p) + if err != nil { + return nil, err + } + var chatType models.ChatType + if params.ChatType != "" { + chatType = models.ChatType(params.ChatType) + } + chatsList, err := jsonclient.client.Chats.ListChats(context.TODO(), &chatType) + if err != nil { + return nil, err + } + return chatsList, nil +} + +type listChatMessagesParams struct { + StartTime time.Time `json:"startTime"` + EndTime time.Time `json:"endTime"` + Top *int32 `json:"top"` +} + +func (jsonclient *TeamsJSONClient) ListMyChatMessages(p map[string]interface{}) (interface{}, error) { + params, err := decoders.DecodeParams[listChatMessagesParams](p) + if err != nil { + return nil, err + } + messages, err := jsonclient.client.Chats.ListAllMessages(context.TODO(), ¶ms.StartTime, ¶ms.EndTime, params.Top) + if err != nil { + return nil, err + } + return messages, nil +} + +func (jsonclient *TeamsJSONClient) ListPinnedMessagesInChat(p map[string]interface{}) (interface{}, error) { + params, err := decoders.DecodeParams[baseChatParams](p) + if err != nil { + return nil, err + } + chatRef, err := decoders.GetChatRef(params.ChatRef) + if err != nil { + return nil, err + } + messages, err := jsonclient.client.Chats.ListPinnedMessages(context.TODO(), chatRef) + if err != nil { + return nil, err + } + return messages, nil +} + +func (jsonclient *TeamsJSONClient) PinMessageInChat(p map[string]interface{}) (interface{}, error) { + params, err := decoders.DecodeParams[messageInChatParams](p) + if err != nil { + return nil, err + } + chatRef, err := decoders.GetChatRef(params.ChatRef) + if err != nil { + return nil, err + } + err = jsonclient.client.Chats.PinMessage(context.TODO(), chatRef, params.MessageID) + if err != nil { + return nil, err + } + return "pinned", nil +} + +func (jsonclient *TeamsJSONClient) UnpinMessageInChat(p map[string]interface{}) (interface{}, error) { + params, err := decoders.DecodeParams[messageInChatParams](p) + if err != nil { + return nil, err + } + chatRef, err := decoders.GetChatRef(params.ChatRef) + if err != nil { + return nil, err + } + err = jsonclient.client.Chats.UnpinMessage(context.TODO(), chatRef, params.MessageID) + if err != nil { + return nil, err + } + return "unpinned", nil +} + +type mentionInChatParams struct { + ChatRef decoders.ChatRefDTO `json:"chatRef"` + RawMentions []string `json:"rawMentions"` +} + +func (jsonclient *TeamsJSONClient) GetMentionsInChat(p map[string]interface{}) (interface{}, error) { + params, err := decoders.DecodeParams[mentionInChatParams](p) + if err != nil { + return nil, err + } + chatRef, err := decoders.GetChatRef(params.ChatRef) + if err != nil { + return nil, err + } + mentions, err := jsonclient.client.Chats.GetMentions(context.TODO(), chatRef, params.RawMentions) if err != nil { return nil, err } - return chats, nil + return mentions, nil } From a3ce10bdb858801cf40050f02bc281192f4877bc Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Wed, 31 Dec 2025 13:42:10 +0100 Subject: [PATCH 33/99] feat: adjusted channels endpoints to lib changes --- go/internal/json-client/requests-channels.go | 44 +++++++++++++++++++- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/go/internal/json-client/requests-channels.go b/go/internal/json-client/requests-channels.go index d0f9659..1f522ec 100644 --- a/go/internal/json-client/requests-channels.go +++ b/go/internal/json-client/requests-channels.go @@ -89,8 +89,8 @@ func (jsonclient *TeamsJSONClient) DeleteChannel(p map[string]interface{}) (inte } type sendMessageToChannelParams struct { - TeamRef string `json:"teamRef"` - ChannelRef string `json:"channelRef"` + TeamRef string `json:"teamRef"` + ChannelRef string `json:"channelRef"` Body decoders.MessageBodyDTO `json:"body"` } @@ -110,6 +110,29 @@ func (jsonclient *TeamsJSONClient) SendMessageToChannel(p map[string]interface{} return sentMessage, nil } +type sendReplyToChannelParams struct { + TeamRef string `json:"teamRef"` + ChannelRef string `json:"channelRef"` + MessageID string `json:"messageId"` + Body decoders.MessageBodyDTO `json:"body"` +} + +func (jsonclient *TeamsJSONClient) SendReplyToChannel(p map[string]interface{}) (interface{}, error) { + params, err := decoders.DecodeParams[sendReplyToChannelParams](p) + if err != nil { + return nil, err + } + body, err := decoders.DecodeParams[models.MessageBody](¶ms.Body) + if err != nil { + return nil, err + } + sentMessage, err := jsonclient.client.Channels.SendReply(context.TODO(), params.TeamRef, params.ChannelRef, params.MessageID, *body) + if err != nil { + return nil, err + } + return sentMessage, nil +} + type listMessagesOptionsDTO struct { Top *int32 `json:"top"` ExpandReplies bool `json:"expandReplies"` @@ -254,3 +277,20 @@ func (jsonclient *TeamsJSONClient) RemoveMemberFromChannel(p map[string]interfac return "removed", nil } +type getMentionsInChannelParams struct { + TeamRef string `json:"teamRef"` + ChannelRef string `json:"channelRef"` + RawMentions []string `json:"rawMentions"` +} + +func (jsonclient *TeamsJSONClient) GetMentionsInChannel(p map[string]interface{}) (interface{}, error) { + params, err := decoders.DecodeParams[getMentionsInChannelParams](p) + if err != nil { + return nil, err + } + mentions, err := jsonclient.client.Channels.GetMentions(context.TODO(), params.TeamRef, params.ChannelRef, params.RawMentions) + if err != nil { + return nil, err + } + return mentions, nil +} From e3da1b674a3d716abe098ac87979dd6561ed96d8 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Wed, 31 Dec 2025 13:46:57 +0100 Subject: [PATCH 34/99] feat: adjusted requests map --- go/internal/json-client/request-map.go | 86 ++++++++++++++++++----- go/internal/json-client/requests-chats.go | 2 +- 2 files changed, 68 insertions(+), 20 deletions(-) diff --git a/go/internal/json-client/request-map.go b/go/internal/json-client/request-map.go index b91027f..8bdcf99 100644 --- a/go/internal/json-client/request-map.go +++ b/go/internal/json-client/request-map.go @@ -62,6 +62,30 @@ var Handlers = map[string]HandlerFunc{ return c.DeleteChannel(p) }, + "sendMessageToChannel": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + return c.SendMessageToChannel(p) + }, + + "sendReplyToChannel": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + return c.SendReplyToChannel(p) + }, + + "listMessagesInChannel": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + return c.ListMessagesInChannel(p) + }, + + "getMessageInChannel": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + return c.GetMessageInChannel(p) + }, + + "listMessageRepliesInChannel": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + return c.ListMessageRepliesInChannel(p) + }, + + "getMessageReplyInChannel": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + return c.GetMessageReplyInChannel(p) + }, + "listChannelMembers": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { return c.ListChannelMembers(p) }, @@ -78,48 +102,72 @@ var Handlers = map[string]HandlerFunc{ return c.UpdateMemberInChannel(p) }, - "sendMessageToChannel": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { - return c.SendMessageToChannel(p) + "getMentionsInChannel": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + return c.GetMentionsInChannel(p) }, - "listMessagesInChannel": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { - return c.ListMessagesInChannel(p) + // CHATS + "createOneOnOneChat": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + return c.CreateOneToOneChat(p) }, - "getMessageInChannel": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { - return c.GetMessageInChannel(p) + "createGroupChat": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + return c.CreateGroupChat(p) }, - "listMessageRepliesInChannel": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { - return c.ListMessageRepliesInChannel(p) + "addMemberGroupToChat": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + return c.AddMemberGroupToChat(p) }, - "getMessageReplyInChannel": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { - return c.GetMessageReplyInChannel(p) + "removeMemberFromGroupChat": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + return c.RemoveMemberFromGroupChat(p) }, - // CHATS - "createOneOnOneChat": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { - return c.CreateOneToOneChat(p) + "listMembersInGroupChat": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + return c.ListGroupChatMembers(p) }, - "createGroupChat": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { - return c.CreateGroupChat(p) + "updateGroupChatTopic": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + return c.UpdateGroupChatTopic(p) + }, + + "listMessagesInChat": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + return c.ListMessagesInChat(p) }, "sendMessageInChat": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { return c.SendMessageInChat(p) }, + "deleteMessageInChat": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + return c.DeleteMessageInChat(p) + }, + + "getMessageInChat": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + return c.GetMessageInChat(p) + }, + "listMyChats": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { return c.ListMyChats(p) }, - "listMembersInChat": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { - return c.ListMembersInChat(p) + "listMyChatMessages": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + return c.ListMyChatMessages(p) + }, + + "listPinnedMessagesInChat": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + return c.ListPinnedMessagesInChat(p) + }, + + "pinMessageInChat": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + return c.PinMessageInChat(p) + }, + + "unpinMessageInChat": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + return c.UnpinMessageInChat(p) }, - "addMemberToChat": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { - return c.AddMemberToChat(p) + "getMentionsInChat": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + return c.GetMentionsInChat(p) }, } diff --git a/go/internal/json-client/requests-chats.go b/go/internal/json-client/requests-chats.go index 1356d7a..448314d 100644 --- a/go/internal/json-client/requests-chats.go +++ b/go/internal/json-client/requests-chats.go @@ -60,7 +60,7 @@ func (jsonclient *TeamsJSONClient) AddMemberGroupToChat(p map[string]interface{} return member, nil } -func (jsonclient *TeamsJSONClient) RemoveMemberToGroupChat(p map[string]interface{}) (interface{}, error) { +func (jsonclient *TeamsJSONClient) RemoveMemberFromGroupChat(p map[string]interface{}) (interface{}, error) { params, err := decoders.DecodeParams[groupChatMemberParams](p) if err != nil { return nil, err From b83ea849fa50d812f7430a28efeb35aa799161a4 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Wed, 31 Dec 2025 13:55:41 +0100 Subject: [PATCH 35/99] refactor: requests schema --- go/internal/json-client/requests-channels.go | 252 +++++---------- go/internal/json-client/requests-chats.go | 316 +++++++------------ go/internal/json-client/requests-teams.go | 133 +++----- go/internal/json-client/teams-json-client.go | 12 +- 4 files changed, 244 insertions(+), 469 deletions(-) diff --git a/go/internal/json-client/requests-channels.go b/go/internal/json-client/requests-channels.go index 1f522ec..e9d390c 100644 --- a/go/internal/json-client/requests-channels.go +++ b/go/internal/json-client/requests-channels.go @@ -11,16 +11,10 @@ type listChannelsParams struct { TeamRef string `json:"teamRef"` } -func (jsonclient *TeamsJSONClient) ListChannels(p map[string]interface{}) (interface{}, error) { - params, err := decoders.DecodeParams[listChannelsParams](p) - if err != nil { - return nil, err - } - channels, err := jsonclient.client.Channels.ListChannels(context.TODO(), params.TeamRef) - if err != nil { - return nil, err - } - return channels, nil +func (c *TeamsJSONClient) ListChannels(p map[string]interface{}) (interface{}, error) { + return execute(p, func(params listChannelsParams) (interface{}, error) { + return c.client.Channels.ListChannels(context.TODO(), params.TeamRef) + }) } type baseChannelParams struct { @@ -28,16 +22,10 @@ type baseChannelParams struct { ChannelRef string `json:"channelRef"` } -func (jsonclient *TeamsJSONClient) GetChannel(p map[string]interface{}) (interface{}, error) { - params, err := decoders.DecodeParams[baseChannelParams](p) - if err != nil { - return nil, err - } - channel, err := jsonclient.client.Channels.Get(context.TODO(), params.TeamRef, params.ChannelRef) - if err != nil { - return nil, err - } - return channel, nil +func (c *TeamsJSONClient) GetChannel(p map[string]interface{}) (interface{}, error) { + return execute(p, func(params baseChannelParams) (interface{}, error) { + return c.client.Channels.Get(context.TODO(), params.TeamRef, params.ChannelRef) + }) } type createChannelParams struct { @@ -45,16 +33,10 @@ type createChannelParams struct { Name string `json:"name"` } -func (jsonclient *TeamsJSONClient) CreateStandardChannel(p map[string]interface{}) (interface{}, error) { - params, err := decoders.DecodeParams[createChannelParams](p) - if err != nil { - return nil, err - } - channel, err := jsonclient.client.Channels.CreateStandardChannel(context.TODO(), params.TeamRef, params.Name) - if err != nil { - return nil, err - } - return channel, nil +func (c *TeamsJSONClient) CreateStandardChannel(p map[string]interface{}) (interface{}, error) { + return execute(p, func(params createChannelParams) (interface{}, error) { + return c.client.Channels.CreateStandardChannel(context.TODO(), params.TeamRef, params.Name) + }) } type createPrivateChannelParams struct { @@ -64,28 +46,17 @@ type createPrivateChannelParams struct { OwnerRefs []string `json:"ownerRefs"` } -func (jsonclient *TeamsJSONClient) CreatePrivateChannel(p map[string]interface{}) (interface{}, error) { - params, err := decoders.DecodeParams[createPrivateChannelParams](p) - if err != nil { - return nil, err - } - channel, err := jsonclient.client.Channels.CreatePrivateChannel(context.TODO(), params.TeamRef, params.Name, params.MemberRefs, params.OwnerRefs) - if err != nil { - return nil, err - } - return channel, nil +func (c *TeamsJSONClient) CreatePrivateChannel(p map[string]interface{}) (interface{}, error) { + return execute(p, func(params createPrivateChannelParams) (interface{}, error) { + return c.client.Channels.CreatePrivateChannel(context.TODO(), params.TeamRef, params.Name, params.MemberRefs, params.OwnerRefs) + }) } -func (jsonclient *TeamsJSONClient) DeleteChannel(p map[string]interface{}) (interface{}, error) { - params, err := decoders.DecodeParams[baseChannelParams](p) - if err != nil { - return nil, err - } - err = jsonclient.client.Channels.Delete(context.TODO(), params.TeamRef, params.ChannelRef) - if err != nil { - return nil, err - } - return "deleted", nil +func (c *TeamsJSONClient) DeleteChannel(p map[string]interface{}) (interface{}, error) { + return execute(p, func(params baseChannelParams) (interface{}, error) { + err := c.client.Channels.Delete(context.TODO(), params.TeamRef, params.ChannelRef) + return "deleted", err + }) } type sendMessageToChannelParams struct { @@ -94,20 +65,14 @@ type sendMessageToChannelParams struct { Body decoders.MessageBodyDTO `json:"body"` } -func (jsonclient *TeamsJSONClient) SendMessageToChannel(p map[string]interface{}) (interface{}, error) { - params, err := decoders.DecodeParams[sendMessageToChannelParams](p) - if err != nil { - return nil, err - } - body, err := decoders.DecodeParams[models.MessageBody](¶ms.Body) - if err != nil { - return nil, err - } - sentMessage, err := jsonclient.client.Channels.SendMessage(context.TODO(), params.TeamRef, params.ChannelRef, *body) - if err != nil { - return nil, err - } - return sentMessage, nil +func (c *TeamsJSONClient) SendMessageToChannel(p map[string]interface{}) (interface{}, error) { + return execute(p, func(params sendMessageToChannelParams) (interface{}, error) { + body, err := decoders.DecodeParams[models.MessageBody](¶ms.Body) + if err != nil { + return nil, err + } + return c.client.Channels.SendMessage(context.TODO(), params.TeamRef, params.ChannelRef, *body) + }) } type sendReplyToChannelParams struct { @@ -117,20 +82,14 @@ type sendReplyToChannelParams struct { Body decoders.MessageBodyDTO `json:"body"` } -func (jsonclient *TeamsJSONClient) SendReplyToChannel(p map[string]interface{}) (interface{}, error) { - params, err := decoders.DecodeParams[sendReplyToChannelParams](p) - if err != nil { - return nil, err - } - body, err := decoders.DecodeParams[models.MessageBody](¶ms.Body) - if err != nil { - return nil, err - } - sentMessage, err := jsonclient.client.Channels.SendReply(context.TODO(), params.TeamRef, params.ChannelRef, params.MessageID, *body) - if err != nil { - return nil, err - } - return sentMessage, nil +func (c *TeamsJSONClient) SendReplyToChannel(p map[string]interface{}) (interface{}, error) { + return execute(p, func(params sendReplyToChannelParams) (interface{}, error) { + body, err := decoders.DecodeParams[models.MessageBody](¶ms.Body) + if err != nil { + return nil, err + } + return c.client.Channels.SendReply(context.TODO(), params.TeamRef, params.ChannelRef, params.MessageID, *body) + }) } type listMessagesOptionsDTO struct { @@ -144,20 +103,14 @@ type listMessagesParams struct { Options listMessagesOptionsDTO `json:"options"` } -func (jsonclient *TeamsJSONClient) ListMessagesInChannel(p map[string]interface{}) (interface{}, error) { - params, err := decoders.DecodeParams[listMessagesParams](p) - if err != nil { - return nil, err - } - options, err := decoders.DecodeParams[models.ListMessagesOptions](params.Options) - if err != nil { - return nil, err - } - messages, err := jsonclient.client.Channels.ListMessages(context.TODO(), params.TeamRef, params.ChannelRef, options) - if err != nil { - return nil, err - } - return messages, nil +func (c *TeamsJSONClient) ListMessagesInChannel(p map[string]interface{}) (interface{}, error) { + return execute(p, func(params listMessagesParams) (interface{}, error) { + options, err := decoders.DecodeParams[models.ListMessagesOptions](params.Options) + if err != nil { + return nil, err + } + return c.client.Channels.ListMessages(context.TODO(), params.TeamRef, params.ChannelRef, options) + }) } type getMessageParams struct { @@ -166,16 +119,10 @@ type getMessageParams struct { MessageID string `json:"messageId"` } -func (jsonclient *TeamsJSONClient) GetMessageInChannel(p map[string]interface{}) (interface{}, error) { - params, err := decoders.DecodeParams[getMessageParams](p) - if err != nil { - return nil, err - } - message, err := jsonclient.client.Channels.GetMessage(context.TODO(), params.TeamRef, params.ChannelRef, params.MessageID) - if err != nil { - return nil, err - } - return message, nil +func (c *TeamsJSONClient) GetMessageInChannel(p map[string]interface{}) (interface{}, error) { + return execute(p, func(params getMessageParams) (interface{}, error) { + return c.client.Channels.GetMessage(context.TODO(), params.TeamRef, params.ChannelRef, params.MessageID) + }) } type listRepliesParams struct { @@ -185,16 +132,10 @@ type listRepliesParams struct { Top *int32 `json:"top"` } -func (jsonclient *TeamsJSONClient) ListMessageRepliesInChannel(p map[string]interface{}) (interface{}, error) { - params, err := decoders.DecodeParams[listRepliesParams](p) - if err != nil { - return nil, err - } - messages, err := jsonclient.client.Channels.ListReplies(context.TODO(), params.TeamRef, params.ChannelRef, params.MessageID, params.Top) - if err != nil { - return nil, err - } - return messages, nil +func (c *TeamsJSONClient) ListMessageRepliesInChannel(p map[string]interface{}) (interface{}, error) { + return execute(p, func(params listRepliesParams) (interface{}, error) { + return c.client.Channels.ListReplies(context.TODO(), params.TeamRef, params.ChannelRef, params.MessageID, params.Top) + }) } type getReplyParams struct { @@ -204,28 +145,16 @@ type getReplyParams struct { ReplyID string `json:"replyId"` } -func (jsonclient *TeamsJSONClient) GetMessageReplyInChannel(p map[string]interface{}) (interface{}, error) { - params, err := decoders.DecodeParams[getReplyParams](p) - if err != nil { - return nil, err - } - message, err := jsonclient.client.Channels.GetReply(context.TODO(), params.TeamRef, params.ChannelRef, params.MessageID, params.ReplyID) - if err != nil { - return nil, err - } - return message, nil +func (c *TeamsJSONClient) GetMessageReplyInChannel(p map[string]interface{}) (interface{}, error) { + return execute(p, func(params getReplyParams) (interface{}, error) { + return c.client.Channels.GetReply(context.TODO(), params.TeamRef, params.ChannelRef, params.MessageID, params.ReplyID) + }) } -func (jsonclient *TeamsJSONClient) ListChannelMembers(p map[string]interface{}) (interface{}, error) { - params, err := decoders.DecodeParams[baseChannelParams](p) - if err != nil { - return nil, err - } - members, err := jsonclient.client.Channels.ListMembers(context.TODO(), params.TeamRef, params.ChannelRef) - if err != nil { - return nil, err - } - return members, nil +func (c *TeamsJSONClient) ListChannelMembers(p map[string]interface{}) (interface{}, error) { + return execute(p, func(params baseChannelParams) (interface{}, error) { + return c.client.Channels.ListMembers(context.TODO(), params.TeamRef, params.ChannelRef) + }) } type addOrUpdateMemberToChannelParams struct { @@ -235,28 +164,16 @@ type addOrUpdateMemberToChannelParams struct { IsOwner bool `json:"isOwner"` } -func (jsonclient *TeamsJSONClient) AddMemberToChannel(p map[string]interface{}) (interface{}, error) { - params, err := decoders.DecodeParams[addOrUpdateMemberToChannelParams](p) - if err != nil { - return nil, err - } - member, err := jsonclient.client.Channels.AddMember(context.TODO(), params.TeamRef, params.ChannelRef, params.UserRef, params.IsOwner) - if err != nil { - return nil, err - } - return member, nil +func (c *TeamsJSONClient) AddMemberToChannel(p map[string]interface{}) (interface{}, error) { + return execute(p, func(params addOrUpdateMemberToChannelParams) (interface{}, error) { + return c.client.Channels.AddMember(context.TODO(), params.TeamRef, params.ChannelRef, params.UserRef, params.IsOwner) + }) } -func (jsonclient *TeamsJSONClient) UpdateMemberInChannel(p map[string]interface{}) (interface{}, error) { - params, err := decoders.DecodeParams[addOrUpdateMemberToChannelParams](p) - if err != nil { - return nil, err - } - member, err := jsonclient.client.Channels.UpdateMemberRole(context.TODO(), params.TeamRef, params.ChannelRef, params.UserRef, params.IsOwner) - if err != nil { - return nil, err - } - return member, nil +func (c *TeamsJSONClient) UpdateMemberInChannel(p map[string]interface{}) (interface{}, error) { + return execute(p, func(params addOrUpdateMemberToChannelParams) (interface{}, error) { + return c.client.Channels.UpdateMemberRole(context.TODO(), params.TeamRef, params.ChannelRef, params.UserRef, params.IsOwner) + }) } type removeMemberFromChannelParams struct { @@ -265,16 +182,11 @@ type removeMemberFromChannelParams struct { UserRef string `json:"userRef"` } -func (jsonclient *TeamsJSONClient) RemoveMemberFromChannel(p map[string]interface{}) (interface{}, error) { - params, err := decoders.DecodeParams[removeMemberFromChannelParams](p) - if err != nil { - return nil, err - } - err = jsonclient.client.Channels.RemoveMember(context.TODO(), params.TeamRef, params.ChannelRef, params.UserRef) - if err != nil { - return nil, err - } - return "removed", nil +func (c *TeamsJSONClient) RemoveMemberFromChannel(p map[string]interface{}) (interface{}, error) { + return execute(p, func(params removeMemberFromChannelParams) (interface{}, error) { + err := c.client.Channels.RemoveMember(context.TODO(), params.TeamRef, params.ChannelRef, params.UserRef) + return "removed", err + }) } type getMentionsInChannelParams struct { @@ -283,14 +195,8 @@ type getMentionsInChannelParams struct { RawMentions []string `json:"rawMentions"` } -func (jsonclient *TeamsJSONClient) GetMentionsInChannel(p map[string]interface{}) (interface{}, error) { - params, err := decoders.DecodeParams[getMentionsInChannelParams](p) - if err != nil { - return nil, err - } - mentions, err := jsonclient.client.Channels.GetMentions(context.TODO(), params.TeamRef, params.ChannelRef, params.RawMentions) - if err != nil { - return nil, err - } - return mentions, nil -} +func (c *TeamsJSONClient) GetMentionsInChannel(p map[string]interface{}) (interface{}, error) { + return execute(p, func(params getMentionsInChannelParams) (interface{}, error) { + return c.client.Channels.GetMentions(context.TODO(), params.TeamRef, params.ChannelRef, params.RawMentions) + }) +} \ No newline at end of file diff --git a/go/internal/json-client/requests-chats.go b/go/internal/json-client/requests-chats.go index 448314d..39cb588 100644 --- a/go/internal/json-client/requests-chats.go +++ b/go/internal/json-client/requests-chats.go @@ -13,16 +13,10 @@ type createOneToOneChatParams struct { RecipientRef string `json:"recipientRef"` } -func (jsonclient *TeamsJSONClient) CreateOneToOneChat(p map[string]interface{}) (interface{}, error) { - params, err := decoders.DecodeParams[createOneToOneChatParams](p) - if err != nil { - return nil, err - } - chat, err := jsonclient.client.Chats.CreateOneOneOne(context.TODO(), params.RecipientRef) - if err != nil { - return nil, err - } - return chat, nil +func (c *TeamsJSONClient) CreateOneToOneChat(p map[string]interface{}) (interface{}, error) { + return execute(p, func(params createOneToOneChatParams) (interface{}, error) { + return c.client.Chats.CreateOneOneOne(context.TODO(), params.RecipientRef) + }) } type createGroupChatParams struct { @@ -31,16 +25,10 @@ type createGroupChatParams struct { IncludeMe bool `json:"includeMe"` } -func (jsonclient *TeamsJSONClient) CreateGroupChat(p map[string]interface{}) (interface{}, error) { - params, err := decoders.DecodeParams[createGroupChatParams](p) - if err != nil { - return nil, err - } - chat, err := jsonclient.client.Chats.CreateGroup(context.TODO(), params.RecipientRefs, params.Topic, params.IncludeMe) - if err != nil { - return nil, err - } - return chat, nil +func (c *TeamsJSONClient) CreateGroupChat(p map[string]interface{}) (interface{}, error) { + return execute(p, func(params createGroupChatParams) (interface{}, error) { + return c.client.Chats.CreateGroup(context.TODO(), params.RecipientRefs, params.Topic, params.IncludeMe) + }) } type groupChatMemberParams struct { @@ -48,44 +36,27 @@ type groupChatMemberParams struct { UserRef string `json:"userRef"` } -func (jsonclient *TeamsJSONClient) AddMemberGroupToChat(p map[string]interface{}) (interface{}, error) { - params, err := decoders.DecodeParams[groupChatMemberParams](p) - if err != nil { - return nil, err - } - member, err := jsonclient.client.Chats.AddMemberToGroupChat(context.TODO(), chats.GroupChatRef{Ref: params.GroupChatRef}, params.UserRef) - if err != nil { - return nil, err - } - return member, nil +func (c *TeamsJSONClient) AddMemberGroupToChat(p map[string]interface{}) (interface{}, error) { + return execute(p, func(params groupChatMemberParams) (interface{}, error) { + return c.client.Chats.AddMemberToGroupChat(context.TODO(), chats.GroupChatRef{Ref: params.GroupChatRef}, params.UserRef) + }) } -func (jsonclient *TeamsJSONClient) RemoveMemberFromGroupChat(p map[string]interface{}) (interface{}, error) { - params, err := decoders.DecodeParams[groupChatMemberParams](p) - if err != nil { - return nil, err - } - err = jsonclient.client.Chats.RemoveMemberFromGroupChat(context.TODO(), chats.GroupChatRef{Ref: params.GroupChatRef}, params.UserRef) - if err != nil { - return nil, err - } - return "removed", nil +func (c *TeamsJSONClient) RemoveMemberFromGroupChat(p map[string]interface{}) (interface{}, error) { + return execute(p, func(params groupChatMemberParams) (interface{}, error) { + err := c.client.Chats.RemoveMemberFromGroupChat(context.TODO(), chats.GroupChatRef{Ref: params.GroupChatRef}, params.UserRef) + return "removed", err + }) } type listMembersInChatParams struct { GroupChatRef string `json:"groupChatRef"` } -func (jsonclient *TeamsJSONClient) ListGroupChatMembers(p map[string]interface{}) (interface{}, error) { - params, err := decoders.DecodeParams[listMembersInChatParams](p) - if err != nil { - return nil, err - } - members, err := jsonclient.client.Chats.ListGroupChatMembers(context.TODO(), chats.GroupChatRef{Ref: params.GroupChatRef}) - if err != nil { - return nil, err - } - return members, nil +func (c *TeamsJSONClient) ListGroupChatMembers(p map[string]interface{}) (interface{}, error) { + return execute(p, func(params listMembersInChatParams) (interface{}, error) { + return c.client.Chats.ListGroupChatMembers(context.TODO(), chats.GroupChatRef{Ref: params.GroupChatRef}) + }) } type updateGroupChatTopicParams struct { @@ -93,36 +64,24 @@ type updateGroupChatTopicParams struct { Topic string `json:"topic"` } -func (jsonclient *TeamsJSONClient) UpdateGroupChatTopic(p map[string]interface{}) (interface{}, error) { - params, err := decoders.DecodeParams[updateGroupChatTopicParams](p) - if err != nil { - return nil, err - } - chat, err := jsonclient.client.Chats.UpdateGroupChatTopic(context.TODO(), chats.GroupChatRef{Ref: params.GroupChatRef}, params.Topic) - if err != nil { - return nil, err - } - return chat, nil +func (c *TeamsJSONClient) UpdateGroupChatTopic(p map[string]interface{}) (interface{}, error) { + return execute(p, func(params updateGroupChatTopicParams) (interface{}, error) { + return c.client.Chats.UpdateGroupChatTopic(context.TODO(), chats.GroupChatRef{Ref: params.GroupChatRef}, params.Topic) + }) } type baseChatParams struct { ChatRef decoders.ChatRefDTO `json:"chatRef"` } -func (jsonclient *TeamsJSONClient) ListMessagesInChat(p map[string]interface{}) (interface{}, error) { - params, err := decoders.DecodeParams[baseChatParams](p) - if err != nil { - return nil, err - } - chatRef, err := decoders.GetChatRef(params.ChatRef) - if err != nil { - return nil, err - } - messages, err := jsonclient.client.Chats.ListMessages(context.TODO(), chatRef) - if err != nil { - return nil, err - } - return messages, nil +func (c *TeamsJSONClient) ListMessagesInChat(p map[string]interface{}) (interface{}, error) { + return execute(p, func(params baseChatParams) (interface{}, error) { + chatRef, err := decoders.GetChatRef(params.ChatRef) + if err != nil { + return nil, err + } + return c.client.Chats.ListMessages(context.TODO(), chatRef) + }) } type sendMessageInChatParams struct { @@ -130,24 +89,18 @@ type sendMessageInChatParams struct { Body decoders.MessageBodyDTO `json:"body"` } -func (jsonclient *TeamsJSONClient) SendMessageInChat(p map[string]interface{}) (interface{}, error) { - params, err := decoders.DecodeParams[sendMessageInChatParams](p) - if err != nil { - return nil, err - } - chatRef, err := decoders.GetChatRef(params.ChatRef) - if err != nil { - return nil, err - } - body, err := decoders.DecodeParams[models.MessageBody](¶ms.Body) - if err != nil { - return nil, err - } - message, err := jsonclient.client.Chats.SendMessage(context.TODO(), chatRef, *body) - if err != nil { - return nil, err - } - return message, nil +func (c *TeamsJSONClient) SendMessageInChat(p map[string]interface{}) (interface{}, error) { + return execute(p, func(params sendMessageInChatParams) (interface{}, error) { + chatRef, err := decoders.GetChatRef(params.ChatRef) + if err != nil { + return nil, err + } + body, err := decoders.DecodeParams[models.MessageBody](params.Body) + if err != nil { + return nil, err + } + return c.client.Chats.SendMessage(context.TODO(), chatRef, *body) + }) } type messageInChatParams struct { @@ -155,56 +108,39 @@ type messageInChatParams struct { MessageID string `json:"messageId"` } -func (jsonclient *TeamsJSONClient) DeleteMessageInChat(p map[string]interface{}) (interface{}, error) { - params, err := decoders.DecodeParams[messageInChatParams](p) - if err != nil { - return nil, err - } - chatRef, err := decoders.GetChatRef(params.ChatRef) - if err != nil { - return nil, err - } - err = jsonclient.client.Chats.DeleteMessage(context.TODO(), chatRef, params.MessageID) - if err != nil { - return nil, err - } - return "deleted", nil +func (c *TeamsJSONClient) DeleteMessageInChat(p map[string]interface{}) (interface{}, error) { + return execute(p, func(params messageInChatParams) (interface{}, error) { + chatRef, err := decoders.GetChatRef(params.ChatRef) + if err != nil { + return nil, err + } + err = c.client.Chats.DeleteMessage(context.TODO(), chatRef, params.MessageID) + return "deleted", err + }) } -func (jsonclient *TeamsJSONClient) GetMessageInChat(p map[string]interface{}) (interface{}, error) { - params, err := decoders.DecodeParams[messageInChatParams](p) - if err != nil { - return nil, err - } - chatRef, err := decoders.GetChatRef(params.ChatRef) - if err != nil { - return nil, err - } - message, err := jsonclient.client.Chats.GetMessage(context.TODO(), chatRef, params.MessageID) - if err != nil { - return nil, err - } - return message, nil +func (c *TeamsJSONClient) GetMessageInChat(p map[string]interface{}) (interface{}, error) { + return execute(p, func(params messageInChatParams) (interface{}, error) { + chatRef, err := decoders.GetChatRef(params.ChatRef) + if err != nil { + return nil, err + } + return c.client.Chats.GetMessage(context.TODO(), chatRef, params.MessageID) + }) } type ListMyChatsParams struct { ChatType string `json:"chatType"` } -func (jsonclient *TeamsJSONClient) ListMyChats(p map[string]interface{}) (interface{}, error) { - params, err := decoders.DecodeParams[ListMyChatsParams](p) - if err != nil { - return nil, err - } - var chatType models.ChatType - if params.ChatType != "" { - chatType = models.ChatType(params.ChatType) - } - chatsList, err := jsonclient.client.Chats.ListChats(context.TODO(), &chatType) - if err != nil { - return nil, err - } - return chatsList, nil +func (c *TeamsJSONClient) ListMyChats(p map[string]interface{}) (interface{}, error) { + return execute(p, func(params ListMyChatsParams) (interface{}, error) { + var chatType models.ChatType + if params.ChatType != "" { + chatType = models.ChatType(params.ChatType) + } + return c.client.Chats.ListChats(context.TODO(), &chatType) + }) } type listChatMessagesParams struct { @@ -213,83 +149,55 @@ type listChatMessagesParams struct { Top *int32 `json:"top"` } -func (jsonclient *TeamsJSONClient) ListMyChatMessages(p map[string]interface{}) (interface{}, error) { - params, err := decoders.DecodeParams[listChatMessagesParams](p) - if err != nil { - return nil, err - } - messages, err := jsonclient.client.Chats.ListAllMessages(context.TODO(), ¶ms.StartTime, ¶ms.EndTime, params.Top) - if err != nil { - return nil, err - } - return messages, nil +func (c *TeamsJSONClient) ListMyChatMessages(p map[string]interface{}) (interface{}, error) { + return execute(p, func(params listChatMessagesParams) (interface{}, error) { + return c.client.Chats.ListAllMessages(context.TODO(), ¶ms.StartTime, ¶ms.EndTime, params.Top) + }) } -func (jsonclient *TeamsJSONClient) ListPinnedMessagesInChat(p map[string]interface{}) (interface{}, error) { - params, err := decoders.DecodeParams[baseChatParams](p) - if err != nil { - return nil, err - } - chatRef, err := decoders.GetChatRef(params.ChatRef) - if err != nil { - return nil, err - } - messages, err := jsonclient.client.Chats.ListPinnedMessages(context.TODO(), chatRef) - if err != nil { - return nil, err - } - return messages, nil +func (c *TeamsJSONClient) ListPinnedMessagesInChat(p map[string]interface{}) (interface{}, error) { + return execute(p, func(params baseChatParams) (interface{}, error) { + chatRef, err := decoders.GetChatRef(params.ChatRef) + if err != nil { + return nil, err + } + return c.client.Chats.ListPinnedMessages(context.TODO(), chatRef) + }) } -func (jsonclient *TeamsJSONClient) PinMessageInChat(p map[string]interface{}) (interface{}, error) { - params, err := decoders.DecodeParams[messageInChatParams](p) - if err != nil { - return nil, err - } - chatRef, err := decoders.GetChatRef(params.ChatRef) - if err != nil { - return nil, err - } - err = jsonclient.client.Chats.PinMessage(context.TODO(), chatRef, params.MessageID) - if err != nil { - return nil, err - } - return "pinned", nil +func (c *TeamsJSONClient) PinMessageInChat(p map[string]interface{}) (interface{}, error) { + return execute(p, func(params messageInChatParams) (interface{}, error) { + chatRef, err := decoders.GetChatRef(params.ChatRef) + if err != nil { + return nil, err + } + err = c.client.Chats.PinMessage(context.TODO(), chatRef, params.MessageID) + return "pinned", err + }) } -func (jsonclient *TeamsJSONClient) UnpinMessageInChat(p map[string]interface{}) (interface{}, error) { - params, err := decoders.DecodeParams[messageInChatParams](p) - if err != nil { - return nil, err - } - chatRef, err := decoders.GetChatRef(params.ChatRef) - if err != nil { - return nil, err - } - err = jsonclient.client.Chats.UnpinMessage(context.TODO(), chatRef, params.MessageID) - if err != nil { - return nil, err - } - return "unpinned", nil +func (c *TeamsJSONClient) UnpinMessageInChat(p map[string]interface{}) (interface{}, error) { + return execute(p, func(params messageInChatParams) (interface{}, error) { + chatRef, err := decoders.GetChatRef(params.ChatRef) + if err != nil { + return nil, err + } + err = c.client.Chats.UnpinMessage(context.TODO(), chatRef, params.MessageID) + return "unpinned", err + }) } type mentionInChatParams struct { - ChatRef decoders.ChatRefDTO `json:"chatRef"` - RawMentions []string `json:"rawMentions"` -} - -func (jsonclient *TeamsJSONClient) GetMentionsInChat(p map[string]interface{}) (interface{}, error) { - params, err := decoders.DecodeParams[mentionInChatParams](p) - if err != nil { - return nil, err - } - chatRef, err := decoders.GetChatRef(params.ChatRef) - if err != nil { - return nil, err - } - mentions, err := jsonclient.client.Chats.GetMentions(context.TODO(), chatRef, params.RawMentions) - if err != nil { - return nil, err - } - return mentions, nil + ChatRef decoders.ChatRefDTO `json:"chatRef"` + RawMentions []string `json:"rawMentions"` +} + +func (c *TeamsJSONClient) GetMentionsInChat(p map[string]interface{}) (interface{}, error) { + return execute(p, func(params mentionInChatParams) (interface{}, error) { + chatRef, err := decoders.GetChatRef(params.ChatRef) + if err != nil { + return nil, err + } + return c.client.Chats.GetMentions(context.TODO(), chatRef, params.RawMentions) + }) } diff --git a/go/internal/json-client/requests-teams.go b/go/internal/json-client/requests-teams.go index 0c8a3d4..8cbc140 100644 --- a/go/internal/json-client/requests-teams.go +++ b/go/internal/json-client/requests-teams.go @@ -10,24 +10,14 @@ type getTeamParams struct { TeamRef string `json:"teamRef"` } -func (jsonclient *TeamsJSONClient) GetTeam(p map[string]interface{}) (interface{}, error) { - params, err := decoders.DecodeParams[getTeamParams](p) - if err != nil { - return nil, err - } - team, err := jsonclient.client.Teams.Get(context.TODO(), params.TeamRef) - if err != nil { - return nil, err - } - return team, nil -} - -func (jsonclient *TeamsJSONClient) ListMyJoined() (interface{}, error) { - teams, err := jsonclient.client.Teams.ListMyJoined(context.TODO()) - if err != nil { - return nil, err - } - return teams, nil +func (c *TeamsJSONClient) GetTeam(p map[string]interface{}) (interface{}, error) { + return execute(p, func(params getTeamParams) (interface{}, error) { + return c.client.Teams.Get(context.TODO(), params.TeamRef) + }) +} + +func (c *TeamsJSONClient) ListMyJoined() (interface{}, error) { + return c.client.Teams.ListMyJoined(context.TODO()) } type updateTeamParams struct { @@ -35,17 +25,11 @@ type updateTeamParams struct { TeamPatch decoders.TeamPatchDTO `json:"team"` } -func (jsonclient *TeamsJSONClient) UpdateTeam(p map[string]interface{}) (interface{}, error) { - params, err := decoders.DecodeParams[updateTeamParams](p) - msTeam := decoders.GetMSTeam(¶ms.TeamPatch) - if err != nil { - return nil, err - } - updatedTeam, err := jsonclient.client.Teams.Update(context.TODO(), params.TeamRef, msTeam) - if err != nil { - return nil, err - } - return updatedTeam, nil +func (c *TeamsJSONClient) UpdateTeam(p map[string]interface{}) (interface{}, error) { + return execute(p, func(params updateTeamParams) (interface{}, error) { + msTeam := decoders.GetMSTeam(¶ms.TeamPatch) + return c.client.Teams.Update(context.TODO(), params.TeamRef, msTeam) + }) } type createViaGroupParams struct { @@ -54,16 +38,10 @@ type createViaGroupParams struct { Visibility string `json:"visibility"` } -func (jsonclient *TeamsJSONClient) CreateTeamViaGroup(p map[string]interface{}) (interface{}, error) { - params, err := decoders.DecodeParams[createViaGroupParams](p) - if err != nil { - return nil, err - } - createdTeam, err := jsonclient.client.Teams.CreateViaGroup(context.TODO(), params.DisplayName, params.MailNickname, params.Visibility) - if err != nil { - return nil, err - } - return createdTeam, nil +func (c *TeamsJSONClient) CreateTeamViaGroup(p map[string]interface{}) (interface{}, error) { + return execute(p, func(params createViaGroupParams) (interface{}, error) { + return c.client.Teams.CreateViaGroup(context.TODO(), params.DisplayName, params.MailNickname, params.Visibility) + }) } type createFromTemplateParams struct { @@ -72,79 +50,52 @@ type createFromTemplateParams struct { Owners []string `json:"owners"` } -func (jsonclient *TeamsJSONClient) CreateTeamFromTemplate(p map[string]interface{}) (interface{}, error) { - params, err := decoders.DecodeParams[createFromTemplateParams](p) - if err != nil { - return nil, err - } - createdTeam, err := jsonclient.client.Teams.CreateFromTemplate(context.TODO(), params.DisplayName, params.Description, params.Owners) - if err != nil { - return nil, err - } - return createdTeam, nil +func (c *TeamsJSONClient) CreateTeamFromTemplate(p map[string]interface{}) (interface{}, error) { + return execute(p, func(params createFromTemplateParams) (interface{}, error) { + return c.client.Teams.CreateFromTemplate(context.TODO(), params.DisplayName, params.Description, params.Owners) + }) } type archiveTeamParams struct { - TeamRef string `json:"teamRef"` + TeamRef string `json:"teamRef"` SpoReadOnlyFromMembers *bool `json:"spoReadOnlyFromMembers,omitempty"` } -func (jsonclient *TeamsJSONClient) ArchiveTeam(p map[string]interface{}) (interface{}, error) { - params, err := decoders.DecodeParams[archiveTeamParams](p) - if err != nil { - return nil, err - } - err = jsonclient.client.Teams.Archive(context.TODO(), params.TeamRef, params.SpoReadOnlyFromMembers) - if err != nil { - return nil, err - } - return "archived", nil +func (c *TeamsJSONClient) ArchiveTeam(p map[string]interface{}) (interface{}, error) { + return execute(p, func(params archiveTeamParams) (interface{}, error) { + err := c.client.Teams.Archive(context.TODO(), params.TeamRef, params.SpoReadOnlyFromMembers) + return "archived", err + }) } type unarchiveTeamParams struct { TeamRef string `json:"teamRef"` } -func (jsonclient *TeamsJSONClient) UnarchiveTeam(p map[string]interface{}) (interface{}, error) { - params, err := decoders.DecodeParams[unarchiveTeamParams](p) - if err != nil { - return nil, err - } - err = jsonclient.client.Teams.Unarchive(context.TODO(), params.TeamRef) - if err != nil { - return nil, err - } - return "unarchived", nil +func (c *TeamsJSONClient) UnarchiveTeam(p map[string]interface{}) (interface{}, error) { + return execute(p, func(params unarchiveTeamParams) (interface{}, error) { + err := c.client.Teams.Unarchive(context.TODO(), params.TeamRef) + return "unarchived", err + }) } type deleteTeamParams struct { TeamRef string `json:"teamRef"` } -func (jsonclient *TeamsJSONClient) DeleteTeam(p map[string]interface{}) (interface{}, error) { - params, err := decoders.DecodeParams[deleteTeamParams](p) - if err != nil { - return nil, err - } - err = jsonclient.client.Teams.Delete(context.TODO(), params.TeamRef) - if err != nil { - return nil, err - } - return "deleted", nil +func (c *TeamsJSONClient) DeleteTeam(p map[string]interface{}) (interface{}, error) { + return execute(p, func(params deleteTeamParams) (interface{}, error) { + err := c.client.Teams.Delete(context.TODO(), params.TeamRef) + return "deleted", err + }) } type restoreTeamParams struct { DeletedGroupID string `json:"deletedGroupId"` } -func (jsonclient *TeamsJSONClient) RestoreDeletedTeam(p map[string]interface{}) (interface{}, error) { - params, err := decoders.DecodeParams[restoreTeamParams](p) - if err != nil { - return nil, err - } - restoredTeam, err := jsonclient.client.Teams.RestoreDeleted(context.TODO(), params.DeletedGroupID) - if err != nil { - return nil, err - } - return restoredTeam, nil -} +func (c *TeamsJSONClient) RestoreDeletedTeam(p map[string]interface{}) (interface{}, error) { + return execute(p, func(params restoreTeamParams) (interface{}, error) { + return c.client.Teams.RestoreDeleted(context.TODO(), params.DeletedGroupID) + }) +} \ No newline at end of file diff --git a/go/internal/json-client/teams-json-client.go b/go/internal/json-client/teams-json-client.go index afe9430..20b87a8 100644 --- a/go/internal/json-client/teams-json-client.go +++ b/go/internal/json-client/teams-json-client.go @@ -1,9 +1,19 @@ package jsonclient import ( - lib "github.com/pzsp-teams/lib" + "github.com/pzsp-teams/lib" + + "github.com/pzsp-teams/lib-python/internal/json-client/decoders" ) type TeamsJSONClient struct { client *lib.Client } + +func execute[T any](p map[string]interface{}, fn func(params T) (interface{}, error)) (interface{}, error) { + params, err := decoders.DecodeParams[T](p) + if err != nil { + return nil, err + } + return fn(*params) +} From 2a0944bff1b681b94f7160a3adf0b811bfb1f560 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Thu, 1 Jan 2026 18:51:32 +0100 Subject: [PATCH 36/99] chore: tried to fix resolving user bug (failed) --- go/internal/json-client/requests-channels.go | 14 +++++++++++++- python/teams_lib_pzsp2_z1/client.py | 2 ++ python/tests/fake_server/data.py | 20 ++++++-------------- python/tests/test_teams.py | 2 +- 4 files changed, 22 insertions(+), 16 deletions(-) diff --git a/go/internal/json-client/requests-channels.go b/go/internal/json-client/requests-channels.go index e9d390c..f249de1 100644 --- a/go/internal/json-client/requests-channels.go +++ b/go/internal/json-client/requests-channels.go @@ -2,6 +2,8 @@ package jsonclient import ( "context" + "fmt" + "os" "github.com/pzsp-teams/lib-python/internal/json-client/decoders" "github.com/pzsp-teams/lib/models" @@ -172,6 +174,11 @@ func (c *TeamsJSONClient) AddMemberToChannel(p map[string]interface{}) (interfac func (c *TeamsJSONClient) UpdateMemberInChannel(p map[string]interface{}) (interface{}, error) { return execute(p, func(params addOrUpdateMemberToChannelParams) (interface{}, error) { + + // --- LOGOWANIE NA STDERR --- + fmt.Fprintf(os.Stderr, "\n[GO DEBUG] UpdateMemberInChannel PARAMS:\n -> TeamRef: '%s'\n -> ChannelRef: '%s'\n -> UserRef: '%s'\n -> IsOwner: %v\n", + params.TeamRef, params.ChannelRef, params.UserRef, params.IsOwner) + // --------------------------- return c.client.Channels.UpdateMemberRole(context.TODO(), params.TeamRef, params.ChannelRef, params.UserRef, params.IsOwner) }) } @@ -184,6 +191,11 @@ type removeMemberFromChannelParams struct { func (c *TeamsJSONClient) RemoveMemberFromChannel(p map[string]interface{}) (interface{}, error) { return execute(p, func(params removeMemberFromChannelParams) (interface{}, error) { + + // --- LOGOWANIE NA STDERR --- + fmt.Fprintf(os.Stderr, "\n[GO DEBUG] RemoveMemberFromChannel PARAMS:\n -> TeamRef: '%s'\n -> ChannelRef: '%s'\n -> UserRef: '%s'\n", + params.TeamRef, params.ChannelRef, params.UserRef) + // --------------------------- err := c.client.Channels.RemoveMember(context.TODO(), params.TeamRef, params.ChannelRef, params.UserRef) return "removed", err }) @@ -199,4 +211,4 @@ func (c *TeamsJSONClient) GetMentionsInChannel(p map[string]interface{}) (interf return execute(p, func(params getMentionsInChannelParams) (interface{}, error) { return c.client.Channels.GetMentions(context.TODO(), params.TeamRef, params.ChannelRef, params.RawMentions) }) -} \ No newline at end of file +} diff --git a/python/teams_lib_pzsp2_z1/client.py b/python/teams_lib_pzsp2_z1/client.py index 7a0e76c..b64c63a 100644 --- a/python/teams_lib_pzsp2_z1/client.py +++ b/python/teams_lib_pzsp2_z1/client.py @@ -106,6 +106,8 @@ def execute( self.proc.stdin.write(json_payload + "\n") self.proc.stdin.flush() + print("Sent to Go process:", json_payload) # Debug print + raw_response = self.proc.stdout.readline() except BrokenPipeError: raise RuntimeError("Go process crashed or closed connection") # noqa: B904 diff --git a/python/tests/fake_server/data.py b/python/tests/fake_server/data.py index 27e22ca..bcc22a2 100644 --- a/python/tests/fake_server/data.py +++ b/python/tests/fake_server/data.py @@ -44,8 +44,8 @@ def __init__(self) -> FakeServerData: "team-123-abc": [ Channel( ID="19:123123@thread.tacv2", - Name="General", - IsGeneral=True, + Name="Something", + IsGeneral=False, ), Channel( ID="19:999999@thread.tacv2", @@ -171,11 +171,15 @@ def get_myJoinedTeams_response(self) -> dict: def get_listChannels_response(self, team_id: str) -> dict: return { + "@odata.context": f"https://graph.microsoft.com/v1.0/$metadata#teams('{team_id}')/channels", "value": [ { + "@odata.type": "#microsoft.graph.channel", "id": channel.ID, "displayName": channel.Name, "isGeneral": channel.IsGeneral, + "membershipType": "standard", + "email": "" } for channel in self.channels.get(team_id, []) ], @@ -509,15 +513,3 @@ def get_remove_member_response(self, team_id: str, channel_id: str, member_id: s channel_members.remove(member) return {"success": True} - - - - - - - - - - - - diff --git a/python/tests/test_teams.py b/python/tests/test_teams.py index 6b1873a..a234c9f 100644 --- a/python/tests/test_teams.py +++ b/python/tests/test_teams.py @@ -1,4 +1,4 @@ -from python.teams_lib_pzsp2_z1.model.team import MSTeamsUpdate +from teams_lib_pzsp2_z1.model.team import MSTeamsUpdate from teams_lib_pzsp2_z1.client import TeamsClient from tests.init_fake_client import init_fake_client from tests.fake_server.setup import setup_fake_server From 8f306804364b937b757e222563e82c46c7834ba2 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Thu, 1 Jan 2026 19:08:08 +0100 Subject: [PATCH 37/99] feat: added majority of chats request in python client --- go/internal/json-client/request-map.go | 4 +- go/internal/json-client/requests-chats.go | 2 +- python/teams_lib_pzsp2_z1/model/chat.py | 5 + python/teams_lib_pzsp2_z1/services/chats.py | 309 +++++++++++++++++++- 4 files changed, 303 insertions(+), 17 deletions(-) diff --git a/go/internal/json-client/request-map.go b/go/internal/json-client/request-map.go index 8bdcf99..68bd08f 100644 --- a/go/internal/json-client/request-map.go +++ b/go/internal/json-client/request-map.go @@ -115,8 +115,8 @@ var Handlers = map[string]HandlerFunc{ return c.CreateGroupChat(p) }, - "addMemberGroupToChat": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { - return c.AddMemberGroupToChat(p) + "addMemberToGroupChat": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + return c.AddMemberToGroupChat(p) }, "removeMemberFromGroupChat": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { diff --git a/go/internal/json-client/requests-chats.go b/go/internal/json-client/requests-chats.go index 39cb588..f843158 100644 --- a/go/internal/json-client/requests-chats.go +++ b/go/internal/json-client/requests-chats.go @@ -36,7 +36,7 @@ type groupChatMemberParams struct { UserRef string `json:"userRef"` } -func (c *TeamsJSONClient) AddMemberGroupToChat(p map[string]interface{}) (interface{}, error) { +func (c *TeamsJSONClient) AddMemberToGroupChat(p map[string]interface{}) (interface{}, error) { return execute(p, func(params groupChatMemberParams) (interface{}, error) { return c.client.Chats.AddMemberToGroupChat(context.TODO(), chats.GroupChatRef{Ref: params.GroupChatRef}, params.UserRef) }) diff --git a/python/teams_lib_pzsp2_z1/model/chat.py b/python/teams_lib_pzsp2_z1/model/chat.py index 7224aed..206bc83 100644 --- a/python/teams_lib_pzsp2_z1/model/chat.py +++ b/python/teams_lib_pzsp2_z1/model/chat.py @@ -13,3 +13,8 @@ class Chat: Type: ChatType IsHidden: bool Topic: str + +@dataclass +class ChatRef: + Ref: str + type: ChatType diff --git a/python/teams_lib_pzsp2_z1/services/chats.py b/python/teams_lib_pzsp2_z1/services/chats.py index baec592..4042559 100644 --- a/python/teams_lib_pzsp2_z1/services/chats.py +++ b/python/teams_lib_pzsp2_z1/services/chats.py @@ -1,4 +1,4 @@ -from teams_lib_pzsp2_z1.model.chat import Chat, ChatType +from teams_lib_pzsp2_z1.model.chat import Chat, ChatType, ChatRef from teams_lib_pzsp2_z1.model.member import Member from teams_lib_pzsp2_z1.model.message import ( Message, @@ -8,30 +8,105 @@ ) from teams_lib_pzsp2_z1.services.base_service import BaseService +from datetime import datetime + class ChatsService(BaseService): - def list_my_joined(self) -> list[Chat]: + def create_one_on_one(self, recipient_ref: str) -> Chat: response = self.client.execute( cmd_type="request", - method="listMyJoinedChats", - params={}, + method="createOneOnOneChat", + params={ + "recipientRef": recipient_ref, + }, + ) + + return Chat( + ID=response["ID"], + Type=ChatType(response["Type"]), + IsHidden=(True if response["IsHidden"] else False), + Topic=response["Topic"], + ) + + def create_group_chat( + self, recipient_refs: list[str], topic: str, include_me: bool + ) -> Chat: + response = self.client.execute( + cmd_type="request", + method="createGroupChat", + params={ + "recipientRefs": recipient_refs, + "topic": topic, + "includeMe": include_me, + }, + ) + + return Chat( + ID=response["ID"], + Type=ChatType(response["Type"]), + IsHidden=(True if response["IsHidden"] else False), + Topic=response["Topic"], + ) + + def add_member_to_group_chat(self, group_chat_ref: str, user_ref: str) -> Member: + member = self.client.execute( + cmd_type="request", + method="addMemberToGroupChat", + params={ + "groupChatRef": group_chat_ref, + "userRef": user_ref, + }, + ) + + return Member( + ID=member["ID"], + DisplayName=member["DisplayName"], + UserRef=member["UserRef"], + Roles=member["Roles"], + Email=member["Email"], ) + + def remove_member_from_group_chat( + self, group_chat_ref: str, member_ref: str + ) -> bool: + result = self.client.execute( + cmd_type="request", + method="removeMemberFromGroupChat", + params={ + "groupChatRef": group_chat_ref, + "userRef": member_ref, + }, + ) + + return result == "removed" + + def list_group_chat_members(self, group_chat_ref: str) -> list[Member]: + members = self.client.execute( + cmd_type="request", + method="listGroupChatMembers", + params={ + "groupChatRef": group_chat_ref, + }, + ) + return [ - Chat( - ID=chat["ID"], - Type=ChatType(chat["Type"]), - IsHidden=(True if chat["IsHidden"] else False), - Topic=chat["Topic"], + Member( + ID=member["ID"], + DisplayName=member["DisplayName"], + UserRef=member["UserRef"], + Roles=member["Roles"], + Email=member["Email"], ) - for chat in response + for member in members ] - def create_one_on_one(self, user_id: str) -> Chat: + def update_group_chat_topic(self, group_chat_ref: str, new_topic: str) -> Chat: response = self.client.execute( cmd_type="request", - method="createOneOnOneChat", + method="updateGroupChatTopic", params={ - "userID": user_id, + "groupChatRef": group_chat_ref, + "topic": new_topic, }, ) @@ -40,4 +115,210 @@ def create_one_on_one(self, user_id: str) -> Chat: Type=ChatType(response["Type"]), IsHidden=(True if response["IsHidden"] else False), Topic=response["Topic"], - ) \ No newline at end of file + ) + + def list_messages(self, chat_ref: str) -> list[Message]: + messages = self.client.execute( + cmd_type="request", + method="listChatMessages", + params={ + "chatRef": chat_ref, + }, + ) + + return [ + Message( + ID=message["ID"], + Body=MessageBody( + Content=message["Body"]["Content"], + ContentType=MessageContentType(message["Body"]["ContentType"]), + ), + From=MessageFrom( + UserRef=message["From"]["UserRef"], + DisplayName=message["From"]["DisplayName"], + ), + CreatedDateTime=message["CreatedDateTime"], + ) + for message in messages + ] + + def send_message(self, chat_ref: ChatRef, body: MessageBody) -> Message: + message = self.client.execute( + cmd_type="request", + method="sendMessageToChat", + params={ + "chatRef": { + "ref": chat_ref.Ref, + "type": chat_ref.type.value, + }, + "body": { + "content": body.Content, + "contentType": body.ContentType.value, + }, + }, + ) + + return Message( + ID=message["ID"], + Body=MessageBody( + Content=message["Body"]["Content"], + ContentType=MessageContentType(message["Body"]["ContentType"]), + ), + From=MessageFrom( + UserRef=message["From"]["UserRef"], + DisplayName=message["From"]["DisplayName"], + ), + CreatedDateTime=message["CreatedDateTime"], + ) + + def delete_message(self, chat_ref: ChatRef, message_id: str) -> bool: + result = self.client.execute( + cmd_type="request", + method="deleteMessageFromChat", + params={ + "chatRef": { + "ref": chat_ref.Ref, + "type": chat_ref.type.value, + }, + "messageID": message_id, + }, + ) + + return result == "deleted" + + def get_message(self, chat_ref: ChatRef, message_id: str) -> Message: + message = self.client.execute( + cmd_type="request", + method="getMessageFromChat", + params={ + "chatRef": { + "ref": chat_ref.Ref, + "type": chat_ref.type.value, + }, + "messageID": message_id, + }, + ) + + return Message( + ID=message["ID"], + Body=MessageBody( + Content=message["Body"]["Content"], + ContentType=MessageContentType(message["Body"]["ContentType"]), + ), + From=MessageFrom( + UserRef=message["From"]["UserRef"], + DisplayName=message["From"]["DisplayName"], + ), + CreatedDateTime=message["CreatedDateTime"], + ) + + def list_my_joined(self, chat_type: ChatType | None = None) -> list[Chat]: + params = {} + if chat_type: + params["chatType"] = chat_type.value + + chats = self.client.execute( + cmd_type="request", + method="listMyJoinedChats", + params=params, + ) + + return [ + Chat( + ID=chat["ID"], + Type=ChatType(chat["Type"]), + IsHidden=(True if chat["IsHidden"] else False), + Topic=chat["Topic"], + ) + for chat in chats + ] + + def list_all_messages( + self, start_time: datetime, end_time: datetime, top: int + ) -> list[Message]: + messages = self.client.execute( + cmd_type="request", + method="listAllMessages", + params={ + "startTime": start_time.isoformat(), + "endTime": end_time.isoformat(), + "top": top, + }, + ) + + return [ + Message( + ID=message["ID"], + Body=MessageBody( + Content=message["Body"]["Content"], + ContentType=MessageContentType(message["Body"]["ContentType"]), + ), + From=MessageFrom( + UserRef=message["From"]["UserRef"], + DisplayName=message["From"]["DisplayName"], + ), + CreatedDateTime=message["CreatedDateTime"], + ) + for message in messages + ] + + def list_pinned_messages(self, chat_ref: ChatRef) -> list[Message]: + messages = self.client.execute( + cmd_type="request", + method="listPinnedMessages", + params={ + "chatRef": { + "ref": chat_ref.Ref, + "type": chat_ref.type.value, + }, + }, + ) + + return [ + Message( + ID=message["ID"], + Body=MessageBody( + Content=message["Body"]["Content"], + ContentType=MessageContentType(message["Body"]["ContentType"]), + ), + From=MessageFrom( + UserRef=message["From"]["UserRef"], + DisplayName=message["From"]["DisplayName"], + ), + CreatedDateTime=message["CreatedDateTime"], + ) + for message in messages + ] + + def pin_message(self, chat_ref: ChatRef, message_id: str) -> bool: + result = self.client.execute( + cmd_type="request", + method="pinMessageInChat", + params={ + "chatRef": { + "ref": chat_ref.Ref, + "type": chat_ref.type.value, + }, + "messageID": message_id, + }, + ) + + return result == "pinned" + + + def unpin_message(self, chat_ref: ChatRef, message_id: str) -> bool: + result = self.client.execute( + cmd_type="request", + method="unpinMessageInChat", + params={ + "chatRef": { + "ref": chat_ref.Ref, + "type": chat_ref.type.value, + }, + "messageID": message_id, + }, + ) + + return result == "unpinned" + + # def get_mentions(self, chat_ref: ChatRef, raw_mentions: list[str]) From e1202fd8de9dd6c056061291f361a1a1db3950f1 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Thu, 1 Jan 2026 19:16:09 +0100 Subject: [PATCH 38/99] feat: implemented get mentions handling --- python/teams_lib_pzsp2_z1/model/mention.py | 16 +++++++ python/teams_lib_pzsp2_z1/model/message.py | 3 ++ .../teams_lib_pzsp2_z1/services/channels.py | 15 +++++++ python/teams_lib_pzsp2_z1/services/chats.py | 45 ++++++++++++++----- 4 files changed, 67 insertions(+), 12 deletions(-) create mode 100644 python/teams_lib_pzsp2_z1/model/mention.py diff --git a/python/teams_lib_pzsp2_z1/model/mention.py b/python/teams_lib_pzsp2_z1/model/mention.py new file mode 100644 index 0000000..361df1b --- /dev/null +++ b/python/teams_lib_pzsp2_z1/model/mention.py @@ -0,0 +1,16 @@ +from dataclasses import dataclass +from enum import Enum + + +class MentionKind(Enum): + USER = "user" + CHANNEL = "channel" + TEAM = "team" + EVERYONE = "everyone" + +@dataclass +class Mention: + Kind: MentionKind + AtID: int + Text: str + TargetID: str diff --git a/python/teams_lib_pzsp2_z1/model/message.py b/python/teams_lib_pzsp2_z1/model/message.py index 5ff9a4d..52f8139 100644 --- a/python/teams_lib_pzsp2_z1/model/message.py +++ b/python/teams_lib_pzsp2_z1/model/message.py @@ -2,6 +2,8 @@ from datetime import datetime from enum import Enum +from teams_lib_pzsp2_z1.model.mention import Mention + class MessageContentType(Enum): TEXT = "text" @@ -28,6 +30,7 @@ class Message: class MessageBody: ContentType: MessageContentType Content: str + Mentions: list[Mention] def __dict__(self): return { diff --git a/python/teams_lib_pzsp2_z1/services/channels.py b/python/teams_lib_pzsp2_z1/services/channels.py index c4c5172..084e529 100644 --- a/python/teams_lib_pzsp2_z1/services/channels.py +++ b/python/teams_lib_pzsp2_z1/services/channels.py @@ -1,5 +1,6 @@ from teams_lib_pzsp2_z1.model.channel import Channel from teams_lib_pzsp2_z1.model.member import Member +from teams_lib_pzsp2_z1.model.mention import Mention from teams_lib_pzsp2_z1.model.message import ( Message, MessageBody, @@ -290,3 +291,17 @@ def remove_member(self, teamRef: str, channelRef: str, userRef: str) -> bool: }, ) return response == "removed" + + def get_mentions( + self, teamRef: str, channelRef: str, rawMentions: list[str] + ) -> list[Mention]: + response = self.client.execute( + cmd_type="request", + method="getChannelMentions", + params={ + "teamRef": teamRef, + "channelRef": channelRef, + "rawMentions": rawMentions, + }, + ) + return [Mention(**mention) for mention in response] diff --git a/python/teams_lib_pzsp2_z1/services/chats.py b/python/teams_lib_pzsp2_z1/services/chats.py index 4042559..5838203 100644 --- a/python/teams_lib_pzsp2_z1/services/chats.py +++ b/python/teams_lib_pzsp2_z1/services/chats.py @@ -1,5 +1,8 @@ -from teams_lib_pzsp2_z1.model.chat import Chat, ChatType, ChatRef +from datetime import datetime + +from teams_lib_pzsp2_z1.model.chat import Chat, ChatRef, ChatType from teams_lib_pzsp2_z1.model.member import Member +from teams_lib_pzsp2_z1.model.mention import Mention from teams_lib_pzsp2_z1.model.message import ( Message, MessageBody, @@ -8,8 +11,6 @@ ) from teams_lib_pzsp2_z1.services.base_service import BaseService -from datetime import datetime - class ChatsService(BaseService): def create_one_on_one(self, recipient_ref: str) -> Chat: @@ -83,7 +84,7 @@ def remove_member_from_group_chat( def list_group_chat_members(self, group_chat_ref: str) -> list[Member]: members = self.client.execute( cmd_type="request", - method="listGroupChatMembers", + method="listMembersInGroupChat", params={ "groupChatRef": group_chat_ref, }, @@ -120,7 +121,7 @@ def update_group_chat_topic(self, group_chat_ref: str, new_topic: str) -> Chat: def list_messages(self, chat_ref: str) -> list[Message]: messages = self.client.execute( cmd_type="request", - method="listChatMessages", + method="listMessagesInChat", params={ "chatRef": chat_ref, }, @@ -145,7 +146,7 @@ def list_messages(self, chat_ref: str) -> list[Message]: def send_message(self, chat_ref: ChatRef, body: MessageBody) -> Message: message = self.client.execute( cmd_type="request", - method="sendMessageToChat", + method="sendMessageInChat", params={ "chatRef": { "ref": chat_ref.Ref, @@ -174,7 +175,7 @@ def send_message(self, chat_ref: ChatRef, body: MessageBody) -> Message: def delete_message(self, chat_ref: ChatRef, message_id: str) -> bool: result = self.client.execute( cmd_type="request", - method="deleteMessageFromChat", + method="deleteMessageInChat", params={ "chatRef": { "ref": chat_ref.Ref, @@ -189,7 +190,7 @@ def delete_message(self, chat_ref: ChatRef, message_id: str) -> bool: def get_message(self, chat_ref: ChatRef, message_id: str) -> Message: message = self.client.execute( cmd_type="request", - method="getMessageFromChat", + method="getMessageInChat", params={ "chatRef": { "ref": chat_ref.Ref, @@ -219,7 +220,7 @@ def list_my_joined(self, chat_type: ChatType | None = None) -> list[Chat]: chats = self.client.execute( cmd_type="request", - method="listMyJoinedChats", + method="listMyChats", params=params, ) @@ -238,7 +239,7 @@ def list_all_messages( ) -> list[Message]: messages = self.client.execute( cmd_type="request", - method="listAllMessages", + method="listMyChatMessages", params={ "startTime": start_time.isoformat(), "endTime": end_time.isoformat(), @@ -265,7 +266,7 @@ def list_all_messages( def list_pinned_messages(self, chat_ref: ChatRef) -> list[Message]: messages = self.client.execute( cmd_type="request", - method="listPinnedMessages", + method="listPinnedMessagesInChat", params={ "chatRef": { "ref": chat_ref.Ref, @@ -321,4 +322,24 @@ def unpin_message(self, chat_ref: ChatRef, message_id: str) -> bool: return result == "unpinned" - # def get_mentions(self, chat_ref: ChatRef, raw_mentions: list[str]) + def get_mentions(self, chat_ref: ChatRef, raw_mentions: list[str]) -> list[Mention]: + mentions = self.client.execute( + cmd_type="request", + method="getMentionsInChat", + params={ + "chatRef": { + "ref": chat_ref.Ref, + "type": chat_ref.type.value, + }, + "rawMentions": raw_mentions, + }, + ) + + return [ + Mention( + ID=mention["ID"], + Mentioned=mention["Mentioned"], + MentionText=mention["MentionText"], + ) + for mention in mentions + ] From 3935f7f919382e39a63731b5df369f0c8b345e9f Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Thu, 1 Jan 2026 19:51:22 +0100 Subject: [PATCH 39/99] feat: adjusted client creation in bridge to new lib version --- go/go.mod | 2 +- go/go.sum | 2 ++ go/internal/json-client/fake-client.go | 4 +-- go/internal/json-client/real-client.go | 23 +++++++------ go/internal/json-model/json-model.go | 45 +++++++++++++++----------- python/tests/test_channels.py | 3 +- 6 files changed, 48 insertions(+), 31 deletions(-) diff --git a/go/go.mod b/go/go.mod index a0e5afd..25ad986 100644 --- a/go/go.mod +++ b/go/go.mod @@ -6,7 +6,7 @@ require ( github.com/microsoft/kiota-abstractions-go v1.9.3 github.com/microsoftgraph/msgraph-sdk-go v1.92.0 github.com/mitchellh/mapstructure v1.5.0 - github.com/pzsp-teams/lib v0.0.0-20251231113700-9da62b291ebb + github.com/pzsp-teams/lib v0.0.0-20260101113908-bafa8ca37eae ) require ( diff --git a/go/go.sum b/go/go.sum index bff219a..e99e6f0 100644 --- a/go/go.sum +++ b/go/go.sum @@ -63,6 +63,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pzsp-teams/lib v0.0.0-20251231113700-9da62b291ebb h1:Bzr1vl4wA1X0g+FIKEn5vBexVJGhP1CztwTCyy7TPlo= github.com/pzsp-teams/lib v0.0.0-20251231113700-9da62b291ebb/go.mod h1:EPtQVPz+HYZNsSpcoQ3QWLbMEz1O2qeIH5ojlYJLEic= +github.com/pzsp-teams/lib v0.0.0-20260101113908-bafa8ca37eae h1:Q0NnGhGtyUbPzDHxLsBc39cHMjZBCG4yPCzU3ocNY+s= +github.com/pzsp-teams/lib v0.0.0-20260101113908-bafa8ca37eae/go.mod h1:OUTZq+jo2AAzjV5p0w7c9VVuOKiHVWxuZhdzOhjOq5s= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/std-uritemplate/std-uritemplate/go/v2 v2.0.8 h1:gMBdYMTHt2mmTdXW8YfvRjRUZ0GhyGV+IqSH9H15bGw= diff --git a/go/internal/json-client/fake-client.go b/go/internal/json-client/fake-client.go index 2d731ca..eb11a74 100644 --- a/go/internal/json-client/fake-client.go +++ b/go/internal/json-client/fake-client.go @@ -12,7 +12,7 @@ import ( azcore "github.com/microsoft/kiota-abstractions-go" graph "github.com/microsoftgraph/msgraph-sdk-go" - lib "github.com/pzsp-teams/lib" + "github.com/pzsp-teams/lib/config" jsonModel "github.com/pzsp-teams/lib-python/internal/json-model" ) @@ -54,7 +54,7 @@ func NewJSONClient(req jsonModel.Request) (*TeamsJSONClient, error) { return nil, fmt.Errorf("invalid mockServerUrl parameter") } - senderConfig := lib.SenderConfig{ + senderConfig := config.SenderConfig{ MaxRetries: 3, NextRetryDelay: 2, Timeout: 5, diff --git a/go/internal/json-client/real-client.go b/go/internal/json-client/real-client.go index a00178c..628347f 100644 --- a/go/internal/json-client/real-client.go +++ b/go/internal/json-client/real-client.go @@ -5,7 +5,8 @@ package jsonclient import ( "context" - lib "github.com/pzsp-teams/lib" + "github.com/pzsp-teams/lib" + "github.com/pzsp-teams/lib/config" jsonModel "github.com/pzsp-teams/lib-python/internal/json-model" ) @@ -20,22 +21,26 @@ func NewJSONClient(req jsonModel.Request) (*TeamsJSONClient, error) { return nil, err } - cacheEnabled, err := jsonModel.ParseCacheEnabled(req.Params["cacheEnabled"]) + cacheMode, err := jsonModel.ParseCacheMode(req.Params["cacheMode"]) if err != nil { return nil, err } - - var cachePath *string - if cacheEnabled { - path, err := jsonModel.ParseCachePath(req.Params["cachePath"]) - if err != nil { + cachePath, err := jsonModel.ParseCachePath(req.Params["cachePath"]) + if err != nil { + if cacheMode == config.CacheDisabled { + cachePath = nil + } else { return nil, err } - cachePath = path + } + cacheConfig := config.CacheConfig{ + Mode: config.CacheDisabled, + Provider: config.CacheProviderJSONFile, + Path: cachePath, } - client, err := lib.NewClient(context.TODO(), authConfig, senderConfig, cacheEnabled, cachePath) + client, err := lib.NewClient(context.TODO(), authConfig, senderConfig, &cacheConfig) if err != nil { return nil, err } diff --git a/go/internal/json-model/json-model.go b/go/internal/json-model/json-model.go index 3d76018..39abf5f 100644 --- a/go/internal/json-model/json-model.go +++ b/go/internal/json-model/json-model.go @@ -3,7 +3,7 @@ package jsonmodel import ( "fmt" - "github.com/pzsp-teams/lib" + "github.com/pzsp-teams/lib/config" ) type Request struct { @@ -26,7 +26,7 @@ type SenderConfigMap struct { Timeout int `json:"timeout"` } -func (scm SenderConfigMap) ToSenderConfig() (*lib.SenderConfig, error) { +func (scm SenderConfigMap) ToSenderConfig() (*config.SenderConfig, error) { if scm.MaxRetries < 0 { return nil, fmt.Errorf("maxRetries cannot be negative") } @@ -37,7 +37,7 @@ func (scm SenderConfigMap) ToSenderConfig() (*lib.SenderConfig, error) { return nil, fmt.Errorf("timeout must be > 0") } - return &lib.SenderConfig{ + return &config.SenderConfig{ MaxRetries: scm.MaxRetries, NextRetryDelay: scm.NextRetryDelay, Timeout: scm.Timeout, @@ -52,7 +52,7 @@ type AuthConfigMap struct { AuthMethod string `json:"authMethod"` } -func (acm AuthConfigMap) ToAuthConfig() (*lib.AuthConfig, error) { +func (acm AuthConfigMap) ToAuthConfig() (*config.AuthConfig, error) { if acm.ClientID == "" { return nil, fmt.Errorf("clientId is required") } @@ -74,27 +74,15 @@ func (acm AuthConfigMap) ToAuthConfig() (*lib.AuthConfig, error) { return nil, err } - return &lib.AuthConfig{ + return &config.AuthConfig{ ClientID: acm.ClientID, Tenant: acm.Tenant, Email: acm.Email, Scopes: acm.Scopes, - AuthMethod: authMethod, + AuthMethod: config.Method(authMethod), }, nil } -func ParseCacheEnabled(value interface{}) (bool, error) { - if value == nil { - return false, nil // default - } - - enabled, ok := value.(bool) - if !ok { - return false, fmt.Errorf("cacheEnabled must be boolean") - } - return enabled, nil -} - func ParseCachePath(value interface{}) (*string, error) { path, ok := value.(string) if !ok || path == "" { @@ -103,6 +91,27 @@ func ParseCachePath(value interface{}) (*string, error) { return &path, nil } +func ParseCacheMode(value interface{}) (config.CacheMode, error) { + switch v := value.(type) { + case string: + switch v { + case "DISABLED": + return config.CacheDisabled, nil + case "SYNC": + return config.CacheSync, nil + case "ASYNC": + return config.CacheAsync, nil + default: + return config.CacheDisabled, fmt.Errorf("invalid cacheMode: %s", v) + } + case nil: + return config.CacheDisabled, fmt.Errorf("invalid cacheMode: %s", v) + default: + return config.CacheDisabled, fmt.Errorf("cacheMode must be a string") + } +} + + func validateAuthMethod(method string) (string, error) { if method == "DEVICE_CODE" || method == "INTERACTIVE" { return method, nil diff --git a/python/tests/test_channels.py b/python/tests/test_channels.py index fe8e161..9200b27 100644 --- a/python/tests/test_channels.py +++ b/python/tests/test_channels.py @@ -129,7 +129,8 @@ def test_send_message_integration(httpserver): channelRef=data.channels[data.teams[0].ID][0].Name, body=MessageBody( Content=data.newMessageTemplate.Content, - ContentType=MessageContentType.TEXT + ContentType=MessageContentType.TEXT, + Mentions=[], ) ) From 39c9c88a334b0bdc939f22c9c0d3446ae0b8a78c Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Thu, 1 Jan 2026 19:54:15 +0100 Subject: [PATCH 40/99] chore: adjusted python client creation to new lib version --- python/teams_lib_pzsp2_z1/client.py | 6 ++++-- python/teams_lib_pzsp2_z1/config.py | 11 +++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/python/teams_lib_pzsp2_z1/client.py b/python/teams_lib_pzsp2_z1/client.py index b64c63a..b865ee2 100644 --- a/python/teams_lib_pzsp2_z1/client.py +++ b/python/teams_lib_pzsp2_z1/client.py @@ -51,7 +51,9 @@ def _binary(self): raise RuntimeError("Unsupported OS") def init_client( - self, cache_enabled: bool = False, cache_path: str | None = None + self, + cache_mode: config.CacheMode = config.CacheMode.DISABLED, + cache_path: str | None = None, ) -> Any: sender_config = config.SenderConfig() auth_config = config.load_auth_config(self.env_path) @@ -70,7 +72,7 @@ def init_client( "scopes": auth_config.scopes, "authMethod": auth_config.auth_method, }, - "cacheEnabled": cache_enabled, + "cacheMode": cache_mode.value, "cachePath": cache_path, }, ) diff --git a/python/teams_lib_pzsp2_z1/config.py b/python/teams_lib_pzsp2_z1/config.py index 286aa89..f785c0a 100644 --- a/python/teams_lib_pzsp2_z1/config.py +++ b/python/teams_lib_pzsp2_z1/config.py @@ -1,5 +1,6 @@ import os from dataclasses import dataclass +from enum import Enum from dotenv import load_dotenv @@ -24,6 +25,16 @@ class AuthConfig: auth_method: str +class CacheMode(Enum): + DISABLED = "DISABLED" + SYNC = "SYNC" + ASYNC = "ASYNC" + +@dataclass +class CacheConfig: + mode: CacheMode + path: str | None = None + def load_auth_config(env_path: str | None = None) -> AuthConfig: load_dotenv(env_path) From 2e1c804b6d1aa5d6871b777898a5b8af90778422 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Thu, 1 Jan 2026 20:24:10 +0100 Subject: [PATCH 41/99] chore: adjusted fake client creating --- go/go.mod | 2 +- go/go.sum | 6 ++++-- go/internal/json-client/fake-client.go | 10 +++++++--- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/go/go.mod b/go/go.mod index 25ad986..fee5462 100644 --- a/go/go.mod +++ b/go/go.mod @@ -6,7 +6,7 @@ require ( github.com/microsoft/kiota-abstractions-go v1.9.3 github.com/microsoftgraph/msgraph-sdk-go v1.92.0 github.com/mitchellh/mapstructure v1.5.0 - github.com/pzsp-teams/lib v0.0.0-20260101113908-bafa8ca37eae + github.com/pzsp-teams/lib v0.0.0-20260101185821-fa4110d82a33 ) require ( diff --git a/go/go.sum b/go/go.sum index e99e6f0..7327d45 100644 --- a/go/go.sum +++ b/go/go.sum @@ -61,10 +61,10 @@ github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmd github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pzsp-teams/lib v0.0.0-20251231113700-9da62b291ebb h1:Bzr1vl4wA1X0g+FIKEn5vBexVJGhP1CztwTCyy7TPlo= -github.com/pzsp-teams/lib v0.0.0-20251231113700-9da62b291ebb/go.mod h1:EPtQVPz+HYZNsSpcoQ3QWLbMEz1O2qeIH5ojlYJLEic= github.com/pzsp-teams/lib v0.0.0-20260101113908-bafa8ca37eae h1:Q0NnGhGtyUbPzDHxLsBc39cHMjZBCG4yPCzU3ocNY+s= github.com/pzsp-teams/lib v0.0.0-20260101113908-bafa8ca37eae/go.mod h1:OUTZq+jo2AAzjV5p0w7c9VVuOKiHVWxuZhdzOhjOq5s= +github.com/pzsp-teams/lib v0.0.0-20260101185821-fa4110d82a33 h1:snmBQwIaJV1MBynhm8OKzvaKtpUva4wem3VKmyaVU0Q= +github.com/pzsp-teams/lib v0.0.0-20260101185821-fa4110d82a33/go.mod h1:OUTZq+jo2AAzjV5p0w7c9VVuOKiHVWxuZhdzOhjOq5s= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/std-uritemplate/std-uritemplate/go/v2 v2.0.8 h1:gMBdYMTHt2mmTdXW8YfvRjRUZ0GhyGV+IqSH9H15bGw= @@ -83,6 +83,8 @@ go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= +go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= +go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/go/internal/json-client/fake-client.go b/go/internal/json-client/fake-client.go index eb11a74..2f131b6 100644 --- a/go/internal/json-client/fake-client.go +++ b/go/internal/json-client/fake-client.go @@ -13,6 +13,7 @@ import ( graph "github.com/microsoftgraph/msgraph-sdk-go" "github.com/pzsp-teams/lib/config" + "github.com/pzsp-teams/lib" jsonModel "github.com/pzsp-teams/lib-python/internal/json-model" ) @@ -60,8 +61,11 @@ func NewJSONClient(req jsonModel.Request) (*TeamsJSONClient, error) { Timeout: 5, } - cacheEnabled := false - var cachePath *string = nil + cacheConfig := config.CacheConfig{ + Mode: config.CacheDisabled, + Provider: config.CacheProviderJSONFile, + Path: nil, + } hijackedHttpClient := &http.Client{ Transport: &HijackTransport{ @@ -85,7 +89,7 @@ func NewJSONClient(req jsonModel.Request) (*TeamsJSONClient, error) { graphClient := graph.NewGraphServiceClient(adapter) - client, err := lib.NewClientFromGraphClient(graphClient, &senderConfig, cacheEnabled, cachePath) + client, err := lib.NewClientFromGraphClient(graphClient, &senderConfig, &cacheConfig) if err != nil { return nil, err } From a15217883d4a88455215fce338068ab39287ca07 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Thu, 1 Jan 2026 20:40:14 +0100 Subject: [PATCH 42/99] fix: real client init --- go/internal/json-client/real-client.go | 4 +-- go/internal/json-model/json-model.go | 36 ++++++++++---------------- python/teams_lib_pzsp2_z1/client.py | 4 +-- 3 files changed, 18 insertions(+), 26 deletions(-) diff --git a/go/internal/json-client/real-client.go b/go/internal/json-client/real-client.go index 628347f..1711f7a 100644 --- a/go/internal/json-client/real-client.go +++ b/go/internal/json-client/real-client.go @@ -21,11 +21,11 @@ func NewJSONClient(req jsonModel.Request) (*TeamsJSONClient, error) { return nil, err } - cacheMode, err := jsonModel.ParseCacheMode(req.Params["cacheMode"]) + cacheMode, err := jsonModel.ParseCacheMode(req.Config.CacheMode) if err != nil { return nil, err } - cachePath, err := jsonModel.ParseCachePath(req.Params["cachePath"]) + cachePath, err := jsonModel.ParseCachePath(req.Config.CachePath) if err != nil { if cacheMode == config.CacheDisabled { cachePath = nil diff --git a/go/internal/json-model/json-model.go b/go/internal/json-model/json-model.go index 39abf5f..750604a 100644 --- a/go/internal/json-model/json-model.go +++ b/go/internal/json-model/json-model.go @@ -16,7 +16,7 @@ type Request struct { type Config struct { SenderConfigMap SenderConfigMap `json:"senderConfig"` AuthConfigMap AuthConfigMap `json:"authConfig"` - CacheEnabled bool `json:"cacheEnabled"` + CacheMode string `json:"cacheMode,omitempty"` CachePath string `json:"cachePath,omitempty"` } @@ -84,34 +84,26 @@ func (acm AuthConfigMap) ToAuthConfig() (*config.AuthConfig, error) { } func ParseCachePath(value interface{}) (*string, error) { - path, ok := value.(string) - if !ok || path == "" { - return nil, fmt.Errorf("cachePath must be a non-empty string") - } - return &path, nil + path, ok := value.(string) + if !ok || path == "" { + return nil, fmt.Errorf("cachePath must be a non-empty string") + } + return &path, nil } func ParseCacheMode(value interface{}) (config.CacheMode, error) { - switch v := value.(type) { - case string: - switch v { - case "DISABLED": - return config.CacheDisabled, nil - case "SYNC": - return config.CacheSync, nil - case "ASYNC": - return config.CacheAsync, nil - default: - return config.CacheDisabled, fmt.Errorf("invalid cacheMode: %s", v) - } - case nil: - return config.CacheDisabled, fmt.Errorf("invalid cacheMode: %s", v) + switch value { + case "DISABLED": + return config.CacheDisabled, nil + case "SYNC": + return config.CacheSync, nil + case "ASYNC": + return config.CacheAsync, nil default: - return config.CacheDisabled, fmt.Errorf("cacheMode must be a string") + return config.CacheDisabled, fmt.Errorf("invalid cacheMode: %s", value) } } - func validateAuthMethod(method string) (string, error) { if method == "DEVICE_CODE" || method == "INTERACTIVE" { return method, nil diff --git a/python/teams_lib_pzsp2_z1/client.py b/python/teams_lib_pzsp2_z1/client.py index b865ee2..dc321dd 100644 --- a/python/teams_lib_pzsp2_z1/client.py +++ b/python/teams_lib_pzsp2_z1/client.py @@ -17,7 +17,7 @@ def __init__( self, auto_init: bool = True, env_path: str | None = None, - cache_enabled: bool = False, + cache_mode: config.CacheMode = config.CacheMode.DISABLED, cache_path: str | None = None, ): self._lock = threading.Lock() @@ -37,7 +37,7 @@ def __init__( self.chats = ChatsService(self) if auto_init: - self.init_client(cache_enabled, cache_path) + self.init_client(cache_mode, cache_path) def _binary(self): base = pathlib.Path(__file__).parent / "bin" From f7f57599ab1d2cedfb71b41cbae3a5d8c078f303 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Fri, 2 Jan 2026 12:28:17 +0100 Subject: [PATCH 43/99] feat: implemented test for list chats --- python/teams_lib_pzsp2_z1/client.py | 2 ++ python/tests/fake_server/data.py | 48 +++++++++++++++++++++++++ python/tests/fake_server/setup.py | 28 +++++++++++++++ python/tests/test_channels.py | 4 +-- python/tests/test_chats.py | 56 +++++++++++++++++++++++++++++ 5 files changed, 136 insertions(+), 2 deletions(-) create mode 100644 python/tests/test_chats.py diff --git a/python/teams_lib_pzsp2_z1/client.py b/python/teams_lib_pzsp2_z1/client.py index dc321dd..bf00843 100644 --- a/python/teams_lib_pzsp2_z1/client.py +++ b/python/teams_lib_pzsp2_z1/client.py @@ -111,6 +111,8 @@ def execute( print("Sent to Go process:", json_payload) # Debug print raw_response = self.proc.stdout.readline() + + print("Received from Go process:", raw_response.strip()) # Debug print except BrokenPipeError: raise RuntimeError("Go process crashed or closed connection") # noqa: B904 diff --git a/python/tests/fake_server/data.py b/python/tests/fake_server/data.py index bcc22a2..95eb59c 100644 --- a/python/tests/fake_server/data.py +++ b/python/tests/fake_server/data.py @@ -1,5 +1,6 @@ from dataclasses import dataclass from teams_lib_pzsp2_z1.model.team import Team +from teams_lib_pzsp2_z1.model.chat import Chat, ChatType from teams_lib_pzsp2_z1.model.channel import Channel from teams_lib_pzsp2_z1.model.message import Message, MessageFrom from teams_lib_pzsp2_z1.model.member import Member @@ -126,6 +127,28 @@ def __init__(self) -> FakeServerData: ], } } + self.group_chats = [ + Chat( + ID="chat-123-abc", + Type=ChatType.GROUP, + IsHidden=False, + Topic="Project Discussion", + ), + Chat( + ID="chat-456-def", + Type=ChatType.GROUP, + IsHidden=True, + Topic="Secret Plans", + ), + ] + self.oneonone_chats = [ + Chat( + ID="chat-789-ghi", + Type=ChatType.ONEONONE, + IsHidden=False, + Topic="", + ), + ] self.newGroupID = "group-789-ghi" self.newTeamName = "New Team" self.newGroupMailNickname = "new-team-nickname" @@ -513,3 +536,28 @@ def get_remove_member_response(self, team_id: str, channel_id: str, member_id: s channel_members.remove(member) return {"success": True} + + def get_list_chats_response(self, chat_type: ChatType) -> dict: + if chat_type == ChatType.ONEONONE: + chats = self.oneonone_chats + else: + chats = self.group_chats + + print(chat_type) + print(chat_type == ChatType.ONEONONE) + + return { + "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#chats", + "@odata.count": len(chats), + "value": [ + { + "id": chat.ID, + "chatType": "oneOnOne" if chat.Type == ChatType.ONEONONE else "group", + "isHiddenForAllMembers": chat.IsHidden, + "topic": chat.Topic, + } + for chat in chats + ], + } + + diff --git a/python/tests/fake_server/setup.py b/python/tests/fake_server/setup.py index 93e1bb1..a979b9f 100644 --- a/python/tests/fake_server/setup.py +++ b/python/tests/fake_server/setup.py @@ -3,6 +3,7 @@ import re import sys from werkzeug.wrappers import Response +from teams_lib_pzsp2_z1.model.chat import ChatType def setup_fake_server(httpserver) -> FakeServerData: """ @@ -44,6 +45,33 @@ def make_log_response(request, data_dict: dict, context_msg: str = "") -> Respon req, data.get_myJoinedTeams_response(), "List Joined Teams" )) + def handle_chats_request(req): + filter_param = req.args.get('$filter', '') + chat_type = None + match = re.search(r"chatType\s+eq\s+'([^']+)'", filter_param) + + if match: + chat_type = match.group(1) + + print(f"DEBUG: Parsowanie filtra: '{filter_param}' -> Typ: '{chat_type}'") + + if chat_type == "oneOnOne": + chat_type = ChatType.ONEONONE + elif chat_type == "group": + chat_type = ChatType.GROUP + + return make_log_response( + req, + data.get_list_chats_response(chat_type), + f"List Chats ({chat_type})" + ) + + # GET /users/me/chats + httpserver.expect_request( + "/v1.0/users/me-token-to-replace/chats", + method="GET" + ).respond_with_handler(handle_chats_request) + #POST /teams/{team_id}/channels/{channel_id}/members httpserver.expect_request( re.compile(r"^/v1.0/teams/([^/]+)/channels/([^/]+)/members"), diff --git a/python/tests/test_channels.py b/python/tests/test_channels.py index 9200b27..e0695d0 100644 --- a/python/tests/test_channels.py +++ b/python/tests/test_channels.py @@ -339,7 +339,7 @@ def test_update_member_role_integration(httpserver): member = client.channels.update_member_role( teamRef=data.teams[0].DisplayName, channelRef=data.channels[data.teams[0].ID][0].Name, - userRef=data.members[data.teams[0].ID][data.channels[data.teams[0].ID][0].ID][1].DisplayName, + userRef=data.members[data.teams[0].ID][data.channels[data.teams[0].ID][0].ID][1].Email, isOwner=True ) @@ -363,7 +363,7 @@ def test_remove_member_integration(httpserver): success = client.channels.remove_member( teamRef=data.teams[0].DisplayName, channelRef=data.channels[data.teams[0].ID][0].Name, - userRef=data.members[data.teams[0].ID][data.channels[data.teams[0].ID][0].ID][1].DisplayName, + userRef=data.members[data.teams[0].ID][data.channels[data.teams[0].ID][0].ID][1].Email, ) assert success is True diff --git a/python/tests/test_chats.py b/python/tests/test_chats.py new file mode 100644 index 0000000..5393f7e --- /dev/null +++ b/python/tests/test_chats.py @@ -0,0 +1,56 @@ +from teams_lib_pzsp2_z1.client import TeamsClient +from tests.init_fake_client import init_fake_client +from tests.fake_server.setup import setup_fake_server +from teams_lib_pzsp2_z1.model.chat import ChatType + + +def test_list_my_group_chats_integration(httpserver): + """ + Integration test: Python -> Go Binary -> Fake HTTP -> Python Mock Server + """ + + data = setup_fake_server(httpserver) + + # Init fake client + client = TeamsClient(auto_init=False) + try: + init_fake_client(client, httpserver.url_for("")) + + chats = client.chats.list_my_joined(ChatType.GROUP) + + assert len(chats) == len(data.group_chats) + assert chats[0].ID == data.group_chats[0].ID + assert chats[0].Type == data.group_chats[0].Type + assert chats[0].Topic == data.group_chats[0].Topic + assert chats[0].IsHidden == data.group_chats[0].IsHidden + + assert chats[1].ID == data.group_chats[1].ID + assert chats[1].Type == data.group_chats[1].Type + assert chats[1].Topic == data.group_chats[1].Topic + assert chats[1].IsHidden == data.group_chats[1].IsHidden + + finally: + client.close() + +def test_list_my_one_on_one_chats_integration(httpserver): + """ + Integration test: Python -> Go Binary -> Fake HTTP -> Python Mock Server + """ + + data = setup_fake_server(httpserver) + + # Init fake client + client = TeamsClient(auto_init=False) + try: + init_fake_client(client, httpserver.url_for("")) + + chats = client.chats.list_my_joined(ChatType.ONEONONE) + + assert len(chats) == len(data.oneonone_chats) + assert chats[0].ID == data.oneonone_chats[0].ID + assert chats[0].Type == data.oneonone_chats[0].Type + assert chats[0].Topic == data.oneonone_chats[0].Topic + assert chats[0].IsHidden == data.oneonone_chats[0].IsHidden + + finally: + client.close() \ No newline at end of file From 33da7572d84470b8d828a37ebdf598aba7869c25 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Fri, 2 Jan 2026 12:41:29 +0100 Subject: [PATCH 44/99] feat: implemented test for create group chat endpoint --- python/tests/fake_server/data.py | 54 +++++++++++++++++++++++++++++++ python/tests/fake_server/setup.py | 20 ++++++++++++ python/tests/test_chats.py | 30 ++++++++++++++--- 3 files changed, 100 insertions(+), 4 deletions(-) diff --git a/python/tests/fake_server/data.py b/python/tests/fake_server/data.py index 95eb59c..dcff1d1 100644 --- a/python/tests/fake_server/data.py +++ b/python/tests/fake_server/data.py @@ -127,6 +127,29 @@ def __init__(self) -> FakeServerData: ], } } + self.users = [ + Member( + ID="user-123-abc", + UserID="user-123-abc", + DisplayName="Alice", + Role="owner", + Email="alice@example.com" + ), + Member( + ID="user-456-def", + UserID="user-456-def", + DisplayName="Bob", + Role="member", + Email="bob@example.com" + ), + ] + self.me = Member( + ID="user-me-001", + UserID="user-me-001", + DisplayName="Current User", + Role="member", + Email="me@example.com" + ) self.group_chats = [ Chat( ID="chat-123-abc", @@ -174,6 +197,12 @@ def __init__(self) -> FakeServerData: Role="owner", Email="newmember@example.com" ) + self.newChatTemplate = Chat( + ID="new-chat-001", + Type=ChatType.GROUP, + IsHidden=False, + Topic="New Chat Topic" + ) @@ -560,4 +589,29 @@ def get_list_chats_response(self, chat_type: ChatType) -> dict: ], } + def get_me_response(self) -> dict: + return { + "id": self.me.ID, + "displayName": self.me.DisplayName, + "email": self.me.Email, + } + + def get_create_chat_response(self, request_json: dict) -> dict: + if request_json["chatType"] == "oneOnOne": + return { + "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#chats/$entity", + "id": self.newChatTemplate.ID, + "chatType": "oneOnOne", + "isHiddenForAllMembers": self.newChatTemplate.IsHidden, + "topic": None, + } + else: + return { + "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#chats/$entity", + "id": self.newChatTemplate.ID, + "chatType": "group", + "isHiddenForAllMembers": self.newChatTemplate.IsHidden, + "topic": self.newChatTemplate.Topic, + } + diff --git a/python/tests/fake_server/setup.py b/python/tests/fake_server/setup.py index a979b9f..00a6f35 100644 --- a/python/tests/fake_server/setup.py +++ b/python/tests/fake_server/setup.py @@ -72,6 +72,26 @@ def handle_chats_request(req): method="GET" ).respond_with_handler(handle_chats_request) + # GET /users/me + httpserver.expect_request( + "/v1.0/users/me-token-to-replace", + method="GET" + ).respond_with_handler(lambda req: make_log_response( + req, + data.get_me_response(), + "Get Users" + )) + + #POST /chats + httpserver.expect_request( + "/v1.0/chats", + method="POST" + ).respond_with_handler(lambda req: make_log_response( + req, + data.get_create_chat_response(req.json), + "Create Chat" + )) + #POST /teams/{team_id}/channels/{channel_id}/members httpserver.expect_request( re.compile(r"^/v1.0/teams/([^/]+)/channels/([^/]+)/members"), diff --git a/python/tests/test_chats.py b/python/tests/test_chats.py index 5393f7e..0f16307 100644 --- a/python/tests/test_chats.py +++ b/python/tests/test_chats.py @@ -33,13 +33,9 @@ def test_list_my_group_chats_integration(httpserver): client.close() def test_list_my_one_on_one_chats_integration(httpserver): - """ - Integration test: Python -> Go Binary -> Fake HTTP -> Python Mock Server - """ data = setup_fake_server(httpserver) - # Init fake client client = TeamsClient(auto_init=False) try: init_fake_client(client, httpserver.url_for("")) @@ -52,5 +48,31 @@ def test_list_my_one_on_one_chats_integration(httpserver): assert chats[0].Topic == data.oneonone_chats[0].Topic assert chats[0].IsHidden == data.oneonone_chats[0].IsHidden + finally: + client.close() + + +def test_create_group_chat_integration(httpserver): + + data = setup_fake_server(httpserver) + + client = TeamsClient(auto_init=False) + try: + init_fake_client(client, httpserver.url_for("")) + + chat = client.chats.create_group_chat( + topic=data.newChatTemplate.Topic, + recipient_refs=[ + data.users[0].Email, + data.users[1].Email, + ], + include_me=True, + ) + + assert chat.ID == data.newChatTemplate.ID + assert chat.Type == ChatType.GROUP + assert chat.Topic == data.newChatTemplate.Topic + assert chat.IsHidden == data.newChatTemplate.IsHidden + finally: client.close() \ No newline at end of file From c36f6195a77c3c540c09039626ff472bd83ab6b9 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Fri, 2 Jan 2026 13:13:04 +0100 Subject: [PATCH 45/99] feat: implemented test for list_members in chat --- python/teams_lib_pzsp2_z1/services/chats.py | 4 +- python/tests/fake_server/data.py | 50 +++++++++++++++++++++ python/tests/fake_server/setup.py | 12 +++++ python/tests/test_chats.py | 50 +++++++++++++++++++++ 4 files changed, 114 insertions(+), 2 deletions(-) diff --git a/python/teams_lib_pzsp2_z1/services/chats.py b/python/teams_lib_pzsp2_z1/services/chats.py index 5838203..ad59b1b 100644 --- a/python/teams_lib_pzsp2_z1/services/chats.py +++ b/python/teams_lib_pzsp2_z1/services/chats.py @@ -94,8 +94,8 @@ def list_group_chat_members(self, group_chat_ref: str) -> list[Member]: Member( ID=member["ID"], DisplayName=member["DisplayName"], - UserRef=member["UserRef"], - Roles=member["Roles"], + UserID=member["UserID"], + Role=member["Role"], Email=member["Email"], ) for member in members diff --git a/python/tests/fake_server/data.py b/python/tests/fake_server/data.py index dcff1d1..fbe78e9 100644 --- a/python/tests/fake_server/data.py +++ b/python/tests/fake_server/data.py @@ -127,6 +127,24 @@ def __init__(self) -> FakeServerData: ], } } + self.group_chat_members = { + "chat-123-abc": [ + Member( + ID="user-123-abc", + UserID="user-123-abc", + DisplayName="Alice", + Role="owner", + Email="alice@example.com" + ), + Member( + ID="user-456-def", + UserID="user-456-def", + DisplayName="Bob", + Role="member", + Email="bob@example.com" + ), + ], + } self.users = [ Member( ID="user-123-abc", @@ -615,3 +633,35 @@ def get_create_chat_response(self, request_json: dict) -> dict: } + def get_add_member_to_group_chat_response(self, chat_id: str, request_json: dict) -> dict: + return { + "@odata.context": f"https://graph.microsoft.com/v1.0/$metadata#chats('{chat_id}')/members/$entity", + "@odata.type": "#microsoft.graph.aadUserConversationMember", + "id": self.newMemberTemplate.ID, + "roles": [self.newMemberTemplate.Role] if self.newMemberTemplate.Role == "owner" else [], + "displayName": self.newMemberTemplate.DisplayName, + "userId": self.newMemberTemplate.UserID, + "email": self.newMemberTemplate.Email, + } + + def get_remove_member_from_group_chat_response(self, chat_id: str, member_id: str) -> dict: + return { + "success": True + } + + def get_list_group_chat_members_response(self, chat_id: str) -> dict: + return { + "@odata.context": f"https://graph.microsoft.com/v1.0/$metadata#chats('{chat_id}')/members", + "value": [ + { + "@odata.type": "#microsoft.graph.aadUserConversationMember", + "id": member.ID, + "userId": member.UserID, + "displayName": member.DisplayName, + "roles": [member.Role] if member.Role == "owner" else [], + "email": member.Email, + } + for member in self.group_chat_members.get(chat_id, []) + ], + } + diff --git a/python/tests/fake_server/setup.py b/python/tests/fake_server/setup.py index 00a6f35..0d6c455 100644 --- a/python/tests/fake_server/setup.py +++ b/python/tests/fake_server/setup.py @@ -92,6 +92,18 @@ def handle_chats_request(req): "Create Chat" )) + #GET /chats/{chat_id}/members + httpserver.expect_request( + re.compile(r"^/v1.0/chats/([^/]+)/members"), + method="GET" + ).respond_with_handler(lambda req: make_log_response( + req, + data.get_list_group_chat_members_response( + re.search(r"/chats/([^/]+)/members", req.path).group(1) + ), + "List Chat Members" + )) + #POST /teams/{team_id}/channels/{channel_id}/members httpserver.expect_request( re.compile(r"^/v1.0/teams/([^/]+)/channels/([^/]+)/members"), diff --git a/python/tests/test_chats.py b/python/tests/test_chats.py index 0f16307..4549b4d 100644 --- a/python/tests/test_chats.py +++ b/python/tests/test_chats.py @@ -74,5 +74,55 @@ def test_create_group_chat_integration(httpserver): assert chat.Topic == data.newChatTemplate.Topic assert chat.IsHidden == data.newChatTemplate.IsHidden + finally: + client.close() + +def test_create_one_on_one_chat_integration(httpserver): + + data = setup_fake_server(httpserver) + + client = TeamsClient(auto_init=False) + try: + init_fake_client(client, httpserver.url_for("")) + + chat = client.chats.create_one_on_one( + recipient_ref=data.users[0].Email, + ) + + assert chat.ID == data.newChatTemplate.ID + assert chat.Type == ChatType.ONEONONE + assert chat.Topic == None + assert chat.IsHidden == data.newChatTemplate.IsHidden + + finally: + client.close() + + +def test_list_group_chat_members_integration(httpserver): + + data = setup_fake_server(httpserver) + + client = TeamsClient(auto_init=False) + try: + init_fake_client(client, httpserver.url_for("")) + + members = client.chats.list_group_chat_members(data.group_chats[0].Topic) + + assert len(members) == len(data.group_chat_members[data.group_chats[0].ID]) + + assert members[0].ID == data.group_chat_members[data.group_chats[0].ID][0].ID + assert members[0].UserID == data.group_chat_members[data.group_chats[0].ID][0].UserID + assert members[0].DisplayName == data.group_chat_members[data.group_chats[0].ID][0].DisplayName + if data.group_chat_members[data.group_chats[0].ID][0].Role == "owner": + assert members[0].Role == "owner" + assert members[0].Email == data.group_chat_members[data.group_chats[0].ID][0].Email + + assert members[1].ID == data.group_chat_members[data.group_chats[0].ID][1].ID + assert members[1].UserID == data.group_chat_members[data.group_chats[0].ID][1].UserID + assert members[1].DisplayName == data.group_chat_members[data.group_chats[0].ID][1].DisplayName + if data.group_chat_members[data.group_chats[0].ID][1].Role == "owner": + assert members[1].Role == "owner" + assert members[1].Email == data.group_chat_members[data.group_chats[0].ID][1].Email + finally: client.close() \ No newline at end of file From b8549ad21f7186f2ea1bd469408e1af5b7dea210 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Fri, 2 Jan 2026 13:20:49 +0100 Subject: [PATCH 46/99] feat: implemented membership management tests --- python/teams_lib_pzsp2_z1/services/chats.py | 14 ++--- python/tests/fake_server/data.py | 15 +++++ python/tests/fake_server/setup.py | 39 +++++++++++++ python/tests/test_chats.py | 63 +++++++++++++++++++++ 4 files changed, 124 insertions(+), 7 deletions(-) diff --git a/python/teams_lib_pzsp2_z1/services/chats.py b/python/teams_lib_pzsp2_z1/services/chats.py index ad59b1b..ab6bef9 100644 --- a/python/teams_lib_pzsp2_z1/services/chats.py +++ b/python/teams_lib_pzsp2_z1/services/chats.py @@ -62,8 +62,8 @@ def add_member_to_group_chat(self, group_chat_ref: str, user_ref: str) -> Member return Member( ID=member["ID"], DisplayName=member["DisplayName"], - UserRef=member["UserRef"], - Roles=member["Roles"], + UserID=member["UserID"], + Role=member["Role"], Email=member["Email"], ) @@ -135,7 +135,7 @@ def list_messages(self, chat_ref: str) -> list[Message]: ContentType=MessageContentType(message["Body"]["ContentType"]), ), From=MessageFrom( - UserRef=message["From"]["UserRef"], + UserID=message["From"]["UserID"], DisplayName=message["From"]["DisplayName"], ), CreatedDateTime=message["CreatedDateTime"], @@ -166,7 +166,7 @@ def send_message(self, chat_ref: ChatRef, body: MessageBody) -> Message: ContentType=MessageContentType(message["Body"]["ContentType"]), ), From=MessageFrom( - UserRef=message["From"]["UserRef"], + UserID=message["From"]["UserID"], DisplayName=message["From"]["DisplayName"], ), CreatedDateTime=message["CreatedDateTime"], @@ -207,7 +207,7 @@ def get_message(self, chat_ref: ChatRef, message_id: str) -> Message: ContentType=MessageContentType(message["Body"]["ContentType"]), ), From=MessageFrom( - UserRef=message["From"]["UserRef"], + UserID=message["From"]["UserID"], DisplayName=message["From"]["DisplayName"], ), CreatedDateTime=message["CreatedDateTime"], @@ -255,7 +255,7 @@ def list_all_messages( ContentType=MessageContentType(message["Body"]["ContentType"]), ), From=MessageFrom( - UserRef=message["From"]["UserRef"], + UserID=message["From"]["UserID"], DisplayName=message["From"]["DisplayName"], ), CreatedDateTime=message["CreatedDateTime"], @@ -283,7 +283,7 @@ def list_pinned_messages(self, chat_ref: ChatRef) -> list[Message]: ContentType=MessageContentType(message["Body"]["ContentType"]), ), From=MessageFrom( - UserRef=message["From"]["UserRef"], + UserID=message["From"]["UserID"], DisplayName=message["From"]["DisplayName"], ), CreatedDateTime=message["CreatedDateTime"], diff --git a/python/tests/fake_server/data.py b/python/tests/fake_server/data.py index fbe78e9..e2dc2a2 100644 --- a/python/tests/fake_server/data.py +++ b/python/tests/fake_server/data.py @@ -221,6 +221,7 @@ def __init__(self) -> FakeServerData: IsHidden=False, Topic="New Chat Topic" ) + self.updatedGroupChatTopic = "Updated Chat Topic" @@ -665,3 +666,17 @@ def get_list_group_chat_members_response(self, chat_id: str) -> dict: ], } + def get_update_group_chat_topic_response(self, chat_id: str, request_json: dict) -> dict: + chat = next((c for c in self.group_chats if c.ID == chat_id), None) + if not chat: + return {} + + chat.Topic = request_json.get("topic", chat.Topic) + + return { + "@odata.context": f"https://graph.microsoft.com/v1.0/$metadata#chats('{chat_id}')", + "id": chat.ID, + "chatType": "group", + "isHiddenForAllMembers": chat.IsHidden, + "topic": chat.Topic, + } diff --git a/python/tests/fake_server/setup.py b/python/tests/fake_server/setup.py index 0d6c455..b386812 100644 --- a/python/tests/fake_server/setup.py +++ b/python/tests/fake_server/setup.py @@ -104,6 +104,45 @@ def handle_chats_request(req): "List Chat Members" )) + #POST /chats/{chat_id}/members + httpserver.expect_request( + re.compile(r"^/v1.0/chats/([^/]+)/members"), + method="POST" + ).respond_with_handler(lambda req: make_log_response( + req, + data.get_add_member_to_group_chat_response( + re.search(r"/chats/([^/]+)/members", req.path).group(1), + req.json + ), + "Add Chat Member" + )) + + #DELETE /chats/{chat_id}/members/{member_id} + httpserver.expect_request( + re.compile(r"^/v1.0/chats/([^/]+)/members/([^/]+)"), + method="DELETE" + ).respond_with_handler(lambda req: make_log_response( + req, + data.get_remove_member_from_group_chat_response( + re.search(r"/chats/([^/]+)/members/([^/]+)", req.path).group(1), + re.search(r"/chats/([^/]+)/members/([^/]+)", req.path).group(2) + ), + "Remove Chat Member" + )) + + #PATCH /chats/{chat_id} + httpserver.expect_request( + re.compile(r"^/v1.0/chats/([^/]+)"), + method="PATCH" + ).respond_with_handler(lambda req: make_log_response( + req, + data.get_update_group_chat_topic_response( + re.search(r"/chats/([^/]+)", req.path).group(1), + req.json + ), + "Update Chat" + )) + #POST /teams/{team_id}/channels/{channel_id}/members httpserver.expect_request( re.compile(r"^/v1.0/teams/([^/]+)/channels/([^/]+)/members"), diff --git a/python/tests/test_chats.py b/python/tests/test_chats.py index 4549b4d..d688f6d 100644 --- a/python/tests/test_chats.py +++ b/python/tests/test_chats.py @@ -124,5 +124,68 @@ def test_list_group_chat_members_integration(httpserver): assert members[1].Role == "owner" assert members[1].Email == data.group_chat_members[data.group_chats[0].ID][1].Email + finally: + client.close() + + +def test_add_group_chat_member_integration(httpserver): + + data = setup_fake_server(httpserver) + + client = TeamsClient(auto_init=False) + try: + init_fake_client(client, httpserver.url_for("")) + + member = client.chats.add_member_to_group_chat( + group_chat_ref=data.group_chats[0].Topic, + user_ref=data.newMemberTemplate.Email, + ) + + assert member.ID == data.newMemberTemplate.ID + assert member.UserID == data.newMemberTemplate.UserID + assert member.DisplayName == data.newMemberTemplate.DisplayName + assert member.Role == data.newMemberTemplate.Role + assert member.Email == data.newMemberTemplate.Email + finally: + client.close() + + +def test_remove_group_chat_member_integration(httpserver): + + data = setup_fake_server(httpserver) + + client = TeamsClient(auto_init=False) + try: + init_fake_client(client, httpserver.url_for("")) + + result = client.chats.remove_member_from_group_chat( + group_chat_ref=data.group_chats[0].Topic, + member_ref=data.group_chat_members[data.group_chats[0].ID][0].Email, + ) + + assert result is True + + finally: + client.close() + + +def test_update_group_chat_topic_integration(httpserver): + + data = setup_fake_server(httpserver) + + client = TeamsClient(auto_init=False) + try: + init_fake_client(client, httpserver.url_for("")) + + chat = client.chats.update_group_chat_topic( + group_chat_ref=data.group_chats[0].Topic, + new_topic=data.updatedGroupChatTopic, + ) + + assert chat.ID == data.group_chats[0].ID + assert chat.Type == data.group_chats[0].Type + assert chat.Topic == data.updatedGroupChatTopic + assert chat.IsHidden == data.group_chats[0].IsHidden + finally: client.close() \ No newline at end of file From 8e5491e61f12ed7ef64e4e7469e570fd71669c16 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Fri, 2 Jan 2026 14:57:06 +0100 Subject: [PATCH 47/99] feat: added list chat messages test --- python/teams_lib_pzsp2_z1/model/chat.py | 3 +- python/teams_lib_pzsp2_z1/services/chats.py | 57 ++++++++-------- python/tests/fake_server/data.py | 74 +++++++++++++++++++++ python/tests/fake_server/setup.py | 38 +++++++++++ python/tests/test_chats.py | 40 ++++++++++- 5 files changed, 180 insertions(+), 32 deletions(-) diff --git a/python/teams_lib_pzsp2_z1/model/chat.py b/python/teams_lib_pzsp2_z1/model/chat.py index 206bc83..63be741 100644 --- a/python/teams_lib_pzsp2_z1/model/chat.py +++ b/python/teams_lib_pzsp2_z1/model/chat.py @@ -14,7 +14,8 @@ class Chat: IsHidden: bool Topic: str + @dataclass class ChatRef: Ref: str - type: ChatType + Type: ChatType diff --git a/python/teams_lib_pzsp2_z1/services/chats.py b/python/teams_lib_pzsp2_z1/services/chats.py index ab6bef9..2e705b3 100644 --- a/python/teams_lib_pzsp2_z1/services/chats.py +++ b/python/teams_lib_pzsp2_z1/services/chats.py @@ -118,27 +118,29 @@ def update_group_chat_topic(self, group_chat_ref: str, new_topic: str) -> Chat: Topic=response["Topic"], ) - def list_messages(self, chat_ref: str) -> list[Message]: + def list_messages(self, chat_ref: ChatRef) -> list[Message]: messages = self.client.execute( cmd_type="request", method="listMessagesInChat", params={ - "chatRef": chat_ref, + "chatRef": { + "ref": chat_ref.Ref, + "type": chat_ref.Type.value, + }, }, ) return [ Message( ID=message["ID"], - Body=MessageBody( - Content=message["Body"]["Content"], - ContentType=MessageContentType(message["Body"]["ContentType"]), - ), + Content=message["Content"], + ContentType=MessageContentType(message["ContentType"]), From=MessageFrom( UserID=message["From"]["UserID"], DisplayName=message["From"]["DisplayName"], ), CreatedDateTime=message["CreatedDateTime"], + ReplyCount=message["ReplyCount"], ) for message in messages ] @@ -150,7 +152,7 @@ def send_message(self, chat_ref: ChatRef, body: MessageBody) -> Message: params={ "chatRef": { "ref": chat_ref.Ref, - "type": chat_ref.type.value, + "type": chat_ref.Type.value, }, "body": { "content": body.Content, @@ -161,15 +163,14 @@ def send_message(self, chat_ref: ChatRef, body: MessageBody) -> Message: return Message( ID=message["ID"], - Body=MessageBody( - Content=message["Body"]["Content"], - ContentType=MessageContentType(message["Body"]["ContentType"]), - ), + Content=message["Content"], + ContentType=MessageContentType(message["ContentType"]), From=MessageFrom( UserID=message["From"]["UserID"], DisplayName=message["From"]["DisplayName"], ), CreatedDateTime=message["CreatedDateTime"], + ReplyCount=message["ReplyCount"], ) def delete_message(self, chat_ref: ChatRef, message_id: str) -> bool: @@ -179,7 +180,7 @@ def delete_message(self, chat_ref: ChatRef, message_id: str) -> bool: params={ "chatRef": { "ref": chat_ref.Ref, - "type": chat_ref.type.value, + "type": chat_ref.Type.value, }, "messageID": message_id, }, @@ -194,7 +195,7 @@ def get_message(self, chat_ref: ChatRef, message_id: str) -> Message: params={ "chatRef": { "ref": chat_ref.Ref, - "type": chat_ref.type.value, + "type": chat_ref.Type.value, }, "messageID": message_id, }, @@ -202,15 +203,14 @@ def get_message(self, chat_ref: ChatRef, message_id: str) -> Message: return Message( ID=message["ID"], - Body=MessageBody( - Content=message["Body"]["Content"], - ContentType=MessageContentType(message["Body"]["ContentType"]), - ), + Content=message["Content"], + ContentType=MessageContentType(message["ContentType"]), From=MessageFrom( UserID=message["From"]["UserID"], DisplayName=message["From"]["DisplayName"], ), CreatedDateTime=message["CreatedDateTime"], + ReplyCount=message["ReplyCount"], ) def list_my_joined(self, chat_type: ChatType | None = None) -> list[Chat]: @@ -250,15 +250,14 @@ def list_all_messages( return [ Message( ID=message["ID"], - Body=MessageBody( - Content=message["Body"]["Content"], - ContentType=MessageContentType(message["Body"]["ContentType"]), - ), + Content=message["Content"], + ContentType=MessageContentType(message["ContentType"]), From=MessageFrom( UserID=message["From"]["UserID"], DisplayName=message["From"]["DisplayName"], ), CreatedDateTime=message["CreatedDateTime"], + ReplyCount=message["ReplyCount"], ) for message in messages ] @@ -270,7 +269,7 @@ def list_pinned_messages(self, chat_ref: ChatRef) -> list[Message]: params={ "chatRef": { "ref": chat_ref.Ref, - "type": chat_ref.type.value, + "type": chat_ref.Type.value, }, }, ) @@ -278,15 +277,14 @@ def list_pinned_messages(self, chat_ref: ChatRef) -> list[Message]: return [ Message( ID=message["ID"], - Body=MessageBody( - Content=message["Body"]["Content"], - ContentType=MessageContentType(message["Body"]["ContentType"]), - ), + Content=message["Content"], + ContentType=MessageContentType(message["ContentType"]), From=MessageFrom( UserID=message["From"]["UserID"], DisplayName=message["From"]["DisplayName"], ), CreatedDateTime=message["CreatedDateTime"], + ReplyCount=message["ReplyCount"], ) for message in messages ] @@ -298,7 +296,7 @@ def pin_message(self, chat_ref: ChatRef, message_id: str) -> bool: params={ "chatRef": { "ref": chat_ref.Ref, - "type": chat_ref.type.value, + "type": chat_ref.Type.value, }, "messageID": message_id, }, @@ -306,7 +304,6 @@ def pin_message(self, chat_ref: ChatRef, message_id: str) -> bool: return result == "pinned" - def unpin_message(self, chat_ref: ChatRef, message_id: str) -> bool: result = self.client.execute( cmd_type="request", @@ -314,7 +311,7 @@ def unpin_message(self, chat_ref: ChatRef, message_id: str) -> bool: params={ "chatRef": { "ref": chat_ref.Ref, - "type": chat_ref.type.value, + "type": chat_ref.Type.value, }, "messageID": message_id, }, @@ -329,7 +326,7 @@ def get_mentions(self, chat_ref: ChatRef, raw_mentions: list[str]) -> list[Menti params={ "chatRef": { "ref": chat_ref.Ref, - "type": chat_ref.type.value, + "type": chat_ref.Type.value, }, "rawMentions": raw_mentions, }, diff --git a/python/tests/fake_server/data.py b/python/tests/fake_server/data.py index e2dc2a2..75c42f6 100644 --- a/python/tests/fake_server/data.py +++ b/python/tests/fake_server/data.py @@ -145,6 +145,32 @@ def __init__(self) -> FakeServerData: ), ], } + self.chat_messages = { + "chat-123-abc": [ + Message( + ID="msg-001", + Content="Hello, team!", + ContentType="text", + From=MessageFrom( + UserID="user-123-abc", + DisplayName="Alice" + ), + CreatedDateTime="2024-01-01T10:00:00Z", + ReplyCount=0, + ), + Message( + ID="msg-002", + Content="Don't forget the meeting at 3 PM.", + ContentType="text", + From=MessageFrom( + UserID="user-456-def", + DisplayName="Bob" + ), + CreatedDateTime="2024-01-01T11:00:00Z", + ReplyCount=2, + ), + ], + } self.users = [ Member( ID="user-123-abc", @@ -680,3 +706,51 @@ def get_update_group_chat_topic_response(self, chat_id: str, request_json: dict) "isHiddenForAllMembers": chat.IsHidden, "topic": chat.Topic, } + + def get_list_messages_in_chat_response(self, chat_id: str) -> dict: + return { + "value": [ + { + "id": message.ID, + "body": { + "content": message.Content, + "contentType": message.ContentType, + }, + "from": { + "user": { + "id": message.From.UserID, + "displayName": message.From.DisplayName, + } + }, + "createdDateTime": message.CreatedDateTime, + "replies": [{"id": f"dummy-reply-{i}"} for i in range(message.ReplyCount)] + } + for message in self.chat_messages.get(chat_id, []) + ], + } + + def get_send_message_in_chat_response(self, chat_id: str, request_json: dict) -> dict: + return { + "id": self.newMessageTemplate.ID, + "body": { + "content": request_json.get("body", {}).get("content"), + "contentType": request_json.get("body", {}).get("contentType"), + }, + "from": { + "user": { + "id": self.newMessageTemplate.From.UserID, + "displayName": self.newMessageTemplate.From.DisplayName, + } + }, + "createdDateTime": self.newMessageTemplate.CreatedDateTime, + } + + def get_delete_message_in_chat_response(self, chat_id: str, message_id: str) -> dict: + chat_messages = self.chat_messages.get(chat_id, []) + + message = next((m for m in chat_messages if m.ID == message_id), None) + if not message: + return {"success": False} + + chat_messages.remove(message) + return {"success": True} diff --git a/python/tests/fake_server/setup.py b/python/tests/fake_server/setup.py index b386812..d4d2c25 100644 --- a/python/tests/fake_server/setup.py +++ b/python/tests/fake_server/setup.py @@ -143,6 +143,44 @@ def handle_chats_request(req): "Update Chat" )) + #GET /chats/{chat_id}/messages + httpserver.expect_request( + re.compile(r"^/v1.0/chats/([^/]+)/messages"), + method="GET" + ).respond_with_handler(lambda req: make_log_response( + req, + data.get_list_messages_in_chat_response( + re.search(r"/chats/([^/]+)/messages", req.path).group(1) + ), + "List Chat Messages" + )) + + #POST /chats/{chat_id}/messages + httpserver.expect_request( + re.compile(r"^/v1.0/chats/([^/]+)/messages"), + method="POST" + ).respond_with_handler(lambda req: make_log_response( + req, + data.get_send_message_in_chat_response( + re.search(r"/chats/([^/]+)/messages", req.path).group(1), + req.json + ), + "Send Chat Message" + )) + + #DELETE /chats/{chat_id}/messages/{message_id} + httpserver.expect_request( + re.compile(r"^/v1.0/chats/([^/]+)/messages/([^/]+)"), + method="DELETE" + ).respond_with_handler(lambda req: make_log_response( + req, + data.get_delete_message_in_chat_response( + re.search(r"/chats/([^/]+)/messages/([^/]+)", req.path).group(1), + re.search(r"/chats/([^/]+)/messages/([^/]+)", req.path).group(2) + ), + "Delete Chat Message" + )) + #POST /teams/{team_id}/channels/{channel_id}/members httpserver.expect_request( re.compile(r"^/v1.0/teams/([^/]+)/channels/([^/]+)/members"), diff --git a/python/tests/test_chats.py b/python/tests/test_chats.py index d688f6d..89e2773 100644 --- a/python/tests/test_chats.py +++ b/python/tests/test_chats.py @@ -1,7 +1,8 @@ from teams_lib_pzsp2_z1.client import TeamsClient from tests.init_fake_client import init_fake_client from tests.fake_server.setup import setup_fake_server -from teams_lib_pzsp2_z1.model.chat import ChatType +from teams_lib_pzsp2_z1.model.chat import ChatType, ChatRef +from teams_lib_pzsp2_z1.model.message import MessageContentType def test_list_my_group_chats_integration(httpserver): @@ -187,5 +188,42 @@ def test_update_group_chat_topic_integration(httpserver): assert chat.Topic == data.updatedGroupChatTopic assert chat.IsHidden == data.group_chats[0].IsHidden + finally: + client.close() + + +def test_list_messeges_in_chat_integration(httpserver): + + data = setup_fake_server(httpserver) + + client = TeamsClient(auto_init=False) + try: + init_fake_client(client, httpserver.url_for("")) + + messages = client.chats.list_messages( + chat_ref=ChatRef( + Ref=data.group_chats[0].Topic, + Type=ChatType.GROUP, + ) + ) + + assert len(messages) == len(data.chat_messages[data.group_chats[0].ID]) + + assert messages[0].ID == data.chat_messages[data.group_chats[0].ID][0].ID + assert messages[0].Content == data.chat_messages[data.group_chats[0].ID][0].Content + assert messages[0].ContentType == MessageContentType(data.chat_messages[data.group_chats[0].ID][0].ContentType) + assert messages[0].From.UserID == data.chat_messages[data.group_chats[0].ID][0].From.UserID + assert messages[0].From.DisplayName == data.chat_messages[data.group_chats[0].ID][0].From.DisplayName + assert messages[0].ReplyCount == data.chat_messages[data.group_chats[0].ID][0].ReplyCount + assert messages[0].CreatedDateTime == data.chat_messages[data.group_chats[0].ID][0].CreatedDateTime + + assert messages[1].ID == data.chat_messages[data.group_chats[0].ID][1].ID + assert messages[1].Content == data.chat_messages[data.group_chats[0].ID][1].Content + assert messages[1].ContentType == MessageContentType(data.chat_messages[data.group_chats[0].ID][1].ContentType) + assert messages[1].From.UserID == data.chat_messages[data.group_chats[0].ID][1].From.UserID + assert messages[1].From.DisplayName == data.chat_messages[data.group_chats[0].ID][1].From.DisplayName + assert messages[1].ReplyCount == data.chat_messages[data.group_chats[0].ID][1].ReplyCount + assert messages[1].CreatedDateTime == data.chat_messages[data.group_chats[0].ID][1].CreatedDateTime + finally: client.close() \ No newline at end of file From 76801abe517780847332cfa2ff1297a3110f31dc Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Fri, 2 Jan 2026 15:05:41 +0100 Subject: [PATCH 48/99] feat: added message management tests --- python/tests/fake_server/data.py | 9 ------ python/tests/fake_server/setup.py | 53 +++++++++++++++++------------- python/tests/test_chats.py | 54 ++++++++++++++++++++++++++++++- 3 files changed, 84 insertions(+), 32 deletions(-) diff --git a/python/tests/fake_server/data.py b/python/tests/fake_server/data.py index 75c42f6..80d4287 100644 --- a/python/tests/fake_server/data.py +++ b/python/tests/fake_server/data.py @@ -745,12 +745,3 @@ def get_send_message_in_chat_response(self, chat_id: str, request_json: dict) -> "createdDateTime": self.newMessageTemplate.CreatedDateTime, } - def get_delete_message_in_chat_response(self, chat_id: str, message_id: str) -> dict: - chat_messages = self.chat_messages.get(chat_id, []) - - message = next((m for m in chat_messages if m.ID == message_id), None) - if not message: - return {"success": False} - - chat_messages.remove(message) - return {"success": True} diff --git a/python/tests/fake_server/setup.py b/python/tests/fake_server/setup.py index d4d2c25..58b450e 100644 --- a/python/tests/fake_server/setup.py +++ b/python/tests/fake_server/setup.py @@ -66,6 +66,30 @@ def handle_chats_request(req): f"List Chats ({chat_type})" ) + #GET /chats/{chat_id}/messages + httpserver.expect_request( + re.compile(r"^/v1.0/chats/([^/]+)/messages"), + method="GET" + ).respond_with_handler(lambda req: make_log_response( + req, + data.get_list_messages_in_chat_response( + re.search(r"/chats/([^/]+)/messages", req.path).group(1) + ), + "List Chat Messages" + )) + + #GET users/me/chats/{chat_id}/messages + httpserver.expect_request( + re.compile(r"^/v1.0/users/me-token-to-replace/chats/([^/]+)/messages"), + method="GET" + ).respond_with_handler(lambda req: make_log_response( + req, + data.get_list_messages_in_chat_response( + re.search(r"/users/me-token-to-replace/chats/([^/]+)/messages", req.path).group(1) + ), + "List Chat Messages (me)" + )) + # GET /users/me/chats httpserver.expect_request( "/v1.0/users/me-token-to-replace/chats", @@ -143,17 +167,7 @@ def handle_chats_request(req): "Update Chat" )) - #GET /chats/{chat_id}/messages - httpserver.expect_request( - re.compile(r"^/v1.0/chats/([^/]+)/messages"), - method="GET" - ).respond_with_handler(lambda req: make_log_response( - req, - data.get_list_messages_in_chat_response( - re.search(r"/chats/([^/]+)/messages", req.path).group(1) - ), - "List Chat Messages" - )) + #POST /chats/{chat_id}/messages httpserver.expect_request( @@ -168,18 +182,13 @@ def handle_chats_request(req): "Send Chat Message" )) - #DELETE /chats/{chat_id}/messages/{message_id} + #DELETE users/me/chats/{chat_id}/messages/{message_id}/softDelete httpserver.expect_request( - re.compile(r"^/v1.0/chats/([^/]+)/messages/([^/]+)"), - method="DELETE" - ).respond_with_handler(lambda req: make_log_response( - req, - data.get_delete_message_in_chat_response( - re.search(r"/chats/([^/]+)/messages/([^/]+)", req.path).group(1), - re.search(r"/chats/([^/]+)/messages/([^/]+)", req.path).group(2) - ), - "Delete Chat Message" - )) + re.compile(r"/v1.0/users/[^/]+/chats/[^/]+/messages/[^/]+/softDelete"), + method="POST" + ).respond_with_response( + Response(status=204) + ) #POST /teams/{team_id}/channels/{channel_id}/members httpserver.expect_request( diff --git a/python/tests/test_chats.py b/python/tests/test_chats.py index 89e2773..3602482 100644 --- a/python/tests/test_chats.py +++ b/python/tests/test_chats.py @@ -2,7 +2,7 @@ from tests.init_fake_client import init_fake_client from tests.fake_server.setup import setup_fake_server from teams_lib_pzsp2_z1.model.chat import ChatType, ChatRef -from teams_lib_pzsp2_z1.model.message import MessageContentType +from teams_lib_pzsp2_z1.model.message import MessageContentType, MessageBody def test_list_my_group_chats_integration(httpserver): @@ -225,5 +225,57 @@ def test_list_messeges_in_chat_integration(httpserver): assert messages[1].ReplyCount == data.chat_messages[data.group_chats[0].ID][1].ReplyCount assert messages[1].CreatedDateTime == data.chat_messages[data.group_chats[0].ID][1].CreatedDateTime + finally: + client.close() + +def test_send_message_in_chat_integration(httpserver): + + data = setup_fake_server(httpserver) + + client = TeamsClient(auto_init=False) + try: + init_fake_client(client, httpserver.url_for("")) + + message = client.chats.send_message( + chat_ref=ChatRef( + Ref=data.group_chats[0].Topic, + Type=ChatType.GROUP, + ), + body=MessageBody( + Content=data.newMessageTemplate.Content, + ContentType=MessageContentType(data.newMessageTemplate.ContentType), + Mentions=[], + ) + ) + + assert message.ID == data.newMessageTemplate.ID + assert message.Content == data.newMessageTemplate.Content + assert message.ContentType == MessageContentType(data.newMessageTemplate.ContentType) + assert message.From.UserID == data.newMessageTemplate.From.UserID + assert message.From.DisplayName == data.newMessageTemplate.From.DisplayName + assert message.ReplyCount == data.newMessageTemplate.ReplyCount + assert message.CreatedDateTime == data.newMessageTemplate.CreatedDateTime + + finally: + client.close() + +def test_delete_message_in_chat_integration(httpserver): + + data = setup_fake_server(httpserver) + + client = TeamsClient(auto_init=False) + try: + init_fake_client(client, httpserver.url_for("")) + + result = client.chats.delete_message( + chat_ref=ChatRef( + Ref=data.group_chats[0].Topic, + Type=ChatType.GROUP, + ), + message_id=data.chat_messages[data.group_chats[0].ID][0].ID, + ) + + assert result is True + finally: client.close() \ No newline at end of file From 8d75893bb7fa16db992a4d064203d45534d06caf Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Fri, 2 Jan 2026 15:10:59 +0100 Subject: [PATCH 49/99] feat: implemented get message in chat test --- python/tests/fake_server/data.py | 20 ++++++++++++++++++++ python/tests/fake_server/setup.py | 17 +++++++++++++++-- python/tests/test_chats.py | 26 ++++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 2 deletions(-) diff --git a/python/tests/fake_server/data.py b/python/tests/fake_server/data.py index 80d4287..ecc97b2 100644 --- a/python/tests/fake_server/data.py +++ b/python/tests/fake_server/data.py @@ -745,3 +745,23 @@ def get_send_message_in_chat_response(self, chat_id: str, request_json: dict) -> "createdDateTime": self.newMessageTemplate.CreatedDateTime, } + def get_get_message_in_chat_response(self, chat_id: str, message_id: str) -> dict | None: + message = next((m for m in self.chat_messages.get(chat_id, []) if m.ID == message_id), None) + if not message: + return None + + return { + "id": message.ID, + "body": { + "content": message.Content, + "contentType": message.ContentType, + }, + "from": { + "user": { + "id": message.From.UserID, + "displayName": message.From.DisplayName, + } + }, + "createdDateTime": message.CreatedDateTime, + "replies": [{"id": f"dummy-reply-{i}"} for i in range(message.ReplyCount)] + } diff --git a/python/tests/fake_server/setup.py b/python/tests/fake_server/setup.py index 58b450e..4e840b9 100644 --- a/python/tests/fake_server/setup.py +++ b/python/tests/fake_server/setup.py @@ -66,6 +66,20 @@ def handle_chats_request(req): f"List Chats ({chat_type})" ) + + #GET /chats/{chat_id}/messages/{message_id} + httpserver.expect_request( + re.compile(r"^/v1.0/chats/([^/]+)/messages/([^/]+)"), + method="GET" + ).respond_with_handler(lambda req: make_log_response( + req, + data.get_get_message_in_chat_response( + re.search(r"/chats/([^/]+)/messages/([^/]+)", req.path).group(1), + re.search(r"/chats/([^/]+)/messages/([^/]+)", req.path).group(2) + ), + "Get Chat Message" + )) + #GET /chats/{chat_id}/messages httpserver.expect_request( re.compile(r"^/v1.0/chats/([^/]+)/messages"), @@ -167,8 +181,6 @@ def handle_chats_request(req): "Update Chat" )) - - #POST /chats/{chat_id}/messages httpserver.expect_request( re.compile(r"^/v1.0/chats/([^/]+)/messages"), @@ -190,6 +202,7 @@ def handle_chats_request(req): Response(status=204) ) + #POST /teams/{team_id}/channels/{channel_id}/members httpserver.expect_request( re.compile(r"^/v1.0/teams/([^/]+)/channels/([^/]+)/members"), diff --git a/python/tests/test_chats.py b/python/tests/test_chats.py index 3602482..4b4797e 100644 --- a/python/tests/test_chats.py +++ b/python/tests/test_chats.py @@ -277,5 +277,31 @@ def test_delete_message_in_chat_integration(httpserver): assert result is True + finally: + client.close() + +def test_get_message_in_chat_integration(httpserver): + + data = setup_fake_server(httpserver) + + client = TeamsClient(auto_init=False) + try: + init_fake_client(client, httpserver.url_for("")) + + message = client.chats.get_message( + chat_ref=ChatRef( + Ref=data.group_chats[0].Topic, + Type=ChatType.GROUP, + ), + message_id=data.chat_messages[data.group_chats[0].ID][0].ID, + ) + + assert message.ID == data.chat_messages[data.group_chats[0].ID][0].ID + assert message.Content == data.chat_messages[data.group_chats[0].ID][0].Content + assert message.ContentType == MessageContentType(data.chat_messages[data.group_chats[0].ID][0].ContentType) + assert message.From.UserID == data.chat_messages[data.group_chats[0].ID][0].From.UserID + assert message.From.DisplayName == data.chat_messages[data.group_chats[0].ID][0].From.DisplayName + assert message.ReplyCount == data.chat_messages[data.group_chats[0].ID][0].ReplyCount + assert message.CreatedDateTime == data.chat_messages[data.group_chats[0].ID][0].CreatedDateTime finally: client.close() \ No newline at end of file From c69a7eda5fa4e7023aab9a995893044181a72ec7 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Fri, 2 Jan 2026 16:06:06 +0100 Subject: [PATCH 50/99] feat: implemented test for list All my messeges --- .../json-client/decoders/decode-timeRange.go | 33 +++++++++++++++++ go/internal/json-client/requests-chats.go | 11 ++++-- python/tests/fake_server/data.py | 28 ++++++++++++++ python/tests/fake_server/setup.py | 13 +++++++ python/tests/test_chats.py | 37 +++++++++++++++++++ 5 files changed, 118 insertions(+), 4 deletions(-) create mode 100644 go/internal/json-client/decoders/decode-timeRange.go diff --git a/go/internal/json-client/decoders/decode-timeRange.go b/go/internal/json-client/decoders/decode-timeRange.go new file mode 100644 index 0000000..e6f74b6 --- /dev/null +++ b/go/internal/json-client/decoders/decode-timeRange.go @@ -0,0 +1,33 @@ +package decoders + +import ( + "fmt" + "time" +) + +func DecodeTimeRange(startTime string, endTime string) (*time.Time, *time.Time, error) { + parseTime := func(s string) (time.Time, error) { + t, err := time.Parse(time.RFC3339, s) + if err == nil { + return t, nil + } + layoutNaive := "2006-01-02T15:04:05" + if len(s) > 19 { + layoutNaive = "2006-01-02T15:04:05.999999" + } + + return time.Parse(layoutNaive, s) + } + + parsedStartTime, err := parseTime(startTime) + if err != nil { + return nil, nil, fmt.Errorf("error parsing startTime ('%s'): %v", startTime, err) + } + + parsedEndTime, err := parseTime(endTime) + if err != nil { + return nil, nil, fmt.Errorf("error parsing endTime ('%s'): %v", endTime, err) + } + + return &parsedStartTime, &parsedEndTime, nil +} diff --git a/go/internal/json-client/requests-chats.go b/go/internal/json-client/requests-chats.go index f843158..68eca9d 100644 --- a/go/internal/json-client/requests-chats.go +++ b/go/internal/json-client/requests-chats.go @@ -2,7 +2,6 @@ package jsonclient import ( "context" - "time" "github.com/pzsp-teams/lib-python/internal/json-client/decoders" "github.com/pzsp-teams/lib/chats" @@ -144,14 +143,18 @@ func (c *TeamsJSONClient) ListMyChats(p map[string]interface{}) (interface{}, er } type listChatMessagesParams struct { - StartTime time.Time `json:"startTime"` - EndTime time.Time `json:"endTime"` + StartTime string `json:"startTime"` + EndTime string `json:"endTime"` Top *int32 `json:"top"` } func (c *TeamsJSONClient) ListMyChatMessages(p map[string]interface{}) (interface{}, error) { return execute(p, func(params listChatMessagesParams) (interface{}, error) { - return c.client.Chats.ListAllMessages(context.TODO(), ¶ms.StartTime, ¶ms.EndTime, params.Top) + parsedStartTime, parsedEndTime, err := decoders.DecodeTimeRange(params.StartTime, params.EndTime) + if err != nil { + return nil, err + } + return c.client.Chats.ListAllMessages(context.TODO(), parsedStartTime, parsedEndTime, params.Top) }) } diff --git a/python/tests/fake_server/data.py b/python/tests/fake_server/data.py index ecc97b2..2278cf1 100644 --- a/python/tests/fake_server/data.py +++ b/python/tests/fake_server/data.py @@ -765,3 +765,31 @@ def get_get_message_in_chat_response(self, chat_id: str, message_id: str) -> dic "createdDateTime": message.CreatedDateTime, "replies": [{"id": f"dummy-reply-{i}"} for i in range(message.ReplyCount)] } + + def get_all_messeges_in_chats_response(self) -> dict: + return { + "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#Collection(chatMessage)", + "@odata.count": len(self.chat_messages), + "value": [ + { + "@odata.type": "#microsoft.graph.chatMessage", + "id": message.ID, + "etag": message.ID, + "messageType": "message", + "chatId": self.group_chats[0].ID, + "body": { + "content": message.Content, + "contentType": message.ContentType, + }, + "from": { + "user": { + "id": message.From.UserID, + "displayName": message.From.DisplayName, + } + }, + "createdDateTime": message.CreatedDateTime, + "lastModifiedDateTime": message.CreatedDateTime, + } + for message in self.chat_messages[self.group_chats[0].ID] + ], + } diff --git a/python/tests/fake_server/setup.py b/python/tests/fake_server/setup.py index 4e840b9..d815fc8 100644 --- a/python/tests/fake_server/setup.py +++ b/python/tests/fake_server/setup.py @@ -4,6 +4,7 @@ import sys from werkzeug.wrappers import Response from teams_lib_pzsp2_z1.model.chat import ChatType +import traceback def setup_fake_server(httpserver) -> FakeServerData: """ @@ -45,6 +46,16 @@ def make_log_response(request, data_dict: dict, context_msg: str = "") -> Respon req, data.get_myJoinedTeams_response(), "List Joined Teams" )) + #GET /users/me/chats/getAllMessages() + httpserver.expect_request( + "/v1.0/users/me-token-to-replace/chats/getAllMessages()", + method="GET" + ).respond_with_handler(lambda req: make_log_response( + req, + data.get_all_messeges_in_chats_response(), + "List All Chat Messages" + )) + def handle_chats_request(req): filter_param = req.args.get('$filter', '') chat_type = None @@ -67,6 +78,8 @@ def handle_chats_request(req): ) + + #GET /chats/{chat_id}/messages/{message_id} httpserver.expect_request( re.compile(r"^/v1.0/chats/([^/]+)/messages/([^/]+)"), diff --git a/python/tests/test_chats.py b/python/tests/test_chats.py index 4b4797e..a2bc139 100644 --- a/python/tests/test_chats.py +++ b/python/tests/test_chats.py @@ -3,6 +3,7 @@ from tests.fake_server.setup import setup_fake_server from teams_lib_pzsp2_z1.model.chat import ChatType, ChatRef from teams_lib_pzsp2_z1.model.message import MessageContentType, MessageBody +from datetime import datetime def test_list_my_group_chats_integration(httpserver): @@ -303,5 +304,41 @@ def test_get_message_in_chat_integration(httpserver): assert message.From.DisplayName == data.chat_messages[data.group_chats[0].ID][0].From.DisplayName assert message.ReplyCount == data.chat_messages[data.group_chats[0].ID][0].ReplyCount assert message.CreatedDateTime == data.chat_messages[data.group_chats[0].ID][0].CreatedDateTime + finally: + client.close() + + +def test_get_all_messages_in_chat_integration(httpserver): + + data = setup_fake_server(httpserver) + + client = TeamsClient(auto_init=False) + try: + init_fake_client(client, httpserver.url_for("")) + + messages = client.chats.list_all_messages( + start_time=datetime.min, + end_time=datetime.max, + top=50, + ) + + assert len(messages) == len(data.chat_messages[data.group_chats[0].ID]) + + assert messages[0].ID == data.chat_messages[data.group_chats[0].ID][0].ID + assert messages[0].Content == data.chat_messages[data.group_chats[0].ID][0].Content + assert messages[0].ContentType == MessageContentType(data.chat_messages[data.group_chats[0].ID][0].ContentType) + assert messages[0].From.UserID == data.chat_messages[data.group_chats[0].ID][0].From.UserID + assert messages[0].From.DisplayName == data.chat_messages[data.group_chats[0].ID][0].From.DisplayName + assert messages[0].ReplyCount == 0 + assert messages[0].CreatedDateTime == data.chat_messages[data.group_chats[0].ID][0].CreatedDateTime + + assert messages[1].ID == data.chat_messages[data.group_chats[0].ID][1].ID + assert messages[1].Content == data.chat_messages[data.group_chats[0].ID][1].Content + assert messages[1].ContentType == MessageContentType(data.chat_messages[data.group_chats[0].ID][1].ContentType) + assert messages[1].From.UserID == data.chat_messages[data.group_chats[0].ID][1].From.UserID + assert messages[1].From.DisplayName == data.chat_messages[data.group_chats[0].ID][1].From.DisplayName + assert messages[1].ReplyCount == 0 + assert messages[1].CreatedDateTime == data.chat_messages[data.group_chats[0].ID][1].CreatedDateTime + finally: client.close() \ No newline at end of file From c7f793561c28f92f86d652ec72694aa6822903bc Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Fri, 2 Jan 2026 16:43:04 +0100 Subject: [PATCH 51/99] feat: implemented tests for pinned messages --- python/tests/fake_server/data.py | 15 ++++-- python/tests/fake_server/setup.py | 30 ++++++++++- python/tests/test_chats.py | 84 ++++++++++++++++++++++++++++++- 3 files changed, 123 insertions(+), 6 deletions(-) diff --git a/python/tests/fake_server/data.py b/python/tests/fake_server/data.py index 2278cf1..b9b766a 100644 --- a/python/tests/fake_server/data.py +++ b/python/tests/fake_server/data.py @@ -617,9 +617,6 @@ def get_list_chats_response(self, chat_type: ChatType) -> dict: else: chats = self.group_chats - print(chat_type) - print(chat_type == ChatType.ONEONONE) - return { "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#chats", "@odata.count": len(chats), @@ -793,3 +790,15 @@ def get_all_messeges_in_chats_response(self) -> dict: for message in self.chat_messages[self.group_chats[0].ID] ], } + + def get_list_pinned_messages_in_chat_response(self, chat_id: str) -> dict: + return { + "value": [ + { + "@odata.type": "#microsoft.graph.pinnedChatMessageInfo", + "id": message.ID, + } + for message in self.chat_messages.get(chat_id, []) + ], + } + diff --git a/python/tests/fake_server/setup.py b/python/tests/fake_server/setup.py index d815fc8..979bbcf 100644 --- a/python/tests/fake_server/setup.py +++ b/python/tests/fake_server/setup.py @@ -56,6 +56,34 @@ def make_log_response(request, data_dict: dict, context_msg: str = "") -> Respon "List All Chat Messages" )) + #GET /chats/{chat_id}/pinnedMessages + httpserver.expect_request( + re.compile(r"^/v1.0/chats/([^/]+)/pinnedMessages"), + method="GET" + ).respond_with_handler(lambda req: make_log_response( + req, + data.get_list_pinned_messages_in_chat_response( + re.search(r"/chats/([^/]+)/pinnedMessages", req.path).group(1) + ), + "List Pinned Chat Messages" + )) + + # POST /chats/{chat_id}/pinnedMessages + httpserver.expect_request( + re.compile(r"^/v1.0/chats/([^/]+)/pinnedMessages"), + method="POST" + ).respond_with_response( + Response(status=204) + ) + + # DELETE /chats/{chat_id}/pinnedMessages/{message_id} + httpserver.expect_request( + re.compile(r"^/v1.0/chats/([^/]+)/pinnedMessages/([^/]+)"), + method="DELETE" + ).respond_with_response( + Response(status=204) + ) + def handle_chats_request(req): filter_param = req.args.get('$filter', '') chat_type = None @@ -64,8 +92,6 @@ def handle_chats_request(req): if match: chat_type = match.group(1) - print(f"DEBUG: Parsowanie filtra: '{filter_param}' -> Typ: '{chat_type}'") - if chat_type == "oneOnOne": chat_type = ChatType.ONEONONE elif chat_type == "group": diff --git a/python/tests/test_chats.py b/python/tests/test_chats.py index a2bc139..4a48c6d 100644 --- a/python/tests/test_chats.py +++ b/python/tests/test_chats.py @@ -341,4 +341,86 @@ def test_get_all_messages_in_chat_integration(httpserver): assert messages[1].CreatedDateTime == data.chat_messages[data.group_chats[0].ID][1].CreatedDateTime finally: - client.close() \ No newline at end of file + client.close() + + +def test_list_pinned_messages_integration(httpserver): + + data = setup_fake_server(httpserver) + + client = TeamsClient(auto_init=False) + try: + init_fake_client(client, httpserver.url_for("")) + + messages = client.chats.list_pinned_messages( + chat_ref=ChatRef( + Ref=data.group_chats[0].Topic, + Type=ChatType.GROUP, + ) + ) + + assert len(messages) == len(data.chat_messages[data.group_chats[0].ID]) + + assert messages[0].ID == data.chat_messages[data.group_chats[0].ID][0].ID + assert messages[0].Content == data.chat_messages[data.group_chats[0].ID][0].Content + assert messages[0].ContentType == MessageContentType(data.chat_messages[data.group_chats[0].ID][0].ContentType) + assert messages[0].From.UserID == data.chat_messages[data.group_chats[0].ID][0].From.UserID + assert messages[0].From.DisplayName == data.chat_messages[data.group_chats[0].ID][0].From.DisplayName + assert messages[0].ReplyCount == 0 + assert messages[0].CreatedDateTime == data.chat_messages[data.group_chats[0].ID][0].CreatedDateTime + + assert messages[1].ID == data.chat_messages[data.group_chats[0].ID][1].ID + assert messages[1].Content == data.chat_messages[data.group_chats[0].ID][1].Content + assert messages[1].ContentType == MessageContentType(data.chat_messages[data.group_chats[0].ID][1].ContentType) + assert messages[1].From.UserID == data.chat_messages[data.group_chats[0].ID][1].From.UserID + assert messages[1].From.DisplayName == data.chat_messages[data.group_chats[0].ID][1].From.DisplayName + assert messages[1].ReplyCount == 0 + assert messages[1].CreatedDateTime == data.chat_messages[data.group_chats[0].ID][1].CreatedDateTime + + finally: + client.close() + + +def test_pin_message_in_chat_integration(httpserver): + + data = setup_fake_server(httpserver) + + client = TeamsClient(auto_init=False) + try: + init_fake_client(client, httpserver.url_for("")) + + result = client.chats.pin_message( + chat_ref=ChatRef( + Ref=data.group_chats[0].Topic, + Type=ChatType.GROUP, + ), + message_id=data.chat_messages[data.group_chats[0].ID][0].ID, + ) + + assert result is True + + finally: + client.close() + + +def test_unpin_message_in_chat_integration(httpserver): + + data = setup_fake_server(httpserver) + + client = TeamsClient(auto_init=False) + try: + init_fake_client(client, httpserver.url_for("")) + + result = client.chats.unpin_message( + chat_ref=ChatRef( + Ref=data.group_chats[0].Topic, + Type=ChatType.GROUP, + ), + message_id=data.chat_messages[data.group_chats[0].ID][0].ID, + ) + + assert result is True + + finally: + client.close() + From 0f5713d2be66da811e68dfe4230a5d9acc2b5283 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Fri, 2 Jan 2026 16:56:01 +0100 Subject: [PATCH 52/99] feat: implemented tests for get mentions --- .../teams_lib_pzsp2_z1/services/channels.py | 6 ++--- python/teams_lib_pzsp2_z1/services/chats.py | 7 ++--- python/tests/test_channels.py | 23 ++++++++++++++++ python/tests/test_chats.py | 26 +++++++++++++++++++ 4 files changed, 56 insertions(+), 6 deletions(-) diff --git a/python/teams_lib_pzsp2_z1/services/channels.py b/python/teams_lib_pzsp2_z1/services/channels.py index 084e529..b2bd3d8 100644 --- a/python/teams_lib_pzsp2_z1/services/channels.py +++ b/python/teams_lib_pzsp2_z1/services/channels.py @@ -293,15 +293,15 @@ def remove_member(self, teamRef: str, channelRef: str, userRef: str) -> bool: return response == "removed" def get_mentions( - self, teamRef: str, channelRef: str, rawMentions: list[str] + self, teamRef: str, channelRef: str, raw_mentions: list[str] ) -> list[Mention]: response = self.client.execute( cmd_type="request", - method="getChannelMentions", + method="getMentionsInChannel", params={ "teamRef": teamRef, "channelRef": channelRef, - "rawMentions": rawMentions, + "rawMentions": raw_mentions, }, ) return [Mention(**mention) for mention in response] diff --git a/python/teams_lib_pzsp2_z1/services/chats.py b/python/teams_lib_pzsp2_z1/services/chats.py index 2e705b3..dab5f1b 100644 --- a/python/teams_lib_pzsp2_z1/services/chats.py +++ b/python/teams_lib_pzsp2_z1/services/chats.py @@ -334,9 +334,10 @@ def get_mentions(self, chat_ref: ChatRef, raw_mentions: list[str]) -> list[Menti return [ Mention( - ID=mention["ID"], - Mentioned=mention["Mentioned"], - MentionText=mention["MentionText"], + Kind=mention["Kind"], + AtID=mention["AtID"], + Text=mention["Text"], + TargetID=mention["TargetID"], ) for mention in mentions ] diff --git a/python/tests/test_channels.py b/python/tests/test_channels.py index e0695d0..74135e7 100644 --- a/python/tests/test_channels.py +++ b/python/tests/test_channels.py @@ -2,6 +2,7 @@ from tests.init_fake_client import init_fake_client from tests.fake_server.setup import setup_fake_server from teams_lib_pzsp2_z1.model.message import MessageContentType, MessageBody +from teams_lib_pzsp2_z1.model.mention import MentionKind def test_list_channels_integration(httpserver): @@ -371,6 +372,28 @@ def test_remove_member_integration(httpserver): finally: client.close() +def test_get_mention_integration(httpserver): + + data = setup_fake_server(httpserver) + + client = TeamsClient(auto_init=False) + try: + init_fake_client(client, httpserver.url_for("")) + + mention = client.channels.get_mentions( + teamRef=data.teams[0].DisplayName, + channelRef=data.channels[data.teams[0].ID][0].Name, + raw_mentions=MentionKind.TEAM.value, + ) + + assert mention[0].Kind == MentionKind.TEAM.value + assert mention[0].AtID == 0 + assert mention[0].Text == data.teams[0].DisplayName + assert mention[0].TargetID == data.teams[0].ID + + finally: + client.close() + diff --git a/python/tests/test_chats.py b/python/tests/test_chats.py index 4a48c6d..8966601 100644 --- a/python/tests/test_chats.py +++ b/python/tests/test_chats.py @@ -3,6 +3,7 @@ from tests.fake_server.setup import setup_fake_server from teams_lib_pzsp2_z1.model.chat import ChatType, ChatRef from teams_lib_pzsp2_z1.model.message import MessageContentType, MessageBody +from teams_lib_pzsp2_z1.model.mention import MentionKind from datetime import datetime @@ -424,3 +425,28 @@ def test_unpin_message_in_chat_integration(httpserver): finally: client.close() + +def test_get_mention_integration(httpserver): + + data = setup_fake_server(httpserver) + + client = TeamsClient(auto_init=False) + try: + init_fake_client(client, httpserver.url_for("")) + + mention = client.chats.get_mentions( + chat_ref=ChatRef( + Ref=data.group_chats[0].Topic, + Type=ChatType.GROUP, + ), + raw_mentions=MentionKind.EVERYONE.value, + ) + + assert mention[0].Kind == MentionKind.EVERYONE.value + assert mention[0].AtID == 0 + assert mention[0].Text == "Everyone" + assert mention[0].TargetID == data.group_chats[0].ID + + finally: + client.close() + From 854263aa97f4e804a9c84d306b9d6afe5bcae14d Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Fri, 2 Jan 2026 17:08:04 +0100 Subject: [PATCH 53/99] chore: cleaned mock server code formatting a bit --- python/tests/fake_server/data.py | 629 +++++++++++++----------------- python/tests/fake_server/setup.py | 419 ++++++++++---------- 2 files changed, 486 insertions(+), 562 deletions(-) diff --git a/python/tests/fake_server/data.py b/python/tests/fake_server/data.py index b9b766a..525b4fe 100644 --- a/python/tests/fake_server/data.py +++ b/python/tests/fake_server/data.py @@ -1,256 +1,169 @@ -from dataclasses import dataclass +from dataclasses import dataclass, field +from typing import Optional, List, Dict, Any + from teams_lib_pzsp2_z1.model.team import Team from teams_lib_pzsp2_z1.model.chat import Chat, ChatType from teams_lib_pzsp2_z1.model.channel import Channel from teams_lib_pzsp2_z1.model.message import Message, MessageFrom from teams_lib_pzsp2_z1.model.member import Member -from dataclasses import field + +# --- Constants for OData --- +ODATA_CONTEXT = "@odata.context" +ODATA_TYPE = "@odata.type" +ODATA_COUNT = "@odata.count" +GRAPH_URL = "https://graph.microsoft.com/v1.0/$metadata" @dataclass class FakeServerData: - teams: list[Team] - channels: dict[str, list[Channel]] + teams: List[Team] + channels: Dict[str, List[Channel]] + # State tracking variables newGroupID: str newTeamName: str newGroupMailNickname: str newTeamVisibility: str - potentialTeams: list[Team] = field(default_factory=list) - - def __init__(self) -> FakeServerData: + potentialTeams: List[Team] = field(default_factory=list) + + # Initial data containers + messages: Dict[str, List[Message]] = field(default_factory=dict) + replies: Dict[str, List[Message]] = field(default_factory=dict) + members: Dict[str, Dict[str, List[Member]]] = field(default_factory=dict) + group_chat_members: Dict[str, List[Member]] = field(default_factory=dict) + chat_messages: Dict[str, List[Message]] = field(default_factory=dict) + users: List[Member] = field(default_factory=list) + me: Member = field(default_factory=Member) # type: ignore + group_chats: List[Chat] = field(default_factory=list) + oneonone_chats: List[Chat] = field(default_factory=list) + + # Templates and temporary state + newChannelName: str = "New Channel" + newChannelID: str = "19:newchannelid@thread.tacv2" + newMessageTemplate: Message = field(default_factory=lambda: Message( + ID="new-message-id", + Content="This is a new message.", + ContentType="text", + From=MessageFrom(UserID="user-new-001", DisplayName="New User"), + CreatedDateTime="2024-01-02T10:00:00Z", + ReplyCount=0, + )) + newMemberTemplate: Member = field(default_factory=lambda: Member( + ID="user-new-002", UserID="new-user-002", DisplayName="New Member", + Role="owner", Email="newmember@example.com" + )) + newChatTemplate: Chat = field(default_factory=lambda: Chat( + ID="new-chat-001", Type=ChatType.GROUP, IsHidden=False, Topic="New Chat Topic" + )) + updatedGroupChatTopic: str = "Updated Chat Topic" + + def __init__(self) -> None: + # --- Teams --- self.teams = [ - Team( - ID="team-123-abc", - DisplayName="Test Team", - Description="A team for testing", - IsArchived=False, - Visibility="private", - ), - Team( - ID="team-456-def", - DisplayName="Another Team", - Description="Another team for testing", - IsArchived=False, - Visibility="public", - ), - Team( - ID="archived-team-789-ghi", - DisplayName="Archived Team", - Description="An archived team for testing", - IsArchived=True, - Visibility="private", - ) + Team(ID="team-123-abc", DisplayName="Test Team", Description="A team for testing", IsArchived=False, Visibility="private"), + Team(ID="team-456-def", DisplayName="Another Team", Description="Another team for testing", IsArchived=False, Visibility="public"), + Team(ID="archived-team-789-ghi", DisplayName="Archived Team", Description="An archived team for testing", IsArchived=True, Visibility="private") ] + self.potentialTeams = [] + + # --- Channels --- self.channels = { "team-123-abc": [ - Channel( - ID="19:123123@thread.tacv2", - Name="Something", - IsGeneral=False, - ), - Channel( - ID="19:999999@thread.tacv2", - Name="Development", - IsGeneral=False, - ), + Channel(ID="19:123123@thread.tacv2", Name="Something", IsGeneral=False), + Channel(ID="19:999999@thread.tacv2", Name="Development", IsGeneral=False), ], } + + # --- Messages (Channel) --- self.messages = { "19:123123@thread.tacv2": [ - Message( - ID="msg-001", - Content="Hello, team!", - ContentType="text", - From=MessageFrom( - UserID="user-123-abc", - DisplayName="Alice" - ), - CreatedDateTime="2024-01-01T10:00:00Z", - ReplyCount=0, - ), - Message( - ID="msg-002", - Content="Don't forget the meeting at 3 PM.", - ContentType="text", - From=MessageFrom( - UserID="user-456-def", - DisplayName="Bob" - ), - CreatedDateTime="2024-01-01T11:00:00Z", - ReplyCount=2, - ), + Message(ID="msg-001", Content="Hello, team!", ContentType="text", From=MessageFrom(UserID="user-123-abc", DisplayName="Alice"), CreatedDateTime="2024-01-01T10:00:00Z", ReplyCount=0), + Message(ID="msg-002", Content="Don't forget the meeting at 3 PM.", ContentType="text", From=MessageFrom(UserID="user-456-def", DisplayName="Bob"), CreatedDateTime="2024-01-01T11:00:00Z", ReplyCount=2), ], } + + # --- Replies --- self.replies = { "msg-002": [ - Message( - ID="msg-002-reply-001", - Content="Thanks for the reminder!", - ContentType="text", - From=MessageFrom( - UserID="user-789-ghi", - DisplayName="Charlie" - ), - CreatedDateTime="2024-01-01T12:00:00Z", - ReplyCount=0, - ), - Message( - ID="msg-002-reply-002", - Content="I'll be there.", - ContentType="text", - From=MessageFrom( - UserID="user-123-abc", - DisplayName="Alice" - ), - CreatedDateTime="2024-01-01T12:30:00Z", - ReplyCount=0, - ), + Message(ID="msg-002-reply-001", Content="Thanks for the reminder!", ContentType="text", From=MessageFrom(UserID="user-789-ghi", DisplayName="Charlie"), CreatedDateTime="2024-01-01T12:00:00Z", ReplyCount=0), + Message(ID="msg-002-reply-002", Content="I'll be there.", ContentType="text", From=MessageFrom(UserID="user-123-abc", DisplayName="Alice"), CreatedDateTime="2024-01-01T12:30:00Z", ReplyCount=0), ], } + + # --- Members (Channel) --- self.members = { "team-123-abc": { "19:123123@thread.tacv2": [ - Member( - ID="user-123-abc", - UserID="user-123-abc", - DisplayName="Alice", - Role="owner", - Email="alice@example.com" - ), - Member( - ID="user-456-def", - UserID="user-456-def", - DisplayName="Bob", - Role="member", - Email="bob@example.com" - ), + Member(ID="user-123-abc", UserID="user-123-abc", DisplayName="Alice", Role="owner", Email="alice@example.com"), + Member(ID="user-456-def", UserID="user-456-def", DisplayName="Bob", Role="member", Email="bob@example.com"), ], } } + + # --- Users --- + self.users = [ + Member(ID="user-123-abc", UserID="user-123-abc", DisplayName="Alice", Role="owner", Email="alice@example.com"), + Member(ID="user-456-def", UserID="user-456-def", DisplayName="Bob", Role="member", Email="bob@example.com"), + ] + self.me = Member(ID="user-me-001", UserID="user-me-001", DisplayName="Current User", Role="member", Email="me@example.com") + + # --- Chats & Chat Members/Messages --- + self.group_chats = [ + Chat(ID="chat-123-abc", Type=ChatType.GROUP, IsHidden=False, Topic="Project Discussion"), + Chat(ID="chat-456-def", Type=ChatType.GROUP, IsHidden=True, Topic="Secret Plans"), + ] + self.oneonone_chats = [ + Chat(ID="chat-789-ghi", Type=ChatType.ONEONONE, IsHidden=False, Topic=""), + ] + self.group_chat_members = { "chat-123-abc": [ - Member( - ID="user-123-abc", - UserID="user-123-abc", - DisplayName="Alice", - Role="owner", - Email="alice@example.com" - ), - Member( - ID="user-456-def", - UserID="user-456-def", - DisplayName="Bob", - Role="member", - Email="bob@example.com" - ), + Member(ID="user-123-abc", UserID="user-123-abc", DisplayName="Alice", Role="owner", Email="alice@example.com"), + Member(ID="user-456-def", UserID="user-456-def", DisplayName="Bob", Role="member", Email="bob@example.com"), ], } + self.chat_messages = { "chat-123-abc": [ - Message( - ID="msg-001", - Content="Hello, team!", - ContentType="text", - From=MessageFrom( - UserID="user-123-abc", - DisplayName="Alice" - ), - CreatedDateTime="2024-01-01T10:00:00Z", - ReplyCount=0, - ), - Message( - ID="msg-002", - Content="Don't forget the meeting at 3 PM.", - ContentType="text", - From=MessageFrom( - UserID="user-456-def", - DisplayName="Bob" - ), - CreatedDateTime="2024-01-01T11:00:00Z", - ReplyCount=2, - ), + Message(ID="msg-001", Content="Hello, team!", ContentType="text", From=MessageFrom(UserID="user-123-abc", DisplayName="Alice"), CreatedDateTime="2024-01-01T10:00:00Z", ReplyCount=0), + Message(ID="msg-002", Content="Don't forget the meeting at 3 PM.", ContentType="text", From=MessageFrom(UserID="user-456-def", DisplayName="Bob"), CreatedDateTime="2024-01-01T11:00:00Z", ReplyCount=2), ], } - self.users = [ - Member( - ID="user-123-abc", - UserID="user-123-abc", - DisplayName="Alice", - Role="owner", - Email="alice@example.com" - ), - Member( - ID="user-456-def", - UserID="user-456-def", - DisplayName="Bob", - Role="member", - Email="bob@example.com" - ), - ] - self.me = Member( - ID="user-me-001", - UserID="user-me-001", - DisplayName="Current User", - Role="member", - Email="me@example.com" - ) - self.group_chats = [ - Chat( - ID="chat-123-abc", - Type=ChatType.GROUP, - IsHidden=False, - Topic="Project Discussion", - ), - Chat( - ID="chat-456-def", - Type=ChatType.GROUP, - IsHidden=True, - Topic="Secret Plans", - ), - ] - self.oneonone_chats = [ - Chat( - ID="chat-789-ghi", - Type=ChatType.ONEONONE, - IsHidden=False, - Topic="", - ), - ] + + # --- State Variables --- self.newGroupID = "group-789-ghi" self.newTeamName = "New Team" self.newGroupMailNickname = "new-team-nickname" self.newTeamVisibility = "private" - self.potentialTeams = [] + + # Init Templates (re-assigning to ensure freshness if needed, though default_factory handles it) self.newChannelName = "New Channel" self.newChannelID = "19:newchannelid@thread.tacv2" + self.updatedGroupChatTopic = "Updated Chat Topic" + + # Initialize templates manually to match original structure exactly if factories behave differently self.newMessageTemplate = Message( - ID="new-message-id", - Content="This is a new message.", - ContentType="text", - From=MessageFrom( - UserID="user-new-001", - DisplayName="New User" - ), - CreatedDateTime="2024-01-02T10:00:00Z", - ReplyCount=0, + ID="new-message-id", Content="This is a new message.", ContentType="text", + From=MessageFrom(UserID="user-new-001", DisplayName="New User"), + CreatedDateTime="2024-01-02T10:00:00Z", ReplyCount=0 ) self.newMemberTemplate = Member( - ID="user-new-002", - UserID="new-user-002", - DisplayName="New Member", - Role="owner", - Email="newmember@example.com" + ID="user-new-002", UserID="new-user-002", DisplayName="New Member", + Role="owner", Email="newmember@example.com" ) self.newChatTemplate = Chat( - ID="new-chat-001", - Type=ChatType.GROUP, - IsHidden=False, - Topic="New Chat Topic" + ID="new-chat-001", Type=ChatType.GROUP, IsHidden=False, Topic="New Chat Topic" ) - self.updatedGroupChatTopic = "Updated Chat Topic" + # --- Helpers --- + def _find_team(self, team_id: str) -> Optional[Team]: + return next((t for t in self.teams if t.ID == team_id), None) + def _find_channel(self, team_id: str, channel_id: str) -> Optional[Channel]: + return next((c for c in self.channels.get(team_id, []) if c.ID == channel_id), None) + # ========================================== + # TEAMS & GROUPS + # ========================================== def get_myJoinedTeams_response(self) -> dict: return { @@ -266,33 +179,20 @@ def get_myJoinedTeams_response(self) -> dict: ], } - def get_listChannels_response(self, team_id: str) -> dict: - return { - "@odata.context": f"https://graph.microsoft.com/v1.0/$metadata#teams('{team_id}')/channels", - "value": [ - { - "@odata.type": "#microsoft.graph.channel", - "id": channel.ID, - "displayName": channel.Name, - "isGeneral": channel.IsGeneral, - "membershipType": "standard", - "email": "" - } - for channel in self.channels.get(team_id, []) - ], - } - - def get_team_response(self, team_id: str) -> dict | None: + def get_team_response(self, team_id: str) -> Optional[dict]: + team = self._find_team(team_id) + if not team: + return None return { "id": team.ID, "displayName": team.DisplayName, "description": team.Description, "isArchived": team.IsArchived, "visibility": team.Visibility, - } if (team := next((t for t in self.teams if t.ID == team_id), None)) else None + } - def get_updateTeam_response(self, team_id: str, update_json: dict) -> dict | None: - team = next((t for t in self.teams if t.ID == team_id), None) + def get_updateTeam_response(self, team_id: str, update_json: dict) -> Optional[dict]: + team = self._find_team(team_id) if not team: return None @@ -312,7 +212,7 @@ def get_updateTeam_response(self, team_id: str, update_json: dict) -> dict | Non } def get_createGroup_response(self, request_json: dict) -> dict: - visibility = request_json.get("visibility").lower() + visibility = request_json.get("visibility", "").lower() if visibility not in ("private", "public"): visibility = "private" @@ -364,31 +264,26 @@ def get_createTeamFromTemplate_response(self, request_json: dict) -> dict: ) self.teams.append(new_team) - return { - "id": new_team_id, - } + return {"id": new_team_id} def get_archiveTeam_response(self, team_id: str) -> dict: - team = next((t for t in self.teams if t.ID == team_id), None) + team = self._find_team(team_id) if not team: return {"success": False} - team.IsArchived = True return {"success": True} def get_unarchiveTeam_response(self, team_id: str) -> dict: - team = next((t for t in self.teams if t.ID == team_id), None) + team = self._find_team(team_id) if not team: return {"success": False} - team.IsArchived = False return {"success": True} def get_deleteTeam_response(self, team_id: str) -> dict: - team = next((t for t in self.teams if t.ID == team_id), None) + team = self._find_team(team_id) if not team: return {"success": False} - self.teams.remove(team) return {"success": True} @@ -407,11 +302,30 @@ def get_restoreTeam_response(self, team_id: str) -> dict: "mailNickname": self.newGroupMailNickname } - def get_channel_response(self, team_id: str, channel_id: str) -> dict | None: - channel = next((c for c in self.channels.get(team_id, []) if c.ID == channel_id), None) + # ========================================== + # CHANNELS + # ========================================== + + def get_listChannels_response(self, team_id: str) -> dict: + return { + ODATA_CONTEXT: f"{GRAPH_URL}#teams('{team_id}')/channels", + "value": [ + { + ODATA_TYPE: "#microsoft.graph.channel", + "id": channel.ID, + "displayName": channel.Name, + "isGeneral": channel.IsGeneral, + "membershipType": "standard", + "email": "" + } + for channel in self.channels.get(team_id, []) + ], + } + + def get_channel_response(self, team_id: str, channel_id: str) -> Optional[dict]: + channel = self._find_channel(team_id, channel_id) if not channel: return None - return { "id": channel.ID, "displayName": channel.Name, @@ -430,7 +344,6 @@ def get_create_channel_response(self, team_id: str, request_json: dict) -> dict: if team_id not in self.channels: self.channels[team_id] = [] - self.channels[team_id].append(new_channel) return { @@ -442,28 +355,15 @@ def get_create_channel_response(self, team_id: str, request_json: dict) -> dict: } def get_delete_channel_response(self, team_id: str, channel_id: str) -> dict: - channel = next((c for c in self.channels.get(team_id, []) if c.ID == channel_id), None) + channel = self._find_channel(team_id, channel_id) if not channel: return {"success": False} - self.channels[team_id].remove(channel) return {"success": True} - def get_send_message_response(self, team_id: str, channel_id: str, request_json: dict) -> dict: - return { - "id": self.newMessageTemplate.ID, - "body": { - "content": request_json.get("body", {}).get("content"), - "contentType": request_json.get("body", {}).get("contentType"), - }, - "from": { - "user": { - "id": self.newMessageTemplate.From.UserID, - "displayName": self.newMessageTemplate.From.DisplayName, - } - }, - "createdDateTime": self.newMessageTemplate.CreatedDateTime, - } + # ========================================== + # MESSAGES (CHANNEL) + # ========================================== def get_list_messages_response(self, team_id: str, channel_id: str) -> dict: return { @@ -487,7 +387,7 @@ def get_list_messages_response(self, team_id: str, channel_id: str) -> dict: ], } - def get_message_response(self, team_id: str, channel_id: str, message_id: str) -> dict | None: + def get_message_response(self, team_id: str, channel_id: str, message_id: str) -> Optional[dict]: message = next((m for m in self.messages.get(channel_id, []) if m.ID == message_id), None) if not message: return None @@ -508,6 +408,22 @@ def get_message_response(self, team_id: str, channel_id: str, message_id: str) - "replies": [{"id": f"dummy-reply-{i}"} for i in range(message.ReplyCount)] } + def get_send_message_response(self, team_id: str, channel_id: str, request_json: dict) -> dict: + return { + "id": self.newMessageTemplate.ID, + "body": { + "content": request_json.get("body", {}).get("content"), + "contentType": request_json.get("body", {}).get("contentType"), + }, + "from": { + "user": { + "id": self.newMessageTemplate.From.UserID, + "displayName": self.newMessageTemplate.From.DisplayName, + } + }, + "createdDateTime": self.newMessageTemplate.CreatedDateTime, + } + def get_list_replies_response(self, team_id: str, channel_id: str, message_id: str) -> dict: return { "value": [ @@ -529,11 +445,10 @@ def get_list_replies_response(self, team_id: str, channel_id: str, message_id: s ], } - def get_reply_response(self, team_id: str, channel_id: str, message_id: str, reply_id: str) -> dict | None: + def get_reply_response(self, team_id: str, channel_id: str, message_id: str, reply_id: str) -> Optional[dict]: reply = next((r for r in self.replies.get(message_id, []) if r.ID == reply_id), None) if not reply: return None - return { "id": reply.ID, "body": { @@ -549,15 +464,31 @@ def get_reply_response(self, team_id: str, channel_id: str, message_id: str, rep "createdDateTime": reply.CreatedDateTime, } - def get_add_member_response(self, team_id: str, request_json: dict) -> dict: - roles = [] - if self.newMemberTemplate.Role == "owner": - roles = ["owner"] + # ========================================== + # MEMBERS (CHANNEL) + # ========================================== + def get_list_members_response(self, team_id: str, channel_id: str) -> dict: return { - "@odata.context": f"https://graph.microsoft.com/v1.0/$metadata#teams('{team_id}')/members/$entity", - "@odata.type": "#microsoft.graph.aadUserConversationMember", + ODATA_CONTEXT: f"{GRAPH_URL}#teams('{team_id}')/channels('{channel_id}')/members", + "value": [ + { + ODATA_TYPE: "#microsoft.graph.aadUserConversationMember", + "id": member.ID, + "userId": member.UserID, + "displayName": member.DisplayName, + "roles": [member.Role] if member.Role == "owner" else [], + "email": member.Email, + } + for member in self.members.get(team_id, {}).get(channel_id, []) + ], + } + def get_add_member_response(self, team_id: str, request_json: dict) -> dict: + roles = ["owner"] if self.newMemberTemplate.Role == "owner" else [] + return { + ODATA_CONTEXT: f"{GRAPH_URL}#teams('{team_id}')/members/$entity", + ODATA_TYPE: "#microsoft.graph.aadUserConversationMember", "id": self.newMemberTemplate.ID, "roles": roles, "displayName": self.newMemberTemplate.DisplayName, @@ -565,9 +496,8 @@ def get_add_member_response(self, team_id: str, request_json: dict) -> dict: "email": self.newMemberTemplate.Email, } - def get_update_member_role_response(self, team_id: str, channel_id: str, member_id: str, request_json: dict) -> dict | None: + def get_update_member_role_response(self, team_id: str, channel_id: str, member_id: str, request_json: dict) -> Optional[dict]: channel_members = self.members.get(team_id, {}).get(channel_id, []) - member = next((m for m in channel_members if m.ID == member_id), None) if not member: return None @@ -576,8 +506,8 @@ def get_update_member_role_response(self, team_id: str, channel_id: str, member_ member.Role = "owner" if "owner" in request_json["roles"] else "member" return { - "@odata.context": f"https://graph.microsoft.com/v1.0/$metadata#teams('{team_id}')/channels('{channel_id}')/members/$entity", - "@odata.type": "#microsoft.graph.aadUserConversationMember", + ODATA_CONTEXT: f"{GRAPH_URL}#teams('{team_id}')/channels('{channel_id}')/members/$entity", + ODATA_TYPE: "#microsoft.graph.aadUserConversationMember", "id": member.ID, "userId": member.UserID, "displayName": member.DisplayName, @@ -585,41 +515,23 @@ def get_update_member_role_response(self, team_id: str, channel_id: str, member_ "email": member.Email, } - def get_list_members_response(self, team_id: str, channel_id: str) -> dict: - return { - "@odata.context": f"https://graph.microsoft.com/v1.0/$metadata#teams('{team_id}')/channels('{channel_id}')/members", - "value": [ - { - "@odata.type": "#microsoft.graph.aadUserConversationMember", - "id": member.ID, - "userId": member.UserID, - "displayName": member.DisplayName, - "roles": [member.Role] if member.Role == "owner" else [], - "email": member.Email, - } - for member in self.members.get(team_id, {}).get(channel_id, []) - ], - } - def get_remove_member_response(self, team_id: str, channel_id: str, member_id: str) -> dict: channel_members = self.members.get(team_id, {}).get(channel_id, []) - member = next((m for m in channel_members if m.ID == member_id), None) if not member: return {"success": False} - channel_members.remove(member) return {"success": True} - def get_list_chats_response(self, chat_type: ChatType) -> dict: - if chat_type == ChatType.ONEONONE: - chats = self.oneonone_chats - else: - chats = self.group_chats + # ========================================== + # CHATS + # ========================================== + def get_list_chats_response(self, chat_type: ChatType) -> dict: + chats = self.oneonone_chats if chat_type == ChatType.ONEONONE else self.group_chats return { - "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#chats", - "@odata.count": len(chats), + ODATA_CONTEXT: f"{GRAPH_URL}#chats", + ODATA_COUNT: len(chats), "value": [ { "id": chat.ID, @@ -631,54 +543,41 @@ def get_list_chats_response(self, chat_type: ChatType) -> dict: ], } - def get_me_response(self) -> dict: - return { - "id": self.me.ID, - "displayName": self.me.DisplayName, - "email": self.me.Email, - } - def get_create_chat_response(self, request_json: dict) -> dict: - if request_json["chatType"] == "oneOnOne": - return { - "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#chats/$entity", - "id": self.newChatTemplate.ID, - "chatType": "oneOnOne", - "isHiddenForAllMembers": self.newChatTemplate.IsHidden, - "topic": None, - } - else: - return { - "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#chats/$entity", - "id": self.newChatTemplate.ID, - "chatType": "group", - "isHiddenForAllMembers": self.newChatTemplate.IsHidden, - "topic": self.newChatTemplate.Topic, - } + is_one_on_one = request_json["chatType"] == "oneOnOne" + response = { + ODATA_CONTEXT: f"{GRAPH_URL}#chats/$entity", + "id": self.newChatTemplate.ID, + "chatType": "oneOnOne" if is_one_on_one else "group", + "isHiddenForAllMembers": self.newChatTemplate.IsHidden, + "topic": None if is_one_on_one else self.newChatTemplate.Topic, + } + return response + def get_update_group_chat_topic_response(self, chat_id: str, request_json: dict) -> dict: + chat = next((c for c in self.group_chats if c.ID == chat_id), None) + if not chat: + return {} - def get_add_member_to_group_chat_response(self, chat_id: str, request_json: dict) -> dict: + chat.Topic = request_json.get("topic", chat.Topic) return { - "@odata.context": f"https://graph.microsoft.com/v1.0/$metadata#chats('{chat_id}')/members/$entity", - "@odata.type": "#microsoft.graph.aadUserConversationMember", - "id": self.newMemberTemplate.ID, - "roles": [self.newMemberTemplate.Role] if self.newMemberTemplate.Role == "owner" else [], - "displayName": self.newMemberTemplate.DisplayName, - "userId": self.newMemberTemplate.UserID, - "email": self.newMemberTemplate.Email, + ODATA_CONTEXT: f"{GRAPH_URL}#chats('{chat_id}')", + "id": chat.ID, + "chatType": "group", + "isHiddenForAllMembers": chat.IsHidden, + "topic": chat.Topic, } - def get_remove_member_from_group_chat_response(self, chat_id: str, member_id: str) -> dict: - return { - "success": True - } + # ========================================== + # CHAT MEMBERS + # ========================================== def get_list_group_chat_members_response(self, chat_id: str) -> dict: return { - "@odata.context": f"https://graph.microsoft.com/v1.0/$metadata#chats('{chat_id}')/members", + ODATA_CONTEXT: f"{GRAPH_URL}#chats('{chat_id}')/members", "value": [ { - "@odata.type": "#microsoft.graph.aadUserConversationMember", + ODATA_TYPE: "#microsoft.graph.aadUserConversationMember", "id": member.ID, "userId": member.UserID, "displayName": member.DisplayName, @@ -689,21 +588,24 @@ def get_list_group_chat_members_response(self, chat_id: str) -> dict: ], } - def get_update_group_chat_topic_response(self, chat_id: str, request_json: dict) -> dict: - chat = next((c for c in self.group_chats if c.ID == chat_id), None) - if not chat: - return {} - - chat.Topic = request_json.get("topic", chat.Topic) - + def get_add_member_to_group_chat_response(self, chat_id: str, request_json: dict) -> dict: return { - "@odata.context": f"https://graph.microsoft.com/v1.0/$metadata#chats('{chat_id}')", - "id": chat.ID, - "chatType": "group", - "isHiddenForAllMembers": chat.IsHidden, - "topic": chat.Topic, + ODATA_CONTEXT: f"{GRAPH_URL}#chats('{chat_id}')/members/$entity", + ODATA_TYPE: "#microsoft.graph.aadUserConversationMember", + "id": self.newMemberTemplate.ID, + "roles": [self.newMemberTemplate.Role] if self.newMemberTemplate.Role == "owner" else [], + "displayName": self.newMemberTemplate.DisplayName, + "userId": self.newMemberTemplate.UserID, + "email": self.newMemberTemplate.Email, } + def get_remove_member_from_group_chat_response(self, chat_id: str, member_id: str) -> dict: + return {"success": True} + + # ========================================== + # CHAT MESSAGES + # ========================================== + def get_list_messages_in_chat_response(self, chat_id: str) -> dict: return { "value": [ @@ -726,23 +628,7 @@ def get_list_messages_in_chat_response(self, chat_id: str) -> dict: ], } - def get_send_message_in_chat_response(self, chat_id: str, request_json: dict) -> dict: - return { - "id": self.newMessageTemplate.ID, - "body": { - "content": request_json.get("body", {}).get("content"), - "contentType": request_json.get("body", {}).get("contentType"), - }, - "from": { - "user": { - "id": self.newMessageTemplate.From.UserID, - "displayName": self.newMessageTemplate.From.DisplayName, - } - }, - "createdDateTime": self.newMessageTemplate.CreatedDateTime, - } - - def get_get_message_in_chat_response(self, chat_id: str, message_id: str) -> dict | None: + def get_get_message_in_chat_response(self, chat_id: str, message_id: str) -> Optional[dict]: message = next((m for m in self.chat_messages.get(chat_id, []) if m.ID == message_id), None) if not message: return None @@ -763,17 +649,34 @@ def get_get_message_in_chat_response(self, chat_id: str, message_id: str) -> dic "replies": [{"id": f"dummy-reply-{i}"} for i in range(message.ReplyCount)] } + def get_send_message_in_chat_response(self, chat_id: str, request_json: dict) -> dict: + return { + "id": self.newMessageTemplate.ID, + "body": { + "content": request_json.get("body", {}).get("content"), + "contentType": request_json.get("body", {}).get("contentType"), + }, + "from": { + "user": { + "id": self.newMessageTemplate.From.UserID, + "displayName": self.newMessageTemplate.From.DisplayName, + } + }, + "createdDateTime": self.newMessageTemplate.CreatedDateTime, + } + def get_all_messeges_in_chats_response(self) -> dict: + target_chat_id = self.group_chats[0].ID return { - "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#Collection(chatMessage)", - "@odata.count": len(self.chat_messages), + ODATA_CONTEXT: f"{GRAPH_URL}#Collection(chatMessage)", + ODATA_COUNT: len(self.chat_messages), "value": [ { - "@odata.type": "#microsoft.graph.chatMessage", + ODATA_TYPE: "#microsoft.graph.chatMessage", "id": message.ID, "etag": message.ID, "messageType": "message", - "chatId": self.group_chats[0].ID, + "chatId": target_chat_id, "body": { "content": message.Content, "contentType": message.ContentType, @@ -787,7 +690,7 @@ def get_all_messeges_in_chats_response(self) -> dict: "createdDateTime": message.CreatedDateTime, "lastModifiedDateTime": message.CreatedDateTime, } - for message in self.chat_messages[self.group_chats[0].ID] + for message in self.chat_messages[target_chat_id] ], } @@ -795,10 +698,20 @@ def get_list_pinned_messages_in_chat_response(self, chat_id: str) -> dict: return { "value": [ { - "@odata.type": "#microsoft.graph.pinnedChatMessageInfo", + ODATA_TYPE: "#microsoft.graph.pinnedChatMessageInfo", "id": message.ID, } for message in self.chat_messages.get(chat_id, []) ], } + # ========================================== + # USERS + # ========================================== + + def get_me_response(self) -> dict: + return { + "id": self.me.ID, + "displayName": self.me.DisplayName, + "email": self.me.Email, + } \ No newline at end of file diff --git a/python/tests/fake_server/setup.py b/python/tests/fake_server/setup.py index 979bbcf..9edbe3d 100644 --- a/python/tests/fake_server/setup.py +++ b/python/tests/fake_server/setup.py @@ -39,6 +39,10 @@ def make_log_response(request, data_dict: dict, context_msg: str = "") -> Respon status=status_code ) + # ========================================== + # USERS + # ========================================== + # GET /users/me/joinedTeams httpserver.expect_request( "/v1.0/users/me-token-to-replace/joinedTeams", method="GET" @@ -46,7 +50,7 @@ def make_log_response(request, data_dict: dict, context_msg: str = "") -> Respon req, data.get_myJoinedTeams_response(), "List Joined Teams" )) - #GET /users/me/chats/getAllMessages() + # GET /users/me/chats/getAllMessages() httpserver.expect_request( "/v1.0/users/me-token-to-replace/chats/getAllMessages()", method="GET" @@ -56,34 +60,26 @@ def make_log_response(request, data_dict: dict, context_msg: str = "") -> Respon "List All Chat Messages" )) - #GET /chats/{chat_id}/pinnedMessages + # GET users/me/chats/{chat_id}/messages httpserver.expect_request( - re.compile(r"^/v1.0/chats/([^/]+)/pinnedMessages"), + re.compile(r"^/v1.0/users/me-token-to-replace/chats/([^/]+)/messages"), method="GET" ).respond_with_handler(lambda req: make_log_response( req, - data.get_list_pinned_messages_in_chat_response( - re.search(r"/chats/([^/]+)/pinnedMessages", req.path).group(1) + data.get_list_messages_in_chat_response( + re.search(r"/users/me-token-to-replace/chats/([^/]+)/messages", req.path).group(1) ), - "List Pinned Chat Messages" + "List Chat Messages (me)" )) - # POST /chats/{chat_id}/pinnedMessages + # DELETE users/me/chats/{chat_id}/messages/{message_id}/softDelete httpserver.expect_request( - re.compile(r"^/v1.0/chats/([^/]+)/pinnedMessages"), + re.compile(r"/v1.0/users/[^/]+/chats/[^/]+/messages/[^/]+/softDelete"), method="POST" ).respond_with_response( Response(status=204) ) - # DELETE /chats/{chat_id}/pinnedMessages/{message_id} - httpserver.expect_request( - re.compile(r"^/v1.0/chats/([^/]+)/pinnedMessages/([^/]+)"), - method="DELETE" - ).respond_with_response( - Response(status=204) - ) - def handle_chats_request(req): filter_param = req.args.get('$filter', '') chat_type = None @@ -103,46 +99,6 @@ def handle_chats_request(req): f"List Chats ({chat_type})" ) - - - - #GET /chats/{chat_id}/messages/{message_id} - httpserver.expect_request( - re.compile(r"^/v1.0/chats/([^/]+)/messages/([^/]+)"), - method="GET" - ).respond_with_handler(lambda req: make_log_response( - req, - data.get_get_message_in_chat_response( - re.search(r"/chats/([^/]+)/messages/([^/]+)", req.path).group(1), - re.search(r"/chats/([^/]+)/messages/([^/]+)", req.path).group(2) - ), - "Get Chat Message" - )) - - #GET /chats/{chat_id}/messages - httpserver.expect_request( - re.compile(r"^/v1.0/chats/([^/]+)/messages"), - method="GET" - ).respond_with_handler(lambda req: make_log_response( - req, - data.get_list_messages_in_chat_response( - re.search(r"/chats/([^/]+)/messages", req.path).group(1) - ), - "List Chat Messages" - )) - - #GET users/me/chats/{chat_id}/messages - httpserver.expect_request( - re.compile(r"^/v1.0/users/me-token-to-replace/chats/([^/]+)/messages"), - method="GET" - ).respond_with_handler(lambda req: make_log_response( - req, - data.get_list_messages_in_chat_response( - re.search(r"/users/me-token-to-replace/chats/([^/]+)/messages", req.path).group(1) - ), - "List Chat Messages (me)" - )) - # GET /users/me/chats httpserver.expect_request( "/v1.0/users/me-token-to-replace/chats", @@ -159,90 +115,11 @@ def handle_chats_request(req): "Get Users" )) - #POST /chats - httpserver.expect_request( - "/v1.0/chats", - method="POST" - ).respond_with_handler(lambda req: make_log_response( - req, - data.get_create_chat_response(req.json), - "Create Chat" - )) - - #GET /chats/{chat_id}/members - httpserver.expect_request( - re.compile(r"^/v1.0/chats/([^/]+)/members"), - method="GET" - ).respond_with_handler(lambda req: make_log_response( - req, - data.get_list_group_chat_members_response( - re.search(r"/chats/([^/]+)/members", req.path).group(1) - ), - "List Chat Members" - )) - - #POST /chats/{chat_id}/members - httpserver.expect_request( - re.compile(r"^/v1.0/chats/([^/]+)/members"), - method="POST" - ).respond_with_handler(lambda req: make_log_response( - req, - data.get_add_member_to_group_chat_response( - re.search(r"/chats/([^/]+)/members", req.path).group(1), - req.json - ), - "Add Chat Member" - )) + # ========================================== + # TEAMS + # ========================================== - #DELETE /chats/{chat_id}/members/{member_id} - httpserver.expect_request( - re.compile(r"^/v1.0/chats/([^/]+)/members/([^/]+)"), - method="DELETE" - ).respond_with_handler(lambda req: make_log_response( - req, - data.get_remove_member_from_group_chat_response( - re.search(r"/chats/([^/]+)/members/([^/]+)", req.path).group(1), - re.search(r"/chats/([^/]+)/members/([^/]+)", req.path).group(2) - ), - "Remove Chat Member" - )) - - #PATCH /chats/{chat_id} - httpserver.expect_request( - re.compile(r"^/v1.0/chats/([^/]+)"), - method="PATCH" - ).respond_with_handler(lambda req: make_log_response( - req, - data.get_update_group_chat_topic_response( - re.search(r"/chats/([^/]+)", req.path).group(1), - req.json - ), - "Update Chat" - )) - - #POST /chats/{chat_id}/messages - httpserver.expect_request( - re.compile(r"^/v1.0/chats/([^/]+)/messages"), - method="POST" - ).respond_with_handler(lambda req: make_log_response( - req, - data.get_send_message_in_chat_response( - re.search(r"/chats/([^/]+)/messages", req.path).group(1), - req.json - ), - "Send Chat Message" - )) - - #DELETE users/me/chats/{chat_id}/messages/{message_id}/softDelete - httpserver.expect_request( - re.compile(r"/v1.0/users/[^/]+/chats/[^/]+/messages/[^/]+/softDelete"), - method="POST" - ).respond_with_response( - Response(status=204) - ) - - - #POST /teams/{team_id}/channels/{channel_id}/members + # POST /teams/{id}/channels/{channel_id}/members httpserver.expect_request( re.compile(r"^/v1.0/teams/([^/]+)/channels/([^/]+)/members"), method="POST" @@ -255,7 +132,6 @@ def handle_chats_request(req): "Add member to channel" )) - # PATCH /teams/{tid}/channels/{cid}/members/{mid} httpserver.expect_request( re.compile(r"^/v1.0/teams/([^/]+)/channels/([^/]+)/members/([^/]+)"), @@ -285,7 +161,7 @@ def handle_chats_request(req): "Remove Member from Channel" )) - #GET /teams/{team_id}/channels/{channel_id}/members + # GET /teams/{team_id}/channels/{channel_id}/members httpserver.expect_request( re.compile(r"^/v1.0/teams/([^/]+)/channels/([^/]+)/members"), method="GET" @@ -298,22 +174,6 @@ def handle_chats_request(req): "List channel members" )) - - #POST /teams/{team_id}/channels/{channel_id}/messages - httpserver.expect_request( - re.compile(r"^/v1.0/teams/([^/]+)/channels/([^/]+)/messages"), - method="POST" - ).respond_with_handler(lambda req: make_log_response( - req, - data.get_send_message_response( - re.search(r"/teams/([^/]+)/channels/([^/]+)/messages", req.path).group(1), - re.search(r"/teams/([^/]+)/channels/([^/]+)/messages", req.path).group(2), - req.json - ), - "Create Channel Message" - )) - - # GET /teams/{team_id}/channels/{channel_id}/messages/{message_id}/replies/{reply_id} httpserver.expect_request( re.compile(r"^/v1.0/teams/([^/]+)/channels/([^/]+)/messages/([^/]+)/replies/([^/]+)"), @@ -357,6 +217,20 @@ def handle_chats_request(req): "Get Channel Message" )) + # POST /teams/{team_id}/channels/{channel_id}/messages + httpserver.expect_request( + re.compile(r"^/v1.0/teams/([^/]+)/channels/([^/]+)/messages"), + method="POST" + ).respond_with_handler(lambda req: make_log_response( + req, + data.get_send_message_response( + re.search(r"/teams/([^/]+)/channels/([^/]+)/messages", req.path).group(1), + re.search(r"/teams/([^/]+)/channels/([^/]+)/messages", req.path).group(2), + req.json + ), + "Create Channel Message" + )) + # GET /teams/{team_id}/channels/{channel_id}/messages httpserver.expect_request( re.compile(r"^/v1.0/teams/([^/]+)/channels/([^/]+)/messages"), @@ -370,6 +244,19 @@ def handle_chats_request(req): "List Channel Messages" )) + # DELETE /teams/{team_id}/channels/{channel_id} + httpserver.expect_request( + re.compile(r"^/v1.0/teams/([^/]+)/channels/([^/]+)"), + method="DELETE" + ).respond_with_handler(lambda req: make_log_response( + req, + data.get_delete_channel_response( + re.search(r"/teams/([^/]+)/channels/([^/]+)", req.path).group(1), + re.search(r"/teams/([^/]+)/channels/([^/]+)", req.path).group(2) + ), + "Delete Channel (DELETE)" + )) + # GET /teams/{team_id}/channels/{channel_id} httpserver.expect_request( re.compile(r"^/v1.0/teams/([^/]+)/channels/([^/]+)"), @@ -383,44 +270,47 @@ def handle_chats_request(req): "Get Channel Details" )) - # GET /teams/{id}/channels + # POST /teams/{id}/channels httpserver.expect_request( re.compile(r"^/v1.0/teams/([^/]+)/channels"), - method="GET" + method="POST" ).respond_with_handler(lambda req: make_log_response( req, - data.get_listChannels_response(re.search(r"/teams/([^/]+)/channels", req.path).group(1)), - "List Channels" + data.get_create_channel_response( + re.search(r"/teams/([^/]+)/channels", req.path).group(1), + req.json + ), + "Create Channel (POST)" )) - # POST /groups + # GET /teams/{id}/channels httpserver.expect_request( - "/v1.0/groups", - method="POST" + re.compile(r"^/v1.0/teams/([^/]+)/channels"), + method="GET" ).respond_with_handler(lambda req: make_log_response( req, - data.get_createGroup_response(req.json), - "Create Group" + data.get_listChannels_response(re.search(r"/teams/([^/]+)/channels", req.path).group(1)), + "List Channels" )) - # PUT /groups/{id}/team + # POST /teams/{id}/archive httpserver.expect_request( - re.compile(r"^/v1.0/groups/([^/]+)/team"), - method="PUT" + re.compile(r"^/v1.0/teams/([^/]+)/archive"), + method="POST" ).respond_with_handler(lambda req: make_log_response( req, - data.get_createTeamViaGroup_response(re.search(r"/groups/([^/]+)/team", req.path).group(1)), - "Teamify Group (PUT)" + data.get_archiveTeam_response(re.search(r"/teams/([^/]+)/archive", req.path).group(1)), + "Archive Team (POST)" )) - # GET /teams/{id} + # POST /teams/{id}/unarchive httpserver.expect_request( - re.compile(r"^/v1.0/teams/([^/]+)(?:$|\?)"), - method="GET" + re.compile(r"^/v1.0/teams/([^/]+)/unarchive"), + method="POST" ).respond_with_handler(lambda req: make_log_response( req, - data.get_team_response(re.search(r"/teams/([^/?]+)", req.path).group(1)), - "Get Team Details" + data.get_unarchiveTeam_response(re.search(r"/teams/([^/]+)/unarchive", req.path).group(1)), + "Unarchive Team (POST)" )) # PATCH /teams/{id} @@ -436,37 +326,41 @@ def handle_chats_request(req): "Update Team (PATCH)" )) - # POST /teams + # GET /teams/{id} httpserver.expect_request( - "/v1.0/teams", - method="POST" + re.compile(r"^/v1.0/teams/([^/]+)(?:$|\?)"), + method="GET" ).respond_with_handler(lambda req: make_log_response( req, - data.get_createTeamFromTemplate_response(req.json), - "Create Team from Template (POST)" + data.get_team_response(re.search(r"/teams/([^/?]+)", req.path).group(1)), + "Get Team Details" )) - # POST /teams/{id}/archive + # POST /teams httpserver.expect_request( - re.compile(r"^/v1.0/teams/([^/]+)/archive"), + "/v1.0/teams", method="POST" ).respond_with_handler(lambda req: make_log_response( req, - data.get_archiveTeam_response(re.search(r"/teams/([^/]+)/archive", req.path).group(1)), - "Archive Team (POST)" + data.get_createTeamFromTemplate_response(req.json), + "Create Team from Template (POST)" )) - # POST /teams/{id}/unarchive + # ========================================== + # GROUPS + # ========================================== + + # PUT /groups/{id}/team httpserver.expect_request( - re.compile(r"^/v1.0/teams/([^/]+)/unarchive"), - method="POST" + re.compile(r"^/v1.0/groups/([^/]+)/team"), + method="PUT" ).respond_with_handler(lambda req: make_log_response( req, - data.get_unarchiveTeam_response(re.search(r"/teams/([^/]+)/unarchive", req.path).group(1)), - "Unarchive Team (POST)" + data.get_createTeamViaGroup_response(re.search(r"/groups/([^/]+)/team", req.path).group(1)), + "Teamify Group (PUT)" )) - # DELETE /teams/{id}/delete + # DELETE /groups/{id} (Delete Team uses group endpoint) httpserver.expect_request( re.compile(r"^/v1.0/groups/([^/]+)"), method="DELETE" @@ -476,7 +370,21 @@ def handle_chats_request(req): "Delete Team (DELETE)" )) - # POST /teams/{id}/restore + # POST /groups + httpserver.expect_request( + "/v1.0/groups", + method="POST" + ).respond_with_handler(lambda req: make_log_response( + req, + data.get_createGroup_response(req.json), + "Create Group" + )) + + # ========================================== + # DIRECTORY / OTHER + # ========================================== + + # POST /directory/deletedItems/{id}/restore httpserver.expect_request( re.compile(r"^/v1.0/directory/deletedItems/([^/]+)/restore"), method="POST" @@ -486,33 +394,136 @@ def handle_chats_request(req): "Restore Deleted Team (POST)" )) - # POST /teams/{id}/channels + # ========================================== + # CHATS + # ========================================== + + # DELETE /chats/{chat_id}/pinnedMessages/{message_id} httpserver.expect_request( - re.compile(r"^/v1.0/teams/([^/]+)/channels"), + re.compile(r"^/v1.0/chats/([^/]+)/pinnedMessages/([^/]+)"), + method="DELETE" + ).respond_with_response( + Response(status=204) + ) + + # POST /chats/{chat_id}/pinnedMessages + httpserver.expect_request( + re.compile(r"^/v1.0/chats/([^/]+)/pinnedMessages"), method="POST" + ).respond_with_response( + Response(status=204) + ) + + # GET /chats/{chat_id}/pinnedMessages + httpserver.expect_request( + re.compile(r"^/v1.0/chats/([^/]+)/pinnedMessages"), + method="GET" ).respond_with_handler(lambda req: make_log_response( req, - data.get_create_channel_response( - re.search(r"/teams/([^/]+)/channels", req.path).group(1), - req.json + data.get_list_pinned_messages_in_chat_response( + re.search(r"/chats/([^/]+)/pinnedMessages", req.path).group(1) ), - "Create Channel (POST)" + "List Pinned Chat Messages" )) - # DELETE /teams/{team_id}/channels/{channel_id} + # DELETE /chats/{chat_id}/members/{member_id} httpserver.expect_request( - re.compile(r"^/v1.0/teams/([^/]+)/channels/([^/]+)"), + re.compile(r"^/v1.0/chats/([^/]+)/members/([^/]+)"), method="DELETE" ).respond_with_handler(lambda req: make_log_response( req, - data.get_delete_channel_response( - re.search(r"/teams/([^/]+)/channels/([^/]+)", req.path).group(1), - re.search(r"/teams/([^/]+)/channels/([^/]+)", req.path).group(2) + data.get_remove_member_from_group_chat_response( + re.search(r"/chats/([^/]+)/members/([^/]+)", req.path).group(1), + re.search(r"/chats/([^/]+)/members/([^/]+)", req.path).group(2) ), - "Delete Channel (DELETE)" + "Remove Chat Member" + )) + + # POST /chats/{chat_id}/members + httpserver.expect_request( + re.compile(r"^/v1.0/chats/([^/]+)/members"), + method="POST" + ).respond_with_handler(lambda req: make_log_response( + req, + data.get_add_member_to_group_chat_response( + re.search(r"/chats/([^/]+)/members", req.path).group(1), + req.json + ), + "Add Chat Member" + )) + + # GET /chats/{chat_id}/members + httpserver.expect_request( + re.compile(r"^/v1.0/chats/([^/]+)/members"), + method="GET" + ).respond_with_handler(lambda req: make_log_response( + req, + data.get_list_group_chat_members_response( + re.search(r"/chats/([^/]+)/members", req.path).group(1) + ), + "List Chat Members" + )) + + # GET /chats/{chat_id}/messages/{message_id} + httpserver.expect_request( + re.compile(r"^/v1.0/chats/([^/]+)/messages/([^/]+)"), + method="GET" + ).respond_with_handler(lambda req: make_log_response( + req, + data.get_get_message_in_chat_response( + re.search(r"/chats/([^/]+)/messages/([^/]+)", req.path).group(1), + re.search(r"/chats/([^/]+)/messages/([^/]+)", req.path).group(2) + ), + "Get Chat Message" + )) + + # POST /chats/{chat_id}/messages + httpserver.expect_request( + re.compile(r"^/v1.0/chats/([^/]+)/messages"), + method="POST" + ).respond_with_handler(lambda req: make_log_response( + req, + data.get_send_message_in_chat_response( + re.search(r"/chats/([^/]+)/messages", req.path).group(1), + req.json + ), + "Send Chat Message" + )) + + # GET /chats/{chat_id}/messages + httpserver.expect_request( + re.compile(r"^/v1.0/chats/([^/]+)/messages"), + method="GET" + ).respond_with_handler(lambda req: make_log_response( + req, + data.get_list_messages_in_chat_response( + re.search(r"/chats/([^/]+)/messages", req.path).group(1) + ), + "List Chat Messages" )) + # PATCH /chats/{chat_id} + httpserver.expect_request( + re.compile(r"^/v1.0/chats/([^/]+)"), + method="PATCH" + ).respond_with_handler(lambda req: make_log_response( + req, + data.get_update_group_chat_topic_response( + re.search(r"/chats/([^/]+)", req.path).group(1), + req.json + ), + "Update Chat" + )) + # POST /chats + httpserver.expect_request( + "/v1.0/chats", + method="POST" + ).respond_with_handler(lambda req: make_log_response( + req, + data.get_create_chat_response(req.json), + "Create Chat" + )) # Fallback for unmatched routes httpserver.expect_request(re.compile(".*")).respond_with_handler( From 26c325cf0e8c8c7f8425a3d8dd00ce5f69606d37 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Wed, 7 Jan 2026 21:21:40 +0100 Subject: [PATCH 54/99] feat: commented and formatted channels service --- .../teams_lib_pzsp2_z1/services/channels.py | 300 ++++++++++++++---- python/tests/test_channels.py | 70 ++-- 2 files changed, 276 insertions(+), 94 deletions(-) diff --git a/python/teams_lib_pzsp2_z1/services/channels.py b/python/teams_lib_pzsp2_z1/services/channels.py index b2bd3d8..0f025b4 100644 --- a/python/teams_lib_pzsp2_z1/services/channels.py +++ b/python/teams_lib_pzsp2_z1/services/channels.py @@ -1,3 +1,7 @@ +""" +This module provides the ChannelsService class for managing Microsoft Teams channels. +""" + from teams_lib_pzsp2_z1.model.channel import Channel from teams_lib_pzsp2_z1.model.member import Member from teams_lib_pzsp2_z1.model.mention import Mention @@ -11,12 +15,35 @@ class ChannelsService(BaseService): - def list_channels(self, teamRef: str) -> list[Channel]: + """Service for managing Microsoft Teams channels via the Go backend. + + This class acts as a high-level Python wrapper. It delegates logical operations + to the underlying Go library through the `self.client.execute` bridge. + + **Architecture & Caching:** + The decision to use caching or direct API calls is handled internally by the + compiled Go library, based on the configuration provided during Client initialization. + This Python class provides a unified interface regardless of the underlying mode. + + **Concepts:** + * **References**: Arguments like `team_ref` or `channel_ref` accept either UUIDs + or Display Names. The Go backend resolves these automatically. + * **Identity**: Users are identified by UUID or Email. + """ + + def list_channels(self, team_ref: str) -> list[Channel]: + """Retrieves all channels associated with a specific team. + + Args: + team_ref (str): The reference to the team (UUID or Display Name). + Returns: + List[Channel]: A list of channel objects found in the team. + """ response = self.client.execute( cmd_type="request", method="listChannels", params={ - "teamRef": teamRef, + "teamRef": team_ref, }, ) return [ @@ -28,13 +55,25 @@ def list_channels(self, teamRef: str) -> list[Channel]: for channel in response ] - def get(self, teamRef: str, channelRef: str) -> Channel: + def get(self, team_ref: str, channel_ref: str) -> Channel: + """Retrieves a specific channel by its reference within a team. + + Args: + team_ref (str): The reference to the team (UUID or Display Name). + channel_ref (str): The reference to the channel (ID or Display Name). + + Returns: + Channel: The requested channel object. + + Raises: + Exception: If the reference is ambiguous or not found (propagated from Go). + """ response = self.client.execute( cmd_type="request", method="getChannel", params={ - "teamRef": teamRef, - "channelRef": channelRef, + "teamRef": team_ref, + "channelRef": channel_ref, }, ) @@ -44,12 +83,21 @@ def get(self, teamRef: str, channelRef: str) -> Channel: IsGeneral=(True if response["IsGeneral"] else False), ) - def create_standard(self, teamRef: str, display_name: str) -> Channel: + def create_standard(self, team_ref: str, display_name: str) -> Channel: + """Creates a standard (public) channel within a team. + + Args: + team_ref (str): The reference to the team. + display_name (str): The display name for the new channel. + + Returns: + Channel: The newly created channel object. + """ response = self.client.execute( cmd_type="request", method="createStandardChannel", params={ - "teamRef": teamRef, + "teamRef": team_ref, "name": display_name, }, ) @@ -62,16 +110,28 @@ def create_standard(self, teamRef: str, display_name: str) -> Channel: def create_private( self, - teamRef: str, + team_ref: str, display_name: str, member_refs: list[str], owner_refs: list[str], ) -> Channel: + """Creates a private channel restricted to specific members. + + Args: + team_ref (str): The reference to the team. + display_name (str): The display name for the new private channel. + member_refs (list[str]): List of user references (IDs or emails) to add as members. + owner_refs (list[str]): List of user references to add as owners. + At least one owner is required. + + Returns: + Channel: The newly created private channel object. + """ response = self.client.execute( cmd_type="request", method="createPrivateChannel", params={ - "teamRef": teamRef, + "teamRef": team_ref, "name": display_name, "memberRefs": member_refs, "ownerRefs": owner_refs, @@ -84,24 +144,46 @@ def create_private( IsGeneral=(True if response["IsGeneral"] else False), ) - def delete(self, teamRef: str, channelRef: str) -> bool: + def delete(self, team_ref: str, channel_ref: str) -> bool: + """Removes a channel from a team. + + Args: + team_ref (str): The reference to the team. + channel_ref (str): The reference to the channel to be deleted. + + Returns: + bool: True if the operation was successful. + """ response = self.client.execute( cmd_type="request", method="deleteChannel", params={ - "teamRef": teamRef, - "channelRef": channelRef, + "teamRef": team_ref, + "channelRef": channel_ref, }, ) return response == "deleted" - def send_message(self, teamRef: str, channelRef: str, body: MessageBody) -> Message: + def send_message( + self, team_ref: str, channel_ref: str, body: MessageBody + ) -> Message: + """Sends a new message to a channel. + + Args: + team_ref (str): The reference to the team. + channel_ref (str): The reference to the channel. + body (MessageBody): The message payload containing content, content type + (Text/HTML), and optional mentions. + + Returns: + Message: The created message object. + """ response = self.client.execute( cmd_type="request", method="sendMessageToChannel", params={ - "teamRef": teamRef, - "channelRef": channelRef, + "teamRef": team_ref, + "channelRef": channel_ref, "body": dict(body), }, ) @@ -120,17 +202,28 @@ def send_message(self, teamRef: str, channelRef: str, body: MessageBody) -> Mess def list_messages( self, - teamRef: str, - channelRef: str, + team_ref: str, + channel_ref: str, top: int | None = 10, expand_replies: bool = False, - ): + ) -> list[Message]: + """Retrieves messages from a channel. + + Args: + team_ref (str): The reference to the team. + channel_ref (str): The reference to the channel. + top (Optional[int]): The maximum number of messages to retrieve. Defaults to 10. + expand_replies (bool): If True, system fetches replies for each message. Defaults to False. + + Returns: + List[Message]: A list of messages from the channel. + """ response = self.client.execute( cmd_type="request", method="listMessagesInChannel", params={ - "teamRef": teamRef, - "channelRef": channelRef, + "teamRef": team_ref, + "channelRef": channel_ref, "options": { "top": top, "expandReplies": expand_replies, @@ -153,14 +246,24 @@ def list_messages( for message in response ] - def get_message(self, teamRef: str, channelRef: str, messageID: str) -> Message: + def get_message(self, team_ref: str, channel_ref: str, message_id: str) -> Message: + """Retrieves a specific message by its ID. + + Args: + team_ref (str): The reference to the team. + channel_ref (str): The reference to the channel. + message_id (str): The unique identifier of the message. + + Returns: + Message: The requested message object. + """ response = self.client.execute( cmd_type="request", method="getMessageInChannel", params={ - "teamRef": teamRef, - "channelRef": channelRef, - "messageID": messageID, + "teamRef": team_ref, + "channelRef": channel_ref, + "messageID": message_id, }, ) @@ -178,18 +281,29 @@ def get_message(self, teamRef: str, channelRef: str, messageID: str) -> Message: def list_message_replies( self, - teamRef: str, - channelRef: str, - messageID: str, + team_ref: str, + channel_ref: str, + message_id: str, top: int | None = 10, - ): + ) -> list[Message]: + """Retrieves all replies to a specific message thread. + + Args: + team_ref (str): The reference to the team. + channel_ref (str): The reference to the channel. + message_id (str): The ID of the parent message. + top (Optional[int]): Max number of replies to fetch. Defaults to 10. + + Returns: + List[Message]: A list of replies. + """ response = self.client.execute( cmd_type="request", method="listMessageRepliesInChannel", params={ - "teamRef": teamRef, - "channelRef": channelRef, - "messageID": messageID, + "teamRef": team_ref, + "channelRef": channel_ref, + "messageID": message_id, "top": top, }, ) @@ -211,19 +325,30 @@ def list_message_replies( def get_message_reply( self, - teamRef: str, - channelRef: str, - messageID: str, - replyID: str, + team_ref: str, + channel_ref: str, + message_id: str, + reply_id: str, ) -> Message: + """Retrieves a specific reply from a thread. + + Args: + team_ref (str): The reference to the team. + channel_ref (str): The reference to the channel. + message_id (str): The ID of the parent message. + reply_id (str): The ID of the reply to retrieve. + + Returns: + Message: The requested reply object. + """ response = self.client.execute( cmd_type="request", method="getMessageReplyInChannel", params={ - "teamRef": teamRef, - "channelRef": channelRef, - "messageID": messageID, - "replyID": replyID, + "teamRef": team_ref, + "channelRef": channel_ref, + "messageID": message_id, + "replyID": reply_id, }, ) @@ -239,68 +364,125 @@ def get_message_reply( ReplyCount=response["ReplyCount"], ) - def list_members(self, teamRef: str, channelRef: str) -> list[Member]: + def list_members(self, team_ref: str, channel_ref: str) -> list[Member]: + """Lists all members of a channel. + + Args: + team_ref (str): The reference to the team. + channel_ref (str): The reference to the channel. + + Returns: + List[Member]: A list of channel members. + """ response = self.client.execute( cmd_type="request", method="listChannelMembers", params={ - "teamRef": teamRef, - "channelRef": channelRef, + "teamRef": team_ref, + "channelRef": channel_ref, }, ) return [Member(**member) for member in response] def add_member( - self, teamRef: str, channelRef: str, userRef: str, isOwner: bool + self, team_ref: str, channel_ref: str, user_ref: str, is_owner: bool ) -> Member: + """Adds a user to a channel. + + Args: + team_ref (str): The reference to the team. + channel_ref (str): The reference to the channel. + user_ref (str): The user to add (User ID or Email). + is_owner (bool): Whether to grant Owner privileges. + + Returns: + Member: The newly added member object. + """ response = self.client.execute( cmd_type="request", method="addMemberToChannel", params={ - "teamRef": teamRef, - "channelRef": channelRef, - "userRef": userRef, - "isOwner": isOwner, + "teamRef": team_ref, + "channelRef": channel_ref, + "userRef": user_ref, + "isOwner": is_owner, }, ) return Member(**response) def update_member_role( - self, teamRef: str, channelRef: str, userRef: str, isOwner: bool + self, team_ref: str, channel_ref: str, user_ref: str, is_owner: bool ) -> Member: + """Updates the role of an existing channel member. + + Args: + team_ref (str): The reference to the team. + channel_ref (str): The reference to the channel. + user_ref (str): The user reference (ID or Email). + is_owner (bool): True for Owner role, False for Member role. + + Returns: + Member: The updated member object. + """ response = self.client.execute( cmd_type="request", method="updateMemberRoleInChannel", params={ - "teamRef": teamRef, - "channelRef": channelRef, - "userRef": userRef, - "isOwner": isOwner, + "teamRef": team_ref, + "channelRef": channel_ref, + "userRef": user_ref, + "isOwner": is_owner, }, ) return Member(**response) - def remove_member(self, teamRef: str, channelRef: str, userRef: str) -> bool: + def remove_member(self, team_ref: str, channel_ref: str, user_ref: str) -> bool: + """Removes a user from a channel. + + Args: + team_ref (str): The reference to the team. + channel_ref (str): The reference to the channel. + user_ref (str): The user reference (ID or Email) to remove. + + Returns: + bool: True if the operation was successful. + """ response = self.client.execute( cmd_type="request", method="removeMemberFromChannel", params={ - "teamRef": teamRef, - "channelRef": channelRef, - "userRef": userRef, + "teamRef": team_ref, + "channelRef": channel_ref, + "userRef": user_ref, }, ) return response == "removed" def get_mentions( - self, teamRef: str, channelRef: str, raw_mentions: list[str] + self, team_ref: str, channel_ref: str, raw_mentions: list[str] ) -> list[Mention]: + """Resolves raw mention strings into formal Mention objects. + + This is used to construct the `mentions` field for messages before sending. + + Args: + team_ref (str): The reference to the team. + channel_ref (str): The reference to the channel. + raw_mentions (List[str]): A list of raw strings to resolve. + Supported formats: + * **Email/User ID**: Resolves to a specific user. + * **'channel'**: Mentions the current channel. + * **'team'**: Mentions the parent team. + + Returns: + List[Mention]: A list of resolved Mention objects ready for the API. + """ response = self.client.execute( cmd_type="request", method="getMentionsInChannel", params={ - "teamRef": teamRef, - "channelRef": channelRef, + "teamRef": team_ref, + "channelRef": channel_ref, "rawMentions": raw_mentions, }, ) diff --git a/python/tests/test_channels.py b/python/tests/test_channels.py index 74135e7..89d4f66 100644 --- a/python/tests/test_channels.py +++ b/python/tests/test_channels.py @@ -42,8 +42,8 @@ def test_get_channel_integration(httpserver): init_fake_client(client, httpserver.url_for("")) channel = client.channels.get( - teamRef=data.teams[0].DisplayName, - channelRef=data.channels[data.teams[0].ID][1].Name, + team_ref=data.teams[0].DisplayName, + channel_ref=data.channels[data.teams[0].ID][1].Name, ) assert channel.Name == data.channels[data.teams[0].ID][1].Name @@ -63,7 +63,7 @@ def test_create_standard_channel_integration(httpserver): init_fake_client(client, httpserver.url_for("")) channel = client.channels.create_standard( - teamRef=data.teams[0].DisplayName, + team_ref=data.teams[0].DisplayName, display_name=data.newChannelName, ) @@ -84,7 +84,7 @@ def test_create_private_channel_integration(httpserver): init_fake_client(client, httpserver.url_for("")) channel = client.channels.create_private( - teamRef=data.teams[0].DisplayName, + team_ref=data.teams[0].DisplayName, display_name=data.newChannelName, member_refs=[], owner_refs=[], @@ -107,8 +107,8 @@ def test_delete_channel_integration(httpserver): init_fake_client(client, httpserver.url_for("")) success = client.channels.delete( - teamRef=data.teams[0].DisplayName, - channelRef=data.channels[data.teams[0].ID][1].Name, + team_ref=data.teams[0].DisplayName, + channel_ref=data.channels[data.teams[0].ID][1].Name, ) assert success is True @@ -126,8 +126,8 @@ def test_send_message_integration(httpserver): init_fake_client(client, httpserver.url_for("")) message = client.channels.send_message( - teamRef=data.teams[0].DisplayName, - channelRef=data.channels[data.teams[0].ID][0].Name, + team_ref=data.teams[0].DisplayName, + channel_ref=data.channels[data.teams[0].ID][0].Name, body=MessageBody( Content=data.newMessageTemplate.Content, ContentType=MessageContentType.TEXT, @@ -156,8 +156,8 @@ def test_list_messages_integration(httpserver): init_fake_client(client, httpserver.url_for("")) messages = client.channels.list_messages( - teamRef=data.teams[0].DisplayName, - channelRef=data.channels[data.teams[0].ID][0].Name, + team_ref=data.teams[0].DisplayName, + channel_ref=data.channels[data.teams[0].ID][0].Name, ) assert len(messages) == len(data.messages[data.channels[data.teams[0].ID][0].ID]) @@ -190,9 +190,9 @@ def test_get_message_integration(httpserver): init_fake_client(client, httpserver.url_for("")) message = client.channels.get_message( - teamRef=data.teams[0].DisplayName, - channelRef=data.channels[data.teams[0].ID][0].Name, - messageID=data.messages[data.channels[data.teams[0].ID][0].ID][0].ID, + team_ref=data.teams[0].DisplayName, + channel_ref=data.channels[data.teams[0].ID][0].Name, + message_id=data.messages[data.channels[data.teams[0].ID][0].ID][0].ID, ) assert message.ID == data.messages[data.channels[data.teams[0].ID][0].ID][0].ID @@ -216,9 +216,9 @@ def test_list_replies_integration(httpserver): init_fake_client(client, httpserver.url_for("")) replies = client.channels.list_message_replies( - teamRef=data.teams[0].DisplayName, - channelRef=data.channels[data.teams[0].ID][0].Name, - messageID=data.messages[data.channels[data.teams[0].ID][0].ID][1].ID, + team_ref=data.teams[0].DisplayName, + channel_ref=data.channels[data.teams[0].ID][0].Name, + message_id=data.messages[data.channels[data.teams[0].ID][0].ID][1].ID, ) assert len(replies) == len(data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID]) @@ -249,10 +249,10 @@ def test_get_reply_integration(httpserver): init_fake_client(client, httpserver.url_for("")) reply = client.channels.get_message_reply( - teamRef=data.teams[0].DisplayName, - channelRef=data.channels[data.teams[0].ID][0].Name, - messageID=data.messages[data.channels[data.teams[0].ID][0].ID][1].ID, - replyID=data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][0].ID, + team_ref=data.teams[0].DisplayName, + channel_ref=data.channels[data.teams[0].ID][0].Name, + message_id=data.messages[data.channels[data.teams[0].ID][0].ID][1].ID, + reply_id=data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][0].ID, ) assert reply.ID == data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][0].ID @@ -278,8 +278,8 @@ def test_list_members_integration(httpserver): channel_id = channel.ID members = client.channels.list_members( - teamRef=data.teams[0].DisplayName, - channelRef=channel.Name, + team_ref=data.teams[0].DisplayName, + channel_ref=channel.Name, ) expected_members = data.members[team_id][channel_id] @@ -313,10 +313,10 @@ def test_add_member_integration(httpserver): init_fake_client(client, httpserver.url_for("")) member = client.channels.add_member( - teamRef=data.teams[0].DisplayName, - channelRef=data.channels[data.teams[0].ID][1].Name, - userRef=data.newMemberTemplate.DisplayName, - isOwner=True if data.newMemberTemplate.Role == "owner" else False, + team_ref=data.teams[0].DisplayName, + channel_ref=data.channels[data.teams[0].ID][1].Name, + user_ref=data.newMemberTemplate.DisplayName, + is_owner=True if data.newMemberTemplate.Role == "owner" else False, ) assert member.UserID == data.newMemberTemplate.UserID @@ -338,10 +338,10 @@ def test_update_member_role_integration(httpserver): init_fake_client(client, httpserver.url_for("")) member = client.channels.update_member_role( - teamRef=data.teams[0].DisplayName, - channelRef=data.channels[data.teams[0].ID][0].Name, - userRef=data.members[data.teams[0].ID][data.channels[data.teams[0].ID][0].ID][1].Email, - isOwner=True + team_ref=data.teams[0].DisplayName, + channel_ref=data.channels[data.teams[0].ID][0].Name, + user_ref=data.members[data.teams[0].ID][data.channels[data.teams[0].ID][0].ID][1].Email, + is_owner=True ) assert member.UserID == data.members[data.teams[0].ID][data.channels[data.teams[0].ID][0].ID][1].UserID @@ -362,9 +362,9 @@ def test_remove_member_integration(httpserver): init_fake_client(client, httpserver.url_for("")) success = client.channels.remove_member( - teamRef=data.teams[0].DisplayName, - channelRef=data.channels[data.teams[0].ID][0].Name, - userRef=data.members[data.teams[0].ID][data.channels[data.teams[0].ID][0].ID][1].Email, + team_ref=data.teams[0].DisplayName, + channel_ref=data.channels[data.teams[0].ID][0].Name, + user_ref=data.members[data.teams[0].ID][data.channels[data.teams[0].ID][0].ID][1].Email, ) assert success is True @@ -381,8 +381,8 @@ def test_get_mention_integration(httpserver): init_fake_client(client, httpserver.url_for("")) mention = client.channels.get_mentions( - teamRef=data.teams[0].DisplayName, - channelRef=data.channels[data.teams[0].ID][0].Name, + team_ref=data.teams[0].DisplayName, + channel_ref=data.channels[data.teams[0].ID][0].Name, raw_mentions=MentionKind.TEAM.value, ) From 19b9f1f370408942a672d982bb107f6b78103a22 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Wed, 7 Jan 2026 21:27:40 +0100 Subject: [PATCH 55/99] feat: added docstrings for chat service --- python/teams_lib_pzsp2_z1/services/chats.py | 176 ++++++++++++++++++++ 1 file changed, 176 insertions(+) diff --git a/python/teams_lib_pzsp2_z1/services/chats.py b/python/teams_lib_pzsp2_z1/services/chats.py index dab5f1b..86e53a1 100644 --- a/python/teams_lib_pzsp2_z1/services/chats.py +++ b/python/teams_lib_pzsp2_z1/services/chats.py @@ -1,3 +1,7 @@ +""" +This module contains the service for managing Microsoft Teams chats via the Go backend. +""" + from datetime import datetime from teams_lib_pzsp2_z1.model.chat import Chat, ChatRef, ChatType @@ -13,7 +17,34 @@ class ChatsService(BaseService): + """Service for managing Microsoft Teams chats via the Go backend. + + This class acts as a high-level wrapper, delegating operations to the + underlying Go library. It handles One-on-One chats, Group chats, messages, + and membership management. + + **Concepts:** + * **Chat Types**: Chats are distinct entities, either **One-on-One** (between two users) + or **Group** (multiple users, has a topic). + * **References**: + * `recipient_ref` / `user_ref`: Can be a User ID (UUID) or an Email. + * `group_chat_ref`: Can be the Chat ID or the Group Topic (if unique). + * `chat_ref` (Object): A specific model `ChatRef` containing the ID/Name and the `ChatType`. + * **Permissions**: Some operations (like `list_all_messages`) require Application Permissions + and will not work in Delegated (user context) mode. + """ + def create_one_on_one(self, recipient_ref: str) -> Chat: + """Creates a One-on-One chat with a specific user. + + The authenticated user is automatically added to the chat. + + Args: + recipient_ref (str): The reference to the other user (User ID or Email). + + Returns: + Chat: The created chat object. + """ response = self.client.execute( cmd_type="request", method="createOneOnOneChat", @@ -32,6 +63,16 @@ def create_one_on_one(self, recipient_ref: str) -> Chat: def create_group_chat( self, recipient_refs: list[str], topic: str, include_me: bool ) -> Chat: + """Creates a Group Chat with multiple participants. + + Args: + recipient_refs (list[str]): A list of user references (IDs or Emails) to include. + topic (str): The subject/topic of the group chat. + include_me (bool): If True, the authenticated user is added to the group. + + Returns: + Chat: The created group chat object. + """ response = self.client.execute( cmd_type="request", method="createGroupChat", @@ -50,6 +91,15 @@ def create_group_chat( ) def add_member_to_group_chat(self, group_chat_ref: str, user_ref: str) -> Member: + """Adds a user to an existing Group Chat. + + Args: + group_chat_ref (str): The reference to the group chat (ID or Topic). + user_ref (str): The user to add (User ID or Email). + + Returns: + Member: The newly added member details. + """ member = self.client.execute( cmd_type="request", method="addMemberToGroupChat", @@ -70,6 +120,15 @@ def add_member_to_group_chat(self, group_chat_ref: str, user_ref: str) -> Member def remove_member_from_group_chat( self, group_chat_ref: str, member_ref: str ) -> bool: + """Removes a member from a Group Chat. + + Args: + group_chat_ref (str): The reference to the group chat (ID or Topic). + member_ref (str): The user reference to remove (User ID or Email). + + Returns: + bool: True if the member was successfully removed. + """ result = self.client.execute( cmd_type="request", method="removeMemberFromGroupChat", @@ -82,6 +141,14 @@ def remove_member_from_group_chat( return result == "removed" def list_group_chat_members(self, group_chat_ref: str) -> list[Member]: + """Lists all members of a Group Chat. + + Args: + group_chat_ref (str): The reference to the group chat (ID or Topic). + + Returns: + list[Member]: A list of members in the chat. + """ members = self.client.execute( cmd_type="request", method="listMembersInGroupChat", @@ -102,6 +169,15 @@ def list_group_chat_members(self, group_chat_ref: str) -> list[Member]: ] def update_group_chat_topic(self, group_chat_ref: str, new_topic: str) -> Chat: + """Updates the topic of a Group Chat. + + Args: + group_chat_ref (str): The reference to the group chat (ID or old Topic). + new_topic (str): The new topic string. + + Returns: + Chat: The updated chat object. + """ response = self.client.execute( cmd_type="request", method="updateGroupChatTopic", @@ -119,6 +195,14 @@ def update_group_chat_topic(self, group_chat_ref: str, new_topic: str) -> Chat: ) def list_messages(self, chat_ref: ChatRef) -> list[Message]: + """Retrieves messages from a specific chat. + + Args: + chat_ref (ChatRef): The chat reference object containing the ID/Name and ChatType. + + Returns: + list[Message]: A list of messages from the chat history. + """ messages = self.client.execute( cmd_type="request", method="listMessagesInChat", @@ -146,6 +230,15 @@ def list_messages(self, chat_ref: ChatRef) -> list[Message]: ] def send_message(self, chat_ref: ChatRef, body: MessageBody) -> Message: + """Sends a message to a chat. + + Args: + chat_ref (ChatRef): The chat reference object. + body (MessageBody): The message payload (content, type, mentions). + + Returns: + Message: The created message object. + """ message = self.client.execute( cmd_type="request", method="sendMessageInChat", @@ -174,6 +267,17 @@ def send_message(self, chat_ref: ChatRef, body: MessageBody) -> Message: ) def delete_message(self, chat_ref: ChatRef, message_id: str) -> bool: + """Soft-deletes a message from a chat. + + This action is reversible via the Graph API (though not exposed here). + + Args: + chat_ref (ChatRef): The chat reference object. + message_id (str): The unique identifier of the message to delete. + + Returns: + bool: True if the message was successfully deleted. + """ result = self.client.execute( cmd_type="request", method="deleteMessageInChat", @@ -189,6 +293,15 @@ def delete_message(self, chat_ref: ChatRef, message_id: str) -> bool: return result == "deleted" def get_message(self, chat_ref: ChatRef, message_id: str) -> Message: + """Retrieves a single message by its ID. + + Args: + chat_ref (ChatRef): The chat reference object. + message_id (str): The unique identifier of the message. + + Returns: + Message: The requested message object. + """ message = self.client.execute( cmd_type="request", method="getMessageInChat", @@ -214,6 +327,15 @@ def get_message(self, chat_ref: ChatRef, message_id: str) -> Message: ) def list_my_joined(self, chat_type: ChatType | None = None) -> list[Chat]: + """Lists all chats the authenticated user is part of. + + Args: + chat_type (ChatType | None, optional): Filter by chat type (e.g., only OneOnOne). + Defaults to None (return all). + + Returns: + list[Chat]: A list of chat objects. + """ params = {} if chat_type: params["chatType"] = chat_type.value @@ -237,6 +359,20 @@ def list_my_joined(self, chat_type: ChatType | None = None) -> list[Chat]: def list_all_messages( self, start_time: datetime, end_time: datetime, top: int ) -> list[Message]: + """Retrieves messages across ALL chats within a time range. + + Note: + This operation typically requires **Application Permissions** and may not + work with standard Delegated (User) credentials depending on the organization's policy. + + Args: + start_time (datetime): The start of the time range. + end_time (datetime): The end of the time range. + top (int): Maximum number of messages to retrieve. + + Returns: + list[Message]: A list of messages from all chats. + """ messages = self.client.execute( cmd_type="request", method="listMyChatMessages", @@ -263,6 +399,14 @@ def list_all_messages( ] def list_pinned_messages(self, chat_ref: ChatRef) -> list[Message]: + """Lists all pinned messages in a specific chat. + + Args: + chat_ref (ChatRef): The chat reference object. + + Returns: + list[Message]: A list of pinned messages. + """ messages = self.client.execute( cmd_type="request", method="listPinnedMessagesInChat", @@ -290,6 +434,15 @@ def list_pinned_messages(self, chat_ref: ChatRef) -> list[Message]: ] def pin_message(self, chat_ref: ChatRef, message_id: str) -> bool: + """Pins a message in the chat. + + Args: + chat_ref (ChatRef): The chat reference object. + message_id (str): The ID of the message to pin. + + Returns: + bool: True if the message was successfully pinned. + """ result = self.client.execute( cmd_type="request", method="pinMessageInChat", @@ -305,6 +458,15 @@ def pin_message(self, chat_ref: ChatRef, message_id: str) -> bool: return result == "pinned" def unpin_message(self, chat_ref: ChatRef, message_id: str) -> bool: + """Unpins a message in the chat. + + Args: + chat_ref (ChatRef): The chat reference object. + message_id (str): The ID of the message to unpin. + + Returns: + bool: True if the message was successfully unpinned. + """ result = self.client.execute( cmd_type="request", method="unpinMessageInChat", @@ -320,6 +482,20 @@ def unpin_message(self, chat_ref: ChatRef, message_id: str) -> bool: return result == "unpinned" def get_mentions(self, chat_ref: ChatRef, raw_mentions: list[str]) -> list[Mention]: + """Resolves raw mention strings into formal Mention objects. + + This helps in processing @mentions within message content before sending. + + Args: + chat_ref (ChatRef): The chat reference object. + raw_mentions (list[str]): A list of raw strings to resolve. + Supported formats: + * **Email/User ID**: Resolves to a specific user. + * **'Everyone'**: Mentions all members (Group Chats only). + + Returns: + list[Mention]: A list of resolved Mention objects. + """ mentions = self.client.execute( cmd_type="request", method="getMentionsInChat", From 0dd0eaec3a35c1fc8689b9f19e253142fd5c2e3f Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Wed, 7 Jan 2026 21:29:41 +0100 Subject: [PATCH 56/99] style: ignored ruff line too long warnings --- python/pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/python/pyproject.toml b/python/pyproject.toml index fcf29b1..d709581 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -23,6 +23,7 @@ fix = true show-fixes = true src = ["teams_lib_pzsp2_z1"] exclude = ["test_*.py", "tests/*"] +ignore = ["E501"] [tool.ruff.lint] From 652653de9bcd9bb093691069661e3d25bd5acf61 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Wed, 7 Jan 2026 21:43:18 +0100 Subject: [PATCH 57/99] chore: adjusted go channels requests to new lib version --- go/go.mod | 4 +-- go/go.sum | 4 +++ go/internal/json-client/requests-channels.go | 36 ++++++++------------ 3 files changed, 20 insertions(+), 24 deletions(-) diff --git a/go/go.mod b/go/go.mod index fee5462..21c34a4 100644 --- a/go/go.mod +++ b/go/go.mod @@ -4,9 +4,9 @@ go 1.25.5 require ( github.com/microsoft/kiota-abstractions-go v1.9.3 - github.com/microsoftgraph/msgraph-sdk-go v1.92.0 + github.com/microsoftgraph/msgraph-sdk-go v1.93.0 github.com/mitchellh/mapstructure v1.5.0 - github.com/pzsp-teams/lib v0.0.0-20260101185821-fa4110d82a33 + github.com/pzsp-teams/lib v0.0.0-20260107201101-e9e6f48e39e8 ) require ( diff --git a/go/go.sum b/go/go.sum index 7327d45..d6423b7 100644 --- a/go/go.sum +++ b/go/go.sum @@ -53,6 +53,8 @@ github.com/microsoft/kiota-serialization-text-go v1.1.3 h1:8z7Cebn0YAAr++xswVgfd github.com/microsoft/kiota-serialization-text-go v1.1.3/go.mod h1:NDSvz4A3QalGMjNboKKQI9wR+8k+ih8UuagNmzIRgTQ= github.com/microsoftgraph/msgraph-sdk-go v1.92.0 h1:LOTZ5un7tsSki0S072D+TIHsiOIpmGJnDkNps9Em6cw= github.com/microsoftgraph/msgraph-sdk-go v1.92.0/go.mod h1:sue5+4Z9FCOon6pHgvC1djjybs9ZYB3LZaAGYI1Qcfo= +github.com/microsoftgraph/msgraph-sdk-go v1.93.0 h1:H4WgYetjWYpr79S2ylNJchFDQTBE0ldKx8sR0qxyRGQ= +github.com/microsoftgraph/msgraph-sdk-go v1.93.0/go.mod h1:sue5+4Z9FCOon6pHgvC1djjybs9ZYB3LZaAGYI1Qcfo= github.com/microsoftgraph/msgraph-sdk-go-core v1.4.0 h1:0SrIoFl7TQnMRrsi5TFaeNe0q8KO5lRzRp4GSCCL2So= github.com/microsoftgraph/msgraph-sdk-go-core v1.4.0/go.mod h1:A1iXs+vjsRjzANxF6UeKv2ACExG7fqTwHHbwh1FL+EE= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= @@ -65,6 +67,8 @@ github.com/pzsp-teams/lib v0.0.0-20260101113908-bafa8ca37eae h1:Q0NnGhGtyUbPzDHx github.com/pzsp-teams/lib v0.0.0-20260101113908-bafa8ca37eae/go.mod h1:OUTZq+jo2AAzjV5p0w7c9VVuOKiHVWxuZhdzOhjOq5s= github.com/pzsp-teams/lib v0.0.0-20260101185821-fa4110d82a33 h1:snmBQwIaJV1MBynhm8OKzvaKtpUva4wem3VKmyaVU0Q= github.com/pzsp-teams/lib v0.0.0-20260101185821-fa4110d82a33/go.mod h1:OUTZq+jo2AAzjV5p0w7c9VVuOKiHVWxuZhdzOhjOq5s= +github.com/pzsp-teams/lib v0.0.0-20260107201101-e9e6f48e39e8 h1:1ifLBNVdw8TCLPzHnlQfgxlL8pbj/9MVquTLNiX2t0Q= +github.com/pzsp-teams/lib v0.0.0-20260107201101-e9e6f48e39e8/go.mod h1:BQwULf+u8TtbKR0U23MlZXgm2CVcLBJn2cS5gmR7L8M= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/std-uritemplate/std-uritemplate/go/v2 v2.0.8 h1:gMBdYMTHt2mmTdXW8YfvRjRUZ0GhyGV+IqSH9H15bGw= diff --git a/go/internal/json-client/requests-channels.go b/go/internal/json-client/requests-channels.go index f249de1..e3c8473 100644 --- a/go/internal/json-client/requests-channels.go +++ b/go/internal/json-client/requests-channels.go @@ -2,8 +2,6 @@ package jsonclient import ( "context" - "fmt" - "os" "github.com/pzsp-teams/lib-python/internal/json-client/decoders" "github.com/pzsp-teams/lib/models" @@ -100,9 +98,11 @@ type listMessagesOptionsDTO struct { } type listMessagesParams struct { - TeamRef string `json:"teamRef"` - ChannelRef string `json:"channelRef"` - Options listMessagesOptionsDTO `json:"options"` + TeamRef string `json:"teamRef"` + ChannelRef string `json:"channelRef"` + Options listMessagesOptionsDTO `json:"options"` + IncludeSystem bool `json:"includeSystem"` + NextLink string `json:"nextLink"` } func (c *TeamsJSONClient) ListMessagesInChannel(p map[string]interface{}) (interface{}, error) { @@ -111,7 +111,7 @@ func (c *TeamsJSONClient) ListMessagesInChannel(p map[string]interface{}) (inter if err != nil { return nil, err } - return c.client.Channels.ListMessages(context.TODO(), params.TeamRef, params.ChannelRef, options) + return c.client.Channels.ListMessages(context.TODO(), params.TeamRef, params.ChannelRef, options, params.IncludeSystem, ¶ms.NextLink) }) } @@ -128,15 +128,17 @@ func (c *TeamsJSONClient) GetMessageInChannel(p map[string]interface{}) (interfa } type listRepliesParams struct { - TeamRef string `json:"teamRef"` - ChannelRef string `json:"channelRef"` - MessageID string `json:"messageId"` - Top *int32 `json:"top"` + TeamRef string `json:"teamRef"` + ChannelRef string `json:"channelRef"` + MessageID string `json:"messageId"` + Top *int32 `json:"top"` + IncludeSystem bool `json:"includeSystem"` + NextLink string `json:"nextLink"` } func (c *TeamsJSONClient) ListMessageRepliesInChannel(p map[string]interface{}) (interface{}, error) { return execute(p, func(params listRepliesParams) (interface{}, error) { - return c.client.Channels.ListReplies(context.TODO(), params.TeamRef, params.ChannelRef, params.MessageID, params.Top) + return c.client.Channels.ListReplies(context.TODO(), params.TeamRef, params.ChannelRef, params.MessageID, params.Top, params.IncludeSystem, ¶ms.NextLink) }) } @@ -174,12 +176,7 @@ func (c *TeamsJSONClient) AddMemberToChannel(p map[string]interface{}) (interfac func (c *TeamsJSONClient) UpdateMemberInChannel(p map[string]interface{}) (interface{}, error) { return execute(p, func(params addOrUpdateMemberToChannelParams) (interface{}, error) { - - // --- LOGOWANIE NA STDERR --- - fmt.Fprintf(os.Stderr, "\n[GO DEBUG] UpdateMemberInChannel PARAMS:\n -> TeamRef: '%s'\n -> ChannelRef: '%s'\n -> UserRef: '%s'\n -> IsOwner: %v\n", - params.TeamRef, params.ChannelRef, params.UserRef, params.IsOwner) - // --------------------------- - return c.client.Channels.UpdateMemberRole(context.TODO(), params.TeamRef, params.ChannelRef, params.UserRef, params.IsOwner) + return c.client.Channels.UpdateMemberRoles(context.TODO(), params.TeamRef, params.ChannelRef, params.UserRef, params.IsOwner) }) } @@ -191,11 +188,6 @@ type removeMemberFromChannelParams struct { func (c *TeamsJSONClient) RemoveMemberFromChannel(p map[string]interface{}) (interface{}, error) { return execute(p, func(params removeMemberFromChannelParams) (interface{}, error) { - - // --- LOGOWANIE NA STDERR --- - fmt.Fprintf(os.Stderr, "\n[GO DEBUG] RemoveMemberFromChannel PARAMS:\n -> TeamRef: '%s'\n -> ChannelRef: '%s'\n -> UserRef: '%s'\n", - params.TeamRef, params.ChannelRef, params.UserRef) - // --------------------------- err := c.client.Channels.RemoveMember(context.TODO(), params.TeamRef, params.ChannelRef, params.UserRef) return "removed", err }) From 757a703a63785f7093385919cee36f606b576d17 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Wed, 7 Jan 2026 22:24:21 +0100 Subject: [PATCH 58/99] chore: adjusted chats and teams in go bridge to new lib version --- go/go.mod | 2 +- go/go.sum | 2 + .../json-client/decoders/decode-msTeam.go | 134 ------------------ .../json-client/decoders/decode-updateTeam.go | 7 + go/internal/json-client/requests-chats.go | 18 ++- go/internal/json-client/requests-teams.go | 19 ++- 6 files changed, 35 insertions(+), 147 deletions(-) delete mode 100644 go/internal/json-client/decoders/decode-msTeam.go create mode 100644 go/internal/json-client/decoders/decode-updateTeam.go diff --git a/go/go.mod b/go/go.mod index 21c34a4..5bbfd43 100644 --- a/go/go.mod +++ b/go/go.mod @@ -6,7 +6,7 @@ require ( github.com/microsoft/kiota-abstractions-go v1.9.3 github.com/microsoftgraph/msgraph-sdk-go v1.93.0 github.com/mitchellh/mapstructure v1.5.0 - github.com/pzsp-teams/lib v0.0.0-20260107201101-e9e6f48e39e8 + github.com/pzsp-teams/lib v0.0.0-20260107211439-3d301706e02c ) require ( diff --git a/go/go.sum b/go/go.sum index d6423b7..14ee7df 100644 --- a/go/go.sum +++ b/go/go.sum @@ -69,6 +69,8 @@ github.com/pzsp-teams/lib v0.0.0-20260101185821-fa4110d82a33 h1:snmBQwIaJV1MBynh github.com/pzsp-teams/lib v0.0.0-20260101185821-fa4110d82a33/go.mod h1:OUTZq+jo2AAzjV5p0w7c9VVuOKiHVWxuZhdzOhjOq5s= github.com/pzsp-teams/lib v0.0.0-20260107201101-e9e6f48e39e8 h1:1ifLBNVdw8TCLPzHnlQfgxlL8pbj/9MVquTLNiX2t0Q= github.com/pzsp-teams/lib v0.0.0-20260107201101-e9e6f48e39e8/go.mod h1:BQwULf+u8TtbKR0U23MlZXgm2CVcLBJn2cS5gmR7L8M= +github.com/pzsp-teams/lib v0.0.0-20260107211439-3d301706e02c h1:ZtUkM66y/jg5g5kEQhteQeACuwKl+cOkRuGPlX1mkGg= +github.com/pzsp-teams/lib v0.0.0-20260107211439-3d301706e02c/go.mod h1:BQwULf+u8TtbKR0U23MlZXgm2CVcLBJn2cS5gmR7L8M= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/std-uritemplate/std-uritemplate/go/v2 v2.0.8 h1:gMBdYMTHt2mmTdXW8YfvRjRUZ0GhyGV+IqSH9H15bGw= diff --git a/go/internal/json-client/decoders/decode-msTeam.go b/go/internal/json-client/decoders/decode-msTeam.go deleted file mode 100644 index 3447881..0000000 --- a/go/internal/json-client/decoders/decode-msTeam.go +++ /dev/null @@ -1,134 +0,0 @@ -package decoders - -import( - msmodels "github.com/microsoftgraph/msgraph-sdk-go/models" -) - -type TeamPatchDTO struct { - DisplayName *string `json:"displayName"` - Description *string `json:"description"` - Classification *string `json:"classification"` - Visibility *string `json:"visibility"` - IsArchived *bool `json:"isArchived"` - - MemberSettings *TeamMemberSettingsDTO `json:"memberSettings"` - GuestSettings *TeamGuestSettingsDTO `json:"guestSettings"` - MessagingSettings *TeamMessagingSettingsDTO `json:"messagingSettings"` - FunSettings *TeamFunSettingsDTO `json:"funSettings"` -} - -type TeamMemberSettingsDTO struct { - AllowCreateUpdateChannels *bool `json:"allowCreateUpdateChannels"` - AllowDeleteChannels *bool `json:"allowDeleteChannels"` - AllowAddRemoveApps *bool `json:"allowAddRemoveApps"` - AllowCreateUpdateRemoveTabs *bool `json:"allowCreateUpdateRemoveTabs"` - AllowCreateUpdateRemoveConnectors *bool `json:"allowCreateUpdateRemoveConnectors"` -} - -type TeamGuestSettingsDTO struct { - AllowCreateUpdateChannels *bool `json:"allowCreateUpdateChannels"` - AllowDeleteChannels *bool `json:"allowDeleteChannels"` -} - -type TeamMessagingSettingsDTO struct { - AllowUserEditMessages *bool `json:"allowUserEditMessages"` - AllowUserDeleteMessages *bool `json:"allowUserDeleteMessages"` - AllowOwnerDeleteMessages *bool `json:"allowOwnerDeleteMessages"` - AllowTeamMentions *bool `json:"allowTeamMentions"` - AllowChannelMentions *bool `json:"allowChannelMentions"` -} - -type TeamFunSettingsDTO struct { - AllowGiphy *bool `json:"allowGiphy"` - GiphyContentRating *string `json:"giphyContentRating"` - AllowStickersAndMemes *bool `json:"allowStickersAndMemes"` - AllowCustomMemes *bool `json:"allowCustomMemes"` -} - -func GetMSTeam(patch *TeamPatchDTO) *msmodels.Team { - team := msmodels.NewTeam() - - if patch.DisplayName != nil { - team.SetDisplayName(patch.DisplayName) - } - if patch.Description != nil { - team.SetDescription(patch.Description) - } - if patch.Classification != nil { - team.SetClassification(patch.Classification) - } - if patch.IsArchived != nil { - team.SetIsArchived(patch.IsArchived) - } - if patch.Visibility != nil { - switch *patch.Visibility { - case "Public": - v := msmodels.PUBLIC_TEAMVISIBILITYTYPE - team.SetVisibility(&v) - case "Private": - v := msmodels.PRIVATE_TEAMVISIBILITYTYPE - team.SetVisibility(&v) - case "HiddenMembership": - v := msmodels.HIDDENMEMBERSHIP_TEAMVISIBILITYTYPE - team.SetVisibility(&v) - } - } - - if patch.MemberSettings != nil { - msSettings := msmodels.NewTeamMemberSettings() - p := patch.MemberSettings - if p.AllowCreateUpdateChannels != nil { msSettings.SetAllowCreateUpdateChannels(p.AllowCreateUpdateChannels) } - if p.AllowDeleteChannels != nil { msSettings.SetAllowDeleteChannels(p.AllowDeleteChannels) } - if p.AllowAddRemoveApps != nil { msSettings.SetAllowAddRemoveApps(p.AllowAddRemoveApps) } - if p.AllowCreateUpdateRemoveTabs != nil { msSettings.SetAllowCreateUpdateRemoveTabs(p.AllowCreateUpdateRemoveTabs) } - if p.AllowCreateUpdateRemoveConnectors != nil { msSettings.SetAllowCreateUpdateRemoveConnectors(p.AllowCreateUpdateRemoveConnectors) } - - team.SetMemberSettings(msSettings) - } - - if patch.GuestSettings != nil { - msSettings := msmodels.NewTeamGuestSettings() - p := patch.GuestSettings - if p.AllowCreateUpdateChannels != nil { msSettings.SetAllowCreateUpdateChannels(p.AllowCreateUpdateChannels) } - if p.AllowDeleteChannels != nil { msSettings.SetAllowDeleteChannels(p.AllowDeleteChannels) } - - team.SetGuestSettings(msSettings) - } - - if patch.MessagingSettings != nil { - msSettings := msmodels.NewTeamMessagingSettings() - p := patch.MessagingSettings - if p.AllowUserEditMessages != nil { msSettings.SetAllowUserEditMessages(p.AllowUserEditMessages) } - if p.AllowUserDeleteMessages != nil { msSettings.SetAllowUserDeleteMessages(p.AllowUserDeleteMessages) } - if p.AllowOwnerDeleteMessages != nil { msSettings.SetAllowOwnerDeleteMessages(p.AllowOwnerDeleteMessages) } - if p.AllowTeamMentions != nil { msSettings.SetAllowTeamMentions(p.AllowTeamMentions) } - if p.AllowChannelMentions != nil { msSettings.SetAllowChannelMentions(p.AllowChannelMentions) } - - team.SetMessagingSettings(msSettings) - } - - if patch.FunSettings != nil { - msSettings := msmodels.NewTeamFunSettings() - p := patch.FunSettings - if p.AllowGiphy != nil { msSettings.SetAllowGiphy(p.AllowGiphy) } - if p.GiphyContentRating != nil { - switch *p.GiphyContentRating { - case "moderate": - v := msmodels.MODERATE_GIPHYRATINGTYPE - msSettings.SetGiphyContentRating(&v) - case "strict": - v := msmodels.STRICT_GIPHYRATINGTYPE - msSettings.SetGiphyContentRating(&v) - default: - v := msmodels.MODERATE_GIPHYRATINGTYPE - msSettings.SetGiphyContentRating(&v) - } -} - if p.AllowStickersAndMemes != nil { msSettings.SetAllowStickersAndMemes(p.AllowStickersAndMemes) } - if p.AllowCustomMemes != nil { msSettings.SetAllowCustomMemes(p.AllowCustomMemes) } - - team.SetFunSettings(msSettings) - } - - return team -} \ No newline at end of file diff --git a/go/internal/json-client/decoders/decode-updateTeam.go b/go/internal/json-client/decoders/decode-updateTeam.go new file mode 100644 index 0000000..40207a4 --- /dev/null +++ b/go/internal/json-client/decoders/decode-updateTeam.go @@ -0,0 +1,7 @@ +package decoders + +type UpdateTeamDTO struct { + DisplayName *string `json:"displayName"` + Description *string `json:"description"` + Visibility *string `json:"visibility"` +} diff --git a/go/internal/json-client/requests-chats.go b/go/internal/json-client/requests-chats.go index 68eca9d..cab3135 100644 --- a/go/internal/json-client/requests-chats.go +++ b/go/internal/json-client/requests-chats.go @@ -14,7 +14,7 @@ type createOneToOneChatParams struct { func (c *TeamsJSONClient) CreateOneToOneChat(p map[string]interface{}) (interface{}, error) { return execute(p, func(params createOneToOneChatParams) (interface{}, error) { - return c.client.Chats.CreateOneOneOne(context.TODO(), params.RecipientRef) + return c.client.Chats.CreateOneOnOne(context.TODO(), params.RecipientRef) }) } @@ -69,17 +69,19 @@ func (c *TeamsJSONClient) UpdateGroupChatTopic(p map[string]interface{}) (interf }) } -type baseChatParams struct { - ChatRef decoders.ChatRefDTO `json:"chatRef"` +type listMessagesInChatParams struct { + ChatRef decoders.ChatRefDTO `json:"chatRef"` + IncludeSystem bool `json:"includeSystem"` + NextLink string `json:"nextLink"` } func (c *TeamsJSONClient) ListMessagesInChat(p map[string]interface{}) (interface{}, error) { - return execute(p, func(params baseChatParams) (interface{}, error) { + return execute(p, func(params listMessagesInChatParams) (interface{}, error) { chatRef, err := decoders.GetChatRef(params.ChatRef) if err != nil { return nil, err } - return c.client.Chats.ListMessages(context.TODO(), chatRef) + return c.client.Chats.ListMessages(context.TODO(), chatRef, params.IncludeSystem, ¶ms.NextLink) }) } @@ -145,7 +147,7 @@ func (c *TeamsJSONClient) ListMyChats(p map[string]interface{}) (interface{}, er type listChatMessagesParams struct { StartTime string `json:"startTime"` EndTime string `json:"endTime"` - Top *int32 `json:"top"` + Top *int32 `json:"top"` } func (c *TeamsJSONClient) ListMyChatMessages(p map[string]interface{}) (interface{}, error) { @@ -158,6 +160,10 @@ func (c *TeamsJSONClient) ListMyChatMessages(p map[string]interface{}) (interfac }) } +type baseChatParams struct { + ChatRef decoders.ChatRefDTO `json:"chatRef"` +} + func (c *TeamsJSONClient) ListPinnedMessagesInChat(p map[string]interface{}) (interface{}, error) { return execute(p, func(params baseChatParams) (interface{}, error) { chatRef, err := decoders.GetChatRef(params.ChatRef) diff --git a/go/internal/json-client/requests-teams.go b/go/internal/json-client/requests-teams.go index 8cbc140..0b8479a 100644 --- a/go/internal/json-client/requests-teams.go +++ b/go/internal/json-client/requests-teams.go @@ -4,6 +4,7 @@ import ( "context" "github.com/pzsp-teams/lib-python/internal/json-client/decoders" + "github.com/pzsp-teams/lib/models" ) type getTeamParams struct { @@ -21,14 +22,17 @@ func (c *TeamsJSONClient) ListMyJoined() (interface{}, error) { } type updateTeamParams struct { - TeamRef string `json:"teamRef"` - TeamPatch decoders.TeamPatchDTO `json:"team"` + TeamRef string `json:"teamRef"` + TeamUpdate decoders.UpdateTeamDTO `json:"team"` } func (c *TeamsJSONClient) UpdateTeam(p map[string]interface{}) (interface{}, error) { return execute(p, func(params updateTeamParams) (interface{}, error) { - msTeam := decoders.GetMSTeam(¶ms.TeamPatch) - return c.client.Teams.Update(context.TODO(), params.TeamRef, msTeam) + updateTeam, err := decoders.DecodeParams[models.TeamUpdate](¶ms.TeamUpdate) + if err != nil { + return nil, err + } + return c.client.Teams.UpdateTeam(context.TODO(), params.TeamRef, updateTeam) }) } @@ -48,11 +52,14 @@ type createFromTemplateParams struct { DisplayName string `json:"displayName"` Description string `json:"description"` Owners []string `json:"owners"` + Members []string `json:"members"` + Visibility string `json:"visibility"` + IncludeMe bool `json:"includeMe"` } func (c *TeamsJSONClient) CreateTeamFromTemplate(p map[string]interface{}) (interface{}, error) { return execute(p, func(params createFromTemplateParams) (interface{}, error) { - return c.client.Teams.CreateFromTemplate(context.TODO(), params.DisplayName, params.Description, params.Owners) + return c.client.Teams.CreateFromTemplate(context.TODO(), params.DisplayName, params.Description, params.Owners, params.Members, params.Visibility, params.IncludeMe) }) } @@ -98,4 +105,4 @@ func (c *TeamsJSONClient) RestoreDeletedTeam(p map[string]interface{}) (interfac return execute(p, func(params restoreTeamParams) (interface{}, error) { return c.client.Teams.RestoreDeleted(context.TODO(), params.DeletedGroupID) }) -} \ No newline at end of file +} From 72fe0af79bac26ea620d3c565340e21c09d16030 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Wed, 7 Jan 2026 22:26:17 +0100 Subject: [PATCH 59/99] feat: added new data models to python --- python/teams_lib_pzsp2_z1/model/message.py | 5 +++ python/teams_lib_pzsp2_z1/model/team.py | 36 +--------------------- 2 files changed, 6 insertions(+), 35 deletions(-) diff --git a/python/teams_lib_pzsp2_z1/model/message.py b/python/teams_lib_pzsp2_z1/model/message.py index 52f8139..e3decf2 100644 --- a/python/teams_lib_pzsp2_z1/model/message.py +++ b/python/teams_lib_pzsp2_z1/model/message.py @@ -47,3 +47,8 @@ def __iter__(self): class ListMessagesOptions: Top: int | None = None ExpandReplies: bool = False + +@dataclass +class MessageCollection: + Messages: list[Message] + NextLink: str | None = None diff --git a/python/teams_lib_pzsp2_z1/model/team.py b/python/teams_lib_pzsp2_z1/model/team.py index d53ee4a..3e332e6 100644 --- a/python/teams_lib_pzsp2_z1/model/team.py +++ b/python/teams_lib_pzsp2_z1/model/team.py @@ -11,41 +11,7 @@ class Team: @dataclass -class MSTeamsUpdate: +class UpdateTeam: DisplayName: str | None = None Description: str | None = None - Classification: str | None = None Visibility: str | None = None - IsArchived: bool | None = None - MemberSettings: "TeamMemberSettingsDTO | None" = None - GuestSettings: "TeamGuestSettingsDTO | None" = None - MessagingSettings: "TeamMessagingSettingsDTO | None" = None - FunSettings: "TeamFunSettingsDTO | None" = None - - @dataclass - class TeamMemberSettingsDTO: - AllowCreateUpdateChannels: bool | None = None - AllowDeleteChannels: bool | None = None - AllowAddRemoveApps: bool | None = None - AllowCreateUpdateRemoveTabs: bool | None = None - AllowCreateUpdateRemoveConnectors: bool | None = None - - @dataclass - class TeamGuestSettingsDTO: - AllowCreateUpdateChannels: bool | None = None - AllowDeleteChannels: bool | None = None - - @dataclass - class TeamMessagingSettingsDTO: - AllowUserEditMessages: bool | None = None - AllowUserDeleteMessages: bool | None = None - AllowOwnerDeleteMessages: bool | None = None - AllowTeamMentions: bool | None = None - AllowChannelMentions: bool | None = None - - @dataclass - class TeamFunSettingsDTO: - AllowGiphy: bool | None = None - GiphyContentRating: str | None = None - AllowStickersAndMemes: bool | None = None - AllowCustomMemes: bool | None = None From dd97bde4fb07b1f365ca553223fcd94d0cb3fd72 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Wed, 7 Jan 2026 22:45:16 +0100 Subject: [PATCH 60/99] chore: adjusted update team python and test --- .../json-client/decoders/decode-updateTeam.go | 26 ++++++++++-- go/internal/json-client/requests-teams.go | 8 +--- python/teams_lib_pzsp2_z1/model/team.py | 6 +-- .../teams_lib_pzsp2_z1/services/channels.py | 42 ++++++++++++------- python/teams_lib_pzsp2_z1/services/teams.py | 4 +- python/tests/fake_server/setup.py | 27 +++++++++--- python/tests/test_channels.py | 35 ++++++++-------- python/tests/test_teams.py | 6 +-- 8 files changed, 98 insertions(+), 56 deletions(-) diff --git a/go/internal/json-client/decoders/decode-updateTeam.go b/go/internal/json-client/decoders/decode-updateTeam.go index 40207a4..b5a1eea 100644 --- a/go/internal/json-client/decoders/decode-updateTeam.go +++ b/go/internal/json-client/decoders/decode-updateTeam.go @@ -1,7 +1,27 @@ package decoders +import ( + "github.com/pzsp-teams/lib/models" +) + type UpdateTeamDTO struct { - DisplayName *string `json:"displayName"` - Description *string `json:"description"` - Visibility *string `json:"visibility"` + DisplayName string `json:"displayName"` + Description string `json:"description"` + Visibility string `json:"visibility"` +} + +func GetUpdateTeam(dto *UpdateTeamDTO) models.TeamUpdate { + update := models.TeamUpdate{} + + if dto.DisplayName != "" { + update.DisplayName = &dto.DisplayName + } + if dto.Description != "" { + update.Description = &dto.Description + } + if dto.Visibility != "" { + update.Visibility = &dto.Visibility + } + + return update } diff --git a/go/internal/json-client/requests-teams.go b/go/internal/json-client/requests-teams.go index 0b8479a..49d267c 100644 --- a/go/internal/json-client/requests-teams.go +++ b/go/internal/json-client/requests-teams.go @@ -4,7 +4,6 @@ import ( "context" "github.com/pzsp-teams/lib-python/internal/json-client/decoders" - "github.com/pzsp-teams/lib/models" ) type getTeamParams struct { @@ -28,11 +27,8 @@ type updateTeamParams struct { func (c *TeamsJSONClient) UpdateTeam(p map[string]interface{}) (interface{}, error) { return execute(p, func(params updateTeamParams) (interface{}, error) { - updateTeam, err := decoders.DecodeParams[models.TeamUpdate](¶ms.TeamUpdate) - if err != nil { - return nil, err - } - return c.client.Teams.UpdateTeam(context.TODO(), params.TeamRef, updateTeam) + updateTeam := decoders.GetUpdateTeam(¶ms.TeamUpdate) + return c.client.Teams.UpdateTeam(context.TODO(), params.TeamRef, &updateTeam) }) } diff --git a/python/teams_lib_pzsp2_z1/model/team.py b/python/teams_lib_pzsp2_z1/model/team.py index 3e332e6..0d17576 100644 --- a/python/teams_lib_pzsp2_z1/model/team.py +++ b/python/teams_lib_pzsp2_z1/model/team.py @@ -12,6 +12,6 @@ class Team: @dataclass class UpdateTeam: - DisplayName: str | None = None - Description: str | None = None - Visibility: str | None = None + displayName: str | None = None + description: str | None = None + visibility: str | None = None diff --git a/python/teams_lib_pzsp2_z1/services/channels.py b/python/teams_lib_pzsp2_z1/services/channels.py index 0f025b4..4c3d790 100644 --- a/python/teams_lib_pzsp2_z1/services/channels.py +++ b/python/teams_lib_pzsp2_z1/services/channels.py @@ -8,6 +8,7 @@ from teams_lib_pzsp2_z1.model.message import ( Message, MessageBody, + MessageCollection, MessageContentType, MessageFrom, ) @@ -200,13 +201,15 @@ def send_message( ReplyCount=response["ReplyCount"], ) - def list_messages( + def list_messages( # noqa: PLR0913 self, team_ref: str, channel_ref: str, top: int | None = 10, expand_replies: bool = False, - ) -> list[Message]: + include_system_messages: bool = False, + next_link: str | None = None, + ) -> MessageCollection: """Retrieves messages from a channel. Args: @@ -214,6 +217,8 @@ def list_messages( channel_ref (str): The reference to the channel. top (Optional[int]): The maximum number of messages to retrieve. Defaults to 10. expand_replies (bool): If True, system fetches replies for each message. Defaults to False. + include_system_messages (bool): If True, includes system-generated messages. Defaults to False. + next_link (Optional[str]): A link for pagination to fetch the next set of messages. Returns: List[Message]: A list of messages from the channel. @@ -227,24 +232,29 @@ def list_messages( "options": { "top": top, "expandReplies": expand_replies, + "includeSystem": include_system_messages, + "nextLink": next_link, }, }, ) - return [ - Message( - ID=message["ID"], - Content=message["Content"], - ContentType=MessageContentType(message["ContentType"]), - CreatedDateTime=message["CreatedDateTime"], - From=MessageFrom( - UserID=message["From"]["UserID"], - DisplayName=message["From"]["DisplayName"], - ), - ReplyCount=message["ReplyCount"], - ) - for message in response - ] + return MessageCollection( + Messages=[ + Message( + ID=message["ID"], + Content=message["Content"], + ContentType=MessageContentType(message["ContentType"]), + CreatedDateTime=message["CreatedDateTime"], + From=MessageFrom( + UserID=message["From"]["UserID"], + DisplayName=message["From"]["DisplayName"], + ), + ReplyCount=message["ReplyCount"], + ) + for message in response + ], + NextLink=response["NextLink"], + ) def get_message(self, team_ref: str, channel_ref: str, message_id: str) -> Message: """Retrieves a specific message by its ID. diff --git a/python/teams_lib_pzsp2_z1/services/teams.py b/python/teams_lib_pzsp2_z1/services/teams.py index 47602a4..9c1976c 100644 --- a/python/teams_lib_pzsp2_z1/services/teams.py +++ b/python/teams_lib_pzsp2_z1/services/teams.py @@ -1,4 +1,4 @@ -from teams_lib_pzsp2_z1.model.team import MSTeamsUpdate, Team +from teams_lib_pzsp2_z1.model.team import Team, UpdateTeam from teams_lib_pzsp2_z1.services.base_service import BaseService @@ -37,7 +37,7 @@ def list_my_joined(self) -> list[Team]: for team in response ] - def update(self, teamRef: str, update: MSTeamsUpdate) -> Team: + def update(self, teamRef: str, update: UpdateTeam) -> Team: params = {"teamRef": teamRef} update_dict = {k: v for k, v in update.__dict__.items() if v is not None} params["team"] = update_dict diff --git a/python/tests/fake_server/setup.py b/python/tests/fake_server/setup.py index 9edbe3d..5b5579c 100644 --- a/python/tests/fake_server/setup.py +++ b/python/tests/fake_server/setup.py @@ -326,6 +326,19 @@ def handle_chats_request(req): "Update Team (PATCH)" )) + # PATCH /groups/{id} + httpserver.expect_request( + re.compile(r"^/v1.0/groups/([^/]+)"), + method="PATCH" + ).respond_with_handler(lambda req: make_log_response( + req, + data.get_updateTeam_response( + re.search(r"/groups/([^/]+)", req.path).group(1), + req.json + ), + "Update Team (PATCH)" + )) + # GET /teams/{id} httpserver.expect_request( re.compile(r"^/v1.0/teams/([^/]+)(?:$|\?)"), @@ -527,11 +540,15 @@ def handle_chats_request(req): # Fallback for unmatched routes httpserver.expect_request(re.compile(".*")).respond_with_handler( - lambda req: Response( - json.dumps({"error": f"Route not matched in Mock: {req.method} {req.path}"}), - status=404, - mimetype="application/json" - ) + lambda req: ( + print(f"[MOCK SERVER] ⚠️ UNMATCHED: {req.method} {req.path}", file=sys.stdout), + print(f"[MOCK SERVER] Body: {req.get_data(as_text=True)}", file=sys.stdout), + Response( + json.dumps({"error": f"Route not matched in Mock: {req.method} {req.path}"}), + status=404, + mimetype="application/json" + ) + )[-1] ) return data \ No newline at end of file diff --git a/python/tests/test_channels.py b/python/tests/test_channels.py index 89d4f66..979d81f 100644 --- a/python/tests/test_channels.py +++ b/python/tests/test_channels.py @@ -155,28 +155,27 @@ def test_list_messages_integration(httpserver): try: init_fake_client(client, httpserver.url_for("")) - messages = client.channels.list_messages( + collection = client.channels.list_messages( team_ref=data.teams[0].DisplayName, channel_ref=data.channels[data.teams[0].ID][0].Name, ) - assert len(messages) == len(data.messages[data.channels[data.teams[0].ID][0].ID]) - assert messages[0].ID == data.messages[data.channels[data.teams[0].ID][0].ID][0].ID - assert messages[0].Content == data.messages[data.channels[data.teams[0].ID][0].ID][0].Content - assert messages[0].ContentType == MessageContentType(data.messages[data.channels[data.teams[0].ID][0].ID][0].ContentType) - assert messages[0].From.UserID == data.messages[data.channels[data.teams[0].ID][0].ID][0].From.UserID - assert messages[0].From.DisplayName == data.messages[data.channels[data.teams[0].ID][0].ID][0].From.DisplayName - assert messages[0].ReplyCount == data.messages[data.channels[data.teams[0].ID][0].ID][0].ReplyCount - assert messages[0].CreatedDateTime == data.messages[data.channels[data.teams[0].ID][0].ID][0].CreatedDateTime - - assert messages[1].ID == data.messages[data.channels[data.teams[0].ID][0].ID][1].ID - assert messages[1].Content == data.messages[data.channels[data.teams[0].ID][0].ID][1].Content - assert messages[1].ContentType == MessageContentType(data.messages[data.channels[data.teams[0].ID][0].ID][1].ContentType) - assert messages[1].From.UserID == data.messages[data.channels[data.teams[0].ID][0].ID][1].From.UserID - assert messages[1].From.DisplayName == data.messages[data.channels[data.teams[0].ID][0].ID][1].From.DisplayName - assert messages[1].ReplyCount == data.messages[data.channels[data.teams[0].ID][0].ID][1].ReplyCount - assert messages[1].CreatedDateTime == data.messages[data.channels[data.teams[0].ID][0].ID][1].CreatedDateTime - + assert len(collection) == len(data.messages[data.channels[data.teams[0].ID][0].ID]) + assert collection.Messages[0].ID == data.messages[data.channels[data.teams[0].ID][0].ID][0].ID + assert collection.Messages[0].Content == data.messages[data.channels[data.teams[0].ID][0].ID][0].Content + assert collection.Messages[0].ContentType == MessageContentType(data.messages[data.channels[data.teams[0].ID][0].ID][0].ContentType) + assert collection.Messages[0].From.UserID == data.messages[data.channels[data.teams[0].ID][0].ID][0].From.UserID + assert collection.Messages[0].From.DisplayName == data.messages[data.channels[data.teams[0].ID][0].ID][0].From.DisplayName + assert collection.Messages[0].ReplyCount == data.messages[data.channels[data.teams[0].ID][0].ID][0].ReplyCount + assert collection.Messages[0].CreatedDateTime == data.messages[data.channels[data.teams[0].ID][0].ID][0].CreatedDateTime + + assert collection.Messages[1].ID == data.messages[data.channels[data.teams[0].ID][0].ID][1].ID + assert collection.Messages[1].Content == data.messages[data.channels[data.teams[0].ID][0].ID][1].Content + assert collection.Messages[1].ContentType == MessageContentType(data.messages[data.channels[data.teams[0].ID][0].ID][1].ContentType) + assert collection.Messages[1].From.UserID == data.messages[data.channels[data.teams[0].ID][0].ID][1].From.UserID + assert collection.Messages[1].From.DisplayName == data.messages[data.channels[data.teams[0].ID][0].ID][1].From.DisplayName + assert collection.Messages[1].ReplyCount == data.messages[data.channels[data.teams[0].ID][0].ID][1].ReplyCount + assert collection.Messages[1].CreatedDateTime == data.messages[data.channels[data.teams[0].ID][0].ID][1].CreatedDateTime finally: client.close() diff --git a/python/tests/test_teams.py b/python/tests/test_teams.py index a234c9f..9ab7d75 100644 --- a/python/tests/test_teams.py +++ b/python/tests/test_teams.py @@ -1,4 +1,4 @@ -from teams_lib_pzsp2_z1.model.team import MSTeamsUpdate +from teams_lib_pzsp2_z1.model.team import UpdateTeam from teams_lib_pzsp2_z1.client import TeamsClient from tests.init_fake_client import init_fake_client from tests.fake_server.setup import setup_fake_server @@ -68,8 +68,8 @@ def test_update_team_integration(httpserver): updated_description = "Updated team description" team = client.teams.update( teamRef=data.teams[0].DisplayName, - update=MSTeamsUpdate( - Description=updated_description, + update=UpdateTeam( + description=updated_description, ), ) From 5d0e60b8c92481b12b1d1fed836f6aa337b3ea84 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Wed, 7 Jan 2026 22:54:54 +0100 Subject: [PATCH 61/99] chore: adjusted list channels messages --- .../json-client/decoders/decode-nextLink.go | 12 +++++++++++ go/internal/json-client/requests-channels.go | 20 ++++++++++-------- go/internal/json-client/requests-chats.go | 9 ++++---- .../teams_lib_pzsp2_z1/services/channels.py | 21 ++++++++++--------- python/tests/test_channels.py | 2 +- 5 files changed, 40 insertions(+), 24 deletions(-) create mode 100644 go/internal/json-client/decoders/decode-nextLink.go diff --git a/go/internal/json-client/decoders/decode-nextLink.go b/go/internal/json-client/decoders/decode-nextLink.go new file mode 100644 index 0000000..584689d --- /dev/null +++ b/go/internal/json-client/decoders/decode-nextLink.go @@ -0,0 +1,12 @@ +package decoders + +type NextLinkDTO struct { + NextLink string `json:"nextLink"` +} + +func GetNextLink(dto *NextLinkDTO) *string { + if dto.NextLink == "" { + return nil + } + return &dto.NextLink +} \ No newline at end of file diff --git a/go/internal/json-client/requests-channels.go b/go/internal/json-client/requests-channels.go index e3c8473..81b4f2c 100644 --- a/go/internal/json-client/requests-channels.go +++ b/go/internal/json-client/requests-channels.go @@ -102,7 +102,7 @@ type listMessagesParams struct { ChannelRef string `json:"channelRef"` Options listMessagesOptionsDTO `json:"options"` IncludeSystem bool `json:"includeSystem"` - NextLink string `json:"nextLink"` + NextLink decoders.NextLinkDTO `json:"nextLink"` } func (c *TeamsJSONClient) ListMessagesInChannel(p map[string]interface{}) (interface{}, error) { @@ -111,7 +111,8 @@ func (c *TeamsJSONClient) ListMessagesInChannel(p map[string]interface{}) (inter if err != nil { return nil, err } - return c.client.Channels.ListMessages(context.TODO(), params.TeamRef, params.ChannelRef, options, params.IncludeSystem, ¶ms.NextLink) + nextLink := decoders.GetNextLink(¶ms.NextLink) + return c.client.Channels.ListMessages(context.TODO(), params.TeamRef, params.ChannelRef, options, params.IncludeSystem, nextLink) }) } @@ -128,17 +129,18 @@ func (c *TeamsJSONClient) GetMessageInChannel(p map[string]interface{}) (interfa } type listRepliesParams struct { - TeamRef string `json:"teamRef"` - ChannelRef string `json:"channelRef"` - MessageID string `json:"messageId"` - Top *int32 `json:"top"` - IncludeSystem bool `json:"includeSystem"` - NextLink string `json:"nextLink"` + TeamRef string `json:"teamRef"` + ChannelRef string `json:"channelRef"` + MessageID string `json:"messageId"` + Top *int32 `json:"top"` + IncludeSystem bool `json:"includeSystem"` + NextLink decoders.NextLinkDTO `json:"nextLink"` } func (c *TeamsJSONClient) ListMessageRepliesInChannel(p map[string]interface{}) (interface{}, error) { return execute(p, func(params listRepliesParams) (interface{}, error) { - return c.client.Channels.ListReplies(context.TODO(), params.TeamRef, params.ChannelRef, params.MessageID, params.Top, params.IncludeSystem, ¶ms.NextLink) + nextLink := decoders.GetNextLink(¶ms.NextLink) + return c.client.Channels.ListReplies(context.TODO(), params.TeamRef, params.ChannelRef, params.MessageID, params.Top, params.IncludeSystem, nextLink) }) } diff --git a/go/internal/json-client/requests-chats.go b/go/internal/json-client/requests-chats.go index cab3135..9a9241b 100644 --- a/go/internal/json-client/requests-chats.go +++ b/go/internal/json-client/requests-chats.go @@ -70,9 +70,9 @@ func (c *TeamsJSONClient) UpdateGroupChatTopic(p map[string]interface{}) (interf } type listMessagesInChatParams struct { - ChatRef decoders.ChatRefDTO `json:"chatRef"` - IncludeSystem bool `json:"includeSystem"` - NextLink string `json:"nextLink"` + ChatRef decoders.ChatRefDTO `json:"chatRef"` + IncludeSystem bool `json:"includeSystem"` + NextLink decoders.NextLinkDTO `json:"nextLink"` } func (c *TeamsJSONClient) ListMessagesInChat(p map[string]interface{}) (interface{}, error) { @@ -81,7 +81,8 @@ func (c *TeamsJSONClient) ListMessagesInChat(p map[string]interface{}) (interfac if err != nil { return nil, err } - return c.client.Chats.ListMessages(context.TODO(), chatRef, params.IncludeSystem, ¶ms.NextLink) + nextLink := decoders.GetNextLink(¶ms.NextLink) + return c.client.Chats.ListMessages(context.TODO(), chatRef, params.IncludeSystem, nextLink) }) } diff --git a/python/teams_lib_pzsp2_z1/services/channels.py b/python/teams_lib_pzsp2_z1/services/channels.py index 4c3d790..a82bd47 100644 --- a/python/teams_lib_pzsp2_z1/services/channels.py +++ b/python/teams_lib_pzsp2_z1/services/channels.py @@ -239,23 +239,24 @@ def list_messages( # noqa: PLR0913 ) return MessageCollection( - Messages=[ + Messages=[ Message( - ID=message["ID"], - Content=message["Content"], - ContentType=MessageContentType(message["ContentType"]), - CreatedDateTime=message["CreatedDateTime"], + ID=msg["ID"], + Content=msg["Content"], + ContentType=MessageContentType(msg["ContentType"]), + CreatedDateTime=msg["CreatedDateTime"], From=MessageFrom( - UserID=message["From"]["UserID"], - DisplayName=message["From"]["DisplayName"], + UserID=msg["From"]["UserID"], + DisplayName=msg["From"]["DisplayName"], ), - ReplyCount=message["ReplyCount"], + ReplyCount=msg["ReplyCount"], ) - for message in response + for msg in response["Messages"] ], - NextLink=response["NextLink"], + NextLink=response.get("NextLink"), ) + def get_message(self, team_ref: str, channel_ref: str, message_id: str) -> Message: """Retrieves a specific message by its ID. diff --git a/python/tests/test_channels.py b/python/tests/test_channels.py index 979d81f..ccb8047 100644 --- a/python/tests/test_channels.py +++ b/python/tests/test_channels.py @@ -160,7 +160,7 @@ def test_list_messages_integration(httpserver): channel_ref=data.channels[data.teams[0].ID][0].Name, ) - assert len(collection) == len(data.messages[data.channels[data.teams[0].ID][0].ID]) + assert len(collection.Messages) == len(data.messages[data.channels[data.teams[0].ID][0].ID]) assert collection.Messages[0].ID == data.messages[data.channels[data.teams[0].ID][0].ID][0].ID assert collection.Messages[0].Content == data.messages[data.channels[data.teams[0].ID][0].ID][0].Content assert collection.Messages[0].ContentType == MessageContentType(data.messages[data.channels[data.teams[0].ID][0].ID][0].ContentType) From 712e3dff75db2dab58c4ee83ad6b3922baddf772 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Wed, 7 Jan 2026 22:59:57 +0100 Subject: [PATCH 62/99] chore: adjusted list replies in channels --- .../teams_lib_pzsp2_z1/services/channels.py | 46 +++++++++++-------- python/tests/test_channels.py | 28 +++++------ 2 files changed, 41 insertions(+), 33 deletions(-) diff --git a/python/teams_lib_pzsp2_z1/services/channels.py b/python/teams_lib_pzsp2_z1/services/channels.py index a82bd47..0af5dc0 100644 --- a/python/teams_lib_pzsp2_z1/services/channels.py +++ b/python/teams_lib_pzsp2_z1/services/channels.py @@ -221,7 +221,7 @@ def list_messages( # noqa: PLR0913 next_link (Optional[str]): A link for pagination to fetch the next set of messages. Returns: - List[Message]: A list of messages from the channel. + MessageCollection: An object containing the list of messages and a next link for pagination. """ response = self.client.execute( cmd_type="request", @@ -256,7 +256,6 @@ def list_messages( # noqa: PLR0913 NextLink=response.get("NextLink"), ) - def get_message(self, team_ref: str, channel_ref: str, message_id: str) -> Message: """Retrieves a specific message by its ID. @@ -290,13 +289,15 @@ def get_message(self, team_ref: str, channel_ref: str, message_id: str) -> Messa ReplyCount=response["ReplyCount"], ) - def list_message_replies( + def list_message_replies( # noqa: PLR0913 self, team_ref: str, channel_ref: str, message_id: str, top: int | None = 10, - ) -> list[Message]: + include_system_messages: bool = False, + next_link: str | None = None, + ) -> MessageCollection: """Retrieves all replies to a specific message thread. Args: @@ -304,9 +305,11 @@ def list_message_replies( channel_ref (str): The reference to the channel. message_id (str): The ID of the parent message. top (Optional[int]): Max number of replies to fetch. Defaults to 10. + include_system_messages (bool): If True, includes system-generated messages. Defaults to False. + next_link (Optional[str]): A link for pagination to fetch the next set of replies. Returns: - List[Message]: A list of replies. + MessageCollection: An object containing the list of replies and a next link for pagination. """ response = self.client.execute( cmd_type="request", @@ -316,23 +319,28 @@ def list_message_replies( "channelRef": channel_ref, "messageID": message_id, "top": top, + "includeSystem": include_system_messages, + "nextLink": next_link, }, ) - return [ - Message( - ID=message["ID"], - Content=message["Content"], - ContentType=MessageContentType(message["ContentType"]), - CreatedDateTime=message["CreatedDateTime"], - From=MessageFrom( - UserID=message["From"]["UserID"], - DisplayName=message["From"]["DisplayName"], - ), - ReplyCount=message["ReplyCount"], - ) - for message in response - ] + return MessageCollection( + Messages=[ + Message( + ID=msg["ID"], + Content=msg["Content"], + ContentType=MessageContentType(msg["ContentType"]), + CreatedDateTime=msg["CreatedDateTime"], + From=MessageFrom( + UserID=msg["From"]["UserID"], + DisplayName=msg["From"]["DisplayName"], + ), + ReplyCount=msg["ReplyCount"], + ) + for msg in response["Messages"] + ], + NextLink=response.get("NextLink"), + ) def get_message_reply( self, diff --git a/python/tests/test_channels.py b/python/tests/test_channels.py index ccb8047..5eb8831 100644 --- a/python/tests/test_channels.py +++ b/python/tests/test_channels.py @@ -220,20 +220,20 @@ def test_list_replies_integration(httpserver): message_id=data.messages[data.channels[data.teams[0].ID][0].ID][1].ID, ) - assert len(replies) == len(data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID]) - assert replies[0].ID == data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][0].ID - assert replies[0].Content == data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][0].Content - assert replies[0].ContentType == MessageContentType(data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][0].ContentType) - assert replies[0].From.UserID == data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][0].From.UserID - assert replies[0].From.DisplayName == data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][0].From.DisplayName - assert replies[0].CreatedDateTime == data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][0].CreatedDateTime - - assert replies[1].ID == data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][1].ID - assert replies[1].Content == data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][1].Content - assert replies[1].ContentType == MessageContentType(data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][1].ContentType) - assert replies[1].From.UserID == data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][1].From.UserID - assert replies[1].From.DisplayName == data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][1].From.DisplayName - assert replies[1].CreatedDateTime == data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][1].CreatedDateTime + assert len(replies.Messages) == len(data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID]) + assert replies.Messages[0].ID == data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][0].ID + assert replies.Messages[0].Content == data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][0].Content + assert replies.Messages[0].ContentType == MessageContentType(data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][0].ContentType) + assert replies.Messages[0].From.UserID == data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][0].From.UserID + assert replies.Messages[0].From.DisplayName == data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][0].From.DisplayName + assert replies.Messages[0].CreatedDateTime == data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][0].CreatedDateTime + + assert replies.Messages[1].ID == data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][1].ID + assert replies.Messages[1].Content == data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][1].Content + assert replies.Messages[1].ContentType == MessageContentType(data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][1].ContentType) + assert replies.Messages[1].From.UserID == data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][1].From.UserID + assert replies.Messages[1].From.DisplayName == data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][1].From.DisplayName + assert replies.Messages[1].CreatedDateTime == data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][1].CreatedDateTime finally: client.close() From b3d9bea4034af1be30e517e8065d990bc61567c5 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Wed, 7 Jan 2026 23:05:27 +0100 Subject: [PATCH 63/99] chore: adjusted list messages in chats --- python/teams_lib_pzsp2_z1/services/chats.py | 43 ++++++++++++++------- python/tests/test_chats.py | 36 ++++++++--------- 2 files changed, 46 insertions(+), 33 deletions(-) diff --git a/python/teams_lib_pzsp2_z1/services/chats.py b/python/teams_lib_pzsp2_z1/services/chats.py index 86e53a1..11f6c07 100644 --- a/python/teams_lib_pzsp2_z1/services/chats.py +++ b/python/teams_lib_pzsp2_z1/services/chats.py @@ -10,6 +10,7 @@ from teams_lib_pzsp2_z1.model.message import ( Message, MessageBody, + MessageCollection, MessageContentType, MessageFrom, ) @@ -194,11 +195,18 @@ def update_group_chat_topic(self, group_chat_ref: str, new_topic: str) -> Chat: Topic=response["Topic"], ) - def list_messages(self, chat_ref: ChatRef) -> list[Message]: + def list_messages( + self, + chat_ref: ChatRef, + include_system_messages: bool = False, + next_link: str | None = None, + ) -> list[Message]: """Retrieves messages from a specific chat. Args: chat_ref (ChatRef): The chat reference object containing the ID/Name and ChatType. + include_system_messages (bool): If True, includes system-generated messages. Defaults to False. + next_link (str | None): A link for pagination to fetch the next set of messages. Defaults to None. Returns: list[Message]: A list of messages from the chat history. @@ -211,23 +219,28 @@ def list_messages(self, chat_ref: ChatRef) -> list[Message]: "ref": chat_ref.Ref, "type": chat_ref.Type.value, }, + "includeSystem": include_system_messages, + "nextLink": next_link, }, ) - return [ - Message( - ID=message["ID"], - Content=message["Content"], - ContentType=MessageContentType(message["ContentType"]), - From=MessageFrom( - UserID=message["From"]["UserID"], - DisplayName=message["From"]["DisplayName"], - ), - CreatedDateTime=message["CreatedDateTime"], - ReplyCount=message["ReplyCount"], - ) - for message in messages - ] + return MessageCollection( + Messages=[ + Message( + ID=msg["ID"], + Content=msg["Content"], + ContentType=MessageContentType(msg["ContentType"]), + CreatedDateTime=msg["CreatedDateTime"], + From=MessageFrom( + UserID=msg["From"]["UserID"], + DisplayName=msg["From"]["DisplayName"], + ), + ReplyCount=msg["ReplyCount"], + ) + for msg in messages["Messages"] + ], + NextLink=messages.get("NextLink"), + ) def send_message(self, chat_ref: ChatRef, body: MessageBody) -> Message: """Sends a message to a chat. diff --git a/python/tests/test_chats.py b/python/tests/test_chats.py index 8966601..2ef176c 100644 --- a/python/tests/test_chats.py +++ b/python/tests/test_chats.py @@ -202,30 +202,30 @@ def test_list_messeges_in_chat_integration(httpserver): try: init_fake_client(client, httpserver.url_for("")) - messages = client.chats.list_messages( + collection = client.chats.list_messages( chat_ref=ChatRef( Ref=data.group_chats[0].Topic, Type=ChatType.GROUP, ) ) - assert len(messages) == len(data.chat_messages[data.group_chats[0].ID]) - - assert messages[0].ID == data.chat_messages[data.group_chats[0].ID][0].ID - assert messages[0].Content == data.chat_messages[data.group_chats[0].ID][0].Content - assert messages[0].ContentType == MessageContentType(data.chat_messages[data.group_chats[0].ID][0].ContentType) - assert messages[0].From.UserID == data.chat_messages[data.group_chats[0].ID][0].From.UserID - assert messages[0].From.DisplayName == data.chat_messages[data.group_chats[0].ID][0].From.DisplayName - assert messages[0].ReplyCount == data.chat_messages[data.group_chats[0].ID][0].ReplyCount - assert messages[0].CreatedDateTime == data.chat_messages[data.group_chats[0].ID][0].CreatedDateTime - - assert messages[1].ID == data.chat_messages[data.group_chats[0].ID][1].ID - assert messages[1].Content == data.chat_messages[data.group_chats[0].ID][1].Content - assert messages[1].ContentType == MessageContentType(data.chat_messages[data.group_chats[0].ID][1].ContentType) - assert messages[1].From.UserID == data.chat_messages[data.group_chats[0].ID][1].From.UserID - assert messages[1].From.DisplayName == data.chat_messages[data.group_chats[0].ID][1].From.DisplayName - assert messages[1].ReplyCount == data.chat_messages[data.group_chats[0].ID][1].ReplyCount - assert messages[1].CreatedDateTime == data.chat_messages[data.group_chats[0].ID][1].CreatedDateTime + assert len(collection.Messages) == len(data.chat_messages[data.group_chats[0].ID]) + + assert collection.Messages[0].ID == data.chat_messages[data.group_chats[0].ID][0].ID + assert collection.Messages[0].Content == data.chat_messages[data.group_chats[0].ID][0].Content + assert collection.Messages[0].ContentType == MessageContentType(data.chat_messages[data.group_chats[0].ID][0].ContentType) + assert collection.Messages[0].From.UserID == data.chat_messages[data.group_chats[0].ID][0].From.UserID + assert collection.Messages[0].From.DisplayName == data.chat_messages[data.group_chats[0].ID][0].From.DisplayName + assert collection.Messages[0].ReplyCount == data.chat_messages[data.group_chats[0].ID][0].ReplyCount + assert collection.Messages[0].CreatedDateTime == data.chat_messages[data.group_chats[0].ID][0].CreatedDateTime + + assert collection.Messages[1].ID == data.chat_messages[data.group_chats[0].ID][1].ID + assert collection.Messages[1].Content == data.chat_messages[data.group_chats[0].ID][1].Content + assert collection.Messages[1].ContentType == MessageContentType(data.chat_messages[data.group_chats[0].ID][1].ContentType) + assert collection.Messages[1].From.UserID == data.chat_messages[data.group_chats[0].ID][1].From.UserID + assert collection.Messages[1].From.DisplayName == data.chat_messages[data.group_chats[0].ID][1].From.DisplayName + assert collection.Messages[1].ReplyCount == data.chat_messages[data.group_chats[0].ID][1].ReplyCount + assert collection.Messages[1].CreatedDateTime == data.chat_messages[data.group_chats[0].ID][1].CreatedDateTime finally: client.close() From 6ec5db7bfbee09cf66e0dcce0fe299ea83eca6c8 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Wed, 7 Jan 2026 23:42:32 +0100 Subject: [PATCH 64/99] chore: adjusted create team from template to lib changes --- python/teams_lib_pzsp2_z1/services/teams.py | 8 +++++++- python/tests/fake_server/data.py | 18 +++++++++++++++--- python/tests/fake_server/setup.py | 17 +++++++++++++++-- python/tests/test_teams.py | 4 ++-- 4 files changed, 39 insertions(+), 8 deletions(-) diff --git a/python/teams_lib_pzsp2_z1/services/teams.py b/python/teams_lib_pzsp2_z1/services/teams.py index 9c1976c..830b248 100644 --- a/python/teams_lib_pzsp2_z1/services/teams.py +++ b/python/teams_lib_pzsp2_z1/services/teams.py @@ -75,11 +75,14 @@ def create_via_group( Visibility=response["Visibility"], ) - def create_from_template( + def create_from_template( # noqa: PLR0913 self, display_name: str, description: str, owners: list[str], + members: list[str] | None = None, + visibility: str = "public", + include_me: bool = True, ) -> str: response = self.client.execute( cmd_type="request", @@ -88,6 +91,9 @@ def create_from_template( "displayName": display_name, "description": description, "owners": owners, + "members": members, + "visibility": visibility, + "includeMe": include_me, }, ) return response diff --git a/python/tests/fake_server/data.py b/python/tests/fake_server/data.py index 525b4fe..733aa0b 100644 --- a/python/tests/fake_server/data.py +++ b/python/tests/fake_server/data.py @@ -134,6 +134,7 @@ def __init__(self) -> None: self.newTeamName = "New Team" self.newGroupMailNickname = "new-team-nickname" self.newTeamVisibility = "private" + self.newTeamID = "f47ac10b-58cc-4372-a567-0e02b2c3d479" # Init Templates (re-assigning to ensure freshness if needed, though default_factory handles it) self.newChannelName = "New Channel" @@ -254,9 +255,8 @@ def get_createTeamViaGroup_response(self, group_id: str) -> dict: } def get_createTeamFromTemplate_response(self, request_json: dict) -> dict: - new_team_id = "team-from-template-001" new_team = Team( - ID=new_team_id, + ID=self.newTeamID, DisplayName=request_json.get("displayName"), Description=request_json.get("description"), IsArchived=False, @@ -264,7 +264,19 @@ def get_createTeamFromTemplate_response(self, request_json: dict) -> dict: ) self.teams.append(new_team) - return {"id": new_team_id} + # self.newTeamID == "f47ac10b-58cc-4372-a567-0e02b2c3d479" + op_id = "00000000-0000-0000-0000-000000000000" + + return { + "status": 202, + "headers": { + "Location": f"/teams('{self.newTeamID}')/operations('{op_id}')", + "Content-Location": f"/teams('{self.newTeamID}')", + "Content-Type": "application/json", + "Content-Length": "0" + }, + "body": "" + } def get_archiveTeam_response(self, team_id: str) -> dict: team = self._find_team(team_id) diff --git a/python/tests/fake_server/setup.py b/python/tests/fake_server/setup.py index 5b5579c..4e6033b 100644 --- a/python/tests/fake_server/setup.py +++ b/python/tests/fake_server/setup.py @@ -353,10 +353,23 @@ def handle_chats_request(req): httpserver.expect_request( "/v1.0/teams", method="POST" + ).respond_with_handler(lambda req: Response( + response=data.get_createTeamFromTemplate_response(req.json)["body"], + status=data.get_createTeamFromTemplate_response(req.json)["status"], + headers=data.get_createTeamFromTemplate_response(req.json)["headers"] + )) + + # POST /teams/{team_id}/members + httpserver.expect_request( + re.compile(r"^/v1.0/teams/([^/]+)/members"), + method="POST" ).respond_with_handler(lambda req: make_log_response( req, - data.get_createTeamFromTemplate_response(req.json), - "Create Team from Template (POST)" + data.get_add_member_response( + re.search(r"/teams/([^/]+)/members", req.path).group(1), + req.json + ), + "Add Team Member" )) # ========================================== diff --git a/python/tests/test_teams.py b/python/tests/test_teams.py index 9ab7d75..9fd61ca 100644 --- a/python/tests/test_teams.py +++ b/python/tests/test_teams.py @@ -113,13 +113,13 @@ def test_create_team_from_template_integration(httpserver): try: init_fake_client(client, httpserver.url_for("")) - msg = client.teams.create_from_template( + id = client.teams.create_from_template( display_name=data.newTeamName, description="A team created from a template", owners=["user-123-abc"], ) - assert msg == "id will be given later (async)" + assert id == data.newTeamID finally: client.close() From cf8e8129b676c12acc392a80c244a2caa5185613 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Thu, 8 Jan 2026 10:27:40 +0100 Subject: [PATCH 65/99] docs: added docstrings for team service --- python/teams_lib_pzsp2_z1/services/teams.py | 119 ++++++++++++++++++-- python/tests/test_teams.py | 8 +- 2 files changed, 113 insertions(+), 14 deletions(-) diff --git a/python/teams_lib_pzsp2_z1/services/teams.py b/python/teams_lib_pzsp2_z1/services/teams.py index 830b248..e04f738 100644 --- a/python/teams_lib_pzsp2_z1/services/teams.py +++ b/python/teams_lib_pzsp2_z1/services/teams.py @@ -1,14 +1,39 @@ +""" +This module contains the service for managing Microsoft Teams via the Go backend. +""" + from teams_lib_pzsp2_z1.model.team import Team, UpdateTeam from teams_lib_pzsp2_z1.services.base_service import BaseService class TeamsService(BaseService): - def get(self, teamRef: str) -> Team: + """Service for managing Microsoft Teams lifecycle and properties via the Go backend. + + This class acts as a high-level Python wrapper. It delegates logical operations + to the underlying Go library. + + **Concepts:** + * **References**: The `teamRef` argument accepts either the Team UUID or the + Team Display Name. The Go backend resolves these automatically. + * **Context**: Operations are executed on behalf of the authenticated user + (derived from the MSAL context). + * **Caching**: If the Go client was initialized with cache enabled, this service + will transparently use cached team references to reduce API calls. + """ + + def get(self, team_ref: str) -> Team: + """Retrieves a specific team by its reference. + + Args: + team_ref (str): The reference to the team (UUID or Display Name). + Returns: + Team: The requested team object containing details like ID, DisplayName, and Visibility. + """ response = self.client.execute( cmd_type="request", method="getTeam", params={ - "teamRef": teamRef, + "teamRef": team_ref, }, ) @@ -21,6 +46,11 @@ def get(self, teamRef: str) -> Team: ) def list_my_joined(self) -> list[Team]: + """Lists all teams that the authenticated user has joined. + + Returns: + list[Team]: A list of team objects the user is a member of. + """ response = self.client.execute( cmd_type="request", method="listMyJoined", @@ -37,8 +67,20 @@ def list_my_joined(self) -> list[Team]: for team in response ] - def update(self, teamRef: str, update: UpdateTeam) -> Team: - params = {"teamRef": teamRef} + def update(self, team_ref: str, update: UpdateTeam) -> Team: + """Applies updates to a team's properties. + + Only fields that are not None in the `update` object will be modified. + + Args: + team_ref (str): The reference to the team (UUID or Display Name). + update (UpdateTeam): An object containing the fields to update (e.g., DisplayName). + + Returns: + Team: The updated team object. + """ + params = {"teamRef": team_ref} + # Filter out None values to perform a PATCH-like update update_dict = {k: v for k, v in update.__dict__.items() if v is not None} params["team"] = update_dict @@ -58,6 +100,16 @@ def update(self, teamRef: str, update: UpdateTeam) -> Team: def create_via_group( self, display_name: str, mail_nickname: str, visibility: str ) -> Team: + """Creates a new team associated with a standard Microsoft 365 group. + + Args: + display_name (str): The name of the new team. + mail_nickname (str): The mail alias for the underlying group. + visibility (str): The privacy level ('Public' or 'Private'). + + Returns: + Team: The newly created team object. + """ response = self.client.execute( cmd_type="request", method="createTeamViaGroup", @@ -84,6 +136,21 @@ def create_from_template( # noqa: PLR0913 visibility: str = "public", include_me: bool = True, ) -> str: + """Creates a new team based on a template. + + This operation is often asynchronous. + + Args: + display_name (str): The name of the team. + description (str): A brief description of the team. + owners (list[str]): A list of user references (IDs or emails) to be owners. + members (list[str] | None, optional): A list of user references to be members. Defaults to None. + visibility (str, optional): The privacy level ('public' or 'private'). Defaults to "public". + include_me (bool, optional): Whether to add the authenticated user as an owner. Defaults to True. + + Returns: + str: The ID of the newly created team (or the async operation ID). + """ response = self.client.execute( cmd_type="request", method="createTeamFromTemplate", @@ -98,38 +165,70 @@ def create_from_template( # noqa: PLR0913 ) return response - def archive(self, teamRef: str, spo_read_only_from_members: bool) -> bool: + def archive(self, team_ref: str, spo_read_only_from_members: bool) -> bool: + """Archives a team. + + Args: + team_ref (str): The reference to the team (UUID or Display Name). + spo_read_only_from_members (bool): If True, sets the associated SharePoint site + to read-only for members. + + Returns: + bool: True if the team was successfully archived. + """ response = self.client.execute( cmd_type="request", method="archiveTeam", params={ - "teamRef": teamRef, + "teamRef": team_ref, "spoReadOnlyFromMembers": spo_read_only_from_members, }, ) return response == "archived" - def unarchive(self, teamRef: str) -> bool: + def unarchive(self, team_ref: str) -> bool: + """Restores an archived team to an active state. + + Args: + team_ref (str): The reference to the team (UUID or Display Name). + Returns: + bool: True if the team was successfully unarchived. + """ response = self.client.execute( cmd_type="request", method="unarchiveTeam", params={ - "teamRef": teamRef, + "teamRef": team_ref, }, ) return response == "unarchived" - def delete(self, teamRef: str) -> bool: + def delete(self, team_ref: str) -> bool: + """Deletes a team (soft delete). + + Args: + team_ref (str): The reference to the team (UUID or Display Name). + Returns: + bool: True if the delete request was successful. + """ response = self.client.execute( cmd_type="request", method="deleteTeam", params={ - "teamRef": teamRef, + "teamRef": team_ref, }, ) return response == "deleted" def restore_deleted(self, deleted_team_ID: str) -> str: + """Restores a previously deleted team. + + Args: + deleted_team_ID (str): The ID of the deleted group/team. + + Returns: + str: The ID of the restored team/group. + """ response = self.client.execute( cmd_type="request", method="restoreDeletedTeam", diff --git a/python/tests/test_teams.py b/python/tests/test_teams.py index 9fd61ca..c9e6bb7 100644 --- a/python/tests/test_teams.py +++ b/python/tests/test_teams.py @@ -67,7 +67,7 @@ def test_update_team_integration(httpserver): updated_description = "Updated team description" team = client.teams.update( - teamRef=data.teams[0].DisplayName, + team_ref=data.teams[0].DisplayName, update=UpdateTeam( description=updated_description, ), @@ -134,7 +134,7 @@ def test_archive_team_integration(httpserver): init_fake_client(client, httpserver.url_for("")) success = client.teams.archive( - teamRef=data.teams[0].DisplayName, + team_ref=data.teams[0].DisplayName, spo_read_only_from_members=True, ) @@ -153,7 +153,7 @@ def test_unarchive_team_integration(httpserver): init_fake_client(client, httpserver.url_for("")) success = client.teams.unarchive( - teamRef=data.teams[2].DisplayName, + team_ref=data.teams[2].DisplayName, ) assert success is True @@ -171,7 +171,7 @@ def test_delete_team_integration(httpserver): init_fake_client(client, httpserver.url_for("")) success = client.teams.delete( - teamRef=data.teams[0].DisplayName, + team_ref=data.teams[0].DisplayName, ) assert success is True From 426bcbb1478e8f2477f4ddc7e74d5b7c17113cac Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Thu, 8 Jan 2026 10:33:43 +0100 Subject: [PATCH 66/99] docs: added comments for channel and chat models --- python/teams_lib_pzsp2_z1/model/channel.py | 18 +- python/teams_lib_pzsp2_z1/model/chat.py | 47 +++- .../teams_lib_pzsp2_z1/services/channels.py | 24 +- python/teams_lib_pzsp2_z1/services/chats.py | 64 ++--- python/tests/fake_server/data.py | 68 +++--- python/tests/fake_server/setup.py | 2 +- python/tests/test_channels.py | 162 ++++++------- python/tests/test_chats.py | 222 +++++++++--------- 8 files changed, 326 insertions(+), 281 deletions(-) diff --git a/python/teams_lib_pzsp2_z1/model/channel.py b/python/teams_lib_pzsp2_z1/model/channel.py index ace8276..32959ce 100644 --- a/python/teams_lib_pzsp2_z1/model/channel.py +++ b/python/teams_lib_pzsp2_z1/model/channel.py @@ -3,6 +3,18 @@ @dataclass class Channel: - ID: str - Name: str - IsGeneral: bool + """Represents a Microsoft Teams channel. + + This model serves as a data container for channel properties returned + by the underlying Go library. + + Attributes: + id (str): The unique identifier of the channel. + name (str): The display name of the channel (e.g., "General", "Development"). + is_general (bool): Indicates whether this is the default 'General' channel. + The General channel typically cannot be deleted or renamed. + """ + + id: str + name: str + is_general: bool diff --git a/python/teams_lib_pzsp2_z1/model/chat.py b/python/teams_lib_pzsp2_z1/model/chat.py index 63be741..fe99164 100644 --- a/python/teams_lib_pzsp2_z1/model/chat.py +++ b/python/teams_lib_pzsp2_z1/model/chat.py @@ -3,19 +3,52 @@ class ChatType(Enum): - ONEONONE = "one-on-one" + """Represents the type of chat in Microsoft Teams. + + Attributes: + ONE_ON_ONE: Represents a direct conversation between two users. + GROUP: Represents a conversation with multiple participants. + """ + + ONE_ON_ONE = "one-on-one" GROUP = "group" @dataclass class Chat: - ID: str - Type: ChatType - IsHidden: bool - Topic: str + """Represents a chat in Microsoft Teams. + + Attributes: + id (str): The unique identifier of the chat. + chat_type (ChatType): The type of the conversation (One-on-One or Group). + is_hidden (bool): Indicates whether the chat is hidden for the user. + topic (Optional[str]): The subject or topic of the chat. + This is typically None for one-on-one chats. + """ + + id: str + type: ChatType + is_hidden: bool + topic: str | None @dataclass class ChatRef: - Ref: str - Type: ChatType + """Represents a polymorphic reference to a chat used for API lookups. + + This class corresponds to `GroupChatRef` and `OneOnOneChatRef` in the Go library. + The interpretation of the `ref` field depends on the `chat_type`. + + Attributes: + ref (str): The identifier string. + * If **chat_type is GROUP**: Can be a unique Chat ID or a Chat Topic. + **Note**: Using a topic may lead to ambiguities if multiple group chats + share the same name. + * If **chat_type is ONE_ON_ONE**: Can be a unique Chat ID or the + Recipient's reference (User ID or Email). + **Note**: For resolution by User ID/Email to work, the chat must + already exist between the logged-in user and the recipient. + chat_type (ChatType): Determines how the `ref` string is interpreted. + """ + ref: str + type: ChatType diff --git a/python/teams_lib_pzsp2_z1/services/channels.py b/python/teams_lib_pzsp2_z1/services/channels.py index 0af5dc0..8bc0207 100644 --- a/python/teams_lib_pzsp2_z1/services/channels.py +++ b/python/teams_lib_pzsp2_z1/services/channels.py @@ -49,9 +49,9 @@ def list_channels(self, team_ref: str) -> list[Channel]: ) return [ Channel( - ID=channel["ID"], - Name=channel["Name"], - IsGeneral=(True if channel["IsGeneral"] else False), + id=channel["ID"], + name=channel["Name"], + is_general=(True if channel["IsGeneral"] else False), ) for channel in response ] @@ -79,9 +79,9 @@ def get(self, team_ref: str, channel_ref: str) -> Channel: ) return Channel( - ID=response["ID"], - Name=response["Name"], - IsGeneral=(True if response["IsGeneral"] else False), + id=response["ID"], + name=response["Name"], + is_general=(True if response["IsGeneral"] else False), ) def create_standard(self, team_ref: str, display_name: str) -> Channel: @@ -104,9 +104,9 @@ def create_standard(self, team_ref: str, display_name: str) -> Channel: ) return Channel( - ID=response["ID"], - Name=response["Name"], - IsGeneral=(True if response["IsGeneral"] else False), + id=response["ID"], + name=response["Name"], + is_general=(True if response["IsGeneral"] else False), ) def create_private( @@ -140,9 +140,9 @@ def create_private( ) return Channel( - ID=response["ID"], - Name=response["Name"], - IsGeneral=(True if response["IsGeneral"] else False), + id=response["ID"], + name=response["Name"], + is_general=(True if response["IsGeneral"] else False), ) def delete(self, team_ref: str, channel_ref: str) -> bool: diff --git a/python/teams_lib_pzsp2_z1/services/chats.py b/python/teams_lib_pzsp2_z1/services/chats.py index 11f6c07..07dd0a3 100644 --- a/python/teams_lib_pzsp2_z1/services/chats.py +++ b/python/teams_lib_pzsp2_z1/services/chats.py @@ -55,10 +55,10 @@ def create_one_on_one(self, recipient_ref: str) -> Chat: ) return Chat( - ID=response["ID"], - Type=ChatType(response["Type"]), - IsHidden=(True if response["IsHidden"] else False), - Topic=response["Topic"], + id=response["ID"], + type=ChatType(response["Type"]), + is_hidden=(True if response["IsHidden"] else False), + topic=response["Topic"], ) def create_group_chat( @@ -85,10 +85,10 @@ def create_group_chat( ) return Chat( - ID=response["ID"], - Type=ChatType(response["Type"]), - IsHidden=(True if response["IsHidden"] else False), - Topic=response["Topic"], + id=response["ID"], + type=ChatType(response["Type"]), + is_hidden=(True if response["IsHidden"] else False), + topic=response["Topic"], ) def add_member_to_group_chat(self, group_chat_ref: str, user_ref: str) -> Member: @@ -189,10 +189,10 @@ def update_group_chat_topic(self, group_chat_ref: str, new_topic: str) -> Chat: ) return Chat( - ID=response["ID"], - Type=ChatType(response["Type"]), - IsHidden=(True if response["IsHidden"] else False), - Topic=response["Topic"], + id=response["ID"], + type=ChatType(response["Type"]), + is_hidden=(True if response["IsHidden"] else False), + topic=response["Topic"], ) def list_messages( @@ -216,8 +216,8 @@ def list_messages( method="listMessagesInChat", params={ "chatRef": { - "ref": chat_ref.Ref, - "type": chat_ref.Type.value, + "ref": chat_ref.ref, + "type": chat_ref.type.value, }, "includeSystem": include_system_messages, "nextLink": next_link, @@ -257,8 +257,8 @@ def send_message(self, chat_ref: ChatRef, body: MessageBody) -> Message: method="sendMessageInChat", params={ "chatRef": { - "ref": chat_ref.Ref, - "type": chat_ref.Type.value, + "ref": chat_ref.ref, + "type": chat_ref.type.value, }, "body": { "content": body.Content, @@ -296,8 +296,8 @@ def delete_message(self, chat_ref: ChatRef, message_id: str) -> bool: method="deleteMessageInChat", params={ "chatRef": { - "ref": chat_ref.Ref, - "type": chat_ref.Type.value, + "ref": chat_ref.ref, + "type": chat_ref.type.value, }, "messageID": message_id, }, @@ -320,8 +320,8 @@ def get_message(self, chat_ref: ChatRef, message_id: str) -> Message: method="getMessageInChat", params={ "chatRef": { - "ref": chat_ref.Ref, - "type": chat_ref.Type.value, + "ref": chat_ref.ref, + "type": chat_ref.type.value, }, "messageID": message_id, }, @@ -361,10 +361,10 @@ def list_my_joined(self, chat_type: ChatType | None = None) -> list[Chat]: return [ Chat( - ID=chat["ID"], - Type=ChatType(chat["Type"]), - IsHidden=(True if chat["IsHidden"] else False), - Topic=chat["Topic"], + id=chat["ID"], + type=ChatType(chat["Type"]), + is_hidden=(True if chat["IsHidden"] else False), + topic=chat["Topic"], ) for chat in chats ] @@ -425,8 +425,8 @@ def list_pinned_messages(self, chat_ref: ChatRef) -> list[Message]: method="listPinnedMessagesInChat", params={ "chatRef": { - "ref": chat_ref.Ref, - "type": chat_ref.Type.value, + "ref": chat_ref.ref, + "type": chat_ref.type.value, }, }, ) @@ -461,8 +461,8 @@ def pin_message(self, chat_ref: ChatRef, message_id: str) -> bool: method="pinMessageInChat", params={ "chatRef": { - "ref": chat_ref.Ref, - "type": chat_ref.Type.value, + "ref": chat_ref.ref, + "type": chat_ref.type.value, }, "messageID": message_id, }, @@ -485,8 +485,8 @@ def unpin_message(self, chat_ref: ChatRef, message_id: str) -> bool: method="unpinMessageInChat", params={ "chatRef": { - "ref": chat_ref.Ref, - "type": chat_ref.Type.value, + "ref": chat_ref.ref, + "type": chat_ref.type.value, }, "messageID": message_id, }, @@ -514,8 +514,8 @@ def get_mentions(self, chat_ref: ChatRef, raw_mentions: list[str]) -> list[Menti method="getMentionsInChat", params={ "chatRef": { - "ref": chat_ref.Ref, - "type": chat_ref.Type.value, + "ref": chat_ref.ref, + "type": chat_ref.type.value, }, "rawMentions": raw_mentions, }, diff --git a/python/tests/fake_server/data.py b/python/tests/fake_server/data.py index 733aa0b..0a2581a 100644 --- a/python/tests/fake_server/data.py +++ b/python/tests/fake_server/data.py @@ -52,7 +52,7 @@ class FakeServerData: Role="owner", Email="newmember@example.com" )) newChatTemplate: Chat = field(default_factory=lambda: Chat( - ID="new-chat-001", Type=ChatType.GROUP, IsHidden=False, Topic="New Chat Topic" + id="new-chat-001", type=ChatType.GROUP, is_hidden=False, topic="New Chat Topic" )) updatedGroupChatTopic: str = "Updated Chat Topic" @@ -68,8 +68,8 @@ def __init__(self) -> None: # --- Channels --- self.channels = { "team-123-abc": [ - Channel(ID="19:123123@thread.tacv2", Name="Something", IsGeneral=False), - Channel(ID="19:999999@thread.tacv2", Name="Development", IsGeneral=False), + Channel(id="19:123123@thread.tacv2", name="Something", is_general=False), + Channel(id="19:999999@thread.tacv2", name="Development", is_general=False), ], } @@ -108,11 +108,11 @@ def __init__(self) -> None: # --- Chats & Chat Members/Messages --- self.group_chats = [ - Chat(ID="chat-123-abc", Type=ChatType.GROUP, IsHidden=False, Topic="Project Discussion"), - Chat(ID="chat-456-def", Type=ChatType.GROUP, IsHidden=True, Topic="Secret Plans"), + Chat(id="chat-123-abc", type=ChatType.GROUP, is_hidden=False, topic="Project Discussion"), + Chat(id="chat-456-def", type=ChatType.GROUP, is_hidden=True, topic="Secret Plans"), ] self.oneonone_chats = [ - Chat(ID="chat-789-ghi", Type=ChatType.ONEONONE, IsHidden=False, Topic=""), + Chat(id="chat-789-ghi", type=ChatType.ONE_ON_ONE, is_hidden=False, topic=""), ] self.group_chat_members = { @@ -152,7 +152,7 @@ def __init__(self) -> None: Role="owner", Email="newmember@example.com" ) self.newChatTemplate = Chat( - ID="new-chat-001", Type=ChatType.GROUP, IsHidden=False, Topic="New Chat Topic" + id="new-chat-001", type=ChatType.GROUP, is_hidden=False, topic="New Chat Topic" ) # --- Helpers --- @@ -160,7 +160,7 @@ def _find_team(self, team_id: str) -> Optional[Team]: return next((t for t in self.teams if t.ID == team_id), None) def _find_channel(self, team_id: str, channel_id: str) -> Optional[Channel]: - return next((c for c in self.channels.get(team_id, []) if c.ID == channel_id), None) + return next((c for c in self.channels.get(team_id, []) if c.id == channel_id), None) # ========================================== # TEAMS & GROUPS @@ -324,9 +324,9 @@ def get_listChannels_response(self, team_id: str) -> dict: "value": [ { ODATA_TYPE: "#microsoft.graph.channel", - "id": channel.ID, - "displayName": channel.Name, - "isGeneral": channel.IsGeneral, + "id": channel.id, + "displayName": channel.name, + "isGeneral": channel.is_general, "membershipType": "standard", "email": "" } @@ -339,9 +339,9 @@ def get_channel_response(self, team_id: str, channel_id: str) -> Optional[dict]: if not channel: return None return { - "id": channel.ID, - "displayName": channel.Name, - "isGeneral": channel.IsGeneral, + "id": channel.id, + "displayName": channel.name, + "isGeneral": channel.is_general, } def get_create_channel_response(self, team_id: str, request_json: dict) -> dict: @@ -349,9 +349,9 @@ def get_create_channel_response(self, team_id: str, request_json: dict) -> dict: description = request_json.get("description", display_name) new_channel = Channel( - ID=self.newChannelID, - Name=display_name, - IsGeneral=False, + id=self.newChannelID, + name=display_name, + is_general=False, ) if team_id not in self.channels: @@ -359,10 +359,10 @@ def get_create_channel_response(self, team_id: str, request_json: dict) -> dict: self.channels[team_id].append(new_channel) return { - "id": new_channel.ID, - "displayName": new_channel.Name, + "id": new_channel.id, + "displayName": new_channel.name, "description": description, - "isGeneral": new_channel.IsGeneral, + "isGeneral": new_channel.is_general, "membershipType": "standard" } @@ -540,16 +540,16 @@ def get_remove_member_response(self, team_id: str, channel_id: str, member_id: s # ========================================== def get_list_chats_response(self, chat_type: ChatType) -> dict: - chats = self.oneonone_chats if chat_type == ChatType.ONEONONE else self.group_chats + chats = self.oneonone_chats if chat_type == ChatType.ONE_ON_ONE else self.group_chats return { ODATA_CONTEXT: f"{GRAPH_URL}#chats", ODATA_COUNT: len(chats), "value": [ { - "id": chat.ID, - "chatType": "oneOnOne" if chat.Type == ChatType.ONEONONE else "group", - "isHiddenForAllMembers": chat.IsHidden, - "topic": chat.Topic, + "id": chat.id, + "chatType": "oneOnOne" if chat.type == ChatType.ONE_ON_ONE else "group", + "isHiddenForAllMembers": chat.is_hidden, + "topic": chat.topic, } for chat in chats ], @@ -559,25 +559,25 @@ def get_create_chat_response(self, request_json: dict) -> dict: is_one_on_one = request_json["chatType"] == "oneOnOne" response = { ODATA_CONTEXT: f"{GRAPH_URL}#chats/$entity", - "id": self.newChatTemplate.ID, + "id": self.newChatTemplate.id, "chatType": "oneOnOne" if is_one_on_one else "group", - "isHiddenForAllMembers": self.newChatTemplate.IsHidden, - "topic": None if is_one_on_one else self.newChatTemplate.Topic, + "isHiddenForAllMembers": self.newChatTemplate.is_hidden, + "topic": None if is_one_on_one else self.newChatTemplate.topic, } return response def get_update_group_chat_topic_response(self, chat_id: str, request_json: dict) -> dict: - chat = next((c for c in self.group_chats if c.ID == chat_id), None) + chat = next((c for c in self.group_chats if c.id == chat_id), None) if not chat: return {} - chat.Topic = request_json.get("topic", chat.Topic) + chat.topic = request_json.get("topic", chat.topic) return { ODATA_CONTEXT: f"{GRAPH_URL}#chats('{chat_id}')", - "id": chat.ID, + "id": chat.id, "chatType": "group", - "isHiddenForAllMembers": chat.IsHidden, - "topic": chat.Topic, + "isHiddenForAllMembers": chat.is_hidden, + "topic": chat.topic, } # ========================================== @@ -678,7 +678,7 @@ def get_send_message_in_chat_response(self, chat_id: str, request_json: dict) -> } def get_all_messeges_in_chats_response(self) -> dict: - target_chat_id = self.group_chats[0].ID + target_chat_id = self.group_chats[0].id return { ODATA_CONTEXT: f"{GRAPH_URL}#Collection(chatMessage)", ODATA_COUNT: len(self.chat_messages), diff --git a/python/tests/fake_server/setup.py b/python/tests/fake_server/setup.py index 4e6033b..f1f2ff2 100644 --- a/python/tests/fake_server/setup.py +++ b/python/tests/fake_server/setup.py @@ -89,7 +89,7 @@ def handle_chats_request(req): chat_type = match.group(1) if chat_type == "oneOnOne": - chat_type = ChatType.ONEONONE + chat_type = ChatType.ONE_ON_ONE elif chat_type == "group": chat_type = ChatType.GROUP diff --git a/python/tests/test_channels.py b/python/tests/test_channels.py index 5eb8831..62ba85b 100644 --- a/python/tests/test_channels.py +++ b/python/tests/test_channels.py @@ -21,13 +21,13 @@ def test_list_channels_integration(httpserver): assert len(channels) == len(data.channels[data.teams[0].ID]) - assert channels[0].Name == data.channels[data.teams[0].ID][0].Name - assert channels[0].ID == data.channels[data.teams[0].ID][0].ID - assert channels[0].IsGeneral == data.channels[data.teams[0].ID][0].IsGeneral + assert channels[0].name == data.channels[data.teams[0].ID][0].name + assert channels[0].id == data.channels[data.teams[0].ID][0].id + assert channels[0].is_general == data.channels[data.teams[0].ID][0].is_general - assert channels[1].Name == data.channels[data.teams[0].ID][1].Name - assert channels[1].ID == data.channels[data.teams[0].ID][1].ID - assert channels[1].IsGeneral == data.channels[data.teams[0].ID][1].IsGeneral + assert channels[1].name == data.channels[data.teams[0].ID][1].name + assert channels[1].id == data.channels[data.teams[0].ID][1].id + assert channels[1].is_general == data.channels[data.teams[0].ID][1].is_general finally: client.close() @@ -43,12 +43,12 @@ def test_get_channel_integration(httpserver): channel = client.channels.get( team_ref=data.teams[0].DisplayName, - channel_ref=data.channels[data.teams[0].ID][1].Name, + channel_ref=data.channels[data.teams[0].ID][1].name, ) - assert channel.Name == data.channels[data.teams[0].ID][1].Name - assert channel.ID == data.channels[data.teams[0].ID][1].ID - assert channel.IsGeneral == data.channels[data.teams[0].ID][1].IsGeneral + assert channel.name == data.channels[data.teams[0].ID][1].name + assert channel.id == data.channels[data.teams[0].ID][1].id + assert channel.is_general == data.channels[data.teams[0].ID][1].is_general finally: client.close() @@ -67,9 +67,9 @@ def test_create_standard_channel_integration(httpserver): display_name=data.newChannelName, ) - assert channel.Name == data.newChannelName - assert channel.ID == data.newChannelID - assert channel.IsGeneral is False + assert channel.name == data.newChannelName + assert channel.id == data.newChannelID + assert channel.is_general is False finally: client.close() @@ -90,9 +90,9 @@ def test_create_private_channel_integration(httpserver): owner_refs=[], ) - assert channel.Name == data.newChannelName - assert channel.ID == data.newChannelID - assert channel.IsGeneral is False + assert channel.name == data.newChannelName + assert channel.id == data.newChannelID + assert channel.is_general is False finally: client.close() @@ -108,7 +108,7 @@ def test_delete_channel_integration(httpserver): success = client.channels.delete( team_ref=data.teams[0].DisplayName, - channel_ref=data.channels[data.teams[0].ID][1].Name, + channel_ref=data.channels[data.teams[0].ID][1].name, ) assert success is True @@ -127,7 +127,7 @@ def test_send_message_integration(httpserver): message = client.channels.send_message( team_ref=data.teams[0].DisplayName, - channel_ref=data.channels[data.teams[0].ID][0].Name, + channel_ref=data.channels[data.teams[0].ID][0].name, body=MessageBody( Content=data.newMessageTemplate.Content, ContentType=MessageContentType.TEXT, @@ -157,25 +157,25 @@ def test_list_messages_integration(httpserver): collection = client.channels.list_messages( team_ref=data.teams[0].DisplayName, - channel_ref=data.channels[data.teams[0].ID][0].Name, + channel_ref=data.channels[data.teams[0].ID][0].name, ) - assert len(collection.Messages) == len(data.messages[data.channels[data.teams[0].ID][0].ID]) - assert collection.Messages[0].ID == data.messages[data.channels[data.teams[0].ID][0].ID][0].ID - assert collection.Messages[0].Content == data.messages[data.channels[data.teams[0].ID][0].ID][0].Content - assert collection.Messages[0].ContentType == MessageContentType(data.messages[data.channels[data.teams[0].ID][0].ID][0].ContentType) - assert collection.Messages[0].From.UserID == data.messages[data.channels[data.teams[0].ID][0].ID][0].From.UserID - assert collection.Messages[0].From.DisplayName == data.messages[data.channels[data.teams[0].ID][0].ID][0].From.DisplayName - assert collection.Messages[0].ReplyCount == data.messages[data.channels[data.teams[0].ID][0].ID][0].ReplyCount - assert collection.Messages[0].CreatedDateTime == data.messages[data.channels[data.teams[0].ID][0].ID][0].CreatedDateTime - - assert collection.Messages[1].ID == data.messages[data.channels[data.teams[0].ID][0].ID][1].ID - assert collection.Messages[1].Content == data.messages[data.channels[data.teams[0].ID][0].ID][1].Content - assert collection.Messages[1].ContentType == MessageContentType(data.messages[data.channels[data.teams[0].ID][0].ID][1].ContentType) - assert collection.Messages[1].From.UserID == data.messages[data.channels[data.teams[0].ID][0].ID][1].From.UserID - assert collection.Messages[1].From.DisplayName == data.messages[data.channels[data.teams[0].ID][0].ID][1].From.DisplayName - assert collection.Messages[1].ReplyCount == data.messages[data.channels[data.teams[0].ID][0].ID][1].ReplyCount - assert collection.Messages[1].CreatedDateTime == data.messages[data.channels[data.teams[0].ID][0].ID][1].CreatedDateTime + assert len(collection.Messages) == len(data.messages[data.channels[data.teams[0].ID][0].id]) + assert collection.Messages[0].ID == data.messages[data.channels[data.teams[0].ID][0].id][0].ID + assert collection.Messages[0].Content == data.messages[data.channels[data.teams[0].ID][0].id][0].Content + assert collection.Messages[0].ContentType == MessageContentType(data.messages[data.channels[data.teams[0].ID][0].id][0].ContentType) + assert collection.Messages[0].From.UserID == data.messages[data.channels[data.teams[0].ID][0].id][0].From.UserID + assert collection.Messages[0].From.DisplayName == data.messages[data.channels[data.teams[0].ID][0].id][0].From.DisplayName + assert collection.Messages[0].ReplyCount == data.messages[data.channels[data.teams[0].ID][0].id][0].ReplyCount + assert collection.Messages[0].CreatedDateTime == data.messages[data.channels[data.teams[0].ID][0].id][0].CreatedDateTime + + assert collection.Messages[1].ID == data.messages[data.channels[data.teams[0].ID][0].id][1].ID + assert collection.Messages[1].Content == data.messages[data.channels[data.teams[0].ID][0].id][1].Content + assert collection.Messages[1].ContentType == MessageContentType(data.messages[data.channels[data.teams[0].ID][0].id][1].ContentType) + assert collection.Messages[1].From.UserID == data.messages[data.channels[data.teams[0].ID][0].id][1].From.UserID + assert collection.Messages[1].From.DisplayName == data.messages[data.channels[data.teams[0].ID][0].id][1].From.DisplayName + assert collection.Messages[1].ReplyCount == data.messages[data.channels[data.teams[0].ID][0].id][1].ReplyCount + assert collection.Messages[1].CreatedDateTime == data.messages[data.channels[data.teams[0].ID][0].id][1].CreatedDateTime finally: client.close() @@ -190,17 +190,17 @@ def test_get_message_integration(httpserver): message = client.channels.get_message( team_ref=data.teams[0].DisplayName, - channel_ref=data.channels[data.teams[0].ID][0].Name, - message_id=data.messages[data.channels[data.teams[0].ID][0].ID][0].ID, + channel_ref=data.channels[data.teams[0].ID][0].name, + message_id=data.messages[data.channels[data.teams[0].ID][0].id][0].ID, ) - assert message.ID == data.messages[data.channels[data.teams[0].ID][0].ID][0].ID - assert message.Content == data.messages[data.channels[data.teams[0].ID][0].ID][0].Content - assert message.ContentType == MessageContentType(data.messages[data.channels[data.teams[0].ID][0].ID][0].ContentType) - assert message.From.UserID == data.messages[data.channels[data.teams[0].ID][0].ID][0].From.UserID - assert message.From.DisplayName == data.messages[data.channels[data.teams[0].ID][0].ID][0].From.DisplayName - assert message.ReplyCount == data.messages[data.channels[data.teams[0].ID][0].ID][0].ReplyCount - assert message.CreatedDateTime == data.messages[data.channels[data.teams[0].ID][0].ID][0].CreatedDateTime + assert message.ID == data.messages[data.channels[data.teams[0].ID][0].id][0].ID + assert message.Content == data.messages[data.channels[data.teams[0].ID][0].id][0].Content + assert message.ContentType == MessageContentType(data.messages[data.channels[data.teams[0].ID][0].id][0].ContentType) + assert message.From.UserID == data.messages[data.channels[data.teams[0].ID][0].id][0].From.UserID + assert message.From.DisplayName == data.messages[data.channels[data.teams[0].ID][0].id][0].From.DisplayName + assert message.ReplyCount == data.messages[data.channels[data.teams[0].ID][0].id][0].ReplyCount + assert message.CreatedDateTime == data.messages[data.channels[data.teams[0].ID][0].id][0].CreatedDateTime finally: client.close() @@ -216,24 +216,24 @@ def test_list_replies_integration(httpserver): replies = client.channels.list_message_replies( team_ref=data.teams[0].DisplayName, - channel_ref=data.channels[data.teams[0].ID][0].Name, - message_id=data.messages[data.channels[data.teams[0].ID][0].ID][1].ID, + channel_ref=data.channels[data.teams[0].ID][0].name, + message_id=data.messages[data.channels[data.teams[0].ID][0].id][1].ID, ) - assert len(replies.Messages) == len(data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID]) - assert replies.Messages[0].ID == data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][0].ID - assert replies.Messages[0].Content == data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][0].Content - assert replies.Messages[0].ContentType == MessageContentType(data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][0].ContentType) - assert replies.Messages[0].From.UserID == data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][0].From.UserID - assert replies.Messages[0].From.DisplayName == data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][0].From.DisplayName - assert replies.Messages[0].CreatedDateTime == data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][0].CreatedDateTime - - assert replies.Messages[1].ID == data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][1].ID - assert replies.Messages[1].Content == data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][1].Content - assert replies.Messages[1].ContentType == MessageContentType(data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][1].ContentType) - assert replies.Messages[1].From.UserID == data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][1].From.UserID - assert replies.Messages[1].From.DisplayName == data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][1].From.DisplayName - assert replies.Messages[1].CreatedDateTime == data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][1].CreatedDateTime + assert len(replies.Messages) == len(data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].ID]) + assert replies.Messages[0].ID == data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].ID][0].ID + assert replies.Messages[0].Content == data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].ID][0].Content + assert replies.Messages[0].ContentType == MessageContentType(data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].ID][0].ContentType) + assert replies.Messages[0].From.UserID == data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].ID][0].From.UserID + assert replies.Messages[0].From.DisplayName == data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].ID][0].From.DisplayName + assert replies.Messages[0].CreatedDateTime == data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].ID][0].CreatedDateTime + + assert replies.Messages[1].ID == data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].ID][1].ID + assert replies.Messages[1].Content == data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].ID][1].Content + assert replies.Messages[1].ContentType == MessageContentType(data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].ID][1].ContentType) + assert replies.Messages[1].From.UserID == data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].ID][1].From.UserID + assert replies.Messages[1].From.DisplayName == data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].ID][1].From.DisplayName + assert replies.Messages[1].CreatedDateTime == data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].ID][1].CreatedDateTime finally: client.close() @@ -249,17 +249,17 @@ def test_get_reply_integration(httpserver): reply = client.channels.get_message_reply( team_ref=data.teams[0].DisplayName, - channel_ref=data.channels[data.teams[0].ID][0].Name, - message_id=data.messages[data.channels[data.teams[0].ID][0].ID][1].ID, - reply_id=data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][0].ID, + channel_ref=data.channels[data.teams[0].ID][0].name, + message_id=data.messages[data.channels[data.teams[0].ID][0].id][1].ID, + reply_id=data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].ID][0].ID, ) - assert reply.ID == data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][0].ID - assert reply.Content == data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][0].Content - assert reply.ContentType == MessageContentType(data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][0].ContentType) - assert reply.From.UserID == data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][0].From.UserID - assert reply.From.DisplayName == data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][0].From.DisplayName - assert reply.CreatedDateTime == data.replies[data.messages[data.channels[data.teams[0].ID][0].ID][1].ID][0].CreatedDateTime + assert reply.ID == data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].ID][0].ID + assert reply.Content == data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].ID][0].Content + assert reply.ContentType == MessageContentType(data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].ID][0].ContentType) + assert reply.From.UserID == data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].ID][0].From.UserID + assert reply.From.DisplayName == data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].ID][0].From.DisplayName + assert reply.CreatedDateTime == data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].ID][0].CreatedDateTime finally: client.close() @@ -274,11 +274,11 @@ def test_list_members_integration(httpserver): team_id = data.teams[0].ID channel = data.channels[team_id][0] - channel_id = channel.ID + channel_id = channel.id members = client.channels.list_members( team_ref=data.teams[0].DisplayName, - channel_ref=channel.Name, + channel_ref=channel.name, ) expected_members = data.members[team_id][channel_id] @@ -313,7 +313,7 @@ def test_add_member_integration(httpserver): member = client.channels.add_member( team_ref=data.teams[0].DisplayName, - channel_ref=data.channels[data.teams[0].ID][1].Name, + channel_ref=data.channels[data.teams[0].ID][1].name, user_ref=data.newMemberTemplate.DisplayName, is_owner=True if data.newMemberTemplate.Role == "owner" else False, ) @@ -338,16 +338,16 @@ def test_update_member_role_integration(httpserver): member = client.channels.update_member_role( team_ref=data.teams[0].DisplayName, - channel_ref=data.channels[data.teams[0].ID][0].Name, - user_ref=data.members[data.teams[0].ID][data.channels[data.teams[0].ID][0].ID][1].Email, + channel_ref=data.channels[data.teams[0].ID][0].name, + user_ref=data.members[data.teams[0].ID][data.channels[data.teams[0].ID][0].id][1].Email, is_owner=True ) - assert member.UserID == data.members[data.teams[0].ID][data.channels[data.teams[0].ID][0].ID][1].UserID - assert member.DisplayName == data.members[data.teams[0].ID][data.channels[data.teams[0].ID][0].ID][1].DisplayName + assert member.UserID == data.members[data.teams[0].ID][data.channels[data.teams[0].ID][0].id][1].UserID + assert member.DisplayName == data.members[data.teams[0].ID][data.channels[data.teams[0].ID][0].id][1].DisplayName assert member.Role == "owner" - assert member.Email == data.members[data.teams[0].ID][data.channels[data.teams[0].ID][0].ID][1].Email - assert member.ID == data.members[data.teams[0].ID][data.channels[data.teams[0].ID][0].ID][1].ID + assert member.Email == data.members[data.teams[0].ID][data.channels[data.teams[0].ID][0].id][1].Email + assert member.ID == data.members[data.teams[0].ID][data.channels[data.teams[0].ID][0].id][1].ID finally: client.close() @@ -362,8 +362,8 @@ def test_remove_member_integration(httpserver): success = client.channels.remove_member( team_ref=data.teams[0].DisplayName, - channel_ref=data.channels[data.teams[0].ID][0].Name, - user_ref=data.members[data.teams[0].ID][data.channels[data.teams[0].ID][0].ID][1].Email, + channel_ref=data.channels[data.teams[0].ID][0].name, + user_ref=data.members[data.teams[0].ID][data.channels[data.teams[0].ID][0].id][1].Email, ) assert success is True @@ -381,7 +381,7 @@ def test_get_mention_integration(httpserver): mention = client.channels.get_mentions( team_ref=data.teams[0].DisplayName, - channel_ref=data.channels[data.teams[0].ID][0].Name, + channel_ref=data.channels[data.teams[0].ID][0].name, raw_mentions=MentionKind.TEAM.value, ) diff --git a/python/tests/test_chats.py b/python/tests/test_chats.py index 2ef176c..adaf71e 100644 --- a/python/tests/test_chats.py +++ b/python/tests/test_chats.py @@ -22,15 +22,15 @@ def test_list_my_group_chats_integration(httpserver): chats = client.chats.list_my_joined(ChatType.GROUP) assert len(chats) == len(data.group_chats) - assert chats[0].ID == data.group_chats[0].ID - assert chats[0].Type == data.group_chats[0].Type - assert chats[0].Topic == data.group_chats[0].Topic - assert chats[0].IsHidden == data.group_chats[0].IsHidden + assert chats[0].id == data.group_chats[0].id + assert chats[0].type == data.group_chats[0].type + assert chats[0].topic == data.group_chats[0].topic + assert chats[0].is_hidden == data.group_chats[0].is_hidden - assert chats[1].ID == data.group_chats[1].ID - assert chats[1].Type == data.group_chats[1].Type - assert chats[1].Topic == data.group_chats[1].Topic - assert chats[1].IsHidden == data.group_chats[1].IsHidden + assert chats[1].id == data.group_chats[1].id + assert chats[1].type == data.group_chats[1].type + assert chats[1].topic == data.group_chats[1].topic + assert chats[1].is_hidden == data.group_chats[1].is_hidden finally: client.close() @@ -43,13 +43,13 @@ def test_list_my_one_on_one_chats_integration(httpserver): try: init_fake_client(client, httpserver.url_for("")) - chats = client.chats.list_my_joined(ChatType.ONEONONE) + chats = client.chats.list_my_joined(ChatType.ONE_ON_ONE) assert len(chats) == len(data.oneonone_chats) - assert chats[0].ID == data.oneonone_chats[0].ID - assert chats[0].Type == data.oneonone_chats[0].Type - assert chats[0].Topic == data.oneonone_chats[0].Topic - assert chats[0].IsHidden == data.oneonone_chats[0].IsHidden + assert chats[0].id == data.oneonone_chats[0].id + assert chats[0].type == data.oneonone_chats[0].type + assert chats[0].topic == data.oneonone_chats[0].topic + assert chats[0].is_hidden == data.oneonone_chats[0].is_hidden finally: client.close() @@ -64,7 +64,7 @@ def test_create_group_chat_integration(httpserver): init_fake_client(client, httpserver.url_for("")) chat = client.chats.create_group_chat( - topic=data.newChatTemplate.Topic, + topic=data.newChatTemplate.topic, recipient_refs=[ data.users[0].Email, data.users[1].Email, @@ -72,10 +72,10 @@ def test_create_group_chat_integration(httpserver): include_me=True, ) - assert chat.ID == data.newChatTemplate.ID - assert chat.Type == ChatType.GROUP - assert chat.Topic == data.newChatTemplate.Topic - assert chat.IsHidden == data.newChatTemplate.IsHidden + assert chat.id == data.newChatTemplate.id + assert chat.type == ChatType.GROUP + assert chat.topic == data.newChatTemplate.topic + assert chat.is_hidden == data.newChatTemplate.is_hidden finally: client.close() @@ -92,10 +92,10 @@ def test_create_one_on_one_chat_integration(httpserver): recipient_ref=data.users[0].Email, ) - assert chat.ID == data.newChatTemplate.ID - assert chat.Type == ChatType.ONEONONE - assert chat.Topic == None - assert chat.IsHidden == data.newChatTemplate.IsHidden + assert chat.id == data.newChatTemplate.id + assert chat.type == ChatType.ONE_ON_ONE + assert chat.topic == None + assert chat.is_hidden == data.newChatTemplate.is_hidden finally: client.close() @@ -109,23 +109,23 @@ def test_list_group_chat_members_integration(httpserver): try: init_fake_client(client, httpserver.url_for("")) - members = client.chats.list_group_chat_members(data.group_chats[0].Topic) + members = client.chats.list_group_chat_members(data.group_chats[0].topic) - assert len(members) == len(data.group_chat_members[data.group_chats[0].ID]) + assert len(members) == len(data.group_chat_members[data.group_chats[0].id]) - assert members[0].ID == data.group_chat_members[data.group_chats[0].ID][0].ID - assert members[0].UserID == data.group_chat_members[data.group_chats[0].ID][0].UserID - assert members[0].DisplayName == data.group_chat_members[data.group_chats[0].ID][0].DisplayName - if data.group_chat_members[data.group_chats[0].ID][0].Role == "owner": + assert members[0].ID == data.group_chat_members[data.group_chats[0].id][0].ID + assert members[0].UserID == data.group_chat_members[data.group_chats[0].id][0].UserID + assert members[0].DisplayName == data.group_chat_members[data.group_chats[0].id][0].DisplayName + if data.group_chat_members[data.group_chats[0].id][0].Role == "owner": assert members[0].Role == "owner" - assert members[0].Email == data.group_chat_members[data.group_chats[0].ID][0].Email + assert members[0].Email == data.group_chat_members[data.group_chats[0].id][0].Email - assert members[1].ID == data.group_chat_members[data.group_chats[0].ID][1].ID - assert members[1].UserID == data.group_chat_members[data.group_chats[0].ID][1].UserID - assert members[1].DisplayName == data.group_chat_members[data.group_chats[0].ID][1].DisplayName - if data.group_chat_members[data.group_chats[0].ID][1].Role == "owner": + assert members[1].ID == data.group_chat_members[data.group_chats[0].id][1].ID + assert members[1].UserID == data.group_chat_members[data.group_chats[0].id][1].UserID + assert members[1].DisplayName == data.group_chat_members[data.group_chats[0].id][1].DisplayName + if data.group_chat_members[data.group_chats[0].id][1].Role == "owner": assert members[1].Role == "owner" - assert members[1].Email == data.group_chat_members[data.group_chats[0].ID][1].Email + assert members[1].Email == data.group_chat_members[data.group_chats[0].id][1].Email finally: client.close() @@ -140,7 +140,7 @@ def test_add_group_chat_member_integration(httpserver): init_fake_client(client, httpserver.url_for("")) member = client.chats.add_member_to_group_chat( - group_chat_ref=data.group_chats[0].Topic, + group_chat_ref=data.group_chats[0].topic, user_ref=data.newMemberTemplate.Email, ) @@ -162,8 +162,8 @@ def test_remove_group_chat_member_integration(httpserver): init_fake_client(client, httpserver.url_for("")) result = client.chats.remove_member_from_group_chat( - group_chat_ref=data.group_chats[0].Topic, - member_ref=data.group_chat_members[data.group_chats[0].ID][0].Email, + group_chat_ref=data.group_chats[0].topic, + member_ref=data.group_chat_members[data.group_chats[0].id][0].Email, ) assert result is True @@ -181,14 +181,14 @@ def test_update_group_chat_topic_integration(httpserver): init_fake_client(client, httpserver.url_for("")) chat = client.chats.update_group_chat_topic( - group_chat_ref=data.group_chats[0].Topic, + group_chat_ref=data.group_chats[0].topic, new_topic=data.updatedGroupChatTopic, ) - assert chat.ID == data.group_chats[0].ID - assert chat.Type == data.group_chats[0].Type - assert chat.Topic == data.updatedGroupChatTopic - assert chat.IsHidden == data.group_chats[0].IsHidden + assert chat.id == data.group_chats[0].id + assert chat.type == data.group_chats[0].type + assert chat.topic == data.updatedGroupChatTopic + assert chat.is_hidden == data.group_chats[0].is_hidden finally: client.close() @@ -204,28 +204,28 @@ def test_list_messeges_in_chat_integration(httpserver): collection = client.chats.list_messages( chat_ref=ChatRef( - Ref=data.group_chats[0].Topic, - Type=ChatType.GROUP, + ref=data.group_chats[0].topic, + type=ChatType.GROUP, ) ) - assert len(collection.Messages) == len(data.chat_messages[data.group_chats[0].ID]) + assert len(collection.Messages) == len(data.chat_messages[data.group_chats[0].id]) - assert collection.Messages[0].ID == data.chat_messages[data.group_chats[0].ID][0].ID - assert collection.Messages[0].Content == data.chat_messages[data.group_chats[0].ID][0].Content - assert collection.Messages[0].ContentType == MessageContentType(data.chat_messages[data.group_chats[0].ID][0].ContentType) - assert collection.Messages[0].From.UserID == data.chat_messages[data.group_chats[0].ID][0].From.UserID - assert collection.Messages[0].From.DisplayName == data.chat_messages[data.group_chats[0].ID][0].From.DisplayName - assert collection.Messages[0].ReplyCount == data.chat_messages[data.group_chats[0].ID][0].ReplyCount - assert collection.Messages[0].CreatedDateTime == data.chat_messages[data.group_chats[0].ID][0].CreatedDateTime + assert collection.Messages[0].ID == data.chat_messages[data.group_chats[0].id][0].ID + assert collection.Messages[0].Content == data.chat_messages[data.group_chats[0].id][0].Content + assert collection.Messages[0].ContentType == MessageContentType(data.chat_messages[data.group_chats[0].id][0].ContentType) + assert collection.Messages[0].From.UserID == data.chat_messages[data.group_chats[0].id][0].From.UserID + assert collection.Messages[0].From.DisplayName == data.chat_messages[data.group_chats[0].id][0].From.DisplayName + assert collection.Messages[0].ReplyCount == data.chat_messages[data.group_chats[0].id][0].ReplyCount + assert collection.Messages[0].CreatedDateTime == data.chat_messages[data.group_chats[0].id][0].CreatedDateTime - assert collection.Messages[1].ID == data.chat_messages[data.group_chats[0].ID][1].ID - assert collection.Messages[1].Content == data.chat_messages[data.group_chats[0].ID][1].Content - assert collection.Messages[1].ContentType == MessageContentType(data.chat_messages[data.group_chats[0].ID][1].ContentType) - assert collection.Messages[1].From.UserID == data.chat_messages[data.group_chats[0].ID][1].From.UserID - assert collection.Messages[1].From.DisplayName == data.chat_messages[data.group_chats[0].ID][1].From.DisplayName - assert collection.Messages[1].ReplyCount == data.chat_messages[data.group_chats[0].ID][1].ReplyCount - assert collection.Messages[1].CreatedDateTime == data.chat_messages[data.group_chats[0].ID][1].CreatedDateTime + assert collection.Messages[1].ID == data.chat_messages[data.group_chats[0].id][1].ID + assert collection.Messages[1].Content == data.chat_messages[data.group_chats[0].id][1].Content + assert collection.Messages[1].ContentType == MessageContentType(data.chat_messages[data.group_chats[0].id][1].ContentType) + assert collection.Messages[1].From.UserID == data.chat_messages[data.group_chats[0].id][1].From.UserID + assert collection.Messages[1].From.DisplayName == data.chat_messages[data.group_chats[0].id][1].From.DisplayName + assert collection.Messages[1].ReplyCount == data.chat_messages[data.group_chats[0].id][1].ReplyCount + assert collection.Messages[1].CreatedDateTime == data.chat_messages[data.group_chats[0].id][1].CreatedDateTime finally: client.close() @@ -240,8 +240,8 @@ def test_send_message_in_chat_integration(httpserver): message = client.chats.send_message( chat_ref=ChatRef( - Ref=data.group_chats[0].Topic, - Type=ChatType.GROUP, + ref=data.group_chats[0].topic, + type=ChatType.GROUP, ), body=MessageBody( Content=data.newMessageTemplate.Content, @@ -271,10 +271,10 @@ def test_delete_message_in_chat_integration(httpserver): result = client.chats.delete_message( chat_ref=ChatRef( - Ref=data.group_chats[0].Topic, - Type=ChatType.GROUP, + ref=data.group_chats[0].topic, + type=ChatType.GROUP, ), - message_id=data.chat_messages[data.group_chats[0].ID][0].ID, + message_id=data.chat_messages[data.group_chats[0].id][0].ID, ) assert result is True @@ -292,19 +292,19 @@ def test_get_message_in_chat_integration(httpserver): message = client.chats.get_message( chat_ref=ChatRef( - Ref=data.group_chats[0].Topic, - Type=ChatType.GROUP, + ref=data.group_chats[0].topic, + type=ChatType.GROUP, ), - message_id=data.chat_messages[data.group_chats[0].ID][0].ID, + message_id=data.chat_messages[data.group_chats[0].id][0].ID, ) - assert message.ID == data.chat_messages[data.group_chats[0].ID][0].ID - assert message.Content == data.chat_messages[data.group_chats[0].ID][0].Content - assert message.ContentType == MessageContentType(data.chat_messages[data.group_chats[0].ID][0].ContentType) - assert message.From.UserID == data.chat_messages[data.group_chats[0].ID][0].From.UserID - assert message.From.DisplayName == data.chat_messages[data.group_chats[0].ID][0].From.DisplayName - assert message.ReplyCount == data.chat_messages[data.group_chats[0].ID][0].ReplyCount - assert message.CreatedDateTime == data.chat_messages[data.group_chats[0].ID][0].CreatedDateTime + assert message.ID == data.chat_messages[data.group_chats[0].id][0].ID + assert message.Content == data.chat_messages[data.group_chats[0].id][0].Content + assert message.ContentType == MessageContentType(data.chat_messages[data.group_chats[0].id][0].ContentType) + assert message.From.UserID == data.chat_messages[data.group_chats[0].id][0].From.UserID + assert message.From.DisplayName == data.chat_messages[data.group_chats[0].id][0].From.DisplayName + assert message.ReplyCount == data.chat_messages[data.group_chats[0].id][0].ReplyCount + assert message.CreatedDateTime == data.chat_messages[data.group_chats[0].id][0].CreatedDateTime finally: client.close() @@ -323,23 +323,23 @@ def test_get_all_messages_in_chat_integration(httpserver): top=50, ) - assert len(messages) == len(data.chat_messages[data.group_chats[0].ID]) + assert len(messages) == len(data.chat_messages[data.group_chats[0].id]) - assert messages[0].ID == data.chat_messages[data.group_chats[0].ID][0].ID - assert messages[0].Content == data.chat_messages[data.group_chats[0].ID][0].Content - assert messages[0].ContentType == MessageContentType(data.chat_messages[data.group_chats[0].ID][0].ContentType) - assert messages[0].From.UserID == data.chat_messages[data.group_chats[0].ID][0].From.UserID - assert messages[0].From.DisplayName == data.chat_messages[data.group_chats[0].ID][0].From.DisplayName + assert messages[0].ID == data.chat_messages[data.group_chats[0].id][0].ID + assert messages[0].Content == data.chat_messages[data.group_chats[0].id][0].Content + assert messages[0].ContentType == MessageContentType(data.chat_messages[data.group_chats[0].id][0].ContentType) + assert messages[0].From.UserID == data.chat_messages[data.group_chats[0].id][0].From.UserID + assert messages[0].From.DisplayName == data.chat_messages[data.group_chats[0].id][0].From.DisplayName assert messages[0].ReplyCount == 0 - assert messages[0].CreatedDateTime == data.chat_messages[data.group_chats[0].ID][0].CreatedDateTime + assert messages[0].CreatedDateTime == data.chat_messages[data.group_chats[0].id][0].CreatedDateTime - assert messages[1].ID == data.chat_messages[data.group_chats[0].ID][1].ID - assert messages[1].Content == data.chat_messages[data.group_chats[0].ID][1].Content - assert messages[1].ContentType == MessageContentType(data.chat_messages[data.group_chats[0].ID][1].ContentType) - assert messages[1].From.UserID == data.chat_messages[data.group_chats[0].ID][1].From.UserID - assert messages[1].From.DisplayName == data.chat_messages[data.group_chats[0].ID][1].From.DisplayName + assert messages[1].ID == data.chat_messages[data.group_chats[0].id][1].ID + assert messages[1].Content == data.chat_messages[data.group_chats[0].id][1].Content + assert messages[1].ContentType == MessageContentType(data.chat_messages[data.group_chats[0].id][1].ContentType) + assert messages[1].From.UserID == data.chat_messages[data.group_chats[0].id][1].From.UserID + assert messages[1].From.DisplayName == data.chat_messages[data.group_chats[0].id][1].From.DisplayName assert messages[1].ReplyCount == 0 - assert messages[1].CreatedDateTime == data.chat_messages[data.group_chats[0].ID][1].CreatedDateTime + assert messages[1].CreatedDateTime == data.chat_messages[data.group_chats[0].id][1].CreatedDateTime finally: client.close() @@ -355,28 +355,28 @@ def test_list_pinned_messages_integration(httpserver): messages = client.chats.list_pinned_messages( chat_ref=ChatRef( - Ref=data.group_chats[0].Topic, - Type=ChatType.GROUP, + ref=data.group_chats[0].topic, + type=ChatType.GROUP, ) ) - assert len(messages) == len(data.chat_messages[data.group_chats[0].ID]) + assert len(messages) == len(data.chat_messages[data.group_chats[0].id]) - assert messages[0].ID == data.chat_messages[data.group_chats[0].ID][0].ID - assert messages[0].Content == data.chat_messages[data.group_chats[0].ID][0].Content - assert messages[0].ContentType == MessageContentType(data.chat_messages[data.group_chats[0].ID][0].ContentType) - assert messages[0].From.UserID == data.chat_messages[data.group_chats[0].ID][0].From.UserID - assert messages[0].From.DisplayName == data.chat_messages[data.group_chats[0].ID][0].From.DisplayName + assert messages[0].ID == data.chat_messages[data.group_chats[0].id][0].ID + assert messages[0].Content == data.chat_messages[data.group_chats[0].id][0].Content + assert messages[0].ContentType == MessageContentType(data.chat_messages[data.group_chats[0].id][0].ContentType) + assert messages[0].From.UserID == data.chat_messages[data.group_chats[0].id][0].From.UserID + assert messages[0].From.DisplayName == data.chat_messages[data.group_chats[0].id][0].From.DisplayName assert messages[0].ReplyCount == 0 - assert messages[0].CreatedDateTime == data.chat_messages[data.group_chats[0].ID][0].CreatedDateTime + assert messages[0].CreatedDateTime == data.chat_messages[data.group_chats[0].id][0].CreatedDateTime - assert messages[1].ID == data.chat_messages[data.group_chats[0].ID][1].ID - assert messages[1].Content == data.chat_messages[data.group_chats[0].ID][1].Content - assert messages[1].ContentType == MessageContentType(data.chat_messages[data.group_chats[0].ID][1].ContentType) - assert messages[1].From.UserID == data.chat_messages[data.group_chats[0].ID][1].From.UserID - assert messages[1].From.DisplayName == data.chat_messages[data.group_chats[0].ID][1].From.DisplayName + assert messages[1].ID == data.chat_messages[data.group_chats[0].id][1].ID + assert messages[1].Content == data.chat_messages[data.group_chats[0].id][1].Content + assert messages[1].ContentType == MessageContentType(data.chat_messages[data.group_chats[0].id][1].ContentType) + assert messages[1].From.UserID == data.chat_messages[data.group_chats[0].id][1].From.UserID + assert messages[1].From.DisplayName == data.chat_messages[data.group_chats[0].id][1].From.DisplayName assert messages[1].ReplyCount == 0 - assert messages[1].CreatedDateTime == data.chat_messages[data.group_chats[0].ID][1].CreatedDateTime + assert messages[1].CreatedDateTime == data.chat_messages[data.group_chats[0].id][1].CreatedDateTime finally: client.close() @@ -392,10 +392,10 @@ def test_pin_message_in_chat_integration(httpserver): result = client.chats.pin_message( chat_ref=ChatRef( - Ref=data.group_chats[0].Topic, - Type=ChatType.GROUP, + ref=data.group_chats[0].topic, + type=ChatType.GROUP, ), - message_id=data.chat_messages[data.group_chats[0].ID][0].ID, + message_id=data.chat_messages[data.group_chats[0].id][0].ID, ) assert result is True @@ -414,10 +414,10 @@ def test_unpin_message_in_chat_integration(httpserver): result = client.chats.unpin_message( chat_ref=ChatRef( - Ref=data.group_chats[0].Topic, - Type=ChatType.GROUP, + ref=data.group_chats[0].topic, + type=ChatType.GROUP, ), - message_id=data.chat_messages[data.group_chats[0].ID][0].ID, + message_id=data.chat_messages[data.group_chats[0].id][0].ID, ) assert result is True @@ -436,8 +436,8 @@ def test_get_mention_integration(httpserver): mention = client.chats.get_mentions( chat_ref=ChatRef( - Ref=data.group_chats[0].Topic, - Type=ChatType.GROUP, + ref=data.group_chats[0].topic, + type=ChatType.GROUP, ), raw_mentions=MentionKind.EVERYONE.value, ) @@ -445,7 +445,7 @@ def test_get_mention_integration(httpserver): assert mention[0].Kind == MentionKind.EVERYONE.value assert mention[0].AtID == 0 assert mention[0].Text == "Everyone" - assert mention[0].TargetID == data.group_chats[0].ID + assert mention[0].TargetID == data.group_chats[0].id finally: client.close() From 63d26bbd2b3fb8f256c8881001324d1a133866f1 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Thu, 8 Jan 2026 10:37:08 +0100 Subject: [PATCH 67/99] docs: added docstrings for member and mention models --- python/teams_lib_pzsp2_z1/model/member.py | 22 ++++-- python/teams_lib_pzsp2_z1/model/mention.py | 35 ++++++++- python/teams_lib_pzsp2_z1/services/chats.py | 28 +++---- python/tests/fake_server/data.py | 84 ++++++++++----------- python/tests/test_channels.py | 62 +++++++-------- python/tests/test_chats.py | 52 ++++++------- 6 files changed, 161 insertions(+), 122 deletions(-) diff --git a/python/teams_lib_pzsp2_z1/model/member.py b/python/teams_lib_pzsp2_z1/model/member.py index 4d8b88e..09b721b 100644 --- a/python/teams_lib_pzsp2_z1/model/member.py +++ b/python/teams_lib_pzsp2_z1/model/member.py @@ -3,8 +3,20 @@ @dataclass class Member: - ID: str - UserID: str - DisplayName: str - Role: str - Email: str + """Represents a member of a Microsoft Teams channel, direct chat, or team. + + Attributes: + id (str): The unique identifier of the membership object itself. + **Note:** This is distinct from the `user_id`. + user_id (str): The unique Azure Active Directory (AAD) identifier of the user. + display_name (str): The visible name of the user. + role (str): The role of the user within the specific context + (e.g., "owner", "member"). + email (str): The email address of the user. + """ + + id: str + user_id: str + display_name: str + role: str + email: str diff --git a/python/teams_lib_pzsp2_z1/model/mention.py b/python/teams_lib_pzsp2_z1/model/mention.py index 361df1b..273be4f 100644 --- a/python/teams_lib_pzsp2_z1/model/mention.py +++ b/python/teams_lib_pzsp2_z1/model/mention.py @@ -3,14 +3,41 @@ class MentionKind(Enum): + """Represents the kind of mention in a Microsoft Teams message. + + Attributes: + USER: Represents a mention of a specific user. + CHANNEL: Represents a mention of the current channel. + TEAM: Represents a mention of the entire team. + EVERYONE: Represents a mention of all participants. + **Note:** This is applicable to Group Chats only. + """ + USER = "user" CHANNEL = "channel" TEAM = "team" EVERYONE = "everyone" + @dataclass class Mention: - Kind: MentionKind - AtID: int - Text: str - TargetID: str + """Represents a mention entity within a Microsoft Teams message. + + This object maps a specific entity (User, Channel, etc.) to a placeholder + in the message content. + + Attributes: + kind (MentionKind): The type of the mention. + at_id (int): The internal identifier used to link this mention to the + message content. + *Example:* If the message body contains `@John`, + this field should be `0`. + text (str): The text to display in the message (e.g., "@John Doe"). + target_id (str): The unique identifier (UUID) of the entity being mentioned + (User ID, Channel ID, or Team ID). + """ + + kind: MentionKind + at_id: int + text: str + target_id: str diff --git a/python/teams_lib_pzsp2_z1/services/chats.py b/python/teams_lib_pzsp2_z1/services/chats.py index 07dd0a3..1b9705c 100644 --- a/python/teams_lib_pzsp2_z1/services/chats.py +++ b/python/teams_lib_pzsp2_z1/services/chats.py @@ -111,11 +111,11 @@ def add_member_to_group_chat(self, group_chat_ref: str, user_ref: str) -> Member ) return Member( - ID=member["ID"], - DisplayName=member["DisplayName"], - UserID=member["UserID"], - Role=member["Role"], - Email=member["Email"], + id=member["ID"], + display_name=member["DisplayName"], + user_id=member["UserID"], + role=member["Role"], + email=member["Email"], ) def remove_member_from_group_chat( @@ -160,11 +160,11 @@ def list_group_chat_members(self, group_chat_ref: str) -> list[Member]: return [ Member( - ID=member["ID"], - DisplayName=member["DisplayName"], - UserID=member["UserID"], - Role=member["Role"], - Email=member["Email"], + id=member["ID"], + display_name=member["DisplayName"], + user_id=member["UserID"], + role=member["Role"], + email=member["Email"], ) for member in members ] @@ -523,10 +523,10 @@ def get_mentions(self, chat_ref: ChatRef, raw_mentions: list[str]) -> list[Menti return [ Mention( - Kind=mention["Kind"], - AtID=mention["AtID"], - Text=mention["Text"], - TargetID=mention["TargetID"], + kind=mention["Kind"], + at_id=mention["AtID"], + text=mention["Text"], + target_id=mention["TargetID"], ) for mention in mentions ] diff --git a/python/tests/fake_server/data.py b/python/tests/fake_server/data.py index 0a2581a..ab24160 100644 --- a/python/tests/fake_server/data.py +++ b/python/tests/fake_server/data.py @@ -48,8 +48,8 @@ class FakeServerData: ReplyCount=0, )) newMemberTemplate: Member = field(default_factory=lambda: Member( - ID="user-new-002", UserID="new-user-002", DisplayName="New Member", - Role="owner", Email="newmember@example.com" + id="user-new-002", user_id="new-user-002", display_name="New Member", + role="owner", email="newmember@example.com" )) newChatTemplate: Chat = field(default_factory=lambda: Chat( id="new-chat-001", type=ChatType.GROUP, is_hidden=False, topic="New Chat Topic" @@ -93,18 +93,18 @@ def __init__(self) -> None: self.members = { "team-123-abc": { "19:123123@thread.tacv2": [ - Member(ID="user-123-abc", UserID="user-123-abc", DisplayName="Alice", Role="owner", Email="alice@example.com"), - Member(ID="user-456-def", UserID="user-456-def", DisplayName="Bob", Role="member", Email="bob@example.com"), + Member(id="user-123-abc", user_id="user-123-abc", display_name="Alice", role="owner", email="alice@example.com"), + Member(id="user-456-def", user_id="user-456-def", display_name="Bob", role="member", email="bob@example.com"), ], } } # --- Users --- self.users = [ - Member(ID="user-123-abc", UserID="user-123-abc", DisplayName="Alice", Role="owner", Email="alice@example.com"), - Member(ID="user-456-def", UserID="user-456-def", DisplayName="Bob", Role="member", Email="bob@example.com"), + Member(id="user-123-abc", user_id="user-123-abc", display_name="Alice", role="owner", email="alice@example.com"), + Member(id="user-456-def", user_id="user-456-def", display_name="Bob", role="member", email="bob@example.com"), ] - self.me = Member(ID="user-me-001", UserID="user-me-001", DisplayName="Current User", Role="member", Email="me@example.com") + self.me = Member(id="user-me-001", user_id="user-me-001", display_name="Current User", role="member", email="me@example.com") # --- Chats & Chat Members/Messages --- self.group_chats = [ @@ -117,8 +117,8 @@ def __init__(self) -> None: self.group_chat_members = { "chat-123-abc": [ - Member(ID="user-123-abc", UserID="user-123-abc", DisplayName="Alice", Role="owner", Email="alice@example.com"), - Member(ID="user-456-def", UserID="user-456-def", DisplayName="Bob", Role="member", Email="bob@example.com"), + Member(id="user-123-abc", user_id="user-123-abc", display_name="Alice", role="owner", email="alice@example.com"), + Member(id="user-456-def", user_id="user-456-def", display_name="Bob", role="member", email="bob@example.com"), ], } @@ -148,8 +148,8 @@ def __init__(self) -> None: CreatedDateTime="2024-01-02T10:00:00Z", ReplyCount=0 ) self.newMemberTemplate = Member( - ID="user-new-002", UserID="new-user-002", DisplayName="New Member", - Role="owner", Email="newmember@example.com" + id="user-new-002", user_id="new-user-002", display_name="New Member", + role="owner", email="newmember@example.com" ) self.newChatTemplate = Chat( id="new-chat-001", type=ChatType.GROUP, is_hidden=False, topic="New Chat Topic" @@ -486,50 +486,50 @@ def get_list_members_response(self, team_id: str, channel_id: str) -> dict: "value": [ { ODATA_TYPE: "#microsoft.graph.aadUserConversationMember", - "id": member.ID, - "userId": member.UserID, - "displayName": member.DisplayName, - "roles": [member.Role] if member.Role == "owner" else [], - "email": member.Email, + "id": member.id, + "userId": member.user_id, + "displayName": member.display_name, + "roles": [member.role] if member.role == "owner" else [], + "email": member.email, } for member in self.members.get(team_id, {}).get(channel_id, []) ], } def get_add_member_response(self, team_id: str, request_json: dict) -> dict: - roles = ["owner"] if self.newMemberTemplate.Role == "owner" else [] + roles = ["owner"] if self.newMemberTemplate.role == "owner" else [] return { ODATA_CONTEXT: f"{GRAPH_URL}#teams('{team_id}')/members/$entity", ODATA_TYPE: "#microsoft.graph.aadUserConversationMember", - "id": self.newMemberTemplate.ID, + "id": self.newMemberTemplate.id, "roles": roles, - "displayName": self.newMemberTemplate.DisplayName, - "userId": self.newMemberTemplate.UserID, - "email": self.newMemberTemplate.Email, + "displayName": self.newMemberTemplate.display_name, + "userId": self.newMemberTemplate.user_id, + "email": self.newMemberTemplate.email, } def get_update_member_role_response(self, team_id: str, channel_id: str, member_id: str, request_json: dict) -> Optional[dict]: channel_members = self.members.get(team_id, {}).get(channel_id, []) - member = next((m for m in channel_members if m.ID == member_id), None) + member = next((m for m in channel_members if m.id == member_id), None) if not member: return None if "roles" in request_json: - member.Role = "owner" if "owner" in request_json["roles"] else "member" + member.role = "owner" if "owner" in request_json["roles"] else "member" return { ODATA_CONTEXT: f"{GRAPH_URL}#teams('{team_id}')/channels('{channel_id}')/members/$entity", ODATA_TYPE: "#microsoft.graph.aadUserConversationMember", - "id": member.ID, - "userId": member.UserID, - "displayName": member.DisplayName, - "roles": [member.Role] if member.Role == "owner" else [], - "email": member.Email, + "id": member.id, + "userId": member.user_id, + "displayName": member.display_name, + "roles": [member.role] if member.role == "owner" else [], + "email": member.email, } def get_remove_member_response(self, team_id: str, channel_id: str, member_id: str) -> dict: channel_members = self.members.get(team_id, {}).get(channel_id, []) - member = next((m for m in channel_members if m.ID == member_id), None) + member = next((m for m in channel_members if m.id == member_id), None) if not member: return {"success": False} channel_members.remove(member) @@ -590,11 +590,11 @@ def get_list_group_chat_members_response(self, chat_id: str) -> dict: "value": [ { ODATA_TYPE: "#microsoft.graph.aadUserConversationMember", - "id": member.ID, - "userId": member.UserID, - "displayName": member.DisplayName, - "roles": [member.Role] if member.Role == "owner" else [], - "email": member.Email, + "id": member.id, + "userId": member.user_id, + "displayName": member.display_name, + "roles": [member.role] if member.role == "owner" else [], + "email": member.email, } for member in self.group_chat_members.get(chat_id, []) ], @@ -604,11 +604,11 @@ def get_add_member_to_group_chat_response(self, chat_id: str, request_json: dict return { ODATA_CONTEXT: f"{GRAPH_URL}#chats('{chat_id}')/members/$entity", ODATA_TYPE: "#microsoft.graph.aadUserConversationMember", - "id": self.newMemberTemplate.ID, - "roles": [self.newMemberTemplate.Role] if self.newMemberTemplate.Role == "owner" else [], - "displayName": self.newMemberTemplate.DisplayName, - "userId": self.newMemberTemplate.UserID, - "email": self.newMemberTemplate.Email, + "id": self.newMemberTemplate.id, + "roles": [self.newMemberTemplate.role] if self.newMemberTemplate.role == "owner" else [], + "displayName": self.newMemberTemplate.display_name, + "userId": self.newMemberTemplate.user_id, + "email": self.newMemberTemplate.email, } def get_remove_member_from_group_chat_response(self, chat_id: str, member_id: str) -> dict: @@ -723,7 +723,7 @@ def get_list_pinned_messages_in_chat_response(self, chat_id: str) -> dict: def get_me_response(self) -> dict: return { - "id": self.me.ID, - "displayName": self.me.DisplayName, - "email": self.me.Email, + "id": self.me.id, + "displayName": self.me.display_name, + "email": self.me.email, } \ No newline at end of file diff --git a/python/tests/test_channels.py b/python/tests/test_channels.py index 62ba85b..8830458 100644 --- a/python/tests/test_channels.py +++ b/python/tests/test_channels.py @@ -285,19 +285,19 @@ def test_list_members_integration(httpserver): assert len(members) == len(expected_members) - assert members[0].UserID == expected_members[0].UserID - assert members[0].DisplayName == expected_members[0].DisplayName - expected_role_0 = "owner" if expected_members[0].Role == "owner" else "" - assert members[0].Role == expected_role_0 - assert members[0].Email == expected_members[0].Email - assert members[0].ID == expected_members[0].ID - - assert members[1].UserID == expected_members[1].UserID - assert members[1].DisplayName == expected_members[1].DisplayName - expected_role_1 = "owner" if expected_members[1].Role == "owner" else "" - assert members[1].Role == expected_role_1 - assert members[1].Email == expected_members[1].Email - assert members[1].ID == expected_members[1].ID + assert members[0].user_id == expected_members[0].user_id + assert members[0].display_name == expected_members[0].display_name + expected_role_0 = "owner" if expected_members[0].role == "owner" else "" + assert members[0].role == expected_role_0 + assert members[0].email == expected_members[0].email + assert members[0].id == expected_members[0].id + + assert members[1].user_id == expected_members[1].user_id + assert members[1].display_name == expected_members[1].display_name + expected_role_1 = "owner" if expected_members[1].role == "owner" else "" + assert members[1].role == expected_role_1 + assert members[1].email == expected_members[1].email + assert members[1].id == expected_members[1].id finally: client.close() @@ -314,15 +314,15 @@ def test_add_member_integration(httpserver): member = client.channels.add_member( team_ref=data.teams[0].DisplayName, channel_ref=data.channels[data.teams[0].ID][1].name, - user_ref=data.newMemberTemplate.DisplayName, - is_owner=True if data.newMemberTemplate.Role == "owner" else False, + user_ref=data.newMemberTemplate.display_name, + is_owner=True if data.newMemberTemplate.role == "owner" else False, ) - assert member.UserID == data.newMemberTemplate.UserID - assert member.DisplayName == data.newMemberTemplate.DisplayName - assert member.Role == data.newMemberTemplate.Role - assert member.Email == data.newMemberTemplate.Email - assert member.ID == data.newMemberTemplate.ID + assert member.user_id == data.newMemberTemplate.user_id + assert member.display_name == data.newMemberTemplate.display_name + assert member.role == data.newMemberTemplate.role + assert member.email == data.newMemberTemplate.email + assert member.id == data.newMemberTemplate.id finally: client.close() @@ -339,15 +339,15 @@ def test_update_member_role_integration(httpserver): member = client.channels.update_member_role( team_ref=data.teams[0].DisplayName, channel_ref=data.channels[data.teams[0].ID][0].name, - user_ref=data.members[data.teams[0].ID][data.channels[data.teams[0].ID][0].id][1].Email, + user_ref=data.members[data.teams[0].ID][data.channels[data.teams[0].ID][0].id][1].email, is_owner=True ) - assert member.UserID == data.members[data.teams[0].ID][data.channels[data.teams[0].ID][0].id][1].UserID - assert member.DisplayName == data.members[data.teams[0].ID][data.channels[data.teams[0].ID][0].id][1].DisplayName - assert member.Role == "owner" - assert member.Email == data.members[data.teams[0].ID][data.channels[data.teams[0].ID][0].id][1].Email - assert member.ID == data.members[data.teams[0].ID][data.channels[data.teams[0].ID][0].id][1].ID + assert member.user_id == data.members[data.teams[0].ID][data.channels[data.teams[0].ID][0].id][1].user_id + assert member.display_name == data.members[data.teams[0].ID][data.channels[data.teams[0].ID][0].id][1].display_name + assert member.role == "owner" + assert member.email == data.members[data.teams[0].ID][data.channels[data.teams[0].ID][0].id][1].email + assert member.id == data.members[data.teams[0].ID][data.channels[data.teams[0].ID][0].id][1].id finally: client.close() @@ -363,7 +363,7 @@ def test_remove_member_integration(httpserver): success = client.channels.remove_member( team_ref=data.teams[0].DisplayName, channel_ref=data.channels[data.teams[0].ID][0].name, - user_ref=data.members[data.teams[0].ID][data.channels[data.teams[0].ID][0].id][1].Email, + user_ref=data.members[data.teams[0].ID][data.channels[data.teams[0].ID][0].id][1].email, ) assert success is True @@ -385,10 +385,10 @@ def test_get_mention_integration(httpserver): raw_mentions=MentionKind.TEAM.value, ) - assert mention[0].Kind == MentionKind.TEAM.value - assert mention[0].AtID == 0 - assert mention[0].Text == data.teams[0].DisplayName - assert mention[0].TargetID == data.teams[0].ID + assert mention[0].kind == MentionKind.TEAM.value + assert mention[0].at_id == 0 + assert mention[0].text == data.teams[0].DisplayName + assert mention[0].target_id == data.teams[0].ID finally: client.close() diff --git a/python/tests/test_chats.py b/python/tests/test_chats.py index adaf71e..cb971de 100644 --- a/python/tests/test_chats.py +++ b/python/tests/test_chats.py @@ -66,8 +66,8 @@ def test_create_group_chat_integration(httpserver): chat = client.chats.create_group_chat( topic=data.newChatTemplate.topic, recipient_refs=[ - data.users[0].Email, - data.users[1].Email, + data.users[0].email, + data.users[1].email, ], include_me=True, ) @@ -89,7 +89,7 @@ def test_create_one_on_one_chat_integration(httpserver): init_fake_client(client, httpserver.url_for("")) chat = client.chats.create_one_on_one( - recipient_ref=data.users[0].Email, + recipient_ref=data.users[0].email, ) assert chat.id == data.newChatTemplate.id @@ -113,19 +113,19 @@ def test_list_group_chat_members_integration(httpserver): assert len(members) == len(data.group_chat_members[data.group_chats[0].id]) - assert members[0].ID == data.group_chat_members[data.group_chats[0].id][0].ID - assert members[0].UserID == data.group_chat_members[data.group_chats[0].id][0].UserID - assert members[0].DisplayName == data.group_chat_members[data.group_chats[0].id][0].DisplayName - if data.group_chat_members[data.group_chats[0].id][0].Role == "owner": - assert members[0].Role == "owner" - assert members[0].Email == data.group_chat_members[data.group_chats[0].id][0].Email + assert members[0].id == data.group_chat_members[data.group_chats[0].id][0].id + assert members[0].user_id == data.group_chat_members[data.group_chats[0].id][0].user_id + assert members[0].display_name == data.group_chat_members[data.group_chats[0].id][0].display_name + if data.group_chat_members[data.group_chats[0].id][0].role == "owner": + assert members[0].role == "owner" + assert members[0].email == data.group_chat_members[data.group_chats[0].id][0].email - assert members[1].ID == data.group_chat_members[data.group_chats[0].id][1].ID - assert members[1].UserID == data.group_chat_members[data.group_chats[0].id][1].UserID - assert members[1].DisplayName == data.group_chat_members[data.group_chats[0].id][1].DisplayName - if data.group_chat_members[data.group_chats[0].id][1].Role == "owner": - assert members[1].Role == "owner" - assert members[1].Email == data.group_chat_members[data.group_chats[0].id][1].Email + assert members[1].id == data.group_chat_members[data.group_chats[0].id][1].id + assert members[1].user_id == data.group_chat_members[data.group_chats[0].id][1].user_id + assert members[1].display_name == data.group_chat_members[data.group_chats[0].id][1].display_name + if data.group_chat_members[data.group_chats[0].id][1].role == "owner": + assert members[1].role == "owner" + assert members[1].email == data.group_chat_members[data.group_chats[0].id][1].email finally: client.close() @@ -141,14 +141,14 @@ def test_add_group_chat_member_integration(httpserver): member = client.chats.add_member_to_group_chat( group_chat_ref=data.group_chats[0].topic, - user_ref=data.newMemberTemplate.Email, + user_ref=data.newMemberTemplate.email, ) - assert member.ID == data.newMemberTemplate.ID - assert member.UserID == data.newMemberTemplate.UserID - assert member.DisplayName == data.newMemberTemplate.DisplayName - assert member.Role == data.newMemberTemplate.Role - assert member.Email == data.newMemberTemplate.Email + assert member.id == data.newMemberTemplate.id + assert member.user_id == data.newMemberTemplate.user_id + assert member.display_name == data.newMemberTemplate.display_name + assert member.role == data.newMemberTemplate.role + assert member.email == data.newMemberTemplate.email finally: client.close() @@ -163,7 +163,7 @@ def test_remove_group_chat_member_integration(httpserver): result = client.chats.remove_member_from_group_chat( group_chat_ref=data.group_chats[0].topic, - member_ref=data.group_chat_members[data.group_chats[0].id][0].Email, + member_ref=data.group_chat_members[data.group_chats[0].id][0].email, ) assert result is True @@ -442,10 +442,10 @@ def test_get_mention_integration(httpserver): raw_mentions=MentionKind.EVERYONE.value, ) - assert mention[0].Kind == MentionKind.EVERYONE.value - assert mention[0].AtID == 0 - assert mention[0].Text == "Everyone" - assert mention[0].TargetID == data.group_chats[0].id + assert mention[0].kind == MentionKind.EVERYONE.value + assert mention[0].at_id == 0 + assert mention[0].text == "Everyone" + assert mention[0].target_id == data.group_chats[0].id finally: client.close() From 07b2da5b9ef01830479465274327cf35edc116a4 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Thu, 8 Jan 2026 10:43:36 +0100 Subject: [PATCH 68/99] fix: tests after changing models fields to snake_case --- python/teams_lib_pzsp2_z1/model/mention.py | 2 +- python/teams_lib_pzsp2_z1/model/message.py | 39 ++--- .../teams_lib_pzsp2_z1/services/channels.py | 121 ++++++++------ python/teams_lib_pzsp2_z1/services/chats.py | 88 +++++----- python/tests/fake_server/data.py | 150 +++++++++--------- python/tests/test_channels.py | 116 +++++++------- python/tests/test_chats.py | 136 ++++++++-------- 7 files changed, 338 insertions(+), 314 deletions(-) diff --git a/python/teams_lib_pzsp2_z1/model/mention.py b/python/teams_lib_pzsp2_z1/model/mention.py index 273be4f..950f49f 100644 --- a/python/teams_lib_pzsp2_z1/model/mention.py +++ b/python/teams_lib_pzsp2_z1/model/mention.py @@ -24,7 +24,7 @@ class Mention: """Represents a mention entity within a Microsoft Teams message. This object maps a specific entity (User, Channel, etc.) to a placeholder - in the message content. + in the message content. Can be obtained by get_mentions() method in the Chats and Channels service. Attributes: kind (MentionKind): The type of the mention. diff --git a/python/teams_lib_pzsp2_z1/model/message.py b/python/teams_lib_pzsp2_z1/model/message.py index e3decf2..485acf6 100644 --- a/python/teams_lib_pzsp2_z1/model/message.py +++ b/python/teams_lib_pzsp2_z1/model/message.py @@ -12,43 +12,44 @@ class MessageContentType(Enum): @dataclass class MessageFrom: - UserID: str - DisplayName: str + user_id: str + display_name: str @dataclass class Message: - ID: str - Content: str - ContentType: MessageContentType - CreatedDateTime: datetime - From: MessageFrom - ReplyCount: int + id: str + content: str + content_type: MessageContentType + created_date_time: datetime + sender: MessageFrom + reply_count: int @dataclass class MessageBody: - ContentType: MessageContentType - Content: str - Mentions: list[Mention] + content_type: MessageContentType + content: str + mentions: list[Mention] def __dict__(self): return { - "ContentType": self.ContentType.value, - "Content": self.Content, + "ContentType": self.content_type.value, + "Content": self.content, } def __iter__(self): - yield "ContentType", self.ContentType.value - yield "Content", self.Content + yield "ContentType", self.content_type.value + yield "Content", self.content @dataclass class ListMessagesOptions: - Top: int | None = None - ExpandReplies: bool = False + top: int | None = None + expand_replies: bool = False + @dataclass class MessageCollection: - Messages: list[Message] - NextLink: str | None = None + messages: list[Message] + next_link: str | None = None diff --git a/python/teams_lib_pzsp2_z1/services/channels.py b/python/teams_lib_pzsp2_z1/services/channels.py index 8bc0207..6c39c77 100644 --- a/python/teams_lib_pzsp2_z1/services/channels.py +++ b/python/teams_lib_pzsp2_z1/services/channels.py @@ -4,7 +4,7 @@ from teams_lib_pzsp2_z1.model.channel import Channel from teams_lib_pzsp2_z1.model.member import Member -from teams_lib_pzsp2_z1.model.mention import Mention +from teams_lib_pzsp2_z1.model.mention import Mention, MentionKind from teams_lib_pzsp2_z1.model.message import ( Message, MessageBody, @@ -190,15 +190,15 @@ def send_message( ) return Message( - ID=response["ID"], - Content=response["Content"], - ContentType=MessageContentType(response["ContentType"]), - CreatedDateTime=response["CreatedDateTime"], - From=MessageFrom( - UserID=response["From"]["UserID"], - DisplayName=response["From"]["DisplayName"], + id=response["ID"], + content=response["Content"], + content_type=MessageContentType(response["ContentType"]), + created_date_time=response["CreatedDateTime"], + sender=MessageFrom( + user_id=response["From"]["UserID"], + display_name=response["From"]["DisplayName"], ), - ReplyCount=response["ReplyCount"], + reply_count=response["ReplyCount"], ) def list_messages( # noqa: PLR0913 @@ -239,21 +239,21 @@ def list_messages( # noqa: PLR0913 ) return MessageCollection( - Messages=[ + messages=[ Message( - ID=msg["ID"], - Content=msg["Content"], - ContentType=MessageContentType(msg["ContentType"]), - CreatedDateTime=msg["CreatedDateTime"], - From=MessageFrom( - UserID=msg["From"]["UserID"], - DisplayName=msg["From"]["DisplayName"], + id=msg["ID"], + content=msg["Content"], + content_type=MessageContentType(msg["ContentType"]), + created_date_time=msg["CreatedDateTime"], + sender=MessageFrom( + user_id=msg["From"]["UserID"], + display_name=msg["From"]["DisplayName"], ), - ReplyCount=msg["ReplyCount"], + reply_count=msg["ReplyCount"], ) for msg in response["Messages"] ], - NextLink=response.get("NextLink"), + next_link=response.get("NextLink"), ) def get_message(self, team_ref: str, channel_ref: str, message_id: str) -> Message: @@ -278,15 +278,15 @@ def get_message(self, team_ref: str, channel_ref: str, message_id: str) -> Messa ) return Message( - ID=response["ID"], - Content=response["Content"], - ContentType=MessageContentType(response["ContentType"]), - CreatedDateTime=response["CreatedDateTime"], - From=MessageFrom( - UserID=response["From"]["UserID"], - DisplayName=response["From"]["DisplayName"], + id=response["ID"], + content=response["Content"], + content_type=MessageContentType(response["ContentType"]), + created_date_time=response["CreatedDateTime"], + sender=MessageFrom( + user_id=response["From"]["UserID"], + display_name=response["From"]["DisplayName"], ), - ReplyCount=response["ReplyCount"], + reply_count=response["ReplyCount"], ) def list_message_replies( # noqa: PLR0913 @@ -325,21 +325,21 @@ def list_message_replies( # noqa: PLR0913 ) return MessageCollection( - Messages=[ + messages=[ Message( - ID=msg["ID"], - Content=msg["Content"], - ContentType=MessageContentType(msg["ContentType"]), - CreatedDateTime=msg["CreatedDateTime"], - From=MessageFrom( - UserID=msg["From"]["UserID"], - DisplayName=msg["From"]["DisplayName"], + id=msg["ID"], + content=msg["Content"], + content_type=MessageContentType(msg["ContentType"]), + created_date_time=msg["CreatedDateTime"], + sender=MessageFrom( + user_id=msg["From"]["UserID"], + display_name=msg["From"]["DisplayName"], ), - ReplyCount=msg["ReplyCount"], + reply_count=msg["ReplyCount"], ) for msg in response["Messages"] ], - NextLink=response.get("NextLink"), + next_link=response.get("NextLink"), ) def get_message_reply( @@ -372,15 +372,15 @@ def get_message_reply( ) return Message( - ID=response["ID"], - Content=response["Content"], - ContentType=MessageContentType(response["ContentType"]), - CreatedDateTime=response["CreatedDateTime"], - From=MessageFrom( - UserID=response["From"]["UserID"], - DisplayName=response["From"]["DisplayName"], + id=response["ID"], + content=response["Content"], + content_type=MessageContentType(response["ContentType"]), + created_date_time=response["CreatedDateTime"], + sender=MessageFrom( + user_id=response["From"]["UserID"], + display_name=response["From"]["DisplayName"], ), - ReplyCount=response["ReplyCount"], + reply_count=response["ReplyCount"], ) def list_members(self, team_ref: str, channel_ref: str) -> list[Member]: @@ -401,7 +401,13 @@ def list_members(self, team_ref: str, channel_ref: str) -> list[Member]: "channelRef": channel_ref, }, ) - return [Member(**member) for member in response] + return [Member( + id=member["ID"], + user_id=member["UserID"], + display_name=member["DisplayName"], + role=member["Role"], + email=member["Email"], + ) for member in response] def add_member( self, team_ref: str, channel_ref: str, user_ref: str, is_owner: bool @@ -427,7 +433,13 @@ def add_member( "isOwner": is_owner, }, ) - return Member(**response) + return Member( + id=response["ID"], + user_id=response["UserID"], + display_name=response["DisplayName"], + role=response["Role"], + email=response["Email"], + ) def update_member_role( self, team_ref: str, channel_ref: str, user_ref: str, is_owner: bool @@ -453,7 +465,13 @@ def update_member_role( "isOwner": is_owner, }, ) - return Member(**response) + return Member( + id=response["ID"], + user_id=response["UserID"], + display_name=response["DisplayName"], + role=response["Role"], + email=response["Email"], + ) def remove_member(self, team_ref: str, channel_ref: str, user_ref: str) -> bool: """Removes a user from a channel. @@ -505,4 +523,9 @@ def get_mentions( "rawMentions": raw_mentions, }, ) - return [Mention(**mention) for mention in response] + return [Mention( + kind=MentionKind(mention["Kind"]), + at_id=mention["AtID"], + text=mention["Text"], + target_id=mention["TargetID"], + ) for mention in response] diff --git a/python/teams_lib_pzsp2_z1/services/chats.py b/python/teams_lib_pzsp2_z1/services/chats.py index 1b9705c..1d072d2 100644 --- a/python/teams_lib_pzsp2_z1/services/chats.py +++ b/python/teams_lib_pzsp2_z1/services/chats.py @@ -225,21 +225,21 @@ def list_messages( ) return MessageCollection( - Messages=[ + messages=[ Message( - ID=msg["ID"], - Content=msg["Content"], - ContentType=MessageContentType(msg["ContentType"]), - CreatedDateTime=msg["CreatedDateTime"], - From=MessageFrom( - UserID=msg["From"]["UserID"], - DisplayName=msg["From"]["DisplayName"], + id=msg["ID"], + content=msg["Content"], + content_type=MessageContentType(msg["ContentType"]), + created_date_time=msg["CreatedDateTime"], + sender=MessageFrom( + user_id=msg["From"]["UserID"], + display_name=msg["From"]["DisplayName"], ), - ReplyCount=msg["ReplyCount"], + reply_count=msg["ReplyCount"], ) for msg in messages["Messages"] ], - NextLink=messages.get("NextLink"), + next_link=messages.get("NextLink"), ) def send_message(self, chat_ref: ChatRef, body: MessageBody) -> Message: @@ -261,22 +261,22 @@ def send_message(self, chat_ref: ChatRef, body: MessageBody) -> Message: "type": chat_ref.type.value, }, "body": { - "content": body.Content, - "contentType": body.ContentType.value, + "content": body.content, + "contentType": body.content_type.value, }, }, ) return Message( - ID=message["ID"], - Content=message["Content"], - ContentType=MessageContentType(message["ContentType"]), - From=MessageFrom( - UserID=message["From"]["UserID"], - DisplayName=message["From"]["DisplayName"], + id=message["ID"], + content=message["Content"], + content_type=MessageContentType(message["ContentType"]), + sender=MessageFrom( + user_id=message["From"]["UserID"], + display_name=message["From"]["DisplayName"], ), - CreatedDateTime=message["CreatedDateTime"], - ReplyCount=message["ReplyCount"], + created_date_time=message["CreatedDateTime"], + reply_count=message["ReplyCount"], ) def delete_message(self, chat_ref: ChatRef, message_id: str) -> bool: @@ -328,15 +328,15 @@ def get_message(self, chat_ref: ChatRef, message_id: str) -> Message: ) return Message( - ID=message["ID"], - Content=message["Content"], - ContentType=MessageContentType(message["ContentType"]), - From=MessageFrom( - UserID=message["From"]["UserID"], - DisplayName=message["From"]["DisplayName"], + id=message["ID"], + content=message["Content"], + content_type=MessageContentType(message["ContentType"]), + sender=MessageFrom( + user_id=message["From"]["UserID"], + display_name=message["From"]["DisplayName"], ), - CreatedDateTime=message["CreatedDateTime"], - ReplyCount=message["ReplyCount"], + created_date_time=message["CreatedDateTime"], + reply_count=message["ReplyCount"], ) def list_my_joined(self, chat_type: ChatType | None = None) -> list[Chat]: @@ -398,15 +398,15 @@ def list_all_messages( return [ Message( - ID=message["ID"], - Content=message["Content"], - ContentType=MessageContentType(message["ContentType"]), - From=MessageFrom( - UserID=message["From"]["UserID"], - DisplayName=message["From"]["DisplayName"], + id=message["ID"], + content=message["Content"], + content_type=MessageContentType(message["ContentType"]), + sender=MessageFrom( + user_id=message["From"]["UserID"], + display_name=message["From"]["DisplayName"], ), - CreatedDateTime=message["CreatedDateTime"], - ReplyCount=message["ReplyCount"], + created_date_time=message["CreatedDateTime"], + reply_count=message["ReplyCount"], ) for message in messages ] @@ -433,15 +433,15 @@ def list_pinned_messages(self, chat_ref: ChatRef) -> list[Message]: return [ Message( - ID=message["ID"], - Content=message["Content"], - ContentType=MessageContentType(message["ContentType"]), - From=MessageFrom( - UserID=message["From"]["UserID"], - DisplayName=message["From"]["DisplayName"], + id=message["ID"], + content=message["Content"], + content_type=MessageContentType(message["ContentType"]), + sender=MessageFrom( + user_id=message["From"]["UserID"], + display_name=message["From"]["DisplayName"], ), - CreatedDateTime=message["CreatedDateTime"], - ReplyCount=message["ReplyCount"], + created_date_time=message["CreatedDateTime"], + reply_count=message["ReplyCount"], ) for message in messages ] diff --git a/python/tests/fake_server/data.py b/python/tests/fake_server/data.py index ab24160..82bbf00 100644 --- a/python/tests/fake_server/data.py +++ b/python/tests/fake_server/data.py @@ -40,12 +40,12 @@ class FakeServerData: newChannelName: str = "New Channel" newChannelID: str = "19:newchannelid@thread.tacv2" newMessageTemplate: Message = field(default_factory=lambda: Message( - ID="new-message-id", - Content="This is a new message.", - ContentType="text", - From=MessageFrom(UserID="user-new-001", DisplayName="New User"), - CreatedDateTime="2024-01-02T10:00:00Z", - ReplyCount=0, + id="new-message-id", + content="This is a new message.", + content_type="text", + sender=MessageFrom(user_id="user-new-001", display_name="New User"), + created_date_time="2024-01-02T10:00:00Z", + reply_count=0, )) newMemberTemplate: Member = field(default_factory=lambda: Member( id="user-new-002", user_id="new-user-002", display_name="New Member", @@ -76,16 +76,16 @@ def __init__(self) -> None: # --- Messages (Channel) --- self.messages = { "19:123123@thread.tacv2": [ - Message(ID="msg-001", Content="Hello, team!", ContentType="text", From=MessageFrom(UserID="user-123-abc", DisplayName="Alice"), CreatedDateTime="2024-01-01T10:00:00Z", ReplyCount=0), - Message(ID="msg-002", Content="Don't forget the meeting at 3 PM.", ContentType="text", From=MessageFrom(UserID="user-456-def", DisplayName="Bob"), CreatedDateTime="2024-01-01T11:00:00Z", ReplyCount=2), + Message(id="msg-001", content="Hello, team!", content_type="text", sender=MessageFrom(user_id="user-123-abc", display_name="Alice"), created_date_time="2024-01-01T10:00:00Z", reply_count=0), + Message(id="msg-002", content="Don't forget the meeting at 3 PM.", content_type="text", sender=MessageFrom(user_id="user-456-def", display_name="Bob"), created_date_time="2024-01-01T11:00:00Z", reply_count=2), ], } # --- Replies --- self.replies = { "msg-002": [ - Message(ID="msg-002-reply-001", Content="Thanks for the reminder!", ContentType="text", From=MessageFrom(UserID="user-789-ghi", DisplayName="Charlie"), CreatedDateTime="2024-01-01T12:00:00Z", ReplyCount=0), - Message(ID="msg-002-reply-002", Content="I'll be there.", ContentType="text", From=MessageFrom(UserID="user-123-abc", DisplayName="Alice"), CreatedDateTime="2024-01-01T12:30:00Z", ReplyCount=0), + Message(id="msg-002-reply-001", content="Thanks for the reminder!", content_type="text", sender=MessageFrom(user_id="user-789-ghi", display_name="Charlie"), created_date_time="2024-01-01T12:00:00Z", reply_count=0), + Message(id="msg-002-reply-002", content="I'll be there.", content_type="text", sender=MessageFrom(user_id="user-123-abc", display_name="Alice"), created_date_time="2024-01-01T12:30:00Z", reply_count=0), ], } @@ -124,8 +124,8 @@ def __init__(self) -> None: self.chat_messages = { "chat-123-abc": [ - Message(ID="msg-001", Content="Hello, team!", ContentType="text", From=MessageFrom(UserID="user-123-abc", DisplayName="Alice"), CreatedDateTime="2024-01-01T10:00:00Z", ReplyCount=0), - Message(ID="msg-002", Content="Don't forget the meeting at 3 PM.", ContentType="text", From=MessageFrom(UserID="user-456-def", DisplayName="Bob"), CreatedDateTime="2024-01-01T11:00:00Z", ReplyCount=2), + Message(id="msg-001", content="Hello, team!", content_type="text", sender=MessageFrom(user_id="user-123-abc", display_name="Alice"), created_date_time="2024-01-01T10:00:00Z", reply_count=0), + Message(id="msg-002", content="Don't forget the meeting at 3 PM.", content_type="text", sender=MessageFrom(user_id="user-456-def", display_name="Bob"), created_date_time="2024-01-01T11:00:00Z", reply_count=2), ], } @@ -143,9 +143,9 @@ def __init__(self) -> None: # Initialize templates manually to match original structure exactly if factories behave differently self.newMessageTemplate = Message( - ID="new-message-id", Content="This is a new message.", ContentType="text", - From=MessageFrom(UserID="user-new-001", DisplayName="New User"), - CreatedDateTime="2024-01-02T10:00:00Z", ReplyCount=0 + id="new-message-id", content="This is a new message.", content_type="text", + sender=MessageFrom(user_id="user-new-001", display_name="New User"), + created_date_time="2024-01-02T10:00:00Z", reply_count=0 ) self.newMemberTemplate = Member( id="user-new-002", user_id="new-user-002", display_name="New Member", @@ -381,99 +381,99 @@ def get_list_messages_response(self, team_id: str, channel_id: str) -> dict: return { "value": [ { - "id": message.ID, + "id": message.id, "body": { - "content": message.Content, - "contentType": message.ContentType, + "content": message.content, + "contentType": message.content_type, }, "from": { "user": { - "id": message.From.UserID, - "displayName": message.From.DisplayName, + "id": message.sender.user_id, + "displayName": message.sender.display_name, } }, - "createdDateTime": message.CreatedDateTime, - "replies": [{"id": f"dummy-reply-{i}"} for i in range(message.ReplyCount)] + "createdDateTime": message.created_date_time, + "replies": [{"id": f"dummy-reply-{i}"} for i in range(message.reply_count)] } for message in self.messages.get(channel_id, []) ], } def get_message_response(self, team_id: str, channel_id: str, message_id: str) -> Optional[dict]: - message = next((m for m in self.messages.get(channel_id, []) if m.ID == message_id), None) + message = next((m for m in self.messages.get(channel_id, []) if m.id == message_id), None) if not message: return None return { - "id": message.ID, + "id": message.id, "body": { - "content": message.Content, - "contentType": message.ContentType, + "content": message.content, + "contentType": message.content_type, }, "from": { "user": { - "id": message.From.UserID, - "displayName": message.From.DisplayName, + "id": message.sender.user_id, + "displayName": message.sender.display_name, } }, - "createdDateTime": message.CreatedDateTime, - "replies": [{"id": f"dummy-reply-{i}"} for i in range(message.ReplyCount)] + "createdDateTime": message.created_date_time, + "replies": [{"id": f"dummy-reply-{i}"} for i in range(message.reply_count)] } def get_send_message_response(self, team_id: str, channel_id: str, request_json: dict) -> dict: return { - "id": self.newMessageTemplate.ID, + "id": self.newMessageTemplate.id, "body": { "content": request_json.get("body", {}).get("content"), "contentType": request_json.get("body", {}).get("contentType"), }, "from": { "user": { - "id": self.newMessageTemplate.From.UserID, - "displayName": self.newMessageTemplate.From.DisplayName, + "id": self.newMessageTemplate.sender.user_id, + "displayName": self.newMessageTemplate.sender.display_name, } }, - "createdDateTime": self.newMessageTemplate.CreatedDateTime, + "createdDateTime": self.newMessageTemplate.created_date_time, } def get_list_replies_response(self, team_id: str, channel_id: str, message_id: str) -> dict: return { "value": [ { - "id": reply.ID, + "id": reply.id, "body": { - "content": reply.Content, - "contentType": reply.ContentType, + "content": reply.content, + "contentType": reply.content_type, }, "from": { "user": { - "id": reply.From.UserID, - "displayName": reply.From.DisplayName, + "id": reply.sender.user_id, + "displayName": reply.sender.display_name, } }, - "createdDateTime": reply.CreatedDateTime, + "createdDateTime": reply.created_date_time, } for reply in self.replies.get(message_id, []) ], } def get_reply_response(self, team_id: str, channel_id: str, message_id: str, reply_id: str) -> Optional[dict]: - reply = next((r for r in self.replies.get(message_id, []) if r.ID == reply_id), None) + reply = next((r for r in self.replies.get(message_id, []) if r.id == reply_id), None) if not reply: return None return { - "id": reply.ID, + "id": reply.id, "body": { - "content": reply.Content, - "contentType": reply.ContentType, + "content": reply.content, + "contentType": reply.content_type, }, "from": { "user": { - "id": reply.From.UserID, - "displayName": reply.From.DisplayName, + "id": reply.sender.user_id, + "displayName": reply.sender.display_name, } }, - "createdDateTime": reply.CreatedDateTime, + "createdDateTime": reply.created_date_time, } # ========================================== @@ -622,59 +622,59 @@ def get_list_messages_in_chat_response(self, chat_id: str) -> dict: return { "value": [ { - "id": message.ID, + "id": message.id, "body": { - "content": message.Content, - "contentType": message.ContentType, + "content": message.content, + "contentType": message.content_type, }, "from": { "user": { - "id": message.From.UserID, - "displayName": message.From.DisplayName, + "id": message.sender.user_id, + "displayName": message.sender.display_name, } }, - "createdDateTime": message.CreatedDateTime, - "replies": [{"id": f"dummy-reply-{i}"} for i in range(message.ReplyCount)] + "createdDateTime": message.created_date_time, + "replies": [{"id": f"dummy-reply-{i}"} for i in range(message.reply_count)] } for message in self.chat_messages.get(chat_id, []) ], } def get_get_message_in_chat_response(self, chat_id: str, message_id: str) -> Optional[dict]: - message = next((m for m in self.chat_messages.get(chat_id, []) if m.ID == message_id), None) + message = next((m for m in self.chat_messages.get(chat_id, []) if m.id == message_id), None) if not message: return None return { - "id": message.ID, + "id": message.id, "body": { - "content": message.Content, - "contentType": message.ContentType, + "content": message.content, + "contentType": message.content_type, }, "from": { "user": { - "id": message.From.UserID, - "displayName": message.From.DisplayName, + "id": message.sender.user_id, + "displayName": message.sender.display_name, } }, - "createdDateTime": message.CreatedDateTime, - "replies": [{"id": f"dummy-reply-{i}"} for i in range(message.ReplyCount)] + "createdDateTime": message.created_date_time, + "replies": [{"id": f"dummy-reply-{i}"} for i in range(message.reply_count)] } def get_send_message_in_chat_response(self, chat_id: str, request_json: dict) -> dict: return { - "id": self.newMessageTemplate.ID, + "id": self.newMessageTemplate.id, "body": { "content": request_json.get("body", {}).get("content"), "contentType": request_json.get("body", {}).get("contentType"), }, "from": { "user": { - "id": self.newMessageTemplate.From.UserID, - "displayName": self.newMessageTemplate.From.DisplayName, + "id": self.newMessageTemplate.sender.user_id, + "displayName": self.newMessageTemplate.sender.display_name, } }, - "createdDateTime": self.newMessageTemplate.CreatedDateTime, + "createdDateTime": self.newMessageTemplate.created_date_time, } def get_all_messeges_in_chats_response(self) -> dict: @@ -685,22 +685,22 @@ def get_all_messeges_in_chats_response(self) -> dict: "value": [ { ODATA_TYPE: "#microsoft.graph.chatMessage", - "id": message.ID, - "etag": message.ID, + "id": message.id, + "etag": message.id, "messageType": "message", "chatId": target_chat_id, "body": { - "content": message.Content, - "contentType": message.ContentType, + "content": message.content, + "contentType": message.content_type, }, "from": { "user": { - "id": message.From.UserID, - "displayName": message.From.DisplayName, + "id": message.sender.user_id, + "displayName": message.sender.display_name, } }, - "createdDateTime": message.CreatedDateTime, - "lastModifiedDateTime": message.CreatedDateTime, + "createdDateTime": message.created_date_time, + "lastModifiedDateTime": message.created_date_time, } for message in self.chat_messages[target_chat_id] ], @@ -711,7 +711,7 @@ def get_list_pinned_messages_in_chat_response(self, chat_id: str) -> dict: "value": [ { ODATA_TYPE: "#microsoft.graph.pinnedChatMessageInfo", - "id": message.ID, + "id": message.id, } for message in self.chat_messages.get(chat_id, []) ], diff --git a/python/tests/test_channels.py b/python/tests/test_channels.py index 8830458..8635287 100644 --- a/python/tests/test_channels.py +++ b/python/tests/test_channels.py @@ -129,19 +129,19 @@ def test_send_message_integration(httpserver): team_ref=data.teams[0].DisplayName, channel_ref=data.channels[data.teams[0].ID][0].name, body=MessageBody( - Content=data.newMessageTemplate.Content, - ContentType=MessageContentType.TEXT, - Mentions=[], + content=data.newMessageTemplate.content, + content_type=MessageContentType.TEXT, + mentions=[], ) ) - assert message.ID == data.newMessageTemplate.ID - assert message.Content == data.newMessageTemplate.Content - assert message.ContentType == MessageContentType(data.newMessageTemplate.ContentType) - assert message.From.UserID == data.newMessageTemplate.From.UserID - assert message.From.DisplayName == data.newMessageTemplate.From.DisplayName - assert message.ReplyCount == data.newMessageTemplate.ReplyCount - assert message.CreatedDateTime == data.newMessageTemplate.CreatedDateTime + assert message.id == data.newMessageTemplate.id + assert message.content == data.newMessageTemplate.content + assert message.content_type == MessageContentType(data.newMessageTemplate.content_type) + assert message.sender.user_id == data.newMessageTemplate.sender.user_id + assert message.sender.display_name == data.newMessageTemplate.sender.display_name + assert message.reply_count == data.newMessageTemplate.reply_count + assert message.created_date_time == data.newMessageTemplate.created_date_time finally: client.close() @@ -160,22 +160,22 @@ def test_list_messages_integration(httpserver): channel_ref=data.channels[data.teams[0].ID][0].name, ) - assert len(collection.Messages) == len(data.messages[data.channels[data.teams[0].ID][0].id]) - assert collection.Messages[0].ID == data.messages[data.channels[data.teams[0].ID][0].id][0].ID - assert collection.Messages[0].Content == data.messages[data.channels[data.teams[0].ID][0].id][0].Content - assert collection.Messages[0].ContentType == MessageContentType(data.messages[data.channels[data.teams[0].ID][0].id][0].ContentType) - assert collection.Messages[0].From.UserID == data.messages[data.channels[data.teams[0].ID][0].id][0].From.UserID - assert collection.Messages[0].From.DisplayName == data.messages[data.channels[data.teams[0].ID][0].id][0].From.DisplayName - assert collection.Messages[0].ReplyCount == data.messages[data.channels[data.teams[0].ID][0].id][0].ReplyCount - assert collection.Messages[0].CreatedDateTime == data.messages[data.channels[data.teams[0].ID][0].id][0].CreatedDateTime - - assert collection.Messages[1].ID == data.messages[data.channels[data.teams[0].ID][0].id][1].ID - assert collection.Messages[1].Content == data.messages[data.channels[data.teams[0].ID][0].id][1].Content - assert collection.Messages[1].ContentType == MessageContentType(data.messages[data.channels[data.teams[0].ID][0].id][1].ContentType) - assert collection.Messages[1].From.UserID == data.messages[data.channels[data.teams[0].ID][0].id][1].From.UserID - assert collection.Messages[1].From.DisplayName == data.messages[data.channels[data.teams[0].ID][0].id][1].From.DisplayName - assert collection.Messages[1].ReplyCount == data.messages[data.channels[data.teams[0].ID][0].id][1].ReplyCount - assert collection.Messages[1].CreatedDateTime == data.messages[data.channels[data.teams[0].ID][0].id][1].CreatedDateTime + assert len(collection.messages) == len(data.messages[data.channels[data.teams[0].ID][0].id]) + assert collection.messages[0].id == data.messages[data.channels[data.teams[0].ID][0].id][0].id + assert collection.messages[0].content == data.messages[data.channels[data.teams[0].ID][0].id][0].content + assert collection.messages[0].content_type == MessageContentType(data.messages[data.channels[data.teams[0].ID][0].id][0].content_type) + assert collection.messages[0].sender.user_id == data.messages[data.channels[data.teams[0].ID][0].id][0].sender.user_id + assert collection.messages[0].sender.display_name == data.messages[data.channels[data.teams[0].ID][0].id][0].sender.display_name + assert collection.messages[0].reply_count == data.messages[data.channels[data.teams[0].ID][0].id][0].reply_count + assert collection.messages[0].created_date_time == data.messages[data.channels[data.teams[0].ID][0].id][0].created_date_time + + assert collection.messages[1].id == data.messages[data.channels[data.teams[0].ID][0].id][1].id + assert collection.messages[1].content == data.messages[data.channels[data.teams[0].ID][0].id][1].content + assert collection.messages[1].content_type == MessageContentType(data.messages[data.channels[data.teams[0].ID][0].id][1].content_type) + assert collection.messages[1].sender.user_id == data.messages[data.channels[data.teams[0].ID][0].id][1].sender.user_id + assert collection.messages[1].sender.display_name == data.messages[data.channels[data.teams[0].ID][0].id][1].sender.display_name + assert collection.messages[1].reply_count == data.messages[data.channels[data.teams[0].ID][0].id][1].reply_count + assert collection.messages[1].created_date_time == data.messages[data.channels[data.teams[0].ID][0].id][1].created_date_time finally: client.close() @@ -191,16 +191,16 @@ def test_get_message_integration(httpserver): message = client.channels.get_message( team_ref=data.teams[0].DisplayName, channel_ref=data.channels[data.teams[0].ID][0].name, - message_id=data.messages[data.channels[data.teams[0].ID][0].id][0].ID, + message_id=data.messages[data.channels[data.teams[0].ID][0].id][0].id, ) - assert message.ID == data.messages[data.channels[data.teams[0].ID][0].id][0].ID - assert message.Content == data.messages[data.channels[data.teams[0].ID][0].id][0].Content - assert message.ContentType == MessageContentType(data.messages[data.channels[data.teams[0].ID][0].id][0].ContentType) - assert message.From.UserID == data.messages[data.channels[data.teams[0].ID][0].id][0].From.UserID - assert message.From.DisplayName == data.messages[data.channels[data.teams[0].ID][0].id][0].From.DisplayName - assert message.ReplyCount == data.messages[data.channels[data.teams[0].ID][0].id][0].ReplyCount - assert message.CreatedDateTime == data.messages[data.channels[data.teams[0].ID][0].id][0].CreatedDateTime + assert message.id == data.messages[data.channels[data.teams[0].ID][0].id][0].id + assert message.content == data.messages[data.channels[data.teams[0].ID][0].id][0].content + assert message.content_type == MessageContentType(data.messages[data.channels[data.teams[0].ID][0].id][0].content_type) + assert message.sender.user_id == data.messages[data.channels[data.teams[0].ID][0].id][0].sender.user_id + assert message.sender.display_name == data.messages[data.channels[data.teams[0].ID][0].id][0].sender.display_name + assert message.reply_count == data.messages[data.channels[data.teams[0].ID][0].id][0].reply_count + assert message.created_date_time == data.messages[data.channels[data.teams[0].ID][0].id][0].created_date_time finally: client.close() @@ -217,23 +217,23 @@ def test_list_replies_integration(httpserver): replies = client.channels.list_message_replies( team_ref=data.teams[0].DisplayName, channel_ref=data.channels[data.teams[0].ID][0].name, - message_id=data.messages[data.channels[data.teams[0].ID][0].id][1].ID, + message_id=data.messages[data.channels[data.teams[0].ID][0].id][1].id, ) - assert len(replies.Messages) == len(data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].ID]) - assert replies.Messages[0].ID == data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].ID][0].ID - assert replies.Messages[0].Content == data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].ID][0].Content - assert replies.Messages[0].ContentType == MessageContentType(data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].ID][0].ContentType) - assert replies.Messages[0].From.UserID == data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].ID][0].From.UserID - assert replies.Messages[0].From.DisplayName == data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].ID][0].From.DisplayName - assert replies.Messages[0].CreatedDateTime == data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].ID][0].CreatedDateTime - - assert replies.Messages[1].ID == data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].ID][1].ID - assert replies.Messages[1].Content == data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].ID][1].Content - assert replies.Messages[1].ContentType == MessageContentType(data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].ID][1].ContentType) - assert replies.Messages[1].From.UserID == data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].ID][1].From.UserID - assert replies.Messages[1].From.DisplayName == data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].ID][1].From.DisplayName - assert replies.Messages[1].CreatedDateTime == data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].ID][1].CreatedDateTime + assert len(replies.messages) == len(data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].id]) + assert replies.messages[0].id == data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].id][0].id + assert replies.messages[0].content == data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].id][0].content + assert replies.messages[0].content_type == MessageContentType(data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].id][0].content_type) + assert replies.messages[0].sender.user_id == data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].id][0].sender.user_id + assert replies.messages[0].sender.display_name == data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].id][0].sender.display_name + assert replies.messages[0].created_date_time == data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].id][0].created_date_time + + assert replies.messages[1].id == data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].id][1].id + assert replies.messages[1].content == data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].id][1].content + assert replies.messages[1].content_type == MessageContentType(data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].id][1].content_type) + assert replies.messages[1].sender.user_id == data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].id][1].sender.user_id + assert replies.messages[1].sender.display_name == data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].id][1].sender.display_name + assert replies.messages[1].created_date_time == data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].id][1].created_date_time finally: client.close() @@ -250,16 +250,16 @@ def test_get_reply_integration(httpserver): reply = client.channels.get_message_reply( team_ref=data.teams[0].DisplayName, channel_ref=data.channels[data.teams[0].ID][0].name, - message_id=data.messages[data.channels[data.teams[0].ID][0].id][1].ID, - reply_id=data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].ID][0].ID, + message_id=data.messages[data.channels[data.teams[0].ID][0].id][1].id, + reply_id=data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].id][0].id, ) - assert reply.ID == data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].ID][0].ID - assert reply.Content == data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].ID][0].Content - assert reply.ContentType == MessageContentType(data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].ID][0].ContentType) - assert reply.From.UserID == data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].ID][0].From.UserID - assert reply.From.DisplayName == data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].ID][0].From.DisplayName - assert reply.CreatedDateTime == data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].ID][0].CreatedDateTime + assert reply.id == data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].id][0].id + assert reply.content == data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].id][0].content + assert reply.content_type == MessageContentType(data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].id][0].content_type) + assert reply.sender.user_id == data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].id][0].sender.user_id + assert reply.sender.display_name == data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].id][0].sender.display_name + assert reply.created_date_time == data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].id][0].created_date_time finally: client.close() @@ -385,7 +385,7 @@ def test_get_mention_integration(httpserver): raw_mentions=MentionKind.TEAM.value, ) - assert mention[0].kind == MentionKind.TEAM.value + assert mention[0].kind == MentionKind.TEAM assert mention[0].at_id == 0 assert mention[0].text == data.teams[0].DisplayName assert mention[0].target_id == data.teams[0].ID diff --git a/python/tests/test_chats.py b/python/tests/test_chats.py index cb971de..a4dfe99 100644 --- a/python/tests/test_chats.py +++ b/python/tests/test_chats.py @@ -209,23 +209,23 @@ def test_list_messeges_in_chat_integration(httpserver): ) ) - assert len(collection.Messages) == len(data.chat_messages[data.group_chats[0].id]) - - assert collection.Messages[0].ID == data.chat_messages[data.group_chats[0].id][0].ID - assert collection.Messages[0].Content == data.chat_messages[data.group_chats[0].id][0].Content - assert collection.Messages[0].ContentType == MessageContentType(data.chat_messages[data.group_chats[0].id][0].ContentType) - assert collection.Messages[0].From.UserID == data.chat_messages[data.group_chats[0].id][0].From.UserID - assert collection.Messages[0].From.DisplayName == data.chat_messages[data.group_chats[0].id][0].From.DisplayName - assert collection.Messages[0].ReplyCount == data.chat_messages[data.group_chats[0].id][0].ReplyCount - assert collection.Messages[0].CreatedDateTime == data.chat_messages[data.group_chats[0].id][0].CreatedDateTime - - assert collection.Messages[1].ID == data.chat_messages[data.group_chats[0].id][1].ID - assert collection.Messages[1].Content == data.chat_messages[data.group_chats[0].id][1].Content - assert collection.Messages[1].ContentType == MessageContentType(data.chat_messages[data.group_chats[0].id][1].ContentType) - assert collection.Messages[1].From.UserID == data.chat_messages[data.group_chats[0].id][1].From.UserID - assert collection.Messages[1].From.DisplayName == data.chat_messages[data.group_chats[0].id][1].From.DisplayName - assert collection.Messages[1].ReplyCount == data.chat_messages[data.group_chats[0].id][1].ReplyCount - assert collection.Messages[1].CreatedDateTime == data.chat_messages[data.group_chats[0].id][1].CreatedDateTime + assert len(collection.messages) == len(data.chat_messages[data.group_chats[0].id]) + + assert collection.messages[0].id == data.chat_messages[data.group_chats[0].id][0].id + assert collection.messages[0].content == data.chat_messages[data.group_chats[0].id][0].content + assert collection.messages[0].content_type == MessageContentType(data.chat_messages[data.group_chats[0].id][0].content_type) + assert collection.messages[0].sender.user_id == data.chat_messages[data.group_chats[0].id][0].sender.user_id + assert collection.messages[0].sender.display_name == data.chat_messages[data.group_chats[0].id][0].sender.display_name + assert collection.messages[0].reply_count == data.chat_messages[data.group_chats[0].id][0].reply_count + assert collection.messages[0].created_date_time == data.chat_messages[data.group_chats[0].id][0].created_date_time + + assert collection.messages[1].id == data.chat_messages[data.group_chats[0].id][1].id + assert collection.messages[1].content == data.chat_messages[data.group_chats[0].id][1].content + assert collection.messages[1].content_type == MessageContentType(data.chat_messages[data.group_chats[0].id][1].content_type) + assert collection.messages[1].sender.user_id == data.chat_messages[data.group_chats[0].id][1].sender.user_id + assert collection.messages[1].sender.display_name == data.chat_messages[data.group_chats[0].id][1].sender.display_name + assert collection.messages[1].reply_count == data.chat_messages[data.group_chats[0].id][1].reply_count + assert collection.messages[1].created_date_time == data.chat_messages[data.group_chats[0].id][1].created_date_time finally: client.close() @@ -244,19 +244,19 @@ def test_send_message_in_chat_integration(httpserver): type=ChatType.GROUP, ), body=MessageBody( - Content=data.newMessageTemplate.Content, - ContentType=MessageContentType(data.newMessageTemplate.ContentType), - Mentions=[], + content=data.newMessageTemplate.content, + content_type=MessageContentType(data.newMessageTemplate.content_type), + mentions=[], ) ) - assert message.ID == data.newMessageTemplate.ID - assert message.Content == data.newMessageTemplate.Content - assert message.ContentType == MessageContentType(data.newMessageTemplate.ContentType) - assert message.From.UserID == data.newMessageTemplate.From.UserID - assert message.From.DisplayName == data.newMessageTemplate.From.DisplayName - assert message.ReplyCount == data.newMessageTemplate.ReplyCount - assert message.CreatedDateTime == data.newMessageTemplate.CreatedDateTime + assert message.id == data.newMessageTemplate.id + assert message.content == data.newMessageTemplate.content + assert message.content_type == MessageContentType(data.newMessageTemplate.content_type) + assert message.sender.user_id == data.newMessageTemplate.sender.user_id + assert message.sender.display_name == data.newMessageTemplate.sender.display_name + assert message.reply_count == data.newMessageTemplate.reply_count + assert message.created_date_time == data.newMessageTemplate.created_date_time finally: client.close() @@ -274,7 +274,7 @@ def test_delete_message_in_chat_integration(httpserver): ref=data.group_chats[0].topic, type=ChatType.GROUP, ), - message_id=data.chat_messages[data.group_chats[0].id][0].ID, + message_id=data.chat_messages[data.group_chats[0].id][0].id, ) assert result is True @@ -295,16 +295,16 @@ def test_get_message_in_chat_integration(httpserver): ref=data.group_chats[0].topic, type=ChatType.GROUP, ), - message_id=data.chat_messages[data.group_chats[0].id][0].ID, + message_id=data.chat_messages[data.group_chats[0].id][0].id, ) - assert message.ID == data.chat_messages[data.group_chats[0].id][0].ID - assert message.Content == data.chat_messages[data.group_chats[0].id][0].Content - assert message.ContentType == MessageContentType(data.chat_messages[data.group_chats[0].id][0].ContentType) - assert message.From.UserID == data.chat_messages[data.group_chats[0].id][0].From.UserID - assert message.From.DisplayName == data.chat_messages[data.group_chats[0].id][0].From.DisplayName - assert message.ReplyCount == data.chat_messages[data.group_chats[0].id][0].ReplyCount - assert message.CreatedDateTime == data.chat_messages[data.group_chats[0].id][0].CreatedDateTime + assert message.id == data.chat_messages[data.group_chats[0].id][0].id + assert message.content == data.chat_messages[data.group_chats[0].id][0].content + assert message.content_type == MessageContentType(data.chat_messages[data.group_chats[0].id][0].content_type) + assert message.sender.user_id == data.chat_messages[data.group_chats[0].id][0].sender.user_id + assert message.sender.display_name == data.chat_messages[data.group_chats[0].id][0].sender.display_name + assert message.reply_count == data.chat_messages[data.group_chats[0].id][0].reply_count + assert message.created_date_time == data.chat_messages[data.group_chats[0].id][0].created_date_time finally: client.close() @@ -325,21 +325,21 @@ def test_get_all_messages_in_chat_integration(httpserver): assert len(messages) == len(data.chat_messages[data.group_chats[0].id]) - assert messages[0].ID == data.chat_messages[data.group_chats[0].id][0].ID - assert messages[0].Content == data.chat_messages[data.group_chats[0].id][0].Content - assert messages[0].ContentType == MessageContentType(data.chat_messages[data.group_chats[0].id][0].ContentType) - assert messages[0].From.UserID == data.chat_messages[data.group_chats[0].id][0].From.UserID - assert messages[0].From.DisplayName == data.chat_messages[data.group_chats[0].id][0].From.DisplayName - assert messages[0].ReplyCount == 0 - assert messages[0].CreatedDateTime == data.chat_messages[data.group_chats[0].id][0].CreatedDateTime - - assert messages[1].ID == data.chat_messages[data.group_chats[0].id][1].ID - assert messages[1].Content == data.chat_messages[data.group_chats[0].id][1].Content - assert messages[1].ContentType == MessageContentType(data.chat_messages[data.group_chats[0].id][1].ContentType) - assert messages[1].From.UserID == data.chat_messages[data.group_chats[0].id][1].From.UserID - assert messages[1].From.DisplayName == data.chat_messages[data.group_chats[0].id][1].From.DisplayName - assert messages[1].ReplyCount == 0 - assert messages[1].CreatedDateTime == data.chat_messages[data.group_chats[0].id][1].CreatedDateTime + assert messages[0].id == data.chat_messages[data.group_chats[0].id][0].id + assert messages[0].content == data.chat_messages[data.group_chats[0].id][0].content + assert messages[0].content_type == MessageContentType(data.chat_messages[data.group_chats[0].id][0].content_type) + assert messages[0].sender.user_id == data.chat_messages[data.group_chats[0].id][0].sender.user_id + assert messages[0].sender.display_name == data.chat_messages[data.group_chats[0].id][0].sender.display_name + assert messages[0].reply_count == 0 + assert messages[0].created_date_time == data.chat_messages[data.group_chats[0].id][0].created_date_time + + assert messages[1].id == data.chat_messages[data.group_chats[0].id][1].id + assert messages[1].content == data.chat_messages[data.group_chats[0].id][1].content + assert messages[1].content_type == MessageContentType(data.chat_messages[data.group_chats[0].id][1].content_type) + assert messages[1].sender.user_id == data.chat_messages[data.group_chats[0].id][1].sender.user_id + assert messages[1].sender.display_name == data.chat_messages[data.group_chats[0].id][1].sender.display_name + assert messages[1].reply_count == 0 + assert messages[1].created_date_time == data.chat_messages[data.group_chats[0].id][1].created_date_time finally: client.close() @@ -362,21 +362,21 @@ def test_list_pinned_messages_integration(httpserver): assert len(messages) == len(data.chat_messages[data.group_chats[0].id]) - assert messages[0].ID == data.chat_messages[data.group_chats[0].id][0].ID - assert messages[0].Content == data.chat_messages[data.group_chats[0].id][0].Content - assert messages[0].ContentType == MessageContentType(data.chat_messages[data.group_chats[0].id][0].ContentType) - assert messages[0].From.UserID == data.chat_messages[data.group_chats[0].id][0].From.UserID - assert messages[0].From.DisplayName == data.chat_messages[data.group_chats[0].id][0].From.DisplayName - assert messages[0].ReplyCount == 0 - assert messages[0].CreatedDateTime == data.chat_messages[data.group_chats[0].id][0].CreatedDateTime - - assert messages[1].ID == data.chat_messages[data.group_chats[0].id][1].ID - assert messages[1].Content == data.chat_messages[data.group_chats[0].id][1].Content - assert messages[1].ContentType == MessageContentType(data.chat_messages[data.group_chats[0].id][1].ContentType) - assert messages[1].From.UserID == data.chat_messages[data.group_chats[0].id][1].From.UserID - assert messages[1].From.DisplayName == data.chat_messages[data.group_chats[0].id][1].From.DisplayName - assert messages[1].ReplyCount == 0 - assert messages[1].CreatedDateTime == data.chat_messages[data.group_chats[0].id][1].CreatedDateTime + assert messages[0].id == data.chat_messages[data.group_chats[0].id][0].id + assert messages[0].content == data.chat_messages[data.group_chats[0].id][0].content + assert messages[0].content_type == MessageContentType(data.chat_messages[data.group_chats[0].id][0].content_type) + assert messages[0].sender.user_id == data.chat_messages[data.group_chats[0].id][0].sender.user_id + assert messages[0].sender.display_name == data.chat_messages[data.group_chats[0].id][0].sender.display_name + assert messages[0].reply_count == 0 + assert messages[0].created_date_time == data.chat_messages[data.group_chats[0].id][0].created_date_time + + assert messages[1].id == data.chat_messages[data.group_chats[0].id][1].id + assert messages[1].content == data.chat_messages[data.group_chats[0].id][1].content + assert messages[1].content_type == MessageContentType(data.chat_messages[data.group_chats[0].id][1].content_type) + assert messages[1].sender.user_id == data.chat_messages[data.group_chats[0].id][1].sender.user_id + assert messages[1].sender.display_name == data.chat_messages[data.group_chats[0].id][1].sender.display_name + assert messages[1].reply_count == 0 + assert messages[1].created_date_time == data.chat_messages[data.group_chats[0].id][1].created_date_time finally: client.close() @@ -395,7 +395,7 @@ def test_pin_message_in_chat_integration(httpserver): ref=data.group_chats[0].topic, type=ChatType.GROUP, ), - message_id=data.chat_messages[data.group_chats[0].id][0].ID, + message_id=data.chat_messages[data.group_chats[0].id][0].id, ) assert result is True @@ -417,7 +417,7 @@ def test_unpin_message_in_chat_integration(httpserver): ref=data.group_chats[0].topic, type=ChatType.GROUP, ), - message_id=data.chat_messages[data.group_chats[0].id][0].ID, + message_id=data.chat_messages[data.group_chats[0].id][0].id, ) assert result is True From 372aac60fcf422e84f14f069cc7f3aa0b93125ed Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Thu, 8 Jan 2026 10:45:41 +0100 Subject: [PATCH 69/99] docs: added docstrings for message models --- python/teams_lib_pzsp2_z1/model/message.py | 61 ++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/python/teams_lib_pzsp2_z1/model/message.py b/python/teams_lib_pzsp2_z1/model/message.py index 485acf6..08d87c0 100644 --- a/python/teams_lib_pzsp2_z1/model/message.py +++ b/python/teams_lib_pzsp2_z1/model/message.py @@ -6,18 +6,47 @@ class MessageContentType(Enum): + """Represents the type of content in a Microsoft Teams message. + + Attributes: + TEXT: Plain text content. + HTML: HTML formatted content. + """ + TEXT = "text" HTML = "html" @dataclass class MessageFrom: + """Represents the sender of a message in Microsoft Teams. + + Attributes: + user_id (str): The unique Azure Active Directory (AAD) identifier of the sender. + display_name (str): The visible name of the sender. + """ + user_id: str display_name: str @dataclass class Message: + """Represents a Microsoft Teams chat message. + + Used in both direct chats and channels. + + Attributes: + id (str): The unique identifier of the message. + content (str): The actual body text or HTML of the message. + content_type (MessageContentType): The format of the content (Text or HTML). + created_date_time (datetime): The timestamp when the message was created. + sender (MessageFrom): The user who sent the message. + **Note:** Mapped from the `From` field in the API/Go model to avoid + conflict with the Python `from` keyword. + reply_count (int): The number of replies to this message. + """ + id: str content: str content_type: MessageContentType @@ -28,28 +57,60 @@ class Message: @dataclass class MessageBody: + """Represents the body of a message to be sent. + + This class contains the payload structure required to send a new message + or reply. + + Attributes: + content_type (MessageContentType): The format of the content. + content (str): The text or HTML content. + mentions (List[Mention]): A list of mentions to include in the message. + """ + content_type: MessageContentType content: str mentions: list[Mention] def __dict__(self): + """Serializes the object keys to PascalCase for the Go backend.""" + return { "ContentType": self.content_type.value, "Content": self.content, } def __iter__(self): + """Allows iteration over fields with PascalCase keys.""" + yield "ContentType", self.content_type.value yield "Content", self.content @dataclass class ListMessagesOptions: + """Contains options for listing messages (pagination and expansion). + + Attributes: + top (Optional[int]): The maximum number of messages to return in one page. + Defaults to None (server default). + expand_replies (bool): If True, the response will include replies + nested within the messages. Defaults to False. + """ + top: int | None = None expand_replies: bool = False @dataclass class MessageCollection: + """Represents a paginated collection of messages. + + Attributes: + messages (List[Message]): The list of message objects in the current page. + next_link (Optional[str]): The URL to retrieve the next page of results. + If None, there are no more messages. + """ + messages: list[Message] next_link: str | None = None From c7fb49d1e66f1bb258bbf850b0417cde213f562f Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Thu, 8 Jan 2026 10:47:33 +0100 Subject: [PATCH 70/99] docs: added docstrings for team models --- python/teams_lib_pzsp2_z1/model/team.py | 35 +++- python/teams_lib_pzsp2_z1/services/teams.py | 40 ++--- python/tests/fake_server/data.py | 94 +++++----- python/tests/test_channels.py | 184 ++++++++++---------- python/tests/test_teams.py | 64 +++---- 5 files changed, 220 insertions(+), 197 deletions(-) diff --git a/python/teams_lib_pzsp2_z1/model/team.py b/python/teams_lib_pzsp2_z1/model/team.py index 0d17576..aa690d7 100644 --- a/python/teams_lib_pzsp2_z1/model/team.py +++ b/python/teams_lib_pzsp2_z1/model/team.py @@ -3,15 +3,38 @@ @dataclass class Team: - ID: str - DisplayName: str - Description: str - IsArchived: bool - Visibility: str + """Represents a Microsoft Teams team. + + Attributes: + id (str): The unique identifier (UUID) of the team. + display_name (str): The name of the team. + description (str): A description of the team's purpose. + is_archived (bool): Indicates whether the team is in an archived state. + Archived teams are read-only for members. + visibility (str): The privacy setting of the team. + Typically "public", "private". + """ + + id: str + display_name: str + description: str + is_archived: bool + visibility: str @dataclass class UpdateTeam: - displayName: str | None = None + """Represents the subset of Team fields that can be updated. + + This class is used as a payload for patch/update operations. + Only fields that are not None will be sent to the API. + + Attributes: + display_name (Optional[str]): The new name for the team. + description (Optional[str]): The new description. + visibility (Optional[str]): The new visibility setting (e.g., "private"). + """ + + display_name: str | None = None description: str | None = None visibility: str | None = None diff --git a/python/teams_lib_pzsp2_z1/services/teams.py b/python/teams_lib_pzsp2_z1/services/teams.py index e04f738..06646c6 100644 --- a/python/teams_lib_pzsp2_z1/services/teams.py +++ b/python/teams_lib_pzsp2_z1/services/teams.py @@ -38,11 +38,11 @@ def get(self, team_ref: str) -> Team: ) return Team( - ID=response["ID"], - DisplayName=response["DisplayName"], - Description=response["Description"], - IsArchived=(True if response["IsArchived"] else False), - Visibility=response["Visibility"], + id=response["ID"], + display_name=response["DisplayName"], + description=response["Description"], + is_archived=(True if response["IsArchived"] else False), + visibility=response["Visibility"], ) def list_my_joined(self) -> list[Team]: @@ -58,11 +58,11 @@ def list_my_joined(self) -> list[Team]: ) return [ Team( - ID=team["ID"], - DisplayName=team["DisplayName"], - Description=team["Description"], - IsArchived=(True if team["IsArchived"] else False), - Visibility=team["Visibility"], + id=team["ID"], + display_name=team["DisplayName"], + description=team["Description"], + is_archived=(True if team["IsArchived"] else False), + visibility=team["Visibility"], ) for team in response ] @@ -90,11 +90,11 @@ def update(self, team_ref: str, update: UpdateTeam) -> Team: params=params, ) return Team( - ID=response["ID"], - DisplayName=response["DisplayName"], - Description=response["Description"], - IsArchived=(True if response["IsArchived"] else False), - Visibility=response["Visibility"], + id=response["ID"], + display_name=response["DisplayName"], + description=response["Description"], + is_archived=(True if response["IsArchived"] else False), + visibility=response["Visibility"], ) def create_via_group( @@ -120,11 +120,11 @@ def create_via_group( }, ) return Team( - ID=response["ID"], - DisplayName=response["DisplayName"], - Description=response["Description"], - IsArchived=(True if response["IsArchived"] else False), - Visibility=response["Visibility"], + id=response["ID"], + display_name=response["DisplayName"], + description=response["Description"], + is_archived=(True if response["IsArchived"] else False), + visibility=response["Visibility"], ) def create_from_template( # noqa: PLR0913 diff --git a/python/tests/fake_server/data.py b/python/tests/fake_server/data.py index 82bbf00..50fbeaa 100644 --- a/python/tests/fake_server/data.py +++ b/python/tests/fake_server/data.py @@ -59,9 +59,9 @@ class FakeServerData: def __init__(self) -> None: # --- Teams --- self.teams = [ - Team(ID="team-123-abc", DisplayName="Test Team", Description="A team for testing", IsArchived=False, Visibility="private"), - Team(ID="team-456-def", DisplayName="Another Team", Description="Another team for testing", IsArchived=False, Visibility="public"), - Team(ID="archived-team-789-ghi", DisplayName="Archived Team", Description="An archived team for testing", IsArchived=True, Visibility="private") + Team(id="team-123-abc", display_name="Test Team", description="A team for testing", is_archived=False, visibility="private"), + Team(id="team-456-def", display_name="Another Team", description="Another team for testing", is_archived=False, visibility="public"), + Team(id="archived-team-789-ghi", display_name="Archived Team", description="An archived team for testing", is_archived=True, visibility="private") ] self.potentialTeams = [] @@ -157,7 +157,7 @@ def __init__(self) -> None: # --- Helpers --- def _find_team(self, team_id: str) -> Optional[Team]: - return next((t for t in self.teams if t.ID == team_id), None) + return next((t for t in self.teams if t.id == team_id), None) def _find_channel(self, team_id: str, channel_id: str) -> Optional[Channel]: return next((c for c in self.channels.get(team_id, []) if c.id == channel_id), None) @@ -170,11 +170,11 @@ def get_myJoinedTeams_response(self) -> dict: return { "value": [ { - "id": team.ID, - "displayName": team.DisplayName, - "description": team.Description, - "isArchived": team.IsArchived, - "visibility": team.Visibility, + "id": team.id, + "displayName": team.display_name, + "description": team.description, + "isArchived": team.is_archived, + "visibility": team.visibility, } for team in self.teams ], @@ -185,11 +185,11 @@ def get_team_response(self, team_id: str) -> Optional[dict]: if not team: return None return { - "id": team.ID, - "displayName": team.DisplayName, - "description": team.Description, - "isArchived": team.IsArchived, - "visibility": team.Visibility, + "id": team.id, + "displayName": team.display_name, + "description": team.description, + "isArchived": team.is_archived, + "visibility": team.visibility, } def get_updateTeam_response(self, team_id: str, update_json: dict) -> Optional[dict]: @@ -198,18 +198,18 @@ def get_updateTeam_response(self, team_id: str, update_json: dict) -> Optional[d return None if "displayName" in update_json: - team.DisplayName = update_json["displayName"] + team.display_name = update_json["displayName"] if "description" in update_json: - team.Description = update_json["description"] + team.description = update_json["description"] if "visibility" in update_json: - team.Visibility = update_json["visibility"] + team.visibility = update_json["visibility"] return { - "id": team.ID, - "displayName": team.DisplayName, - "description": team.Description, - "isArchived": team.IsArchived, - "visibility": team.Visibility, + "id": team.id, + "displayName": team.display_name, + "description": team.description, + "isArchived": team.is_archived, + "visibility": team.visibility, } def get_createGroup_response(self, request_json: dict) -> dict: @@ -219,11 +219,11 @@ def get_createGroup_response(self, request_json: dict) -> dict: self.potentialTeams.append( Team( - ID=self.newGroupID, - DisplayName=request_json.get("displayName"), - Description=request_json.get("description"), - IsArchived=False, - Visibility=visibility, + id=self.newGroupID, + display_name=request_json.get("displayName"), + description=request_json.get("description"), + is_archived=False, + visibility=visibility, ) ) @@ -240,27 +240,27 @@ def get_createGroup_response(self, request_json: dict) -> dict: } def get_createTeamViaGroup_response(self, group_id: str) -> dict: - team = next((t for t in self.potentialTeams if t.ID == group_id), None) + team = next((t for t in self.potentialTeams if t.id == group_id), None) if not team: return {} self.teams.append(team) return { - "id": team.ID, - "displayName": team.DisplayName, - "description": team.Description, - "isArchived": team.IsArchived, - "visibility": team.Visibility, + "id": team.id, + "displayName": team.display_name, + "description": team.description, + "isArchived": team.is_archived, + "visibility": team.visibility, } def get_createTeamFromTemplate_response(self, request_json: dict) -> dict: new_team = Team( - ID=self.newTeamID, - DisplayName=request_json.get("displayName"), - Description=request_json.get("description"), - IsArchived=False, - Visibility="private", + id=self.newTeamID, + display_name=request_json.get("displayName"), + description=request_json.get("description"), + is_archived=False, + visibility="private", ) self.teams.append(new_team) @@ -282,14 +282,14 @@ def get_archiveTeam_response(self, team_id: str) -> dict: team = self._find_team(team_id) if not team: return {"success": False} - team.IsArchived = True + team.is_archived = True return {"success": True} def get_unarchiveTeam_response(self, team_id: str) -> dict: team = self._find_team(team_id) if not team: return {"success": False} - team.IsArchived = False + team.is_archived = False return {"success": True} def get_deleteTeam_response(self, team_id: str) -> dict: @@ -300,17 +300,17 @@ def get_deleteTeam_response(self, team_id: str) -> dict: return {"success": True} def get_restoreTeam_response(self, team_id: str) -> dict: - archived_team = next((t for t in self.teams if t.ID == team_id and t.IsArchived), None) + archived_team = next((t for t in self.teams if t.id == team_id and t.is_archived), None) if not archived_team: return {"success": False} - archived_team.IsArchived = False + archived_team.is_archived = False return { - "id": archived_team.ID, - "displayName": archived_team.DisplayName, - "description": archived_team.Description, - "isArchived": archived_team.IsArchived, - "visibility": archived_team.Visibility, + "id": archived_team.id, + "displayName": archived_team.display_name, + "description": archived_team.description, + "isArchived": archived_team.is_archived, + "visibility": archived_team.visibility, "mailNickname": self.newGroupMailNickname } diff --git a/python/tests/test_channels.py b/python/tests/test_channels.py index 8635287..c8989f8 100644 --- a/python/tests/test_channels.py +++ b/python/tests/test_channels.py @@ -17,17 +17,17 @@ def test_list_channels_integration(httpserver): try: init_fake_client(client, httpserver.url_for("")) - channels = client.channels.list_channels(data.teams[0].DisplayName) + channels = client.channels.list_channels(data.teams[0].display_name) - assert len(channels) == len(data.channels[data.teams[0].ID]) + assert len(channels) == len(data.channels[data.teams[0].id]) - assert channels[0].name == data.channels[data.teams[0].ID][0].name - assert channels[0].id == data.channels[data.teams[0].ID][0].id - assert channels[0].is_general == data.channels[data.teams[0].ID][0].is_general + assert channels[0].name == data.channels[data.teams[0].id][0].name + assert channels[0].id == data.channels[data.teams[0].id][0].id + assert channels[0].is_general == data.channels[data.teams[0].id][0].is_general - assert channels[1].name == data.channels[data.teams[0].ID][1].name - assert channels[1].id == data.channels[data.teams[0].ID][1].id - assert channels[1].is_general == data.channels[data.teams[0].ID][1].is_general + assert channels[1].name == data.channels[data.teams[0].id][1].name + assert channels[1].id == data.channels[data.teams[0].id][1].id + assert channels[1].is_general == data.channels[data.teams[0].id][1].is_general finally: client.close() @@ -42,13 +42,13 @@ def test_get_channel_integration(httpserver): init_fake_client(client, httpserver.url_for("")) channel = client.channels.get( - team_ref=data.teams[0].DisplayName, - channel_ref=data.channels[data.teams[0].ID][1].name, + team_ref=data.teams[0].display_name, + channel_ref=data.channels[data.teams[0].id][1].name, ) - assert channel.name == data.channels[data.teams[0].ID][1].name - assert channel.id == data.channels[data.teams[0].ID][1].id - assert channel.is_general == data.channels[data.teams[0].ID][1].is_general + assert channel.name == data.channels[data.teams[0].id][1].name + assert channel.id == data.channels[data.teams[0].id][1].id + assert channel.is_general == data.channels[data.teams[0].id][1].is_general finally: client.close() @@ -63,7 +63,7 @@ def test_create_standard_channel_integration(httpserver): init_fake_client(client, httpserver.url_for("")) channel = client.channels.create_standard( - team_ref=data.teams[0].DisplayName, + team_ref=data.teams[0].display_name, display_name=data.newChannelName, ) @@ -84,7 +84,7 @@ def test_create_private_channel_integration(httpserver): init_fake_client(client, httpserver.url_for("")) channel = client.channels.create_private( - team_ref=data.teams[0].DisplayName, + team_ref=data.teams[0].display_name, display_name=data.newChannelName, member_refs=[], owner_refs=[], @@ -107,8 +107,8 @@ def test_delete_channel_integration(httpserver): init_fake_client(client, httpserver.url_for("")) success = client.channels.delete( - team_ref=data.teams[0].DisplayName, - channel_ref=data.channels[data.teams[0].ID][1].name, + team_ref=data.teams[0].display_name, + channel_ref=data.channels[data.teams[0].id][1].name, ) assert success is True @@ -126,8 +126,8 @@ def test_send_message_integration(httpserver): init_fake_client(client, httpserver.url_for("")) message = client.channels.send_message( - team_ref=data.teams[0].DisplayName, - channel_ref=data.channels[data.teams[0].ID][0].name, + team_ref=data.teams[0].display_name, + channel_ref=data.channels[data.teams[0].id][0].name, body=MessageBody( content=data.newMessageTemplate.content, content_type=MessageContentType.TEXT, @@ -156,26 +156,26 @@ def test_list_messages_integration(httpserver): init_fake_client(client, httpserver.url_for("")) collection = client.channels.list_messages( - team_ref=data.teams[0].DisplayName, - channel_ref=data.channels[data.teams[0].ID][0].name, + team_ref=data.teams[0].display_name, + channel_ref=data.channels[data.teams[0].id][0].name, ) - assert len(collection.messages) == len(data.messages[data.channels[data.teams[0].ID][0].id]) - assert collection.messages[0].id == data.messages[data.channels[data.teams[0].ID][0].id][0].id - assert collection.messages[0].content == data.messages[data.channels[data.teams[0].ID][0].id][0].content - assert collection.messages[0].content_type == MessageContentType(data.messages[data.channels[data.teams[0].ID][0].id][0].content_type) - assert collection.messages[0].sender.user_id == data.messages[data.channels[data.teams[0].ID][0].id][0].sender.user_id - assert collection.messages[0].sender.display_name == data.messages[data.channels[data.teams[0].ID][0].id][0].sender.display_name - assert collection.messages[0].reply_count == data.messages[data.channels[data.teams[0].ID][0].id][0].reply_count - assert collection.messages[0].created_date_time == data.messages[data.channels[data.teams[0].ID][0].id][0].created_date_time - - assert collection.messages[1].id == data.messages[data.channels[data.teams[0].ID][0].id][1].id - assert collection.messages[1].content == data.messages[data.channels[data.teams[0].ID][0].id][1].content - assert collection.messages[1].content_type == MessageContentType(data.messages[data.channels[data.teams[0].ID][0].id][1].content_type) - assert collection.messages[1].sender.user_id == data.messages[data.channels[data.teams[0].ID][0].id][1].sender.user_id - assert collection.messages[1].sender.display_name == data.messages[data.channels[data.teams[0].ID][0].id][1].sender.display_name - assert collection.messages[1].reply_count == data.messages[data.channels[data.teams[0].ID][0].id][1].reply_count - assert collection.messages[1].created_date_time == data.messages[data.channels[data.teams[0].ID][0].id][1].created_date_time + assert len(collection.messages) == len(data.messages[data.channels[data.teams[0].id][0].id]) + assert collection.messages[0].id == data.messages[data.channels[data.teams[0].id][0].id][0].id + assert collection.messages[0].content == data.messages[data.channels[data.teams[0].id][0].id][0].content + assert collection.messages[0].content_type == MessageContentType(data.messages[data.channels[data.teams[0].id][0].id][0].content_type) + assert collection.messages[0].sender.user_id == data.messages[data.channels[data.teams[0].id][0].id][0].sender.user_id + assert collection.messages[0].sender.display_name == data.messages[data.channels[data.teams[0].id][0].id][0].sender.display_name + assert collection.messages[0].reply_count == data.messages[data.channels[data.teams[0].id][0].id][0].reply_count + assert collection.messages[0].created_date_time == data.messages[data.channels[data.teams[0].id][0].id][0].created_date_time + + assert collection.messages[1].id == data.messages[data.channels[data.teams[0].id][0].id][1].id + assert collection.messages[1].content == data.messages[data.channels[data.teams[0].id][0].id][1].content + assert collection.messages[1].content_type == MessageContentType(data.messages[data.channels[data.teams[0].id][0].id][1].content_type) + assert collection.messages[1].sender.user_id == data.messages[data.channels[data.teams[0].id][0].id][1].sender.user_id + assert collection.messages[1].sender.display_name == data.messages[data.channels[data.teams[0].id][0].id][1].sender.display_name + assert collection.messages[1].reply_count == data.messages[data.channels[data.teams[0].id][0].id][1].reply_count + assert collection.messages[1].created_date_time == data.messages[data.channels[data.teams[0].id][0].id][1].created_date_time finally: client.close() @@ -189,18 +189,18 @@ def test_get_message_integration(httpserver): init_fake_client(client, httpserver.url_for("")) message = client.channels.get_message( - team_ref=data.teams[0].DisplayName, - channel_ref=data.channels[data.teams[0].ID][0].name, - message_id=data.messages[data.channels[data.teams[0].ID][0].id][0].id, + team_ref=data.teams[0].display_name, + channel_ref=data.channels[data.teams[0].id][0].name, + message_id=data.messages[data.channels[data.teams[0].id][0].id][0].id, ) - assert message.id == data.messages[data.channels[data.teams[0].ID][0].id][0].id - assert message.content == data.messages[data.channels[data.teams[0].ID][0].id][0].content - assert message.content_type == MessageContentType(data.messages[data.channels[data.teams[0].ID][0].id][0].content_type) - assert message.sender.user_id == data.messages[data.channels[data.teams[0].ID][0].id][0].sender.user_id - assert message.sender.display_name == data.messages[data.channels[data.teams[0].ID][0].id][0].sender.display_name - assert message.reply_count == data.messages[data.channels[data.teams[0].ID][0].id][0].reply_count - assert message.created_date_time == data.messages[data.channels[data.teams[0].ID][0].id][0].created_date_time + assert message.id == data.messages[data.channels[data.teams[0].id][0].id][0].id + assert message.content == data.messages[data.channels[data.teams[0].id][0].id][0].content + assert message.content_type == MessageContentType(data.messages[data.channels[data.teams[0].id][0].id][0].content_type) + assert message.sender.user_id == data.messages[data.channels[data.teams[0].id][0].id][0].sender.user_id + assert message.sender.display_name == data.messages[data.channels[data.teams[0].id][0].id][0].sender.display_name + assert message.reply_count == data.messages[data.channels[data.teams[0].id][0].id][0].reply_count + assert message.created_date_time == data.messages[data.channels[data.teams[0].id][0].id][0].created_date_time finally: client.close() @@ -215,25 +215,25 @@ def test_list_replies_integration(httpserver): init_fake_client(client, httpserver.url_for("")) replies = client.channels.list_message_replies( - team_ref=data.teams[0].DisplayName, - channel_ref=data.channels[data.teams[0].ID][0].name, - message_id=data.messages[data.channels[data.teams[0].ID][0].id][1].id, + team_ref=data.teams[0].display_name, + channel_ref=data.channels[data.teams[0].id][0].name, + message_id=data.messages[data.channels[data.teams[0].id][0].id][1].id, ) - assert len(replies.messages) == len(data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].id]) - assert replies.messages[0].id == data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].id][0].id - assert replies.messages[0].content == data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].id][0].content - assert replies.messages[0].content_type == MessageContentType(data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].id][0].content_type) - assert replies.messages[0].sender.user_id == data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].id][0].sender.user_id - assert replies.messages[0].sender.display_name == data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].id][0].sender.display_name - assert replies.messages[0].created_date_time == data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].id][0].created_date_time - - assert replies.messages[1].id == data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].id][1].id - assert replies.messages[1].content == data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].id][1].content - assert replies.messages[1].content_type == MessageContentType(data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].id][1].content_type) - assert replies.messages[1].sender.user_id == data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].id][1].sender.user_id - assert replies.messages[1].sender.display_name == data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].id][1].sender.display_name - assert replies.messages[1].created_date_time == data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].id][1].created_date_time + assert len(replies.messages) == len(data.replies[data.messages[data.channels[data.teams[0].id][0].id][1].id]) + assert replies.messages[0].id == data.replies[data.messages[data.channels[data.teams[0].id][0].id][1].id][0].id + assert replies.messages[0].content == data.replies[data.messages[data.channels[data.teams[0].id][0].id][1].id][0].content + assert replies.messages[0].content_type == MessageContentType(data.replies[data.messages[data.channels[data.teams[0].id][0].id][1].id][0].content_type) + assert replies.messages[0].sender.user_id == data.replies[data.messages[data.channels[data.teams[0].id][0].id][1].id][0].sender.user_id + assert replies.messages[0].sender.display_name == data.replies[data.messages[data.channels[data.teams[0].id][0].id][1].id][0].sender.display_name + assert replies.messages[0].created_date_time == data.replies[data.messages[data.channels[data.teams[0].id][0].id][1].id][0].created_date_time + + assert replies.messages[1].id == data.replies[data.messages[data.channels[data.teams[0].id][0].id][1].id][1].id + assert replies.messages[1].content == data.replies[data.messages[data.channels[data.teams[0].id][0].id][1].id][1].content + assert replies.messages[1].content_type == MessageContentType(data.replies[data.messages[data.channels[data.teams[0].id][0].id][1].id][1].content_type) + assert replies.messages[1].sender.user_id == data.replies[data.messages[data.channels[data.teams[0].id][0].id][1].id][1].sender.user_id + assert replies.messages[1].sender.display_name == data.replies[data.messages[data.channels[data.teams[0].id][0].id][1].id][1].sender.display_name + assert replies.messages[1].created_date_time == data.replies[data.messages[data.channels[data.teams[0].id][0].id][1].id][1].created_date_time finally: client.close() @@ -248,18 +248,18 @@ def test_get_reply_integration(httpserver): init_fake_client(client, httpserver.url_for("")) reply = client.channels.get_message_reply( - team_ref=data.teams[0].DisplayName, - channel_ref=data.channels[data.teams[0].ID][0].name, - message_id=data.messages[data.channels[data.teams[0].ID][0].id][1].id, - reply_id=data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].id][0].id, + team_ref=data.teams[0].display_name, + channel_ref=data.channels[data.teams[0].id][0].name, + message_id=data.messages[data.channels[data.teams[0].id][0].id][1].id, + reply_id=data.replies[data.messages[data.channels[data.teams[0].id][0].id][1].id][0].id, ) - assert reply.id == data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].id][0].id - assert reply.content == data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].id][0].content - assert reply.content_type == MessageContentType(data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].id][0].content_type) - assert reply.sender.user_id == data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].id][0].sender.user_id - assert reply.sender.display_name == data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].id][0].sender.display_name - assert reply.created_date_time == data.replies[data.messages[data.channels[data.teams[0].ID][0].id][1].id][0].created_date_time + assert reply.id == data.replies[data.messages[data.channels[data.teams[0].id][0].id][1].id][0].id + assert reply.content == data.replies[data.messages[data.channels[data.teams[0].id][0].id][1].id][0].content + assert reply.content_type == MessageContentType(data.replies[data.messages[data.channels[data.teams[0].id][0].id][1].id][0].content_type) + assert reply.sender.user_id == data.replies[data.messages[data.channels[data.teams[0].id][0].id][1].id][0].sender.user_id + assert reply.sender.display_name == data.replies[data.messages[data.channels[data.teams[0].id][0].id][1].id][0].sender.display_name + assert reply.created_date_time == data.replies[data.messages[data.channels[data.teams[0].id][0].id][1].id][0].created_date_time finally: client.close() @@ -272,12 +272,12 @@ def test_list_members_integration(httpserver): try: init_fake_client(client, httpserver.url_for("")) - team_id = data.teams[0].ID + team_id = data.teams[0].id channel = data.channels[team_id][0] channel_id = channel.id members = client.channels.list_members( - team_ref=data.teams[0].DisplayName, + team_ref=data.teams[0].display_name, channel_ref=channel.name, ) @@ -312,8 +312,8 @@ def test_add_member_integration(httpserver): init_fake_client(client, httpserver.url_for("")) member = client.channels.add_member( - team_ref=data.teams[0].DisplayName, - channel_ref=data.channels[data.teams[0].ID][1].name, + team_ref=data.teams[0].display_name, + channel_ref=data.channels[data.teams[0].id][1].name, user_ref=data.newMemberTemplate.display_name, is_owner=True if data.newMemberTemplate.role == "owner" else False, ) @@ -337,17 +337,17 @@ def test_update_member_role_integration(httpserver): init_fake_client(client, httpserver.url_for("")) member = client.channels.update_member_role( - team_ref=data.teams[0].DisplayName, - channel_ref=data.channels[data.teams[0].ID][0].name, - user_ref=data.members[data.teams[0].ID][data.channels[data.teams[0].ID][0].id][1].email, + team_ref=data.teams[0].display_name, + channel_ref=data.channels[data.teams[0].id][0].name, + user_ref=data.members[data.teams[0].id][data.channels[data.teams[0].id][0].id][1].email, is_owner=True ) - assert member.user_id == data.members[data.teams[0].ID][data.channels[data.teams[0].ID][0].id][1].user_id - assert member.display_name == data.members[data.teams[0].ID][data.channels[data.teams[0].ID][0].id][1].display_name + assert member.user_id == data.members[data.teams[0].id][data.channels[data.teams[0].id][0].id][1].user_id + assert member.display_name == data.members[data.teams[0].id][data.channels[data.teams[0].id][0].id][1].display_name assert member.role == "owner" - assert member.email == data.members[data.teams[0].ID][data.channels[data.teams[0].ID][0].id][1].email - assert member.id == data.members[data.teams[0].ID][data.channels[data.teams[0].ID][0].id][1].id + assert member.email == data.members[data.teams[0].id][data.channels[data.teams[0].id][0].id][1].email + assert member.id == data.members[data.teams[0].id][data.channels[data.teams[0].id][0].id][1].id finally: client.close() @@ -361,9 +361,9 @@ def test_remove_member_integration(httpserver): init_fake_client(client, httpserver.url_for("")) success = client.channels.remove_member( - team_ref=data.teams[0].DisplayName, - channel_ref=data.channels[data.teams[0].ID][0].name, - user_ref=data.members[data.teams[0].ID][data.channels[data.teams[0].ID][0].id][1].email, + team_ref=data.teams[0].display_name, + channel_ref=data.channels[data.teams[0].id][0].name, + user_ref=data.members[data.teams[0].id][data.channels[data.teams[0].id][0].id][1].email, ) assert success is True @@ -380,15 +380,15 @@ def test_get_mention_integration(httpserver): init_fake_client(client, httpserver.url_for("")) mention = client.channels.get_mentions( - team_ref=data.teams[0].DisplayName, - channel_ref=data.channels[data.teams[0].ID][0].name, + team_ref=data.teams[0].display_name, + channel_ref=data.channels[data.teams[0].id][0].name, raw_mentions=MentionKind.TEAM.value, ) assert mention[0].kind == MentionKind.TEAM assert mention[0].at_id == 0 - assert mention[0].text == data.teams[0].DisplayName - assert mention[0].target_id == data.teams[0].ID + assert mention[0].text == data.teams[0].display_name + assert mention[0].target_id == data.teams[0].id finally: client.close() diff --git a/python/tests/test_teams.py b/python/tests/test_teams.py index c9e6bb7..3c2d7fb 100644 --- a/python/tests/test_teams.py +++ b/python/tests/test_teams.py @@ -20,17 +20,17 @@ def test_list_my_teams_integration(httpserver): assert len(teams) == len(data.teams) - assert teams[0].DisplayName == data.teams[0].DisplayName - assert teams[0].ID == data.teams[0].ID - assert teams[0].Description == data.teams[0].Description - assert teams[0].IsArchived == data.teams[0].IsArchived - assert teams[0].Visibility == data.teams[0].Visibility - - assert teams[1].DisplayName == data.teams[1].DisplayName - assert teams[1].ID == data.teams[1].ID - assert teams[1].Description == data.teams[1].Description - assert teams[1].IsArchived == data.teams[1].IsArchived - assert teams[1].Visibility == data.teams[1].Visibility + assert teams[0].display_name == data.teams[0].display_name + assert teams[0].id == data.teams[0].id + assert teams[0].description == data.teams[0].description + assert teams[0].is_archived == data.teams[0].is_archived + assert teams[0].visibility == data.teams[0].visibility + + assert teams[1].display_name == data.teams[1].display_name + assert teams[1].id == data.teams[1].id + assert teams[1].description == data.teams[1].description + assert teams[1].is_archived == data.teams[1].is_archived + assert teams[1].visibility == data.teams[1].visibility finally: client.close() @@ -43,15 +43,15 @@ def test_get_team_integration(httpserver): try: init_fake_client(client, httpserver.url_for("")) - team = client.teams.get(data.teams[0].DisplayName) + team = client.teams.get(data.teams[0].display_name) - print(data.get_team_response(data.teams[0].ID)) + print(data.get_team_response(data.teams[0].id)) - assert team.DisplayName == data.teams[0].DisplayName - assert team.ID == data.teams[0].ID - assert team.Description == data.teams[0].Description - assert team.IsArchived == data.teams[0].IsArchived - assert team.Visibility == data.teams[0].Visibility + assert team.display_name == data.teams[0].display_name + assert team.id == data.teams[0].id + assert team.description == data.teams[0].description + assert team.is_archived == data.teams[0].is_archived + assert team.visibility == data.teams[0].visibility finally: client.close() @@ -67,17 +67,17 @@ def test_update_team_integration(httpserver): updated_description = "Updated team description" team = client.teams.update( - team_ref=data.teams[0].DisplayName, + team_ref=data.teams[0].display_name, update=UpdateTeam( description=updated_description, ), ) - assert team.DisplayName == data.teams[0].DisplayName - assert team.ID == data.teams[0].ID - assert team.Description == updated_description - assert team.IsArchived == data.teams[0].IsArchived - assert team.Visibility == data.teams[0].Visibility + assert team.display_name == data.teams[0].display_name + assert team.id == data.teams[0].id + assert team.description == updated_description + assert team.is_archived == data.teams[0].is_archived + assert team.visibility == data.teams[0].visibility finally: client.close() @@ -97,9 +97,9 @@ def test_create_team_via_group_integration(httpserver): visibility=data.newTeamVisibility, ) - assert team.ID == data.newGroupID - assert team.DisplayName == data.newTeamName - assert team.Visibility == data.newTeamVisibility + assert team.id == data.newGroupID + assert team.display_name == data.newTeamName + assert team.visibility == data.newTeamVisibility finally: client.close() @@ -134,7 +134,7 @@ def test_archive_team_integration(httpserver): init_fake_client(client, httpserver.url_for("")) success = client.teams.archive( - team_ref=data.teams[0].DisplayName, + team_ref=data.teams[0].display_name, spo_read_only_from_members=True, ) @@ -153,7 +153,7 @@ def test_unarchive_team_integration(httpserver): init_fake_client(client, httpserver.url_for("")) success = client.teams.unarchive( - team_ref=data.teams[2].DisplayName, + team_ref=data.teams[2].display_name, ) assert success is True @@ -171,7 +171,7 @@ def test_delete_team_integration(httpserver): init_fake_client(client, httpserver.url_for("")) success = client.teams.delete( - team_ref=data.teams[0].DisplayName, + team_ref=data.teams[0].display_name, ) assert success is True @@ -189,10 +189,10 @@ def test_restore_deleted_team_integration(httpserver): init_fake_client(client, httpserver.url_for("")) msg = client.teams.restore_deleted( - deleted_team_ID=data.teams[2].ID, + deleted_team_ID=data.teams[2].id, ) - assert msg == data.teams[2].ID + assert msg == data.teams[2].id finally: client.close() From 46c7046df77ace19350d5ccbde3b7024d664adb0 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Thu, 8 Jan 2026 10:56:43 +0100 Subject: [PATCH 71/99] docs: client init docstrings --- python/teams_lib_pzsp2_z1/client.py | 110 +++++++++++++++++- .../services/base_service.py | 2 + 2 files changed, 108 insertions(+), 4 deletions(-) diff --git a/python/teams_lib_pzsp2_z1/client.py b/python/teams_lib_pzsp2_z1/client.py index bf00843..b7f39c7 100644 --- a/python/teams_lib_pzsp2_z1/client.py +++ b/python/teams_lib_pzsp2_z1/client.py @@ -13,6 +13,24 @@ class TeamsClient: + """The main entry point for interacting with the Microsoft Teams library. + + This class manages the lifecycle of the underlying Go subprocess, handles + Inter-Process Communication (IPC) via JSON over stdin/stdout, and exposes + high-level services for Teams, Channels, and Chats. + + **Architecture:** + The Python library acts as a frontend wrapper. It spawns a compiled Go binary + (`teamsClientLib`) as a subprocess. Commands are serialized to JSON and sent + to the Go process, which executes the actual Microsoft Graph API calls and + returns the results. + + Attributes: + channels (ChannelsService): Service for managing channels. + teams (TeamsService): Service for managing teams. + chats (ChatsService): Service for managing chats and messages. + """ + def __init__( self, auto_init: bool = True, @@ -20,18 +38,37 @@ def __init__( cache_mode: config.CacheMode = config.CacheMode.DISABLED, cache_path: str | None = None, ): + """Initializes the TeamsClient and spawns the Go subprocess. + + Args: + auto_init (bool, optional): If True, automatically calls `init_client` + using configuration loaded from the environment. Defaults to True. + env_path (str | None, optional): Path to the `.env` file containing + credentials (CLIENT_ID, TENANT_ID, etc.). If None, defaults are used. + cache_mode (config.CacheMode, optional): The caching strategy to use + (e.g., DISABLED, IN_MEMORY, DISK). Defaults to DISABLED. + cache_path (str | None, optional): File path for the cache (required if + cache_mode is DISK). Defaults to None. + + Raises: + RuntimeError: If the operating system is not supported (only Windows/Linux). + """ + self._lock = threading.Lock() + # Spawn the Go binary in a subprocess with piped IO self.proc = subprocess.Popen( # noqa: S603 [str(self._binary())], stdin=subprocess.PIPE, stdout=subprocess.PIPE, - stderr=sys.stderr, + stderr=sys.stderr, # Pass Go logs directly to stderr text=True, bufsize=1, ) self.env_path = env_path + + # Initialize services with a reference to self (to access .execute()) self.channels = ChannelsService(self) self.teams = TeamsService(self) self.chats = ChatsService(self) @@ -40,6 +77,15 @@ def __init__( self.init_client(cache_mode, cache_path) def _binary(self): + """Resolves the path to the correct Go binary for the current OS. + + Returns: + pathlib.Path: The absolute path to the executable. + + Raises: + RuntimeError: If the OS is not Windows or Linux. + """ + base = pathlib.Path(__file__).parent / "bin" osname = platform.system() @@ -55,6 +101,22 @@ def init_client( cache_mode: config.CacheMode = config.CacheMode.DISABLED, cache_path: str | None = None, ) -> Any: + """Initializes the Go backend with authentication and cache configuration. + + This method sends the 'init' command to the Go process, which sets up the + MSAL token provider and the Graph Service Client. + + Args: + cache_mode (config.CacheMode): The caching strategy. + cache_path (str | None): Path to the cache file (if applicable). + + Returns: + Any: The initialization result from the Go process. + + Raises: + RuntimeError: If the Go process reports an initialization error. + """ + sender_config = config.SenderConfig() auth_config = config.load_auth_config(self.env_path) return self.execute( @@ -78,6 +140,18 @@ def init_client( ) def init_fake_client(self, mock_server_url: str) -> Any: + """Initializes the Go backend in test mode using a mock server. + + This bypasses real MSAL authentication and directs Graph API calls to + the provided local URL. [For testing purposes only] + + Args: + mock_server_url (str): The URL of the mock HTTP server. + + Returns: + Any: The result of the initialization. + """ + return self.execute( cmd_type="init", params={ @@ -92,6 +166,27 @@ def execute( config: dict[str, Any] | None = None, params: dict[str, Any] | None = None, ) -> Any: + """Executes a command on the Go subprocess via JSON-IPC. + + This is the low-level bridge method used by services to communicate with the backend. + It handles serialization, thread-safe writing to stdin, reading from stdout, + and error propagation. + + Args: + cmd_type (str): The type of command (e.g., "init", "request"). + method (str | None): The specific API method to call (e.g., "listChannels"). + Required if cmd_type is "request". + config (dict | None): Configuration payload (used primarily for initialization). + params (dict | None): Parameters for the method call (e.g., teamRef, body). + + Returns: + Any: The 'result' field from the Go response. + + Raises: + RuntimeError: If the Go process crashes, closes the pipe, returns an empty response, + or explicitly reports an error in the "error" field. + """ + payload = {"type": cmd_type} if method: payload["method"] = method @@ -102,17 +197,19 @@ def execute( json_payload = json.dumps(payload) - # Critical section to avoid interleaving requests/responses + # Critical section to avoid interleaving requests/responses# + # (e.g., thread A writes request A, thread B writes request B, + # then thread A reads response B). with self._lock: try: self.proc.stdin.write(json_payload + "\n") self.proc.stdin.flush() - print("Sent to Go process:", json_payload) # Debug print + # print("Sent to Go process:", json_payload) # Debug print raw_response = self.proc.stdout.readline() - print("Received from Go process:", raw_response.strip()) # Debug print + # print("Received from Go process:", raw_response.strip()) # Debug print except BrokenPipeError: raise RuntimeError("Go process crashed or closed connection") # noqa: B904 @@ -127,4 +224,9 @@ def execute( return res.get("result") def close(self): + """ + Terminates the underlying Go subprocess. + + Should be called when the client is no longer needed to free system resources. + """ self.proc.terminate() diff --git a/python/teams_lib_pzsp2_z1/services/base_service.py b/python/teams_lib_pzsp2_z1/services/base_service.py index 073288d..b5627b5 100644 --- a/python/teams_lib_pzsp2_z1/services/base_service.py +++ b/python/teams_lib_pzsp2_z1/services/base_service.py @@ -6,5 +6,7 @@ from teams_lib_pzsp2_z1.client import TeamsClient class BaseService: + """Base class for all service classes in the Teams library.""" + def __init__(self, client: TeamsClient): self.client = client From 7ab77a883e63f3c14f8823c9df7c54ba26e2c554 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Thu, 8 Jan 2026 11:12:07 +0100 Subject: [PATCH 72/99] feat: improved lib closing to ensure cache operations are completed --- go/bridge/main.go | 7 ++++++ python/teams_lib_pzsp2_z1/client.py | 37 +++++++++++++++++++++++++---- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/go/bridge/main.go b/go/bridge/main.go index d14276c..d598b9e 100644 --- a/go/bridge/main.go +++ b/go/bridge/main.go @@ -8,6 +8,7 @@ import ( jsonClientLib "github.com/pzsp-teams/lib-python/internal/json-client" jsonModel "github.com/pzsp-teams/lib-python/internal/json-model" + "github.com/pzsp-teams/lib" ) var client *jsonClientLib.TeamsJSONClient @@ -64,5 +65,11 @@ func main() { respondResult(writer, result) continue } + + if req.Type == "close" { + lib.Close() + respondResult(writer, "closed") + return + } } } diff --git a/python/teams_lib_pzsp2_z1/client.py b/python/teams_lib_pzsp2_z1/client.py index b7f39c7..68210f2 100644 --- a/python/teams_lib_pzsp2_z1/client.py +++ b/python/teams_lib_pzsp2_z1/client.py @@ -31,6 +31,7 @@ class TeamsClient: chats (ChatsService): Service for managing chats and messages. """ + def __init__( self, auto_init: bool = True, @@ -73,6 +74,8 @@ def __init__( self.teams = TeamsService(self) self.chats = ChatsService(self) + self.is_cache_enabled = cache_mode != config.CacheMode.DISABLED + if auto_init: self.init_client(cache_mode, cache_path) @@ -224,9 +227,35 @@ def execute( return res.get("result") def close(self): - """ - Terminates the underlying Go subprocess. + """Gracefully closes the Go backend, ensuring cache is synced. - Should be called when the client is no longer needed to free system resources. + Sends a 'close' command to the Go process, which triggers `lib.Close()` + to wait for background cache operations. Then terminates the process. """ - self.proc.terminate() + + if self.is_cache_enabled: + with self._lock: + if self.proc.poll() is None: + try: + payload = json.dumps({"type": "close"}) + "\n" + self.proc.stdin.write(payload) + self.proc.stdin.flush() + except (BrokenPipeError, OSError): + pass + + try: + self.proc.wait(timeout=3) + except subprocess.TimeoutExpired: + self.proc.kill() + self.proc.wait() + + try: + if self.proc.stdin: + self.proc.stdin.close() + if self.proc.stdout: + self.proc.stdout.close() + except (OSError, ValueError): + pass + else: + self.proc.terminate() + self.proc.wait() From 3aec827daa378995d0e54548a421f9582e2a4375 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Thu, 8 Jan 2026 11:15:34 +0100 Subject: [PATCH 73/99] docs: docstrings in for client configs --- python/teams_lib_pzsp2_z1/config.py | 87 +++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/python/teams_lib_pzsp2_z1/config.py b/python/teams_lib_pzsp2_z1/config.py index f785c0a..fa6f40f 100644 --- a/python/teams_lib_pzsp2_z1/config.py +++ b/python/teams_lib_pzsp2_z1/config.py @@ -1,3 +1,11 @@ +""" +Package config holds configuration structures used across the application. + +This module defines data classes for authentication, request sending, and caching, +as well as utility functions to load and validate these configurations from +environment variables. +""" + import os from dataclasses import dataclass from enum import Enum @@ -6,11 +14,23 @@ class AuthConfigurationError(Exception): + """Raised when the authentication configuration is missing or invalid.""" pass @dataclass class SenderConfig: + """Defines configuration for the HTTP request sender connecting to Microsoft Graph API. + + Attributes: + max_retries (int): The maximum number of retry attempts for failed requests. + Defaults to 3. + next_retry_delay (int): The delay between retry attempts in **seconds**. + Defaults to 2. + timeout (int): The maximum time to wait for a request to complete in **seconds**. + Defaults to 10. + """ + max_retries: int = 3 next_retry_delay: int = 2 timeout: int = 10 @@ -18,6 +38,21 @@ class SenderConfig: @dataclass class AuthConfig: + """Holds configuration required for authentication via MSAL. + + All fields are required to successfully acquire tokens. + + Attributes: + client_id (str): The Application (client) ID assigned by the Azure portal. + tenant (str): The Directory (tenant) ID or domain. + email (str): The email address of the user to authenticate. + scopes (list[str]): A list of Graph API permission scopes (e.g., "User.Read"). + auth_method (str): The flow used to acquire tokens. + Valid values are: + * **"DEVICE_CODE"**: Prints a code to console; user visits a URL. + * **"INTERACTIVE"**: Opens a local browser window. + """ + client_id: str tenant: str email: str @@ -26,16 +61,54 @@ class AuthConfig: class CacheMode(Enum): + """Defines the caching strategy used by the application. + + Attributes: + DISABLED: Caching is turned off completely. + SYNC: Cache operations (read/write) are performed synchronously. + Safe but may block the main thread. + ASYNC: Cache operations are performed asynchronously in the background. + Faster for the main thread but requires `close()` to ensure data consistency. + """ + DISABLED = "DISABLED" SYNC = "SYNC" ASYNC = "ASYNC" @dataclass class CacheConfig: + """Holds configuration for the caching layer. + + Attributes: + mode (CacheMode): The caching strategy to use. + path (str | None): The file path for the cache storage (e.g., JSON file). + Required if mode is not DISABLED. Defaults to None. + """ + mode: CacheMode path: str | None = None def load_auth_config(env_path: str | None = None) -> AuthConfig: + """Loads authentication configuration from environment variables or a .env file. + + It looks for the following environment variables: + * `CLIENT_ID` + * `TENANT_ID` + * `EMAIL` + * `SCOPES` (comma-separated, defaults to "https://graph.microsoft.com/.default") + * `AUTH_METHOD` (defaults to "DEVICE_CODE") + + Args: + env_path (str | None, optional): Path to the .env file. If None, + loads from the current directory. + + Returns: + AuthConfig: A populated and validated configuration object. + + Raises: + AuthConfigurationError: If required variables are missing or values are invalid. + """ + load_dotenv(env_path) cfg = AuthConfig( @@ -51,10 +124,24 @@ def load_auth_config(env_path: str | None = None) -> AuthConfig: def get_env(key: str, fallback: str) -> str: + """Retrieves an environment variable with a fallback value.""" + return os.getenv(key, fallback) def validate(cfg: AuthConfig): + """Validates the authentication configuration object. + + Checks for the presence of required fields and the validity of the auth method. + + Args: + cfg (AuthConfig): The configuration object to validate. + + Raises: + AuthConfigurationError: If `client_id`, `tenant`, or `email` are empty, + or if `auth_method` is not supported. + """ + missing = [] if not cfg.client_id: missing.append("CLIENT_ID") From d7bf4c1e04ad1cd0f607af0197fcef7ffb941d3e Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Thu, 8 Jan 2026 11:17:29 +0100 Subject: [PATCH 74/99] chore: added env template --- python/.env.template | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 python/.env.template diff --git a/python/.env.template b/python/.env.template new file mode 100644 index 0000000..e1fca19 --- /dev/null +++ b/python/.env.template @@ -0,0 +1,5 @@ +CLIENT_ID=xxxxxxxxxxxxxxxxxxxxxxxxx +TENANT_ID=xxxxxxxxxxxxxxxxxxxxxxxxx +EMAIL=xxxxxxxxxxxxxxxxxxxxxxxxx +SCOPES=openid,profile,User.Read.All,Team.ReadBasic.All,Channel.ReadBasic.All,ChannelSettings.ReadWrite.All,Channel.Create,Channel.Delete.All,ChannelMessage.Send,ChannelMember.ReadWrite.All,Group.ReadWrite.All,TeamMember.ReadWrite.All +AUTH_METHOD=INTERACTIVE \ No newline at end of file From d41b0072a034ad80f7e0e0b9353417689565019a Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Thu, 8 Jan 2026 11:53:41 +0100 Subject: [PATCH 75/99] docs: Generated docs using mkdocstrings lib --- python/docs/index.md | 104 ++++++++++++++++++ python/docs/reference/client.md | 8 ++ python/docs/reference/config.md | 5 + python/docs/reference/models/channel.md | 3 + python/docs/reference/models/chat.md | 3 + python/docs/reference/models/member.md | 3 + python/docs/reference/models/message.md | 5 + python/docs/reference/models/team.md | 3 + python/docs/reference/services/channels.md | 5 + python/docs/reference/services/chats.md | 5 + python/docs/reference/services/teams.md | 5 + python/mkdocs.yml | 52 +++++++++ python/teams_lib_pzsp2_z1/model/__init__.py | 0 .../teams_lib_pzsp2_z1/services/__init__.py | 0 14 files changed, 201 insertions(+) create mode 100644 python/docs/index.md create mode 100644 python/docs/reference/client.md create mode 100644 python/docs/reference/config.md create mode 100644 python/docs/reference/models/channel.md create mode 100644 python/docs/reference/models/chat.md create mode 100644 python/docs/reference/models/member.md create mode 100644 python/docs/reference/models/message.md create mode 100644 python/docs/reference/models/team.md create mode 100644 python/docs/reference/services/channels.md create mode 100644 python/docs/reference/services/chats.md create mode 100644 python/docs/reference/services/teams.md create mode 100644 python/mkdocs.yml create mode 100644 python/teams_lib_pzsp2_z1/model/__init__.py create mode 100644 python/teams_lib_pzsp2_z1/services/__init__.py diff --git a/python/docs/index.md b/python/docs/index.md new file mode 100644 index 0000000..ce210b3 --- /dev/null +++ b/python/docs/index.md @@ -0,0 +1,104 @@ +# Teams Lib Wrapper + +Welcome to the documentation for the **Teams API library**. +This library provides a high-level, Pythonic interface for managing Microsoft Teams, leveraging a compiled Go backend for performance, authentication, and caching. + +--- + +## Overview + +This project bridges the gap between Python's ease of use and Go's concurrency model. +It acts as a wrapper around a Go binary (`teamsClientLib`), communicating via a JSON-based IPC (Inter-Process Communication) protocol. + +--- + +## Key Features + +- **Pythonic Data Models** + Uses standard Python `dataclasses` and `Enum`s instead of raw JSON or complex Microsoft Graph SDK objects. + +- **Automatic Caching** + Reduces API calls by caching Team and Channel references (handled transparently by the Go backend). + +- **Simple Authentication** + Thin wrappers around MSAL (Microsoft Authentication Library) configuration. + +- **Type Hinting** + Full support for static type checkers like `mypy` and IDE autocompletion. + +--- + +## Installation + +Library is available fully via pip + +```bash +pip install pzsp2z1teams +``` + +## Quick start + +### 1. Configuration + +Create a `.env` in your project with your Azure AD credentials: + +```ini +CLIENT_ID=your-client-id-uuid +TENANT_ID=your-tenant-id-uuid +EMAIL=user@example.com +SCOPES=User.Read,Team.ReadBasic.All,Channel.ReadBasic.All +AUTH_METHOD=DEVICE_CODE [or INTERACTIVE] +``` + +Scopes needed by all functions could be found in .env.template. +**//TODO add url to template** + +### 2. Basic usage + +Here is a minimal example showing how to initialize the client and list joined teams: + +```python +from teams_lib_pzsp2_z1.client import TeamsClient +from teams_lib_pzsp2_z1.config import CacheMode + +def main(): + # Initialize the client. + # This spawns the Go subprocess and performs authentication. + client = TeamsClient() + + try: + print("Fetching teams...") + teams = client.teams.list_my_joined() + + for team in teams: + print(f"Team: {team.display_name} | ID: {team.id}") + + # Example: List channels in the first team + channels = client.channels.list_channels(team.id) + print(f" -> Found {len(channels)} channels.") + + except RuntimeError as e: + print(f"An error occurred: {e}") + + finally: + # Crucial: Close the client to ensure background cache writes finish + # and the Go subprocess is terminated gracefully. + client.close() + +if __name__ == "__main__": + main() +``` + +## Architecture + +The library relies on a compiled Go backend to handle performance-critical tasks. When `TeamsClient` is initialized, the following sequence occurs: + +1. **OS Detection**: The library checks the host operating system (Windows/Linux). +2. **Process Launch**: It launches the corresponding Go binary as a background subprocess. +3. **Initialization**: It sends an `init` command via `stdin` to configure the MSAL authentication provider and the caching engine. +4. **Request Handling**: Subsequent method calls (e.g., `client.teams.get(...)`) follow this flow: + - Arguments are **serialized to JSON**. + - The payload is sent to the Go process via **stdin**. + - The Go process executes the request against the **Microsoft Graph API**. + - The JSON response is read from **stdout** and mapped back to native **Python objects**. + diff --git a/python/docs/reference/client.md b/python/docs/reference/client.md new file mode 100644 index 0000000..00d7b9d --- /dev/null +++ b/python/docs/reference/client.md @@ -0,0 +1,8 @@ +# Client Reference + +Main lib entry point. + +::: teams_lib_pzsp2_z1.client.TeamsClient + options: + show_root_heading: true + show_source: true \ No newline at end of file diff --git a/python/docs/reference/config.md b/python/docs/reference/config.md new file mode 100644 index 0000000..b6ccc47 --- /dev/null +++ b/python/docs/reference/config.md @@ -0,0 +1,5 @@ +# Configuration Reference + +Config structures. + +::: teams_lib_pzsp2_z1.config \ No newline at end of file diff --git a/python/docs/reference/models/channel.md b/python/docs/reference/models/channel.md new file mode 100644 index 0000000..483fc6e --- /dev/null +++ b/python/docs/reference/models/channel.md @@ -0,0 +1,3 @@ +# Channel Models + +::: teams_lib_pzsp2_z1.model.channel \ No newline at end of file diff --git a/python/docs/reference/models/chat.md b/python/docs/reference/models/chat.md new file mode 100644 index 0000000..b1d7ccc --- /dev/null +++ b/python/docs/reference/models/chat.md @@ -0,0 +1,3 @@ +# Message Models + +::: teams_lib_pzsp2_z1.model.chat \ No newline at end of file diff --git a/python/docs/reference/models/member.md b/python/docs/reference/models/member.md new file mode 100644 index 0000000..02b83a1 --- /dev/null +++ b/python/docs/reference/models/member.md @@ -0,0 +1,3 @@ +# Message Models + +::: teams_lib_pzsp2_z1.model.member \ No newline at end of file diff --git a/python/docs/reference/models/message.md b/python/docs/reference/models/message.md new file mode 100644 index 0000000..90ec708 --- /dev/null +++ b/python/docs/reference/models/message.md @@ -0,0 +1,5 @@ +# Message Models + +::: teams_lib_pzsp2_z1.model.message + +::: teams_lib_pzsp2_z1.model.mention \ No newline at end of file diff --git a/python/docs/reference/models/team.md b/python/docs/reference/models/team.md new file mode 100644 index 0000000..ef7a4ea --- /dev/null +++ b/python/docs/reference/models/team.md @@ -0,0 +1,3 @@ +# Team Models + +::: teams_lib_pzsp2_z1.model.team \ No newline at end of file diff --git a/python/docs/reference/services/channels.md b/python/docs/reference/services/channels.md new file mode 100644 index 0000000..a0748e0 --- /dev/null +++ b/python/docs/reference/services/channels.md @@ -0,0 +1,5 @@ +# Channels Service + +Channels in team management. + +::: teams_lib_pzsp2_z1.services.channels.ChannelsService \ No newline at end of file diff --git a/python/docs/reference/services/chats.md b/python/docs/reference/services/chats.md new file mode 100644 index 0000000..de4182a --- /dev/null +++ b/python/docs/reference/services/chats.md @@ -0,0 +1,5 @@ +# Chats Service + +Handling one-to-one and group chats. + +::: teams_lib_pzsp2_z1.services.chats.ChatsService \ No newline at end of file diff --git a/python/docs/reference/services/teams.md b/python/docs/reference/services/teams.md new file mode 100644 index 0000000..d62308d --- /dev/null +++ b/python/docs/reference/services/teams.md @@ -0,0 +1,5 @@ +# Teams Service + +Team lifecycle management. + +::: teams_lib_pzsp2_z1.services.teams.TeamsService \ No newline at end of file diff --git a/python/mkdocs.yml b/python/mkdocs.yml new file mode 100644 index 0000000..2e1207c --- /dev/null +++ b/python/mkdocs.yml @@ -0,0 +1,52 @@ +site_name: Teams Lib Wrapper Documentation +site_url: https://pzsp2z1teams.com/ +repo_url: https://github.com/pzsp-teams/lib-python +edit_uri: edit/main/docs/ + +theme: + name: material + features: + - content.code.copy + - navigation.expand + palette: + - scheme: default + primary: indigo + accent: indigo + toggle: + icon: material/brightness-7 + name: Switch to dark mode + - scheme: slate + primary: indigo + accent: indigo + toggle: + icon: material/brightness-4 + name: Switch to light mode + +plugins: + - search + - mkdocstrings: + default_handler: python + handlers: + python: + paths: [.] + options: + docstring_style: google + show_source: true + show_root_heading: true + show_category_heading: true + +nav: + - Home: index.md + - API Reference: + - Client: reference/client.md + - Config: reference/config.md + - Services: + - Teams: reference/services/teams.md + - Channels: reference/services/channels.md + - Chats: reference/services/chats.md + - Models: + - Team: reference/models/team.md + - Channel: reference/models/channel.md + - Message: reference/models/message.md + - Member: reference/models/member.md + - Chat: reference/models/chat.md \ No newline at end of file diff --git a/python/teams_lib_pzsp2_z1/model/__init__.py b/python/teams_lib_pzsp2_z1/model/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/python/teams_lib_pzsp2_z1/services/__init__.py b/python/teams_lib_pzsp2_z1/services/__init__.py new file mode 100644 index 0000000..e69de29 From 30782b773d121a097e6dbe66c18c51f7e384b2ae Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Thu, 8 Jan 2026 11:59:03 +0100 Subject: [PATCH 76/99] docs: added example flow charts --- python/docs/index.md | 25 +++++++++++++++++++++++++ python/mkdocs.yml | 9 +++++++++ 2 files changed, 34 insertions(+) diff --git a/python/docs/index.md b/python/docs/index.md index ce210b3..a34f1bc 100644 --- a/python/docs/index.md +++ b/python/docs/index.md @@ -102,3 +102,28 @@ The library relies on a compiled Go backend to handle performance-critical tasks - The Go process executes the request against the **Microsoft Graph API**. - The JSON response is read from **stdout** and mapped back to native **Python objects**. +### Process Flow Diagram + +```mermaid +sequenceDiagram + participant Py as Python Client + participant Go as Go Subprocess + participant MS as Microsoft Graph + + rect rgba(230, 240, 255, 0.7) + note right of Py: Phase 1: Initialization + Py->>Py: Detect OS & Spawn Process + Py->>Go: Send {"type": "init", ...} + Go->>Go: Setup MSAL & Cache + Go-->>Py: {"result": "initialized"} + end + + rect rgba(240, 255, 240, 0.7) + note right of Py: Phase 2: Normal Operation Loop + Py->>Go: Send {"type": "request", "method": "getTeam", ...} + Go->>MS: GET /v1.0/teams/{id} + MS-->>Go: HTTP 200 OK (JSON) + Go-->>Py: {"result": {...}} + Py->>Py: Map JSON to Python DataClass + end +``` diff --git a/python/mkdocs.yml b/python/mkdocs.yml index 2e1207c..b9a3658 100644 --- a/python/mkdocs.yml +++ b/python/mkdocs.yml @@ -35,6 +35,15 @@ plugins: show_root_heading: true show_category_heading: true +# --- TO JEST NOWA SEKCJA --- +markdown_extensions: + - pymdownx.superfences: + custom_fences: + - name: mermaid + class: mermaid + format: !!python/name:pymdownx.superfences.fence_code_format +# --------------------------- + nav: - Home: index.md - API Reference: From 1983fe161d1d4f20bd0cbc4a5fcc690524b90582 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Thu, 8 Jan 2026 12:23:50 +0100 Subject: [PATCH 77/99] docs: added part of README --- python/.gitignore | 1 + python/README.md | 35 +++++++++++++++++++++++++++++++++++ python/mkdocs.yml | 2 -- 3 files changed, 36 insertions(+), 2 deletions(-) create mode 100644 python/.gitignore diff --git a/python/.gitignore b/python/.gitignore new file mode 100644 index 0000000..45ddf0a --- /dev/null +++ b/python/.gitignore @@ -0,0 +1 @@ +site/ diff --git a/python/README.md b/python/README.md index e69de29..ba8b54b 100644 --- a/python/README.md +++ b/python/README.md @@ -0,0 +1,35 @@ +# Teams API Python Wrapper + +[![Python](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/) +[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) +[![Documentation](https://img.shields.io/badge/docs-online-green)](https://pzsp-teams.github.io/lib-python/) + +
+ +A high-performance **Python wrapper** for the Microsoft Teams Graph API. It leverages a compiled **Go (Golang)** backend to handle heavy lifting, concurrency, and caching, while providing a clean, fully typed Pythonic interface. + +## 🚀 Key Features + +- **Performance of Go**: Uses a compiled Go subprocess for efficient API communication and state management. +- **Pythonic Interface**: Fully typed using `dataclasses` and `Enums`. Native support for IDE autocompletion and type checkers (`mypy`). +- **Intelligent Cache**: Background caching of Team/Channel IDs to minimize API throttling and latency (handled transparently by the backend). +- **Simplified Auth**: Wraps MSAL authentication flows configuration. + +## 📦 Installation + +```bash +pip install teams-lib-pzsp2-z1 +``` + +## 🛠️ Architecture +This library acts as a bridge. When initialized, it spawns a dedicated Go subprocess (`teamsClientLib`) and communicates via JSON-RPC over `stdin`/`stdout`. + +The `TeamsClient` aggregates domain-specific services: + +- **client.teams**: Manage team lifecycles (create, list, delete). + +- **client.channels**: Manage standard and private channels. + +- **client.chats**: Handle messages, mentions, and chat participants. + +## 💻 Quick Start \ No newline at end of file diff --git a/python/mkdocs.yml b/python/mkdocs.yml index b9a3658..ee115fa 100644 --- a/python/mkdocs.yml +++ b/python/mkdocs.yml @@ -35,14 +35,12 @@ plugins: show_root_heading: true show_category_heading: true -# --- TO JEST NOWA SEKCJA --- markdown_extensions: - pymdownx.superfences: custom_fences: - name: mermaid class: mermaid format: !!python/name:pymdownx.superfences.fence_code_format -# --------------------------- nav: - Home: index.md From 3560098d68448aff1c541173d357ce2db76eba8c Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Thu, 8 Jan 2026 12:30:21 +0100 Subject: [PATCH 78/99] docs: added rest of README file --- python/README.md | 88 +++++++++++++++++++++++++++++++++++++++++++- python/docs/index.md | 2 +- 2 files changed, 88 insertions(+), 2 deletions(-) diff --git a/python/README.md b/python/README.md index ba8b54b..8e66add 100644 --- a/python/README.md +++ b/python/README.md @@ -22,6 +22,7 @@ pip install teams-lib-pzsp2-z1 ``` ## 🛠️ Architecture + This library acts as a bridge. When initialized, it spawns a dedicated Go subprocess (`teamsClientLib`) and communicates via JSON-RPC over `stdin`/`stdout`. The `TeamsClient` aggregates domain-specific services: @@ -32,4 +33,89 @@ The `TeamsClient` aggregates domain-specific services: - **client.chats**: Handle messages, mentions, and chat participants. -## 💻 Quick Start \ No newline at end of file +## 💻 Quick Start + +### 1. Configuration + +Create a `.env` in your project with your Azure AD credentials: + +```ini +CLIENT_ID=your-client-id-uuid +TENANT_ID=your-tenant-id-uuid +EMAIL=user@example.com +SCOPES=User.Read,Team.ReadBasic.All,Channel.ReadBasic.All +AUTH_METHOD=DEVICE_CODE [or INTERACTIVE] +``` + +Scopes needed by all functions could be found in .env.template. +**//TODO add url to template** + +### 2. Basic usage + +Here is a minimal example showing how to initialize the client and list joined teams: + +```python +from teams_lib_pzsp2_z1.client import TeamsClient +from teams_lib_pzsp2_z1.config import CacheMode + +def main(): + # Initialize the client. + # This spawns the Go subprocess and performs authentication. + client = TeamsClient() + + try: + print("Fetching teams...") + teams = client.teams.list_my_joined() + + for team in teams: + print(f"Team: {team.display_name} | ID: {team.id}") + + # Example: List channels in the first team + channels = client.channels.list_channels(team.id) + print(f" -> Found {len(channels)} channels.") + + except RuntimeError as e: + print(f"An error occurred: {e}") + + finally: + # Crucial: Close the client to ensure background cache writes finish + # and the Go subprocess is terminated gracefully. + client.close() + +if __name__ == "__main__": + main() +``` + +## 📚 Documentation + +Full API reference, architecture details, and configuration guides are available here: + +👉 [Read the Documentation](https://pzsp-teams.github.io/lib-python/) + +## Authentication + +The library loads credentials from environment variables. Ensure your Azure App Registration has the necessary **API Permissions** (e.g., `Team.ReadBasic.All`, `Channel.ReadBasic.All`) granted in the Azure Portal. + +Supported Authentication Methods (`AUTH_METHOD`): + +- **INTERACTIVE**: Opens a browser window automatically. + +- **DEVICE_CODE**: Prints a code to the console to login via microsoft.com/devicelogin. + +## Cache & Lifecycle + +If caching is enabled (SYNC or ASYNC), the Go backend stores metadata locally (e.g., `teams_cache.json`) to speed up reference resolution. + +⚠️ Important: client.close() +Because the Go backend might be running background goroutines (especially for Async Cache), you **must** call `client.close()` before your script exits. This ensures: + +1. Pending cache writes are flushed to disk. + +2. The Go subprocess is terminated correctly. + +```python +client.close() +``` + +## 📄 License +This project is licensed under the MIT License - see the [LICENSE](https://github.com/pzsp-teams/lib-python/blob/main/LICENSE) file for details. diff --git a/python/docs/index.md b/python/docs/index.md index a34f1bc..c92817b 100644 --- a/python/docs/index.md +++ b/python/docs/index.md @@ -33,7 +33,7 @@ It acts as a wrapper around a Go binary (`teamsClientLib`), communicating via a Library is available fully via pip ```bash -pip install pzsp2z1teams +pip install teams_lib_pzsp2_z1 ``` ## Quick start From 2ec801f03fe37bb0630699a968d55f6adebcfa58 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Thu, 8 Jan 2026 12:34:42 +0100 Subject: [PATCH 79/99] chore: removed docs dir from python release branch --- .github/workflows/integration-tests.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index ca0aa05..7289339 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -2,10 +2,10 @@ name: Python Integration Tests on: pull_request: - branches: [ main ] + branches: [main] types: [review_requested, ready_for_review] push: - branches: [ main ] + branches: [main] repository_dispatch: types: [verify-new-go-lib-version] @@ -237,6 +237,8 @@ jobs: # clean up unneeded files rm -rf python_release/tests + rm -rf python_release/docs + rm mkdocs.yml find python_release -type d -name "__pycache__" -exec rm -rf {} + rm -rf python_release/.venv From 5a83154fd63af3ecac80b1a18b28ce3f8d8d92cf Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Thu, 8 Jan 2026 12:35:04 +0100 Subject: [PATCH 80/99] fix: typo --- .github/workflows/integration-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 7289339..96af91a 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -238,7 +238,7 @@ jobs: # clean up unneeded files rm -rf python_release/tests rm -rf python_release/docs - rm mkdocs.yml + rm python_release/mkdocs.yml find python_release -type d -name "__pycache__" -exec rm -rf {} + rm -rf python_release/.venv From f179f0cee7fe811e18318ab863d3194af5e177f4 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Thu, 8 Jan 2026 13:45:47 +0100 Subject: [PATCH 81/99] docs: updated main project README --- README.md | 122 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 121 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7727354..8e66add 100644 --- a/README.md +++ b/README.md @@ -1 +1,121 @@ -# lib-python +# Teams API Python Wrapper + +[![Python](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/) +[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) +[![Documentation](https://img.shields.io/badge/docs-online-green)](https://pzsp-teams.github.io/lib-python/) + +
+ +A high-performance **Python wrapper** for the Microsoft Teams Graph API. It leverages a compiled **Go (Golang)** backend to handle heavy lifting, concurrency, and caching, while providing a clean, fully typed Pythonic interface. + +## 🚀 Key Features + +- **Performance of Go**: Uses a compiled Go subprocess for efficient API communication and state management. +- **Pythonic Interface**: Fully typed using `dataclasses` and `Enums`. Native support for IDE autocompletion and type checkers (`mypy`). +- **Intelligent Cache**: Background caching of Team/Channel IDs to minimize API throttling and latency (handled transparently by the backend). +- **Simplified Auth**: Wraps MSAL authentication flows configuration. + +## 📦 Installation + +```bash +pip install teams-lib-pzsp2-z1 +``` + +## 🛠️ Architecture + +This library acts as a bridge. When initialized, it spawns a dedicated Go subprocess (`teamsClientLib`) and communicates via JSON-RPC over `stdin`/`stdout`. + +The `TeamsClient` aggregates domain-specific services: + +- **client.teams**: Manage team lifecycles (create, list, delete). + +- **client.channels**: Manage standard and private channels. + +- **client.chats**: Handle messages, mentions, and chat participants. + +## 💻 Quick Start + +### 1. Configuration + +Create a `.env` in your project with your Azure AD credentials: + +```ini +CLIENT_ID=your-client-id-uuid +TENANT_ID=your-tenant-id-uuid +EMAIL=user@example.com +SCOPES=User.Read,Team.ReadBasic.All,Channel.ReadBasic.All +AUTH_METHOD=DEVICE_CODE [or INTERACTIVE] +``` + +Scopes needed by all functions could be found in .env.template. +**//TODO add url to template** + +### 2. Basic usage + +Here is a minimal example showing how to initialize the client and list joined teams: + +```python +from teams_lib_pzsp2_z1.client import TeamsClient +from teams_lib_pzsp2_z1.config import CacheMode + +def main(): + # Initialize the client. + # This spawns the Go subprocess and performs authentication. + client = TeamsClient() + + try: + print("Fetching teams...") + teams = client.teams.list_my_joined() + + for team in teams: + print(f"Team: {team.display_name} | ID: {team.id}") + + # Example: List channels in the first team + channels = client.channels.list_channels(team.id) + print(f" -> Found {len(channels)} channels.") + + except RuntimeError as e: + print(f"An error occurred: {e}") + + finally: + # Crucial: Close the client to ensure background cache writes finish + # and the Go subprocess is terminated gracefully. + client.close() + +if __name__ == "__main__": + main() +``` + +## 📚 Documentation + +Full API reference, architecture details, and configuration guides are available here: + +👉 [Read the Documentation](https://pzsp-teams.github.io/lib-python/) + +## Authentication + +The library loads credentials from environment variables. Ensure your Azure App Registration has the necessary **API Permissions** (e.g., `Team.ReadBasic.All`, `Channel.ReadBasic.All`) granted in the Azure Portal. + +Supported Authentication Methods (`AUTH_METHOD`): + +- **INTERACTIVE**: Opens a browser window automatically. + +- **DEVICE_CODE**: Prints a code to the console to login via microsoft.com/devicelogin. + +## Cache & Lifecycle + +If caching is enabled (SYNC or ASYNC), the Go backend stores metadata locally (e.g., `teams_cache.json`) to speed up reference resolution. + +⚠️ Important: client.close() +Because the Go backend might be running background goroutines (especially for Async Cache), you **must** call `client.close()` before your script exits. This ensures: + +1. Pending cache writes are flushed to disk. + +2. The Go subprocess is terminated correctly. + +```python +client.close() +``` + +## 📄 License +This project is licensed under the MIT License - see the [LICENSE](https://github.com/pzsp-teams/lib-python/blob/main/LICENSE) file for details. From e4f8523f0d48cde06b4e349eab6e345e2be101ff Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Thu, 8 Jan 2026 13:54:03 +0100 Subject: [PATCH 82/99] docs: added go reference --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 8e66add..cbc32a9 100644 --- a/README.md +++ b/README.md @@ -119,3 +119,6 @@ client.close() ## 📄 License This project is licensed under the MIT License - see the [LICENSE](https://github.com/pzsp-teams/lib-python/blob/main/LICENSE) file for details. + +## Porst +This lib is also available in [GO](https://github.com/pzsp-teams/lib) From 8a9f6d468d959823692850df4e242a661a88cf43 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Sun, 11 Jan 2026 23:15:40 +0100 Subject: [PATCH 83/99] chore: adjusted test for listPinned messages --- go/go.mod | 3 ++- go/go.sum | 4 ++++ python/teams_lib_pzsp2_z1/client.py | 4 ++-- python/tests/fake_server/data.py | 20 +++++++++++++++++++- 4 files changed, 27 insertions(+), 4 deletions(-) diff --git a/go/go.mod b/go/go.mod index 5bbfd43..ff058ba 100644 --- a/go/go.mod +++ b/go/go.mod @@ -6,7 +6,7 @@ require ( github.com/microsoft/kiota-abstractions-go v1.9.3 github.com/microsoftgraph/msgraph-sdk-go v1.93.0 github.com/mitchellh/mapstructure v1.5.0 - github.com/pzsp-teams/lib v0.0.0-20260107211439-3d301706e02c + github.com/pzsp-teams/lib v0.0.0-20260111170324-f66ca4f5d369 ) require ( @@ -39,6 +39,7 @@ require ( go.opentelemetry.io/otel/metric v1.39.0 // indirect go.opentelemetry.io/otel/trace v1.39.0 // indirect golang.org/x/net v0.48.0 // indirect + golang.org/x/sync v0.19.0 // indirect golang.org/x/sys v0.39.0 // indirect golang.org/x/text v0.32.0 // indirect ) diff --git a/go/go.sum b/go/go.sum index 14ee7df..a25a60c 100644 --- a/go/go.sum +++ b/go/go.sum @@ -71,6 +71,8 @@ github.com/pzsp-teams/lib v0.0.0-20260107201101-e9e6f48e39e8 h1:1ifLBNVdw8TCLPzH github.com/pzsp-teams/lib v0.0.0-20260107201101-e9e6f48e39e8/go.mod h1:BQwULf+u8TtbKR0U23MlZXgm2CVcLBJn2cS5gmR7L8M= github.com/pzsp-teams/lib v0.0.0-20260107211439-3d301706e02c h1:ZtUkM66y/jg5g5kEQhteQeACuwKl+cOkRuGPlX1mkGg= github.com/pzsp-teams/lib v0.0.0-20260107211439-3d301706e02c/go.mod h1:BQwULf+u8TtbKR0U23MlZXgm2CVcLBJn2cS5gmR7L8M= +github.com/pzsp-teams/lib v0.0.0-20260111170324-f66ca4f5d369 h1:TNB4hQ6u8aH0cekG1xbITdkAxgrY4FmwSWgzfI1e21Q= +github.com/pzsp-teams/lib v0.0.0-20260111170324-f66ca4f5d369/go.mod h1:bpgptBb8PaJV03buetvHnq6plkQHpF0WnY1K5iU31U8= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/std-uritemplate/std-uritemplate/go/v2 v2.0.8 h1:gMBdYMTHt2mmTdXW8YfvRjRUZ0GhyGV+IqSH9H15bGw= @@ -93,6 +95,8 @@ go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= diff --git a/python/teams_lib_pzsp2_z1/client.py b/python/teams_lib_pzsp2_z1/client.py index 68210f2..b2f6ec1 100644 --- a/python/teams_lib_pzsp2_z1/client.py +++ b/python/teams_lib_pzsp2_z1/client.py @@ -208,11 +208,11 @@ def execute( self.proc.stdin.write(json_payload + "\n") self.proc.stdin.flush() - # print("Sent to Go process:", json_payload) # Debug print + print("Sent to Go process:", json_payload) # Debug print raw_response = self.proc.stdout.readline() - # print("Received from Go process:", raw_response.strip()) # Debug print + print("Received from Go process:", raw_response.strip()) # Debug print except BrokenPipeError: raise RuntimeError("Go process crashed or closed connection") # noqa: B904 diff --git a/python/tests/fake_server/data.py b/python/tests/fake_server/data.py index 50fbeaa..c1f0e36 100644 --- a/python/tests/fake_server/data.py +++ b/python/tests/fake_server/data.py @@ -707,11 +707,29 @@ def get_all_messeges_in_chats_response(self) -> dict: } def get_list_pinned_messages_in_chat_response(self, chat_id: str) -> dict: + print("get_list_pinned_messages_in_chat_response called for chat_id:", chat_id) # Debug print return { "value": [ { - ODATA_TYPE: "#microsoft.graph.pinnedChatMessageInfo", "id": message.id, + "message": { + "id": message.id, + "etag": message.id, + "messageType": "message", + "chatId": chat_id, + "body": { + "content": message.content, + "contentType": message.content_type, + }, + "from": { + "user": { + "id": message.sender.user_id, + "displayName": message.sender.display_name, + } + }, + "createdDateTime": message.created_date_time, + "lastModifiedDateTime": message.created_date_time, + } } for message in self.chat_messages.get(chat_id, []) ], From fdf54e2758308b898a8a4183647d0bbb20d0132d Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Sun, 11 Jan 2026 23:58:17 +0100 Subject: [PATCH 84/99] fix: tried to fix resolving id from header --- python/tests/fake_server/data.py | 36 +++++++++++++++++++++++++------ python/tests/fake_server/setup.py | 6 +----- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/python/tests/fake_server/data.py b/python/tests/fake_server/data.py index c1f0e36..c667983 100644 --- a/python/tests/fake_server/data.py +++ b/python/tests/fake_server/data.py @@ -1,5 +1,9 @@ from dataclasses import dataclass, field from typing import Optional, List, Dict, Any +from unittest import result +from urllib import request + +from werkzeug import Response from teams_lib_pzsp2_z1.model.team import Team from teams_lib_pzsp2_z1.model.chat import Chat, ChatType @@ -254,7 +258,8 @@ def get_createTeamViaGroup_response(self, group_id: str) -> dict: "visibility": team.visibility, } - def get_createTeamFromTemplate_response(self, request_json: dict) -> dict: + def get_createTeamFromTemplate_response(self, request: request.Request) -> Response: + request_json = request.json new_team = Team( id=self.newTeamID, display_name=request_json.get("displayName"), @@ -267,16 +272,34 @@ def get_createTeamFromTemplate_response(self, request_json: dict) -> dict: # self.newTeamID == "f47ac10b-58cc-4372-a567-0e02b2c3d479" op_id = "00000000-0000-0000-0000-000000000000" - return { - "status": 202, - "headers": { + return Response( + status= 202, + headers= { "Location": f"/teams('{self.newTeamID}')/operations('{op_id}')", "Content-Location": f"/teams('{self.newTeamID}')", "Content-Type": "application/json", "Content-Length": "0" }, - "body": "" - } + response= "" + ) + + # def create_team_handler(self, req): + # result = self.get_createTeamFromTemplate_response(req.json) + + # base_url = "https://graph.microsoft.com/v1.0" + # loc_header = f"/teams/{TEAM_ID}/operations/{OP_ID}" + # content_loc_header = f"/teams/{TEAM_ID}" + + # return Response( + # response=result["body"], + # status=result["status"], + # headers={ + # "Location": full_location, + # "Content-Location": full_content_loc, + # "Content-Type": result["headers"]["Content-Type"], + # "Content-Length": result["headers"]["Content-Length"] + # } + # ) def get_archiveTeam_response(self, team_id: str) -> dict: team = self._find_team(team_id) @@ -707,7 +730,6 @@ def get_all_messeges_in_chats_response(self) -> dict: } def get_list_pinned_messages_in_chat_response(self, chat_id: str) -> dict: - print("get_list_pinned_messages_in_chat_response called for chat_id:", chat_id) # Debug print return { "value": [ { diff --git a/python/tests/fake_server/setup.py b/python/tests/fake_server/setup.py index f1f2ff2..d4aa92a 100644 --- a/python/tests/fake_server/setup.py +++ b/python/tests/fake_server/setup.py @@ -353,11 +353,7 @@ def handle_chats_request(req): httpserver.expect_request( "/v1.0/teams", method="POST" - ).respond_with_handler(lambda req: Response( - response=data.get_createTeamFromTemplate_response(req.json)["body"], - status=data.get_createTeamFromTemplate_response(req.json)["status"], - headers=data.get_createTeamFromTemplate_response(req.json)["headers"] - )) + ).respond_with_handler(data.get_createTeamFromTemplate_response) # POST /teams/{team_id}/members httpserver.expect_request( From 35967405e2c19f58ebe708fa4ffd2d7a61baaad8 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Mon, 12 Jan 2026 14:45:14 +0100 Subject: [PATCH 85/99] feat: added searchmessages options structs --- go/go.mod | 2 +- go/go.sum | 2 + .../decoders/decode-searchOptions.go | 79 +++++++++++++++++++ 3 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 go/internal/json-client/decoders/decode-searchOptions.go diff --git a/go/go.mod b/go/go.mod index ff058ba..a736666 100644 --- a/go/go.mod +++ b/go/go.mod @@ -6,7 +6,7 @@ require ( github.com/microsoft/kiota-abstractions-go v1.9.3 github.com/microsoftgraph/msgraph-sdk-go v1.93.0 github.com/mitchellh/mapstructure v1.5.0 - github.com/pzsp-teams/lib v0.0.0-20260111170324-f66ca4f5d369 + github.com/pzsp-teams/lib v0.0.0-20260112132712-1335fb93c11f ) require ( diff --git a/go/go.sum b/go/go.sum index a25a60c..5a91b58 100644 --- a/go/go.sum +++ b/go/go.sum @@ -73,6 +73,8 @@ github.com/pzsp-teams/lib v0.0.0-20260107211439-3d301706e02c h1:ZtUkM66y/jg5g5kE github.com/pzsp-teams/lib v0.0.0-20260107211439-3d301706e02c/go.mod h1:BQwULf+u8TtbKR0U23MlZXgm2CVcLBJn2cS5gmR7L8M= github.com/pzsp-teams/lib v0.0.0-20260111170324-f66ca4f5d369 h1:TNB4hQ6u8aH0cekG1xbITdkAxgrY4FmwSWgzfI1e21Q= github.com/pzsp-teams/lib v0.0.0-20260111170324-f66ca4f5d369/go.mod h1:bpgptBb8PaJV03buetvHnq6plkQHpF0WnY1K5iU31U8= +github.com/pzsp-teams/lib v0.0.0-20260112132712-1335fb93c11f h1:MOnTg+X51/lukcWWjHyw9OPx9IzC6Q6TJsZXiaH/KnU= +github.com/pzsp-teams/lib v0.0.0-20260112132712-1335fb93c11f/go.mod h1:bpgptBb8PaJV03buetvHnq6plkQHpF0WnY1K5iU31U8= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/std-uritemplate/std-uritemplate/go/v2 v2.0.8 h1:gMBdYMTHt2mmTdXW8YfvRjRUZ0GhyGV+IqSH9H15bGw= diff --git a/go/internal/json-client/decoders/decode-searchOptions.go b/go/internal/json-client/decoders/decode-searchOptions.go new file mode 100644 index 0000000..48c0a0e --- /dev/null +++ b/go/internal/json-client/decoders/decode-searchOptions.go @@ -0,0 +1,79 @@ +package decoders + +import ( + "time" + + "github.com/pzsp-teams/lib/search" +) + +type SearchConfigDTO struct { + MaxWorkers int `json:"maxWorkers"` +} + +type SearchPageDTO struct { + From *int `json:"from"` + Size *int `json:"size"` +} + +type SearchMessageOptionsDTO struct { + Query string `json:"query"` + SearchPage SearchPageDTO `json:"searchPage"` + From []string `json:"from"` + NotFrom []string `json:"notFrom"` + IsRead bool `json:"isRead"` + IsMentioned bool `json:"isMentioned"` + To []string `json:"to"` + NotTo []string `json:"notTo"` + StartTime time.Time `json:"startTime"` + EndTime time.Time `json:"endTime"` + Interval string `json:"interval"` + NotFromMe bool `json:"notFromMe"` + NotToMe bool `json:"notToMe"` + FromMe bool `json:"fromMe"` + ToMe bool `json:"toMe"` +} + +func DecodeSearchMessageOptions(dto *SearchMessageOptionsDTO) *search.SearchMessagesOptions { + if dto == nil { + return nil + } + + interval := search.TimeInterval(dto.Interval) + + options := &search.SearchMessagesOptions{ + Query: &dto.Query, + SearchPage: DecodeSearchPage(&dto.SearchPage), + From: dto.From, + NotFrom: dto.NotFrom, + IsRead: &dto.IsRead, + IsMentioned: &dto.IsMentioned, + To: dto.To, + NotTo: dto.NotTo, + StartTime: &dto.StartTime, + EndTime: &dto.EndTime, + Interval: &interval, + NotFromMe: dto.NotFromMe, + NotToMe: dto.NotToMe, + FromMe: dto.FromMe, + ToMe: dto.ToMe, + } + return options +} + +func DecodeSearchPage(dto *SearchPageDTO) *search.SearchPage { + if dto == nil { + return nil + } + + page := &search.SearchPage{} + if dto.From != nil { + from := int32(*dto.From) + page.From = &from + } + if dto.Size != nil { + size := int32(*dto.Size) + page.Size = &size + } + + return page +} From 354c67c477ab203f289167b92d7332f09793187d Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Mon, 12 Jan 2026 14:54:03 +0100 Subject: [PATCH 86/99] feat: added new functions to go bridge --- .../json-client/decoders/decode-chatRef.go | 10 +-- go/internal/json-client/request-map.go | 12 +++ go/internal/json-client/requests-channels.go | 19 +++++ go/internal/json-client/requests-chats.go | 76 ++++++++++--------- 4 files changed, 74 insertions(+), 43 deletions(-) diff --git a/go/internal/json-client/decoders/decode-chatRef.go b/go/internal/json-client/decoders/decode-chatRef.go index c9c9c0c..1bc85e7 100644 --- a/go/internal/json-client/decoders/decode-chatRef.go +++ b/go/internal/json-client/decoders/decode-chatRef.go @@ -1,8 +1,6 @@ package decoders import ( - "fmt" - "github.com/pzsp-teams/lib/chats" ) @@ -16,14 +14,14 @@ const ( ChatTypeOneOnOne = "oneOnOne" ) -func GetChatRef(dto ChatRefDTO) (chats.ChatRef, error) { +func GetChatRef(dto ChatRefDTO) chats.ChatRef { switch dto.Type { case ChatTypeGroup: - return chats.GroupChatRef{Ref: dto.Ref}, nil + return chats.GroupChatRef{Ref: dto.Ref} case ChatTypeOneOnOne: - return chats.OneOnOneChatRef{Ref: dto.Ref}, nil + return chats.OneOnOneChatRef{Ref: dto.Ref} default: - return nil, fmt.Errorf("unknown chat type: %s", dto.Type) + return nil } } diff --git a/go/internal/json-client/request-map.go b/go/internal/json-client/request-map.go index 68bd08f..9f31f27 100644 --- a/go/internal/json-client/request-map.go +++ b/go/internal/json-client/request-map.go @@ -106,6 +106,10 @@ var Handlers = map[string]HandlerFunc{ return c.GetMentionsInChannel(p) }, + "searchMessagesInChannel": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + return c.SearchMessagesInChannel(p) + }, + // CHATS "createOneOnOneChat": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { return c.CreateOneToOneChat(p) @@ -115,6 +119,10 @@ var Handlers = map[string]HandlerFunc{ return c.CreateGroupChat(p) }, + "getChat": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + return c.GetChat(p) + }, + "addMemberToGroupChat": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { return c.AddMemberToGroupChat(p) }, @@ -170,4 +178,8 @@ var Handlers = map[string]HandlerFunc{ "getMentionsInChat": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { return c.GetMentionsInChat(p) }, + + "searchMessagesInChat": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + return c.SearchMessagesInChat(p) + }, } diff --git a/go/internal/json-client/requests-channels.go b/go/internal/json-client/requests-channels.go index 81b4f2c..0f0803a 100644 --- a/go/internal/json-client/requests-channels.go +++ b/go/internal/json-client/requests-channels.go @@ -5,6 +5,7 @@ import ( "github.com/pzsp-teams/lib-python/internal/json-client/decoders" "github.com/pzsp-teams/lib/models" + "github.com/pzsp-teams/lib/search" ) type listChannelsParams struct { @@ -206,3 +207,21 @@ func (c *TeamsJSONClient) GetMentionsInChannel(p map[string]interface{}) (interf return c.client.Channels.GetMentions(context.TODO(), params.TeamRef, params.ChannelRef, params.RawMentions) }) } + +type searchMessagesInChannelParams struct { + TeamRef string `json:"teamRef"` + ChannelRef string `json:"channelRef"` + SearchOptions decoders.SearchMessageOptionsDTO `json:"searchMessagesOptions"` + SearchConfig decoders.SearchConfigDTO `json:"searchConfig"` +} + +func (c *TeamsJSONClient) SearchMessagesInChannel(p map[string]interface{}) (interface{}, error) { + return execute(p, func(params searchMessagesInChannelParams) (interface{}, error) { + searchOptions := decoders.DecodeSearchMessageOptions(¶ms.SearchOptions) + searchConfig, err := decoders.DecodeParams[search.SearchConfig](¶ms.SearchConfig) + if err != nil { + return nil, err + } + return c.client.Channels.SearchMessages(context.TODO(), ¶ms.TeamRef, ¶ms.ChannelRef, searchOptions, searchConfig) + }) +} diff --git a/go/internal/json-client/requests-chats.go b/go/internal/json-client/requests-chats.go index 9a9241b..eda2bb6 100644 --- a/go/internal/json-client/requests-chats.go +++ b/go/internal/json-client/requests-chats.go @@ -6,6 +6,7 @@ import ( "github.com/pzsp-teams/lib-python/internal/json-client/decoders" "github.com/pzsp-teams/lib/chats" "github.com/pzsp-teams/lib/models" + "github.com/pzsp-teams/lib/search" ) type createOneToOneChatParams struct { @@ -30,6 +31,17 @@ func (c *TeamsJSONClient) CreateGroupChat(p map[string]interface{}) (interface{} }) } +type baseChatParams struct { + ChatRef decoders.ChatRefDTO `json:"chatRef"` +} + +func (c *TeamsJSONClient) GetChat(p map[string]interface{}) (interface{}, error) { + return execute(p, func(params baseChatParams) (interface{}, error) { + chatRef := decoders.GetChatRef(params.ChatRef) + return c.client.Chats.GetChat(context.TODO(), chatRef) + }) +} + type groupChatMemberParams struct { GroupChatRef string `json:"groupChatRef"` UserRef string `json:"userRef"` @@ -77,10 +89,7 @@ type listMessagesInChatParams struct { func (c *TeamsJSONClient) ListMessagesInChat(p map[string]interface{}) (interface{}, error) { return execute(p, func(params listMessagesInChatParams) (interface{}, error) { - chatRef, err := decoders.GetChatRef(params.ChatRef) - if err != nil { - return nil, err - } + chatRef := decoders.GetChatRef(params.ChatRef) nextLink := decoders.GetNextLink(¶ms.NextLink) return c.client.Chats.ListMessages(context.TODO(), chatRef, params.IncludeSystem, nextLink) }) @@ -93,10 +102,7 @@ type sendMessageInChatParams struct { func (c *TeamsJSONClient) SendMessageInChat(p map[string]interface{}) (interface{}, error) { return execute(p, func(params sendMessageInChatParams) (interface{}, error) { - chatRef, err := decoders.GetChatRef(params.ChatRef) - if err != nil { - return nil, err - } + chatRef := decoders.GetChatRef(params.ChatRef) body, err := decoders.DecodeParams[models.MessageBody](params.Body) if err != nil { return nil, err @@ -112,21 +118,15 @@ type messageInChatParams struct { func (c *TeamsJSONClient) DeleteMessageInChat(p map[string]interface{}) (interface{}, error) { return execute(p, func(params messageInChatParams) (interface{}, error) { - chatRef, err := decoders.GetChatRef(params.ChatRef) - if err != nil { - return nil, err - } - err = c.client.Chats.DeleteMessage(context.TODO(), chatRef, params.MessageID) + chatRef := decoders.GetChatRef(params.ChatRef) + err := c.client.Chats.DeleteMessage(context.TODO(), chatRef, params.MessageID) return "deleted", err }) } func (c *TeamsJSONClient) GetMessageInChat(p map[string]interface{}) (interface{}, error) { return execute(p, func(params messageInChatParams) (interface{}, error) { - chatRef, err := decoders.GetChatRef(params.ChatRef) - if err != nil { - return nil, err - } + chatRef := decoders.GetChatRef(params.ChatRef) return c.client.Chats.GetMessage(context.TODO(), chatRef, params.MessageID) }) } @@ -161,38 +161,25 @@ func (c *TeamsJSONClient) ListMyChatMessages(p map[string]interface{}) (interfac }) } -type baseChatParams struct { - ChatRef decoders.ChatRefDTO `json:"chatRef"` -} - func (c *TeamsJSONClient) ListPinnedMessagesInChat(p map[string]interface{}) (interface{}, error) { return execute(p, func(params baseChatParams) (interface{}, error) { - chatRef, err := decoders.GetChatRef(params.ChatRef) - if err != nil { - return nil, err - } + chatRef := decoders.GetChatRef(params.ChatRef) return c.client.Chats.ListPinnedMessages(context.TODO(), chatRef) }) } func (c *TeamsJSONClient) PinMessageInChat(p map[string]interface{}) (interface{}, error) { return execute(p, func(params messageInChatParams) (interface{}, error) { - chatRef, err := decoders.GetChatRef(params.ChatRef) - if err != nil { - return nil, err - } - err = c.client.Chats.PinMessage(context.TODO(), chatRef, params.MessageID) + chatRef := decoders.GetChatRef(params.ChatRef) + err := c.client.Chats.PinMessage(context.TODO(), chatRef, params.MessageID) return "pinned", err }) } func (c *TeamsJSONClient) UnpinMessageInChat(p map[string]interface{}) (interface{}, error) { return execute(p, func(params messageInChatParams) (interface{}, error) { - chatRef, err := decoders.GetChatRef(params.ChatRef) - if err != nil { - return nil, err - } - err = c.client.Chats.UnpinMessage(context.TODO(), chatRef, params.MessageID) + chatRef := decoders.GetChatRef(params.ChatRef) + err := c.client.Chats.UnpinMessage(context.TODO(), chatRef, params.MessageID) return "unpinned", err }) } @@ -204,10 +191,25 @@ type mentionInChatParams struct { func (c *TeamsJSONClient) GetMentionsInChat(p map[string]interface{}) (interface{}, error) { return execute(p, func(params mentionInChatParams) (interface{}, error) { - chatRef, err := decoders.GetChatRef(params.ChatRef) + chatRef := decoders.GetChatRef(params.ChatRef) + return c.client.Chats.GetMentions(context.TODO(), chatRef, params.RawMentions) + }) +} + +type searchMessagesInChatParams struct { + ChatRef decoders.ChatRefDTO `json:"chatRef"` + SearchOptions decoders.SearchMessageOptionsDTO `json:"searchMessagesOptions"` + SearchConfig decoders.SearchConfigDTO `json:"searchConfig"` +} + +func (c *TeamsJSONClient) SearchMessagesInChat(p map[string]interface{}) (interface{}, error) { + return execute(p, func(params searchMessagesInChatParams) (interface{}, error) { + chatRef := decoders.GetChatRef(params.ChatRef) + searchOptions := decoders.DecodeSearchMessageOptions(¶ms.SearchOptions) + searchConfig, err := decoders.DecodeParams[search.SearchConfig](¶ms.SearchConfig) if err != nil { return nil, err } - return c.client.Chats.GetMentions(context.TODO(), chatRef, params.RawMentions) + return c.client.Chats.SearchMessages(context.TODO(), chatRef, searchOptions, searchConfig) }) } From cf62781168afdb169628df12575358e6f0290345 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Mon, 12 Jan 2026 21:07:30 +0100 Subject: [PATCH 87/99] feat: added search messages structures to python --- .../decoders/decode-searchOptions.go | 55 ++++--- python/teams_lib_pzsp2_z1/model/search.py | 134 ++++++++++++++++++ python/teams_lib_pzsp2_z1/services/chats.py | 28 ++++ 3 files changed, 201 insertions(+), 16 deletions(-) create mode 100644 python/teams_lib_pzsp2_z1/model/search.py diff --git a/go/internal/json-client/decoders/decode-searchOptions.go b/go/internal/json-client/decoders/decode-searchOptions.go index 48c0a0e..45ef127 100644 --- a/go/internal/json-client/decoders/decode-searchOptions.go +++ b/go/internal/json-client/decoders/decode-searchOptions.go @@ -40,22 +40,45 @@ func DecodeSearchMessageOptions(dto *SearchMessageOptionsDTO) *search.SearchMess interval := search.TimeInterval(dto.Interval) - options := &search.SearchMessagesOptions{ - Query: &dto.Query, - SearchPage: DecodeSearchPage(&dto.SearchPage), - From: dto.From, - NotFrom: dto.NotFrom, - IsRead: &dto.IsRead, - IsMentioned: &dto.IsMentioned, - To: dto.To, - NotTo: dto.NotTo, - StartTime: &dto.StartTime, - EndTime: &dto.EndTime, - Interval: &interval, - NotFromMe: dto.NotFromMe, - NotToMe: dto.NotToMe, - FromMe: dto.FromMe, - ToMe: dto.ToMe, + options := &search.SearchMessagesOptions{} + if dto.Query != "" { + options.Query = &dto.Query + } + options.SearchPage = DecodeSearchPage(&dto.SearchPage) + if len(dto.From) > 0 { + options.From = dto.From + } + if len(dto.NotFrom) > 0 { + options.NotFrom = dto.NotFrom + } + options.IsRead = &dto.IsRead + options.IsMentioned = &dto.IsMentioned + if len(dto.To) > 0 { + options.To = dto.To + } + if len(dto.NotTo) > 0 { + options.NotTo = dto.NotTo + } + if !dto.StartTime.IsZero() { + options.StartTime = &dto.StartTime + } + if !dto.EndTime.IsZero() { + options.EndTime = &dto.EndTime + } + if dto.Interval != "" { + options.Interval = &interval + } + if dto.NotFromMe { + options.NotFromMe = dto.NotFromMe + } + if dto.NotToMe { + options.NotToMe = dto.NotToMe + } + if dto.FromMe { + options.FromMe = dto.FromMe + } + if dto.ToMe { + options.ToMe = dto.ToMe } return options } diff --git a/python/teams_lib_pzsp2_z1/model/search.py b/python/teams_lib_pzsp2_z1/model/search.py new file mode 100644 index 0000000..2c871a0 --- /dev/null +++ b/python/teams_lib_pzsp2_z1/model/search.py @@ -0,0 +1,134 @@ +from dataclasses import dataclass +from enum import Enum + +from teams_lib_pzsp2_z1.model.message import Message + + +@dataclass +class SearchConfig: + """ + Configuration parameters for search operations. + + Attributes: + max_workers (int): The maximum number of worker threads to use for concurrent search operations. Defaults to 8. + """ + + max_workers: int = 8 + + +@dataclass +class SearchPage: + """ + Represents pagination parameters for search results. + + Attributes: + from_ (int): The starting index of the search results. + size (int): The number of search results to return. Defaults to 25. + """ + + from_: int = 0 + size: int = 25 + + +class TimeInterval(Enum): + """Enumeration of time intervals for filtering search results. + + Attributes: + TODAY: Represents the current day. + YESTERDAY: Represents the previous day. + THIS_WEEK: Represents the current week. + THIS_MONTH: Represents the current month. + LAST_MONTH: Represents the previous month. + THIS_YEAR: Represents the current year. + LAST_YEAR: Represents the previous year. + """ + + TODAY = "today" + YESTERDAY = "yesterday" + THIS_WEEK = "this week" + THIS_MONTH = "this month" + LAST_MONTH = "last month" + THIS_YEAR = "this year" + LAST_YEAR = "last year" + + @dataclass + class SearchMessagesOptions: + """ + Configuration options for searching messages. + + Attributes: + query (str | None): The search query string. + search_page (SearchPage | None): Pagination options. + from_ (list[str]): List of sender email addresses to include. + not_from (list[str]): List of sender email addresses to exclude. + is_read (bool | None): Filter by read status (True for read, False for unread). + is_mentioned (bool | None): Filter by mention status (True for mentioned, False for not mentioned). + to (list[str]): List of recipient email addresses to include. + not_to (list[str]): List of recipient email addresses to exclude. + start_time (datetime | None): Start time for the sent time range filter. + end_time (datetime | None): End time for the sent time range filter. + interval (TimeInterval | None): Predefined time interval for the sent time filter. + not_from_me (bool): Exclude messages sent by the current user. + not_to_me (bool): Exclude messages sent to the current user. + from_me (bool): Include only messages sent by the current user. + to_me (bool): Include only messages sent to the current user. + + Note: If interval is set, it takes precedence over start_time and end_time. + Note: Using `to` clauses works only in chats, not in team channels. + Note: Currently, queries for is_read may not function as expected due to API limitations. + """ + + query: str | None = None + search_page: SearchPage | None = None + from_: list[str] = None + not_from: list[str] = None + is_read: bool | None = None + is_mentioned: bool | None = None + to: list[str] = None + not_to: list[str] = None + start_time: object = None + end_time: object = None + interval: TimeInterval | None = None + not_from_me: bool = False + not_to_me: bool = False + from_me: bool = False + to_me: bool = False + + def __post_init__(self): + if self.from_ is None: + self.from_ = [] + if self.not_from is None: + self.not_from = [] + if self.to is None: + self.to = [] + if self.not_to is None: + self.not_to = [] + +@dataclass +class SearchResult: + """ + SearchResult represents a single search result containing a message and its context. + + Attributes: + message (Message): The message that matched the search criteria. + channel_id (str | None): The ID of the channel where the message was found (if applicable). + team_id (str | None): The ID of the team where the message was found (if applicable). + chat_id (str | None): The ID of the chat where the message was found (if applicable). + """ + message: Message + channel_id: str | None = None + team_id: str | None = None + chat_id: str | None = None + +@dataclass +class SearchResults: + """ + SearchResults represents the results of a message search. + + Attributes: + messages (list[SearchResult]): A list of search results. + next_from (int | None): The index to use for the next page of results, + or None if there are no more results. + """ + messages: list[SearchResult] + next_from: int | None = None diff --git a/python/teams_lib_pzsp2_z1/services/chats.py b/python/teams_lib_pzsp2_z1/services/chats.py index 1d072d2..c4a207a 100644 --- a/python/teams_lib_pzsp2_z1/services/chats.py +++ b/python/teams_lib_pzsp2_z1/services/chats.py @@ -91,6 +91,34 @@ def create_group_chat( topic=response["Topic"], ) + def get_chat(self, chat_ref: ChatRef) -> Chat: + """ + Retrieves a chat by its reference. + + Args: + chat_ref (ChatRef): The chat reference object containing the ID/Name and ChatType. + + Returns: + Chat: The requested chat object. + """ + response = self.client.execute( + cmd_type="request", + method="getChat", + params={ + "chatRef": { + "ref": chat_ref.ref, + "type": chat_ref.type.value, + }, + }, + ) + + return Chat( + id=response["ID"], + type=ChatType(response["Type"]), + is_hidden=(True if response["IsHidden"] else False), + topic=response["Topic"], + ) + def add_member_to_group_chat(self, group_chat_ref: str, user_ref: str) -> Member: """Adds a user to an existing Group Chat. From 35d3db7194b1dccb71333b50d0870bac0d0fbe5b Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Mon, 12 Jan 2026 21:15:46 +0100 Subject: [PATCH 88/99] feat: added new search functions to python lib --- python/teams_lib_pzsp2_z1/model/search.py | 30 +++++++++++ .../teams_lib_pzsp2_z1/services/channels.py | 52 ++++++++++++++++++ python/teams_lib_pzsp2_z1/services/chats.py | 53 +++++++++++++++++++ 3 files changed, 135 insertions(+) diff --git a/python/teams_lib_pzsp2_z1/model/search.py b/python/teams_lib_pzsp2_z1/model/search.py index 2c871a0..9983dbc 100644 --- a/python/teams_lib_pzsp2_z1/model/search.py +++ b/python/teams_lib_pzsp2_z1/model/search.py @@ -15,6 +15,10 @@ class SearchConfig: max_workers: int = 8 + def __dict__(self): + """Serializes the object keys to camelCase for the Go backend.""" + return {"maxWorkers": self.max_workers} + @dataclass class SearchPage: @@ -29,6 +33,10 @@ class SearchPage: from_: int = 0 size: int = 25 + def __dict__(self): + """Serializes the object keys to camelCase for the Go backend.""" + return {"from": self.from_, "size": self.size} + class TimeInterval(Enum): """Enumeration of time intervals for filtering search results. @@ -104,6 +112,28 @@ def __post_init__(self): if self.not_to is None: self.not_to = [] + def __dict__(self): + """Serializes the object keys to camelCase for the Go backend.""" + + result = { + "query": self.query, + "searchPage": self.search_page, + "from": self.from_, + "notFrom": self.not_from, + "isRead": self.is_read, + "isMentioned": self.is_mentioned, + "to": self.to, + "notTo": self.not_to, + "startTime": self.start_time, + "endTime": self.end_time, + "interval": self.interval.value if self.interval else None, + "notFromMe": self.not_from_me, + "notToMe": self.not_to_me, + "fromMe": self.from_me, + "toMe": self.to_me, + } + return {k: v for k, v in result.items() if v is not None} + @dataclass class SearchResult: """ diff --git a/python/teams_lib_pzsp2_z1/services/channels.py b/python/teams_lib_pzsp2_z1/services/channels.py index 6c39c77..17cd91e 100644 --- a/python/teams_lib_pzsp2_z1/services/channels.py +++ b/python/teams_lib_pzsp2_z1/services/channels.py @@ -12,6 +12,12 @@ MessageContentType, MessageFrom, ) +from teams_lib_pzsp2_z1.model.search import ( + SearchConfig, + SearchMessagesOptions, + SearchResult, + SearchResults, +) from teams_lib_pzsp2_z1.services.base_service import BaseService @@ -529,3 +535,49 @@ def get_mentions( text=mention["Text"], target_id=mention["TargetID"], ) for mention in response] + + def search_messages(self, team_ref: str, channel_ref: str, options: SearchMessagesOptions, config: SearchConfig) -> SearchResults: + """Searches for messages in a channel based on various criteria. + + Args: + team_ref (str): The reference to the team. + channel_ref (str): The reference to the channel. + options (SearchMessagesOptions): The search options and filters. + config (SearchConfig): Configuration for the search operation. + + Returns: + SearchResults: The results of the search operation. + """ + response = self.client.execute( + cmd_type="request", + method="searchMessagesInChat", + params={ + "teamRef": team_ref, + "channelRef": channel_ref, + "searchMessagesOptions": options.__dict__(), + "searchConfig": config.__dict__(), + }, + ) + + return SearchResults( + messages=[ + SearchResult( + message=Message( + id=msg["Message"]["ID"], + content=msg["Message"]["Content"], + content_type=MessageContentType(msg["Message"]["ContentType"]), + created_date_time=msg["Message"]["CreatedDateTime"], + sender=MessageFrom( + user_id=msg["Message"]["From"]["UserID"], + display_name=msg["Message"]["From"]["DisplayName"], + ), + reply_count=msg["Message"]["ReplyCount"], + ), + channel_id=msg.get("ChannelID"), + team_id=msg.get("TeamID"), + chat_id=msg.get("ChatID"), + ) + for msg in response["Messages"] + ], + next_from=response.get("NextFrom"), + ) diff --git a/python/teams_lib_pzsp2_z1/services/chats.py b/python/teams_lib_pzsp2_z1/services/chats.py index c4a207a..48bcbf6 100644 --- a/python/teams_lib_pzsp2_z1/services/chats.py +++ b/python/teams_lib_pzsp2_z1/services/chats.py @@ -14,6 +14,12 @@ MessageContentType, MessageFrom, ) +from teams_lib_pzsp2_z1.model.search import ( + SearchConfig, + SearchMessagesOptions, + SearchResult, + SearchResults, +) from teams_lib_pzsp2_z1.services.base_service import BaseService @@ -558,3 +564,50 @@ def get_mentions(self, chat_ref: ChatRef, raw_mentions: list[str]) -> list[Menti ) for mention in mentions ] + + def search_messages(self, chat_ref: ChatRef, options: SearchMessagesOptions, config: SearchConfig) -> SearchResults: + """Searches for messages in a chat based on various criteria. + + Args: + chat_ref (ChatRef): The chat reference object. + options (SearchMessagesOptions): The search options and filters. + config (SearchConfig): Configuration for the search operation. + + Returns: + SearchResults: The results of the search operation. + """ + response = self.client.execute( + cmd_type="request", + method="searchMessagesInChat", + params={ + "chatRef": { + "ref": chat_ref.ref, + "type": chat_ref.type.value, + }, + "searchMessagesOptions": options.__dict__(), + "searchConfig": config.__dict__(), + }, + ) + + return SearchResults( + messages=[ + SearchResult( + message=Message( + id=msg["Message"]["ID"], + content=msg["Message"]["Content"], + content_type=MessageContentType(msg["Message"]["ContentType"]), + created_date_time=msg["Message"]["CreatedDateTime"], + sender=MessageFrom( + user_id=msg["Message"]["From"]["UserID"], + display_name=msg["Message"]["From"]["DisplayName"], + ), + reply_count=msg["Message"]["ReplyCount"], + ), + channel_id=msg.get("ChannelID"), + team_id=msg.get("TeamID"), + chat_id=msg.get("ChatID"), + ) + for msg in response["Messages"] + ], + next_from=response.get("NextFrom"), + ) From 56c466b81c37cbfaccedc2fcd7c26ccda19669aa Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Mon, 12 Jan 2026 21:17:25 +0100 Subject: [PATCH 89/99] docs: added new search structs to docs --- python/docs/reference/models/channel.md | 2 +- python/docs/reference/models/chat.md | 2 +- python/docs/reference/models/member.md | 2 +- python/docs/reference/models/search.md | 3 +++ python/docs/reference/models/team.md | 2 +- python/mkdocs.yml | 3 ++- 6 files changed, 9 insertions(+), 5 deletions(-) create mode 100644 python/docs/reference/models/search.md diff --git a/python/docs/reference/models/channel.md b/python/docs/reference/models/channel.md index 483fc6e..8346b97 100644 --- a/python/docs/reference/models/channel.md +++ b/python/docs/reference/models/channel.md @@ -1,3 +1,3 @@ -# Channel Models +# Channel Model ::: teams_lib_pzsp2_z1.model.channel \ No newline at end of file diff --git a/python/docs/reference/models/chat.md b/python/docs/reference/models/chat.md index b1d7ccc..b4eb115 100644 --- a/python/docs/reference/models/chat.md +++ b/python/docs/reference/models/chat.md @@ -1,3 +1,3 @@ -# Message Models +# Chat Model ::: teams_lib_pzsp2_z1.model.chat \ No newline at end of file diff --git a/python/docs/reference/models/member.md b/python/docs/reference/models/member.md index 02b83a1..6056a2c 100644 --- a/python/docs/reference/models/member.md +++ b/python/docs/reference/models/member.md @@ -1,3 +1,3 @@ -# Message Models +# Member Model ::: teams_lib_pzsp2_z1.model.member \ No newline at end of file diff --git a/python/docs/reference/models/search.md b/python/docs/reference/models/search.md new file mode 100644 index 0000000..306e71a --- /dev/null +++ b/python/docs/reference/models/search.md @@ -0,0 +1,3 @@ +# Search messages model + +::: teams_lib_pzsp2_z1.model.search \ No newline at end of file diff --git a/python/docs/reference/models/team.md b/python/docs/reference/models/team.md index ef7a4ea..b1176db 100644 --- a/python/docs/reference/models/team.md +++ b/python/docs/reference/models/team.md @@ -1,3 +1,3 @@ -# Team Models +# Team Model ::: teams_lib_pzsp2_z1.model.team \ No newline at end of file diff --git a/python/mkdocs.yml b/python/mkdocs.yml index ee115fa..a861f32 100644 --- a/python/mkdocs.yml +++ b/python/mkdocs.yml @@ -56,4 +56,5 @@ nav: - Channel: reference/models/channel.md - Message: reference/models/message.md - Member: reference/models/member.md - - Chat: reference/models/chat.md \ No newline at end of file + - Chat: reference/models/chat.md + - Search: reference/models/search.md \ No newline at end of file From 5cc79e942a41ad0015b6d6ee3776f1e328e50f5b Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Mon, 12 Jan 2026 21:56:16 +0100 Subject: [PATCH 90/99] test: added test for get chat --- python/teams_lib_pzsp2_z1/model/search.py | 6 ++++-- python/tests/fake_server/data.py | 14 ++++++++++++++ python/tests/fake_server/setup.py | 10 ++++++++++ python/tests/test_chats.py | 23 +++++++++++++++++++++++ 4 files changed, 51 insertions(+), 2 deletions(-) diff --git a/python/teams_lib_pzsp2_z1/model/search.py b/python/teams_lib_pzsp2_z1/model/search.py index 9983dbc..29fb0f5 100644 --- a/python/teams_lib_pzsp2_z1/model/search.py +++ b/python/teams_lib_pzsp2_z1/model/search.py @@ -59,8 +59,9 @@ class TimeInterval(Enum): THIS_YEAR = "this year" LAST_YEAR = "last year" - @dataclass - class SearchMessagesOptions: + +@dataclass +class SearchMessagesOptions: """ Configuration options for searching messages. @@ -134,6 +135,7 @@ def __dict__(self): } return {k: v for k, v in result.items() if v is not None} + @dataclass class SearchResult: """ diff --git a/python/tests/fake_server/data.py b/python/tests/fake_server/data.py index c667983..7009c4a 100644 --- a/python/tests/fake_server/data.py +++ b/python/tests/fake_server/data.py @@ -562,6 +562,19 @@ def get_remove_member_response(self, team_id: str, channel_id: str, member_id: s # CHATS # ========================================== + def get_chat_responses(self, chat_id: str) -> dict: + chat = next((c for c in self.group_chats + self.oneonone_chats if c.id == chat_id), None) + if not chat: + return {} + + return { + ODATA_CONTEXT: "https://graph.microsoft.com/v1.0/$metadata#chats/$entity", + "id": chat.id, + "chatType": "oneOnOne" if chat.type == ChatType.ONE_ON_ONE else "group", + "isHiddenForAllMembers": chat.is_hidden, + "topic": chat.topic, + } + def get_list_chats_response(self, chat_type: ChatType) -> dict: chats = self.oneonone_chats if chat_type == ChatType.ONE_ON_ONE else self.group_chats return { @@ -641,6 +654,7 @@ def get_remove_member_from_group_chat_response(self, chat_id: str, member_id: st # CHAT MESSAGES # ========================================== + def get_list_messages_in_chat_response(self, chat_id: str) -> dict: return { "value": [ diff --git a/python/tests/fake_server/setup.py b/python/tests/fake_server/setup.py index d4aa92a..ed68d90 100644 --- a/python/tests/fake_server/setup.py +++ b/python/tests/fake_server/setup.py @@ -420,6 +420,16 @@ def handle_chats_request(req): # CHATS # ========================================== + # GET /chats/{chat_id} + httpserver.expect_request( + re.compile(r"^/v1.0/chats/([^/]+)"), + method="GET" + ).respond_with_handler(lambda req: make_log_response( + req, + data.get_chat_responses(re.search(r"/chats/([^/]+)", req.path).group(1)), + "Get Chat" + )) + # DELETE /chats/{chat_id}/pinnedMessages/{message_id} httpserver.expect_request( re.compile(r"^/v1.0/chats/([^/]+)/pinnedMessages/([^/]+)"), diff --git a/python/tests/test_chats.py b/python/tests/test_chats.py index a4dfe99..d842dad 100644 --- a/python/tests/test_chats.py +++ b/python/tests/test_chats.py @@ -100,6 +100,29 @@ def test_create_one_on_one_chat_integration(httpserver): finally: client.close() +def test_get_chat_integration(httpserver): + + data = setup_fake_server(httpserver) + + client = TeamsClient(auto_init=False) + try: + init_fake_client(client, httpserver.url_for("")) + + chat = client.chats.get_chat( + chat_ref=ChatRef( + ref=data.group_chats[1].topic, + type=ChatType.GROUP, + ) + ) + + assert chat.id == data.group_chats[1].id + assert chat.type == data.group_chats[1].type + assert chat.topic == data.group_chats[1].topic + assert chat.is_hidden == data.group_chats[1].is_hidden + + finally: + client.close() + def test_list_group_chat_members_integration(httpserver): From 552cbabd931ef55f231acfd12fc51f5dfb324033 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Tue, 13 Jan 2026 15:51:11 +0100 Subject: [PATCH 91/99] test: finally added test for search in chats --- go/go.sum | 12 ++ .../decoders/decode-searchOptions.go | 41 ++--- python/teams_lib_pzsp2_z1/model/search.py | 147 +++++++++--------- .../teams_lib_pzsp2_z1/services/channels.py | 2 +- python/teams_lib_pzsp2_z1/services/chats.py | 2 +- python/tests/fake_server/data.py | 132 +++++++++++++--- python/tests/fake_server/setup.py | 46 ++++-- python/tests/test_chats.py | 46 +++++- 8 files changed, 297 insertions(+), 131 deletions(-) diff --git a/go/go.sum b/go/go.sum index 5a91b58..a8092f1 100644 --- a/go/go.sum +++ b/go/go.sum @@ -75,6 +75,18 @@ github.com/pzsp-teams/lib v0.0.0-20260111170324-f66ca4f5d369 h1:TNB4hQ6u8aH0cekG github.com/pzsp-teams/lib v0.0.0-20260111170324-f66ca4f5d369/go.mod h1:bpgptBb8PaJV03buetvHnq6plkQHpF0WnY1K5iU31U8= github.com/pzsp-teams/lib v0.0.0-20260112132712-1335fb93c11f h1:MOnTg+X51/lukcWWjHyw9OPx9IzC6Q6TJsZXiaH/KnU= github.com/pzsp-teams/lib v0.0.0-20260112132712-1335fb93c11f/go.mod h1:bpgptBb8PaJV03buetvHnq6plkQHpF0WnY1K5iU31U8= +github.com/pzsp-teams/lib v0.0.0-20260113134422-e5326c621551 h1:w4nwFMJLwLYolig9U5r5HpVL0FhrPTP14NggM5/UX9M= +github.com/pzsp-teams/lib v0.0.0-20260113134422-e5326c621551/go.mod h1:bpgptBb8PaJV03buetvHnq6plkQHpF0WnY1K5iU31U8= +github.com/pzsp-teams/lib v0.0.0-20260113134659-c00a30df42d1 h1:ykdfDBsQzNu0DCKA+bm20n3o6EtpVPPhXZNRUbhCsuI= +github.com/pzsp-teams/lib v0.0.0-20260113134659-c00a30df42d1/go.mod h1:bpgptBb8PaJV03buetvHnq6plkQHpF0WnY1K5iU31U8= +github.com/pzsp-teams/lib v0.0.0-20260113135020-602a62d68535 h1:fWsO7MUGT+wblfPy0Cubpew9vWblm7+V0UdOUeqbEKU= +github.com/pzsp-teams/lib v0.0.0-20260113135020-602a62d68535/go.mod h1:bpgptBb8PaJV03buetvHnq6plkQHpF0WnY1K5iU31U8= +github.com/pzsp-teams/lib v0.0.0-20260113135200-1428146acf2e h1:ir/+PHMWaP4x2hZnQB4L6IDz42//Enf/RDA6Ryvzo70= +github.com/pzsp-teams/lib v0.0.0-20260113135200-1428146acf2e/go.mod h1:bpgptBb8PaJV03buetvHnq6plkQHpF0WnY1K5iU31U8= +github.com/pzsp-teams/lib v0.0.0-20260113135504-3f606125f3a8 h1:Ck+6z5keqy6aHZEH7Cpe+ozzQm7rv5dg0tRZf2016Zg= +github.com/pzsp-teams/lib v0.0.0-20260113135504-3f606125f3a8/go.mod h1:bpgptBb8PaJV03buetvHnq6plkQHpF0WnY1K5iU31U8= +github.com/pzsp-teams/lib v0.0.0-20260113135852-d15a162b495f h1:iuSQEjA6JrHLPoNJHPhloQlo8lAvqQXFqcxR3CJTn5s= +github.com/pzsp-teams/lib v0.0.0-20260113135852-d15a162b495f/go.mod h1:bpgptBb8PaJV03buetvHnq6plkQHpF0WnY1K5iU31U8= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/std-uritemplate/std-uritemplate/go/v2 v2.0.8 h1:gMBdYMTHt2mmTdXW8YfvRjRUZ0GhyGV+IqSH9H15bGw= diff --git a/go/internal/json-client/decoders/decode-searchOptions.go b/go/internal/json-client/decoders/decode-searchOptions.go index 45ef127..5958b81 100644 --- a/go/internal/json-client/decoders/decode-searchOptions.go +++ b/go/internal/json-client/decoders/decode-searchOptions.go @@ -20,17 +20,17 @@ type SearchMessageOptionsDTO struct { SearchPage SearchPageDTO `json:"searchPage"` From []string `json:"from"` NotFrom []string `json:"notFrom"` - IsRead bool `json:"isRead"` - IsMentioned bool `json:"isMentioned"` + IsRead *bool `json:"isRead"` + IsMentioned *bool `json:"isMentioned"` To []string `json:"to"` NotTo []string `json:"notTo"` StartTime time.Time `json:"startTime"` EndTime time.Time `json:"endTime"` Interval string `json:"interval"` - NotFromMe bool `json:"notFromMe"` - NotToMe bool `json:"notToMe"` - FromMe bool `json:"fromMe"` - ToMe bool `json:"toMe"` + NotFromMe *bool `json:"notFromMe"` + NotToMe *bool `json:"notToMe"` + FromMe *bool `json:"fromMe"` + ToMe *bool `json:"toMe"` } func DecodeSearchMessageOptions(dto *SearchMessageOptionsDTO) *search.SearchMessagesOptions { @@ -51,8 +51,8 @@ func DecodeSearchMessageOptions(dto *SearchMessageOptionsDTO) *search.SearchMess if len(dto.NotFrom) > 0 { options.NotFrom = dto.NotFrom } - options.IsRead = &dto.IsRead - options.IsMentioned = &dto.IsMentioned + options.IsRead = dto.IsRead + options.IsMentioned = dto.IsMentioned if len(dto.To) > 0 { options.To = dto.To } @@ -68,18 +68,19 @@ func DecodeSearchMessageOptions(dto *SearchMessageOptionsDTO) *search.SearchMess if dto.Interval != "" { options.Interval = &interval } - if dto.NotFromMe { - options.NotFromMe = dto.NotFromMe - } - if dto.NotToMe { - options.NotToMe = dto.NotToMe - } - if dto.FromMe { - options.FromMe = dto.FromMe - } - if dto.ToMe { - options.ToMe = dto.ToMe - } + if dto.NotFromMe != nil { + options.NotFromMe = *dto.NotFromMe + } + if dto.NotToMe != nil { + options.NotToMe = *dto.NotToMe + } + if dto.FromMe != nil { + options.FromMe = *dto.FromMe + } + if dto.ToMe != nil { + options.ToMe = *dto.ToMe + } + return options } diff --git a/python/teams_lib_pzsp2_z1/model/search.py b/python/teams_lib_pzsp2_z1/model/search.py index 29fb0f5..94ee72f 100644 --- a/python/teams_lib_pzsp2_z1/model/search.py +++ b/python/teams_lib_pzsp2_z1/model/search.py @@ -62,78 +62,78 @@ class TimeInterval(Enum): @dataclass class SearchMessagesOptions: - """ - Configuration options for searching messages. - - Attributes: - query (str | None): The search query string. - search_page (SearchPage | None): Pagination options. - from_ (list[str]): List of sender email addresses to include. - not_from (list[str]): List of sender email addresses to exclude. - is_read (bool | None): Filter by read status (True for read, False for unread). - is_mentioned (bool | None): Filter by mention status (True for mentioned, False for not mentioned). - to (list[str]): List of recipient email addresses to include. - not_to (list[str]): List of recipient email addresses to exclude. - start_time (datetime | None): Start time for the sent time range filter. - end_time (datetime | None): End time for the sent time range filter. - interval (TimeInterval | None): Predefined time interval for the sent time filter. - not_from_me (bool): Exclude messages sent by the current user. - not_to_me (bool): Exclude messages sent to the current user. - from_me (bool): Include only messages sent by the current user. - to_me (bool): Include only messages sent to the current user. - - Note: If interval is set, it takes precedence over start_time and end_time. - Note: Using `to` clauses works only in chats, not in team channels. - Note: Currently, queries for is_read may not function as expected due to API limitations. - """ - - query: str | None = None - search_page: SearchPage | None = None - from_: list[str] = None - not_from: list[str] = None - is_read: bool | None = None - is_mentioned: bool | None = None - to: list[str] = None - not_to: list[str] = None - start_time: object = None - end_time: object = None - interval: TimeInterval | None = None - not_from_me: bool = False - not_to_me: bool = False - from_me: bool = False - to_me: bool = False - - def __post_init__(self): - if self.from_ is None: - self.from_ = [] - if self.not_from is None: - self.not_from = [] - if self.to is None: - self.to = [] - if self.not_to is None: - self.not_to = [] - - def __dict__(self): - """Serializes the object keys to camelCase for the Go backend.""" - - result = { - "query": self.query, - "searchPage": self.search_page, - "from": self.from_, - "notFrom": self.not_from, - "isRead": self.is_read, - "isMentioned": self.is_mentioned, - "to": self.to, - "notTo": self.not_to, - "startTime": self.start_time, - "endTime": self.end_time, - "interval": self.interval.value if self.interval else None, - "notFromMe": self.not_from_me, - "notToMe": self.not_to_me, - "fromMe": self.from_me, - "toMe": self.to_me, - } - return {k: v for k, v in result.items() if v is not None} + """ + Configuration options for searching messages. + + Attributes: + query (str | None): The search query string. + search_page (SearchPage | None): Pagination options. + from_ (list[str]): List of sender email addresses to include. + not_from (list[str]): List of sender email addresses to exclude. + is_read (bool | None): Filter by read status (True for read, False for unread). + is_mentioned (bool | None): Filter by mention status (True for mentioned, False for not mentioned). + to (list[str]): List of recipient email addresses to include. + not_to (list[str]): List of recipient email addresses to exclude. + start_time (datetime | None): Start time for the sent time range filter. + end_time (datetime | None): End time for the sent time range filter. + interval (TimeInterval | None): Predefined time interval for the sent time filter. + not_from_me (bool): Exclude messages sent by the current user. + not_to_me (bool): Exclude messages sent to the current user. + from_me (bool): Include only messages sent by the current user. + to_me (bool): Include only messages sent to the current user. + + Note: If interval is set, it takes precedence over start_time and end_time. + Note: Using `to` clauses works only in chats, not in team channels. + Note: Currently, queries for is_read may not function as expected due to API limitations. + """ + + query: str | None = None + search_page: SearchPage | None = None + from_: list[str] = None + not_from: list[str] = None + is_read: bool | None = None + is_mentioned: bool | None = None + to: list[str] = None + not_to: list[str] = None + start_time: object = None + end_time: object = None + interval: TimeInterval | None = None + not_from_me: bool = False + not_to_me: bool = False + from_me: bool = False + to_me: bool = False + + def __post_init__(self): + if self.from_ is None: + self.from_ = [] + if self.not_from is None: + self.not_from = [] + if self.to is None: + self.to = [] + if self.not_to is None: + self.not_to = [] + + def __dict__(self): + """Serializes the object keys to camelCase for the Go backend.""" + + result = { + "query": self.query, + "searchPage": self.search_page, + "from": self.from_, + "notFrom": self.not_from, + "isRead": self.is_read, + "isMentioned": self.is_mentioned, + "to": self.to, + "notTo": self.not_to, + "startTime": self.start_time, + "endTime": self.end_time, + "interval": self.interval.value if self.interval else None, + "notFromMe": self.not_from_me, + "notToMe": self.not_to_me, + "fromMe": self.from_me, + "toMe": self.to_me, + } + return {k: v for k, v in result.items() if v is not None and v != []} @dataclass @@ -147,11 +147,13 @@ class SearchResult: team_id (str | None): The ID of the team where the message was found (if applicable). chat_id (str | None): The ID of the chat where the message was found (if applicable). """ + message: Message channel_id: str | None = None team_id: str | None = None chat_id: str | None = None + @dataclass class SearchResults: """ @@ -162,5 +164,6 @@ class SearchResults: next_from (int | None): The index to use for the next page of results, or None if there are no more results. """ + messages: list[SearchResult] next_from: int | None = None diff --git a/python/teams_lib_pzsp2_z1/services/channels.py b/python/teams_lib_pzsp2_z1/services/channels.py index 17cd91e..f19fbd1 100644 --- a/python/teams_lib_pzsp2_z1/services/channels.py +++ b/python/teams_lib_pzsp2_z1/services/channels.py @@ -578,6 +578,6 @@ def search_messages(self, team_ref: str, channel_ref: str, options: SearchMessag chat_id=msg.get("ChatID"), ) for msg in response["Messages"] - ], + ] if response.get("Messages") else [], next_from=response.get("NextFrom"), ) diff --git a/python/teams_lib_pzsp2_z1/services/chats.py b/python/teams_lib_pzsp2_z1/services/chats.py index 48bcbf6..2524569 100644 --- a/python/teams_lib_pzsp2_z1/services/chats.py +++ b/python/teams_lib_pzsp2_z1/services/chats.py @@ -608,6 +608,6 @@ def search_messages(self, chat_ref: ChatRef, options: SearchMessagesOptions, con chat_id=msg.get("ChatID"), ) for msg in response["Messages"] - ], + ] if response.get("Messages") else [], next_from=response.get("NextFrom"), ) diff --git a/python/tests/fake_server/data.py b/python/tests/fake_server/data.py index 7009c4a..130f8e7 100644 --- a/python/tests/fake_server/data.py +++ b/python/tests/fake_server/data.py @@ -1,4 +1,6 @@ from dataclasses import dataclass, field +import re +import traceback from typing import Optional, List, Dict, Any from unittest import result from urllib import request @@ -28,6 +30,7 @@ class FakeServerData: newGroupMailNickname: str newTeamVisibility: str potentialTeams: List[Team] = field(default_factory=list) + search_results: List[Message] = field(default_factory=list) # Initial data containers messages: Dict[str, List[Message]] = field(default_factory=dict) @@ -128,8 +131,8 @@ def __init__(self) -> None: self.chat_messages = { "chat-123-abc": [ - Message(id="msg-001", content="Hello, team!", content_type="text", sender=MessageFrom(user_id="user-123-abc", display_name="Alice"), created_date_time="2024-01-01T10:00:00Z", reply_count=0), - Message(id="msg-002", content="Don't forget the meeting at 3 PM.", content_type="text", sender=MessageFrom(user_id="user-456-def", display_name="Bob"), created_date_time="2024-01-01T11:00:00Z", reply_count=2), + Message(id="AAMkAGIwMDA5MmY0LWY5ZTgtNGY5YS04NzczLWNhNjc0ZGIyZDBjYgBGAAAAAADm35sgHbzESapJ8_BjBlhEBwDAYtphe7dsRbDrOT-HAHoKAACmqNsoAADAYtphe7dsRbDrOT-HAHoKAAFsBhyEAAA=", content="Hello, team!", content_type="text", sender=MessageFrom(user_id="user-123-abc", display_name="Alice"), created_date_time="2024-01-01T10:00:00Z", reply_count=0), + Message(id="BAMkAGIwMDA5MmY0LWY5ZTgtNGY5YS04NzczLWNhNjc0ZGIyZDBjYgBGAAAAAADm35sgHbzESapJ8_BjBlhEBwDAYtphe7dsRbDrOT-HAHoKAACmqNsoAADAYtphe7dsRbDrOT-HAHoKAAFsBhyEAAA=", content="Don't forget the meeting at 3 PM.", content_type="text", sender=MessageFrom(user_id="user-456-def", display_name="Bob"), created_date_time="2024-01-01T11:00:00Z", reply_count=2), ], } @@ -749,22 +752,22 @@ def get_list_pinned_messages_in_chat_response(self, chat_id: str) -> dict: { "id": message.id, "message": { - "id": message.id, - "etag": message.id, - "messageType": "message", - "chatId": chat_id, - "body": { - "content": message.content, - "contentType": message.content_type, - }, - "from": { - "user": { - "id": message.sender.user_id, - "displayName": message.sender.display_name, - } - }, - "createdDateTime": message.created_date_time, - "lastModifiedDateTime": message.created_date_time, + "id": message.id, + "etag": message.id, + "messageType": "message", + "chatId": chat_id, + "body": { + "content": message.content, + "contentType": message.content_type, + }, + "from": { + "user": { + "id": message.sender.user_id, + "displayName": message.sender.display_name, + } + }, + "createdDateTime": message.created_date_time, + "lastModifiedDateTime": message.created_date_time, } } for message in self.chat_messages.get(chat_id, []) @@ -780,4 +783,95 @@ def get_me_response(self) -> dict: "id": self.me.id, "displayName": self.me.display_name, "email": self.me.email, - } \ No newline at end of file + } + + # ========================================== + # SEARCH + # ========================================== + + def get_search_messages_response(self, request_body) -> dict: + hits = [] + rank = 1 + messages = [] + + # 1. Wyciągamy surowy KQL (to już wiesz że działa) + raw_query = "" + try: + if isinstance(request_body, dict): + req_list = request_body.get('requests', []) + if req_list: + raw_query = req_list[0].get('query', {}).get('queryString', "") + except Exception: + raw_query = "" + + # 2. CZYSZCZENIE (To jest to, czego Ci brakuje) + # Wywalamy wszystko co wygląda jak Klucz:"Wartość" + # Zamieniamy 'Hello IsRead:"false"...' -> 'Hello ...' + search_term = re.sub(r'\w+:"[^"]+"', '', raw_query) + + # Usuwamy spacje z brzegów -> zostaje samo "Hello" + search_term = search_term.strip() + + + # 3. Pętla szukająca + try: + for chat_id, messages in self.chat_messages.items(): + for message in messages: + if not message.content: + continue + + # Teraz szukamy samego "Hello" w treści wiadomości + if search_term and search_term.lower() in message.content.lower(): + + # (Bezpieczne pobieranie sendera) + sender_id = getattr(message.sender, 'user_id', 'unknown') if message.sender else 'unknown' + sender_name = getattr(message.sender, 'display_name', 'Unknown') if message.sender else 'Unknown' + created_dt = getattr(message, 'created_date_time', "2024-01-01T12:00:00Z") + + hits.append({ + "hitId": message.id, + "rank": rank, + "summary": message.content[:200], + "resource": { + "id": message.id, + "chatId": chat_id, + "body": { + "content": message.content, + "contentType": message.content_type, + }, + "from": { + "user": { + "id": message.sender.user_id, + "displayName": message.sender.display_name, + } + }, + "createdDateTime": message.created_date_time, + "lastModifiedDateTime": message.created_date_time, + } + }, + ) + rank += 1 + + except Exception: + traceback.print_exc() + + response = { + "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#Collection(microsoft.graph.searchResponse)", + "value": [ + { + "searchTerms": [search_term], + "hitsContainers": [ + { + "hits": hits, + "total": len(hits), + "moreResultsAvailable": False, + } + ], + } + ] + } + + # self.search_results = messages + + + return response \ No newline at end of file diff --git a/python/tests/fake_server/setup.py b/python/tests/fake_server/setup.py index ed68d90..34fe21c 100644 --- a/python/tests/fake_server/setup.py +++ b/python/tests/fake_server/setup.py @@ -14,11 +14,13 @@ def setup_fake_server(httpserver) -> FakeServerData: data = FakeServerData() - def make_log_response(request, data_dict: dict, context_msg: str = "") -> Response: + def make_log_response(request, data_dict: dict, context_msg: str = "", status_code = None) -> Response: print(f"\n[MOCK SERVER] 🟢 REQUEST: {request.method} {request.path}", file=sys.stdout) if context_msg: print(f"[MOCK SERVER] Context: {context_msg}", file=sys.stdout) + print(f"[MOCK SERVER] Returned body: {data_dict}", file=sys.stdout) + if request.content_type == "application/json": try: req_data = request.json @@ -27,9 +29,10 @@ def make_log_response(request, data_dict: dict, context_msg: str = "") -> Respon except Exception as e: print(f"[MOCK SERVER] ⚠️ Body Error: {e}", file=sys.stdout) - status_code = 200 if data_dict else 404 - if request.method in ["POST", "PUT"] and data_dict: - status_code = 201 + if not status_code: + status_code = 200 if data_dict else 404 + if request.method in ["POST", "PUT"] and data_dict: + status_code = 201 print(f"[MOCK SERVER] 👉 Response: {status_code}", file=sys.stdout) @@ -39,6 +42,21 @@ def make_log_response(request, data_dict: dict, context_msg: str = "") -> Respon status=status_code ) + # ========================================== + # SEARCH + # ========================================== + + # POST /search/query + httpserver.expect_request( + "/v1.0/search/query", + method="POST" + ).respond_with_handler(lambda req: make_log_response( + req, + data.get_search_messages_response(req.json), + "Search Messages", + status_code=200 + )) + # ========================================== # USERS # ========================================== @@ -420,15 +438,6 @@ def handle_chats_request(req): # CHATS # ========================================== - # GET /chats/{chat_id} - httpserver.expect_request( - re.compile(r"^/v1.0/chats/([^/]+)"), - method="GET" - ).respond_with_handler(lambda req: make_log_response( - req, - data.get_chat_responses(re.search(r"/chats/([^/]+)", req.path).group(1)), - "Get Chat" - )) # DELETE /chats/{chat_id}/pinnedMessages/{message_id} httpserver.expect_request( @@ -557,6 +566,17 @@ def handle_chats_request(req): "Create Chat" )) + # GET /chats/{chat_id} + httpserver.expect_request( + re.compile(r"^/v1.0/chats/([^/]+)"), + method="GET" + ).respond_with_handler(lambda req: make_log_response( + req, + data.get_chat_responses(re.search(r"/chats/([^/]+)", req.path).group(1)), + "Get Chat" + )) + + # Fallback for unmatched routes httpserver.expect_request(re.compile(".*")).respond_with_handler( lambda req: ( diff --git a/python/tests/test_chats.py b/python/tests/test_chats.py index d842dad..d42dab2 100644 --- a/python/tests/test_chats.py +++ b/python/tests/test_chats.py @@ -4,6 +4,7 @@ from teams_lib_pzsp2_z1.model.chat import ChatType, ChatRef from teams_lib_pzsp2_z1.model.message import MessageContentType, MessageBody from teams_lib_pzsp2_z1.model.mention import MentionKind +from teams_lib_pzsp2_z1.model.search import SearchConfig, SearchMessagesOptions, TimeInterval from datetime import datetime @@ -110,15 +111,15 @@ def test_get_chat_integration(httpserver): chat = client.chats.get_chat( chat_ref=ChatRef( - ref=data.group_chats[1].topic, + ref=data.group_chats[0].topic, type=ChatType.GROUP, ) ) - assert chat.id == data.group_chats[1].id - assert chat.type == data.group_chats[1].type - assert chat.topic == data.group_chats[1].topic - assert chat.is_hidden == data.group_chats[1].is_hidden + assert chat.id == data.group_chats[0].id + assert chat.type == data.group_chats[0].type + assert chat.topic == data.group_chats[0].topic + assert chat.is_hidden == data.group_chats[0].is_hidden finally: client.close() @@ -473,3 +474,38 @@ def test_get_mention_integration(httpserver): finally: client.close() +def test_search_messages_integration(httpserver): + + data = setup_fake_server(httpserver) + + client = TeamsClient(auto_init=False) + try: + init_fake_client(client, httpserver.url_for("")) + + results = client.chats.search_messages( + chat_ref=ChatRef( + ref=data.group_chats[0].topic, + type=ChatType.GROUP, + ), + options=SearchMessagesOptions( + query="Hello" + ), + config=SearchConfig( + max_workers=4, + ) + ) + + assert len(results.messages) == 1 + + assert results.messages[0].chat_id == data.group_chats[0].id + + assert results.messages[0].message.id == data.chat_messages[data.group_chats[0].id][0].id + assert results.messages[0].message.content == data.chat_messages[data.group_chats[0].id][0].content + assert results.messages[0].message.content_type == MessageContentType(data.chat_messages[data.group_chats[0].id][0].content_type) + assert results.messages[0].message.sender.user_id == data.chat_messages[data.group_chats[0].id][0].sender.user_id + assert results.messages[0].message.sender.display_name == data.chat_messages[data.group_chats[0].id][0].sender.display_name + assert results.messages[0].message.reply_count == data.chat_messages[data.group_chats[0].id][0].reply_count + assert results.messages[0].message.created_date_time == data.chat_messages[data.group_chats[0].id][0].created_date_time + + finally: + client.close() From 9e9573076a54f0f2e2ba6d7af62b531b7b557ac1 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Tue, 13 Jan 2026 16:31:53 +0100 Subject: [PATCH 92/99] test: added test for search in channels --- go/go.sum | 2 + .../teams_lib_pzsp2_z1/services/channels.py | 2 +- python/tests/fake_server/data.py | 72 +++++++++++++------ python/tests/test_channels.py | 39 ++++++++++ python/tests/test_chats.py | 2 +- 5 files changed, 94 insertions(+), 23 deletions(-) diff --git a/go/go.sum b/go/go.sum index a8092f1..6790c62 100644 --- a/go/go.sum +++ b/go/go.sum @@ -87,6 +87,8 @@ github.com/pzsp-teams/lib v0.0.0-20260113135504-3f606125f3a8 h1:Ck+6z5keqy6aHZEH github.com/pzsp-teams/lib v0.0.0-20260113135504-3f606125f3a8/go.mod h1:bpgptBb8PaJV03buetvHnq6plkQHpF0WnY1K5iU31U8= github.com/pzsp-teams/lib v0.0.0-20260113135852-d15a162b495f h1:iuSQEjA6JrHLPoNJHPhloQlo8lAvqQXFqcxR3CJTn5s= github.com/pzsp-teams/lib v0.0.0-20260113135852-d15a162b495f/go.mod h1:bpgptBb8PaJV03buetvHnq6plkQHpF0WnY1K5iU31U8= +github.com/pzsp-teams/lib v0.0.0-20260113152600-e9075fa2951a h1:Y/I0MRE60rwsutOcj1FlnyrsrrL6jdJIcxCnL2R80V4= +github.com/pzsp-teams/lib v0.0.0-20260113152600-e9075fa2951a/go.mod h1:bpgptBb8PaJV03buetvHnq6plkQHpF0WnY1K5iU31U8= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/std-uritemplate/std-uritemplate/go/v2 v2.0.8 h1:gMBdYMTHt2mmTdXW8YfvRjRUZ0GhyGV+IqSH9H15bGw= diff --git a/python/teams_lib_pzsp2_z1/services/channels.py b/python/teams_lib_pzsp2_z1/services/channels.py index f19fbd1..2c80e1d 100644 --- a/python/teams_lib_pzsp2_z1/services/channels.py +++ b/python/teams_lib_pzsp2_z1/services/channels.py @@ -550,7 +550,7 @@ def search_messages(self, team_ref: str, channel_ref: str, options: SearchMessag """ response = self.client.execute( cmd_type="request", - method="searchMessagesInChat", + method="searchMessagesInChannel", params={ "teamRef": team_ref, "channelRef": channel_ref, diff --git a/python/tests/fake_server/data.py b/python/tests/fake_server/data.py index 130f8e7..ae7d85b 100644 --- a/python/tests/fake_server/data.py +++ b/python/tests/fake_server/data.py @@ -30,7 +30,7 @@ class FakeServerData: newGroupMailNickname: str newTeamVisibility: str potentialTeams: List[Team] = field(default_factory=list) - search_results: List[Message] = field(default_factory=list) + searching: str = "chats" # Initial data containers messages: Dict[str, List[Message]] = field(default_factory=dict) @@ -85,6 +85,7 @@ def __init__(self) -> None: "19:123123@thread.tacv2": [ Message(id="msg-001", content="Hello, team!", content_type="text", sender=MessageFrom(user_id="user-123-abc", display_name="Alice"), created_date_time="2024-01-01T10:00:00Z", reply_count=0), Message(id="msg-002", content="Don't forget the meeting at 3 PM.", content_type="text", sender=MessageFrom(user_id="user-456-def", display_name="Bob"), created_date_time="2024-01-01T11:00:00Z", reply_count=2), + Message(id="AAMkAGIwMDA5MmY0LWY5ZTgtNGY5YS04NzczLWNhNjc0ZGIyZDBjYgBGAAAAAADm35sgHbzESapJ8_BjBlhEBwDAYtphe7dsRbDrOT-HAHoKAACmqNsoAADAYtphe7dsRbDrOT-HAHoKAAFsBhyEAAA=", content="Hello, team!", content_type="text", sender=MessageFrom(user_id="user-123-abc", display_name="Alice"), created_date_time="2024-01-01T10:00:00Z", reply_count=0), ], } @@ -828,28 +829,57 @@ def get_search_messages_response(self, request_body) -> dict: sender_name = getattr(message.sender, 'display_name', 'Unknown') if message.sender else 'Unknown' created_dt = getattr(message, 'created_date_time', "2024-01-01T12:00:00Z") - hits.append({ - "hitId": message.id, - "rank": rank, - "summary": message.content[:200], - "resource": { - "id": message.id, - "chatId": chat_id, - "body": { - "content": message.content, - "contentType": message.content_type, - }, - "from": { - "user": { - "id": message.sender.user_id, - "displayName": message.sender.display_name, - } - }, - "createdDateTime": message.created_date_time, - "lastModifiedDateTime": message.created_date_time, + if self.searching == "chats": + hits.append({ + "hitId": message.id, + "rank": rank, + "summary": message.content[:200], + "resource": { + "id": message.id, + "chatId": chat_id, + "body": { + "content": message.content, + "contentType": message.content_type, + }, + "from": { + "user": { + "id": sender_id, + "displayName": sender_name, + } + }, + "createdDateTime": created_dt, + "lastModifiedDateTime": created_dt, + } + }, + ) + elif self.searching == "channels": + hits.append({ + "hitId": message.id, + "rank": rank, + "summary": message.content[:200], + "resource": { + "id": message.id, + "channelIdentity": { + "teamId": self.teams[0].id, + "channelId": self.channels[self.teams[0].id][0].id, + }, + "teamId": self.teams[0].id, + "channelId": self.channels[self.teams[0].id][0].id, + "body": { + "content": message.content, + "contentType": message.content_type, + }, + "from": { + "user": { + "id": sender_id, + "displayName": sender_name, + } + }, + "createdDateTime": created_dt, + "lastModifiedDateTime": created_dt, } }, - ) + ) rank += 1 except Exception: diff --git a/python/tests/test_channels.py b/python/tests/test_channels.py index c8989f8..98323eb 100644 --- a/python/tests/test_channels.py +++ b/python/tests/test_channels.py @@ -3,6 +3,7 @@ from tests.fake_server.setup import setup_fake_server from teams_lib_pzsp2_z1.model.message import MessageContentType, MessageBody from teams_lib_pzsp2_z1.model.mention import MentionKind +from teams_lib_pzsp2_z1.model.search import SearchConfig, SearchMessagesOptions def test_list_channels_integration(httpserver): @@ -394,6 +395,44 @@ def test_get_mention_integration(httpserver): client.close() +def test_search_messages_integration(httpserver): + + data = setup_fake_server(httpserver) + data.searching = "channels" + + client = TeamsClient(auto_init=False) + try: + init_fake_client(client, httpserver.url_for("")) + + results = client.channels.search_messages( + team_ref=data.teams[0].display_name, + channel_ref=data.channels[data.teams[0].id][0].name, + options=SearchMessagesOptions( + query="Hello", + ), + config=SearchConfig( + max_workers=4, + ) + ) + + assert len(results.messages) == 1 + + assert results.messages[0].team_id == data.teams[0].id + assert results.messages[0].channel_id == data.channels[data.teams[0].id][0].id + assert results.messages[0].chat_id is None + + assert results.messages[0].message.id == data.messages[results.messages[0].channel_id][2].id + assert results.messages[0].message.content == data.messages[results.messages[0].channel_id][2].content + assert results.messages[0].message.content_type == MessageContentType(data.messages[results.messages[0].channel_id][2].content_type) + assert results.messages[0].message.sender.user_id == data.messages[results.messages[0].channel_id][2].sender.user_id + assert results.messages[0].message.sender.display_name == data.messages[results.messages[0].channel_id][2].sender.display_name + assert results.messages[0].message.reply_count == data.messages[results.messages[0].channel_id][2].reply_count + assert results.messages[0].message.created_date_time == data.messages[results.messages[0].channel_id][0].created_date_time + + finally: + client.close() + + diff --git a/python/tests/test_chats.py b/python/tests/test_chats.py index d42dab2..d71275b 100644 --- a/python/tests/test_chats.py +++ b/python/tests/test_chats.py @@ -4,7 +4,7 @@ from teams_lib_pzsp2_z1.model.chat import ChatType, ChatRef from teams_lib_pzsp2_z1.model.message import MessageContentType, MessageBody from teams_lib_pzsp2_z1.model.mention import MentionKind -from teams_lib_pzsp2_z1.model.search import SearchConfig, SearchMessagesOptions, TimeInterval +from teams_lib_pzsp2_z1.model.search import SearchConfig, SearchMessagesOptions from datetime import datetime From dec2fae723992c9e132421d6c04f0d222ae011b7 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Tue, 13 Jan 2026 16:33:50 +0100 Subject: [PATCH 93/99] chore: cleaned tests --- python/tests/test_teams.py | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/python/tests/test_teams.py b/python/tests/test_teams.py index 3c2d7fb..be19e85 100644 --- a/python/tests/test_teams.py +++ b/python/tests/test_teams.py @@ -105,26 +105,6 @@ def test_create_team_via_group_integration(httpserver): client.close() -def test_create_team_from_template_integration(httpserver): - - data = setup_fake_server(httpserver) - - client = TeamsClient(auto_init=False) - try: - init_fake_client(client, httpserver.url_for("")) - - id = client.teams.create_from_template( - display_name=data.newTeamName, - description="A team created from a template", - owners=["user-123-abc"], - ) - - assert id == data.newTeamID - - finally: - client.close() - - def test_archive_team_integration(httpserver): data = setup_fake_server(httpserver) From 3c16502d4be4adb1442dcd3fcabe117e67a27155 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Tue, 13 Jan 2026 17:03:34 +0100 Subject: [PATCH 94/99] chore: removed unnecessary comments --- python/tests/fake_server/data.py | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/python/tests/fake_server/data.py b/python/tests/fake_server/data.py index ae7d85b..f3cb45d 100644 --- a/python/tests/fake_server/data.py +++ b/python/tests/fake_server/data.py @@ -287,24 +287,6 @@ def get_createTeamFromTemplate_response(self, request: request.Request) -> Respo response= "" ) - # def create_team_handler(self, req): - # result = self.get_createTeamFromTemplate_response(req.json) - - # base_url = "https://graph.microsoft.com/v1.0" - # loc_header = f"/teams/{TEAM_ID}/operations/{OP_ID}" - # content_loc_header = f"/teams/{TEAM_ID}" - - # return Response( - # response=result["body"], - # status=result["status"], - # headers={ - # "Location": full_location, - # "Content-Location": full_content_loc, - # "Content-Type": result["headers"]["Content-Type"], - # "Content-Length": result["headers"]["Content-Length"] - # } - # ) - def get_archiveTeam_response(self, team_id: str) -> dict: team = self._find_team(team_id) if not team: @@ -795,7 +777,6 @@ def get_search_messages_response(self, request_body) -> dict: rank = 1 messages = [] - # 1. Wyciągamy surowy KQL (to już wiesz że działa) raw_query = "" try: if isinstance(request_body, dict): @@ -805,26 +786,19 @@ def get_search_messages_response(self, request_body) -> dict: except Exception: raw_query = "" - # 2. CZYSZCZENIE (To jest to, czego Ci brakuje) - # Wywalamy wszystko co wygląda jak Klucz:"Wartość" - # Zamieniamy 'Hello IsRead:"false"...' -> 'Hello ...' search_term = re.sub(r'\w+:"[^"]+"', '', raw_query) - # Usuwamy spacje z brzegów -> zostaje samo "Hello" search_term = search_term.strip() - # 3. Pętla szukająca try: for chat_id, messages in self.chat_messages.items(): for message in messages: if not message.content: continue - # Teraz szukamy samego "Hello" w treści wiadomości if search_term and search_term.lower() in message.content.lower(): - # (Bezpieczne pobieranie sendera) sender_id = getattr(message.sender, 'user_id', 'unknown') if message.sender else 'unknown' sender_name = getattr(message.sender, 'display_name', 'Unknown') if message.sender else 'Unknown' created_dt = getattr(message, 'created_date_time', "2024-01-01T12:00:00Z") @@ -901,7 +875,5 @@ def get_search_messages_response(self, request_body) -> dict: ] } - # self.search_results = messages - return response \ No newline at end of file From 50e9956d30e42a8ff0a12c6c956456e531522d8c Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Tue, 13 Jan 2026 18:24:17 +0100 Subject: [PATCH 95/99] docs: README fixes --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index cbc32a9..a9913ec 100644 --- a/README.md +++ b/README.md @@ -47,8 +47,7 @@ SCOPES=User.Read,Team.ReadBasic.All,Channel.ReadBasic.All AUTH_METHOD=DEVICE_CODE [or INTERACTIVE] ``` -Scopes needed by all functions could be found in .env.template. -**//TODO add url to template** +Scopes needed by all functions could be found in [.env.template](https://github.com/pzsp-teams/lib-python/tree/main/.env.template). ### 2. Basic usage @@ -120,5 +119,5 @@ client.close() ## 📄 License This project is licensed under the MIT License - see the [LICENSE](https://github.com/pzsp-teams/lib-python/blob/main/LICENSE) file for details. -## Porst +## Port This lib is also available in [GO](https://github.com/pzsp-teams/lib) From ed2d7cb1e92a6d750c4fa4ecab013f72e362a916 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Tue, 13 Jan 2026 18:31:57 +0100 Subject: [PATCH 96/99] chore: replaced interface{} with any --- go/bridge/responses.go | 4 +- .../json-client/decoders/decode-params.go | 2 +- go/internal/json-client/fake-client.go | 2 +- go/internal/json-client/request-map.go | 90 +++++++++---------- go/internal/json-client/requests-channels.go | 68 +++++++------- go/internal/json-client/requests-chats.go | 72 +++++++-------- go/internal/json-client/requests-teams.go | 34 +++---- go/internal/json-client/teams-json-client.go | 2 +- go/internal/json-model/json-model.go | 6 +- 9 files changed, 140 insertions(+), 140 deletions(-) diff --git a/go/bridge/responses.go b/go/bridge/responses.go index ff2c4f2..72dcedc 100644 --- a/go/bridge/responses.go +++ b/go/bridge/responses.go @@ -6,7 +6,7 @@ import ( ) type response struct { - Result interface{} `json:"result,omitempty"` + Result any `json:"result,omitempty"` Error string `json:"error,omitempty"` } @@ -29,7 +29,7 @@ func respondError(writer *bufio.Writer, err error) { writer.Flush() } -func respondResult(writer *bufio.Writer, result interface{}) { +func respondResult(writer *bufio.Writer, result any) { response := response{ Result: result, } diff --git a/go/internal/json-client/decoders/decode-params.go b/go/internal/json-client/decoders/decode-params.go index 6651123..c6a18ef 100644 --- a/go/internal/json-client/decoders/decode-params.go +++ b/go/internal/json-client/decoders/decode-params.go @@ -4,7 +4,7 @@ import ( "github.com/mitchellh/mapstructure" ) -func DecodeParams[T any](p interface{}) (*T, error) { +func DecodeParams[T any](p any) (*T, error) { var result T config := &mapstructure.DecoderConfig{ diff --git a/go/internal/json-client/fake-client.go b/go/internal/json-client/fake-client.go index 2f131b6..2882bdc 100644 --- a/go/internal/json-client/fake-client.go +++ b/go/internal/json-client/fake-client.go @@ -20,7 +20,7 @@ import ( // --- 1. Fake Auth Provider --- type FakeAuthProvider struct{} -func (f *FakeAuthProvider) AuthenticateRequest(ctx context.Context, request *azcore.RequestInformation, additionalAuthenticationContext map[string]interface{}) error { +func (f *FakeAuthProvider) AuthenticateRequest(ctx context.Context, request *azcore.RequestInformation, additionalAuthenticationContext map[string]any) error { if request.Headers == nil { request.Headers = azcore.NewRequestHeaders() } diff --git a/go/internal/json-client/request-map.go b/go/internal/json-client/request-map.go index 9f31f27..229b095 100644 --- a/go/internal/json-client/request-map.go +++ b/go/internal/json-client/request-map.go @@ -1,185 +1,185 @@ package jsonclient -type HandlerFunc func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) +type HandlerFunc func(c *TeamsJSONClient, p map[string]any) (any, error) var Handlers = map[string]HandlerFunc{ // TEAMS - "getTeam": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + "getTeam": func(c *TeamsJSONClient, p map[string]any) (any, error) { return c.GetTeam(p) }, - "updateTeam": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + "updateTeam": func(c *TeamsJSONClient, p map[string]any) (any, error) { return c.UpdateTeam(p) }, - "listMyJoined": func(c *TeamsJSONClient, _ map[string]interface{}) (interface{}, error) { + "listMyJoined": func(c *TeamsJSONClient, _ map[string]any) (any, error) { return c.ListMyJoined() }, - "createTeamViaGroup": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + "createTeamViaGroup": func(c *TeamsJSONClient, p map[string]any) (any, error) { return c.CreateTeamViaGroup(p) }, - "createTeamFromTemplate": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + "createTeamFromTemplate": func(c *TeamsJSONClient, p map[string]any) (any, error) { return c.CreateTeamFromTemplate(p) }, - "archiveTeam": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + "archiveTeam": func(c *TeamsJSONClient, p map[string]any) (any, error) { return c.ArchiveTeam(p) }, - "unarchiveTeam": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + "unarchiveTeam": func(c *TeamsJSONClient, p map[string]any) (any, error) { return c.UnarchiveTeam(p) }, - "deleteTeam": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + "deleteTeam": func(c *TeamsJSONClient, p map[string]any) (any, error) { return c.DeleteTeam(p) }, - "restoreDeletedTeam": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + "restoreDeletedTeam": func(c *TeamsJSONClient, p map[string]any) (any, error) { return c.RestoreDeletedTeam(p) }, // CHANNELS - "listChannels": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + "listChannels": func(c *TeamsJSONClient, p map[string]any) (any, error) { return c.ListChannels(p) }, - "getChannel": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + "getChannel": func(c *TeamsJSONClient, p map[string]any) (any, error) { return c.GetChannel(p) }, - "createStandardChannel": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + "createStandardChannel": func(c *TeamsJSONClient, p map[string]any) (any, error) { return c.CreateStandardChannel(p) }, - "createPrivateChannel": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + "createPrivateChannel": func(c *TeamsJSONClient, p map[string]any) (any, error) { return c.CreatePrivateChannel(p) }, - "deleteChannel": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + "deleteChannel": func(c *TeamsJSONClient, p map[string]any) (any, error) { return c.DeleteChannel(p) }, - "sendMessageToChannel": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + "sendMessageToChannel": func(c *TeamsJSONClient, p map[string]any) (any, error) { return c.SendMessageToChannel(p) }, - "sendReplyToChannel": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + "sendReplyToChannel": func(c *TeamsJSONClient, p map[string]any) (any, error) { return c.SendReplyToChannel(p) }, - "listMessagesInChannel": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + "listMessagesInChannel": func(c *TeamsJSONClient, p map[string]any) (any, error) { return c.ListMessagesInChannel(p) }, - "getMessageInChannel": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + "getMessageInChannel": func(c *TeamsJSONClient, p map[string]any) (any, error) { return c.GetMessageInChannel(p) }, - "listMessageRepliesInChannel": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + "listMessageRepliesInChannel": func(c *TeamsJSONClient, p map[string]any) (any, error) { return c.ListMessageRepliesInChannel(p) }, - "getMessageReplyInChannel": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + "getMessageReplyInChannel": func(c *TeamsJSONClient, p map[string]any) (any, error) { return c.GetMessageReplyInChannel(p) }, - "listChannelMembers": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + "listChannelMembers": func(c *TeamsJSONClient, p map[string]any) (any, error) { return c.ListChannelMembers(p) }, - "addMemberToChannel": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + "addMemberToChannel": func(c *TeamsJSONClient, p map[string]any) (any, error) { return c.AddMemberToChannel(p) }, - "removeMemberFromChannel": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + "removeMemberFromChannel": func(c *TeamsJSONClient, p map[string]any) (any, error) { return c.RemoveMemberFromChannel(p) }, - "updateMemberRoleInChannel": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + "updateMemberRoleInChannel": func(c *TeamsJSONClient, p map[string]any) (any, error) { return c.UpdateMemberInChannel(p) }, - "getMentionsInChannel": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + "getMentionsInChannel": func(c *TeamsJSONClient, p map[string]any) (any, error) { return c.GetMentionsInChannel(p) }, - "searchMessagesInChannel": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + "searchMessagesInChannel": func(c *TeamsJSONClient, p map[string]any) (any, error) { return c.SearchMessagesInChannel(p) }, // CHATS - "createOneOnOneChat": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + "createOneOnOneChat": func(c *TeamsJSONClient, p map[string]any) (any, error) { return c.CreateOneToOneChat(p) }, - "createGroupChat": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + "createGroupChat": func(c *TeamsJSONClient, p map[string]any) (any, error) { return c.CreateGroupChat(p) }, - "getChat": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + "getChat": func(c *TeamsJSONClient, p map[string]any) (any, error) { return c.GetChat(p) }, - "addMemberToGroupChat": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + "addMemberToGroupChat": func(c *TeamsJSONClient, p map[string]any) (any, error) { return c.AddMemberToGroupChat(p) }, - "removeMemberFromGroupChat": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + "removeMemberFromGroupChat": func(c *TeamsJSONClient, p map[string]any) (any, error) { return c.RemoveMemberFromGroupChat(p) }, - "listMembersInGroupChat": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + "listMembersInGroupChat": func(c *TeamsJSONClient, p map[string]any) (any, error) { return c.ListGroupChatMembers(p) }, - "updateGroupChatTopic": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + "updateGroupChatTopic": func(c *TeamsJSONClient, p map[string]any) (any, error) { return c.UpdateGroupChatTopic(p) }, - "listMessagesInChat": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + "listMessagesInChat": func(c *TeamsJSONClient, p map[string]any) (any, error) { return c.ListMessagesInChat(p) }, - "sendMessageInChat": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + "sendMessageInChat": func(c *TeamsJSONClient, p map[string]any) (any, error) { return c.SendMessageInChat(p) }, - "deleteMessageInChat": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + "deleteMessageInChat": func(c *TeamsJSONClient, p map[string]any) (any, error) { return c.DeleteMessageInChat(p) }, - "getMessageInChat": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + "getMessageInChat": func(c *TeamsJSONClient, p map[string]any) (any, error) { return c.GetMessageInChat(p) }, - "listMyChats": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + "listMyChats": func(c *TeamsJSONClient, p map[string]any) (any, error) { return c.ListMyChats(p) }, - "listMyChatMessages": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + "listMyChatMessages": func(c *TeamsJSONClient, p map[string]any) (any, error) { return c.ListMyChatMessages(p) }, - "listPinnedMessagesInChat": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + "listPinnedMessagesInChat": func(c *TeamsJSONClient, p map[string]any) (any, error) { return c.ListPinnedMessagesInChat(p) }, - "pinMessageInChat": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + "pinMessageInChat": func(c *TeamsJSONClient, p map[string]any) (any, error) { return c.PinMessageInChat(p) }, - "unpinMessageInChat": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + "unpinMessageInChat": func(c *TeamsJSONClient, p map[string]any) (any, error) { return c.UnpinMessageInChat(p) }, - "getMentionsInChat": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + "getMentionsInChat": func(c *TeamsJSONClient, p map[string]any) (any, error) { return c.GetMentionsInChat(p) }, - "searchMessagesInChat": func(c *TeamsJSONClient, p map[string]interface{}) (interface{}, error) { + "searchMessagesInChat": func(c *TeamsJSONClient, p map[string]any) (any, error) { return c.SearchMessagesInChat(p) }, } diff --git a/go/internal/json-client/requests-channels.go b/go/internal/json-client/requests-channels.go index 0f0803a..867c323 100644 --- a/go/internal/json-client/requests-channels.go +++ b/go/internal/json-client/requests-channels.go @@ -12,8 +12,8 @@ type listChannelsParams struct { TeamRef string `json:"teamRef"` } -func (c *TeamsJSONClient) ListChannels(p map[string]interface{}) (interface{}, error) { - return execute(p, func(params listChannelsParams) (interface{}, error) { +func (c *TeamsJSONClient) ListChannels(p map[string]any) (any, error) { + return execute(p, func(params listChannelsParams) (any, error) { return c.client.Channels.ListChannels(context.TODO(), params.TeamRef) }) } @@ -23,8 +23,8 @@ type baseChannelParams struct { ChannelRef string `json:"channelRef"` } -func (c *TeamsJSONClient) GetChannel(p map[string]interface{}) (interface{}, error) { - return execute(p, func(params baseChannelParams) (interface{}, error) { +func (c *TeamsJSONClient) GetChannel(p map[string]any) (any, error) { + return execute(p, func(params baseChannelParams) (any, error) { return c.client.Channels.Get(context.TODO(), params.TeamRef, params.ChannelRef) }) } @@ -34,8 +34,8 @@ type createChannelParams struct { Name string `json:"name"` } -func (c *TeamsJSONClient) CreateStandardChannel(p map[string]interface{}) (interface{}, error) { - return execute(p, func(params createChannelParams) (interface{}, error) { +func (c *TeamsJSONClient) CreateStandardChannel(p map[string]any) (any, error) { + return execute(p, func(params createChannelParams) (any, error) { return c.client.Channels.CreateStandardChannel(context.TODO(), params.TeamRef, params.Name) }) } @@ -47,14 +47,14 @@ type createPrivateChannelParams struct { OwnerRefs []string `json:"ownerRefs"` } -func (c *TeamsJSONClient) CreatePrivateChannel(p map[string]interface{}) (interface{}, error) { - return execute(p, func(params createPrivateChannelParams) (interface{}, error) { +func (c *TeamsJSONClient) CreatePrivateChannel(p map[string]any) (any, error) { + return execute(p, func(params createPrivateChannelParams) (any, error) { return c.client.Channels.CreatePrivateChannel(context.TODO(), params.TeamRef, params.Name, params.MemberRefs, params.OwnerRefs) }) } -func (c *TeamsJSONClient) DeleteChannel(p map[string]interface{}) (interface{}, error) { - return execute(p, func(params baseChannelParams) (interface{}, error) { +func (c *TeamsJSONClient) DeleteChannel(p map[string]any) (any, error) { + return execute(p, func(params baseChannelParams) (any, error) { err := c.client.Channels.Delete(context.TODO(), params.TeamRef, params.ChannelRef) return "deleted", err }) @@ -66,8 +66,8 @@ type sendMessageToChannelParams struct { Body decoders.MessageBodyDTO `json:"body"` } -func (c *TeamsJSONClient) SendMessageToChannel(p map[string]interface{}) (interface{}, error) { - return execute(p, func(params sendMessageToChannelParams) (interface{}, error) { +func (c *TeamsJSONClient) SendMessageToChannel(p map[string]any) (any, error) { + return execute(p, func(params sendMessageToChannelParams) (any, error) { body, err := decoders.DecodeParams[models.MessageBody](¶ms.Body) if err != nil { return nil, err @@ -83,8 +83,8 @@ type sendReplyToChannelParams struct { Body decoders.MessageBodyDTO `json:"body"` } -func (c *TeamsJSONClient) SendReplyToChannel(p map[string]interface{}) (interface{}, error) { - return execute(p, func(params sendReplyToChannelParams) (interface{}, error) { +func (c *TeamsJSONClient) SendReplyToChannel(p map[string]any) (any, error) { + return execute(p, func(params sendReplyToChannelParams) (any, error) { body, err := decoders.DecodeParams[models.MessageBody](¶ms.Body) if err != nil { return nil, err @@ -106,8 +106,8 @@ type listMessagesParams struct { NextLink decoders.NextLinkDTO `json:"nextLink"` } -func (c *TeamsJSONClient) ListMessagesInChannel(p map[string]interface{}) (interface{}, error) { - return execute(p, func(params listMessagesParams) (interface{}, error) { +func (c *TeamsJSONClient) ListMessagesInChannel(p map[string]any) (any, error) { + return execute(p, func(params listMessagesParams) (any, error) { options, err := decoders.DecodeParams[models.ListMessagesOptions](params.Options) if err != nil { return nil, err @@ -123,8 +123,8 @@ type getMessageParams struct { MessageID string `json:"messageId"` } -func (c *TeamsJSONClient) GetMessageInChannel(p map[string]interface{}) (interface{}, error) { - return execute(p, func(params getMessageParams) (interface{}, error) { +func (c *TeamsJSONClient) GetMessageInChannel(p map[string]any) (any, error) { + return execute(p, func(params getMessageParams) (any, error) { return c.client.Channels.GetMessage(context.TODO(), params.TeamRef, params.ChannelRef, params.MessageID) }) } @@ -138,8 +138,8 @@ type listRepliesParams struct { NextLink decoders.NextLinkDTO `json:"nextLink"` } -func (c *TeamsJSONClient) ListMessageRepliesInChannel(p map[string]interface{}) (interface{}, error) { - return execute(p, func(params listRepliesParams) (interface{}, error) { +func (c *TeamsJSONClient) ListMessageRepliesInChannel(p map[string]any) (any, error) { + return execute(p, func(params listRepliesParams) (any, error) { nextLink := decoders.GetNextLink(¶ms.NextLink) return c.client.Channels.ListReplies(context.TODO(), params.TeamRef, params.ChannelRef, params.MessageID, params.Top, params.IncludeSystem, nextLink) }) @@ -152,14 +152,14 @@ type getReplyParams struct { ReplyID string `json:"replyId"` } -func (c *TeamsJSONClient) GetMessageReplyInChannel(p map[string]interface{}) (interface{}, error) { - return execute(p, func(params getReplyParams) (interface{}, error) { +func (c *TeamsJSONClient) GetMessageReplyInChannel(p map[string]any) (any, error) { + return execute(p, func(params getReplyParams) (any, error) { return c.client.Channels.GetReply(context.TODO(), params.TeamRef, params.ChannelRef, params.MessageID, params.ReplyID) }) } -func (c *TeamsJSONClient) ListChannelMembers(p map[string]interface{}) (interface{}, error) { - return execute(p, func(params baseChannelParams) (interface{}, error) { +func (c *TeamsJSONClient) ListChannelMembers(p map[string]any) (any, error) { + return execute(p, func(params baseChannelParams) (any, error) { return c.client.Channels.ListMembers(context.TODO(), params.TeamRef, params.ChannelRef) }) } @@ -171,14 +171,14 @@ type addOrUpdateMemberToChannelParams struct { IsOwner bool `json:"isOwner"` } -func (c *TeamsJSONClient) AddMemberToChannel(p map[string]interface{}) (interface{}, error) { - return execute(p, func(params addOrUpdateMemberToChannelParams) (interface{}, error) { +func (c *TeamsJSONClient) AddMemberToChannel(p map[string]any) (any, error) { + return execute(p, func(params addOrUpdateMemberToChannelParams) (any, error) { return c.client.Channels.AddMember(context.TODO(), params.TeamRef, params.ChannelRef, params.UserRef, params.IsOwner) }) } -func (c *TeamsJSONClient) UpdateMemberInChannel(p map[string]interface{}) (interface{}, error) { - return execute(p, func(params addOrUpdateMemberToChannelParams) (interface{}, error) { +func (c *TeamsJSONClient) UpdateMemberInChannel(p map[string]any) (any, error) { + return execute(p, func(params addOrUpdateMemberToChannelParams) (any, error) { return c.client.Channels.UpdateMemberRoles(context.TODO(), params.TeamRef, params.ChannelRef, params.UserRef, params.IsOwner) }) } @@ -189,8 +189,8 @@ type removeMemberFromChannelParams struct { UserRef string `json:"userRef"` } -func (c *TeamsJSONClient) RemoveMemberFromChannel(p map[string]interface{}) (interface{}, error) { - return execute(p, func(params removeMemberFromChannelParams) (interface{}, error) { +func (c *TeamsJSONClient) RemoveMemberFromChannel(p map[string]any) (any, error) { + return execute(p, func(params removeMemberFromChannelParams) (any, error) { err := c.client.Channels.RemoveMember(context.TODO(), params.TeamRef, params.ChannelRef, params.UserRef) return "removed", err }) @@ -202,8 +202,8 @@ type getMentionsInChannelParams struct { RawMentions []string `json:"rawMentions"` } -func (c *TeamsJSONClient) GetMentionsInChannel(p map[string]interface{}) (interface{}, error) { - return execute(p, func(params getMentionsInChannelParams) (interface{}, error) { +func (c *TeamsJSONClient) GetMentionsInChannel(p map[string]any) (any, error) { + return execute(p, func(params getMentionsInChannelParams) (any, error) { return c.client.Channels.GetMentions(context.TODO(), params.TeamRef, params.ChannelRef, params.RawMentions) }) } @@ -215,8 +215,8 @@ type searchMessagesInChannelParams struct { SearchConfig decoders.SearchConfigDTO `json:"searchConfig"` } -func (c *TeamsJSONClient) SearchMessagesInChannel(p map[string]interface{}) (interface{}, error) { - return execute(p, func(params searchMessagesInChannelParams) (interface{}, error) { +func (c *TeamsJSONClient) SearchMessagesInChannel(p map[string]any) (any, error) { + return execute(p, func(params searchMessagesInChannelParams) (any, error) { searchOptions := decoders.DecodeSearchMessageOptions(¶ms.SearchOptions) searchConfig, err := decoders.DecodeParams[search.SearchConfig](¶ms.SearchConfig) if err != nil { diff --git a/go/internal/json-client/requests-chats.go b/go/internal/json-client/requests-chats.go index eda2bb6..34e9e2b 100644 --- a/go/internal/json-client/requests-chats.go +++ b/go/internal/json-client/requests-chats.go @@ -13,8 +13,8 @@ type createOneToOneChatParams struct { RecipientRef string `json:"recipientRef"` } -func (c *TeamsJSONClient) CreateOneToOneChat(p map[string]interface{}) (interface{}, error) { - return execute(p, func(params createOneToOneChatParams) (interface{}, error) { +func (c *TeamsJSONClient) CreateOneToOneChat(p map[string]any) (any, error) { + return execute(p, func(params createOneToOneChatParams) (any, error) { return c.client.Chats.CreateOneOnOne(context.TODO(), params.RecipientRef) }) } @@ -25,8 +25,8 @@ type createGroupChatParams struct { IncludeMe bool `json:"includeMe"` } -func (c *TeamsJSONClient) CreateGroupChat(p map[string]interface{}) (interface{}, error) { - return execute(p, func(params createGroupChatParams) (interface{}, error) { +func (c *TeamsJSONClient) CreateGroupChat(p map[string]any) (any, error) { + return execute(p, func(params createGroupChatParams) (any, error) { return c.client.Chats.CreateGroup(context.TODO(), params.RecipientRefs, params.Topic, params.IncludeMe) }) } @@ -35,8 +35,8 @@ type baseChatParams struct { ChatRef decoders.ChatRefDTO `json:"chatRef"` } -func (c *TeamsJSONClient) GetChat(p map[string]interface{}) (interface{}, error) { - return execute(p, func(params baseChatParams) (interface{}, error) { +func (c *TeamsJSONClient) GetChat(p map[string]any) (any, error) { + return execute(p, func(params baseChatParams) (any, error) { chatRef := decoders.GetChatRef(params.ChatRef) return c.client.Chats.GetChat(context.TODO(), chatRef) }) @@ -47,14 +47,14 @@ type groupChatMemberParams struct { UserRef string `json:"userRef"` } -func (c *TeamsJSONClient) AddMemberToGroupChat(p map[string]interface{}) (interface{}, error) { - return execute(p, func(params groupChatMemberParams) (interface{}, error) { +func (c *TeamsJSONClient) AddMemberToGroupChat(p map[string]any) (any, error) { + return execute(p, func(params groupChatMemberParams) (any, error) { return c.client.Chats.AddMemberToGroupChat(context.TODO(), chats.GroupChatRef{Ref: params.GroupChatRef}, params.UserRef) }) } -func (c *TeamsJSONClient) RemoveMemberFromGroupChat(p map[string]interface{}) (interface{}, error) { - return execute(p, func(params groupChatMemberParams) (interface{}, error) { +func (c *TeamsJSONClient) RemoveMemberFromGroupChat(p map[string]any) (any, error) { + return execute(p, func(params groupChatMemberParams) (any, error) { err := c.client.Chats.RemoveMemberFromGroupChat(context.TODO(), chats.GroupChatRef{Ref: params.GroupChatRef}, params.UserRef) return "removed", err }) @@ -64,8 +64,8 @@ type listMembersInChatParams struct { GroupChatRef string `json:"groupChatRef"` } -func (c *TeamsJSONClient) ListGroupChatMembers(p map[string]interface{}) (interface{}, error) { - return execute(p, func(params listMembersInChatParams) (interface{}, error) { +func (c *TeamsJSONClient) ListGroupChatMembers(p map[string]any) (any, error) { + return execute(p, func(params listMembersInChatParams) (any, error) { return c.client.Chats.ListGroupChatMembers(context.TODO(), chats.GroupChatRef{Ref: params.GroupChatRef}) }) } @@ -75,8 +75,8 @@ type updateGroupChatTopicParams struct { Topic string `json:"topic"` } -func (c *TeamsJSONClient) UpdateGroupChatTopic(p map[string]interface{}) (interface{}, error) { - return execute(p, func(params updateGroupChatTopicParams) (interface{}, error) { +func (c *TeamsJSONClient) UpdateGroupChatTopic(p map[string]any) (any, error) { + return execute(p, func(params updateGroupChatTopicParams) (any, error) { return c.client.Chats.UpdateGroupChatTopic(context.TODO(), chats.GroupChatRef{Ref: params.GroupChatRef}, params.Topic) }) } @@ -87,8 +87,8 @@ type listMessagesInChatParams struct { NextLink decoders.NextLinkDTO `json:"nextLink"` } -func (c *TeamsJSONClient) ListMessagesInChat(p map[string]interface{}) (interface{}, error) { - return execute(p, func(params listMessagesInChatParams) (interface{}, error) { +func (c *TeamsJSONClient) ListMessagesInChat(p map[string]any) (any, error) { + return execute(p, func(params listMessagesInChatParams) (any, error) { chatRef := decoders.GetChatRef(params.ChatRef) nextLink := decoders.GetNextLink(¶ms.NextLink) return c.client.Chats.ListMessages(context.TODO(), chatRef, params.IncludeSystem, nextLink) @@ -100,8 +100,8 @@ type sendMessageInChatParams struct { Body decoders.MessageBodyDTO `json:"body"` } -func (c *TeamsJSONClient) SendMessageInChat(p map[string]interface{}) (interface{}, error) { - return execute(p, func(params sendMessageInChatParams) (interface{}, error) { +func (c *TeamsJSONClient) SendMessageInChat(p map[string]any) (any, error) { + return execute(p, func(params sendMessageInChatParams) (any, error) { chatRef := decoders.GetChatRef(params.ChatRef) body, err := decoders.DecodeParams[models.MessageBody](params.Body) if err != nil { @@ -116,16 +116,16 @@ type messageInChatParams struct { MessageID string `json:"messageId"` } -func (c *TeamsJSONClient) DeleteMessageInChat(p map[string]interface{}) (interface{}, error) { - return execute(p, func(params messageInChatParams) (interface{}, error) { +func (c *TeamsJSONClient) DeleteMessageInChat(p map[string]any) (any, error) { + return execute(p, func(params messageInChatParams) (any, error) { chatRef := decoders.GetChatRef(params.ChatRef) err := c.client.Chats.DeleteMessage(context.TODO(), chatRef, params.MessageID) return "deleted", err }) } -func (c *TeamsJSONClient) GetMessageInChat(p map[string]interface{}) (interface{}, error) { - return execute(p, func(params messageInChatParams) (interface{}, error) { +func (c *TeamsJSONClient) GetMessageInChat(p map[string]any) (any, error) { + return execute(p, func(params messageInChatParams) (any, error) { chatRef := decoders.GetChatRef(params.ChatRef) return c.client.Chats.GetMessage(context.TODO(), chatRef, params.MessageID) }) @@ -135,8 +135,8 @@ type ListMyChatsParams struct { ChatType string `json:"chatType"` } -func (c *TeamsJSONClient) ListMyChats(p map[string]interface{}) (interface{}, error) { - return execute(p, func(params ListMyChatsParams) (interface{}, error) { +func (c *TeamsJSONClient) ListMyChats(p map[string]any) (any, error) { + return execute(p, func(params ListMyChatsParams) (any, error) { var chatType models.ChatType if params.ChatType != "" { chatType = models.ChatType(params.ChatType) @@ -151,8 +151,8 @@ type listChatMessagesParams struct { Top *int32 `json:"top"` } -func (c *TeamsJSONClient) ListMyChatMessages(p map[string]interface{}) (interface{}, error) { - return execute(p, func(params listChatMessagesParams) (interface{}, error) { +func (c *TeamsJSONClient) ListMyChatMessages(p map[string]any) (any, error) { + return execute(p, func(params listChatMessagesParams) (any, error) { parsedStartTime, parsedEndTime, err := decoders.DecodeTimeRange(params.StartTime, params.EndTime) if err != nil { return nil, err @@ -161,23 +161,23 @@ func (c *TeamsJSONClient) ListMyChatMessages(p map[string]interface{}) (interfac }) } -func (c *TeamsJSONClient) ListPinnedMessagesInChat(p map[string]interface{}) (interface{}, error) { - return execute(p, func(params baseChatParams) (interface{}, error) { +func (c *TeamsJSONClient) ListPinnedMessagesInChat(p map[string]any) (any, error) { + return execute(p, func(params baseChatParams) (any, error) { chatRef := decoders.GetChatRef(params.ChatRef) return c.client.Chats.ListPinnedMessages(context.TODO(), chatRef) }) } -func (c *TeamsJSONClient) PinMessageInChat(p map[string]interface{}) (interface{}, error) { - return execute(p, func(params messageInChatParams) (interface{}, error) { +func (c *TeamsJSONClient) PinMessageInChat(p map[string]any) (any, error) { + return execute(p, func(params messageInChatParams) (any, error) { chatRef := decoders.GetChatRef(params.ChatRef) err := c.client.Chats.PinMessage(context.TODO(), chatRef, params.MessageID) return "pinned", err }) } -func (c *TeamsJSONClient) UnpinMessageInChat(p map[string]interface{}) (interface{}, error) { - return execute(p, func(params messageInChatParams) (interface{}, error) { +func (c *TeamsJSONClient) UnpinMessageInChat(p map[string]any) (any, error) { + return execute(p, func(params messageInChatParams) (any, error) { chatRef := decoders.GetChatRef(params.ChatRef) err := c.client.Chats.UnpinMessage(context.TODO(), chatRef, params.MessageID) return "unpinned", err @@ -189,8 +189,8 @@ type mentionInChatParams struct { RawMentions []string `json:"rawMentions"` } -func (c *TeamsJSONClient) GetMentionsInChat(p map[string]interface{}) (interface{}, error) { - return execute(p, func(params mentionInChatParams) (interface{}, error) { +func (c *TeamsJSONClient) GetMentionsInChat(p map[string]any) (any, error) { + return execute(p, func(params mentionInChatParams) (any, error) { chatRef := decoders.GetChatRef(params.ChatRef) return c.client.Chats.GetMentions(context.TODO(), chatRef, params.RawMentions) }) @@ -202,8 +202,8 @@ type searchMessagesInChatParams struct { SearchConfig decoders.SearchConfigDTO `json:"searchConfig"` } -func (c *TeamsJSONClient) SearchMessagesInChat(p map[string]interface{}) (interface{}, error) { - return execute(p, func(params searchMessagesInChatParams) (interface{}, error) { +func (c *TeamsJSONClient) SearchMessagesInChat(p map[string]any) (any, error) { + return execute(p, func(params searchMessagesInChatParams) (any, error) { chatRef := decoders.GetChatRef(params.ChatRef) searchOptions := decoders.DecodeSearchMessageOptions(¶ms.SearchOptions) searchConfig, err := decoders.DecodeParams[search.SearchConfig](¶ms.SearchConfig) diff --git a/go/internal/json-client/requests-teams.go b/go/internal/json-client/requests-teams.go index 49d267c..22022ac 100644 --- a/go/internal/json-client/requests-teams.go +++ b/go/internal/json-client/requests-teams.go @@ -10,13 +10,13 @@ type getTeamParams struct { TeamRef string `json:"teamRef"` } -func (c *TeamsJSONClient) GetTeam(p map[string]interface{}) (interface{}, error) { - return execute(p, func(params getTeamParams) (interface{}, error) { +func (c *TeamsJSONClient) GetTeam(p map[string]any) (any, error) { + return execute(p, func(params getTeamParams) (any, error) { return c.client.Teams.Get(context.TODO(), params.TeamRef) }) } -func (c *TeamsJSONClient) ListMyJoined() (interface{}, error) { +func (c *TeamsJSONClient) ListMyJoined() (any, error) { return c.client.Teams.ListMyJoined(context.TODO()) } @@ -25,8 +25,8 @@ type updateTeamParams struct { TeamUpdate decoders.UpdateTeamDTO `json:"team"` } -func (c *TeamsJSONClient) UpdateTeam(p map[string]interface{}) (interface{}, error) { - return execute(p, func(params updateTeamParams) (interface{}, error) { +func (c *TeamsJSONClient) UpdateTeam(p map[string]any) (any, error) { + return execute(p, func(params updateTeamParams) (any, error) { updateTeam := decoders.GetUpdateTeam(¶ms.TeamUpdate) return c.client.Teams.UpdateTeam(context.TODO(), params.TeamRef, &updateTeam) }) @@ -38,8 +38,8 @@ type createViaGroupParams struct { Visibility string `json:"visibility"` } -func (c *TeamsJSONClient) CreateTeamViaGroup(p map[string]interface{}) (interface{}, error) { - return execute(p, func(params createViaGroupParams) (interface{}, error) { +func (c *TeamsJSONClient) CreateTeamViaGroup(p map[string]any) (any, error) { + return execute(p, func(params createViaGroupParams) (any, error) { return c.client.Teams.CreateViaGroup(context.TODO(), params.DisplayName, params.MailNickname, params.Visibility) }) } @@ -53,8 +53,8 @@ type createFromTemplateParams struct { IncludeMe bool `json:"includeMe"` } -func (c *TeamsJSONClient) CreateTeamFromTemplate(p map[string]interface{}) (interface{}, error) { - return execute(p, func(params createFromTemplateParams) (interface{}, error) { +func (c *TeamsJSONClient) CreateTeamFromTemplate(p map[string]any) (any, error) { + return execute(p, func(params createFromTemplateParams) (any, error) { return c.client.Teams.CreateFromTemplate(context.TODO(), params.DisplayName, params.Description, params.Owners, params.Members, params.Visibility, params.IncludeMe) }) } @@ -64,8 +64,8 @@ type archiveTeamParams struct { SpoReadOnlyFromMembers *bool `json:"spoReadOnlyFromMembers,omitempty"` } -func (c *TeamsJSONClient) ArchiveTeam(p map[string]interface{}) (interface{}, error) { - return execute(p, func(params archiveTeamParams) (interface{}, error) { +func (c *TeamsJSONClient) ArchiveTeam(p map[string]any) (any, error) { + return execute(p, func(params archiveTeamParams) (any, error) { err := c.client.Teams.Archive(context.TODO(), params.TeamRef, params.SpoReadOnlyFromMembers) return "archived", err }) @@ -75,8 +75,8 @@ type unarchiveTeamParams struct { TeamRef string `json:"teamRef"` } -func (c *TeamsJSONClient) UnarchiveTeam(p map[string]interface{}) (interface{}, error) { - return execute(p, func(params unarchiveTeamParams) (interface{}, error) { +func (c *TeamsJSONClient) UnarchiveTeam(p map[string]any) (any, error) { + return execute(p, func(params unarchiveTeamParams) (any, error) { err := c.client.Teams.Unarchive(context.TODO(), params.TeamRef) return "unarchived", err }) @@ -86,8 +86,8 @@ type deleteTeamParams struct { TeamRef string `json:"teamRef"` } -func (c *TeamsJSONClient) DeleteTeam(p map[string]interface{}) (interface{}, error) { - return execute(p, func(params deleteTeamParams) (interface{}, error) { +func (c *TeamsJSONClient) DeleteTeam(p map[string]any) (any, error) { + return execute(p, func(params deleteTeamParams) (any, error) { err := c.client.Teams.Delete(context.TODO(), params.TeamRef) return "deleted", err }) @@ -97,8 +97,8 @@ type restoreTeamParams struct { DeletedGroupID string `json:"deletedGroupId"` } -func (c *TeamsJSONClient) RestoreDeletedTeam(p map[string]interface{}) (interface{}, error) { - return execute(p, func(params restoreTeamParams) (interface{}, error) { +func (c *TeamsJSONClient) RestoreDeletedTeam(p map[string]any) (any, error) { + return execute(p, func(params restoreTeamParams) (any, error) { return c.client.Teams.RestoreDeleted(context.TODO(), params.DeletedGroupID) }) } diff --git a/go/internal/json-client/teams-json-client.go b/go/internal/json-client/teams-json-client.go index 20b87a8..7ef7360 100644 --- a/go/internal/json-client/teams-json-client.go +++ b/go/internal/json-client/teams-json-client.go @@ -10,7 +10,7 @@ type TeamsJSONClient struct { client *lib.Client } -func execute[T any](p map[string]interface{}, fn func(params T) (interface{}, error)) (interface{}, error) { +func execute[T any](p map[string]any, fn func(params T) (any, error)) (any, error) { params, err := decoders.DecodeParams[T](p) if err != nil { return nil, err diff --git a/go/internal/json-model/json-model.go b/go/internal/json-model/json-model.go index 750604a..05f24bc 100644 --- a/go/internal/json-model/json-model.go +++ b/go/internal/json-model/json-model.go @@ -10,7 +10,7 @@ type Request struct { Type string `json:"type"` Method string `json:"method,omitempty"` Config Config `json:"config,omitempty"` - Params map[string]interface{} `json:"params,omitempty"` + Params map[string]any `json:"params,omitempty"` } type Config struct { @@ -83,7 +83,7 @@ func (acm AuthConfigMap) ToAuthConfig() (*config.AuthConfig, error) { }, nil } -func ParseCachePath(value interface{}) (*string, error) { +func ParseCachePath(value any) (*string, error) { path, ok := value.(string) if !ok || path == "" { return nil, fmt.Errorf("cachePath must be a non-empty string") @@ -91,7 +91,7 @@ func ParseCachePath(value interface{}) (*string, error) { return &path, nil } -func ParseCacheMode(value interface{}) (config.CacheMode, error) { +func ParseCacheMode(value any) (config.CacheMode, error) { switch value { case "DISABLED": return config.CacheDisabled, nil From 4444fb53aa2495556286630354280fec3ec3dbc2 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Tue, 13 Jan 2026 18:47:11 +0100 Subject: [PATCH 97/99] refactor: passed context.background from main --- go/bridge/main.go | 3 +- go/internal/json-client/request-map.go | 182 ++++++++++--------- go/internal/json-client/requests-channels.go | 68 +++---- go/internal/json-client/requests-chats.go | 72 ++++---- go/internal/json-client/requests-teams.go | 36 ++-- 5 files changed, 183 insertions(+), 178 deletions(-) diff --git a/go/bridge/main.go b/go/bridge/main.go index d598b9e..128be65 100644 --- a/go/bridge/main.go +++ b/go/bridge/main.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "os" + "context" jsonClientLib "github.com/pzsp-teams/lib-python/internal/json-client" jsonModel "github.com/pzsp-teams/lib-python/internal/json-model" @@ -58,7 +59,7 @@ func main() { continue } - result, err := handler(client, req.Params) + result, err := handler(client, context.Background(), req.Params) if err != nil { respondError(writer, err) } diff --git a/go/internal/json-client/request-map.go b/go/internal/json-client/request-map.go index 229b095..ffb4a1e 100644 --- a/go/internal/json-client/request-map.go +++ b/go/internal/json-client/request-map.go @@ -1,185 +1,189 @@ package jsonclient -type HandlerFunc func(c *TeamsJSONClient, p map[string]any) (any, error) +import ( + "context" +) + +type HandlerFunc func(c *TeamsJSONClient, ctx context.Context, p map[string]any) (any, error) var Handlers = map[string]HandlerFunc{ // TEAMS - "getTeam": func(c *TeamsJSONClient, p map[string]any) (any, error) { - return c.GetTeam(p) + "getTeam": func(c *TeamsJSONClient, ctx context.Context, p map[string]any) (any, error) { + return c.GetTeam(ctx, p) }, - "updateTeam": func(c *TeamsJSONClient, p map[string]any) (any, error) { - return c.UpdateTeam(p) + "updateTeam": func(c *TeamsJSONClient, ctx context.Context, p map[string]any) (any, error) { + return c.UpdateTeam(ctx, p) }, - "listMyJoined": func(c *TeamsJSONClient, _ map[string]any) (any, error) { - return c.ListMyJoined() + "listMyJoined": func(c *TeamsJSONClient, ctx context.Context, _ map[string]any) (any, error) { + return c.ListMyJoined(ctx) }, - "createTeamViaGroup": func(c *TeamsJSONClient, p map[string]any) (any, error) { - return c.CreateTeamViaGroup(p) + "createTeamViaGroup": func(c *TeamsJSONClient, ctx context.Context, p map[string]any) (any, error) { + return c.CreateTeamViaGroup(ctx, p) }, - "createTeamFromTemplate": func(c *TeamsJSONClient, p map[string]any) (any, error) { - return c.CreateTeamFromTemplate(p) + "createTeamFromTemplate": func(c *TeamsJSONClient, ctx context.Context, p map[string]any) (any, error) { + return c.CreateTeamFromTemplate(ctx, p) }, - "archiveTeam": func(c *TeamsJSONClient, p map[string]any) (any, error) { - return c.ArchiveTeam(p) + "archiveTeam": func(c *TeamsJSONClient, ctx context.Context, p map[string]any) (any, error) { + return c.ArchiveTeam(ctx, p) }, - "unarchiveTeam": func(c *TeamsJSONClient, p map[string]any) (any, error) { - return c.UnarchiveTeam(p) + "unarchiveTeam": func(c *TeamsJSONClient, ctx context.Context, p map[string]any) (any, error) { + return c.UnarchiveTeam(ctx, p) }, - "deleteTeam": func(c *TeamsJSONClient, p map[string]any) (any, error) { - return c.DeleteTeam(p) + "deleteTeam": func(c *TeamsJSONClient, ctx context.Context, p map[string]any) (any, error) { + return c.DeleteTeam(ctx, p) }, - "restoreDeletedTeam": func(c *TeamsJSONClient, p map[string]any) (any, error) { - return c.RestoreDeletedTeam(p) + "restoreDeletedTeam": func(c *TeamsJSONClient, ctx context.Context, p map[string]any) (any, error) { + return c.RestoreDeletedTeam(ctx, p) }, // CHANNELS - "listChannels": func(c *TeamsJSONClient, p map[string]any) (any, error) { - return c.ListChannels(p) + "listChannels": func(c *TeamsJSONClient, ctx context.Context, p map[string]any) (any, error) { + return c.ListChannels(ctx, p) }, - "getChannel": func(c *TeamsJSONClient, p map[string]any) (any, error) { - return c.GetChannel(p) + "getChannel": func(c *TeamsJSONClient, ctx context.Context, p map[string]any) (any, error) { + return c.GetChannel(ctx, p) }, - "createStandardChannel": func(c *TeamsJSONClient, p map[string]any) (any, error) { - return c.CreateStandardChannel(p) + "createStandardChannel": func(c *TeamsJSONClient, ctx context.Context, p map[string]any) (any, error) { + return c.CreateStandardChannel(ctx, p) }, - "createPrivateChannel": func(c *TeamsJSONClient, p map[string]any) (any, error) { - return c.CreatePrivateChannel(p) + "createPrivateChannel": func(c *TeamsJSONClient, ctx context.Context, p map[string]any) (any, error) { + return c.CreatePrivateChannel(ctx, p) }, - "deleteChannel": func(c *TeamsJSONClient, p map[string]any) (any, error) { - return c.DeleteChannel(p) + "deleteChannel": func(c *TeamsJSONClient, ctx context.Context, p map[string]any) (any, error) { + return c.DeleteChannel(ctx, p) }, - "sendMessageToChannel": func(c *TeamsJSONClient, p map[string]any) (any, error) { - return c.SendMessageToChannel(p) + "sendMessageToChannel": func(c *TeamsJSONClient, ctx context.Context, p map[string]any) (any, error) { + return c.SendMessageToChannel(ctx, p) }, - "sendReplyToChannel": func(c *TeamsJSONClient, p map[string]any) (any, error) { - return c.SendReplyToChannel(p) + "sendReplyToChannel": func(c *TeamsJSONClient, ctx context.Context, p map[string]any) (any, error) { + return c.SendReplyToChannel(ctx, p) }, - "listMessagesInChannel": func(c *TeamsJSONClient, p map[string]any) (any, error) { - return c.ListMessagesInChannel(p) + "listMessagesInChannel": func(c *TeamsJSONClient, ctx context.Context, p map[string]any) (any, error) { + return c.ListMessagesInChannel(ctx, p) }, - "getMessageInChannel": func(c *TeamsJSONClient, p map[string]any) (any, error) { - return c.GetMessageInChannel(p) + "getMessageInChannel": func(c *TeamsJSONClient, ctx context.Context, p map[string]any) (any, error) { + return c.GetMessageInChannel(ctx, p) }, - "listMessageRepliesInChannel": func(c *TeamsJSONClient, p map[string]any) (any, error) { - return c.ListMessageRepliesInChannel(p) + "listMessageRepliesInChannel": func(c *TeamsJSONClient, ctx context.Context, p map[string]any) (any, error) { + return c.ListMessageRepliesInChannel(ctx, p) }, - "getMessageReplyInChannel": func(c *TeamsJSONClient, p map[string]any) (any, error) { - return c.GetMessageReplyInChannel(p) + "getMessageReplyInChannel": func(c *TeamsJSONClient, ctx context.Context, p map[string]any) (any, error) { + return c.GetMessageReplyInChannel(ctx, p) }, - "listChannelMembers": func(c *TeamsJSONClient, p map[string]any) (any, error) { - return c.ListChannelMembers(p) + "listChannelMembers": func(c *TeamsJSONClient, ctx context.Context, p map[string]any) (any, error) { + return c.ListChannelMembers(ctx, p) }, - "addMemberToChannel": func(c *TeamsJSONClient, p map[string]any) (any, error) { - return c.AddMemberToChannel(p) + "addMemberToChannel": func(c *TeamsJSONClient, ctx context.Context, p map[string]any) (any, error) { + return c.AddMemberToChannel(ctx, p) }, - "removeMemberFromChannel": func(c *TeamsJSONClient, p map[string]any) (any, error) { - return c.RemoveMemberFromChannel(p) + "removeMemberFromChannel": func(c *TeamsJSONClient, ctx context.Context, p map[string]any) (any, error) { + return c.RemoveMemberFromChannel(ctx, p) }, - "updateMemberRoleInChannel": func(c *TeamsJSONClient, p map[string]any) (any, error) { - return c.UpdateMemberInChannel(p) + "updateMemberRoleInChannel": func(c *TeamsJSONClient, ctx context.Context, p map[string]any) (any, error) { + return c.UpdateMemberInChannel(ctx, p) }, - "getMentionsInChannel": func(c *TeamsJSONClient, p map[string]any) (any, error) { - return c.GetMentionsInChannel(p) + "getMentionsInChannel": func(c *TeamsJSONClient, ctx context.Context, p map[string]any) (any, error) { + return c.GetMentionsInChannel(ctx, p) }, - "searchMessagesInChannel": func(c *TeamsJSONClient, p map[string]any) (any, error) { - return c.SearchMessagesInChannel(p) + "searchMessagesInChannel": func(c *TeamsJSONClient, ctx context.Context, p map[string]any) (any, error) { + return c.SearchMessagesInChannel(ctx, p) }, // CHATS - "createOneOnOneChat": func(c *TeamsJSONClient, p map[string]any) (any, error) { - return c.CreateOneToOneChat(p) + "createOneOnOneChat": func(c *TeamsJSONClient, ctx context.Context, p map[string]any) (any, error) { + return c.CreateOneToOneChat(ctx, p) }, - "createGroupChat": func(c *TeamsJSONClient, p map[string]any) (any, error) { - return c.CreateGroupChat(p) + "createGroupChat": func(c *TeamsJSONClient, ctx context.Context, p map[string]any) (any, error) { + return c.CreateGroupChat(ctx, p) }, - "getChat": func(c *TeamsJSONClient, p map[string]any) (any, error) { - return c.GetChat(p) + "getChat": func(c *TeamsJSONClient, ctx context.Context, p map[string]any) (any, error) { + return c.GetChat(ctx, p) }, - "addMemberToGroupChat": func(c *TeamsJSONClient, p map[string]any) (any, error) { - return c.AddMemberToGroupChat(p) + "addMemberToGroupChat": func(c *TeamsJSONClient, ctx context.Context, p map[string]any) (any, error) { + return c.AddMemberToGroupChat(ctx, p) }, - "removeMemberFromGroupChat": func(c *TeamsJSONClient, p map[string]any) (any, error) { - return c.RemoveMemberFromGroupChat(p) + "removeMemberFromGroupChat": func(c *TeamsJSONClient, ctx context.Context, p map[string]any) (any, error) { + return c.RemoveMemberFromGroupChat(ctx, p) }, - "listMembersInGroupChat": func(c *TeamsJSONClient, p map[string]any) (any, error) { - return c.ListGroupChatMembers(p) + "listMembersInGroupChat": func(c *TeamsJSONClient, ctx context.Context, p map[string]any) (any, error) { + return c.ListGroupChatMembers(ctx, p) }, - "updateGroupChatTopic": func(c *TeamsJSONClient, p map[string]any) (any, error) { - return c.UpdateGroupChatTopic(p) + "updateGroupChatTopic": func(c *TeamsJSONClient, ctx context.Context, p map[string]any) (any, error) { + return c.UpdateGroupChatTopic(ctx, p) }, - "listMessagesInChat": func(c *TeamsJSONClient, p map[string]any) (any, error) { - return c.ListMessagesInChat(p) + "listMessagesInChat": func(c *TeamsJSONClient, ctx context.Context, p map[string]any) (any, error) { + return c.ListMessagesInChat(ctx, p) }, - "sendMessageInChat": func(c *TeamsJSONClient, p map[string]any) (any, error) { - return c.SendMessageInChat(p) + "sendMessageInChat": func(c *TeamsJSONClient, ctx context.Context, p map[string]any) (any, error) { + return c.SendMessageInChat(ctx, p) }, - "deleteMessageInChat": func(c *TeamsJSONClient, p map[string]any) (any, error) { - return c.DeleteMessageInChat(p) + "deleteMessageInChat": func(c *TeamsJSONClient, ctx context.Context, p map[string]any) (any, error) { + return c.DeleteMessageInChat(ctx, p) }, - "getMessageInChat": func(c *TeamsJSONClient, p map[string]any) (any, error) { - return c.GetMessageInChat(p) + "getMessageInChat": func(c *TeamsJSONClient, ctx context.Context, p map[string]any) (any, error) { + return c.GetMessageInChat(ctx, p) }, - "listMyChats": func(c *TeamsJSONClient, p map[string]any) (any, error) { - return c.ListMyChats(p) + "listMyChats": func(c *TeamsJSONClient, ctx context.Context, p map[string]any) (any, error) { + return c.ListMyChats(ctx, p) }, - "listMyChatMessages": func(c *TeamsJSONClient, p map[string]any) (any, error) { - return c.ListMyChatMessages(p) + "listMyChatMessages": func(c *TeamsJSONClient, ctx context.Context, p map[string]any) (any, error) { + return c.ListMyChatMessages(ctx, p) }, - "listPinnedMessagesInChat": func(c *TeamsJSONClient, p map[string]any) (any, error) { - return c.ListPinnedMessagesInChat(p) + "listPinnedMessagesInChat": func(c *TeamsJSONClient, ctx context.Context, p map[string]any) (any, error) { + return c.ListPinnedMessagesInChat(ctx, p) }, - "pinMessageInChat": func(c *TeamsJSONClient, p map[string]any) (any, error) { - return c.PinMessageInChat(p) + "pinMessageInChat": func(c *TeamsJSONClient, ctx context.Context, p map[string]any) (any, error) { + return c.PinMessageInChat(ctx, p) }, - "unpinMessageInChat": func(c *TeamsJSONClient, p map[string]any) (any, error) { - return c.UnpinMessageInChat(p) + "unpinMessageInChat": func(c *TeamsJSONClient, ctx context.Context, p map[string]any) (any, error) { + return c.UnpinMessageInChat(ctx, p) }, - "getMentionsInChat": func(c *TeamsJSONClient, p map[string]any) (any, error) { - return c.GetMentionsInChat(p) + "getMentionsInChat": func(c *TeamsJSONClient, ctx context.Context, p map[string]any) (any, error) { + return c.GetMentionsInChat(ctx, p) }, - "searchMessagesInChat": func(c *TeamsJSONClient, p map[string]any) (any, error) { - return c.SearchMessagesInChat(p) + "searchMessagesInChat": func(c *TeamsJSONClient, ctx context.Context, p map[string]any) (any, error) { + return c.SearchMessagesInChat(ctx, p) }, } diff --git a/go/internal/json-client/requests-channels.go b/go/internal/json-client/requests-channels.go index 867c323..1f3a2fa 100644 --- a/go/internal/json-client/requests-channels.go +++ b/go/internal/json-client/requests-channels.go @@ -12,9 +12,9 @@ type listChannelsParams struct { TeamRef string `json:"teamRef"` } -func (c *TeamsJSONClient) ListChannels(p map[string]any) (any, error) { +func (c *TeamsJSONClient) ListChannels(ctx context.Context, p map[string]any) (any, error) { return execute(p, func(params listChannelsParams) (any, error) { - return c.client.Channels.ListChannels(context.TODO(), params.TeamRef) + return c.client.Channels.ListChannels(ctx, params.TeamRef) }) } @@ -23,9 +23,9 @@ type baseChannelParams struct { ChannelRef string `json:"channelRef"` } -func (c *TeamsJSONClient) GetChannel(p map[string]any) (any, error) { +func (c *TeamsJSONClient) GetChannel(ctx context.Context, p map[string]any) (any, error) { return execute(p, func(params baseChannelParams) (any, error) { - return c.client.Channels.Get(context.TODO(), params.TeamRef, params.ChannelRef) + return c.client.Channels.Get(ctx, params.TeamRef, params.ChannelRef) }) } @@ -34,9 +34,9 @@ type createChannelParams struct { Name string `json:"name"` } -func (c *TeamsJSONClient) CreateStandardChannel(p map[string]any) (any, error) { +func (c *TeamsJSONClient) CreateStandardChannel(ctx context.Context, p map[string]any) (any, error) { return execute(p, func(params createChannelParams) (any, error) { - return c.client.Channels.CreateStandardChannel(context.TODO(), params.TeamRef, params.Name) + return c.client.Channels.CreateStandardChannel(ctx, params.TeamRef, params.Name) }) } @@ -47,15 +47,15 @@ type createPrivateChannelParams struct { OwnerRefs []string `json:"ownerRefs"` } -func (c *TeamsJSONClient) CreatePrivateChannel(p map[string]any) (any, error) { +func (c *TeamsJSONClient) CreatePrivateChannel(ctx context.Context, p map[string]any) (any, error) { return execute(p, func(params createPrivateChannelParams) (any, error) { - return c.client.Channels.CreatePrivateChannel(context.TODO(), params.TeamRef, params.Name, params.MemberRefs, params.OwnerRefs) + return c.client.Channels.CreatePrivateChannel(ctx, params.TeamRef, params.Name, params.MemberRefs, params.OwnerRefs) }) } -func (c *TeamsJSONClient) DeleteChannel(p map[string]any) (any, error) { +func (c *TeamsJSONClient) DeleteChannel(ctx context.Context, p map[string]any) (any, error) { return execute(p, func(params baseChannelParams) (any, error) { - err := c.client.Channels.Delete(context.TODO(), params.TeamRef, params.ChannelRef) + err := c.client.Channels.Delete(ctx, params.TeamRef, params.ChannelRef) return "deleted", err }) } @@ -66,13 +66,13 @@ type sendMessageToChannelParams struct { Body decoders.MessageBodyDTO `json:"body"` } -func (c *TeamsJSONClient) SendMessageToChannel(p map[string]any) (any, error) { +func (c *TeamsJSONClient) SendMessageToChannel(ctx context.Context, p map[string]any) (any, error) { return execute(p, func(params sendMessageToChannelParams) (any, error) { body, err := decoders.DecodeParams[models.MessageBody](¶ms.Body) if err != nil { return nil, err } - return c.client.Channels.SendMessage(context.TODO(), params.TeamRef, params.ChannelRef, *body) + return c.client.Channels.SendMessage(ctx, params.TeamRef, params.ChannelRef, *body) }) } @@ -83,13 +83,13 @@ type sendReplyToChannelParams struct { Body decoders.MessageBodyDTO `json:"body"` } -func (c *TeamsJSONClient) SendReplyToChannel(p map[string]any) (any, error) { +func (c *TeamsJSONClient) SendReplyToChannel(ctx context.Context, p map[string]any) (any, error) { return execute(p, func(params sendReplyToChannelParams) (any, error) { body, err := decoders.DecodeParams[models.MessageBody](¶ms.Body) if err != nil { return nil, err } - return c.client.Channels.SendReply(context.TODO(), params.TeamRef, params.ChannelRef, params.MessageID, *body) + return c.client.Channels.SendReply(ctx, params.TeamRef, params.ChannelRef, params.MessageID, *body) }) } @@ -106,14 +106,14 @@ type listMessagesParams struct { NextLink decoders.NextLinkDTO `json:"nextLink"` } -func (c *TeamsJSONClient) ListMessagesInChannel(p map[string]any) (any, error) { +func (c *TeamsJSONClient) ListMessagesInChannel(ctx context.Context, p map[string]any) (any, error) { return execute(p, func(params listMessagesParams) (any, error) { options, err := decoders.DecodeParams[models.ListMessagesOptions](params.Options) if err != nil { return nil, err } nextLink := decoders.GetNextLink(¶ms.NextLink) - return c.client.Channels.ListMessages(context.TODO(), params.TeamRef, params.ChannelRef, options, params.IncludeSystem, nextLink) + return c.client.Channels.ListMessages(ctx, params.TeamRef, params.ChannelRef, options, params.IncludeSystem, nextLink) }) } @@ -123,9 +123,9 @@ type getMessageParams struct { MessageID string `json:"messageId"` } -func (c *TeamsJSONClient) GetMessageInChannel(p map[string]any) (any, error) { +func (c *TeamsJSONClient) GetMessageInChannel(ctx context.Context, p map[string]any) (any, error) { return execute(p, func(params getMessageParams) (any, error) { - return c.client.Channels.GetMessage(context.TODO(), params.TeamRef, params.ChannelRef, params.MessageID) + return c.client.Channels.GetMessage(ctx, params.TeamRef, params.ChannelRef, params.MessageID) }) } @@ -138,10 +138,10 @@ type listRepliesParams struct { NextLink decoders.NextLinkDTO `json:"nextLink"` } -func (c *TeamsJSONClient) ListMessageRepliesInChannel(p map[string]any) (any, error) { +func (c *TeamsJSONClient) ListMessageRepliesInChannel(ctx context.Context, p map[string]any) (any, error) { return execute(p, func(params listRepliesParams) (any, error) { nextLink := decoders.GetNextLink(¶ms.NextLink) - return c.client.Channels.ListReplies(context.TODO(), params.TeamRef, params.ChannelRef, params.MessageID, params.Top, params.IncludeSystem, nextLink) + return c.client.Channels.ListReplies(ctx, params.TeamRef, params.ChannelRef, params.MessageID, params.Top, params.IncludeSystem, nextLink) }) } @@ -152,15 +152,15 @@ type getReplyParams struct { ReplyID string `json:"replyId"` } -func (c *TeamsJSONClient) GetMessageReplyInChannel(p map[string]any) (any, error) { +func (c *TeamsJSONClient) GetMessageReplyInChannel(ctx context.Context, p map[string]any) (any, error) { return execute(p, func(params getReplyParams) (any, error) { - return c.client.Channels.GetReply(context.TODO(), params.TeamRef, params.ChannelRef, params.MessageID, params.ReplyID) + return c.client.Channels.GetReply(ctx, params.TeamRef, params.ChannelRef, params.MessageID, params.ReplyID) }) } -func (c *TeamsJSONClient) ListChannelMembers(p map[string]any) (any, error) { +func (c *TeamsJSONClient) ListChannelMembers(ctx context.Context, p map[string]any) (any, error) { return execute(p, func(params baseChannelParams) (any, error) { - return c.client.Channels.ListMembers(context.TODO(), params.TeamRef, params.ChannelRef) + return c.client.Channels.ListMembers(ctx, params.TeamRef, params.ChannelRef) }) } @@ -171,15 +171,15 @@ type addOrUpdateMemberToChannelParams struct { IsOwner bool `json:"isOwner"` } -func (c *TeamsJSONClient) AddMemberToChannel(p map[string]any) (any, error) { +func (c *TeamsJSONClient) AddMemberToChannel(ctx context.Context, p map[string]any) (any, error) { return execute(p, func(params addOrUpdateMemberToChannelParams) (any, error) { - return c.client.Channels.AddMember(context.TODO(), params.TeamRef, params.ChannelRef, params.UserRef, params.IsOwner) + return c.client.Channels.AddMember(ctx, params.TeamRef, params.ChannelRef, params.UserRef, params.IsOwner) }) } -func (c *TeamsJSONClient) UpdateMemberInChannel(p map[string]any) (any, error) { +func (c *TeamsJSONClient) UpdateMemberInChannel(ctx context.Context, p map[string]any) (any, error) { return execute(p, func(params addOrUpdateMemberToChannelParams) (any, error) { - return c.client.Channels.UpdateMemberRoles(context.TODO(), params.TeamRef, params.ChannelRef, params.UserRef, params.IsOwner) + return c.client.Channels.UpdateMemberRoles(ctx, params.TeamRef, params.ChannelRef, params.UserRef, params.IsOwner) }) } @@ -189,9 +189,9 @@ type removeMemberFromChannelParams struct { UserRef string `json:"userRef"` } -func (c *TeamsJSONClient) RemoveMemberFromChannel(p map[string]any) (any, error) { +func (c *TeamsJSONClient) RemoveMemberFromChannel(ctx context.Context, p map[string]any) (any, error) { return execute(p, func(params removeMemberFromChannelParams) (any, error) { - err := c.client.Channels.RemoveMember(context.TODO(), params.TeamRef, params.ChannelRef, params.UserRef) + err := c.client.Channels.RemoveMember(ctx, params.TeamRef, params.ChannelRef, params.UserRef) return "removed", err }) } @@ -202,9 +202,9 @@ type getMentionsInChannelParams struct { RawMentions []string `json:"rawMentions"` } -func (c *TeamsJSONClient) GetMentionsInChannel(p map[string]any) (any, error) { +func (c *TeamsJSONClient) GetMentionsInChannel(ctx context.Context, p map[string]any) (any, error) { return execute(p, func(params getMentionsInChannelParams) (any, error) { - return c.client.Channels.GetMentions(context.TODO(), params.TeamRef, params.ChannelRef, params.RawMentions) + return c.client.Channels.GetMentions(ctx, params.TeamRef, params.ChannelRef, params.RawMentions) }) } @@ -215,13 +215,13 @@ type searchMessagesInChannelParams struct { SearchConfig decoders.SearchConfigDTO `json:"searchConfig"` } -func (c *TeamsJSONClient) SearchMessagesInChannel(p map[string]any) (any, error) { +func (c *TeamsJSONClient) SearchMessagesInChannel(ctx context.Context, p map[string]any) (any, error) { return execute(p, func(params searchMessagesInChannelParams) (any, error) { searchOptions := decoders.DecodeSearchMessageOptions(¶ms.SearchOptions) searchConfig, err := decoders.DecodeParams[search.SearchConfig](¶ms.SearchConfig) if err != nil { return nil, err } - return c.client.Channels.SearchMessages(context.TODO(), ¶ms.TeamRef, ¶ms.ChannelRef, searchOptions, searchConfig) + return c.client.Channels.SearchMessages(ctx, ¶ms.TeamRef, ¶ms.ChannelRef, searchOptions, searchConfig) }) } diff --git a/go/internal/json-client/requests-chats.go b/go/internal/json-client/requests-chats.go index 34e9e2b..4708e76 100644 --- a/go/internal/json-client/requests-chats.go +++ b/go/internal/json-client/requests-chats.go @@ -13,9 +13,9 @@ type createOneToOneChatParams struct { RecipientRef string `json:"recipientRef"` } -func (c *TeamsJSONClient) CreateOneToOneChat(p map[string]any) (any, error) { +func (c *TeamsJSONClient) CreateOneToOneChat(ctx context.Context, p map[string]any) (any, error) { return execute(p, func(params createOneToOneChatParams) (any, error) { - return c.client.Chats.CreateOneOnOne(context.TODO(), params.RecipientRef) + return c.client.Chats.CreateOneOnOne(ctx, params.RecipientRef) }) } @@ -25,9 +25,9 @@ type createGroupChatParams struct { IncludeMe bool `json:"includeMe"` } -func (c *TeamsJSONClient) CreateGroupChat(p map[string]any) (any, error) { +func (c *TeamsJSONClient) CreateGroupChat(ctx context.Context, p map[string]any) (any, error) { return execute(p, func(params createGroupChatParams) (any, error) { - return c.client.Chats.CreateGroup(context.TODO(), params.RecipientRefs, params.Topic, params.IncludeMe) + return c.client.Chats.CreateGroup(ctx, params.RecipientRefs, params.Topic, params.IncludeMe) }) } @@ -35,10 +35,10 @@ type baseChatParams struct { ChatRef decoders.ChatRefDTO `json:"chatRef"` } -func (c *TeamsJSONClient) GetChat(p map[string]any) (any, error) { +func (c *TeamsJSONClient) GetChat(ctx context.Context, p map[string]any) (any, error) { return execute(p, func(params baseChatParams) (any, error) { chatRef := decoders.GetChatRef(params.ChatRef) - return c.client.Chats.GetChat(context.TODO(), chatRef) + return c.client.Chats.GetChat(ctx, chatRef) }) } @@ -47,15 +47,15 @@ type groupChatMemberParams struct { UserRef string `json:"userRef"` } -func (c *TeamsJSONClient) AddMemberToGroupChat(p map[string]any) (any, error) { +func (c *TeamsJSONClient) AddMemberToGroupChat(ctx context.Context, p map[string]any) (any, error) { return execute(p, func(params groupChatMemberParams) (any, error) { - return c.client.Chats.AddMemberToGroupChat(context.TODO(), chats.GroupChatRef{Ref: params.GroupChatRef}, params.UserRef) + return c.client.Chats.AddMemberToGroupChat(ctx, chats.GroupChatRef{Ref: params.GroupChatRef}, params.UserRef) }) } -func (c *TeamsJSONClient) RemoveMemberFromGroupChat(p map[string]any) (any, error) { +func (c *TeamsJSONClient) RemoveMemberFromGroupChat(ctx context.Context, p map[string]any) (any, error) { return execute(p, func(params groupChatMemberParams) (any, error) { - err := c.client.Chats.RemoveMemberFromGroupChat(context.TODO(), chats.GroupChatRef{Ref: params.GroupChatRef}, params.UserRef) + err := c.client.Chats.RemoveMemberFromGroupChat(ctx, chats.GroupChatRef{Ref: params.GroupChatRef}, params.UserRef) return "removed", err }) } @@ -64,9 +64,9 @@ type listMembersInChatParams struct { GroupChatRef string `json:"groupChatRef"` } -func (c *TeamsJSONClient) ListGroupChatMembers(p map[string]any) (any, error) { +func (c *TeamsJSONClient) ListGroupChatMembers(ctx context.Context, p map[string]any) (any, error) { return execute(p, func(params listMembersInChatParams) (any, error) { - return c.client.Chats.ListGroupChatMembers(context.TODO(), chats.GroupChatRef{Ref: params.GroupChatRef}) + return c.client.Chats.ListGroupChatMembers(ctx, chats.GroupChatRef{Ref: params.GroupChatRef}) }) } @@ -75,9 +75,9 @@ type updateGroupChatTopicParams struct { Topic string `json:"topic"` } -func (c *TeamsJSONClient) UpdateGroupChatTopic(p map[string]any) (any, error) { +func (c *TeamsJSONClient) UpdateGroupChatTopic(ctx context.Context, p map[string]any) (any, error) { return execute(p, func(params updateGroupChatTopicParams) (any, error) { - return c.client.Chats.UpdateGroupChatTopic(context.TODO(), chats.GroupChatRef{Ref: params.GroupChatRef}, params.Topic) + return c.client.Chats.UpdateGroupChatTopic(ctx, chats.GroupChatRef{Ref: params.GroupChatRef}, params.Topic) }) } @@ -87,11 +87,11 @@ type listMessagesInChatParams struct { NextLink decoders.NextLinkDTO `json:"nextLink"` } -func (c *TeamsJSONClient) ListMessagesInChat(p map[string]any) (any, error) { +func (c *TeamsJSONClient) ListMessagesInChat(ctx context.Context, p map[string]any) (any, error) { return execute(p, func(params listMessagesInChatParams) (any, error) { chatRef := decoders.GetChatRef(params.ChatRef) nextLink := decoders.GetNextLink(¶ms.NextLink) - return c.client.Chats.ListMessages(context.TODO(), chatRef, params.IncludeSystem, nextLink) + return c.client.Chats.ListMessages(ctx, chatRef, params.IncludeSystem, nextLink) }) } @@ -100,14 +100,14 @@ type sendMessageInChatParams struct { Body decoders.MessageBodyDTO `json:"body"` } -func (c *TeamsJSONClient) SendMessageInChat(p map[string]any) (any, error) { +func (c *TeamsJSONClient) SendMessageInChat(ctx context.Context, p map[string]any) (any, error) { return execute(p, func(params sendMessageInChatParams) (any, error) { chatRef := decoders.GetChatRef(params.ChatRef) body, err := decoders.DecodeParams[models.MessageBody](params.Body) if err != nil { return nil, err } - return c.client.Chats.SendMessage(context.TODO(), chatRef, *body) + return c.client.Chats.SendMessage(ctx, chatRef, *body) }) } @@ -116,18 +116,18 @@ type messageInChatParams struct { MessageID string `json:"messageId"` } -func (c *TeamsJSONClient) DeleteMessageInChat(p map[string]any) (any, error) { +func (c *TeamsJSONClient) DeleteMessageInChat(ctx context.Context, p map[string]any) (any, error) { return execute(p, func(params messageInChatParams) (any, error) { chatRef := decoders.GetChatRef(params.ChatRef) - err := c.client.Chats.DeleteMessage(context.TODO(), chatRef, params.MessageID) + err := c.client.Chats.DeleteMessage(ctx, chatRef, params.MessageID) return "deleted", err }) } -func (c *TeamsJSONClient) GetMessageInChat(p map[string]any) (any, error) { +func (c *TeamsJSONClient) GetMessageInChat(ctx context.Context, p map[string]any) (any, error) { return execute(p, func(params messageInChatParams) (any, error) { chatRef := decoders.GetChatRef(params.ChatRef) - return c.client.Chats.GetMessage(context.TODO(), chatRef, params.MessageID) + return c.client.Chats.GetMessage(ctx, chatRef, params.MessageID) }) } @@ -135,13 +135,13 @@ type ListMyChatsParams struct { ChatType string `json:"chatType"` } -func (c *TeamsJSONClient) ListMyChats(p map[string]any) (any, error) { +func (c *TeamsJSONClient) ListMyChats(ctx context.Context, p map[string]any) (any, error) { return execute(p, func(params ListMyChatsParams) (any, error) { var chatType models.ChatType if params.ChatType != "" { chatType = models.ChatType(params.ChatType) } - return c.client.Chats.ListChats(context.TODO(), &chatType) + return c.client.Chats.ListChats(ctx, &chatType) }) } @@ -151,35 +151,35 @@ type listChatMessagesParams struct { Top *int32 `json:"top"` } -func (c *TeamsJSONClient) ListMyChatMessages(p map[string]any) (any, error) { +func (c *TeamsJSONClient) ListMyChatMessages(ctx context.Context, p map[string]any) (any, error) { return execute(p, func(params listChatMessagesParams) (any, error) { parsedStartTime, parsedEndTime, err := decoders.DecodeTimeRange(params.StartTime, params.EndTime) if err != nil { return nil, err } - return c.client.Chats.ListAllMessages(context.TODO(), parsedStartTime, parsedEndTime, params.Top) + return c.client.Chats.ListAllMessages(ctx, parsedStartTime, parsedEndTime, params.Top) }) } -func (c *TeamsJSONClient) ListPinnedMessagesInChat(p map[string]any) (any, error) { +func (c *TeamsJSONClient) ListPinnedMessagesInChat(ctx context.Context, p map[string]any) (any, error) { return execute(p, func(params baseChatParams) (any, error) { chatRef := decoders.GetChatRef(params.ChatRef) - return c.client.Chats.ListPinnedMessages(context.TODO(), chatRef) + return c.client.Chats.ListPinnedMessages(ctx, chatRef) }) } -func (c *TeamsJSONClient) PinMessageInChat(p map[string]any) (any, error) { +func (c *TeamsJSONClient) PinMessageInChat(ctx context.Context, p map[string]any) (any, error) { return execute(p, func(params messageInChatParams) (any, error) { chatRef := decoders.GetChatRef(params.ChatRef) - err := c.client.Chats.PinMessage(context.TODO(), chatRef, params.MessageID) + err := c.client.Chats.PinMessage(ctx, chatRef, params.MessageID) return "pinned", err }) } -func (c *TeamsJSONClient) UnpinMessageInChat(p map[string]any) (any, error) { +func (c *TeamsJSONClient) UnpinMessageInChat(ctx context.Context, p map[string]any) (any, error) { return execute(p, func(params messageInChatParams) (any, error) { chatRef := decoders.GetChatRef(params.ChatRef) - err := c.client.Chats.UnpinMessage(context.TODO(), chatRef, params.MessageID) + err := c.client.Chats.UnpinMessage(ctx, chatRef, params.MessageID) return "unpinned", err }) } @@ -189,10 +189,10 @@ type mentionInChatParams struct { RawMentions []string `json:"rawMentions"` } -func (c *TeamsJSONClient) GetMentionsInChat(p map[string]any) (any, error) { +func (c *TeamsJSONClient) GetMentionsInChat(ctx context.Context, p map[string]any) (any, error) { return execute(p, func(params mentionInChatParams) (any, error) { chatRef := decoders.GetChatRef(params.ChatRef) - return c.client.Chats.GetMentions(context.TODO(), chatRef, params.RawMentions) + return c.client.Chats.GetMentions(ctx, chatRef, params.RawMentions) }) } @@ -202,7 +202,7 @@ type searchMessagesInChatParams struct { SearchConfig decoders.SearchConfigDTO `json:"searchConfig"` } -func (c *TeamsJSONClient) SearchMessagesInChat(p map[string]any) (any, error) { +func (c *TeamsJSONClient) SearchMessagesInChat(ctx context.Context, p map[string]any) (any, error) { return execute(p, func(params searchMessagesInChatParams) (any, error) { chatRef := decoders.GetChatRef(params.ChatRef) searchOptions := decoders.DecodeSearchMessageOptions(¶ms.SearchOptions) @@ -210,6 +210,6 @@ func (c *TeamsJSONClient) SearchMessagesInChat(p map[string]any) (any, error) { if err != nil { return nil, err } - return c.client.Chats.SearchMessages(context.TODO(), chatRef, searchOptions, searchConfig) + return c.client.Chats.SearchMessages(ctx, chatRef, searchOptions, searchConfig) }) } diff --git a/go/internal/json-client/requests-teams.go b/go/internal/json-client/requests-teams.go index 22022ac..f8608a8 100644 --- a/go/internal/json-client/requests-teams.go +++ b/go/internal/json-client/requests-teams.go @@ -10,14 +10,14 @@ type getTeamParams struct { TeamRef string `json:"teamRef"` } -func (c *TeamsJSONClient) GetTeam(p map[string]any) (any, error) { +func (c *TeamsJSONClient) GetTeam(ctx context.Context, p map[string]any) (any, error) { return execute(p, func(params getTeamParams) (any, error) { - return c.client.Teams.Get(context.TODO(), params.TeamRef) + return c.client.Teams.Get(ctx, params.TeamRef) }) } -func (c *TeamsJSONClient) ListMyJoined() (any, error) { - return c.client.Teams.ListMyJoined(context.TODO()) +func (c *TeamsJSONClient) ListMyJoined(ctx context.Context) (any, error) { + return c.client.Teams.ListMyJoined(ctx) } type updateTeamParams struct { @@ -25,10 +25,10 @@ type updateTeamParams struct { TeamUpdate decoders.UpdateTeamDTO `json:"team"` } -func (c *TeamsJSONClient) UpdateTeam(p map[string]any) (any, error) { +func (c *TeamsJSONClient) UpdateTeam(ctx context.Context, p map[string]any) (any, error) { return execute(p, func(params updateTeamParams) (any, error) { updateTeam := decoders.GetUpdateTeam(¶ms.TeamUpdate) - return c.client.Teams.UpdateTeam(context.TODO(), params.TeamRef, &updateTeam) + return c.client.Teams.UpdateTeam(ctx, params.TeamRef, &updateTeam) }) } @@ -38,9 +38,9 @@ type createViaGroupParams struct { Visibility string `json:"visibility"` } -func (c *TeamsJSONClient) CreateTeamViaGroup(p map[string]any) (any, error) { +func (c *TeamsJSONClient) CreateTeamViaGroup(ctx context.Context, p map[string]any) (any, error) { return execute(p, func(params createViaGroupParams) (any, error) { - return c.client.Teams.CreateViaGroup(context.TODO(), params.DisplayName, params.MailNickname, params.Visibility) + return c.client.Teams.CreateViaGroup(ctx, params.DisplayName, params.MailNickname, params.Visibility) }) } @@ -53,9 +53,9 @@ type createFromTemplateParams struct { IncludeMe bool `json:"includeMe"` } -func (c *TeamsJSONClient) CreateTeamFromTemplate(p map[string]any) (any, error) { +func (c *TeamsJSONClient) CreateTeamFromTemplate(ctx context.Context, p map[string]any) (any, error) { return execute(p, func(params createFromTemplateParams) (any, error) { - return c.client.Teams.CreateFromTemplate(context.TODO(), params.DisplayName, params.Description, params.Owners, params.Members, params.Visibility, params.IncludeMe) + return c.client.Teams.CreateFromTemplate(ctx, params.DisplayName, params.Description, params.Owners, params.Members, params.Visibility, params.IncludeMe) }) } @@ -64,9 +64,9 @@ type archiveTeamParams struct { SpoReadOnlyFromMembers *bool `json:"spoReadOnlyFromMembers,omitempty"` } -func (c *TeamsJSONClient) ArchiveTeam(p map[string]any) (any, error) { +func (c *TeamsJSONClient) ArchiveTeam(ctx context.Context, p map[string]any) (any, error) { return execute(p, func(params archiveTeamParams) (any, error) { - err := c.client.Teams.Archive(context.TODO(), params.TeamRef, params.SpoReadOnlyFromMembers) + err := c.client.Teams.Archive(ctx, params.TeamRef, params.SpoReadOnlyFromMembers) return "archived", err }) } @@ -75,9 +75,9 @@ type unarchiveTeamParams struct { TeamRef string `json:"teamRef"` } -func (c *TeamsJSONClient) UnarchiveTeam(p map[string]any) (any, error) { +func (c *TeamsJSONClient) UnarchiveTeam(ctx context.Context, p map[string]any) (any, error) { return execute(p, func(params unarchiveTeamParams) (any, error) { - err := c.client.Teams.Unarchive(context.TODO(), params.TeamRef) + err := c.client.Teams.Unarchive(ctx, params.TeamRef) return "unarchived", err }) } @@ -86,9 +86,9 @@ type deleteTeamParams struct { TeamRef string `json:"teamRef"` } -func (c *TeamsJSONClient) DeleteTeam(p map[string]any) (any, error) { +func (c *TeamsJSONClient) DeleteTeam(ctx context.Context, p map[string]any) (any, error) { return execute(p, func(params deleteTeamParams) (any, error) { - err := c.client.Teams.Delete(context.TODO(), params.TeamRef) + err := c.client.Teams.Delete(ctx, params.TeamRef) return "deleted", err }) } @@ -97,8 +97,8 @@ type restoreTeamParams struct { DeletedGroupID string `json:"deletedGroupId"` } -func (c *TeamsJSONClient) RestoreDeletedTeam(p map[string]any) (any, error) { +func (c *TeamsJSONClient) RestoreDeletedTeam(ctx context.Context, p map[string]any) (any, error) { return execute(p, func(params restoreTeamParams) (any, error) { - return c.client.Teams.RestoreDeleted(context.TODO(), params.DeletedGroupID) + return c.client.Teams.RestoreDeleted(ctx, params.DeletedGroupID) }) } From 44eaca4086455ca4b632b1fa465c46cab9ae4a54 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Tue, 13 Jan 2026 18:50:32 +0100 Subject: [PATCH 98/99] docs: solved todo in python docs readme --- python/README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/python/README.md b/python/README.md index 8e66add..1ccd0ae 100644 --- a/python/README.md +++ b/python/README.md @@ -47,8 +47,7 @@ SCOPES=User.Read,Team.ReadBasic.All,Channel.ReadBasic.All AUTH_METHOD=DEVICE_CODE [or INTERACTIVE] ``` -Scopes needed by all functions could be found in .env.template. -**//TODO add url to template** +Scopes needed by all functions could be found in [.env.template](https://github.com/pzsp-teams/lib-python/tree/main/.env.template). ### 2. Basic usage From 386a2319b373dbabeaaa8d32f439cd8910208085 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Tue, 13 Jan 2026 18:55:07 +0100 Subject: [PATCH 99/99] docs: fixed index.md --- python/docs/index.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/python/docs/index.md b/python/docs/index.md index c92817b..dd570f2 100644 --- a/python/docs/index.md +++ b/python/docs/index.md @@ -50,8 +50,7 @@ SCOPES=User.Read,Team.ReadBasic.All,Channel.ReadBasic.All AUTH_METHOD=DEVICE_CODE [or INTERACTIVE] ``` -Scopes needed by all functions could be found in .env.template. -**//TODO add url to template** +Scopes needed by all functions could be found in [.env.template](https://github.com/pzsp-teams/lib-python/tree/main/.env.template). ### 2. Basic usage