 fed2d81c44
			
		
	
	
	fed2d81c44
	
	
	
		
			
			Co-authored-by: Renovate Bot <forgejo-renovate-action@forgejo.org> Co-committed-by: Renovate Bot <forgejo-renovate-action@forgejo.org>
		
			
				
	
	
		
			1147 lines
		
	
	
	
		
			31 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			1147 lines
		
	
	
	
		
			31 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright 2024 The Forgejo Authors. All rights reserved.
 | |
| // SPDX-License-Identifier: MIT
 | |
| 
 | |
| package integration
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"mime/multipart"
 | |
| 	"net/http"
 | |
| 	"net/http/httptest"
 | |
| 	"net/url"
 | |
| 	"strings"
 | |
| 	"testing"
 | |
| 
 | |
| 	"forgejo.org/models/db"
 | |
| 	org_model "forgejo.org/models/organization"
 | |
| 	quota_model "forgejo.org/models/quota"
 | |
| 	repo_model "forgejo.org/models/repo"
 | |
| 	"forgejo.org/models/unittest"
 | |
| 	user_model "forgejo.org/models/user"
 | |
| 	"forgejo.org/modules/git"
 | |
| 	"forgejo.org/modules/setting"
 | |
| 	api "forgejo.org/modules/structs"
 | |
| 	"forgejo.org/modules/test"
 | |
| 	"forgejo.org/routers"
 | |
| 	forgejo_context "forgejo.org/services/context"
 | |
| 	repo_service "forgejo.org/services/repository"
 | |
| 	"forgejo.org/tests"
 | |
| 
 | |
| 	gouuid "github.com/google/uuid"
 | |
| 	"github.com/stretchr/testify/assert"
 | |
| 	"github.com/stretchr/testify/require"
 | |
| )
 | |
| 
 | |
