-
Notifications
You must be signed in to change notification settings - Fork 44
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #470 from NishantBansal2003/checker-proposal
added checker module to validate kpm three-party dependencies
- Loading branch information
Showing
2 changed files
with
321 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
}) | ||
} | ||
} |