summaryrefslogtreecommitdiffstats
path: root/tests/integration/api_quota_management_test.go
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--tests/integration/api_quota_management_test.go846
1 files changed, 846 insertions, 0 deletions
diff --git a/tests/integration/api_quota_management_test.go b/tests/integration/api_quota_management_test.go
new file mode 100644
index 0000000..6337e66
--- /dev/null
+++ b/tests/integration/api_quota_management_test.go
@@ -0,0 +1,846 @@
+// Copyright 2024 The Forgejo Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package integration
+
+import (
+ "fmt"
+ "net/http"
+ "testing"
+
+ auth_model "code.gitea.io/gitea/models/auth"
+ "code.gitea.io/gitea/models/db"
+ quota_model "code.gitea.io/gitea/models/quota"
+ "code.gitea.io/gitea/models/unittest"
+ user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/setting"
+ api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/test"
+ "code.gitea.io/gitea/routers"
+ "code.gitea.io/gitea/tests"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestAPIQuotaDisabled(t *testing.T) {
+ defer tests.PrepareTestEnv(t)()
+ defer test.MockVariableValue(&setting.Quota.Enabled, false)()
+ defer test.MockVariableValue(&testWebRoutes, routers.NormalRoutes())()
+
+ user := unittest.AssertExistsAndLoadBean(t, &user_model.User{IsAdmin: true})
+ session := loginUser(t, user.Name)
+
+ req := NewRequest(t, "GET", "/api/v1/user/quota")
+ session.MakeRequest(t, req, http.StatusNotFound)
+}
+
+func apiCreateUser(t *testing.T, username string) func() {
+ t.Helper()
+
+ admin := unittest.AssertExistsAndLoadBean(t, &user_model.User{IsAdmin: true})
+ session := loginUser(t, admin.Name)
+ token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeAll)
+
+ mustChangePassword := false
+ req := NewRequestWithJSON(t, "POST", "/api/v1/admin/users", api.CreateUserOption{
+ Email: "api+" + username + "@example.com",
+ Username: username,
+ Password: "password",
+ MustChangePassword: &mustChangePassword,
+ }).AddTokenAuth(token)
+ session.MakeRequest(t, req, http.StatusCreated)
+
+ return func() {
+ req := NewRequest(t, "DELETE", "/api/v1/admin/users/"+username+"?purge=true").AddTokenAuth(token)
+ session.MakeRequest(t, req, http.StatusNoContent)
+ }
+}
+
+func TestAPIQuotaCreateGroupWithRules(t *testing.T) {
+ defer tests.PrepareTestEnv(t)()
+ defer test.MockVariableValue(&setting.Quota.Enabled, true)()
+ defer test.MockVariableValue(&testWebRoutes, routers.NormalRoutes())()
+
+ // Create two rules in advance
+ unlimited := int64(-1)
+ defer createQuotaRule(t, api.CreateQuotaRuleOptions{
+ Name: "unlimited",
+ Limit: &unlimited,
+ Subjects: []string{"size:all"},
+ })()
+ zero := int64(0)
+ defer createQuotaRule(t, api.CreateQuotaRuleOptions{
+ Name: "deny-git-lfs",
+ Limit: &zero,
+ Subjects: []string{"size:git:lfs"},
+ })()
+
+ // Log in as admin
+ admin := unittest.AssertExistsAndLoadBean(t, &user_model.User{IsAdmin: true})
+ adminSession := loginUser(t, admin.Name)
+ adminToken := getTokenForLoggedInUser(t, adminSession, auth_model.AccessTokenScopeAll)
+
+ // Create a new group, with rules specified
+ req := NewRequestWithJSON(t, "POST", "/api/v1/admin/quota/groups", api.CreateQuotaGroupOptions{
+ Name: "group-with-rules",
+ Rules: []api.CreateQuotaRuleOptions{
+ // First: an existing group, unlimited, name only
+ {
+ Name: "unlimited",
+ },
+ // Second: an existing group, deny-git-lfs, with different params
+ {
+ Name: "deny-git-lfs",
+ Limit: &unlimited,
+ },
+ // Third: an entirely new group
+ {
+ Name: "new-rule",
+ Subjects: []string{"size:assets:all"},
+ },
+ },
+ }).AddTokenAuth(adminToken)
+ resp := adminSession.MakeRequest(t, req, http.StatusCreated)
+ defer func() {
+ req := NewRequest(t, "DELETE", "/api/v1/admin/quota/groups/group-with-rules").AddTokenAuth(adminToken)
+ adminSession.MakeRequest(t, req, http.StatusNoContent)
+
+ req = NewRequest(t, "DELETE", "/api/v1/admin/quota/rules/new-rule").AddTokenAuth(adminToken)
+ adminSession.MakeRequest(t, req, http.StatusNoContent)
+ }()
+
+ // Verify that we created a group with rules included
+ var q api.QuotaGroup
+ DecodeJSON(t, resp, &q)
+
+ assert.Equal(t, "group-with-rules", q.Name)
+ assert.Len(t, q.Rules, 3)
+
+ // Verify that the previously existing rules are unchanged
+ rule, err := quota_model.GetRuleByName(db.DefaultContext, "unlimited")
+ require.NoError(t, err)
+ assert.NotNil(t, rule)
+ assert.EqualValues(t, -1, rule.Limit)
+ assert.EqualValues(t, quota_model.LimitSubjects{quota_model.LimitSubjectSizeAll}, rule.Subjects)
+
+ rule, err = quota_model.GetRuleByName(db.DefaultContext, "deny-git-lfs")
+ require.NoError(t, err)
+ assert.NotNil(t, rule)
+ assert.EqualValues(t, 0, rule.Limit)
+ assert.EqualValues(t, quota_model.LimitSubjects{quota_model.LimitSubjectSizeGitLFS}, rule.Subjects)
+
+ // Verify that the new rule was also created
+ rule, err = quota_model.GetRuleByName(db.DefaultContext, "new-rule")
+ require.NoError(t, err)
+ assert.NotNil(t, rule)
+ assert.EqualValues(t, 0, rule.Limit)
+ assert.EqualValues(t, quota_model.LimitSubjects{quota_model.LimitSubjectSizeAssetsAll}, rule.Subjects)
+
+ t.Run("invalid rule spec", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ req := NewRequestWithJSON(t, "POST", "/api/v1/admin/quota/groups", api.CreateQuotaGroupOptions{
+ Name: "group-with-invalid-rule-spec",
+ Rules: []api.CreateQuotaRuleOptions{
+ {
+ Name: "rule-with-wrong-spec",
+ Subjects: []string{"valid:false"},
+ },
+ },
+ }).AddTokenAuth(adminToken)
+ adminSession.MakeRequest(t, req, http.StatusUnprocessableEntity)
+ })
+}
+
+func TestAPIQuotaEmptyState(t *testing.T) {
+ defer tests.PrepareTestEnv(t)()
+ defer test.MockVariableValue(&setting.Quota.Enabled, true)()
+ defer test.MockVariableValue(&testWebRoutes, routers.NormalRoutes())()
+
+ username := "quota-empty-user"
+ defer apiCreateUser(t, username)()
+ session := loginUser(t, username)
+ token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeAll)
+
+ t.Run("#/admin/users/quota-empty-user/quota", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ admin := unittest.AssertExistsAndLoadBean(t, &user_model.User{IsAdmin: true})
+ adminSession := loginUser(t, admin.Name)
+ adminToken := getTokenForLoggedInUser(t, adminSession, auth_model.AccessTokenScopeAll)
+
+ req := NewRequest(t, "GET", "/api/v1/admin/users/quota-empty-user/quota").AddTokenAuth(adminToken)
+ resp := adminSession.MakeRequest(t, req, http.StatusOK)
+
+ var q api.QuotaInfo
+ DecodeJSON(t, resp, &q)
+
+ assert.EqualValues(t, api.QuotaUsed{}, q.Used)
+ assert.Empty(t, q.Groups)
+ })
+
+ t.Run("#/user/quota", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ req := NewRequest(t, "GET", "/api/v1/user/quota").AddTokenAuth(token)
+ resp := session.MakeRequest(t, req, http.StatusOK)
+
+ var q api.QuotaInfo
+ DecodeJSON(t, resp, &q)
+
+ assert.EqualValues(t, api.QuotaUsed{}, q.Used)
+ assert.Empty(t, q.Groups)
+
+ t.Run("#/user/quota/artifacts", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ req := NewRequest(t, "GET", "/api/v1/user/quota/artifacts").AddTokenAuth(token)
+ resp := session.MakeRequest(t, req, http.StatusOK)
+
+ var q api.QuotaUsedArtifactList
+ DecodeJSON(t, resp, &q)
+
+ assert.Empty(t, q)
+ })
+
+ t.Run("#/user/quota/attachments", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ req := NewRequest(t, "GET", "/api/v1/user/quota/attachments").AddTokenAuth(token)
+ resp := session.MakeRequest(t, req, http.StatusOK)
+
+ var q api.QuotaUsedAttachmentList
+ DecodeJSON(t, resp, &q)
+
+ assert.Empty(t, q)
+ })
+
+ t.Run("#/user/quota/packages", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ req := NewRequest(t, "GET", "/api/v1/user/quota/packages").AddTokenAuth(token)
+ resp := session.MakeRequest(t, req, http.StatusOK)
+
+ var q api.QuotaUsedPackageList
+ DecodeJSON(t, resp, &q)
+
+ assert.Empty(t, q)
+ })
+ })
+}
+
+func createQuotaRule(t *testing.T, opts api.CreateQuotaRuleOptions) func() {
+ t.Helper()
+
+ admin := unittest.AssertExistsAndLoadBean(t, &user_model.User{IsAdmin: true})
+ adminSession := loginUser(t, admin.Name)
+ adminToken := getTokenForLoggedInUser(t, adminSession, auth_model.AccessTokenScopeAll)
+
+ req := NewRequestWithJSON(t, "POST", "/api/v1/admin/quota/rules", opts).AddTokenAuth(adminToken)
+ adminSession.MakeRequest(t, req, http.StatusCreated)
+
+ return func() {
+ req := NewRequestf(t, "DELETE", "/api/v1/admin/quota/rules/%s", opts.Name).AddTokenAuth(adminToken)
+ adminSession.MakeRequest(t, req, http.StatusNoContent)
+ }
+}
+
+func createQuotaGroup(t *testing.T, name string) func() {
+ t.Helper()
+
+ admin := unittest.AssertExistsAndLoadBean(t, &user_model.User{IsAdmin: true})
+ adminSession := loginUser(t, admin.Name)
+ adminToken := getTokenForLoggedInUser(t, adminSession, auth_model.AccessTokenScopeAll)
+
+ req := NewRequestWithJSON(t, "POST", "/api/v1/admin/quota/groups", api.CreateQuotaGroupOptions{
+ Name: name,
+ }).AddTokenAuth(adminToken)
+ adminSession.MakeRequest(t, req, http.StatusCreated)
+
+ return func() {
+ req := NewRequestf(t, "DELETE", "/api/v1/admin/quota/groups/%s", name).AddTokenAuth(adminToken)
+ adminSession.MakeRequest(t, req, http.StatusNoContent)
+ }
+}
+
+func TestAPIQuotaAdminRoutesRules(t *testing.T) {
+ defer tests.PrepareTestEnv(t)()
+ defer test.MockVariableValue(&setting.Quota.Enabled, true)()
+ defer test.MockVariableValue(&testWebRoutes, routers.NormalRoutes())()
+
+ admin := unittest.AssertExistsAndLoadBean(t, &user_model.User{IsAdmin: true})
+ adminSession := loginUser(t, admin.Name)
+ adminToken := getTokenForLoggedInUser(t, adminSession, auth_model.AccessTokenScopeAll)
+
+ zero := int64(0)
+ oneKb := int64(1024)
+
+ t.Run("adminCreateQuotaRule", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ req := NewRequestWithJSON(t, "POST", "/api/v1/admin/quota/rules", api.CreateQuotaRuleOptions{
+ Name: "deny-all",
+ Limit: &zero,
+ Subjects: []string{"size:all"},
+ }).AddTokenAuth(adminToken)
+ resp := adminSession.MakeRequest(t, req, http.StatusCreated)
+ defer func() {
+ req := NewRequest(t, "DELETE", "/api/v1/admin/quota/rules/deny-all").AddTokenAuth(adminToken)
+ adminSession.MakeRequest(t, req, http.StatusNoContent)
+ }()
+
+ var q api.QuotaRuleInfo
+ DecodeJSON(t, resp, &q)
+
+ assert.Equal(t, "deny-all", q.Name)
+ assert.EqualValues(t, 0, q.Limit)
+ assert.EqualValues(t, []string{"size:all"}, q.Subjects)
+
+ rule, err := quota_model.GetRuleByName(db.DefaultContext, "deny-all")
+ require.NoError(t, err)
+ assert.EqualValues(t, 0, rule.Limit)
+
+ t.Run("unhappy path", func(t *testing.T) {
+ t.Run("missing options", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ req := NewRequestWithJSON(t, "POST", "/api/v1/admin/quota/rules", nil).AddTokenAuth(adminToken)
+ adminSession.MakeRequest(t, req, http.StatusUnprocessableEntity)
+ })
+
+ t.Run("invalid subjects", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ req := NewRequestWithJSON(t, "POST", "/api/v1/admin/quota/rules", api.CreateQuotaRuleOptions{
+ Name: "invalid-subjects",
+ Limit: &zero,
+ Subjects: []string{"valid:false"},
+ }).AddTokenAuth(adminToken)
+ adminSession.MakeRequest(t, req, http.StatusUnprocessableEntity)
+ })
+
+ t.Run("trying to add an existing rule", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ rule := api.CreateQuotaRuleOptions{
+ Name: "double-rule",
+ Limit: &zero,
+ }
+
+ defer createQuotaRule(t, rule)()
+
+ req := NewRequestWithJSON(t, "POST", "/api/v1/admin/quota/rules", rule).AddTokenAuth(adminToken)
+ adminSession.MakeRequest(t, req, http.StatusConflict)
+ })
+ })
+ })
+
+ t.Run("adminDeleteQuotaRule", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ createQuotaRule(t, api.CreateQuotaRuleOptions{
+ Name: "deny-all",
+ Limit: &zero,
+ Subjects: []string{"size:all"},
+ })
+
+ req := NewRequest(t, "DELETE", "/api/v1/admin/quota/rules/deny-all").AddTokenAuth(adminToken)
+ adminSession.MakeRequest(t, req, http.StatusNoContent)
+
+ rule, err := quota_model.GetRuleByName(db.DefaultContext, "deny-all")
+ require.NoError(t, err)
+ assert.Nil(t, rule)
+
+ t.Run("unhappy path", func(t *testing.T) {
+ t.Run("nonexistent rule", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ req := NewRequest(t, "DELETE", "/api/v1/admin/quota/rules/does-not-exist").AddTokenAuth(adminToken)
+ adminSession.MakeRequest(t, req, http.StatusNotFound)
+ })
+ })
+ })
+
+ t.Run("adminEditQuotaRule", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ defer createQuotaRule(t, api.CreateQuotaRuleOptions{
+ Name: "deny-all",
+ Limit: &zero,
+ Subjects: []string{"size:all"},
+ })()
+
+ req := NewRequestWithJSON(t, "PATCH", "/api/v1/admin/quota/rules/deny-all", api.EditQuotaRuleOptions{
+ Limit: &oneKb,
+ }).AddTokenAuth(adminToken)
+ resp := adminSession.MakeRequest(t, req, http.StatusOK)
+
+ var q api.QuotaRuleInfo
+ DecodeJSON(t, resp, &q)
+ assert.EqualValues(t, 1024, q.Limit)
+
+ rule, err := quota_model.GetRuleByName(db.DefaultContext, "deny-all")
+ require.NoError(t, err)
+ assert.EqualValues(t, 1024, rule.Limit)
+
+ t.Run("no options", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ req := NewRequestWithJSON(t, "PATCH", "/api/v1/admin/quota/rules/deny-all", nil).AddTokenAuth(adminToken)
+ adminSession.MakeRequest(t, req, http.StatusOK)
+ })
+
+ t.Run("unhappy path", func(t *testing.T) {
+ t.Run("nonexistent rule", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ req := NewRequestWithJSON(t, "PATCH", "/api/v1/admin/quota/rules/does-not-exist", api.EditQuotaRuleOptions{
+ Limit: &oneKb,
+ }).AddTokenAuth(adminToken)
+ adminSession.MakeRequest(t, req, http.StatusNotFound)
+ })
+
+ t.Run("invalid subjects", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ req := NewRequestWithJSON(t, "PATCH", "/api/v1/admin/quota/rules/deny-all", api.EditQuotaRuleOptions{
+ Subjects: &[]string{"valid:false"},
+ }).AddTokenAuth(adminToken)
+ adminSession.MakeRequest(t, req, http.StatusUnprocessableEntity)
+ })
+ })
+ })
+
+ t.Run("adminListQuotaRules", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ defer createQuotaRule(t, api.CreateQuotaRuleOptions{
+ Name: "deny-all",
+ Limit: &zero,
+ Subjects: []string{"size:all"},
+ })()
+
+ req := NewRequest(t, "GET", "/api/v1/admin/quota/rules").AddTokenAuth(adminToken)
+ resp := adminSession.MakeRequest(t, req, http.StatusOK)
+
+ var rules []api.QuotaRuleInfo
+ DecodeJSON(t, resp, &rules)
+
+ assert.Len(t, rules, 1)
+ assert.Equal(t, "deny-all", rules[0].Name)
+ assert.EqualValues(t, 0, rules[0].Limit)
+ })
+}
+
+func TestAPIQuotaAdminRoutesGroups(t *testing.T) {
+ defer tests.PrepareTestEnv(t)()
+ defer test.MockVariableValue(&setting.Quota.Enabled, true)()
+ defer test.MockVariableValue(&testWebRoutes, routers.NormalRoutes())()
+
+ admin := unittest.AssertExistsAndLoadBean(t, &user_model.User{IsAdmin: true})
+ adminSession := loginUser(t, admin.Name)
+ adminToken := getTokenForLoggedInUser(t, adminSession, auth_model.AccessTokenScopeAll)
+
+ zero := int64(0)
+
+ ruleDenyAll := api.CreateQuotaRuleOptions{
+ Name: "deny-all",
+ Limit: &zero,
+ Subjects: []string{"size:all"},
+ }
+
+ username := "quota-test-user"
+ defer apiCreateUser(t, username)()
+
+ t.Run("adminCreateQuotaGroup", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ req := NewRequestWithJSON(t, "POST", "/api/v1/admin/quota/groups", api.CreateQuotaGroupOptions{
+ Name: "default",
+ }).AddTokenAuth(adminToken)
+ resp := adminSession.MakeRequest(t, req, http.StatusCreated)
+ defer func() {
+ req := NewRequest(t, "DELETE", "/api/v1/admin/quota/groups/default").AddTokenAuth(adminToken)
+ adminSession.MakeRequest(t, req, http.StatusNoContent)
+ }()
+
+ var q api.QuotaGroup
+ DecodeJSON(t, resp, &q)
+
+ assert.Equal(t, "default", q.Name)
+ assert.Empty(t, q.Rules)
+
+ group, err := quota_model.GetGroupByName(db.DefaultContext, "default")
+ require.NoError(t, err)
+ assert.Equal(t, "default", group.Name)
+ assert.Empty(t, group.Rules)
+
+ t.Run("unhappy path", func(t *testing.T) {
+ t.Run("missing options", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ req := NewRequestWithJSON(t, "POST", "/api/v1/admin/quota/groups", nil).AddTokenAuth(adminToken)
+ adminSession.MakeRequest(t, req, http.StatusUnprocessableEntity)
+ })
+
+ t.Run("trying to add an existing group", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ defer createQuotaGroup(t, "duplicate")()
+
+ req := NewRequestWithJSON(t, "POST", "/api/v1/admin/quota/groups", api.CreateQuotaGroupOptions{
+ Name: "duplicate",
+ }).AddTokenAuth(adminToken)
+ adminSession.MakeRequest(t, req, http.StatusConflict)
+ })
+ })
+ })
+
+ t.Run("adminDeleteQuotaGroup", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ createQuotaGroup(t, "default")
+
+ req := NewRequest(t, "DELETE", "/api/v1/admin/quota/groups/default").AddTokenAuth(adminToken)
+ adminSession.MakeRequest(t, req, http.StatusNoContent)
+
+ group, err := quota_model.GetGroupByName(db.DefaultContext, "default")
+ require.NoError(t, err)
+ assert.Nil(t, group)
+
+ t.Run("unhappy path", func(t *testing.T) {
+ t.Run("non-existing group", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ req := NewRequest(t, "DELETE", "/api/v1/admin/quota/groups/does-not-exist").AddTokenAuth(adminToken)
+ adminSession.MakeRequest(t, req, http.StatusNotFound)
+ })
+ })
+ })
+
+ t.Run("adminAddRuleToQuotaGroup", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+ defer createQuotaGroup(t, "default")()
+ defer createQuotaRule(t, ruleDenyAll)()
+
+ req := NewRequest(t, "PUT", "/api/v1/admin/quota/groups/default/rules/deny-all").AddTokenAuth(adminToken)
+ adminSession.MakeRequest(t, req, http.StatusNoContent)
+
+ group, err := quota_model.GetGroupByName(db.DefaultContext, "default")
+ require.NoError(t, err)
+ assert.Len(t, group.Rules, 1)
+ assert.Equal(t, "deny-all", group.Rules[0].Name)
+
+ t.Run("unhappy path", func(t *testing.T) {
+ t.Run("non-existing group", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ req := NewRequest(t, "PUT", "/api/v1/admin/quota/groups/does-not-exist/rules/deny-all").AddTokenAuth(adminToken)
+ adminSession.MakeRequest(t, req, http.StatusNotFound)
+ })
+
+ t.Run("non-existing rule", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ req := NewRequest(t, "PUT", "/api/v1/admin/quota/groups/default/rules/does-not-exist").AddTokenAuth(adminToken)
+ adminSession.MakeRequest(t, req, http.StatusNotFound)
+ })
+ })
+ })
+
+ t.Run("adminRemoveRuleFromQuotaGroup", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+ defer createQuotaGroup(t, "default")()
+ defer createQuotaRule(t, ruleDenyAll)()
+
+ req := NewRequest(t, "PUT", "/api/v1/admin/quota/groups/default/rules/deny-all").AddTokenAuth(adminToken)
+ adminSession.MakeRequest(t, req, http.StatusNoContent)
+
+ req = NewRequest(t, "DELETE", "/api/v1/admin/quota/groups/default/rules/deny-all").AddTokenAuth(adminToken)
+ adminSession.MakeRequest(t, req, http.StatusNoContent)
+
+ group, err := quota_model.GetGroupByName(db.DefaultContext, "default")
+ require.NoError(t, err)
+ assert.Equal(t, "default", group.Name)
+ assert.Empty(t, group.Rules)
+
+ t.Run("unhappy path", func(t *testing.T) {
+ t.Run("non-existing group", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ req := NewRequest(t, "DELETE", "/api/v1/admin/quota/groups/does-not-exist/rules/deny-all").AddTokenAuth(adminToken)
+ adminSession.MakeRequest(t, req, http.StatusNotFound)
+ })
+
+ t.Run("non-existing rule", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ req := NewRequest(t, "DELETE", "/api/v1/admin/quota/groups/default/rules/does-not-exist").AddTokenAuth(adminToken)
+ adminSession.MakeRequest(t, req, http.StatusNotFound)
+ })
+
+ t.Run("rule not in group", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+ defer createQuotaRule(t, api.CreateQuotaRuleOptions{
+ Name: "rule-not-in-group",
+ Limit: &zero,
+ })()
+
+ req := NewRequest(t, "DELETE", "/api/v1/admin/quota/groups/default/rules/rule-not-in-group").AddTokenAuth(adminToken)
+ adminSession.MakeRequest(t, req, http.StatusNotFound)
+ })
+ })
+ })
+
+ t.Run("adminGetQuotaGroup", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+ defer createQuotaGroup(t, "default")()
+ defer createQuotaRule(t, ruleDenyAll)()
+
+ req := NewRequest(t, "PUT", "/api/v1/admin/quota/groups/default/rules/deny-all").AddTokenAuth(adminToken)
+ adminSession.MakeRequest(t, req, http.StatusNoContent)
+
+ req = NewRequest(t, "GET", "/api/v1/admin/quota/groups/default").AddTokenAuth(adminToken)
+ resp := adminSession.MakeRequest(t, req, http.StatusOK)
+
+ var q api.QuotaGroup
+ DecodeJSON(t, resp, &q)
+
+ assert.Equal(t, "default", q.Name)
+ assert.Len(t, q.Rules, 1)
+ assert.Equal(t, "deny-all", q.Rules[0].Name)
+
+ t.Run("unhappy path", func(t *testing.T) {
+ t.Run("non-existing group", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ req := NewRequest(t, "GET", "/api/v1/admin/quota/groups/does-not-exist").AddTokenAuth(adminToken)
+ adminSession.MakeRequest(t, req, http.StatusNotFound)
+ })
+ })
+ })
+
+ t.Run("adminListQuotaGroups", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+ defer createQuotaGroup(t, "default")()
+ defer createQuotaRule(t, ruleDenyAll)()
+
+ req := NewRequest(t, "PUT", "/api/v1/admin/quota/groups/default/rules/deny-all").AddTokenAuth(adminToken)
+ adminSession.MakeRequest(t, req, http.StatusNoContent)
+
+ req = NewRequest(t, "GET", "/api/v1/admin/quota/groups").AddTokenAuth(adminToken)
+ resp := adminSession.MakeRequest(t, req, http.StatusOK)
+
+ var q api.QuotaGroupList
+ DecodeJSON(t, resp, &q)
+
+ assert.Len(t, q, 1)
+ assert.Equal(t, "default", q[0].Name)
+ assert.Len(t, q[0].Rules, 1)
+ assert.Equal(t, "deny-all", q[0].Rules[0].Name)
+ })
+
+ t.Run("adminAddUserToQuotaGroup", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+ defer createQuotaGroup(t, "default")()
+
+ req := NewRequestf(t, "PUT", "/api/v1/admin/quota/groups/default/users/%s", username).AddTokenAuth(adminToken)
+ adminSession.MakeRequest(t, req, http.StatusNoContent)
+
+ user := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: username})
+
+ groups, err := quota_model.GetGroupsForUser(db.DefaultContext, user.ID)
+ require.NoError(t, err)
+ assert.Len(t, groups, 1)
+ assert.Equal(t, "default", groups[0].Name)
+
+ t.Run("unhappy path", func(t *testing.T) {
+ t.Run("non-existing group", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ req := NewRequestf(t, "PUT", "/api/v1/admin/quota/groups/does-not-exist/users/%s", username).AddTokenAuth(adminToken)
+ adminSession.MakeRequest(t, req, http.StatusNotFound)
+ })
+
+ t.Run("non-existing user", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ req := NewRequest(t, "PUT", "/api/v1/admin/quota/groups/default/users/this-user-does-not-exist").AddTokenAuth(adminToken)
+ adminSession.MakeRequest(t, req, http.StatusNotFound)
+ })
+
+ t.Run("user already added", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ req := NewRequest(t, "PUT", "/api/v1/admin/quota/groups/default/users/user1").AddTokenAuth(adminToken)
+ adminSession.MakeRequest(t, req, http.StatusNoContent)
+
+ req = NewRequest(t, "PUT", "/api/v1/admin/quota/groups/default/users/user1").AddTokenAuth(adminToken)
+ adminSession.MakeRequest(t, req, http.StatusConflict)
+ })
+ })
+ })
+
+ t.Run("adminRemoveUserFromQuotaGroup", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+ defer createQuotaGroup(t, "default")()
+
+ req := NewRequestf(t, "PUT", "/api/v1/admin/quota/groups/default/users/%s", username).AddTokenAuth(adminToken)
+ adminSession.MakeRequest(t, req, http.StatusNoContent)
+
+ req = NewRequestf(t, "DELETE", "/api/v1/admin/quota/groups/default/users/%s", username).AddTokenAuth(adminToken)
+ adminSession.MakeRequest(t, req, http.StatusNoContent)
+
+ user := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: username})
+ groups, err := quota_model.GetGroupsForUser(db.DefaultContext, user.ID)
+ require.NoError(t, err)
+ assert.Empty(t, groups)
+
+ t.Run("unhappy path", func(t *testing.T) {
+ t.Run("non-existing group", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ req := NewRequestf(t, "DELETE", "/api/v1/admin/quota/groups/does-not-exist/users/%s", username).AddTokenAuth(adminToken)
+ adminSession.MakeRequest(t, req, http.StatusNotFound)
+ })
+
+ t.Run("non-existing user", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ req := NewRequest(t, "DELETE", "/api/v1/admin/quota/groups/default/users/does-not-exist").AddTokenAuth(adminToken)
+ adminSession.MakeRequest(t, req, http.StatusNotFound)
+ })
+
+ t.Run("user not in group", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ req := NewRequest(t, "DELETE", "/api/v1/admin/quota/groups/default/users/user1").AddTokenAuth(adminToken)
+ adminSession.MakeRequest(t, req, http.StatusNotFound)
+ })
+ })
+ })
+
+ t.Run("adminListUsersInQuotaGroup", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+ defer createQuotaGroup(t, "default")()
+
+ req := NewRequestf(t, "PUT", "/api/v1/admin/quota/groups/default/users/%s", username).AddTokenAuth(adminToken)
+ adminSession.MakeRequest(t, req, http.StatusNoContent)
+
+ req = NewRequest(t, "GET", "/api/v1/admin/quota/groups/default/users").AddTokenAuth(adminToken)
+ resp := adminSession.MakeRequest(t, req, http.StatusOK)
+
+ var q []api.User
+ DecodeJSON(t, resp, &q)
+
+ assert.Len(t, q, 1)
+ assert.Equal(t, username, q[0].UserName)
+
+ t.Run("unhappy path", func(t *testing.T) {
+ t.Run("non-existing group", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ req := NewRequest(t, "GET", "/api/v1/admin/quota/groups/does-not-exist/users").AddTokenAuth(adminToken)
+ adminSession.MakeRequest(t, req, http.StatusNotFound)
+ })
+ })
+ })
+
+ t.Run("adminSetUserQuotaGroups", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+ defer createQuotaGroup(t, "default")()
+ defer createQuotaGroup(t, "test-1")()
+ defer createQuotaGroup(t, "test-2")()
+
+ req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/admin/users/%s/quota/groups", username), api.SetUserQuotaGroupsOptions{
+ Groups: &[]string{"default", "test-1", "test-2"},
+ }).AddTokenAuth(adminToken)
+ adminSession.MakeRequest(t, req, http.StatusNoContent)
+
+ user := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: username})
+
+ groups, err := quota_model.GetGroupsForUser(db.DefaultContext, user.ID)
+ require.NoError(t, err)
+ assert.Len(t, groups, 3)
+
+ t.Run("unhappy path", func(t *testing.T) {
+ t.Run("non-existing user", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ req := NewRequestWithJSON(t, "POST", "/api/v1/admin/users/does-not-exist/quota/groups", api.SetUserQuotaGroupsOptions{
+ Groups: &[]string{"default", "test-1", "test-2"},
+ }).AddTokenAuth(adminToken)
+ adminSession.MakeRequest(t, req, http.StatusNotFound)
+ })
+
+ t.Run("non-existing group", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/admin/users/%s/quota/groups", username), api.SetUserQuotaGroupsOptions{
+ Groups: &[]string{"default", "test-1", "test-2", "this-group-does-not-exist"},
+ }).AddTokenAuth(adminToken)
+ adminSession.MakeRequest(t, req, http.StatusUnprocessableEntity)
+ })
+ })
+ })
+}
+
+func TestAPIQuotaUserRoutes(t *testing.T) {
+ defer tests.PrepareTestEnv(t)()
+ defer test.MockVariableValue(&setting.Quota.Enabled, true)()
+ defer test.MockVariableValue(&testWebRoutes, routers.NormalRoutes())()
+
+ admin := unittest.AssertExistsAndLoadBean(t, &user_model.User{IsAdmin: true})
+ adminSession := loginUser(t, admin.Name)
+ adminToken := getTokenForLoggedInUser(t, adminSession, auth_model.AccessTokenScopeAll)
+
+ // Create a test user
+ username := "quota-test-user-routes"
+ defer apiCreateUser(t, username)()
+ session := loginUser(t, username)
+ token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeAll)
+
+ // Set up rules & groups for the user
+ defer createQuotaGroup(t, "user-routes-deny")()
+ defer createQuotaGroup(t, "user-routes-1kb")()
+
+ zero := int64(0)
+ ruleDenyAll := api.CreateQuotaRuleOptions{
+ Name: "user-routes-deny-all",
+ Limit: &zero,
+ Subjects: []string{"size:all"},
+ }
+ defer createQuotaRule(t, ruleDenyAll)()
+ oneKb := int64(1024)
+ rule1KbStuff := api.CreateQuotaRuleOptions{
+ Name: "user-routes-1kb",
+ Limit: &oneKb,
+ Subjects: []string{"size:assets:attachments:releases", "size:assets:packages:all", "size:git:lfs"},
+ }
+ defer createQuotaRule(t, rule1KbStuff)()
+
+ req := NewRequest(t, "PUT", "/api/v1/admin/quota/groups/user-routes-deny/rules/user-routes-deny-all").AddTokenAuth(adminToken)
+ adminSession.MakeRequest(t, req, http.StatusNoContent)
+ req = NewRequest(t, "PUT", "/api/v1/admin/quota/groups/user-routes-1kb/rules/user-routes-1kb").AddTokenAuth(adminToken)
+ adminSession.MakeRequest(t, req, http.StatusNoContent)
+
+ req = NewRequestf(t, "PUT", "/api/v1/admin/quota/groups/user-routes-deny/users/%s", username).AddTokenAuth(adminToken)
+ adminSession.MakeRequest(t, req, http.StatusNoContent)
+ req = NewRequestf(t, "PUT", "/api/v1/admin/quota/groups/user-routes-1kb/users/%s", username).AddTokenAuth(adminToken)
+ adminSession.MakeRequest(t, req, http.StatusNoContent)
+
+ t.Run("userGetQuota", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ req := NewRequest(t, "GET", "/api/v1/user/quota").AddTokenAuth(token)
+ resp := session.MakeRequest(t, req, http.StatusOK)
+
+ var q api.QuotaInfo
+ DecodeJSON(t, resp, &q)
+
+ assert.Len(t, q.Groups, 2)
+ assert.Len(t, q.Groups[0].Rules, 1)
+ assert.Len(t, q.Groups[1].Rules, 1)
+ })
+}