Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion go/apps/api/openapi/openapi-generated.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1762,7 +1762,6 @@ components:
type: string
minLength: 1
maxLength: 255
pattern: "^[a-zA-Z][a-zA-Z0-9_./-]*$"
description: The id or name of the namespace.
example: sms.sign_up
cost:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ properties:
namespace:
type: string
minLength: 1
maxLength: 255 # Reasonable upper bound for namespace identifiers
pattern: "^[a-zA-Z][a-zA-Z0-9_./-]*$"
maxLength: 255
description: The id or name of the namespace.
example: sms.sign_up
cost:
Comment thread
Flo4604 marked this conversation as resolved.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
additionalProperties: false
properties:
namespace:
description: The id or name of the rate limit namespace to list overrides
for.
description: The id or name of the rate limit namespace to list overrides for.
type: string
minLength: 1
maxLength: 255
Comment thread
Flo4604 marked this conversation as resolved.
Expand Down
82 changes: 81 additions & 1 deletion go/apps/api/routes/v2_ratelimit_limit/200_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ func TestLimitSuccessfully(t *testing.T) {

// Test basic rate limiting
t.Run("basic rate limiting", func(t *testing.T) {

namespaceID, namespaceName := createNamespace(t, h)
rootKey := h.CreateRootKey(h.Resources().UserWorkspace.ID, fmt.Sprintf("ratelimit.%s.limit", namespaceID))

Expand Down Expand Up @@ -256,6 +255,87 @@ func TestLimitSuccessfully(t *testing.T) {
require.Equal(t, int64(1), res2.Body.Data.Limit)
require.Equal(t, int64(0), res2.Body.Data.Remaining)
})
// Test namespace accepts any characters within length bounds
t.Run("namespace accepts any characters within length bounds", func(t *testing.T) {
// Test various character types in namespace names
testCases := []struct {
name string
namespaceName string
}{
{
name: "special characters",
namespaceName: "!@#$%^&*()_+-=[]{}|;':\",./<>?",
},
{
name: "unicode characters",
namespaceName: "αβγδε_测试_테스트_🚀🎉",
},
{
name: "mixed alphanumeric and special",
namespaceName: "test-123_ABC.xyz@domain",
},
{
name: "spaces and tabs",
namespaceName: "namespace with spaces and tabs",
},
{
name: "control characters",
namespaceName: "test\nwith\rnewlines\tand\btabs",
},
{
name: "colon and slash delimiters",
namespaceName: "api:v1:calls/outbound",
},
{
name: "leading and trailing whitespace",
namespaceName: " leading and trailing ",
},
{
name: "minimum length (1 char)",
namespaceName: "a",
},
{
name: "maximum length (255 chars)",
namespaceName: strings.Repeat("x", 255),
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Create namespace with the test name
namespaceID := uid.New(uid.RatelimitNamespacePrefix)
err := db.Query.InsertRatelimitNamespace(t.Context(), h.DB.RW(), db.InsertRatelimitNamespaceParams{
ID: namespaceID,
WorkspaceID: h.Resources().UserWorkspace.ID,
Name: tc.namespaceName,
CreatedAt: time.Now().UnixMilli(),
})
require.NoError(t, err)

// Create root key for this namespace
rootKey := h.CreateRootKey(h.Resources().UserWorkspace.ID, fmt.Sprintf("ratelimit.%s.limit", namespaceID))

headers := http.Header{
"Content-Type": {"application/json"},
"Authorization": {fmt.Sprintf("Bearer %s", rootKey)},
}

req := handler.Request{
Namespace: tc.namespaceName,
Identifier: uid.New("test"),
Limit: 100,
Duration: 60000,
}

// Should be able to use the namespace with any characters
res := testutil.CallRoute[handler.Request, handler.Response](h, route, headers, req)
require.Equal(t, 200, res.Status, "expected 200 for namespace: %s", tc.namespaceName)
require.NotNil(t, res.Body)
require.True(t, res.Body.Data.Success, "Rate limit should succeed for namespace: %s", tc.namespaceName)
})
}
})
Comment thread
Flo4604 marked this conversation as resolved.
Comment thread
Flo4604 marked this conversation as resolved.

t.Run("rate limiting with active override", func(t *testing.T) {
namespaceID, namespaceName := createNamespace(t, h)
rootKey := h.CreateRootKey(h.Resources().UserWorkspace.ID, fmt.Sprintf("ratelimit.%s.limit", namespaceID))
Expand Down
51 changes: 23 additions & 28 deletions go/apps/api/routes/v2_ratelimit_limit/400_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,34 +70,29 @@ func TestBadRequests(t *testing.T) {
require.Greater(t, len(res.Body.Error.Errors), 0)
})

// Uncomment and adapt these tests if needed
Comment thread
Flo4604 marked this conversation as resolved.
/*
t.Run("missing namespace", func(t *testing.T) {
req := openapi.V2RatelimitLimitRequestBody{
Identifier: "user_123",
Limit: 100,
Duration: 60000,
}

headers := http.Header{
"Content-Type": {"application/json"},
"Authorization": {fmt.Sprintf("Bearer %s", h.CreateRootKey(h.Resources().UserWorkspace.ID, "ratelimit.*.limit"))},
}

res := testutil.CallRoute[handler.Request, openapi.BadRequestError](h, route, headers, req)

require.Equal(t, 400, res.Status, "expected 400, sent: %+v, received: %s", req, res.RawBody)
require.NotNil(t, res.Body)

require.Equal(t, "https://unkey.com/docs/errors/unkey/application/invalid_input", res.Body.Type)
require.Equal(t, "POST request body for '/v2/ratelimit.limit' failed to validate schema", res.Body.Detail)
require.Equal(t, http.StatusBadRequest, res.Body.Status)
require.Equal(t, "Bad Request", res.Body.Title)
require.NotEmpty(t, res.Body.RequestId)
require.Greater(t, len(res.Body.Errors), 0)
require.Nil(t, res.Body.Instance)
})
*/
t.Run("missing namespace in request", func(t *testing.T) {
// Create a root key with wildcard permission for any namespace
rootKey := h.CreateRootKey(h.Resources().UserWorkspace.ID, "ratelimit.*.limit")

headers := http.Header{
"Content-Type": {"application/json"},
"Authorization": {fmt.Sprintf("Bearer %s", rootKey)},
}

// Request with empty namespace
req := handler.Request{
// namespace missing
Identifier: "user_123",
Limit: 100,
Duration: 60000,
}

// Should return an error for missing namespace
res := testutil.CallRoute[handler.Request, openapi.BadRequestErrorResponse](h, route, headers, req)
require.Equal(t, 400, res.Status) // API returns 200 even for rate limit errors
require.NotNil(t, res.Body)
require.Equal(t, http.StatusBadRequest, res.Status, "expected 400, received: %s", req, res.Status)
})
Comment thread
Flo4604 marked this conversation as resolved.
Comment thread
Flo4604 marked this conversation as resolved.
}

func TestMissingAuthorizationHeader(t *testing.T) {
Expand Down