Cybersecurity-Projects/PROJECTS/intermediate/secrets-scanner/internal/source/source_test.go

410 lines
9.1 KiB
Go

// ©AngelaMos | 2026
// source_test.go
package source
import (
"context"
"os"
"path/filepath"
"testing"
"time"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing/object"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/CarterPerez-dev/portia/pkg/types"
)
func collectChunks(
t *testing.T, src Source,
) []types.Chunk {
t.Helper()
out := make(chan types.Chunk, 100)
ctx := context.Background()
errCh := make(chan error, 1)
go func() {
errCh <- src.Chunks(ctx, out)
close(out)
}()
var chunks []types.Chunk
for c := range out {
chunks = append(chunks, c)
}
require.NoError(t, <-errCh)
return chunks
}
func TestDirectoryChunks(t *testing.T) {
t.Parallel()
dir := t.TempDir()
require.NoError(t, os.WriteFile( //nolint:gosec
filepath.Join(dir, "config.py"),
[]byte(`password = "sk_live_4eC39HqLyjWDarjtT1zdp7dc"`+"\n"),
0o644,
))
require.NoError(t, os.WriteFile( //nolint:gosec
filepath.Join(dir, "logo.png"),
[]byte{0x89, 0x50, 0x4E, 0x47},
0o644,
))
src := NewDirectory(dir, 0, nil)
chunks := collectChunks(t, src)
require.Len(t, chunks, 1)
assert.Equal(t, "config.py", chunks[0].FilePath)
assert.Contains(t, chunks[0].Content, "sk_live_")
assert.Equal(t, 1, chunks[0].LineStart)
}
func TestDirectoryExcludes(t *testing.T) {
t.Parallel()
dir := t.TempDir()
require.NoError(t, os.WriteFile( //nolint:gosec
filepath.Join(dir, "main.go"),
[]byte("package main\n"),
0o644,
))
require.NoError(t, os.WriteFile( //nolint:gosec
filepath.Join(dir, "secret.env"),
[]byte("API_KEY=test\n"),
0o644,
))
src := NewDirectory(dir, 0, []string{"*.env"})
chunks := collectChunks(t, src)
require.Len(t, chunks, 1)
assert.Equal(t, "main.go", chunks[0].FilePath)
}
func TestDirectorySkipsDotGit(t *testing.T) {
t.Parallel()
dir := t.TempDir()
gitDir := filepath.Join(dir, ".git", "objects")
require.NoError(t, os.MkdirAll(gitDir, 0o755)) //nolint:gosec
require.NoError(t, os.WriteFile( //nolint:gosec
filepath.Join(gitDir, "data"),
[]byte("git internal data\n"),
0o644,
))
require.NoError(t, os.WriteFile( //nolint:gosec
filepath.Join(dir, "app.go"),
[]byte("package main\n"),
0o644,
))
src := NewDirectory(dir, 0, nil)
chunks := collectChunks(t, src)
require.Len(t, chunks, 1)
assert.Equal(t, "app.go", chunks[0].FilePath)
}
func TestDirectoryMaxSize(t *testing.T) {
t.Parallel()
dir := t.TempDir()
require.NoError(t, os.WriteFile( //nolint:gosec
filepath.Join(dir, "small.txt"),
[]byte("small\n"),
0o644,
))
bigContent := make([]byte, 2*1024*1024)
for i := range bigContent {
bigContent[i] = 'x'
}
require.NoError(t, os.WriteFile( //nolint:gosec
filepath.Join(dir, "big.txt"),
bigContent,
0o644,
))
src := NewDirectory(dir, 1024*1024, nil)
chunks := collectChunks(t, src)
require.Len(t, chunks, 1)
assert.Equal(t, "small.txt", chunks[0].FilePath)
}
func TestDirectoryChunking(t *testing.T) {
t.Parallel()
dir := t.TempDir()
var lines string
for i := range 120 {
lines += "line " + string(rune('A'+i%26)) + "\n"
}
require.NoError(t, os.WriteFile( //nolint:gosec
filepath.Join(dir, "long.txt"),
[]byte(lines),
0o644,
))
src := NewDirectory(dir, 0, nil)
chunks := collectChunks(t, src)
require.GreaterOrEqual(t, len(chunks), 2)
assert.Equal(t, 1, chunks[0].LineStart)
assert.Equal(t, 51, chunks[1].LineStart)
}
func TestDirectoryContextCancel(t *testing.T) {
t.Parallel()
dir := t.TempDir()
for i := range 10 {
name := filepath.Join(dir, "file"+string(rune('0'+i))+".txt")
require.NoError(t, os.WriteFile( //nolint:gosec
name, []byte("content\n"), 0o644,
))
}
ctx, cancel := context.WithCancel(context.Background())
cancel()
src := NewDirectory(dir, 0, nil)
out := make(chan types.Chunk, 100)
err := src.Chunks(ctx, out)
close(out)
assert.ErrorIs(t, err, context.Canceled)
}
func TestDirectoryString(t *testing.T) {
t.Parallel()
src := NewDirectory("/tmp/test", 0, nil)
assert.Equal(t, "directory:/tmp/test", src.String())
}
func TestGitChunks(t *testing.T) {
t.Parallel()
dir := t.TempDir()
repo, err := git.PlainInit(dir, false)
require.NoError(t, err)
wt, err := repo.Worktree()
require.NoError(t, err)
require.NoError(t, os.WriteFile( //nolint:gosec
filepath.Join(dir, "secret.py"),
[]byte(`api_key = "sk_live_4eC39HqLyjWDarjtT1zdp7dc"`+"\n"),
0o644,
))
_, err = wt.Add("secret.py")
require.NoError(t, err)
_, err = wt.Commit("add secret", &git.CommitOptions{
Author: &object.Signature{
Name: "Test",
Email: "test@example.com",
When: time.Now(),
},
})
require.NoError(t, err)
src := NewGit(dir, "", "", 0, false, 0, nil)
chunks := collectChunks(t, src)
require.GreaterOrEqual(t, len(chunks), 1)
assert.Equal(t, "secret.py", chunks[0].FilePath)
assert.Contains(t, chunks[0].Content, "sk_live_")
assert.NotEmpty(t, chunks[0].CommitSHA)
assert.Equal(t, "test@example.com", chunks[0].Author)
}
func TestGitStagedOnly(t *testing.T) {
t.Parallel()
dir := t.TempDir()
repo, err := git.PlainInit(dir, false)
require.NoError(t, err)
wt, err := repo.Worktree()
require.NoError(t, err)
require.NoError(t, os.WriteFile( //nolint:gosec
filepath.Join(dir, "committed.txt"),
[]byte("committed content\n"),
0o644,
))
_, err = wt.Add("committed.txt")
require.NoError(t, err)
_, err = wt.Commit("initial", &git.CommitOptions{
Author: &object.Signature{
Name: "Test",
Email: "test@example.com",
When: time.Now(),
},
})
require.NoError(t, err)
require.NoError(t, os.WriteFile( //nolint:gosec
filepath.Join(dir, "staged.txt"),
[]byte("new staged content\n"),
0o644,
))
_, err = wt.Add("staged.txt")
require.NoError(t, err)
src := NewGit(dir, "", "", 0, true, 0, nil)
chunks := collectChunks(t, src)
found := false
for _, c := range chunks {
if c.FilePath == "staged.txt" {
found = true
assert.Contains(t, c.Content, "new staged content")
}
}
assert.True(t, found, "expected to find staged.txt chunk")
}
func TestGitDepthLimit(t *testing.T) {
t.Parallel()
dir := t.TempDir()
repo, err := git.PlainInit(dir, false)
require.NoError(t, err)
wt, err := repo.Worktree()
require.NoError(t, err)
for i := range 5 {
name := "file" + string(rune('0'+i)) + ".txt"
require.NoError(t, os.WriteFile( //nolint:gosec
filepath.Join(dir, name),
[]byte("content "+name+"\n"),
0o644,
))
_, err = wt.Add(name)
require.NoError(t, err)
_, err = wt.Commit("commit "+name, &git.CommitOptions{
Author: &object.Signature{
Name: "Test",
Email: "test@example.com",
When: time.Now().Add(time.Duration(i) * time.Minute),
},
})
require.NoError(t, err)
}
src := NewGit(dir, "", "", 2, false, 0, nil)
chunks := collectChunks(t, src)
files := make(map[string]bool)
for _, c := range chunks {
files[c.FilePath] = true
}
assert.LessOrEqual(t, len(files), 5)
}
func TestGitExcludes(t *testing.T) {
t.Parallel()
dir := t.TempDir()
repo, err := git.PlainInit(dir, false)
require.NoError(t, err)
wt, err := repo.Worktree()
require.NoError(t, err)
require.NoError(t, os.WriteFile( //nolint:gosec
filepath.Join(dir, "keep.go"),
[]byte("package main\n"),
0o644,
))
require.NoError(t, os.WriteFile( //nolint:gosec
filepath.Join(dir, "skip.lock"),
[]byte("lock data\n"),
0o644,
))
_, err = wt.Add("keep.go")
require.NoError(t, err)
_, err = wt.Add("skip.lock")
require.NoError(t, err)
_, err = wt.Commit("add files", &git.CommitOptions{
Author: &object.Signature{
Name: "Test",
Email: "test@example.com",
When: time.Now(),
},
})
require.NoError(t, err)
src := NewGit(dir, "", "", 0, false, 0, []string{".lock"})
chunks := collectChunks(t, src)
for _, c := range chunks {
assert.NotContains(t, c.FilePath, ".lock")
}
}
func TestGitString(t *testing.T) {
t.Parallel()
src := NewGit("/tmp/repo", "", "", 0, false, 0, nil)
assert.Equal(t, "git:/tmp/repo", src.String())
}
func TestSplitIntoChunks(t *testing.T) {
t.Parallel()
var lines []string
for i := range 120 {
lines = append(lines, "line "+string(rune('A'+i%26)))
}
content := ""
for i, l := range lines {
if i > 0 {
content += "\n"
}
content += l
}
chunks := splitIntoChunks(
content, "test.txt", "abc123", "dev@test.com", time.Now(),
)
require.Len(t, chunks, 3)
assert.Equal(t, 1, chunks[0].LineStart)
assert.Equal(t, 51, chunks[1].LineStart)
assert.Equal(t, 101, chunks[2].LineStart)
assert.Equal(t, "abc123", chunks[0].CommitSHA)
assert.Equal(t, "dev@test.com", chunks[0].Author)
}
func TestSplitIntoChunksEmpty(t *testing.T) {
t.Parallel()
chunks := splitIntoChunks(
"", "test.txt", "", "", time.Time{},
)
assert.Empty(t, chunks)
}
func TestIsBinaryExt(t *testing.T) {
t.Parallel()
assert.True(t, isBinaryExt("image.png"))
assert.True(t, isBinaryExt("data.PDF"))
assert.True(t, isBinaryExt("app.exe"))
assert.True(t, isBinaryExt("model.onnx"))
assert.True(t, isBinaryExt("font.otf"))
assert.True(t, isBinaryExt("data.parquet"))
assert.True(t, isBinaryExt("archive.xz"))
assert.True(t, isBinaryExt("package.deb"))
assert.True(t, isBinaryExt("movie.mkv"))
assert.False(t, isBinaryExt("main.go"))
assert.False(t, isBinaryExt("config.yaml"))
assert.False(t, isBinaryExt("script.py"))
}