197 lines
7.3 KiB
Go
197 lines
7.3 KiB
Go
package validation
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"gitea.maximumdirect.net/eric/ReleaseKit/internal/git"
|
|
)
|
|
|
|
func TestCheckFileSizeUsesStrictLimitsAndDefault(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
limit *int64
|
|
size int64
|
|
wantLimit int64
|
|
wantError bool
|
|
}{
|
|
{name: "below", limit: int64Pointer(10), size: 9, wantLimit: 10},
|
|
{name: "at", limit: int64Pointer(10), size: 10, wantLimit: 10},
|
|
{name: "above", limit: int64Pointer(10), size: 11, wantLimit: 10, wantError: true},
|
|
{name: "default at", size: 1_000_000, wantLimit: 1_000_000},
|
|
{name: "default above", size: 1_000_001, wantLimit: 1_000_000, wantError: true},
|
|
{name: "zero empty", limit: int64Pointer(0), size: 0},
|
|
{name: "zero nonempty", limit: int64Pointer(0), size: 1, wantError: true},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
entries := []git.TreeEntry{{
|
|
Path: "file.bin", Type: "blob", Mode: "100644", Size: test.size, HasSize: true,
|
|
}}
|
|
tree := &CandidateTree{entries: entries, inputEntries: entries}
|
|
err := checkFileSize(t.Context(), tree, &FileSizeSettings{LimitBytes: test.limit})
|
|
if !test.wantError {
|
|
if err != nil {
|
|
t.Fatalf("checkFileSize() error = %v, want nil", err)
|
|
}
|
|
return
|
|
}
|
|
failure, ok := err.(*Failure)
|
|
if !ok || failure.Check != FileSize || len(failure.Findings) != 1 || failure.Findings[0].Path != "file.bin" {
|
|
t.Fatalf("checkFileSize() error = %v, want file-size finding", err)
|
|
}
|
|
if failure.Measurement == nil || failure.Measurement.Bytes != test.size || failure.Measurement.LimitBytes != test.wantLimit {
|
|
t.Fatalf("measurement = %#v, want %d bytes against %d", failure.Measurement, test.size, test.wantLimit)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCheckFileSizeAppliesLiteralPathExclusions(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
entries []git.TreeEntry
|
|
exclusions []string
|
|
wantPath string
|
|
}{
|
|
{
|
|
name: "directory boundary and normalized paths",
|
|
entries: []git.TreeEntry{
|
|
{Path: "assets/icon.png", Type: "blob", Size: 20, HasSize: true},
|
|
{Path: "assets-other/icon.png", Type: "blob", Size: 20, HasSize: true},
|
|
},
|
|
exclusions: []string{"./assets/"},
|
|
wantPath: "assets-other/icon.png",
|
|
},
|
|
{
|
|
name: "glob metacharacters are literal",
|
|
entries: []git.TreeEntry{
|
|
{Path: "[ab].dat", Type: "blob", Size: 20, HasSize: true},
|
|
{Path: "a.dat", Type: "blob", Size: 20, HasSize: true},
|
|
},
|
|
exclusions: []string{"[ab].dat", "literal[set]"},
|
|
wantPath: "a.dat",
|
|
},
|
|
{
|
|
name: "directory metacharacters are literal",
|
|
entries: []git.TreeEntry{
|
|
{Path: "literal[set]/file.dat", Type: "blob", Size: 20, HasSize: true},
|
|
{Path: "literalx/file.dat", Type: "blob", Size: 20, HasSize: true},
|
|
},
|
|
exclusions: []string{"literal[set]"},
|
|
wantPath: "literalx/file.dat",
|
|
},
|
|
{
|
|
name: "unmatched exclusion",
|
|
entries: []git.TreeEntry{
|
|
{Path: "selected.dat", Type: "blob", Size: 20, HasSize: true},
|
|
},
|
|
exclusions: []string{"not-present"},
|
|
wantPath: "selected.dat",
|
|
},
|
|
{
|
|
name: "exact file exclusion",
|
|
entries: []git.TreeEntry{
|
|
{Path: "selected.dat", Type: "blob", Size: 20, HasSize: true},
|
|
{Path: "other.dat", Type: "blob", Size: 20, HasSize: true},
|
|
},
|
|
exclusions: []string{"selected.dat"},
|
|
wantPath: "other.dat",
|
|
},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
settings := fileSizeSettingsForModule(&FileSizeSettings{
|
|
LimitBytes: int64Pointer(10),
|
|
Exclusions: test.exclusions,
|
|
}, ".")
|
|
err := checkFileSize(t.Context(), &CandidateTree{entries: test.entries, inputEntries: test.entries}, settings)
|
|
failure, ok := err.(*Failure)
|
|
if !ok || failure.Check != FileSize || len(failure.Findings) != 1 || failure.Findings[0].Path != test.wantPath {
|
|
t.Fatalf("checkFileSize() error = %v, want only %q to exceed the limit", err, test.wantPath)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCheckFileSizeUsesCommittedBlobSizes(t *testing.T) {
|
|
repository := newTreeRepository(t)
|
|
writeTreeFile(t, repository, "small.bin", []byte{0, 1})
|
|
writeTreeFile(t, repository, "boundary.bin", []byte{0, 1, 2})
|
|
if err := os.Symlink("small.bin", filepath.Join(repository, "link")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
submodule := newTreeRepository(t)
|
|
writeTreeFile(t, submodule, "submodule.txt", []byte("submodule"))
|
|
gitTree(t, submodule, "add", "--all")
|
|
gitTree(t, submodule, "commit", "-m", "create nested source")
|
|
submoduleCommit := strings.TrimSpace(gitTree(t, submodule, "rev-parse", "HEAD"))
|
|
gitTree(t, repository, "add", "--all")
|
|
gitTree(t, repository, "update-index", "--add", "--cacheinfo", "160000,"+submoduleCommit+",submodule")
|
|
commit := commitCandidateTree(t, repository)
|
|
tree, err := ReadCandidateTree(context.Background(), repository, commit)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cleanupCandidateTree(t, tree)
|
|
|
|
err = checkFileSize(t.Context(), tree, &FileSizeSettings{LimitBytes: int64Pointer(3), Exclusions: []string{"link"}})
|
|
if err != nil {
|
|
t.Fatalf("checkFileSize() error = %v, want small binary and exact-limit blob to pass", err)
|
|
}
|
|
|
|
err = checkFileSize(t.Context(), tree, &FileSizeSettings{LimitBytes: int64Pointer(3), Exclusions: []string{"small.bin", "boundary.bin"}})
|
|
failure, ok := err.(*Failure)
|
|
if !ok || failure.Check != FileSize || len(failure.Findings) != 1 || failure.Findings[0].Path != "link" {
|
|
t.Fatalf("checkFileSize() error = %v, want symlink blob to exceed the limit", err)
|
|
}
|
|
if failure.Measurement == nil || failure.Measurement.Bytes != int64(len("small.bin")) {
|
|
t.Fatalf("symlink measurement = %#v, want link-text size %d", failure.Measurement, len("small.bin"))
|
|
}
|
|
|
|
err = checkFileSize(t.Context(), tree, &FileSizeSettings{LimitBytes: int64Pointer(0), Exclusions: []string{"small.bin", "boundary.bin", "link"}})
|
|
if err != nil {
|
|
t.Fatalf("checkFileSize() error = %v, want omitted gitlink size", err)
|
|
}
|
|
}
|
|
|
|
func TestCheckFileSizeUsesOnlyCandidateFilesIndividually(t *testing.T) {
|
|
repository := newTreeRepository(t)
|
|
writeTreeFile(t, repository, "historical-large.dat", []byte(strings.Repeat("h", 20)))
|
|
commitCandidateTree(t, repository)
|
|
if err := os.Remove(filepath.Join(repository, "historical-large.dat")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
writeTreeFile(t, repository, "one.dat", []byte(strings.Repeat("a", 6)))
|
|
writeTreeFile(t, repository, "two.dat", []byte(strings.Repeat("b", 6)))
|
|
commit := commitCandidateTree(t, repository)
|
|
tree, err := ReadCandidateTree(context.Background(), repository, commit)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cleanupCandidateTree(t, tree)
|
|
if err := checkFileSize(t.Context(), tree, &FileSizeSettings{LimitBytes: int64Pointer(10)}); err != nil {
|
|
t.Fatalf("checkFileSize() error = %v, want current individual files to pass", err)
|
|
}
|
|
}
|
|
|
|
func TestRunRejectsNegativeFileSizeLimitBeforeGoChecks(t *testing.T) {
|
|
negative := int64(-1)
|
|
err := Run(context.Background(), Request{Settings: Settings{
|
|
FileSize: &FileSizeSettings{LimitBytes: &negative},
|
|
}})
|
|
failure, ok := err.(*Failure)
|
|
if !ok || failure.Check != FileSize || len(failure.Findings) != 1 || !strings.Contains(failure.Findings[0].Message, "negative") {
|
|
t.Fatalf("Run() error = %v, want invalid file-size limit failure", err)
|
|
}
|
|
if errors.Unwrap(failure) != nil {
|
|
t.Fatalf("invalid limit failure cause = %v, want none", failure.Cause)
|
|
}
|
|
}
|
|
|
|
func int64Pointer(value int64) *int64 { return &value }
|