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(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(&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(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(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(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(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 }