diff --git a/apikey.go b/apikey.go new file mode 100644 index 0000000..b8e0ec8 --- /dev/null +++ b/apikey.go @@ -0,0 +1,39 @@ +package clerk + +import "encoding/json" + +// APIKey represents an API key response without the secret field. +// This is used for most endpoints except Create. +type APIKey struct { + APIResource + Object string `json:"object"` + ID string `json:"id"` + Type string `json:"type"` + Subject string `json:"subject"` + Name string `json:"name"` + Description *string `json:"description"` + Claims json.RawMessage `json:"claims"` + Scopes []string `json:"scopes"` + Revoked bool `json:"revoked"` + RevocationReason *string `json:"revocation_reason"` + Expired bool `json:"expired"` + Expiration *int64 `json:"expiration"` + CreatedBy *string `json:"created_by"` + LastUsedAt *int64 `json:"last_used_at"` + CreatedAt int64 `json:"created_at"` + UpdatedAt int64 `json:"updated_at"` +} + +// APIKeyWithSecret represents an API key response that includes the secret field. +// This is only used for the Create endpoint. +type APIKeyWithSecret struct { + APIKey + Secret string `json:"secret"` +} + +// APIKeyList represents a list of API keys without secrets. +type APIKeyList struct { + APIResource + APIKeys []*APIKey `json:"data"` + TotalCount int64 `json:"total_count"` +} diff --git a/apikey/api.go b/apikey/api.go new file mode 100644 index 0000000..8e328fc --- /dev/null +++ b/apikey/api.go @@ -0,0 +1,55 @@ +// Code generated by "gen"; DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. +package apikey + +import ( + "context" + + "github.com/clerk/clerk-sdk-go/v2" +) + +// Create creates a new API key. +func Create(ctx context.Context, params *CreateParams) (*clerk.APIKeyWithSecret, error) { + return getClient().Create(ctx, params) +} + +// Get returns an API key +func Get(ctx context.Context, apiKeyID string) (*clerk.APIKey, error) { + return getClient().Get(ctx, apiKeyID) +} + +// List returns a list of API keys. +func List(ctx context.Context, params *ListParams) (*clerk.APIKeyList, error) { + return getClient().List(ctx, params) +} + +// GetSecret retrieves the secret for an API key. +func GetSecret(ctx context.Context, apiKeyID string) (*APIKeySecret, error) { + return getClient().GetSecret(ctx, apiKeyID) +} + +// Update updates an API key. +func Update(ctx context.Context, apiKeyID string, params *UpdateParams) (*clerk.APIKey, error) { + return getClient().Update(ctx, apiKeyID, params) +} + +// Delete deletes an API key +func Delete(ctx context.Context, apiKeyID string) (*clerk.DeletedResource, error) { + return getClient().Delete(ctx, apiKeyID) +} + +// Revoke revokes an API key. +func Revoke(ctx context.Context, apiKeyID string, params *RevokeParams) (*clerk.APIKey, error) { + return getClient().Revoke(ctx, apiKeyID, params) +} + +// Verify verifies an API key. +func Verify(ctx context.Context, params *VerifyParams) (*clerk.APIKey, error) { + return getClient().Verify(ctx, params) +} + +func getClient() *Client { + return &Client{ + Backend: clerk.GetBackend(), + } +} diff --git a/apikey/api_test.go b/apikey/api_test.go new file mode 100644 index 0000000..dafa794 --- /dev/null +++ b/apikey/api_test.go @@ -0,0 +1,472 @@ +package apikey + +import ( + "context" + "encoding/json" + "net/http" + "net/url" + "testing" + + "github.com/clerk/clerk-sdk-go/v2" + "github.com/clerk/clerk-sdk-go/v2/clerktest" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestPackageCreate(t *testing.T) { + t.Parallel() + + response := map[string]interface{}{ + "object": "api_key", + "id": "ak_test123", + "type": "api_key", + "subject": "user_123", + "name": "test_key", + "description": "Test API key", + "claims": map[string]interface{}{}, + "scopes": []string{}, + "secret": "ak_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", + "revoked": false, + "revocation_reason": nil, + "expired": false, + "expiration": nil, + "created_by": "user_123", + "last_used_at": nil, + "created_at": 1640995200, + "updated_at": 1640995200, + } + + responseJSON, _ := json.Marshal(response) + + // Set up the backend with our mock transport + backend := &clerk.BackendConfig{ + HTTPClient: &http.Client{ + Transport: &clerktest.RoundTripper{ + T: t, + In: json.RawMessage(`{"name":"test_key","subject":"user_123","description":"Test API key"}`), + Out: json.RawMessage(responseJSON), + Method: http.MethodPost, + Path: "/v1/api_keys", + }, + }, + } + + // Set the backend globally + clerk.SetBackend(clerk.NewBackend(backend)) + + params := &CreateParams{ + Name: clerk.String("test_key"), + Subject: clerk.String("user_123"), + Description: clerk.String("Test API key"), + } + + apiKey, err := Create(context.Background(), params) + require.NoError(t, err) + assert.Equal(t, "ak_test123", apiKey.ID) + assert.Equal(t, "test_key", apiKey.Name) + assert.Equal(t, "user_123", apiKey.Subject) + assert.Equal(t, "ak_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", apiKey.Secret) +} + +func TestPackageGet(t *testing.T) { + t.Parallel() + + response := map[string]interface{}{ + "object": "api_key", + "id": "ak_test123", + "type": "api_key", + "subject": "user_123", + "name": "test_key", + "description": "Test API key", + "claims": map[string]interface{}{}, + "scopes": []string{}, + "revoked": false, + "revocation_reason": nil, + "expired": false, + "expiration": nil, + "created_by": "user_123", + "last_used_at": nil, + "created_at": 1640995200, + "updated_at": 1640995200, + } + + responseJSON, _ := json.Marshal(response) + + backend := &clerk.BackendConfig{ + HTTPClient: &http.Client{ + Transport: &clerktest.RoundTripper{ + T: t, + Out: json.RawMessage(responseJSON), + Method: http.MethodGet, + Path: "/v1/api_keys/ak_test123", + }, + }, + } + + clerk.SetBackend(clerk.NewBackend(backend)) + + apiKey, err := Get(context.Background(), "ak_test123") + require.NoError(t, err) + assert.Equal(t, "ak_test123", apiKey.ID) + assert.Equal(t, "test_key", apiKey.Name) + assert.Equal(t, "user_123", apiKey.Subject) + assert.Equal(t, "Test API key", *apiKey.Description) + assert.False(t, apiKey.Revoked) +} + +func TestPackageList(t *testing.T) { + t.Parallel() + + response := map[string]interface{}{ + "data": []map[string]interface{}{ + { + "object": "api_key", + "id": "ak_test123", + "type": "api_key", + "subject": "user_123", + "name": "test_key", + "description": "Test API key", + "claims": map[string]interface{}{}, + "scopes": []string{}, + "revoked": false, + "revocation_reason": nil, + "expired": false, + "expiration": nil, + "created_by": "user_123", + "last_used_at": nil, + "created_at": 1640995200, + "updated_at": 1640995200, + }, + }, + "total_count": int64(1), + } + + responseJSON, _ := json.Marshal(response) + + backend := &clerk.BackendConfig{ + HTTPClient: &http.Client{ + Transport: &clerktest.RoundTripper{ + T: t, + Out: json.RawMessage(responseJSON), + Method: http.MethodGet, + Path: "/v1/api_keys", + Query: &url.Values{ + "subject": []string{"user_123"}, + "type": []string{"api_key"}, + }, + }, + }, + } + + clerk.SetBackend(clerk.NewBackend(backend)) + + params := &ListParams{ + Subject: clerk.String("user_123"), + Type: clerk.String("api_key"), + } + + apiKeys, err := List(context.Background(), params) + require.NoError(t, err) + assert.Equal(t, int64(1), apiKeys.TotalCount) + assert.Len(t, apiKeys.APIKeys, 1) + assert.Equal(t, "ak_test123", apiKeys.APIKeys[0].ID) +} + +func TestPackageGetSecret(t *testing.T) { + t.Parallel() + + response := map[string]interface{}{ + "secret": "ak_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", + } + + responseJSON, _ := json.Marshal(response) + + backend := &clerk.BackendConfig{ + HTTPClient: &http.Client{ + Transport: &clerktest.RoundTripper{ + T: t, + Out: json.RawMessage(responseJSON), + Method: http.MethodGet, + Path: "/v1/api_keys/ak_test123/secret", + }, + }, + } + + clerk.SetBackend(clerk.NewBackend(backend)) + + secret, err := GetSecret(context.Background(), "ak_test123") + require.NoError(t, err) + assert.Equal(t, "ak_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", secret.Secret) +} + +func TestPackageUpdate(t *testing.T) { + t.Parallel() + + response := map[string]interface{}{ + "object": "api_key", + "id": "ak_test123", + "type": "api_key", + "subject": "user_123", + "name": "test_key", + "description": "Updated description", + "claims": map[string]interface{}{}, + "scopes": []string{}, + "revoked": false, + "revocation_reason": nil, + "expired": false, + "expiration": nil, + "created_by": "user_123", + "last_used_at": nil, + "created_at": 1640995200, + "updated_at": 1640995200, + } + + responseJSON, _ := json.Marshal(response) + + backend := &clerk.BackendConfig{ + HTTPClient: &http.Client{ + Transport: &clerktest.RoundTripper{ + T: t, + In: json.RawMessage(`{"description":"Updated description"}`), + Out: json.RawMessage(responseJSON), + Method: http.MethodPatch, + Path: "/v1/api_keys/ak_test123", + }, + }, + } + + clerk.SetBackend(clerk.NewBackend(backend)) + + params := &UpdateParams{ + Description: clerk.String("Updated description"), + } + + apiKey, err := Update(context.Background(), "ak_test123", params) + require.NoError(t, err) + assert.Equal(t, "Updated description", *apiKey.Description) +} + +func TestPackageDelete(t *testing.T) { + t.Parallel() + + response := map[string]interface{}{ + "object": "api_key", + "id": "ak_test123", + "deleted": true, + } + + responseJSON, _ := json.Marshal(response) + + backend := &clerk.BackendConfig{ + HTTPClient: &http.Client{ + Transport: &clerktest.RoundTripper{ + T: t, + Out: json.RawMessage(responseJSON), + Method: http.MethodDelete, + Path: "/v1/api_keys/ak_test123", + }, + }, + } + + clerk.SetBackend(clerk.NewBackend(backend)) + + deletedResource, err := Delete(context.Background(), "ak_test123") + require.NoError(t, err) + assert.Equal(t, "ak_test123", deletedResource.ID) + assert.True(t, deletedResource.Deleted) + assert.Equal(t, "api_key", deletedResource.Object) +} + +func TestPackageUpdateWithSecondsUntilExpiration(t *testing.T) { + t.Parallel() + + response := map[string]interface{}{ + "object": "api_key", + "id": "ak_test123", + "type": "api_key", + "subject": "user_123", + "name": "test_key", + "description": "Updated with expiration", + "claims": map[string]interface{}{}, + "scopes": []string{}, + "revoked": false, + "revocation_reason": nil, + "expired": false, + "expiration": 1716883200, + "created_by": "user_123", + "last_used_at": nil, + "created_at": 1640995200, + "updated_at": 1640995200, + } + + responseJSON, _ := json.Marshal(response) + + backend := &clerk.BackendConfig{ + HTTPClient: &http.Client{ + Transport: &clerktest.RoundTripper{ + T: t, + In: json.RawMessage(`{"description":"Updated with expiration","seconds_until_expiration":3600}`), + Out: json.RawMessage(responseJSON), + Method: http.MethodPatch, + Path: "/v1/api_keys/ak_test123", + }, + }, + } + + clerk.SetBackend(clerk.NewBackend(backend)) + + params := &UpdateParams{ + Description: clerk.String("Updated with expiration"), + SecondsUntilExpiration: clerk.Int64(3600), + } + + apiKey, err := Update(context.Background(), "ak_test123", params) + require.NoError(t, err) + assert.Equal(t, "Updated with expiration", *apiKey.Description) + assert.Equal(t, int64(1716883200), *apiKey.Expiration) +} + +func TestPackageUpdateSecondsUntilExpirationOnly(t *testing.T) { + t.Parallel() + + response := map[string]interface{}{ + "object": "api_key", + "id": "ak_test123", + "type": "api_key", + "subject": "user_123", + "name": "test_key", + "description": "Test API key", + "claims": map[string]interface{}{}, + "scopes": []string{}, + "revoked": false, + "revocation_reason": nil, + "expired": false, + "expiration": 1716883200, + "created_by": "user_123", + "last_used_at": nil, + "created_at": 1640995200, + "updated_at": 1640995200, + } + + responseJSON, _ := json.Marshal(response) + + backend := &clerk.BackendConfig{ + HTTPClient: &http.Client{ + Transport: &clerktest.RoundTripper{ + T: t, + In: json.RawMessage(`{"seconds_until_expiration":3600}`), + Out: json.RawMessage(responseJSON), + Method: http.MethodPatch, + Path: "/v1/api_keys/ak_test123", + }, + }, + } + + clerk.SetBackend(clerk.NewBackend(backend)) + + params := &UpdateParams{ + SecondsUntilExpiration: clerk.Int64(3600), + } + + apiKey, err := Update(context.Background(), "ak_test123", params) + require.NoError(t, err) + assert.Equal(t, "ak_test123", apiKey.ID) + assert.Equal(t, int64(1716883200), *apiKey.Expiration) +} + +func TestPackageRevoke(t *testing.T) { + t.Parallel() + + response := map[string]interface{}{ + "object": "api_key", + "id": "ak_test123", + "type": "api_key", + "subject": "user_123", + "name": "test_key", + "description": "Test API key", + "claims": map[string]interface{}{}, + "scopes": []string{}, + "revoked": true, + "revocation_reason": "Security breach", + "expired": false, + "expiration": nil, + "created_by": "user_123", + "last_used_at": nil, + "created_at": 1640995200, + "updated_at": 1640995200, + } + + responseJSON, _ := json.Marshal(response) + + backend := &clerk.BackendConfig{ + HTTPClient: &http.Client{ + Transport: &clerktest.RoundTripper{ + T: t, + In: json.RawMessage(`{"revocation_reason":"Security breach"}`), + Out: json.RawMessage(responseJSON), + Method: http.MethodPost, + Path: "/v1/api_keys/ak_test123/revoke", + }, + }, + } + + clerk.SetBackend(clerk.NewBackend(backend)) + + params := &RevokeParams{ + RevocationReason: clerk.String("Security breach"), + } + + apiKey, err := Revoke(context.Background(), "ak_test123", params) + require.NoError(t, err) + assert.True(t, apiKey.Revoked) + assert.Equal(t, "Security breach", *apiKey.RevocationReason) +} + +func TestPackageVerify(t *testing.T) { + t.Parallel() + + response := map[string]interface{}{ + "object": "api_key", + "id": "ak_test123", + "type": "api_key", + "subject": "user_123", + "name": "test_key", + "description": "Test API key", + "claims": map[string]interface{}{}, + "scopes": []string{}, + "revoked": false, + "revocation_reason": nil, + "expired": false, + "expiration": nil, + "created_by": "user_123", + "last_used_at": nil, + "created_at": 1640995200, + "updated_at": 1640995200, + } + + responseJSON, _ := json.Marshal(response) + + backend := &clerk.BackendConfig{ + HTTPClient: &http.Client{ + Transport: &clerktest.RoundTripper{ + T: t, + In: json.RawMessage(`{"secret":"ak_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"}`), + Out: json.RawMessage(responseJSON), + Method: http.MethodPost, + Path: "/v1/api_keys/verify", + }, + }, + } + + clerk.SetBackend(clerk.NewBackend(backend)) + + params := &VerifyParams{ + Secret: "ak_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", + } + + apiKey, err := Verify(context.Background(), params) + require.NoError(t, err) + assert.Equal(t, "ak_test123", apiKey.ID) + assert.False(t, apiKey.Revoked) +} diff --git a/apikey/client.go b/apikey/client.go new file mode 100644 index 0000000..faf8ff6 --- /dev/null +++ b/apikey/client.go @@ -0,0 +1,185 @@ +// Package apikey provides the API Keys API. +package apikey + +import ( + "context" + "encoding/json" + "net/http" + "net/url" + "strconv" + + "github.com/clerk/clerk-sdk-go/v2" +) + +//go:generate go run ../cmd/gen/main.go + +const path = "/api_keys" + +// Client is used to invoke the API Keys API. +type Client struct { + Backend clerk.Backend +} + +func NewClient(config *clerk.ClientConfig) *Client { + return &Client{ + Backend: clerk.NewBackend(&config.BackendConfig), + } +} + +type CreateParams struct { + clerk.APIParams + Type *string `json:"type,omitempty"` + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + Subject *string `json:"subject,omitempty"` + Claims json.RawMessage `json:"claims,omitempty"` + Scopes []string `json:"scopes,omitempty"` + CreatedBy *string `json:"created_by,omitempty"` + SecondsUntilExpiration *int64 `json:"seconds_until_expiration,omitempty"` +} + +// Create creates a new API key. +func (c *Client) Create(ctx context.Context, params *CreateParams) (*clerk.APIKeyWithSecret, error) { + req := clerk.NewAPIRequest(http.MethodPost, path) + req.SetParams(params) + resource := &clerk.APIKeyWithSecret{} + err := c.Backend.Call(ctx, req, resource) + return resource, err +} + +// Get returns an API key +func (c *Client) Get(ctx context.Context, apiKeyID string) (*clerk.APIKey, error) { + path, err := clerk.JoinPath(path, apiKeyID) + if err != nil { + return nil, err + } + req := clerk.NewAPIRequest(http.MethodGet, path) + resource := &clerk.APIKey{} + err = c.Backend.Call(ctx, req, resource) + return resource, err +} + +type ListParams struct { + clerk.APIParams + clerk.ListParams + Type *string `json:"type,omitempty"` + Subject *string `json:"subject,omitempty"` + IncludeInvalid *string `json:"include_invalid,omitempty"` +} + +func (params *ListParams) ToQuery() url.Values { + q := url.Values{} + if params.Type != nil { + q.Set("type", *params.Type) + } + if params.Subject != nil { + q.Set("subject", *params.Subject) + } + if params.IncludeInvalid != nil { + q.Set("include_invalid", *params.IncludeInvalid) + } + // Add list params + if params.Limit != nil { + q.Set("limit", strconv.FormatInt(*params.Limit, 10)) + } + if params.Offset != nil { + q.Set("offset", strconv.FormatInt(*params.Offset, 10)) + } + return q +} + +// List returns a list of API keys. +func (c *Client) List(ctx context.Context, params *ListParams) (*clerk.APIKeyList, error) { + req := clerk.NewAPIRequest(http.MethodGet, path) + req.SetParams(params) + resource := &clerk.APIKeyList{} + err := c.Backend.Call(ctx, req, resource) + return resource, err +} + +// GetSecret retrieves the secret for an API key. +func (c *Client) GetSecret(ctx context.Context, apiKeyID string) (*APIKeySecret, error) { + path, err := clerk.JoinPath(path, apiKeyID, "secret") + if err != nil { + return nil, err + } + req := clerk.NewAPIRequest(http.MethodGet, path) + resource := &APIKeySecret{} + err = c.Backend.Call(ctx, req, resource) + return resource, err +} + +type APIKeySecret struct { + clerk.APIResource + Secret string `json:"secret"` +} + +type UpdateParams struct { + clerk.APIParams + Claims json.RawMessage `json:"claims,omitempty"` + Scopes []string `json:"scopes,omitempty"` + Description *string `json:"description,omitempty"` + Subject *string `json:"subject,omitempty"` + SecondsUntilExpiration *int64 `json:"seconds_until_expiration,omitempty"` +} + +// Update updates an API key. +func (c *Client) Update(ctx context.Context, apiKeyID string, params *UpdateParams) (*clerk.APIKey, error) { + path, err := clerk.JoinPath(path, apiKeyID) + if err != nil { + return nil, err + } + req := clerk.NewAPIRequest(http.MethodPatch, path) + req.SetParams(params) + resource := &clerk.APIKey{} + err = c.Backend.Call(ctx, req, resource) + return resource, err +} + +// Delete deletes an API key +func (c *Client) Delete(ctx context.Context, apiKeyID string) (*clerk.DeletedResource, error) { + path, err := clerk.JoinPath(path, apiKeyID) + if err != nil { + return nil, err + } + req := clerk.NewAPIRequest(http.MethodDelete, path) + resource := &clerk.DeletedResource{} + err = c.Backend.Call(ctx, req, resource) + return resource, err +} + +type RevokeParams struct { + clerk.APIParams + RevocationReason *string `json:"revocation_reason,omitempty"` +} + +// Revoke revokes an API key. +func (c *Client) Revoke(ctx context.Context, apiKeyID string, params *RevokeParams) (*clerk.APIKey, error) { + path, err := clerk.JoinPath(path, apiKeyID, "revoke") + if err != nil { + return nil, err + } + req := clerk.NewAPIRequest(http.MethodPost, path) + req.SetParams(params) + resource := &clerk.APIKey{} + err = c.Backend.Call(ctx, req, resource) + return resource, err +} + +type VerifyParams struct { + clerk.APIParams + Secret string `json:"secret"` +} + +// Verify verifies an API key. +func (c *Client) Verify(ctx context.Context, params *VerifyParams) (*clerk.APIKey, error) { + path, err := clerk.JoinPath(path, "verify") + if err != nil { + return nil, err + } + req := clerk.NewAPIRequest(http.MethodPost, path) + req.SetParams(params) + resource := &clerk.APIKey{} + err = c.Backend.Call(ctx, req, resource) + return resource, err +} diff --git a/apikey/client_test.go b/apikey/client_test.go new file mode 100644 index 0000000..a04d5b4 --- /dev/null +++ b/apikey/client_test.go @@ -0,0 +1,449 @@ +package apikey + +import ( + "context" + "encoding/json" + "net/http" + "net/url" + "testing" + + "github.com/clerk/clerk-sdk-go/v2" + "github.com/clerk/clerk-sdk-go/v2/clerktest" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestCreate(t *testing.T) { + t.Parallel() + + response := map[string]interface{}{ + "object": "api_key", + "id": "ak_test123", + "type": "api_key", + "subject": "user_123", + "name": "test_key", + "description": "Test API key", + "claims": map[string]interface{}{}, + "scopes": []string{}, + "secret": "ak_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", + "revoked": false, + "revocation_reason": nil, + "expired": false, + "expiration": nil, + "created_by": "user_123", + "last_used_at": nil, + "created_at": 1640995200, + "updated_at": 1640995200, + } + + responseJSON, _ := json.Marshal(response) + + config := &clerk.ClientConfig{} + config.HTTPClient = &http.Client{ + Transport: &clerktest.RoundTripper{ + T: t, + In: json.RawMessage(`{"name":"test_key","subject":"user_123","description":"Test API key"}`), + Out: json.RawMessage(responseJSON), + Method: http.MethodPost, + Path: "/v1/api_keys", + }, + } + + client := NewClient(config) + params := &CreateParams{ + Name: clerk.String("test_key"), + Subject: clerk.String("user_123"), + Description: clerk.String("Test API key"), + } + + apiKey, err := client.Create(context.Background(), params) + require.NoError(t, err) + assert.Equal(t, "ak_test123", apiKey.ID) + assert.Equal(t, "test_key", apiKey.Name) + assert.Equal(t, "user_123", apiKey.Subject) + assert.Equal(t, "ak_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", apiKey.Secret) +} + +func TestList(t *testing.T) { + t.Parallel() + + response := map[string]interface{}{ + "data": []map[string]interface{}{ + { + "object": "api_key", + "id": "ak_test123", + "type": "api_key", + "subject": "user_123", + "name": "test_key", + "description": "Test API key", + "claims": map[string]interface{}{}, + "scopes": []string{}, + "revoked": false, + "revocation_reason": nil, + "expired": false, + "expiration": nil, + "created_by": "user_123", + "last_used_at": nil, + "created_at": 1640995200, + "updated_at": 1640995200, + }, + }, + "total_count": int64(1), + } + + responseJSON, _ := json.Marshal(response) + + config := &clerk.ClientConfig{} + config.HTTPClient = &http.Client{ + Transport: &clerktest.RoundTripper{ + T: t, + Out: json.RawMessage(responseJSON), + Method: http.MethodGet, + Path: "/v1/api_keys", + Query: &url.Values{ + "subject": []string{"user_123"}, + "type": []string{"api_key"}, + }, + }, + } + + client := NewClient(config) + params := &ListParams{ + Subject: clerk.String("user_123"), + Type: clerk.String("api_key"), + } + + apiKeys, err := client.List(context.Background(), params) + require.NoError(t, err) + assert.Equal(t, int64(1), apiKeys.TotalCount) + assert.Len(t, apiKeys.APIKeys, 1) + assert.Equal(t, "ak_test123", apiKeys.APIKeys[0].ID) +} +func TestGet(t *testing.T) { + t.Parallel() + + response := map[string]interface{}{ + "object": "api_key", + "id": "ak_test123", + "type": "api_key", + "subject": "user_123", + "name": "test_key", + "description": "Test API key", + "claims": map[string]interface{}{}, + "scopes": []string{}, + "revoked": false, + "revocation_reason": nil, + "expired": false, + "expiration": nil, + "created_by": "user_123", + "last_used_at": nil, + "created_at": 1640995200, + "updated_at": 1640995200, + } + + responseJSON, _ := json.Marshal(response) + + config := &clerk.ClientConfig{} + config.HTTPClient = &http.Client{ + Transport: &clerktest.RoundTripper{ + T: t, + Out: json.RawMessage(responseJSON), + Method: http.MethodGet, + Path: "/v1/api_keys/ak_test123", + }, + } + + client := NewClient(config) + apiKey, err := client.Get(context.Background(), "ak_test123") + require.NoError(t, err) + assert.Equal(t, "ak_test123", apiKey.ID) + assert.Equal(t, "test_key", apiKey.Name) + assert.Equal(t, "user_123", apiKey.Subject) + assert.Equal(t, "Test API key", *apiKey.Description) + assert.False(t, apiKey.Revoked) +} + +func TestGetSecret(t *testing.T) { + t.Parallel() + + response := map[string]interface{}{ + "secret": "ak_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", + } + + responseJSON, _ := json.Marshal(response) + + config := &clerk.ClientConfig{} + config.HTTPClient = &http.Client{ + Transport: &clerktest.RoundTripper{ + T: t, + Out: json.RawMessage(responseJSON), + Method: http.MethodGet, + Path: "/v1/api_keys/ak_test123/secret", + }, + } + + client := NewClient(config) + secret, err := client.GetSecret(context.Background(), "ak_test123") + require.NoError(t, err) + assert.Equal(t, "ak_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", secret.Secret) +} + +func TestUpdate(t *testing.T) { + t.Parallel() + + response := map[string]interface{}{ + "object": "api_key", + "id": "ak_test123", + "type": "api_key", + "subject": "user_123", + "name": "test_key", + "description": "Updated description", + "claims": map[string]interface{}{}, + "scopes": []string{}, + "revoked": false, + "revocation_reason": nil, + "expired": false, + "expiration": nil, + "created_by": "user_123", + "last_used_at": nil, + "created_at": 1640995200, + "updated_at": 1640995200, + } + + responseJSON, _ := json.Marshal(response) + + config := &clerk.ClientConfig{} + config.HTTPClient = &http.Client{ + Transport: &clerktest.RoundTripper{ + T: t, + In: json.RawMessage(`{"description":"Updated description"}`), + Out: json.RawMessage(responseJSON), + Method: http.MethodPatch, + Path: "/v1/api_keys/ak_test123", + }, + } + + client := NewClient(config) + params := &UpdateParams{ + Description: clerk.String("Updated description"), + } + + apiKey, err := client.Update(context.Background(), "ak_test123", params) + require.NoError(t, err) + assert.Equal(t, "Updated description", *apiKey.Description) +} + +func TestUpdateWithSecondsUntilExpiration(t *testing.T) { + t.Parallel() + + response := map[string]interface{}{ + "object": "api_key", + "id": "ak_test123", + "type": "api_key", + "subject": "user_123", + "name": "test_key", + "description": "Updated with expiration", + "claims": map[string]interface{}{}, + "scopes": []string{}, + "revoked": false, + "revocation_reason": nil, + "expired": false, + "expiration": 1716883200, + "created_by": "user_123", + "last_used_at": nil, + "created_at": 1640995200, + "updated_at": 1640995200, + } + + responseJSON, _ := json.Marshal(response) + + config := &clerk.ClientConfig{} + config.HTTPClient = &http.Client{ + Transport: &clerktest.RoundTripper{ + T: t, + In: json.RawMessage(`{"description":"Updated with expiration","seconds_until_expiration":3600}`), + Out: json.RawMessage(responseJSON), + Method: http.MethodPatch, + Path: "/v1/api_keys/ak_test123", + }, + } + + client := NewClient(config) + params := &UpdateParams{ + Description: clerk.String("Updated with expiration"), + SecondsUntilExpiration: clerk.Int64(3600), + } + + apiKey, err := client.Update(context.Background(), "ak_test123", params) + require.NoError(t, err) + assert.Equal(t, "Updated with expiration", *apiKey.Description) + assert.Equal(t, int64(1716883200), *apiKey.Expiration) +} + +func TestUpdateSecondsUntilExpirationOnly(t *testing.T) { + t.Parallel() + + response := map[string]interface{}{ + "object": "api_key", + "id": "ak_test123", + "type": "api_key", + "subject": "user_123", + "name": "test_key", + "description": "Test API key", + "claims": map[string]interface{}{}, + "scopes": []string{}, + "revoked": false, + "revocation_reason": nil, + "expired": false, + "expiration": 1716883200, + "created_by": "user_123", + "last_used_at": nil, + "created_at": 1640995200, + "updated_at": 1640995200, + } + + responseJSON, _ := json.Marshal(response) + + config := &clerk.ClientConfig{} + config.HTTPClient = &http.Client{ + Transport: &clerktest.RoundTripper{ + T: t, + In: json.RawMessage(`{"seconds_until_expiration":3600}`), + Out: json.RawMessage(responseJSON), + Method: http.MethodPatch, + Path: "/v1/api_keys/ak_test123", + }, + } + + client := NewClient(config) + params := &UpdateParams{ + SecondsUntilExpiration: clerk.Int64(3600), + } + + apiKey, err := client.Update(context.Background(), "ak_test123", params) + require.NoError(t, err) + assert.Equal(t, "ak_test123", apiKey.ID) + assert.Equal(t, int64(1716883200), *apiKey.Expiration) +} + +func TestDelete(t *testing.T) { + t.Parallel() + + response := map[string]interface{}{ + "object": "api_key", + "id": "ak_test123", + "deleted": true, + } + + responseJSON, _ := json.Marshal(response) + + config := &clerk.ClientConfig{} + config.HTTPClient = &http.Client{ + Transport: &clerktest.RoundTripper{ + T: t, + Out: json.RawMessage(responseJSON), + Method: http.MethodDelete, + Path: "/v1/api_keys/ak_test123", + }, + } + + client := NewClient(config) + deletedResource, err := client.Delete(context.Background(), "ak_test123") + require.NoError(t, err) + assert.Equal(t, "ak_test123", deletedResource.ID) + assert.True(t, deletedResource.Deleted) + assert.Equal(t, "api_key", deletedResource.Object) +} + +func TestRevoke(t *testing.T) { + t.Parallel() + + response := map[string]interface{}{ + "object": "api_key", + "id": "ak_test123", + "type": "api_key", + "subject": "user_123", + "name": "test_key", + "description": "Test API key", + "claims": map[string]interface{}{}, + "scopes": []string{}, + "revoked": true, + "revocation_reason": "Security breach", + "expired": false, + "expiration": nil, + "created_by": "user_123", + "last_used_at": nil, + "created_at": 1640995200, + "updated_at": 1640995200, + } + + responseJSON, _ := json.Marshal(response) + + config := &clerk.ClientConfig{} + config.HTTPClient = &http.Client{ + Transport: &clerktest.RoundTripper{ + T: t, + In: json.RawMessage(`{"revocation_reason":"Security breach"}`), + Out: json.RawMessage(responseJSON), + Method: http.MethodPost, + Path: "/v1/api_keys/ak_test123/revoke", + }, + } + + client := NewClient(config) + params := &RevokeParams{ + RevocationReason: clerk.String("Security breach"), + } + + apiKey, err := client.Revoke(context.Background(), "ak_test123", params) + require.NoError(t, err) + assert.True(t, apiKey.Revoked) + assert.Equal(t, "Security breach", *apiKey.RevocationReason) +} + +func TestVerify(t *testing.T) { + t.Parallel() + + response := map[string]interface{}{ + "object": "api_key", + "id": "ak_test123", + "type": "api_key", + "subject": "user_123", + "name": "test_key", + "description": "Test API key", + "claims": map[string]interface{}{}, + "scopes": []string{}, + "revoked": false, + "revocation_reason": nil, + "expired": false, + "expiration": nil, + "created_by": "user_123", + "last_used_at": nil, + "created_at": 1640995200, + "updated_at": 1640995200, + } + + responseJSON, _ := json.Marshal(response) + + config := &clerk.ClientConfig{} + config.HTTPClient = &http.Client{ + Transport: &clerktest.RoundTripper{ + T: t, + In: json.RawMessage(`{"secret":"ak_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"}`), + Out: json.RawMessage(responseJSON), + Method: http.MethodPost, + Path: "/v1/api_keys/verify", + }, + } + + client := NewClient(config) + params := &VerifyParams{ + Secret: "ak_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", + } + + apiKey, err := client.Verify(context.Background(), params) + require.NoError(t, err) + assert.Equal(t, "ak_test123", apiKey.ID) + assert.False(t, apiKey.Revoked) +}