| func TestWebQuotaEnforcementRepoMigrate(t *testing.T) {
 | |
| 	onGiteaRun(t, func(t *testing.T, u *url.URL) {
 | |
| 		env := createQuotaWebEnv(t)
 | |
| 		defer env.Cleanup()
 | |
| 
 | |
| 		env.RunVisitAndPostToPageTests(t, "/repo/migrate", &Payload{
 | |
| 			"repo_name":  "migration-test",
 | |
| 			"clone_addr": env.Users.Limited.Repo.Link() + ".git",
 | |
| 			"service":    fmt.Sprintf("%d", api.ForgejoService),
 | |
| 		}, http.StatusOK)
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestWebQuotaEnforcementRepoCreate(t *testing.T) {
 | |
| 	onGiteaRun(t, func(t *testing.T, u *url.URL) {
 | |
| 		env := createQuotaWebEnv(t)
 | |
| 		defer env.Cleanup()
 | |
| 
 | |
| 		env.RunVisitAndPostToPageTests(t, "/repo/create", nil, http.StatusOK)
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestWebQuotaEnforcementRepoFork(t *testing.T) {
 | |
| 	onGiteaRun(t, func(t *testing.T, u *url.URL) {
 | |
| 		env := createQuotaWebEnv(t)
 | |
| 		defer env.Cleanup()
 | |
| 
 | |
| 		page := fmt.Sprintf("%s/fork", env.Users.Limited.Repo.Link())
 | |
| 		env.RunVisitAndPostToPageTests(t, page, &Payload{
 | |
| 			"repo_name": "fork-test",
 | |
| 		}, http.StatusSeeOther)
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestWebQuotaEnforcementIssueAttachment(t *testing.T) {
 | |
| 	onGiteaRun(t, func(t *testing.T, u *url.URL) {
 | |
| 		env := createQuotaWebEnv(t)
 | |
| 		defer env.Cleanup()
 | |
| 
 | |
| 		// Uploading to our repo => 413
 | |
| 		env.As(t, env.Users.Limited).
 | |
| 			With(Context{Repo: env.Users.Limited.Repo}).
 | |
| 			CreateIssueAttachment("test.txt").
 | |
| 			ExpectStatus(http.StatusRequestEntityTooLarge)
 | |
| 
 | |
| 		// Uploading to the limited org repo => 413
 | |
| 		env.As(t, env.Users.Limited).
 | |
| 			With(Context{Repo: env.Orgs.Limited.Repo}).
 | |
| 			CreateIssueAttachment("test.txt").
 | |
| 			ExpectStatus(http.StatusRequestEntityTooLarge)
 | |
| 
 | |
| 		// Uploading to the unlimited org repo => 200
 | |
| 		env.As(t, env.Users.Limited).
 | |
| 			With(Context{Repo: env.Orgs.Unlimited.Repo}).
 | |
| 			CreateIssueAttachment("test.txt").
 | |
| 			ExpectStatus(http.StatusOK)
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestWebQuotaEnforcementMirrorSync(t *testing.T) {
 | |
| 	onGiteaRun(t, func(t *testing.T, u *url.URL) {
 | |
| 		env := createQuotaWebEnv(t)
 | |
| 		defer env.Cleanup()
 | |
| 
 | |
| 		var mirrorRepo *repo_model.Repository
 | |
| 
 | |
| 		env.As(t, env.Users.Limited).
 | |
| 			WithoutQuota(func(ctx *quotaWebEnvAsContext) {
 | |
| 				mirrorRepo = ctx.CreateMirror()
 | |
| 			}).
 | |
| 			With(Context{
 | |
| 				Repo:    mirrorRepo,
 | |
| 				Payload: &Payload{"action": "mirror-sync"},
 | |
| 			}).
 | |
| 			PostToPage(mirrorRepo.Link() + "/settings").
 | |
| 			ExpectStatus(http.StatusOK).
 | |
| 			ExpectFlashMessage("Quota exceeded, not pulling changes.")
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestWebQuotaEnforcementRepoContentEditing(t *testing.T) {
 | |
| 	onGiteaRun(t, func(t *testing.T, u *url.URL) {
 | |
| 		env := createQuotaWebEnv(t)
 | |
| 		defer env.Cleanup()
 | |
| 
 | |
| 		// We're only going to test the GET requests here, because the entire combo
 | |
| 		// is covered by a route check.
 | |
| 
 | |
| 		// Lets create a helper!
 | |
| 		runCheck := func(t *testing.T, path string, successStatus int) {
 | |
| 			t.Run("#"+path, func(t *testing.T) {
 | |
| 				defer tests.PrintCurrentTest(t)()
 | |
| 
 | |
| 				// Uploading to a limited user's repo => 413
 | |
| 				env.As(t, env.Users.Limited).
 | |
| 					VisitPage(env.Users.Limited.Repo.Link() + path).
 | |
| 					ExpectStatus(http.StatusRequestEntityTooLarge)
 | |
| 
 | |
| 				// Limited org => 413
 | |
| 				env.As(t, env.Users.Limited).
 | |
| 					VisitPage(env.Orgs.Limited.Repo.Link() + path).
 | |
| 					ExpectStatus(http.StatusRequestEntityTooLarge)
 | |
| 
 | |
| 				// Unlimited org => 200
 | |
| 				env.As(t, env.Users.Limited).
 | |
| 					VisitPage(env.Orgs.Unlimited.Repo.Link() + path).
 | |
| 					ExpectStatus(successStatus)
 | |
| 			})
 | |
| 		}
 | |
| 
 | |
| 		paths := []string{
 | |
| 			"/_new/main",
 | |
| 			"/_edit/main/README.md",
 | |
| 			"/_delete/main",
 | |
| 			"/_upload/main",
 | |
| 			"/_diffpatch/main",
 | |
| 		}
 | |
| 
 | |
| 		for _, path := range paths {
 | |
| 			runCheck(t, path, http.StatusOK)
 | |
| 		}
 | |
| 
 | |
| 		// Run another check for `_cherrypick`. It's cumbersome to dig out a valid
 | |
| 		// commit id, so we'll use a fake, and treat 404 as a success: it's not 413,
 | |
| 		// and that's all we care about for this test.
 | |
| 		runCheck(t, "/_cherrypick/92cfceb39d57d914ed8b14d0e37643de0797ae56/main", http.StatusNotFound)
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestWebQuotaEnforcementRepoBranches(t *testing.T) {
 | |
| 	onGiteaRun(t, func(t *testing.T, u *url.URL) {
 | |
| 		env := createQuotaWebEnv(t)
 | |
| 		defer env.Cleanup()
 | |
| 
 | |
| 		t.Run("create", func(t *testing.T) {
 | |
| 			defer tests.PrintCurrentTest(t)()
 | |
| 
 | |
| 			runTest := func(t *testing.T, path string) {
 | |
| 				t.Run("#"+path, func(t *testing.T) {
 | |
| 					defer tests.PrintCurrentTest(t)()
 | |
| 
 | |
| 					env.As(t, env.Users.Limited).
 | |
| 						With(Context{Payload: &Payload{"new_branch_name": "quota"}}).
 | |
| 						PostToRepoPage("/branches/_new" + path).
 | |
| 						ExpectStatus(http.StatusRequestEntityTooLarge)
 | |
| 
 | |
| 					env.As(t, env.Users.Limited).
 | |
| 						With(Context{
 | |
| 							Payload: &Payload{"new_branch_name": "quota"},
 | |
| 							Repo:    env.Orgs.Limited.Repo,
 | |
| 						}).
 | |
| 						PostToRepoPage("/branches/_new" + path).
 | |
| 						ExpectStatus(http.StatusRequestEntityTooLarge)
 | |
| 
 | |
| 					env.As(t, env.Users.Limited).
 | |
| 						With(Context{
 | |
| 							Payload: &Payload{"new_branch_name": "quota"},
 | |
| 							Repo:    env.Orgs.Unlimited.Repo,
 | |
| 						}).
 | |
| 						PostToRepoPage("/branches/_new" + path).
 | |
| 						ExpectStatus(http.StatusNotFound)
 | |
| 				})
 | |
| 			}
 | |
| 
 | |
| 			// We're testing the first two against things that don't exist, so that
 | |
| 			// all three consistently return 404 if no quota enforcement happens.
 | |
| 			runTest(t, "/branch/no-such-branch")
 | |
| 			runTest(t, "/tag/no-such-tag")
 | |
| 			runTest(t, "/commit/92cfceb39d57d914ed8b14d0e37643de0797ae56")
 | |
| 		})
 | |
| 
 | |
| 		t.Run("delete & restore", func(t *testing.T) {
 | |
| 			defer tests.PrintCurrentTest(t)()
 | |
| 
 | |
| 			env.As(t, env.Users.Limited).
 | |
| 				WithoutQuota(func(ctx *quotaWebEnvAsContext) {
 | |
| 					ctx.With(Context{Payload: &Payload{"new_branch_name": "to-delete"}}).
 | |
| 						PostToRepoPage("/branches/_new/branch/main").
 | |
| 						ExpectStatus(http.StatusSeeOther)
 | |
| 				})
 | |
| 
 | |
| 			env.As(t, env.Users.Limited).
 | |
| 				PostToRepoPage("/branches/delete?name=to-delete").
 | |
| 				ExpectStatus(http.StatusOK)
 | |
| 
 | |
| 			env.As(t, env.Users.Limited).
 | |
| 				PostToRepoPage("/branches/restore?name=to-delete").
 | |
| 				ExpectStatus(http.StatusOK)
 | |
| 		})
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestWebQuotaEnforcementRepoReleases(t *testing.T) {
 | |
| 	onGiteaRun(t, func(t *testing.T, u *url.URL) {
 | |
| 		env := createQuotaWebEnv(t)
 | |
| 		defer env.Cleanup()
 | |
| 
 | |
| 		env.RunVisitAndPostToRepoPageTests(t, "/releases/new", &Payload{
 | |
| 			"tag_name":   "quota",
 | |
| 			"tag_target": "main",
 | |
| 			"title":      "test release",
 | |
| 		}, http.StatusSeeOther)
 | |
| 
 | |
| 		t.Run("attachments", func(t *testing.T) {
 | |
| 			defer tests.PrintCurrentTest(t)()
 | |
| 
 | |
| 			// Uploading to our repo => 413
 | |
| 			env.As(t, env.Users.Limited).
 | |
| 				With(Context{Repo: env.Users.Limited.Repo}).
 | |
| 				CreateReleaseAttachment("test.txt").
 | |
| 				ExpectStatus(http.StatusRequestEntityTooLarge)
 | |
| 
 | |
| 			// Uploading to the limited org repo => 413
 | |
| 			env.As(t, env.Users.Limited).
 | |
| 				With(Context{Repo: env.Orgs.Limited.Repo}).
 | |
| 				CreateReleaseAttachment("test.txt").
 | |
| 				ExpectStatus(http.StatusRequestEntityTooLarge)
 | |
| 
 | |
| 			// Uploading to the unlimited org repo => 200
 | |
| 			env.As(t, env.Users.Limited).
 | |
| 				With(Context{Repo: env.Orgs.Unlimited.Repo}).
 | |
| 				CreateReleaseAttachment("test.txt").
 | |
| 				ExpectStatus(http.StatusOK)
 | |
| 		})
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestWebQuotaEnforcementRepoPulls(t *testing.T) {
 | |
| 	onGiteaRun(t, func(t *testing.T, u *url.URL) {
 | |
| 		env := createQuotaWebEnv(t)
 | |
| 		defer env.Cleanup()
 | |
| 
 | |
| 		// To create a pull request, we first fork the two limited repos into the
 | |
| 		// unlimited org.
 | |
| 		env.As(t, env.Users.Limited).
 | |
| 			With(Context{Repo: env.Users.Limited.Repo}).
 | |
| 			ForkRepoInto(env.Orgs.Unlimited)
 | |
| 		env.As(t, env.Users.Limited).
 | |
| 			With(Context{Repo: env.Orgs.Limited.Repo}).
 | |
| 			ForkRepoInto(env.Orgs.Unlimited)
 | |
| 
 | |
| 		// Then, create pull requests from the forks, back to the main repos
 | |
| 		env.As(t, env.Users.Limited).
 | |
| 			With(Context{Repo: env.Users.Limited.Repo}).
 | |
| 			CreatePullFrom(env.Orgs.Unlimited)
 | |
| 		env.As(t, env.Users.Limited).
 | |
| 			With(Context{Repo: env.Orgs.Limited.Repo}).
 | |
| 			CreatePullFrom(env.Orgs.Unlimited)
 | |
| 
 | |
| 		// Trying to merge the pull request will fail for both, though, due to being
 | |
| 		// over quota.
 | |
| 		env.As(t, env.Users.Limited).
 | |
| 			With(Context{Repo: env.Users.Limited.Repo}).
 | |
| 			With(Context{Payload: &Payload{"do": "merge"}}).
 | |
| 			PostToRepoPage("/pulls/1/merge").
 | |
| 			ExpectStatus(http.StatusRequestEntityTooLarge)
 | |
| 
 | |
| 		env.As(t, env.Users.Limited).
 | |
| 			With(Context{Repo: env.Orgs.Limited.Repo}).
 | |
| 			With(Context{Payload: &Payload{"do": "merge"}}).
 | |
| 			PostToRepoPage("/pulls/1/merge").
 | |
| 			ExpectStatus(http.StatusRequestEntityTooLarge)
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestWebQuotaEnforcementRepoTransfer(t *testing.T) {
 | |
| 	onGiteaRun(t, func(t *testing.T, u *url.URL) {
 | |
| 		env := createQuotaWebEnv(t)
 | |
| 		defer env.Cleanup()
 | |
| 
 | |
| 		t.Run("direct transfer", func(t *testing.T) {
 | |
| 			defer tests.PrintCurrentTest(t)()
 | |
| 
 | |
| 			// Trying to transfer the repository to a limited organization fails.
 | |
| 			env.As(t, env.Users.Limited).
 | |
| 				With(Context{Repo: env.Users.Limited.Repo}).
 | |
| 				With(Context{Payload: &Payload{
 | |
| 					"action":         "transfer",
 | |
| 					"repo_name":      env.Users.Limited.Repo.FullName(),
 | |
| 					"new_owner_name": env.Orgs.Limited.Org.Name,
 | |
| 				}}).
 | |
| 				PostToRepoPage("/settings").
 | |
| 				ExpectStatus(http.StatusOK).
 | |
| 				ExpectFlashMessageContains("over quota", "The repository has not been transferred")
 | |
| 
 | |
| 			// Trying to transfer to a different, also limited user, also fails.
 | |
| 			env.As(t, env.Users.Limited).
 | |
| 				With(Context{Repo: env.Users.Limited.Repo}).
 | |
| 				With(Context{Payload: &Payload{
 | |
| 					"action":         "transfer",
 | |
| 					"repo_name":      env.Users.Limited.Repo.FullName(),
 | |
| 					"new_owner_name": env.Users.Contributor.User.Name,
 | |
| 				}}).
 | |
| 				PostToRepoPage("/settings").
 | |
| 				ExpectStatus(http.StatusOK).
 | |
| 				ExpectFlashMessageContains("over quota", "The repository has not been transferred")
 | |
| 		})
 | |
| 
 | |
| 		t.Run("accept & reject", func(t *testing.T) {
 | |
| 			defer tests.PrintCurrentTest(t)()
 | |
| 
 | |
| 			// Trying to transfer to a different user, with quota lifted, starts the transfer
 | |
| 			env.As(t, env.Users.Contributor).
 | |
| 				WithoutQuota(func(ctx *quotaWebEnvAsContext) {
 | |
| 					env.As(ctx.t, env.Users.Limited).
 | |
| 						With(Context{Repo: env.Users.Limited.Repo}).
 | |
| 						With(Context{Payload: &Payload{
 | |
| 							"action":         "transfer",
 | |
| 							"repo_name":      env.Users.Limited.Repo.FullName(),
 | |
| 							"new_owner_name": env.Users.Contributor.User.Name,
 | |
| 						}}).
 | |
| 						PostToRepoPage("/settings").
 | |
| 						ExpectStatus(http.StatusSeeOther).
 | |
| 						ExpectFlashCookieContains("This repository has been marked for transfer and awaits confirmation")
 | |
| 				})
 | |
| 
 | |
| 			// Trying to accept the transfer, with quota in effect, fails
 | |
| 			env.As(t, env.Users.Contributor).
 | |
| 				With(Context{Repo: env.Users.Limited.Repo}).
 | |
| 				PostToRepoPage("/action/accept_transfer").
 | |
| 				ExpectStatus(http.StatusRequestEntityTooLarge)
 | |
| 
 | |
| 			// Rejecting the transfer, however, succeeds.
 | |
| 			env.As(t, env.Users.Contributor).
 | |
| 				With(Context{Repo: env.Users.Limited.Repo}).
 | |
| 				PostToRepoPage("/action/reject_transfer").
 | |
| 				ExpectStatus(http.StatusSeeOther)
 | |
| 		})
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestGitQuotaEnforcement(t *testing.T) {
 | |
| 	onGiteaRun(t, func(t *testing.T, u *url.URL) {
 | |
| 		env := createQuotaWebEnv(t)
 | |
| 		defer env.Cleanup()
 | |
| 
 | |
| 		// Lets create a little helper that runs a task for three of our repos: the
 | |
| 		// user's repo, the limited org repo, and the unlimited org's.
 | |
| 		//
 | |
| 		// We expect the last one to always work, and the expected status of the
 | |
| 		// other two is decided by the caller.
 | |
| 		runTestForAllRepos := func(t *testing.T, task func(t *testing.T, repo *repo_model.Repository) error, expectSuccess bool) {
 | |
| 			t.Helper()
 | |
| 
 | |
| 			err := task(t, env.Users.Limited.Repo)
 | |
| 			if expectSuccess {
 | |
| 				require.NoError(t, err)
 | |
| 			} else {
 | |
| 				require.Error(t, err)
 | |
| 			}
 | |
| 
 | |
| 			err = task(t, env.Orgs.Limited.Repo)
 | |
| 			if expectSuccess {
 | |
| 				require.NoError(t, err)
 | |
| 			} else {
 | |
| 				require.Error(t, err)
 | |
| 			}
 | |
| 
 | |
| 			err = task(t, env.Orgs.Unlimited.Repo)
 | |
| 			require.NoError(t, err)
 | |
| 		}
 | |
| 
 | |
| 		// Run tests with quotas disabled
 | |
| 		runTestForAllReposWithQuotaDisabled := func(t *testing.T, task func(t *testing.T, repo *repo_model.Repository) error) {
 | |
| 			t.Helper()
 | |
| 
 | |
| 			t.Run("with quota disabled", func(t *testing.T) {
 | |
| 				defer tests.PrintCurrentTest(t)()
 | |
| 				defer test.MockVariableValue(&setting.Quota.Enabled, false)()
 | |
| 				defer test.MockVariableValue(&testWebRoutes, routers.NormalRoutes())()
 | |
| 
 | |
| 				runTestForAllRepos(t, task, true)
 | |
| 			})
 | |
| 		}
 | |
| 
 | |
| 		t.Run("push branch", func(t *testing.T) {
 | |
| 			defer tests.PrintCurrentTest(t)()
 | |
| 
 | |
| 			// Pushing a new branch is denied if the user is over quota.
 | |
| 			runTestForAllRepos(t, func(t *testing.T, repo *repo_model.Repository) error {
 | |
| 				return env.As(t, env.Users.Limited).
 | |
| 					With(Context{Repo: repo}).
 | |
| 					LocalClone(u).
 | |
| 					Push("HEAD:new-branch")
 | |
| 			}, false)
 | |
| 
 | |
| 			// Pushing a new branch is always allowed if quota is disabled
 | |
| 			runTestForAllReposWithQuotaDisabled(t, func(t *testing.T, repo *repo_model.Repository) error {
 | |
| 				return env.As(t, env.Users.Limited).
 | |
| 					With(Context{Repo: repo}).
 | |
| 					LocalClone(u).
 | |
| 					Push("HEAD:new-branch-wo-quota")
 | |
| 			})
 | |
| 		})
 | |
| 
 | |
| 		t.Run("push tag", func(t *testing.T) {
 | |
| 			defer tests.PrintCurrentTest(t)()
 | |
| 
 | |
| 			// Pushing a tag is denied if the user is over quota.
 | |
| 			runTestForAllRepos(t, func(t *testing.T, repo *repo_model.Repository) error {
 | |
| 				return env.As(t, env.Users.Limited).
 | |
| 					With(Context{Repo: repo}).
 | |
| 					LocalClone(u).
 | |
| 					Tag("new-tag").
 | |
| 					Push("new-tag")
 | |
| 			}, false)
 | |
| 
 | |
| 			// ...but succeeds if the quota feature is disabled
 | |
| 			runTestForAllReposWithQuotaDisabled(t, func(t *testing.T, repo *repo_model.Repository) error {
 | |
| 				return env.As(t, env.Users.Limited).
 | |
| 					With(Context{Repo: repo}).
 | |
| 					LocalClone(u).
 | |
| 					Tag("new-tag-wo-quota").
 | |
| 					Push("new-tag-wo-quota")
 | |
| 			})
 | |
| 		})
 | |
| 
 | |
| 		t.Run("Agit PR", func(t *testing.T) {
 | |
| 			defer tests.PrintCurrentTest(t)()
 | |
| 
 | |
| 			// Opening an Agit PR is *always* accepted. At least for now.
 | |
| 			runTestForAllRepos(t, func(t *testing.T, repo *repo_model.Repository) error {
 | |
| 				return env.As(t, env.Users.Limited).
 | |
| 					With(Context{Repo: repo}).
 | |
| 					LocalClone(u).
 | |
| 					Push("HEAD:refs/for/main/agit-pr-branch")
 | |
| 			}, true)
 | |
| 		})
 | |
| 
 | |
| 		t.Run("delete branch", func(t *testing.T) {
 | |
| 			defer tests.PrintCurrentTest(t)()
 | |
| 
 | |
| 			// Deleting a branch is respected, and allowed.
 | |
| 			err := env.As(t, env.Users.Limited).
 | |
| 				WithoutQuota(func(ctx *quotaWebEnvAsContext) {
 | |
| 					err := ctx.
 | |
| 						LocalClone(u).
 | |
| 						Push("HEAD:branch-to-delete")
 | |
| 					require.NoError(ctx.t, err)
 | |
| 				}).
 | |
| 				Push(":branch-to-delete")
 | |
| 			require.NoError(t, err)
 | |
| 		})
 | |
| 
 | |
| 		t.Run("delete tag", func(t *testing.T) {
 | |
| 			defer tests.PrintCurrentTest(t)()
 | |
| 
 | |
| 			// Deleting a tag is always allowed.
 | |
| 			err := env.As(t, env.Users.Limited).
 | |
| 				WithoutQuota(func(ctx *quotaWebEnvAsContext) {
 | |
| 					err := ctx.
 | |
| 						LocalClone(u).
 | |
| 						Tag("tag-to-delete").
 | |
| 						Push("tag-to-delete")
 | |
| 					require.NoError(ctx.t, err)
 | |
| 				}).
 | |
| 				Push(":tag-to-delete")
 | |
| 			require.NoError(t, err)
 | |
| 		})
 | |
| 
 | |
| 		t.Run("mixed push", func(t *testing.T) {
 | |
| 			t.Run("all deletes", func(t *testing.T) {
 | |
| 				defer tests.PrintCurrentTest(t)()
 | |
| 
 | |
| 				// Pushing multiple deletes is allowed.
 | |
| 				err := env.As(t, env.Users.Limited).
 | |
| 					WithoutQuota(func(ctx *quotaWebEnvAsContext) {
 | |
| 						err := ctx.
 | |
| 							LocalClone(u).
 | |
| 							Tag("mixed-push-tag").
 | |
| 							Push("mixed-push-tag", "HEAD:mixed-push-branch")
 | |
| 						require.NoError(ctx.t, err)
 | |
| 					}).
 | |
| 					Push(":mixed-push-tag", ":mixed-push-branch")
 | |
| 				require.NoError(t, err)
 | |
| 			})
 | |
| 
 | |
| 			t.Run("new & delete", func(t *testing.T) {
 | |
| 				defer tests.PrintCurrentTest(t)()
 | |
| 
 | |
| 				// Pushing a mix of deletions & a new branch is rejected together.
 | |
| 				err := env.As(t, env.Users.Limited).
 | |
| 					WithoutQuota(func(ctx *quotaWebEnvAsContext) {
 | |
| 						err := ctx.
 | |
| 							LocalClone(u).
 | |
| 							Tag("mixed-push-tag").
 | |
| 							Push("mixed-push-tag", "HEAD:mixed-push-branch")
 | |
| 						require.NoError(ctx.t, err)
 | |
| 					}).
 | |
| 					Push(":mixed-push-tag", ":mixed-push-branch", "HEAD:mixed-push-branch-new")
 | |
| 				require.Error(t, err)
 | |
| 
 | |
| 				// ...unless quota is disabled
 | |
| 				t.Run("with quota disabled", func(t *testing.T) {
 | |
| 					defer tests.PrintCurrentTest(t)()
 | |
| 					defer test.MockVariableValue(&setting.Quota.Enabled, false)()
 | |
| 					defer test.MockVariableValue(&testWebRoutes, routers.NormalRoutes())()
 | |
| 
 | |
| 					err := env.As(t, env.Users.Limited).
 | |
| 						WithoutQuota(func(ctx *quotaWebEnvAsContext) {
 | |
| 							err := ctx.
 | |
| 								LocalClone(u).
 | |
| 								Tag("mixed-push-tag-2").
 | |
| 								Push("mixed-push-tag-2", "HEAD:mixed-push-branch-2")
 | |
| 							require.NoError(ctx.t, err)
 | |
| 						}).
 | |
| 						Push(":mixed-push-tag-2", ":mixed-push-branch-2", "HEAD:mixed-push-branch-new-2")
 | |
| 					require.NoError(t, err)
 | |
| 				})
 | |
| 			})
 | |
| 		})
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestQuotaConfigDefault(t *testing.T) {
 | |
| 	onGiteaRun(t, func(t *testing.T, u *url.URL) {
 | |
| 		env := createQuotaWebEnv(t)
 | |
| 		defer env.Cleanup()
 | |
| 
 | |
| 		t.Run("with config-based default", func(t *testing.T) {
 | |
| 			defer tests.PrintCurrentTest(t)()
 | |
| 			defer test.MockVariableValue(&setting.Quota.Default.Total, 0)()
 | |
| 
 | |
| 			env.As(t, env.Users.Ungrouped).
 | |
| 				With(Context{
 | |
| 					Payload: &Payload{
 | |
| 						"uid":       env.Users.Ungrouped.ID().AsString(),
 | |
| 						"repo_name": "quota-config-default",
 | |
| 					},
 | |
| 				}).
 | |
| 				PostToPage("/repo/create").
 | |
| 				ExpectStatus(http.StatusRequestEntityTooLarge)
 | |
| 		})
 | |
| 
 | |
| 		t.Run("without config-based default", func(t *testing.T) {
 | |
| 			defer tests.PrintCurrentTest(t)()
 | |
| 
 | |
| 			env.As(t, env.Users.Ungrouped).
 | |
| 				With(Context{
 | |
| 					Payload: &Payload{
 | |
| 						"uid":       env.Users.Ungrouped.ID().AsString(),
 | |
| 						"repo_name": "quota-config-default",
 | |
| 					},
 | |
| 				}).
 | |
| 				PostToPage("/repo/create").
 | |
| 				ExpectStatus(http.StatusSeeOther)
 | |
| 		})
 | |
| 	})
 | |
| }
 | |
| 
 | |
| /**********************
 | |
|  * Here be dragons!   *
 | |
|  *                    *
 | |
|  *      .             *
 | |
|  *  .>   )\;`a__      *
 | |
|  * (  _ _)/ /-." ~~   *
 | |
|  *  `( )_ )/          *
 | |
|  *  <_  <_ sb/dwb     *
 | |
|  **********************/
 | |
| 
 | |
| type quotaWebEnv struct {
 | |
| 	Users quotaWebEnvUsers
 | |
| 	Orgs  quotaWebEnvOrgs
 | |
| 
 | |
| 	cleaners []func()
 | |
| }
 | |
| 
 | |
| type quotaWebEnvUsers struct {
 | |
| 	Limited     quotaWebEnvUser
 | |
| 	Contributor quotaWebEnvUser
 | |
| 	Ungrouped   quotaWebEnvUser
 | |
| }
 | |
| 
 | |
| type quotaWebEnvOrgs struct {
 | |
| 	Limited   quotaWebEnvOrg
 | |
| 	Unlimited quotaWebEnvOrg
 | |
| }
 | |
| 
 | |
| type quotaWebEnvOrg struct {
 | |
| 	Org *org_model.Organization
 | |
| 
 | |
| 	Repo *repo_model.Repository
 | |
| 
 | |
| 	QuotaGroup *quota_model.Group
 | |
| 	QuotaRule  *quota_model.Rule
 | |
| }
 | |
| 
 | |
| type quotaWebEnvUser struct {
 | |
| 	User    *user_model.User
 | |
| 	Session *TestSession
 | |
| 	Repo    *repo_model.Repository
 | |
| 
 | |
| 	QuotaGroup *quota_model.Group
 | |
| 	QuotaRule  *quota_model.Rule
 | |
| }
 | |
| 
 | |
| type Payload map[string]string
 | |
| 
 | |
| type quotaWebEnvAsContext struct {
 | |
| 	t *testing.T
 | |
| 
 | |
| 	Doer *quotaWebEnvUser
 | |
| 	Repo *repo_model.Repository
 | |
| 
 | |
| 	Payload Payload
 | |
| 
 | |
| 	CSRFPath *string
 | |
| 
 | |
| 	gitPath string
 | |
| 
 | |
| 	request  *RequestWrapper
 | |
| 	response *httptest.ResponseRecorder
 | |
| }
 | |
| 
 | |
| type Context struct {
 | |
| 	Repo     *repo_model.Repository
 | |
| 	Payload  *Payload
 | |
| 	CSRFPath *string
 | |
| }
 | |
| 
 | |
| func (ctx *quotaWebEnvAsContext) With(opts Context) *quotaWebEnvAsContext {
 | |
| 	if opts.Repo != nil {
 | |
| 		ctx.Repo = opts.Repo
 | |
| 	}
 | |
| 	if opts.Payload != nil {
 | |
| 		for key, value := range *opts.Payload {
 | |
| 			ctx.Payload[key] = value
 | |
| 		}
 | |
| 	}
 | |
| 	if opts.CSRFPath != nil {
 | |
| 		ctx.CSRFPath = opts.CSRFPath
 | |
| 	}
 | |
| 	return ctx
 | |
| }
 | |
| 
 | |
| func (ctx *quotaWebEnvAsContext) VisitPage(page string) *quotaWebEnvAsContext {
 | |
| 	ctx.t.Helper()
 | |
| 
 | |
| 	ctx.request = NewRequest(ctx.t, "GET", page)
 | |
| 
 | |
| 	return ctx
 | |
| }
 | |
| 
 | |
| func (ctx *quotaWebEnvAsContext) VisitRepoPage(page string) *quotaWebEnvAsContext {
 | |
| 	ctx.t.Helper()
 | |
| 
 | |
| 	return ctx.VisitPage(ctx.Repo.Link() + page)
 | |
| }
 | |
| 
 | |
| func (ctx *quotaWebEnvAsContext) ExpectStatus(status int) *quotaWebEnvAsContext {
 | |
| 	ctx.t.Helper()
 | |
| 
 | |
| 	ctx.response = ctx.Doer.Session.MakeRequest(ctx.t, ctx.request, status)
 | |
| 
 | |
| 	return ctx
 | |
| }
 | |
| 
 | |
| func (ctx *quotaWebEnvAsContext) ExpectFlashMessage(value string) {
 | |
| 	ctx.t.Helper()
 | |
| 
 | |
| 	htmlDoc := NewHTMLParser(ctx.t, ctx.response.Body)
 | |
| 	flashMessage := strings.TrimSpace(htmlDoc.Find(`.flash-message`).Text())
 | |
| 
 | |
| 	assert.Equal(ctx.t, value, flashMessage)
 | |
| }
 | |
| 
 | |
| func (ctx *quotaWebEnvAsContext) ExpectFlashMessageContains(parts ...string) {
 | |
| 	ctx.t.Helper()
 | |
| 
 | |
| 	htmlDoc := NewHTMLParser(ctx.t, ctx.response.Body)
 | |
| 	flashMessage := strings.TrimSpace(htmlDoc.Find(`.flash-message`).Text())
 | |
| 
 | |
| 	for _, part := range parts {
 | |
| 		assert.Contains(ctx.t, flashMessage, part)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (ctx *quotaWebEnvAsContext) ExpectFlashCookieContains(parts ...string) {
 | |
| 	ctx.t.Helper()
 | |
| 
 | |
| 	flashCookie := ctx.Doer.Session.GetCookie(forgejo_context.CookieNameFlash)
 | |
| 	assert.NotNil(ctx.t, flashCookie)
 | |
| 
 | |
| 	// Need to decode the cookie twice
 | |
| 	flashValue, err := url.QueryUnescape(flashCookie.Value)
 | |
| 	require.NoError(ctx.t, err)
 | |
| 	flashValue, err = url.QueryUnescape(flashValue)
 | |
| 	require.NoError(ctx.t, err)
 | |
| 
 | |
| 	for _, part := range parts {
 | |
| 		assert.Contains(ctx.t, flashValue, part)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (ctx *quotaWebEnvAsContext) ForkRepoInto(org quotaWebEnvOrg) {
 | |
| 	ctx.t.Helper()
 | |
| 
 | |
| 	ctx.
 | |
| 		With(Context{Payload: &Payload{
 | |
| 			"uid":       org.ID().AsString(),
 | |
| 			"repo_name": ctx.Repo.Name + "-fork",
 | |
| 		}}).
 | |
| 		PostToRepoPage("/fork").
 | |
| 		ExpectStatus(http.StatusSeeOther)
 | |
| }
 | |
| 
 | |
| func (ctx *quotaWebEnvAsContext) CreatePullFrom(org quotaWebEnvOrg) {
 | |
| 	ctx.t.Helper()
 | |
| 
 | |
| 	url := fmt.Sprintf("/compare/main...%s:main", org.Org.Name)
 | |
| 	ctx.
 | |
| 		With(Context{Payload: &Payload{
 | |
| 			"title": "PR test",
 | |
| 		}}).
 | |
| 		PostToRepoPage(url).
 | |
| 		ExpectStatus(http.StatusOK)
 | |
| }
 | |
| 
 | |
| func (ctx *quotaWebEnvAsContext) PostToPage(page string) *quotaWebEnvAsContext {
 | |
| 	ctx.t.Helper()
 | |
| 
 | |
| 	payload := ctx.Payload
 | |
| 	csrfPath := page
 | |
| 	if ctx.CSRFPath != nil {
 | |
| 		csrfPath = *ctx.CSRFPath
 | |
| 	}
 | |
| 
 | |
| 	payload["_csrf"] = GetCSRF(ctx.t, ctx.Doer.Session, csrfPath)
 | |
| 
 | |
| 	ctx.request = NewRequestWithValues(ctx.t, "POST", page, payload)
 | |
| 
 | |
| 	return ctx
 | |
| }
 | |
| 
 | |
| func (ctx *quotaWebEnvAsContext) PostToRepoPage(page string) *quotaWebEnvAsContext {
 | |
| 	ctx.t.Helper()
 | |
| 
 | |
| 	csrfPath := ctx.Repo.Link()
 | |
| 	return ctx.With(Context{CSRFPath: &csrfPath}).PostToPage(ctx.Repo.Link() + page)
 | |
| }
 | |
| 
 | |
| func (ctx *quotaWebEnvAsContext) CreateAttachment(filename, attachmentType string) *quotaWebEnvAsContext {
 | |
| 	ctx.t.Helper()
 | |
| 
 | |
| 	body := &bytes.Buffer{}
 | |
| 	image := generateImg()
 | |
| 
 | |
| 	// Setup multi-part
 | |
| 	writer := multipart.NewWriter(body)
 | |
| 	part, err := writer.CreateFormFile("file", filename)
 | |
| 	require.NoError(ctx.t, err)
 | |
| 	_, err = io.Copy(part, &image)
 | |
| 	require.NoError(ctx.t, err)
 | |
| 	err = writer.Close()
 | |
| 	require.NoError(ctx.t, err)
 | |
| 
 | |
| 	csrf := GetCSRF(ctx.t, ctx.Doer.Session, ctx.Repo.Link())
 | |
| 
 | |
| 	ctx.request = NewRequestWithBody(ctx.t, "POST", fmt.Sprintf("%s/%s/attachments", ctx.Repo.Link(), attachmentType), body)
 | |
| 	ctx.request.Header.Add("X-Csrf-Token", csrf)
 | |
| 	ctx.request.Header.Add("Content-Type", writer.FormDataContentType())
 | |
| 
 | |
| 	return ctx
 | |
| }
 | |
| 
 | |
| func (ctx *quotaWebEnvAsContext) CreateIssueAttachment(filename string) *quotaWebEnvAsContext {
 | |
| 	ctx.t.Helper()
 | |
| 
 | |
| 	return ctx.CreateAttachment(filename, "issues")
 | |
| }
 | |
| 
 | |
| func (ctx *quotaWebEnvAsContext) CreateReleaseAttachment(filename string) *quotaWebEnvAsContext {
 | |
| 	ctx.t.Helper()
 | |
| 
 | |
| 	return ctx.CreateAttachment(filename, "releases")
 | |
| }
 | |
| 
 | |
| func (ctx *quotaWebEnvAsContext) WithoutQuota(task func(ctx *quotaWebEnvAsContext)) *quotaWebEnvAsContext {
 | |
| 	ctx.t.Helper()
 | |
| 
 | |
| 	defer ctx.Doer.SetQuota(-1)()
 | |
| 	task(ctx)
 | |
| 
 | |
| 	return ctx
 | |
| }
 | |
| 
 | |
| func (ctx *quotaWebEnvAsContext) CreateMirror() *repo_model.Repository {
 | |
| 	ctx.t.Helper()
 | |
| 
 | |
| 	doer := ctx.Doer.User
 | |
| 
 | |
| 	repo, err := repo_service.CreateRepositoryDirectly(db.DefaultContext, doer, doer, repo_service.CreateRepoOptions{
 | |
| 		Name:     "test-mirror",
 | |
| 		IsMirror: true,
 | |
| 		Status:   repo_model.RepositoryBeingMigrated,
 | |
| 	})
 | |
| 	require.NoError(ctx.t, err)
 | |
| 
 | |
| 	return repo
 | |
| }
 | |
| 
 | |
| func (ctx *quotaWebEnvAsContext) LocalClone(u *url.URL) *quotaWebEnvAsContext {
 | |
| 	ctx.t.Helper()
 | |
| 
 | |
| 	gitPath := ctx.t.TempDir()
 | |
| 
 | |
| 	doGitInitTestRepository(gitPath, git.Sha1ObjectFormat)(ctx.t)
 | |
| 
 | |
| 	oldPath := u.Path
 | |
| 	oldUser := u.User
 | |
| 	defer func() {
 | |
| 		u.Path = oldPath
 | |
| 		u.User = oldUser
 | |
| 	}()
 | |
| 	u.Path = ctx.Repo.FullName() + ".git"
 | |
| 	u.User = url.UserPassword(ctx.Doer.User.LowerName, userPassword)
 | |
| 
 | |
| 	doGitAddRemote(gitPath, "origin", u)(ctx.t)
 | |
| 
 | |
| 	ctx.gitPath = gitPath
 | |
| 
 | |
| 	return ctx
 | |
| }
 | |
| 
 | |
| func (ctx *quotaWebEnvAsContext) Push(params ...string) error {
 | |
| 	ctx.t.Helper()
 | |
| 
 | |
| 	gitRepo, err := git.OpenRepository(git.DefaultContext, ctx.gitPath)
 | |
| 	require.NoError(ctx.t, err)
 | |
| 	defer gitRepo.Close()
 | |
| 
 | |
| 	_, _, err = git.NewCommand(git.DefaultContext, "push", "origin").
 | |
| 		AddArguments(git.ToTrustedCmdArgs(params)...).
 | |
| 		RunStdString(&git.RunOpts{Dir: ctx.gitPath})
 | |
| 
 | |
| 	return err
 | |
| }
 | |
| 
 | |
| func (ctx *quotaWebEnvAsContext) Tag(tagName string) *quotaWebEnvAsContext {
 | |
| 	ctx.t.Helper()
 | |
| 
 | |
| 	gitRepo, err := git.OpenRepository(git.DefaultContext, ctx.gitPath)
 | |
| 	require.NoError(ctx.t, err)
 | |
| 	defer gitRepo.Close()
 | |
| 
 | |
| 	_, _, err = git.NewCommand(git.DefaultContext, "tag").
 | |
| 		AddArguments(git.ToTrustedCmdArgs([]string{tagName})...).
 | |
| 		RunStdString(&git.RunOpts{Dir: ctx.gitPath})
 | |
| 	require.NoError(ctx.t, err)
 | |
| 
 | |
| 	return ctx
 | |
| }
 | |
| 
 | |
| func (user *quotaWebEnvUser) SetQuota(limit int64) func() {
 | |
| 	previousLimit := user.QuotaRule.Limit
 | |
| 
 | |
| 	user.QuotaRule.Limit = limit
 | |
| 	user.QuotaRule.Edit(db.DefaultContext, &limit, nil)
 | |
| 
 | |
| 	return func() {
 | |
| 		user.QuotaRule.Limit = previousLimit
 | |
| 		user.QuotaRule.Edit(db.DefaultContext, &previousLimit, nil)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (user *quotaWebEnvUser) ID() convertAs {
 | |
| 	return convertAs{
 | |
| 		asString: fmt.Sprintf("%d", user.User.ID),
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (org *quotaWebEnvOrg) ID() convertAs {
 | |
| 	return convertAs{
 | |
| 		asString: fmt.Sprintf("%d", org.Org.ID),
 | |
| 	}
 | |
| }
 | |
| 
 | |
| type convertAs struct {
 | |
| 	asString string
 | |
| }
 | |
| 
 | |
| func (cas convertAs) AsString() string {
 | |
| 	return cas.asString
 | |
| }
 | |
| 
 | |
| func (env *quotaWebEnv) Cleanup() {
 | |
| 	for i := len(env.cleaners) - 1; i >= 0; i-- {
 | |
| 		env.cleaners[i]()
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (env *quotaWebEnv) As(t *testing.T, user quotaWebEnvUser) *quotaWebEnvAsContext {
 | |
| 	t.Helper()
 | |
| 
 | |
| 	ctx := quotaWebEnvAsContext{
 | |
| 		t:    t,
 | |
| 		Doer: &user,
 | |
| 		Repo: user.Repo,
 | |
| 
 | |
| 		Payload: Payload{},
 | |
| 	}
 | |
| 	return &ctx
 | |
| }
 | |
| 
 | |
| func (env *quotaWebEnv) RunVisitAndPostToRepoPageTests(t *testing.T, page string, payload *Payload, successStatus int) {
 | |
| 	t.Helper()
 | |
| 
 | |
| 	// Visiting the user's repo page fails due to being over quota.
 | |
| 	env.As(t, env.Users.Limited).
 | |
| 		With(Context{Repo: env.Users.Limited.Repo}).
 | |
| 		VisitRepoPage(page).
 | |
| 		ExpectStatus(http.StatusRequestEntityTooLarge)
 | |
| 
 | |
| 	// Posting as the limited user, to the limited repo, fails due to being over
 | |
| 	// quota.
 | |
| 	csrfPath := env.Users.Limited.Repo.Link()
 | |
| 	env.As(t, env.Users.Limited).
 | |
| 		With(Context{
 | |
| 			Payload:  payload,
 | |
| 			CSRFPath: &csrfPath,
 | |
| 			Repo:     env.Users.Limited.Repo,
 | |
| 		}).
 | |
| 		PostToRepoPage(page).
 | |
| 		ExpectStatus(http.StatusRequestEntityTooLarge)
 | |
| 
 | |
| 	// Visiting the limited org's repo page fails due to being over quota.
 | |
| 	env.As(t, env.Users.Limited).
 | |
| 		With(Context{Repo: env.Orgs.Limited.Repo}).
 | |
| 		VisitRepoPage(page).
 | |
| 		ExpectStatus(http.StatusRequestEntityTooLarge)
 | |
| 
 | |
| 	// Posting as the limited user, to a limited org's repo, fails for the same
 | |
| 	// reason.
 | |
| 	csrfPath = env.Orgs.Limited.Repo.Link()
 | |
| 	env.As(t, env.Users.Limited).
 | |
| 		With(Context{
 | |
| 			Payload:  payload,
 | |
| 			CSRFPath: &csrfPath,
 | |
| 			Repo:     env.Orgs.Limited.Repo,
 | |
| 		}).
 | |
| 		PostToRepoPage(page).
 | |
| 		ExpectStatus(http.StatusRequestEntityTooLarge)
 | |
| 
 | |
| 	// Visiting the repo page for the unlimited org succeeds.
 | |
| 	env.As(t, env.Users.Limited).
 | |
| 		With(Context{Repo: env.Orgs.Unlimited.Repo}).
 | |
| 		VisitRepoPage(page).
 | |
| 		ExpectStatus(http.StatusOK)
 | |
| 
 | |
| 	// Posting as the limited user, to an unlimited org's repo, succeeds.
 | |
| 	csrfPath = env.Orgs.Unlimited.Repo.Link()
 | |
| 	env.As(t, env.Users.Limited).
 | |
| 		With(Context{
 | |
| 			Payload:  payload,
 | |
| 			CSRFPath: &csrfPath,
 | |
| 			Repo:     env.Orgs.Unlimited.Repo,
 | |
| 		}).
 | |
| 		PostToRepoPage(page).
 | |
| 		ExpectStatus(successStatus)
 | |
| }
 | |
| 
 | |
| func (env *quotaWebEnv) RunVisitAndPostToPageTests(t *testing.T, page string, payload *Payload, successStatus int) {
 | |
| 	t.Helper()
 | |
| 
 | |
| 	// Visiting the page is always fine.
 | |
| 	env.As(t, env.Users.Limited).
 | |
| 		VisitPage(page).
 | |
| 		ExpectStatus(http.StatusOK)
 | |
| 
 | |
| 	// Posting as the Limited user fails, because it is over quota.
 | |
| 	env.As(t, env.Users.Limited).
 | |
| 		With(Context{Payload: payload}).
 | |
| 		With(Context{
 | |
| 			Payload: &Payload{
 | |
| 				"uid": env.Users.Limited.ID().AsString(),
 | |
| 			},
 | |
| 		}).
 | |
| 		PostToPage(page).
 | |
| 		ExpectStatus(http.StatusRequestEntityTooLarge)
 | |
| 
 | |
| 	// Posting to a limited org also fails, for the same reason.
 | |
| 	env.As(t, env.Users.Limited).
 | |
| 		With(Context{Payload: payload}).
 | |
| 		With(Context{
 | |
| 			Payload: &Payload{
 | |
| 				"uid": env.Orgs.Limited.ID().AsString(),
 | |
| 			},
 | |
| 		}).
 | |
| 		PostToPage(page).
 | |
| 		ExpectStatus(http.StatusRequestEntityTooLarge)
 | |
| 
 | |
| 	// Posting to an unlimited repo works, however.
 | |
| 	env.As(t, env.Users.Limited).
 | |
| 		With(Context{Payload: payload}).
 | |
| 		With(Context{
 | |
| 			Payload: &Payload{
 | |
| 				"uid": env.Orgs.Unlimited.ID().AsString(),
 | |
| 			},
 | |
| 		}).
 | |
| 		PostToPage(page).
 | |
| 		ExpectStatus(successStatus)
 | |
| }
 | |
| 
 | |
| func createQuotaWebEnv(t *testing.T) *quotaWebEnv {
 | |
| 	t.Helper()
 | |
| 
 | |
| 	// *** helpers ***
 | |
| 
 | |
| 	makeUngroupedUser := func(t *testing.T) quotaWebEnvUser {
 | |
| 		t.Helper()
 | |
| 
 | |
| 		user := quotaWebEnvUser{}
 | |
| 
 | |
| 		// Create the user
 | |
| 		userName := gouuid.NewString()
 | |
| 		apiCreateUser(t, userName)
 | |
| 		user.User = unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: userName})
 | |
| 		user.Session = loginUser(t, userName)
 | |
| 
 | |
| 		// Create a repository for the user
 | |
| 		repo, _, _ := tests.CreateDeclarativeRepoWithOptions(t, user.User, tests.DeclarativeRepoOptions{})
 | |
| 		user.Repo = repo
 | |
| 
 | |
| 		return user
 | |
| 	}
 | |
| 
 | |
| 	// Create a user, its quota group & rule
 | |
| 	makeUser := func(t *testing.T, limit int64) quotaWebEnvUser {
 | |
| 		t.Helper()
 | |
| 
 | |
| 		user := makeUngroupedUser(t)
 | |
| 		userName := user.User.Name
 | |
| 
 | |
| 		// Create a quota group for them
 | |
| 		group, err := quota_model.CreateGroup(db.DefaultContext, userName)
 | |
| 		require.NoError(t, err)
 | |
| 		user.QuotaGroup = group
 | |
| 
 | |
| 		// Create a rule
 | |
| 		rule, err := quota_model.CreateRule(db.DefaultContext, userName, limit, quota_model.LimitSubjects{quota_model.LimitSubjectSizeAll})
 | |
| 		require.NoError(t, err)
 | |
| 		user.QuotaRule = rule
 | |
| 
 | |
| 		// Add the rule to the group
 | |
| 		err = group.AddRuleByName(db.DefaultContext, rule.Name)
 | |
| 		require.NoError(t, err)
 | |
| 
 | |
| 		// Add the user to the group
 | |
| 		err = group.AddUserByID(db.DefaultContext, user.User.ID)
 | |
| 		require.NoError(t, err)
 | |
| 
 | |
| 		return user
 | |
| 	}
 | |
| 
 | |
| 	// Create a user, its quota group & rule
 | |
| 	makeOrg := func(t *testing.T, owner *user_model.User, limit int64) quotaWebEnvOrg {
 | |
| 		t.Helper()
 | |
| 
 | |
| 		org := quotaWebEnvOrg{}
 | |
| 
 | |
| 		// Create the org
 | |
| 		userName := gouuid.NewString()
 | |
| 		org.Org = &org_model.Organization{
 | |
| 			Name: userName,
 | |
| 		}
 | |
| 		err := org_model.CreateOrganization(db.DefaultContext, org.Org, owner)
 | |
| 		require.NoError(t, err)
 | |
| 
 | |
| 		// Create a repository for the org
 | |
| 		orgUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: org.Org.ID})
 | |
| 		repo, _, _ := tests.CreateDeclarativeRepoWithOptions(t, orgUser, tests.DeclarativeRepoOptions{})
 | |
| 		org.Repo = repo
 | |
| 
 | |
| 		// Create a quota group for them
 | |
| 		group, err := quota_model.CreateGroup(db.DefaultContext, userName)
 | |
| 		require.NoError(t, err)
 | |
| 		org.QuotaGroup = group
 | |
| 
 | |
| 		// Create a rule
 | |
| 		rule, err := quota_model.CreateRule(db.DefaultContext, userName, limit, quota_model.LimitSubjects{quota_model.LimitSubjectSizeAll})
 | |
| 		require.NoError(t, err)
 | |
| 		org.QuotaRule = rule
 | |
| 
 | |
| 		// Add the rule to the group
 | |
| 		err = group.AddRuleByName(db.DefaultContext, rule.Name)
 | |
| 		require.NoError(t, err)
 | |
| 
 | |
| 		// Add the org to the group
 | |
| 		err = group.AddUserByID(db.DefaultContext, org.Org.ID)
 | |
| 		require.NoError(t, err)
 | |
| 
 | |
| 		return org
 | |
| 	}
 | |
| 
 | |
| 	env := quotaWebEnv{}
 | |
| 	env.cleaners = []func(){
 | |
| 		test.MockVariableValue(&setting.Quota.Enabled, true),
 | |
| 		test.MockVariableValue(&testWebRoutes, routers.NormalRoutes()),
 | |
| 	}
 | |
| 
 | |
| 	// Create the limited user and the various orgs, and a contributor who's not
 | |
| 	// in any of the orgs.
 | |
| 	env.Users.Limited = makeUser(t, int64(0))
 | |
| 	env.Users.Contributor = makeUser(t, int64(0))
 | |
| 	env.Orgs.Limited = makeOrg(t, env.Users.Limited.User, int64(0))
 | |
| 	env.Orgs.Unlimited = makeOrg(t, env.Users.Limited.User, int64(-1))
 | |
| 
 | |
| 	env.Users.Ungrouped = makeUngroupedUser(t)
 | |
| 
 | |
| 	return &env
 | |
| }
 |