From bf08cdc6cce7fa89acce4b6624495d071176121a Mon Sep 17 00:00:00 2001 From: Mary Zhong Date: Sun, 5 Nov 2023 19:33:17 -0500 Subject: [PATCH] feat: Add organization roles endpoints --- clerk/clerk.go | 43 ++-- clerk/instance_organization_roles.go | 121 +++++++++++ clerk/instance_organization_roles_test.go | 236 ++++++++++++++++++++++ 3 files changed, 379 insertions(+), 21 deletions(-) create mode 100644 clerk/instance_organization_roles.go create mode 100644 clerk/instance_organization_roles_test.go diff --git a/clerk/clerk.go b/clerk/clerk.go index b8646579..91c5cdd1 100644 --- a/clerk/clerk.go +++ b/clerk/clerk.go @@ -19,27 +19,28 @@ const version = "1.48.4" const ( ProdUrl = "https://api.clerk.dev/v1/" - ActorTokensUrl = "actor_tokens" - AllowlistsUrl = "allowlist_identifiers" - BlocklistsUrl = "blocklist_identifiers" - ClientsUrl = "clients" - ClientsVerifyUrl = ClientsUrl + "/verify" - DomainsURL = "domains" - EmailAddressesURL = "email_addresses" - EmailsUrl = "emails" - InvitationsURL = "invitations" - OrganizationsUrl = "organizations" - PhoneNumbersURL = "phone_numbers" - ProxyChecksURL = "proxy_checks" - RedirectURLsUrl = "redirect_urls" - SAMLConnectionsUrl = "saml_connections" - SessionsUrl = "sessions" - SMSUrl = "sms_messages" - TemplatesUrl = "templates" - UsersUrl = "users" - UsersCountUrl = UsersUrl + "/count" - WebhooksUrl = "webhooks" - JWTTemplatesUrl = "jwt_templates" + ActorTokensUrl = "actor_tokens" + AllowlistsUrl = "allowlist_identifiers" + BlocklistsUrl = "blocklist_identifiers" + ClientsUrl = "clients" + ClientsVerifyUrl = ClientsUrl + "/verify" + DomainsURL = "domains" + EmailAddressesURL = "email_addresses" + EmailsUrl = "emails" + InvitationsURL = "invitations" + OrganizationsUrl = "organizations" + OrganizationRolesUrl = "organization_roles" + PhoneNumbersURL = "phone_numbers" + ProxyChecksURL = "proxy_checks" + RedirectURLsUrl = "redirect_urls" + SAMLConnectionsUrl = "saml_connections" + SessionsUrl = "sessions" + SMSUrl = "sms_messages" + TemplatesUrl = "templates" + UsersUrl = "users" + UsersCountUrl = UsersUrl + "/count" + WebhooksUrl = "webhooks" + JWTTemplatesUrl = "jwt_templates" ) var defaultHTTPClient = &http.Client{Timeout: time.Second * 5} diff --git a/clerk/instance_organization_roles.go b/clerk/instance_organization_roles.go new file mode 100644 index 00000000..98a44daa --- /dev/null +++ b/clerk/instance_organization_roles.go @@ -0,0 +1,121 @@ +package clerk + +import ( + "fmt" + "net/http" + "strconv" +) + +type InsOrgRole struct { + Object string `json:"object"` + ID string `json:"id"` + Name string `json:"name"` + Key string `json:"key"` + Description string `json:"description"` + Permissions []InsOrgPermission `json:"permissions"` + CreatedAt int64 `json:"created_at"` + UpdatedAt int64 `json:"updated_at"` +} + +type InsOrgRolesResponse struct { + Data []InsOrgRole `json:"data"` + TotalCount int64 `json:"total_count"` +} + +// TODO: move this to a separate file once custom permissions endpoints are done +type InsOrgPermission struct { + Object string `json:"object"` + ID string `json:"id"` + Name string `json:"name"` + Key string `json:"key"` + Description string `json:"description"` + Type string `json:"type"` + CreatedAt int64 `json:"created_at"` + UpdatedAt int64 `json:"updated_at"` +} + +type CreateInsOrgRoleParams struct { + Name string `json:"name"` + Key string `json:"key"` + Description string `json:"description"` + Permissions []string `json:"permissions,omitempty"` +} + +func (s *InstanceService) CreateOrganizationRole(params CreateInsOrgRoleParams) (*InsOrgRole, error) { + req, _ := s.client.NewRequest(http.MethodPost, OrganizationRolesUrl, ¶ms) + + var orgRole InsOrgRole + _, err := s.client.Do(req, &orgRole) + if err != nil { + return nil, err + } + return &orgRole, nil +} + +type ListInsOrgRoleParams struct { + Limit *int `json:"limit,omitempty"` + Offset *int `json:"offset,omitempty"` +} + +func (s *InstanceService) ListOrganizationRole(params ListInsOrgRoleParams) (*InsOrgRolesResponse, error) { + req, _ := s.client.NewRequest(http.MethodGet, OrganizationRolesUrl) + + query := req.URL.Query() + if params.Limit != nil { + query.Set("limit", strconv.Itoa(*params.Limit)) + } + if params.Offset != nil { + query.Set("offset", strconv.Itoa(*params.Offset)) + } + req.URL.RawQuery = query.Encode() + + var orgRolesResponse *InsOrgRolesResponse + _, err := s.client.Do(req, &orgRolesResponse) + if err != nil { + return nil, err + } + return orgRolesResponse, nil +} + +func (s *InstanceService) ReadOrganizationRole(orgRoleID string) (*InsOrgRole, error) { + req, err := s.client.NewRequest(http.MethodGet, fmt.Sprintf("%s/%s", OrganizationRolesUrl, orgRoleID)) + if err != nil { + return nil, err + } + + var orgRole InsOrgRole + _, err = s.client.Do(req, &orgRole) + if err != nil { + return nil, err + } + return &orgRole, nil +} + +type UpdateInsOrgRoleParams struct { + Name *string `json:"name,omitempty"` + Key *string `json:"key,omitempty"` + Description *string `json:"description,omitempty"` + Permissions *[]string `json:"permissions,omitempty"` +} + +func (s *InstanceService) UpdateOrganizationRole(orgRoleID string, params UpdateInsOrgRoleParams) (*InsOrgRole, error) { + req, _ := s.client.NewRequest(http.MethodPatch, fmt.Sprintf("%s/%s", OrganizationRolesUrl, orgRoleID), ¶ms) + + var orgRole InsOrgRole + _, err := s.client.Do(req, &orgRole) + if err != nil { + return nil, err + } + return &orgRole, nil +} + +func (s *InstanceService) DeleteOrganizationRole(orgRoleID string) (*DeleteResponse, error) { + req, _ := s.client.NewRequest(http.MethodDelete, fmt.Sprintf("%s/%s", OrganizationRolesUrl, orgRoleID)) + + var deleteResponse DeleteResponse + _, err := s.client.Do(req, &deleteResponse) + if err != nil { + return nil, err + } + return &deleteResponse, nil +} diff --git a/clerk/instance_organization_roles_test.go b/clerk/instance_organization_roles_test.go new file mode 100644 index 00000000..61720e0e --- /dev/null +++ b/clerk/instance_organization_roles_test.go @@ -0,0 +1,236 @@ +package clerk + +import ( + "encoding/json" + "fmt" + "net/http" + "net/url" + "reflect" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestInstanceService_CreateOrgRole(t *testing.T) { + expectedResponse := dummyOrgRoleJson + + client, mux, _, teardown := setup("token") + defer teardown() + + mux.HandleFunc("/organization_roles", func(w http.ResponseWriter, req *http.Request) { + testHttpMethod(t, req, http.MethodPost) + testHeader(t, req, "Authorization", "Bearer token") + _, _ = fmt.Fprint(w, expectedResponse) + }) + + createParams := CreateInsOrgRoleParams{ + Name: "custom role", + Key: "org:custom_role", + Description: "my org custom role", + Permissions: []string{}, + } + + got, err := client.Instances().CreateOrganizationRole(createParams) + assert.NoError(t, err) + + var want InsOrgRole + err = json.Unmarshal([]byte(expectedResponse), &want) + if err != nil { + t.Fatal(err) + } + + if !reflect.DeepEqual(got, &want) { + t.Errorf("Response = %v, want %v", got, &want) + } +} + +func TestOrganizationRolesService_Read(t *testing.T) { + client, mux, _, teardown := setup("token") + defer teardown() + + expectedResponse := dummyOrgRoleJson + + mux.HandleFunc(fmt.Sprintf("/organization_roles/%s", dummyOrgRoleID), func(w http.ResponseWriter, req *http.Request) { + testHttpMethod(t, req, "GET") + testHeader(t, req, "Authorization", "Bearer token") + fmt.Fprint(w, expectedResponse) + }) + + got, err := client.Instances().ReadOrganizationRole(dummyOrgRoleID) + if err != nil { + t.Fatal(err) + } + + var want InsOrgRole + err = json.Unmarshal([]byte(expectedResponse), &want) + if err != nil { + t.Fatal(err) + } + + if !reflect.DeepEqual(got, &want) { + t.Errorf("Response = %v, want %v", got, &want) + } +} + +func TestOrganizationRolesService_Update(t *testing.T) { + client, mux, _, teardown := setup("token") + defer teardown() + var payload UpdateInsOrgRoleParams + _ = json.Unmarshal([]byte(dummyUpdateOrgRoleJson), &payload) + + expectedResponse := dummyOrgRoleJson + mux.HandleFunc(fmt.Sprintf("/organization_roles/%s", dummyOrgRoleID), func(w http.ResponseWriter, req *http.Request) { + testHttpMethod(t, req, "PATCH") + testHeader(t, req, "Authorization", "Bearer token") + fmt.Fprint(w, expectedResponse) + }) + + got, err := client.Instances().UpdateOrganizationRole(dummyOrgRoleID, payload) + if err != nil { + t.Fatal(err) + } + + var want InsOrgRole + err = json.Unmarshal([]byte(expectedResponse), &want) + if err != nil { + t.Fatal(err) + } + + if !reflect.DeepEqual(got, &want) { + t.Errorf("Response = %v, want %v", got, &want) + } +} + +func TestOrganizationRolesService_Update_invalidServer(t *testing.T) { + client, _ := NewClient("token") + var payload UpdateInsOrgRoleParams + _ = json.Unmarshal([]byte(dummyUpdateOrgRoleJson), &payload) + + _, err := client.Instances().UpdateOrganizationRole("someOrgRoleId", payload) + if err == nil { + t.Errorf("Expected error to be returned") + } +} + +func TestOrganizationsService_List_happyPath(t *testing.T) { + client, mux, _, teardown := setup("token") + defer teardown() + + expectedResponse := fmt.Sprintf(`{ + "data": [%s], + "total_count": 1 + }`, dummyOrgRoleJson) + + mux.HandleFunc("/organization_roles", func(w http.ResponseWriter, req *http.Request) { + testHttpMethod(t, req, "GET") + testHeader(t, req, "Authorization", "Bearer token") + fmt.Fprint(w, expectedResponse) + }) + + var want *InsOrgRolesResponse + _ = json.Unmarshal([]byte(expectedResponse), &want) + + got, _ := client.Instances().ListOrganizationRole(ListInsOrgRoleParams{}) + if len(got.Data) != len(want.Data) { + t.Errorf("Was expecting %d organization roles to be returned, instead got %d", len(want.Data), len(got.Data)) + } + + if !reflect.DeepEqual(got, want) { + t.Errorf("Response = %v, want %v", got, want) + } +} + +func TestOrganizationsService_List_happyPathWithParameters(t *testing.T) { + client, mux, _, teardown := setup("token") + defer teardown() + + expectedResponse := fmt.Sprintf(`{ + "data": [%s], + "total_count": 1 + }`, dummyOrgRoleJson) + + mux.HandleFunc("/organization_roles", func(w http.ResponseWriter, req *http.Request) { + testHttpMethod(t, req, "GET") + testHeader(t, req, "Authorization", "Bearer token") + + actualQuery := req.URL.Query() + expectedQuery := url.Values(map[string][]string{ + "limit": {"5"}, + "offset": {"6"}, + }) + assert.Equal(t, expectedQuery, actualQuery) + fmt.Fprint(w, expectedResponse) + }) + + var want *InsOrgRolesResponse + _ = json.Unmarshal([]byte(expectedResponse), &want) + + limit := 5 + offset := 6 + got, _ := client.Instances().ListOrganizationRole(ListInsOrgRoleParams{ + Limit: &limit, + Offset: &offset, + }) + if len(got.Data) != len(want.Data) { + t.Errorf("Was expecting %d organization roles to be returned, instead got %d", len(want.Data), len(got.Data)) + } + + if !reflect.DeepEqual(got, want) { + t.Errorf("Response = %v, want %v", got, want) + } +} + +func TestOrganizationsService_List_invalidServer(t *testing.T) { + client, _ := NewClient("token") + + orgRoles, err := client.Instances().ListOrganizationRole(ListInsOrgRoleParams{}) + if err == nil { + t.Errorf("Expected error to be returned") + } + if orgRoles != nil { + t.Errorf("Was not expecting any organization roles to be returned, instead got %v", orgRoles) + } +} + +func TestOrganizationRolesService_Delete(t *testing.T) { + client, mux, _, teardown := setup("token") + defer teardown() + + mux.HandleFunc( + fmt.Sprintf("/organization_roles/%s", dummyOrgRoleID), + func(w http.ResponseWriter, req *http.Request) { + testHttpMethod(t, req, http.MethodDelete) + testHeader(t, req, "Authorization", "Bearer token") + fmt.Fprint(w, fmt.Sprintf(`{"id":"%s"}`, dummyOrgRoleID)) + }, + ) + + _, err := client.Instances().DeleteOrganizationRole(dummyOrgRoleID) + if err != nil { + t.Fatal(err) + } +} + +const dummyOrgRoleID = "role_1mebQggrD3xO5JfuHk7clQ94ysA" + +const dummyOrgRoleJson = `{ + "object": "role", + "id": "role_1mebQggrD3xO5JfuHk7clQ94ysA", + "name": "custom role", + "key": "org:custom_role", + "description": "my org custom role", + "permissions": [], + "created_at": 1610783813, + "updated_at": 1610783813 +}` + +const dummyUpdateOrgRoleJson = `{ + "object": "role", + "id": "role_1mebQggrD3xO5JfuHk7clQ94ysA", + "name": "custom org 2", + "key": "org:custom_role_2", + "description": "my org custom role", + "permissions": [], + "created_at": 1610783813, + "updated_at": 1610783813 +}`