Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added checker module to validate kpm three-party dependencies #470

Merged
merged 1 commit into from
Aug 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 98 additions & 0 deletions pkg/checker/checker.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package checker

import (
"fmt"
"regexp"

"github.com/hashicorp/go-version"
pkg "kcl-lang.io/kpm/pkg/package"
)

// Checker defines an interface for KclPkg dependencies checkers.
type Checker interface {
Check(pkg.KclPkg) error
}

type DepChecker struct {
checkers []Checker
}

// NewDepChecker creates a new DepChecker with provided checkers.
func NewDepChecker(checkers ...Checker) *DepChecker {
return &DepChecker{checkers: checkers}
}

// Check runs all individual checks for a kclPkg.
func (c *DepChecker) Check(kclPkg pkg.KclPkg) error {
for _, checker := range c.checkers {
if err := checker.Check(kclPkg); err != nil {
return err
}
}
return nil
}

// IdentChecker validates the dependencies name in kclPkg.
type IdentChecker struct{}

func (c *IdentChecker) Check(kclPkg pkg.KclPkg) error {
for _, key := range kclPkg.Dependencies.Deps.Keys() {
dep, _ := kclPkg.Dependencies.Deps.Get(key)
if !isValidDependencyName(dep.Name) {
return fmt.Errorf("invalid dependency name: %s", dep.Name)
}
}
return nil
}

// VersionChecker validates the dependencies version in kclPkg.
type VersionChecker struct{}

func (c *VersionChecker) Check(kclPkg pkg.KclPkg) error {
for _, key := range kclPkg.Dependencies.Deps.Keys() {
dep, _ := kclPkg.Dependencies.Deps.Get(key)
if !isValidDependencyVersion(dep.Version) {
return fmt.Errorf("invalid dependency version: %s for %s", dep.Version, dep.Name)
}
}
return nil
}

// SumChecker validates the dependencies checksum in kclPkg.
type SumChecker struct{}

func (c *SumChecker) Check(kclPkg pkg.KclPkg) error {
if kclPkg.NoSumCheck {
return nil
}
for _, key := range kclPkg.Dependencies.Deps.Keys() {
dep, _ := kclPkg.Dependencies.Deps.Get(key)
trustedSum, err := getTrustedSum(dep)
if err != nil {
return fmt.Errorf("failed to get checksum from trusted source: %w", err)
}
if dep.Sum != trustedSum {
return fmt.Errorf("checksum verification failed for '%s': expected '%s', got '%s'", dep.Name, trustedSum, dep.Sum)
}
}
return nil
}

// isValidDependencyName reports whether the name of the dependency is appropriate.
func isValidDependencyName(name string) bool {
validNamePattern := `^[a-zA-Z][a-zA-Z0-9_\-\.]*[a-zA-Z0-9_]$`
regex := regexp.MustCompile(validNamePattern)
return regex.MatchString(name)
}

// isValidDependencyVersion reports whether v is a valid semantic version string.
func isValidDependencyVersion(v string) bool {
_, err := version.NewVersion(v)
return err == nil
}

// Placeholder for getTrustedSum function
func getTrustedSum(dep pkg.Dependency) (string, error) {
// Need to be implemented to get the trusted checksum.
return "", nil
}
223 changes: 223 additions & 0 deletions pkg/checker/checker_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
package checker

import (
"testing"

"github.com/elliotchance/orderedmap/v2"
"gotest.tools/v3/assert"
pkg "kcl-lang.io/kpm/pkg/package"
)

