From 5bde5096bbece643e59c75ba3505ecffa291dbf8 Mon Sep 17 00:00:00 2001 From: Mohan Manikanta Date: Wed, 3 Jun 2026 14:53:48 +0530 Subject: [PATCH 1/5] Add test cases to increase coverage for licensecheck and repodata directories --- licensecheck/copyright_test.go | 84 ++++++++++ licensecheck/licensecheck_test.go | 155 +++++++++++++++++- licensecheck/update_test.go | 254 +++++++++++++++++++++++++++++- repodata/repodata_test.go | 179 +++++++++++++++++++++ 4 files changed, 667 insertions(+), 5 deletions(-) diff --git a/licensecheck/copyright_test.go b/licensecheck/copyright_test.go index f862490..13a2253 100644 --- a/licensecheck/copyright_test.go +++ b/licensecheck/copyright_test.go @@ -273,3 +273,87 @@ func TestHasCopyright(t *testing.T) { }) } } + +func TestHasMatchingCopyright_ErrorHandling(t *testing.T) { + t.Run("error on non-existent file", func(t *testing.T) { + hasCopyright, err := HasMatchingCopyright("/nonexistent/file.txt", "Copyright", false) + assert.NotNil(t, err) + assert.False(t, hasCopyright) + }) +} + +func TestHasMatchingCopyright_EdgeCases(t *testing.T) { + AppFs := afero.NewOsFs() + tempDir := t.TempDir() + + t.Run("file exactly 300 bytes with copyright at end", func(t *testing.T) { + // Create a file exactly 300 bytes where "Copyright" appears at byte 290 + padding := make([]byte, 290) + for i := range padding { + padding[i] = 'A' + } + content := string(padding) + "Copyright!" + + f, _ := afero.TempFile(AppFs, tempDir, "") + _ = afero.WriteFile(AppFs, f.Name(), []byte(content), 0644) + + hasCopyright, err := HasMatchingCopyright(f.Name(), "Copyright", false) + assert.Nil(t, err) + assert.True(t, hasCopyright) + }) + + t.Run("file less than 300 bytes", func(t *testing.T) { + content := "Short file with Copyright notice" + + f, _ := afero.TempFile(AppFs, tempDir, "") + _ = afero.WriteFile(AppFs, f.Name(), []byte(content), 0644) + + hasCopyright, err := HasMatchingCopyright(f.Name(), "Copyright", false) + assert.Nil(t, err) + assert.True(t, hasCopyright) + }) + + t.Run("file larger than 300 bytes with copyright after header", func(t *testing.T) { + // Create content > 300 bytes with copyright appearing after byte 300 + header := make([]byte, 350) + for i := range header { + header[i] = 'X' + } + content := string(header) + "\nCopyright notice here" + + f, _ := afero.TempFile(AppFs, tempDir, "") + _ = afero.WriteFile(AppFs, f.Name(), []byte(content), 0644) + + // Should not find copyright since it's after the 300-byte header check + hasCopyright, err := HasMatchingCopyright(f.Name(), "Copyright", false) + assert.Nil(t, err) + assert.False(t, hasCopyright) + }) + + t.Run("empty search string", func(t *testing.T) { + f, _ := afero.TempFile(AppFs, tempDir, "") + _ = afero.WriteFile(AppFs, f.Name(), []byte("Some content"), 0644) + + // Empty string should always be found + hasCopyright, err := HasMatchingCopyright(f.Name(), "", false) + assert.Nil(t, err) + assert.True(t, hasCopyright) + }) + + t.Run("search string longer than file", func(t *testing.T) { + f, _ := afero.TempFile(AppFs, tempDir, "") + _ = afero.WriteFile(AppFs, f.Name(), []byte("Short"), 0644) + + hasCopyright, err := HasMatchingCopyright(f.Name(), "This is a very long copyright statement that is longer than the file content", false) + assert.Nil(t, err) + assert.False(t, hasCopyright) + }) +} + +func TestHasCopyright_ErrorHandling(t *testing.T) { + t.Run("error on non-existent file", func(t *testing.T) { + hasCopyright, err := HasCopyright("/nonexistent/file.txt") + assert.NotNil(t, err) + assert.False(t, hasCopyright) + }) +} diff --git a/licensecheck/licensecheck_test.go b/licensecheck/licensecheck_test.go index 486698e..adf64c6 100644 --- a/licensecheck/licensecheck_test.go +++ b/licensecheck/licensecheck_test.go @@ -85,14 +85,137 @@ func TestEnsureCorrectName(t *testing.T) { } } +func TestEnsureCorrectName_ErrorHandling(t *testing.T) { + t.Run("error when file does not exist", func(t *testing.T) { + _, err := EnsureCorrectName("/nonexistent/path/license.txt") + assert.NotNil(t, err) + }) +} + func TestAddHeader(t *testing.T) { - // stub - t.Skip() + AppFs := afero.NewOsFs() + + t.Run("add header to empty file", func(t *testing.T) { + tempDir := t.TempDir() + filePath := filepath.Join(tempDir, "test.txt") + _ = afero.WriteFile(AppFs, filePath, []byte(""), 0644) + + header := "Copyright (c) 2023 Test Corp" + err := AddHeader(filePath, header) + assert.Nil(t, err) + + // Read file and verify header was added + content, _ := afero.ReadFile(AppFs, filePath) + assert.Contains(t, string(content), header) + // Should have double newline after header + assert.Contains(t, string(content), header+"\n\n") + }) + + t.Run("add header to file with existing content", func(t *testing.T) { + tempDir := t.TempDir() + filePath := filepath.Join(tempDir, "test.txt") + originalContent := "This is the original file content" + _ = afero.WriteFile(AppFs, filePath, []byte(originalContent), 0644) + + header := "Copyright (c) 2023 Test Corp" + err := AddHeader(filePath, header) + assert.Nil(t, err) + + // Read file and verify header was prepended + content, _ := afero.ReadFile(AppFs, filePath) + assert.Contains(t, string(content), header) + assert.Contains(t, string(content), originalContent) + // Header should come before original content + headerIdx := lo.IndexOf([]byte(string(content)), []byte(header)[0]) + contentIdx := lo.IndexOf([]byte(string(content)), []byte(originalContent)[0]) + assert.Less(t, headerIdx, contentIdx) + }) + + t.Run("add multi-line header", func(t *testing.T) { + tempDir := t.TempDir() + filePath := filepath.Join(tempDir, "test.txt") + _ = afero.WriteFile(AppFs, filePath, []byte("Original content"), 0644) + + header := "Copyright (c) 2023 Test Corp\nSPDX-License-Identifier: MPL-2.0" + err := AddHeader(filePath, header) + assert.Nil(t, err) + + content, _ := afero.ReadFile(AppFs, filePath) + assert.Contains(t, string(content), "Copyright (c) 2023 Test Corp") + assert.Contains(t, string(content), "SPDX-License-Identifier: MPL-2.0") + assert.Contains(t, string(content), "Original content") + }) + + t.Run("error on non-existent file", func(t *testing.T) { + header := "Copyright (c) 2023 Test Corp" + err := AddHeader("/nonexistent/path/file.txt", header) + assert.NotNil(t, err) + }) } func TestAddLicenseFile(t *testing.T) { - // stub - t.Skip() + AppFs := afero.NewOsFs() + + t.Run("create LICENSE file with MPL-2.0", func(t *testing.T) { + tempDir := t.TempDir() + licensePath, err := AddLicenseFile(tempDir, "MPL-2.0") + assert.Nil(t, err) + assert.NotEmpty(t, licensePath) + + // Verify file exists + fileExists, _ := afero.Exists(AppFs, licensePath) + assert.True(t, fileExists) + + // Verify content contains MPL-2.0 license text + content, _ := afero.ReadFile(AppFs, licensePath) + assert.Contains(t, string(content), "Mozilla Public License") + }) + + t.Run("create LICENSE file with Apache-2.0", func(t *testing.T) { + tempDir := t.TempDir() + licensePath, err := AddLicenseFile(tempDir, "Apache-2.0") + assert.Nil(t, err) + + content, _ := afero.ReadFile(AppFs, licensePath) + assert.Contains(t, string(content), "Apache License") + }) + + t.Run("create LICENSE file with MIT", func(t *testing.T) { + tempDir := t.TempDir() + licensePath, err := AddLicenseFile(tempDir, "MIT") + assert.Nil(t, err) + + content, _ := afero.ReadFile(AppFs, licensePath) + assert.Contains(t, string(content), "Permission is hereby granted") + }) + + t.Run("error on unknown SPDX ID", func(t *testing.T) { + tempDir := t.TempDir() + licensePath, err := AddLicenseFile(tempDir, "UNKNOWN-LICENSE-99") + assert.NotNil(t, err) + assert.Empty(t, licensePath) + assert.Contains(t, err.Error(), "unknown SPDX license ID") + }) + + t.Run("error on invalid directory path", func(t *testing.T) { + licensePath, err := AddLicenseFile("/nonexistent/invalid/path", "MPL-2.0") + assert.NotNil(t, err) + assert.Empty(t, licensePath) + }) + + t.Run("returned path is absolute", func(t *testing.T) { + tempDir := t.TempDir() + licensePath, err := AddLicenseFile(tempDir, "MPL-2.0") + assert.Nil(t, err) + assert.True(t, filepath.IsAbs(licensePath)) + }) + + t.Run("file is named LICENSE", func(t *testing.T) { + tempDir := t.TempDir() + licensePath, err := AddLicenseFile(tempDir, "Apache-2.0") + assert.Nil(t, err) + assert.Equal(t, "LICENSE", filepath.Base(licensePath)) + }) } func sortSlice(input *[]string) { @@ -179,3 +302,27 @@ func TestFindLicenseFiles(t *testing.T) { }) } } + +func TestFindLicenseFiles_ErrorHandling(t *testing.T) { + t.Run("returns empty slice when directory doesn't exist", func(t *testing.T) { + // When the directory doesn't exist, glob returns empty without error + result, err := FindLicenseFiles("/nonexistent/directory/path") + assert.Nil(t, err) + assert.Equal(t, []string{}, result) + }) + + t.Run("handles subdirectories correctly", func(t *testing.T) { + AppFs := afero.NewOsFs() + tempDir := t.TempDir() + + // Create a subdirectory with a LICENSE file + subDir := filepath.Join(tempDir, "subdir") + _ = AppFs.MkdirAll(subDir, 0755) + _ = afero.WriteFile(AppFs, filepath.Join(subDir, "LICENSE"), []byte("sublicense"), 0644) + + // FindLicenseFiles should only find files in the top-level directory, not subdirs + result, err := FindLicenseFiles(tempDir) + assert.Nil(t, err) + assert.Equal(t, []string{}, result) + }) +} diff --git a/licensecheck/update_test.go b/licensecheck/update_test.go index 0d353c4..947b5ab 100644 --- a/licensecheck/update_test.go +++ b/licensecheck/update_test.go @@ -6,8 +6,10 @@ package licensecheck import ( "fmt" "os" + "os/exec" "path/filepath" "strconv" + "sync" "testing" "time" @@ -925,7 +927,6 @@ func TestUpdateCopyrightHeader_HandlebarsFiles(t *testing.T) { assert.False(t, needsUpdate2, "Should not need another update after being updated to current year") } - func TestUpdateCopyrightHeader_IgnoreYear1(t *testing.T) { tempDir := t.TempDir() testFile := filepath.Join(tempDir, "test.go") @@ -994,3 +995,254 @@ package main expected := fmt.Sprintf("// Copyright IBM Corp. 2021, %d\npackage main\n", currentYear) assert.Equal(t, expected, string(content)) } + +func TestGitOperations(t *testing.T) { + if _, err := exec.LookPath("git"); err != nil { + t.Skip("git executable not found in PATH; skipping git operations test") + } + + tempDir := t.TempDir() + + // Initialize git repo + cmd := exec.Command("git", "init", "--initial-branch=main") + cmd.Dir = tempDir + require.NoError(t, cmd.Run()) + + exec.Command("git", "-C", tempDir, "config", "user.email", "test@example.com").Run() + exec.Command("git", "-C", tempDir, "config", "user.name", "Test User").Run() + + // Create and commit a file with a specific date + testFile := filepath.Join(tempDir, "test.txt") + err := os.WriteFile(testFile, []byte("test content"), 0644) + require.NoError(t, err) + + exec.Command("git", "-C", tempDir, "add", "test.txt").Run() + + commitCmd := exec.Command("git", "-C", tempDir, "commit", "-m", "first commit") + commitCmd.Env = append(os.Environ(), "GIT_AUTHOR_DATE=2020-01-01T12:00:00Z", "GIT_COMMITTER_DATE=2020-01-01T12:00:00Z") + require.NoError(t, commitCmd.Run()) + + // Create and commit a second file to test latest commit year functionality + testFile2 := filepath.Join(tempDir, "test2.txt") + err = os.WriteFile(testFile2, []byte("test content 2"), 0644) + require.NoError(t, err) + exec.Command("git", "-C", tempDir, "add", "test2.txt").Run() + commitCmd2 := exec.Command("git", "-C", tempDir, "commit", "-m", "second commit") + commitCmd2.Env = append(os.Environ(), "GIT_AUTHOR_DATE=2023-01-01T12:00:00Z", "GIT_COMMITTER_DATE=2023-01-01T12:00:00Z") + require.NoError(t, commitCmd2.Run()) + + t.Run("GetRepoRoot", func(t *testing.T) { + root, err := GetRepoRoot(tempDir) + require.NoError(t, err) + + // Resolve symlinks since macOS TempDir paths are symlinks (/var -> /private/var) + evalRoot, _ := filepath.EvalSymlinks(tempDir) + assert.Equal(t, evalRoot, root) + }) + + t.Run("buildRepositoryCache", func(t *testing.T) { + evalRoot, _ := filepath.EvalSymlinks(tempDir) + cache, firstYear, err := buildRepositoryCache(evalRoot) + require.NoError(t, err) + assert.Equal(t, 2020, firstYear) + assert.Contains(t, cache, "test.txt") + assert.Equal(t, 2020, cache["test.txt"]) + }) + + t.Run("executeGitCommand", func(t *testing.T) { + out, err := executeGitCommand(tempDir, "log", "-1", "--format=%s") + require.NoError(t, err) + assert.Equal(t, "second commit\n", string(out)) + }) + + t.Run("InitializeGitCache", func(t *testing.T) { + evalRoot, _ := filepath.EvalSymlinks(tempDir) + once = sync.Once{} + err := InitializeGitCache(evalRoot) + require.NoError(t, err) + assert.Equal(t, 2020, firstCommitYearCached) + assert.Contains(t, lastCommitYearsCache, "test.txt") + assert.Contains(t, lastCommitYearsCache, "test2.txt") + assert.Equal(t, 2020, lastCommitYearsCache["test.txt"]) + assert.Equal(t, 2023, lastCommitYearsCache["test2.txt"]) + }) + + t.Run("InitializeGitCache - Failure", func(t *testing.T) { + once = sync.Once{} + err := InitializeGitCache("/non/existent/path/for/git/cache") + require.NoError(t, err) + assert.NotNil(t, lastCommitYearsCache) + assert.Empty(t, lastCommitYearsCache) + }) + + t.Run("getCachedFileLastCommitYear", func(t *testing.T) { + evalRoot, _ := filepath.EvalSymlinks(tempDir) + once = sync.Once{} + _ = InitializeGitCache(evalRoot) + + year, err := getCachedFileLastCommitYear("test2.txt", evalRoot) + require.NoError(t, err) + assert.Equal(t, 2023, year) + + year, err = getCachedFileLastCommitYear("nonexistent.txt", evalRoot) + assert.Error(t, err) + assert.Equal(t, 0, year) + }) + + t.Run("getFileLastCommitYear", func(t *testing.T) { + evalRoot, _ := filepath.EvalSymlinks(tempDir) + once = sync.Once{} + _ = InitializeGitCache(evalRoot) + + year, err := getFileLastCommitYear(filepath.Join(evalRoot, "test.txt"), evalRoot) + require.NoError(t, err) + assert.Equal(t, 2020, year) + + // Not in cache + year, err = getFileLastCommitYear(filepath.Join(evalRoot, "nonexistent.txt"), evalRoot) + require.NoError(t, err) + assert.Equal(t, 0, year) + }) + + t.Run("GetRepoFirstCommitYear", func(t *testing.T) { + evalRoot, _ := filepath.EvalSymlinks(tempDir) + once = sync.Once{} + year, err := GetRepoFirstCommitYear(evalRoot) + require.NoError(t, err) + assert.Equal(t, 2020, year) + }) + + t.Run("GetRepoFirstCommitYear - Invalid Repo", func(t *testing.T) { + year, err := GetRepoFirstCommitYear(t.TempDir()) + assert.Error(t, err) + assert.Equal(t, 0, year) + }) + + t.Run("GetRepoLastCommitYear - Invalid Repo", func(t *testing.T) { + year, err := GetRepoLastCommitYear(t.TempDir()) + assert.Error(t, err) + assert.Equal(t, 0, year) + }) +} + +func TestUpdateCopyrightHeaderWithCache(t *testing.T) { + currentYear := time.Now().Year() + + tests := []struct { + name string + initialContent string + targetHolder string + configYear int + forceCurrentYear bool + ignoreYear1 bool + repoFirstYear int + repoRoot string + expectModified bool + expectedContent string + }{ + { + name: "Update start year using repoFirstYear when configYear is 0", + initialContent: `// Copyright IBM Corp. 2023 +package main +`, + targetHolder: "IBM Corp.", + configYear: 0, + forceCurrentYear: false, + ignoreYear1: false, + repoFirstYear: 2019, + repoRoot: "", // empty to avoid real git lookups + expectModified: true, + expectedContent: `// Copyright IBM Corp. 2019, 2023 +package main +`, + }, + { + name: "No update when ignoring year 1 and configYear differs", + initialContent: `// Copyright IBM Corp. 2023, 2023 +package main +`, + targetHolder: "IBM Corp.", + configYear: 2020, + forceCurrentYear: false, + ignoreYear1: true, + repoFirstYear: 2018, + repoRoot: "", + expectModified: false, + }, + { + name: "Update end year with forceCurrentYear", + initialContent: `// Copyright IBM Corp. 2020, 2022 +package main +`, + targetHolder: "IBM Corp.", + configYear: 2020, + forceCurrentYear: true, + ignoreYear1: false, + repoFirstYear: 2020, + repoRoot: "", + expectModified: true, + expectedContent: `// Copyright IBM Corp. 2020, ` + strconv.Itoa(currentYear) + ` +package main +`, + }, + { + name: "Skip .copywrite.hcl file", + initialContent: `// Copyright IBM Corp. 2020 +schema_version = 1 +`, + targetHolder: "IBM Corp.", + configYear: 2022, + forceCurrentYear: true, + ignoreYear1: false, + repoFirstYear: 2020, + repoRoot: "", + expectModified: false, + }, + { + name: "Wrong holder (Google Inc.) - no update", + initialContent: `// Copyright (c) Google Inc. +package main +`, + targetHolder: "IBM Corp.", + configYear: 2022, + forceCurrentYear: true, + ignoreYear1: false, + repoFirstYear: 2020, + repoRoot: "", + expectModified: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tempDir := t.TempDir() + + fileName := "test.go" + if tt.name == "Skip .copywrite.hcl file" { + fileName = ".copywrite.hcl" + } + testFile := filepath.Join(tempDir, fileName) + + err := os.WriteFile(testFile, []byte(tt.initialContent), 0644) + require.NoError(t, err) + + modified, err := UpdateCopyrightHeaderWithCache( + testFile, + tt.targetHolder, + tt.configYear, + tt.forceCurrentYear, + tt.ignoreYear1, + tt.repoFirstYear, + tt.repoRoot, + ) + require.NoError(t, err) + assert.Equal(t, tt.expectModified, modified) + + if tt.expectModified && tt.expectedContent != "" { + content, err := os.ReadFile(testFile) + require.NoError(t, err) + assert.Equal(t, tt.expectedContent, string(content)) + } + }) + } +} diff --git a/repodata/repodata_test.go b/repodata/repodata_test.go index 1d9a606..5b121c2 100644 --- a/repodata/repodata_test.go +++ b/repodata/repodata_test.go @@ -5,9 +5,11 @@ package repodata import ( "testing" + "time" "github.com/google/go-github/v45/github" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) // to help with making archived and non archived repos for testing @@ -107,3 +109,180 @@ func TestValidateInputFields(t *testing.T) { }) } } + +func TestTransform(t *testing.T) { + t.Run("empty array returns empty result", func(t *testing.T) { + result, err := Transform([]*github.Repository{}) + require.NoError(t, err) + assert.Equal(t, []map[string]interface{}(nil), result) + }) + + t.Run("transform repo with string fields", func(t *testing.T) { + name := "test-repo" + url := "https://github.com/test/repo" + repo := &github.Repository{ + Name: &name, + HTMLURL: &url, + } + + result, err := Transform([]*github.Repository{repo}) + require.NoError(t, err) + require.Len(t, result, 1) + + assert.Equal(t, "test-repo", result[0]["Name"]) + assert.Equal(t, "https://github.com/test/repo", result[0]["HTMLURL"]) + }) + + t.Run("transform repo with nil string fields", func(t *testing.T) { + repo := &github.Repository{ + Name: nil, + HTMLURL: nil, + } + + result, err := Transform([]*github.Repository{repo}) + require.NoError(t, err) + require.Len(t, result, 1) + + // Nil pointers should be transformed to empty strings + assert.Equal(t, "", result[0]["Name"]) + assert.Equal(t, "", result[0]["HTMLURL"]) + }) + + t.Run("transform repo with license", func(t *testing.T) { + licenseKey := "mit" + license := &github.License{ + Key: &licenseKey, + } + name := "licensed-repo" + repo := &github.Repository{ + Name: &name, + License: license, + } + + result, err := Transform([]*github.Repository{repo}) + require.NoError(t, err) + require.Len(t, result, 1) + + assert.Equal(t, "licensed-repo", result[0]["Name"]) + assert.Equal(t, "mit", result[0]["License"]) + }) + + t.Run("transform repo with nil license", func(t *testing.T) { + name := "unlicensed-repo" + repo := &github.Repository{ + Name: &name, + License: nil, + } + + result, err := Transform([]*github.Repository{repo}) + require.NoError(t, err) + require.Len(t, result, 1) + + assert.Equal(t, "unlicensed-repo", result[0]["Name"]) + assert.Equal(t, "", result[0]["License"]) + }) + + t.Run("transform repo with timestamp", func(t *testing.T) { + name := "timestamped-repo" + testTime := time.Date(2023, 1, 15, 10, 30, 0, 0, time.UTC) + timestamp := &github.Timestamp{Time: testTime} + repo := &github.Repository{ + Name: &name, + CreatedAt: timestamp, + } + + result, err := Transform([]*github.Repository{repo}) + require.NoError(t, err) + require.Len(t, result, 1) + + assert.Equal(t, "timestamped-repo", result[0]["Name"]) + // Timestamp should be converted to string representation + assert.Equal(t, testTime.String(), result[0]["CreatedAt"]) + }) + + t.Run("transform repo with nil timestamp", func(t *testing.T) { + name := "no-timestamp-repo" + repo := &github.Repository{ + Name: &name, + CreatedAt: nil, + } + + result, err := Transform([]*github.Repository{repo}) + require.NoError(t, err) + require.Len(t, result, 1) + + assert.Equal(t, "no-timestamp-repo", result[0]["Name"]) + assert.Equal(t, "", result[0]["CreatedAt"]) + }) + + t.Run("transform multiple repos", func(t *testing.T) { + name1 := "repo-one" + name2 := "repo-two" + url1 := "https://github.com/test/one" + url2 := "https://github.com/test/two" + + repos := []*github.Repository{ + { + Name: &name1, + HTMLURL: &url1, + }, + { + Name: &name2, + HTMLURL: &url2, + }, + } + + result, err := Transform(repos) + require.NoError(t, err) + require.Len(t, result, 2) + + assert.Equal(t, "repo-one", result[0]["Name"]) + assert.Equal(t, "https://github.com/test/one", result[0]["HTMLURL"]) + assert.Equal(t, "repo-two", result[1]["Name"]) + assert.Equal(t, "https://github.com/test/two", result[1]["HTMLURL"]) + }) + + t.Run("transform repo with mixed field types", func(t *testing.T) { + name := "complex-repo" + url := "https://github.com/test/complex" + licenseKey := "apache-2.0" + license := &github.License{Key: &licenseKey} + testTime := time.Date(2024, 6, 1, 12, 0, 0, 0, time.UTC) + timestamp := &github.Timestamp{Time: testTime} + + repo := &github.Repository{ + Name: &name, + HTMLURL: &url, + License: license, + CreatedAt: timestamp, + } + + result, err := Transform([]*github.Repository{repo}) + require.NoError(t, err) + require.Len(t, result, 1) + + assert.Equal(t, "complex-repo", result[0]["Name"]) + assert.Equal(t, "https://github.com/test/complex", result[0]["HTMLURL"]) + assert.Equal(t, "apache-2.0", result[0]["License"]) + assert.Equal(t, testTime.String(), result[0]["CreatedAt"]) + }) + + t.Run("transform repo with all nil fields", func(t *testing.T) { + repo := &github.Repository{ + Name: nil, + HTMLURL: nil, + License: nil, + CreatedAt: nil, + } + + result, err := Transform([]*github.Repository{repo}) + require.NoError(t, err) + require.Len(t, result, 1) + + // All nil fields should become empty strings + assert.Equal(t, "", result[0]["Name"]) + assert.Equal(t, "", result[0]["HTMLURL"]) + assert.Equal(t, "", result[0]["License"]) + assert.Equal(t, "", result[0]["CreatedAt"]) + }) +} From e2a82ef1f10e339269cc7c9c63c1e7b1a7d0efd8 Mon Sep 17 00:00:00 2001 From: Mohan Manikanta Date: Wed, 3 Jun 2026 15:16:15 +0530 Subject: [PATCH 2/5] Lint error fix. --- licensecheck/update_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/licensecheck/update_test.go b/licensecheck/update_test.go index 947b5ab..a57fc2e 100644 --- a/licensecheck/update_test.go +++ b/licensecheck/update_test.go @@ -1008,15 +1008,15 @@ func TestGitOperations(t *testing.T) { cmd.Dir = tempDir require.NoError(t, cmd.Run()) - exec.Command("git", "-C", tempDir, "config", "user.email", "test@example.com").Run() - exec.Command("git", "-C", tempDir, "config", "user.name", "Test User").Run() + require.NoError(t, exec.Command("git", "-C", tempDir, "config", "user.email", "test@example.com").Run()) + require.NoError(t, exec.Command("git", "-C", tempDir, "config", "user.name", "Test User").Run()) // Create and commit a file with a specific date testFile := filepath.Join(tempDir, "test.txt") err := os.WriteFile(testFile, []byte("test content"), 0644) require.NoError(t, err) - exec.Command("git", "-C", tempDir, "add", "test.txt").Run() + require.NoError(t, exec.Command("git", "-C", tempDir, "add", "test.txt").Run()) commitCmd := exec.Command("git", "-C", tempDir, "commit", "-m", "first commit") commitCmd.Env = append(os.Environ(), "GIT_AUTHOR_DATE=2020-01-01T12:00:00Z", "GIT_COMMITTER_DATE=2020-01-01T12:00:00Z") @@ -1026,7 +1026,7 @@ func TestGitOperations(t *testing.T) { testFile2 := filepath.Join(tempDir, "test2.txt") err = os.WriteFile(testFile2, []byte("test content 2"), 0644) require.NoError(t, err) - exec.Command("git", "-C", tempDir, "add", "test2.txt").Run() + require.NoError(t, exec.Command("git", "-C", tempDir, "add", "test2.txt").Run()) commitCmd2 := exec.Command("git", "-C", tempDir, "commit", "-m", "second commit") commitCmd2.Env = append(os.Environ(), "GIT_AUTHOR_DATE=2023-01-01T12:00:00Z", "GIT_COMMITTER_DATE=2023-01-01T12:00:00Z") require.NoError(t, commitCmd2.Run()) From c71ba8319ff4d96221921ff9352f0659c8c3bfdd Mon Sep 17 00:00:00 2001 From: Mohan Manikanta Date: Thu, 4 Jun 2026 11:45:25 +0530 Subject: [PATCH 3/5] Address co-pilot comments. --- licensecheck/copyright_test.go | 13 +++++++++---- licensecheck/licensecheck_test.go | 11 +++++++---- licensecheck/update_test.go | 19 ++++++++++++------- repodata/repodata_test.go | 2 +- 4 files changed, 29 insertions(+), 16 deletions(-) diff --git a/licensecheck/copyright_test.go b/licensecheck/copyright_test.go index 13a2253..13955bf 100644 --- a/licensecheck/copyright_test.go +++ b/licensecheck/copyright_test.go @@ -5,6 +5,7 @@ import ( "github.com/spf13/afero" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestHasMatchingCopyright(t *testing.T) { @@ -294,8 +295,10 @@ func TestHasMatchingCopyright_EdgeCases(t *testing.T) { } content := string(padding) + "Copyright!" - f, _ := afero.TempFile(AppFs, tempDir, "") - _ = afero.WriteFile(AppFs, f.Name(), []byte(content), 0644) + f, err := afero.TempFile(AppFs, tempDir, "") + require.NoError(t, err) + err = afero.WriteFile(AppFs, f.Name(), []byte(content), 0644) + require.NoError(t, err) hasCopyright, err := HasMatchingCopyright(f.Name(), "Copyright", false) assert.Nil(t, err) @@ -305,8 +308,10 @@ func TestHasMatchingCopyright_EdgeCases(t *testing.T) { t.Run("file less than 300 bytes", func(t *testing.T) { content := "Short file with Copyright notice" - f, _ := afero.TempFile(AppFs, tempDir, "") - _ = afero.WriteFile(AppFs, f.Name(), []byte(content), 0644) + f, err := afero.TempFile(AppFs, tempDir, "") + require.NoError(t, err) + err = afero.WriteFile(AppFs, f.Name(), []byte(content), 0644) + require.NoError(t, err) hasCopyright, err := HasMatchingCopyright(f.Name(), "Copyright", false) assert.Nil(t, err) diff --git a/licensecheck/licensecheck_test.go b/licensecheck/licensecheck_test.go index adf64c6..5974397 100644 --- a/licensecheck/licensecheck_test.go +++ b/licensecheck/licensecheck_test.go @@ -6,11 +6,13 @@ package licensecheck import ( "path/filepath" "sort" + "strings" "testing" "github.com/samber/lo" "github.com/spf13/afero" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func createTempFiles(t *testing.T, fileNames []string) (dirPath string, filePaths []string) { @@ -115,10 +117,11 @@ func TestAddHeader(t *testing.T) { tempDir := t.TempDir() filePath := filepath.Join(tempDir, "test.txt") originalContent := "This is the original file content" - _ = afero.WriteFile(AppFs, filePath, []byte(originalContent), 0644) + err := afero.WriteFile(AppFs, filePath, []byte(originalContent), 0644) + require.NoError(t, err) header := "Copyright (c) 2023 Test Corp" - err := AddHeader(filePath, header) + err = AddHeader(filePath, header) assert.Nil(t, err) // Read file and verify header was prepended @@ -126,8 +129,8 @@ func TestAddHeader(t *testing.T) { assert.Contains(t, string(content), header) assert.Contains(t, string(content), originalContent) // Header should come before original content - headerIdx := lo.IndexOf([]byte(string(content)), []byte(header)[0]) - contentIdx := lo.IndexOf([]byte(string(content)), []byte(originalContent)[0]) + headerIdx := strings.Index(string(content), header) + contentIdx := strings.Index(string(content), originalContent) assert.Less(t, headerIdx, contentIdx) }) diff --git a/licensecheck/update_test.go b/licensecheck/update_test.go index a57fc2e..dd1eb14 100644 --- a/licensecheck/update_test.go +++ b/licensecheck/update_test.go @@ -1018,7 +1018,7 @@ func TestGitOperations(t *testing.T) { require.NoError(t, exec.Command("git", "-C", tempDir, "add", "test.txt").Run()) - commitCmd := exec.Command("git", "-C", tempDir, "commit", "-m", "first commit") + commitCmd := exec.Command("git", "-C", tempDir, "-c", "commit.gpgsign=false", "commit", "-m", "first commit") commitCmd.Env = append(os.Environ(), "GIT_AUTHOR_DATE=2020-01-01T12:00:00Z", "GIT_COMMITTER_DATE=2020-01-01T12:00:00Z") require.NoError(t, commitCmd.Run()) @@ -1027,7 +1027,7 @@ func TestGitOperations(t *testing.T) { err = os.WriteFile(testFile2, []byte("test content 2"), 0644) require.NoError(t, err) require.NoError(t, exec.Command("git", "-C", tempDir, "add", "test2.txt").Run()) - commitCmd2 := exec.Command("git", "-C", tempDir, "commit", "-m", "second commit") + commitCmd2 := exec.Command("git", "-C", tempDir, "-c", "commit.gpgsign=false", "commit", "-m", "second commit") commitCmd2.Env = append(os.Environ(), "GIT_AUTHOR_DATE=2023-01-01T12:00:00Z", "GIT_COMMITTER_DATE=2023-01-01T12:00:00Z") require.NoError(t, commitCmd2.Run()) @@ -1036,12 +1036,14 @@ func TestGitOperations(t *testing.T) { require.NoError(t, err) // Resolve symlinks since macOS TempDir paths are symlinks (/var -> /private/var) - evalRoot, _ := filepath.EvalSymlinks(tempDir) + evalRoot, err := filepath.EvalSymlinks(tempDir) + require.NoError(t, err) assert.Equal(t, evalRoot, root) }) t.Run("buildRepositoryCache", func(t *testing.T) { - evalRoot, _ := filepath.EvalSymlinks(tempDir) + evalRoot, err := filepath.EvalSymlinks(tempDir) + require.NoError(t, err) cache, firstYear, err := buildRepositoryCache(evalRoot) require.NoError(t, err) assert.Equal(t, 2020, firstYear) @@ -1076,9 +1078,11 @@ func TestGitOperations(t *testing.T) { }) t.Run("getCachedFileLastCommitYear", func(t *testing.T) { - evalRoot, _ := filepath.EvalSymlinks(tempDir) + evalRoot, err := filepath.EvalSymlinks(tempDir) + require.NoError(t, err) once = sync.Once{} - _ = InitializeGitCache(evalRoot) + err = InitializeGitCache(evalRoot) + require.NoError(t, err) year, err := getCachedFileLastCommitYear("test2.txt", evalRoot) require.NoError(t, err) @@ -1092,7 +1096,8 @@ func TestGitOperations(t *testing.T) { t.Run("getFileLastCommitYear", func(t *testing.T) { evalRoot, _ := filepath.EvalSymlinks(tempDir) once = sync.Once{} - _ = InitializeGitCache(evalRoot) + err = InitializeGitCache(evalRoot) + require.NoError(t, err) year, err := getFileLastCommitYear(filepath.Join(evalRoot, "test.txt"), evalRoot) require.NoError(t, err) diff --git a/repodata/repodata_test.go b/repodata/repodata_test.go index 5b121c2..652d86f 100644 --- a/repodata/repodata_test.go +++ b/repodata/repodata_test.go @@ -114,7 +114,7 @@ func TestTransform(t *testing.T) { t.Run("empty array returns empty result", func(t *testing.T) { result, err := Transform([]*github.Repository{}) require.NoError(t, err) - assert.Equal(t, []map[string]interface{}(nil), result) + assert.Empty(t, result) }) t.Run("transform repo with string fields", func(t *testing.T) { From deac4f0d31bff72384d34ca3229076a067c2c04e Mon Sep 17 00:00:00 2001 From: Mohan Manikanta Date: Mon, 15 Jun 2026 10:17:04 +0530 Subject: [PATCH 4/5] fix: addressed review comments --- licensecheck/copyright.go | 8 +++- licensecheck/copyright_test.go | 46 ++++++++++-------- licensecheck/licensecheck_test.go | 53 +++++++++++++-------- licensecheck/update_test.go | 77 ++++++++++++++++++++----------- 4 files changed, 118 insertions(+), 66 deletions(-) diff --git a/licensecheck/copyright.go b/licensecheck/copyright.go index b9672a8..f9d3aea 100644 --- a/licensecheck/copyright.go +++ b/licensecheck/copyright.go @@ -18,6 +18,10 @@ func HasCopyright(filePath string) (bool, error) { return HasMatchingCopyright(filePath, "copyright", false) } +// copyrightHeaderBytes is the number of bytes from the start of a file +// that are searched for a copyright statement. +const copyrightHeaderBytes = 300 + // HasMatchingCopyright takes an explicit copyright statement and validates that // a given file contains that string in the header (first 1k chars) func HasMatchingCopyright(filePath string, copyrightStatement string, caseSensitive bool) (bool, error) { @@ -26,8 +30,8 @@ func HasMatchingCopyright(filePath string, copyrightStatement string, caseSensit return false, err } - // Check the first 300 characters - n := 300 + // Check the first copyrightHeaderBytes characters + n := copyrightHeaderBytes if len(b) < n { n = len(b) } diff --git a/licensecheck/copyright_test.go b/licensecheck/copyright_test.go index 13955bf..5e717df 100644 --- a/licensecheck/copyright_test.go +++ b/licensecheck/copyright_test.go @@ -142,8 +142,10 @@ func TestHasMatchingCopyright(t *testing.T) { for _, tt := range cases { t.Run(tt.description, func(t *testing.T) { - f, _ := afero.TempFile(AppFs, tempDir, "") - _ = afero.WriteFile(AppFs, f.Name(), []byte(tt.fileContents), 0644) + f, err := afero.TempFile(AppFs, tempDir, "") + require.NoError(t, err) + err = afero.WriteFile(AppFs, f.Name(), []byte(tt.fileContents), 0644) + require.NoError(t, err) // run test actualValid, err := HasMatchingCopyright(f.Name(), desiredCopyrightString, tt.caseSensitive) assert.ErrorIs(t, err, tt.expectedError, tt.description) @@ -265,8 +267,10 @@ func TestHasCopyright(t *testing.T) { for _, tt := range cases { t.Run(tt.description, func(t *testing.T) { - f, _ := afero.TempFile(AppFs, tempDir, "") - _ = afero.WriteFile(AppFs, f.Name(), []byte(tt.fileContents), 0644) + f, err := afero.TempFile(AppFs, tempDir, "") + require.NoError(t, err) + err = afero.WriteFile(AppFs, f.Name(), []byte(tt.fileContents), 0644) + require.NoError(t, err) // run test actualValid, err := HasCopyright(f.Name()) assert.ErrorIs(t, err, tt.expectedError, tt.description) @@ -287,9 +291,9 @@ func TestHasMatchingCopyright_EdgeCases(t *testing.T) { AppFs := afero.NewOsFs() tempDir := t.TempDir() - t.Run("file exactly 300 bytes with copyright at end", func(t *testing.T) { - // Create a file exactly 300 bytes where "Copyright" appears at byte 290 - padding := make([]byte, 290) + t.Run("file exactly copyrightHeaderBytes bytes with copyright at end", func(t *testing.T) { + // Create a file exactly copyrightHeaderBytes bytes where "Copyright" appears near the end + padding := make([]byte, copyrightHeaderBytes-10) for i := range padding { padding[i] = 'A' } @@ -305,7 +309,7 @@ func TestHasMatchingCopyright_EdgeCases(t *testing.T) { assert.True(t, hasCopyright) }) - t.Run("file less than 300 bytes", func(t *testing.T) { + t.Run("file less than copyrightHeaderBytes bytes", func(t *testing.T) { content := "Short file with Copyright notice" f, err := afero.TempFile(AppFs, tempDir, "") @@ -318,26 +322,30 @@ func TestHasMatchingCopyright_EdgeCases(t *testing.T) { assert.True(t, hasCopyright) }) - t.Run("file larger than 300 bytes with copyright after header", func(t *testing.T) { - // Create content > 300 bytes with copyright appearing after byte 300 - header := make([]byte, 350) + t.Run("file larger than copyrightHeaderBytes bytes with copyright after header", func(t *testing.T) { + // Create content > copyrightHeaderBytes bytes with copyright appearing after the header + header := make([]byte, copyrightHeaderBytes+50) for i := range header { header[i] = 'X' } content := string(header) + "\nCopyright notice here" - f, _ := afero.TempFile(AppFs, tempDir, "") - _ = afero.WriteFile(AppFs, f.Name(), []byte(content), 0644) + f, err := afero.TempFile(AppFs, tempDir, "") + require.NoError(t, err) + err = afero.WriteFile(AppFs, f.Name(), []byte(content), 0644) + require.NoError(t, err) - // Should not find copyright since it's after the 300-byte header check + // Should not find copyright since it's after the copyrightHeaderBytes-byte header check hasCopyright, err := HasMatchingCopyright(f.Name(), "Copyright", false) assert.Nil(t, err) assert.False(t, hasCopyright) }) t.Run("empty search string", func(t *testing.T) { - f, _ := afero.TempFile(AppFs, tempDir, "") - _ = afero.WriteFile(AppFs, f.Name(), []byte("Some content"), 0644) + f, err := afero.TempFile(AppFs, tempDir, "") + require.NoError(t, err) + err = afero.WriteFile(AppFs, f.Name(), []byte("Some content"), 0644) + require.NoError(t, err) // Empty string should always be found hasCopyright, err := HasMatchingCopyright(f.Name(), "", false) @@ -346,8 +354,10 @@ func TestHasMatchingCopyright_EdgeCases(t *testing.T) { }) t.Run("search string longer than file", func(t *testing.T) { - f, _ := afero.TempFile(AppFs, tempDir, "") - _ = afero.WriteFile(AppFs, f.Name(), []byte("Short"), 0644) + f, err := afero.TempFile(AppFs, tempDir, "") + require.NoError(t, err) + err = afero.WriteFile(AppFs, f.Name(), []byte("Short"), 0644) + require.NoError(t, err) hasCopyright, err := HasMatchingCopyright(f.Name(), "This is a very long copyright statement that is longer than the file content", false) assert.Nil(t, err) diff --git a/licensecheck/licensecheck_test.go b/licensecheck/licensecheck_test.go index 5974397..41a599a 100644 --- a/licensecheck/licensecheck_test.go +++ b/licensecheck/licensecheck_test.go @@ -21,7 +21,10 @@ func createTempFiles(t *testing.T, fileNames []string) (dirPath string, filePath // create file filePaths = lo.Map(fileNames, func(fileName string, i int) string { filePath := filepath.Join(tempDir, fileName) - _ = afero.WriteFile(AppFs, filePath, []byte("Bob Loblaw's Law Blog"), 0644) + err := AppFs.MkdirAll(filepath.Dir(filePath), 0755) + require.NoError(t, err) + err = afero.WriteFile(AppFs, filePath, []byte("Bob Loblaw's Law Blog"), 0644) + require.NoError(t, err) return filePath }) @@ -100,14 +103,16 @@ func TestAddHeader(t *testing.T) { t.Run("add header to empty file", func(t *testing.T) { tempDir := t.TempDir() filePath := filepath.Join(tempDir, "test.txt") - _ = afero.WriteFile(AppFs, filePath, []byte(""), 0644) + err := afero.WriteFile(AppFs, filePath, []byte(""), 0644) + require.NoError(t, err) header := "Copyright (c) 2023 Test Corp" - err := AddHeader(filePath, header) - assert.Nil(t, err) + err = AddHeader(filePath, header) + require.NoError(t, err) // Read file and verify header was added - content, _ := afero.ReadFile(AppFs, filePath) + content, err := afero.ReadFile(AppFs, filePath) + require.NoError(t, err) assert.Contains(t, string(content), header) // Should have double newline after header assert.Contains(t, string(content), header+"\n\n") @@ -122,10 +127,11 @@ func TestAddHeader(t *testing.T) { header := "Copyright (c) 2023 Test Corp" err = AddHeader(filePath, header) - assert.Nil(t, err) + require.NoError(t, err) // Read file and verify header was prepended - content, _ := afero.ReadFile(AppFs, filePath) + content, err := afero.ReadFile(AppFs, filePath) + require.NoError(t, err) assert.Contains(t, string(content), header) assert.Contains(t, string(content), originalContent) // Header should come before original content @@ -137,13 +143,15 @@ func TestAddHeader(t *testing.T) { t.Run("add multi-line header", func(t *testing.T) { tempDir := t.TempDir() filePath := filepath.Join(tempDir, "test.txt") - _ = afero.WriteFile(AppFs, filePath, []byte("Original content"), 0644) + err := afero.WriteFile(AppFs, filePath, []byte("Original content"), 0644) + require.NoError(t, err) header := "Copyright (c) 2023 Test Corp\nSPDX-License-Identifier: MPL-2.0" - err := AddHeader(filePath, header) - assert.Nil(t, err) + err = AddHeader(filePath, header) + require.NoError(t, err) - content, _ := afero.ReadFile(AppFs, filePath) + content, err := afero.ReadFile(AppFs, filePath) + require.NoError(t, err) assert.Contains(t, string(content), "Copyright (c) 2023 Test Corp") assert.Contains(t, string(content), "SPDX-License-Identifier: MPL-2.0") assert.Contains(t, string(content), "Original content") @@ -166,11 +174,13 @@ func TestAddLicenseFile(t *testing.T) { assert.NotEmpty(t, licensePath) // Verify file exists - fileExists, _ := afero.Exists(AppFs, licensePath) + fileExists, err := afero.Exists(AppFs, licensePath) + require.NoError(t, err) assert.True(t, fileExists) // Verify content contains MPL-2.0 license text - content, _ := afero.ReadFile(AppFs, licensePath) + content, err := afero.ReadFile(AppFs, licensePath) + require.NoError(t, err) assert.Contains(t, string(content), "Mozilla Public License") }) @@ -179,7 +189,8 @@ func TestAddLicenseFile(t *testing.T) { licensePath, err := AddLicenseFile(tempDir, "Apache-2.0") assert.Nil(t, err) - content, _ := afero.ReadFile(AppFs, licensePath) + content, err := afero.ReadFile(AppFs, licensePath) + require.NoError(t, err) assert.Contains(t, string(content), "Apache License") }) @@ -188,7 +199,8 @@ func TestAddLicenseFile(t *testing.T) { licensePath, err := AddLicenseFile(tempDir, "MIT") assert.Nil(t, err) - content, _ := afero.ReadFile(AppFs, licensePath) + content, err := afero.ReadFile(AppFs, licensePath) + require.NoError(t, err) assert.Contains(t, string(content), "Permission is hereby granted") }) @@ -281,7 +293,7 @@ func TestFindLicenseFiles(t *testing.T) { }, { description: "Don't match directories", - input: []string{"LICENSE", "license/blah.txt"}, + input: []string{"LICENSE", "subdir/blah.txt"}, expectedOutput: []string{"LICENSE"}, }, } @@ -291,6 +303,7 @@ func TestFindLicenseFiles(t *testing.T) { tempDir, _ := createTempFiles(t, tt.input) // run test actualOutput, err := FindLicenseFiles(tempDir) + require.NoError(t, err) // validate file was renamed successfully expectedOutputPaths := lo.Map(tt.expectedOutput, func(p string, _ int) string { return filepath.Join(tempDir, p) @@ -320,12 +333,14 @@ func TestFindLicenseFiles_ErrorHandling(t *testing.T) { // Create a subdirectory with a LICENSE file subDir := filepath.Join(tempDir, "subdir") - _ = AppFs.MkdirAll(subDir, 0755) - _ = afero.WriteFile(AppFs, filepath.Join(subDir, "LICENSE"), []byte("sublicense"), 0644) + err := AppFs.MkdirAll(subDir, 0755) + require.NoError(t, err) + err = afero.WriteFile(AppFs, filepath.Join(subDir, "LICENSE"), []byte("sublicense"), 0644) + require.NoError(t, err) // FindLicenseFiles should only find files in the top-level directory, not subdirs result, err := FindLicenseFiles(tempDir) - assert.Nil(t, err) + require.NoError(t, err) assert.Equal(t, []string{}, result) }) } diff --git a/licensecheck/update_test.go b/licensecheck/update_test.go index dd1eb14..c5648c9 100644 --- a/licensecheck/update_test.go +++ b/licensecheck/update_test.go @@ -360,9 +360,7 @@ package main configYear: 2022, forceCurrentYear: true, expectModified: true, - expectedContent: `// Copyright IBM Corp. 2022, ` + string(rune(currentYear/1000+48)) + string(rune((currentYear/100)%10+48)) + string(rune((currentYear/10)%10+48)) + string(rune(currentYear%10+48)) + ` -package main -`, + expectedContent: fmt.Sprintf("// Copyright IBM Corp. 2022, %d\npackage main\n", currentYear), }, { name: "Update start year when different from config", @@ -379,10 +377,8 @@ package main `, }, { - name: "No update needed", - initialContent: `// Copyright IBM Corp. ` + string(rune(currentYear/1000+48)) + string(rune((currentYear/100)%10+48)) + string(rune((currentYear/10)%10+48)) + string(rune(currentYear%10+48)) + ` -package main -`, + name: "No update needed", + initialContent: fmt.Sprintf("// Copyright IBM Corp. %d\npackage main\n", currentYear), targetHolder: "IBM Corp.", configYear: currentYear, expectModified: false, @@ -468,10 +464,8 @@ package main expectNeedsUpdate: true, }, { - name: "No update needed - current", - fileContent: `// Copyright IBM Corp. ` + string(rune(currentYear/1000+48)) + string(rune((currentYear/100)%10+48)) + string(rune((currentYear/10)%10+48)) + string(rune(currentYear%10+48)) + ` -package main -`, + name: "No update needed - current", + fileContent: fmt.Sprintf("// Copyright IBM Corp. %d\npackage main\n", currentYear), targetHolder: "IBM Corp.", configYear: currentYear, expectNeedsUpdate: false, @@ -996,6 +990,29 @@ package main assert.Equal(t, expected, string(content)) } +// resetGitCacheForTest resets the package-level git cache variables to a clean +// state both before a test runs and after it completes (via t.Cleanup). +// +// These variables have no concurrent-access protection in production code, so +// any test that touches them MUST NOT call t.Parallel(). Centralising the +// reset here makes the requirement explicit and ensures cleanup happens even +// when a test fails or panics. +func resetGitCacheForTest(t *testing.T) { + t.Helper() + once = sync.Once{} + lastCommitYearsCache = nil + firstCommitYearCached = 0 + t.Cleanup(func() { + once = sync.Once{} + lastCommitYearsCache = nil + firstCommitYearCached = 0 + }) +} + +// TestGitOperations exercises the package-level git cache (once, +// lastCommitYearsCache, firstCommitYearCached). These subtests share that +// state and must remain sequential — do NOT add t.Parallel() here or to any +// subtest, as the underlying variables have no concurrent-access protection. func TestGitOperations(t *testing.T) { if _, err := exec.LookPath("git"); err != nil { t.Skip("git executable not found in PATH; skipping git operations test") @@ -1058,8 +1075,8 @@ func TestGitOperations(t *testing.T) { }) t.Run("InitializeGitCache", func(t *testing.T) { + resetGitCacheForTest(t) evalRoot, _ := filepath.EvalSymlinks(tempDir) - once = sync.Once{} err := InitializeGitCache(evalRoot) require.NoError(t, err) assert.Equal(t, 2020, firstCommitYearCached) @@ -1069,8 +1086,12 @@ func TestGitOperations(t *testing.T) { assert.Equal(t, 2023, lastCommitYearsCache["test2.txt"]) }) - t.Run("InitializeGitCache - Failure", func(t *testing.T) { - once = sync.Once{} + t.Run("InitializeGitCache - gracefully degrades on invalid repo", func(t *testing.T) { + // InitializeGitCache intentionally swallows git errors so that callers + // never fail hard when the working directory is not a git repository. + // Instead it initialises an empty cache and returns nil, + // letting downstream code fall back to config-supplied years. + resetGitCacheForTest(t) err := InitializeGitCache("/non/existent/path/for/git/cache") require.NoError(t, err) assert.NotNil(t, lastCommitYearsCache) @@ -1078,9 +1099,9 @@ func TestGitOperations(t *testing.T) { }) t.Run("getCachedFileLastCommitYear", func(t *testing.T) { + resetGitCacheForTest(t) evalRoot, err := filepath.EvalSymlinks(tempDir) require.NoError(t, err) - once = sync.Once{} err = InitializeGitCache(evalRoot) require.NoError(t, err) @@ -1094,8 +1115,8 @@ func TestGitOperations(t *testing.T) { }) t.Run("getFileLastCommitYear", func(t *testing.T) { + resetGitCacheForTest(t) evalRoot, _ := filepath.EvalSymlinks(tempDir) - once = sync.Once{} err = InitializeGitCache(evalRoot) require.NoError(t, err) @@ -1110,8 +1131,8 @@ func TestGitOperations(t *testing.T) { }) t.Run("GetRepoFirstCommitYear", func(t *testing.T) { + resetGitCacheForTest(t) evalRoot, _ := filepath.EvalSymlinks(tempDir) - once = sync.Once{} year, err := GetRepoFirstCommitYear(evalRoot) require.NoError(t, err) assert.Equal(t, 2020, year) @@ -1135,6 +1156,7 @@ func TestUpdateCopyrightHeaderWithCache(t *testing.T) { tests := []struct { name string + fileName string initialContent string targetHolder string configYear int @@ -1146,7 +1168,8 @@ func TestUpdateCopyrightHeaderWithCache(t *testing.T) { expectedContent string }{ { - name: "Update start year using repoFirstYear when configYear is 0", + name: "Update start year using repoFirstYear when configYear is 0", + fileName: "test.go", initialContent: `// Copyright IBM Corp. 2023 package main `, @@ -1162,7 +1185,8 @@ package main `, }, { - name: "No update when ignoring year 1 and configYear differs", + name: "No update when ignoring year 1 and configYear differs", + fileName: "test.go", initialContent: `// Copyright IBM Corp. 2023, 2023 package main `, @@ -1175,7 +1199,8 @@ package main expectModified: false, }, { - name: "Update end year with forceCurrentYear", + name: "Update end year with forceCurrentYear", + fileName: "test.go", initialContent: `// Copyright IBM Corp. 2020, 2022 package main `, @@ -1191,7 +1216,8 @@ package main `, }, { - name: "Skip .copywrite.hcl file", + name: "Skip .copywrite.hcl file", + fileName: ".copywrite.hcl", initialContent: `// Copyright IBM Corp. 2020 schema_version = 1 `, @@ -1204,7 +1230,8 @@ schema_version = 1 expectModified: false, }, { - name: "Wrong holder (Google Inc.) - no update", + name: "Wrong holder (Google Inc.) - no update", + fileName: "test.go", initialContent: `// Copyright (c) Google Inc. package main `, @@ -1222,11 +1249,7 @@ package main t.Run(tt.name, func(t *testing.T) { tempDir := t.TempDir() - fileName := "test.go" - if tt.name == "Skip .copywrite.hcl file" { - fileName = ".copywrite.hcl" - } - testFile := filepath.Join(tempDir, fileName) + testFile := filepath.Join(tempDir, tt.fileName) err := os.WriteFile(testFile, []byte(tt.initialContent), 0644) require.NoError(t, err) From ca086c4b737323c27b8acb8388f5e693aac19c6d Mon Sep 17 00:00:00 2001 From: Mohan Manikanta Date: Mon, 15 Jun 2026 10:47:29 +0530 Subject: [PATCH 5/5] fix: tighten assertion in TestAddHeader test-case --- licensecheck/licensecheck_test.go | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/licensecheck/licensecheck_test.go b/licensecheck/licensecheck_test.go index 41a599a..0137ce0 100644 --- a/licensecheck/licensecheck_test.go +++ b/licensecheck/licensecheck_test.go @@ -114,8 +114,8 @@ func TestAddHeader(t *testing.T) { content, err := afero.ReadFile(AppFs, filePath) require.NoError(t, err) assert.Contains(t, string(content), header) - // Should have double newline after header - assert.Contains(t, string(content), header+"\n\n") + // Should have double newline after header, with nothing following it + assert.Equal(t, header+"\n\n", string(content), "file should contain only the header followed by a blank line with nothing after") }) t.Run("add header to file with existing content", func(t *testing.T) { @@ -132,18 +132,18 @@ func TestAddHeader(t *testing.T) { // Read file and verify header was prepended content, err := afero.ReadFile(AppFs, filePath) require.NoError(t, err) - assert.Contains(t, string(content), header) - assert.Contains(t, string(content), originalContent) - // Header should come before original content - headerIdx := strings.Index(string(content), header) - contentIdx := strings.Index(string(content), originalContent) - assert.Less(t, headerIdx, contentIdx) + // File must start with the exact header followed by a blank line + assert.True(t, strings.HasPrefix(string(content), header+"\n\n"), + "file should start with the exact header followed by a blank line") + assert.True(t, strings.HasSuffix(string(content), originalContent), + "original content should follow the header unchanged") }) t.Run("add multi-line header", func(t *testing.T) { tempDir := t.TempDir() filePath := filepath.Join(tempDir, "test.txt") - err := afero.WriteFile(AppFs, filePath, []byte("Original content"), 0644) + originalContent := "This is the original file content" + err := afero.WriteFile(AppFs, filePath, []byte(originalContent), 0644) require.NoError(t, err) header := "Copyright (c) 2023 Test Corp\nSPDX-License-Identifier: MPL-2.0" @@ -152,9 +152,13 @@ func TestAddHeader(t *testing.T) { content, err := afero.ReadFile(AppFs, filePath) require.NoError(t, err) - assert.Contains(t, string(content), "Copyright (c) 2023 Test Corp") - assert.Contains(t, string(content), "SPDX-License-Identifier: MPL-2.0") - assert.Contains(t, string(content), "Original content") + expectedPrefix := header + "\n\n" + assert.True(t, strings.HasPrefix(string(content), expectedPrefix), + "file should start with the multi-line header followed by a blank line") + assert.True(t, strings.HasSuffix(string(content), originalContent), + "original content should follow the header unchanged") + assert.Equal(t, expectedPrefix+originalContent, string(content), + "file should contain only the header and original content with nothing in between") }) t.Run("error on non-existent file", func(t *testing.T) {