summaryrefslogtreecommitdiffstats
path: root/models/quota/quota_rule_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'models/quota/quota_rule_test.go')
-rw-r--r--models/quota/quota_rule_test.go304
1 files changed, 304 insertions, 0 deletions
diff --git a/models/quota/quota_rule_test.go b/models/quota/quota_rule_test.go
new file mode 100644
index 0000000..1e1daf4
--- /dev/null
+++ b/models/quota/quota_rule_test.go
@@ -0,0 +1,304 @@
+// Copyright 2024 The Forgejo Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package quota_test
+
+import (
+ "testing"
+
+ quota_model "code.gitea.io/gitea/models/quota"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func makeFullyUsed() quota_model.Used {
+ return quota_model.Used{
+ Size: quota_model.UsedSize{
+ Repos: quota_model.UsedSizeRepos{
+ Public: 1024,
+ Private: 1024,
+ },
+ Git: quota_model.UsedSizeGit{
+ LFS: 1024,
+ },
+ Assets: quota_model.UsedSizeAssets{
+ Attachments: quota_model.UsedSizeAssetsAttachments{
+ Issues: 1024,
+ Releases: 1024,
+ },
+ Artifacts: 1024,
+ Packages: quota_model.UsedSizeAssetsPackages{
+ All: 1024,
+ },
+ },
+ },
+ }
+}
+
+func makePartiallyUsed() quota_model.Used {
+ return quota_model.Used{
+ Size: quota_model.UsedSize{
+ Repos: quota_model.UsedSizeRepos{
+ Public: 1024,
+ },
+ Assets: quota_model.UsedSizeAssets{
+ Attachments: quota_model.UsedSizeAssetsAttachments{
+ Releases: 1024,
+ },
+ },
+ },
+ }
+}
+
+func setUsed(used quota_model.Used, subject quota_model.LimitSubject, value int64) *quota_model.Used {
+ switch subject {
+ case quota_model.LimitSubjectSizeReposPublic:
+ used.Size.Repos.Public = value
+ return &used
+ case quota_model.LimitSubjectSizeReposPrivate:
+ used.Size.Repos.Private = value
+ return &used
+ case quota_model.LimitSubjectSizeGitLFS:
+ used.Size.Git.LFS = value
+ return &used
+ case quota_model.LimitSubjectSizeAssetsAttachmentsIssues:
+ used.Size.Assets.Attachments.Issues = value
+ return &used
+ case quota_model.LimitSubjectSizeAssetsAttachmentsReleases:
+ used.Size.Assets.Attachments.Releases = value
+ return &used
+ case quota_model.LimitSubjectSizeAssetsArtifacts:
+ used.Size.Assets.Artifacts = value
+ return &used
+ case quota_model.LimitSubjectSizeAssetsPackagesAll:
+ used.Size.Assets.Packages.All = value
+ return &used
+ case quota_model.LimitSubjectSizeWiki:
+ }
+
+ return nil
+}
+
+func assertEvaluation(t *testing.T, rule quota_model.Rule, used quota_model.Used, subject quota_model.LimitSubject, expected bool) {
+ t.Helper()
+
+ t.Run(subject.String(), func(t *testing.T) {
+ ok, has := rule.Evaluate(used, subject)
+ assert.True(t, has)
+ assert.Equal(t, expected, ok)
+ })
+}
+
+func TestQuotaRuleNoEvaluation(t *testing.T) {
+ rule := quota_model.Rule{
+ Limit: 1024,
+ Subjects: quota_model.LimitSubjects{
+ quota_model.LimitSubjectSizeAssetsAttachmentsAll,
+ },
+ }
+ used := quota_model.Used{}
+ used.Size.Repos.Public = 4096
+
+ _, has := rule.Evaluate(used, quota_model.LimitSubjectSizeReposAll)
+
+ // We have a rule for "size:assets:attachments:all", and query for
+ // "size:repos:all". We don't cover that subject, so the evaluation returns
+ // with no rules found.
+ assert.False(t, has)
+}
+
+func TestQuotaRuleDirectEvaluation(t *testing.T) {
+ // This function is meant to test direct rule evaluation: cases where we set
+ // a rule for a subject, and we evaluate against the same subject.
+
+ runTest := func(t *testing.T, subject quota_model.LimitSubject, limit, used int64, expected bool) {
+ t.Helper()
+
+ rule := quota_model.Rule{
+ Limit: limit,
+ Subjects: quota_model.LimitSubjects{
+ subject,
+ },
+ }
+ usedObj := setUsed(quota_model.Used{}, subject, used)
+ if usedObj == nil {
+ return
+ }
+
+ assertEvaluation(t, rule, *usedObj, subject, expected)
+ }
+
+ t.Run("limit:0", func(t *testing.T) {
+ // With limit:0, nothing used is fine.
+ t.Run("used:0", func(t *testing.T) {
+ for subject := quota_model.LimitSubjectFirst; subject <= quota_model.LimitSubjectLast; subject++ {
+ runTest(t, subject, 0, 0, true)
+ }
+ })
+ // With limit:0, any usage will fail evaluation
+ t.Run("used:512", func(t *testing.T) {
+ for subject := quota_model.LimitSubjectFirst; subject <= quota_model.LimitSubjectLast; subject++ {
+ runTest(t, subject, 0, 512, false)
+ }
+ })
+ })
+
+ t.Run("limit:unlimited", func(t *testing.T) {
+ // With no limits, any usage will succeed evaluation
+ t.Run("used:512", func(t *testing.T) {
+ for subject := quota_model.LimitSubjectFirst; subject <= quota_model.LimitSubjectLast; subject++ {
+ runTest(t, subject, -1, 512, true)
+ }
+ })
+ })
+
+ t.Run("limit:1024", func(t *testing.T) {
+ // With a set limit, usage below the limit succeeds
+ t.Run("used:512", func(t *testing.T) {
+ for subject := quota_model.LimitSubjectFirst; subject <= quota_model.LimitSubjectLast; subject++ {
+ runTest(t, subject, 1024, 512, true)
+ }
+ })
+
+ // With a set limit, usage above the limit fails
+ t.Run("used:2048", func(t *testing.T) {
+ for subject := quota_model.LimitSubjectFirst; subject <= quota_model.LimitSubjectLast; subject++ {
+ runTest(t, subject, 1024, 2048, false)
+ }
+ })
+ })
+}
+
+func TestQuotaRuleCombined(t *testing.T) {
+ rule := quota_model.Rule{
+ Limit: 1024,
+ Subjects: quota_model.LimitSubjects{
+ quota_model.LimitSubjectSizeGitLFS,
+ quota_model.LimitSubjectSizeAssetsAttachmentsReleases,
+ quota_model.LimitSubjectSizeAssetsPackagesAll,
+ },
+ }
+ used := quota_model.Used{
+ Size: quota_model.UsedSize{
+ Repos: quota_model.UsedSizeRepos{
+ Public: 4096,
+ },
+ Git: quota_model.UsedSizeGit{
+ LFS: 256,
+ },
+ Assets: quota_model.UsedSizeAssets{
+ Attachments: quota_model.UsedSizeAssetsAttachments{
+ Issues: 2048,
+ Releases: 256,
+ },
+ Packages: quota_model.UsedSizeAssetsPackages{
+ All: 2560,
+ },
+ },
+ },
+ }
+
+ expectationMap := map[quota_model.LimitSubject]bool{
+ quota_model.LimitSubjectSizeGitLFS: false,
+ quota_model.LimitSubjectSizeAssetsAttachmentsReleases: false,
+ quota_model.LimitSubjectSizeAssetsPackagesAll: false,
+ }
+
+ for subject := quota_model.LimitSubjectFirst; subject <= quota_model.LimitSubjectLast; subject++ {
+ t.Run(subject.String(), func(t *testing.T) {
+ evalOk, evalHas := rule.Evaluate(used, subject)
+ expected, expectedHas := expectationMap[subject]
+
+ assert.Equal(t, expectedHas, evalHas)
+ if expectedHas {
+ assert.Equal(t, expected, evalOk)
+ }
+ })
+ }
+}
+
+func TestQuotaRuleSizeAll(t *testing.T) {
+ runTests := func(t *testing.T, rule quota_model.Rule, expected bool) {
+ t.Helper()
+
+ subject := quota_model.LimitSubjectSizeAll
+
+ t.Run("used:0", func(t *testing.T) {
+ used := quota_model.Used{}
+
+ assertEvaluation(t, rule, used, subject, true)
+ })
+
+ t.Run("used:some-each", func(t *testing.T) {
+ used := makeFullyUsed()
+
+ assertEvaluation(t, rule, used, subject, expected)
+ })
+
+ t.Run("used:some", func(t *testing.T) {
+ used := makePartiallyUsed()
+
+ assertEvaluation(t, rule, used, subject, expected)
+ })
+ }
+
+ // With all limits set to 0, evaluation always fails if usage > 0
+ t.Run("rule:0", func(t *testing.T) {
+ rule := quota_model.Rule{
+ Limit: 0,
+ Subjects: quota_model.LimitSubjects{
+ quota_model.LimitSubjectSizeAll,
+ },
+ }
+
+ runTests(t, rule, false)
+ })
+
+ // With no limits, evaluation always succeeds
+ t.Run("rule:unlimited", func(t *testing.T) {
+ rule := quota_model.Rule{
+ Limit: -1,
+ Subjects: quota_model.LimitSubjects{
+ quota_model.LimitSubjectSizeAll,
+ },
+ }
+
+ runTests(t, rule, true)
+ })
+
+ // With a specific, very generous limit, evaluation succeeds if the limit isn't exhausted
+ t.Run("rule:generous", func(t *testing.T) {
+ rule := quota_model.Rule{
+ Limit: 102400,
+ Subjects: quota_model.LimitSubjects{
+ quota_model.LimitSubjectSizeAll,
+ },
+ }
+
+ runTests(t, rule, true)
+
+ t.Run("limit exhaustion", func(t *testing.T) {
+ used := quota_model.Used{
+ Size: quota_model.UsedSize{
+ Repos: quota_model.UsedSizeRepos{
+ Public: 204800,
+ },
+ },
+ }
+
+ assertEvaluation(t, rule, used, quota_model.LimitSubjectSizeAll, false)
+ })
+ })
+
+ // With a specific, small limit, evaluation fails
+ t.Run("rule:limited", func(t *testing.T) {
+ rule := quota_model.Rule{
+ Limit: 512,
+ Subjects: quota_model.LimitSubjects{
+ quota_model.LimitSubjectSizeAll,
+ },
+ }
+
+ runTests(t, rule, false)
+ })
+}