func TestDepCheckerCheck(t *testing.T) {
depChecker := NewDepChecker(
&IdentChecker{},
&VersionChecker{},
&SumChecker{},
)
deps1 := orderedmap.NewOrderedMap[string, pkg.Dependency]()
deps1.Set("kcl1", pkg.Dependency{
Name: "kcl1",
FullName: "kcl1",
Version: "0.0.1",
Sum: "",
})
deps1.Set("kcl2", pkg.Dependency{
Name: "kcl2",
FullName: "kcl2",
Version: "0.2.1",
Sum: "",
})

deps2 := orderedmap.NewOrderedMap[string, pkg.Dependency]()
deps2.Set("kcl1", pkg.Dependency{
Name: "kcl1",
FullName: "kcl1",
Version: "0.0.1",
Sum: "no-sum-check-enabled",
})
deps2.Set("kcl2", pkg.Dependency{
Name: "kcl2",
FullName: "kcl2",
Version: "0.2.1",
Sum: "no-sum-check-enabled",
})

deps3 := orderedmap.NewOrderedMap[string, pkg.Dependency]()
deps3.Set("kcl1", pkg.Dependency{
Name: ".kcl1",
FullName: "kcl1",
Version: "0.0.1",
Sum: "",
})

deps4 := orderedmap.NewOrderedMap[string, pkg.Dependency]()
deps4.Set("kcl1", pkg.Dependency{
Name: "kcl1",
FullName: "kcl1",
Version: "1.0.0-alpha#",
Sum: "",
})

deps5 := orderedmap.NewOrderedMap[string, pkg.Dependency]()
deps5.Set("kcl1", pkg.Dependency{
Name: "kcl1",
FullName: "kcl1",
Version: "0.0.1",
Sum: "invalid-no-sum-check-disabled",
})

tests := []struct {
name string
KclPkg pkg.KclPkg
wantErr bool
}{
{
name: "valid kcl package - with sum check",
KclPkg: pkg.KclPkg{
ModFile: pkg.ModFile{
HomePath: "path/to/modfile",
},
HomePath: "path/to/kcl/pkg",
Dependencies: pkg.Dependencies{
Deps: deps1,
},
NoSumCheck: false,
},
wantErr: false,
},
{
name: "valid kcl package - with no sum check enabled",
KclPkg: pkg.KclPkg{
ModFile: pkg.ModFile{
HomePath: "path/to/modfile",
},
HomePath: "path/to/kcl/pkg",
Dependencies: pkg.Dependencies{
Deps: deps2,
},
NoSumCheck: true,
},
wantErr: false,
},
{
name: "Invalid kcl package - invalid dependency name",
KclPkg: pkg.KclPkg{
ModFile: pkg.ModFile{
HomePath: "path/to/modfile",
},
HomePath: "path/to/kcl/pkg",
Dependencies: pkg.Dependencies{
Deps: deps3,
},
NoSumCheck: false,
},
wantErr: true,
},
{
name: "Invalid kcl package - invalid dependency version",
KclPkg: pkg.KclPkg{
ModFile: pkg.ModFile{
HomePath: "path/to/modfile",
},
HomePath: "path/to/kcl/pkg",
Dependencies: pkg.Dependencies{
Deps: deps4,
},
NoSumCheck: false,
},
wantErr: true,
},
{
name: "Invalid kcl package - with no sum check disabled - checksum mismatches",
KclPkg: pkg.KclPkg{
ModFile: pkg.ModFile{
HomePath: "path/to/modfile",
},
HomePath: "path/to/kcl/pkg",
Dependencies: pkg.Dependencies{
Deps: deps5,
},
NoSumCheck: false,
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotErr := depChecker.Check(tt.KclPkg)
if (gotErr != nil) != tt.wantErr {
t.Errorf("depChecker.Check(%v) = %v, want error %v", tt.KclPkg, gotErr, tt.wantErr)
}
})
}
}

func TestIsValidDependencyName(t *testing.T) {
tests := []struct {
name string
dependencyName string
want bool
}{
{"Empty Name", "", false},
{"Valid Name - Simple", "myDependency", true},
{"Valid Name - With Underscore", "my_dependency", true},
{"Valid Name - With Hyphen", "my-dependency", true},
{"Valid Name - With Dot", "my.dependency", true},
{"Valid Name - Mixed Case", "MyDependency", true},
{"Valid Name - Long Name", "My_Very-Long.Dependency", true},
{"Contains Number", "depend3ncy", true},
{"Starts with Special Character", "-dependency", false},
{"Starts and Ends with Dot", ".dependency.", false},
{"Starts and Ends with Hyphen", "-dependency-", false},
{"Ends with Special Character", "dependency-", false},
{"Only Special Characters", "._-", false},
{"Some Special Characters", "dep!@#$%", false},
{"Whitespace", "my dependency", false},
{"Leading Whitespace", " dependency", false},
{"Trailing Whitespace", "dependency ", false},
{"Only Dot", ".", false},
{"Only Hyphen", "-", false},
{"Only Underscore", "_", false},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := isValidDependencyName(tt.dependencyName)
assert.Equal(t, got, tt.want, tt.dependencyName)
})
}
}

func TestIsValidDependencyVersion(t *testing.T) {
tests := []struct {
name string
version string
want bool
}{
{"Empty String in version", "", false},
{"Valid SemVer - Major", "1", true},
{"Valid SemVer - Major.Minor", "1.0", true},
{"Valid SemVer - Major.Minor.Patch", "1.0.0", true},
{"Valid SemVer with Pre-release", "1.0.0-alpha", true},
{"Invalid Pre-release Format", "1.0.0-alpha..1", false},
{"Invalid Characters in Pre-release", "1.0.0-alpha#", false},
{"Valid SemVer with Pre-release and Numeric", "1.0.0-alpha.1", true},
{"Valid SemVer with Build Metadata", "1.0.0+001", true},
{"Valid SemVer with Pre-release and Build Metadata", "1.0.0-beta+exp.sha.5114f85", true},
{"Valid SemVer - Major.Minor.Patch with Leading Zeros", "01.02.03", true},
{"Trailing Dot in version", "1.0.", false},
{"Leading Dot in version", ".1.0", false},
{"Valid SemVer - Too Many Dots", "1.0.0.0", true},
{"Characters Only", "abc", false},
{"Valid SemVer - Mixed Characters", "1.0.0abc", true},
{"Whitespace", "1.0.0 ", false},
{"Valid SemVer - Non-Numeric", "v1.0.0", true},
{"Special Characters", "!@#$%", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := isValidDependencyVersion(tt.version)
assert.Equal(t, got, tt.want, tt.version)
})
}
}