mirror of
1
Fork 0
forgejo/models/quota/quota_rule_test.go

305 lines
7.7 KiB
Go

// 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)
})
}