From 694eec4079c3ca0b6760c241b5eeb0dea5fe27ba Mon Sep 17 00:00:00 2001 From: Stef Graces Date: Thu, 6 Mar 2025 15:45:47 +0100 Subject: [PATCH] Add downloadLocation URI validation (#3697) * Add downloadLocation URI validation Signed-off-by: Stef Graces * Update function names Signed-off-by: Stef Graces * Fixes for make lint-fix + Changes to when NONE and NOASSERTION in downloadLocation Signed-off-by: Stef Graces --------- Signed-off-by: Stef Graces Co-authored-by: Keith Zantow --- .golangci.yaml | 6 + go.mod | 1 + go.sum | 1 + .../spdxutil/helpers/download_location.go | 38 +- .../helpers/download_location_test.go | 540 +++++++++++++++++- .../spdxutil/helpers/none_if_empty.go | 12 - .../spdxutil/helpers/none_if_empty_test.go | 41 -- 7 files changed, 574 insertions(+), 65 deletions(-) delete mode 100644 syft/format/internal/spdxutil/helpers/none_if_empty.go delete mode 100644 syft/format/internal/spdxutil/helpers/none_if_empty_test.go diff --git a/.golangci.yaml b/.golangci.yaml index 3cd289615..182cfcdc8 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -61,6 +61,12 @@ linters-settings: gosec: excludes: - G115 + + staticcheck: + checks: + - all + - -SA4023 + run: timeout: 10m tests: false diff --git a/go.mod b/go.mod index 83dfa9cd0..ea17c50b2 100644 --- a/go.mod +++ b/go.mod @@ -93,6 +93,7 @@ require ( github.com/bitnami/go-version v0.0.0-20250131085805-b1f57a8634ef github.com/hashicorp/hcl/v2 v2.23.0 github.com/magiconair/properties v1.8.9 + github.com/spdx/gordf v0.0.0-20201111095634-7098f93598fb golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 ) diff --git a/go.sum b/go.sum index 033bd72d0..9c341d966 100644 --- a/go.sum +++ b/go.sum @@ -736,6 +736,7 @@ github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQ github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spdx/gordf v0.0.0-20201111095634-7098f93598fb h1:bLo8hvc8XFm9J47r690TUKBzcjSWdJDxmjXJZ+/f92U= github.com/spdx/gordf v0.0.0-20201111095634-7098f93598fb/go.mod h1:uKWaldnbMnjsSAXRurWqqrdyZen1R7kxl8TkmWk2OyM= github.com/spdx/tools-golang v0.5.5 h1:61c0KLfAcNqAjlg6UNMdkwpMernhw3zVRwDZ2x9XOmk= github.com/spdx/tools-golang v0.5.5/go.mod h1:MVIsXx8ZZzaRWNQpUDhC4Dud34edUYJYecciXgrw5vE= diff --git a/syft/format/internal/spdxutil/helpers/download_location.go b/syft/format/internal/spdxutil/helpers/download_location.go index b2bae8f96..f0576db39 100644 --- a/syft/format/internal/spdxutil/helpers/download_location.go +++ b/syft/format/internal/spdxutil/helpers/download_location.go @@ -1,6 +1,12 @@ package helpers -import "github.com/anchore/syft/syft/pkg" +import ( + "strings" + + urilib "github.com/spdx/gordf/uri" + + "github.com/anchore/syft/syft/pkg" +) const NONE = "NONE" const NOASSERTION = "NOASSERTION" @@ -14,21 +20,37 @@ func DownloadLocation(p pkg.Package) string { // (ii) the SPDX file creator has made no attempt to determine this field; or // (iii) the SPDX file creator has intentionally provided no information (no meaning should be implied by doing so). + var location string if hasMetadata(p) { switch metadata := p.Metadata.(type) { case pkg.ApkDBEntry: - return NoneIfEmpty(metadata.URL) + location = metadata.URL case pkg.NpmPackage: - return NoneIfEmpty(metadata.URL) + location = metadata.URL case pkg.NpmPackageLockEntry: - return NoneIfEmpty(metadata.Resolved) + location = metadata.Resolved case pkg.PhpComposerLockEntry: - return NoneIfEmpty(metadata.Dist.URL) + location = metadata.Dist.URL case pkg.PhpComposerInstalledEntry: - return NoneIfEmpty(metadata.Dist.URL) + location = metadata.Dist.URL case pkg.OpamPackage: - return NoneIfEmpty(metadata.URL) + location = metadata.URL } } - return NOASSERTION + return URIValue(location) +} + +func isURIValid(uri string) bool { + _, err := urilib.NewURIRef(uri) + return err == nil +} + +func URIValue(uri string) string { + if strings.ToLower(uri) != "none" { + if isURIValid(uri) { + return uri + } + return NOASSERTION + } + return NONE } diff --git a/syft/format/internal/spdxutil/helpers/download_location_test.go b/syft/format/internal/spdxutil/helpers/download_location_test.go index 2cbbaa30e..cd69a63ec 100644 --- a/syft/format/internal/spdxutil/helpers/download_location_test.go +++ b/syft/format/internal/spdxutil/helpers/download_location_test.go @@ -44,7 +44,7 @@ func Test_DownloadLocation(t *testing.T) { URL: "", }, }, - expected: NONE, + expected: NOASSERTION, }, { name: "from npm package-lock should include resolved", @@ -62,7 +62,7 @@ func Test_DownloadLocation(t *testing.T) { Resolved: "", }, }, - expected: NONE, + expected: NOASSERTION, }, { name: "from php installed.json", @@ -84,7 +84,7 @@ func Test_DownloadLocation(t *testing.T) { }, }, }, - expected: "NONE", + expected: NOASSERTION, }, { name: "from php composer.lock", @@ -106,7 +106,539 @@ func Test_DownloadLocation(t *testing.T) { }, }, }, - expected: "NONE", + expected: NOASSERTION, + }, + { + name: "none", + input: pkg.Package{ + Metadata: pkg.PhpComposerLockEntry{ + Dist: pkg.PhpComposerExternalReference{ + URL: "none", + }, + }, + }, + expected: NONE, + }, + { + name: "none uppercase", + input: pkg.Package{ + Metadata: pkg.PhpComposerLockEntry{ + Dist: pkg.PhpComposerExternalReference{ + URL: "NONE", + }, + }, + }, + expected: NONE, + }, + { + name: "invalid uri", + input: pkg.Package{ + Metadata: pkg.PhpComposerLockEntry{ + Dist: pkg.PhpComposerExternalReference{ + URL: "example/package", + }, + }, + }, + expected: NOASSERTION, + }, + { + name: "Basic Git Protocol URL", + input: pkg.Package{ + Metadata: pkg.PhpComposerLockEntry{ + Dist: pkg.PhpComposerExternalReference{ + URL: "git://git.myproject.org/MyProject", + }, + }, + }, + expected: "git://git.myproject.org/MyProject", + }, + { + name: "Git HTTPS URL with .git Extension", + input: pkg.Package{ + Metadata: pkg.PhpComposerLockEntry{ + Dist: pkg.PhpComposerExternalReference{ + URL: "git+https://git.myproject.org/MyProject.git", + }, + }, + }, + expected: "git+https://git.myproject.org/MyProject.git", + }, + { + name: "Git HTTP URL", + input: pkg.Package{ + Metadata: pkg.PhpComposerLockEntry{ + Dist: pkg.PhpComposerExternalReference{ + URL: "git+http://git.myproject.org/MyProject", + }, + }, + }, + expected: "git+http://git.myproject.org/MyProject", + }, + { + name: "Git SSH URL with .git Extension", + input: pkg.Package{ + Metadata: pkg.PhpComposerLockEntry{ + Dist: pkg.PhpComposerExternalReference{ + URL: "git+ssh://git.myproject.org/MyProject.git", + }, + }, + }, + expected: "git+ssh://git.myproject.org/MyProject.git", + }, + { + name: "Git Protocol with Prefix", + input: pkg.Package{ + Metadata: pkg.PhpComposerLockEntry{ + Dist: pkg.PhpComposerExternalReference{ + URL: "git+git://git.myproject.org/MyProject", + }, + }, + }, + expected: "git+git://git.myproject.org/MyProject", + }, + { + name: "Git URL with C File Fragment", + input: pkg.Package{ + Metadata: pkg.PhpComposerLockEntry{ + Dist: pkg.PhpComposerExternalReference{ + URL: "git://git.myproject.org/MyProject#src/somefile.c", + }, + }, + }, + expected: "git://git.myproject.org/MyProject#src/somefile.c", + }, + { + name: "Git HTTPS URL with Java File Fragment", + input: pkg.Package{ + Metadata: pkg.PhpComposerLockEntry{ + Dist: pkg.PhpComposerExternalReference{ + URL: "git+https://git.myproject.org/MyProject#src/Class.java", + }, + }, + }, + expected: "git+https://git.myproject.org/MyProject#src/Class.java", + }, + { + name: "Git URL with Master Branch", + input: pkg.Package{ + Metadata: pkg.PhpComposerLockEntry{ + Dist: pkg.PhpComposerExternalReference{ + URL: "git://git.myproject.org/MyProject.git@master", + }, + }, + }, + expected: "git://git.myproject.org/MyProject.git@master", + }, + { + name: "Git HTTPS URL with Version Tag", + input: pkg.Package{ + Metadata: pkg.PhpComposerLockEntry{ + Dist: pkg.PhpComposerExternalReference{ + URL: "git+https://git.myproject.org/MyProject.git@v1.0", + }, + }, + }, + expected: "git+https://git.myproject.org/MyProject.git@v1.0", + }, + { + name: "Git URL with Full Commit Hash", + input: pkg.Package{ + Metadata: pkg.PhpComposerLockEntry{ + Dist: pkg.PhpComposerExternalReference{ + URL: "git://git.myproject.org/MyProject.git@da39a3ee5e6b4b0d3255bfef95601890afd80709", + }, + }, + }, + expected: "git://git.myproject.org/MyProject.git@da39a3ee5e6b4b0d3255bfef95601890afd80709", + }, + { + name: "Git HTTPS URL with Branch and CPP File Path", + input: pkg.Package{ + Metadata: pkg.PhpComposerLockEntry{ + Dist: pkg.PhpComposerExternalReference{ + URL: "git+https://git.myproject.org/MyProject.git@master#/src/MyClass.cpp", + }, + }, + }, + expected: "git+https://git.myproject.org/MyProject.git@master#/src/MyClass.cpp", + }, + { + name: "Git HTTPS URL with Commit Hash and Ruby File", + input: pkg.Package{ + Metadata: pkg.PhpComposerLockEntry{ + Dist: pkg.PhpComposerExternalReference{ + URL: "git+https://git.myproject.org/MyProject@da39a3ee5e6b4b0d3255bfef95601890afd80709#lib/variable.rb", + }, + }, + }, + expected: "git+https://git.myproject.org/MyProject@da39a3ee5e6b4b0d3255bfef95601890afd80709#lib/variable.rb", + }, + { + name: "Basic Mercurial HTTP URL", + input: pkg.Package{ + Metadata: pkg.PhpComposerLockEntry{ + Dist: pkg.PhpComposerExternalReference{ + URL: "hg+http://hg.myproject.org/MyProject", + }, + }, + }, + expected: "hg+http://hg.myproject.org/MyProject", + }, + { + name: "Basic Mercurial HTTPS URL", + input: pkg.Package{ + Metadata: pkg.PhpComposerLockEntry{ + Dist: pkg.PhpComposerExternalReference{ + URL: "hg+https://hg.myproject.org/MyProject", + }, + }, + }, + expected: "hg+https://hg.myproject.org/MyProject", + }, + { + name: "Basic Mercurial SSH URL", + input: pkg.Package{ + Metadata: pkg.PhpComposerLockEntry{ + Dist: pkg.PhpComposerExternalReference{ + URL: "hg+ssh://hg.myproject.org/MyProject", + }, + }, + }, + expected: "hg+ssh://hg.myproject.org/MyProject", + }, + { + name: "Mercurial URL with File Fragment", + input: pkg.Package{ + Metadata: pkg.PhpComposerLockEntry{ + Dist: pkg.PhpComposerExternalReference{ + URL: "hg+https://hg.myproject.org/MyProject#src/somefile.c", + }, + }, + }, + expected: "hg+https://hg.myproject.org/MyProject#src/somefile.c", + }, + { + name: "Mercurial URL with Java Class Fragment", + input: pkg.Package{ + Metadata: pkg.PhpComposerLockEntry{ + Dist: pkg.PhpComposerExternalReference{ + URL: "hg+https://hg.myproject.org/MyProject#src/Class.java", + }, + }, + }, + expected: "hg+https://hg.myproject.org/MyProject#src/Class.java", + }, + { + name: "Mercurial URL with Commit Hash", + input: pkg.Package{ + Metadata: pkg.PhpComposerLockEntry{ + Dist: pkg.PhpComposerExternalReference{ + URL: "hg+https://hg.myproject.org/MyProject@da39a3ee5e6b", + }, + }, + }, + expected: "hg+https://hg.myproject.org/MyProject@da39a3ee5e6b", + }, + { + name: "Mercurial URL with Year Reference", + input: pkg.Package{ + Metadata: pkg.PhpComposerLockEntry{ + Dist: pkg.PhpComposerExternalReference{ + URL: "hg+https://hg.myproject.org/MyProject@2019", + }, + }, + }, + expected: "hg+https://hg.myproject.org/MyProject@2019", + }, + { + name: "Mercurial URL with Version Tag", + input: pkg.Package{ + Metadata: pkg.PhpComposerLockEntry{ + Dist: pkg.PhpComposerExternalReference{ + URL: "hg+https://hg.myproject.org/MyProject@v1.0", + }, + }, + }, + expected: "hg+https://hg.myproject.org/MyProject@v1.0", + }, + { + name: "Mercurial URL with Feature Branch", + input: pkg.Package{ + Metadata: pkg.PhpComposerLockEntry{ + Dist: pkg.PhpComposerExternalReference{ + URL: "hg+https://hg.myproject.org/MyProject@special_feature", + }, + }, + }, + expected: "hg+https://hg.myproject.org/MyProject@special_feature", + }, + { + name: "Mercurial URL with Branch and File Path", + input: pkg.Package{ + Metadata: pkg.PhpComposerLockEntry{ + Dist: pkg.PhpComposerExternalReference{ + URL: "hg+https://hg.myproject.org/MyProject@master#/src/MyClass.cpp", + }, + }, + }, + expected: "hg+https://hg.myproject.org/MyProject@master#/src/MyClass.cpp", + }, + { + name: "Mercurial URL with Commit Hash and Ruby File", + input: pkg.Package{ + Metadata: pkg.PhpComposerLockEntry{ + Dist: pkg.PhpComposerExternalReference{ + URL: "hg+https://hg.myproject.org/MyProject@da39a3ee5e6b#lib/variable.rb", + }, + }, + }, + expected: "hg+https://hg.myproject.org/MyProject@da39a3ee5e6b#lib/variable.rb", + }, + + // Test cases for Subversion (svn) URLs + { + name: "Basic SVN URL", + input: pkg.Package{ + Metadata: pkg.PhpComposerLockEntry{ + Dist: pkg.PhpComposerExternalReference{ + URL: "svn://svn.myproject.org/svn/MyProject", + }, + }, + }, + expected: "svn://svn.myproject.org/svn/MyProject", + }, + { + name: "SVN URL with Protocol Prefix", + input: pkg.Package{ + Metadata: pkg.PhpComposerLockEntry{ + Dist: pkg.PhpComposerExternalReference{ + URL: "svn+svn://svn.myproject.org/svn/MyProject", + }, + }, + }, + expected: "svn+svn://svn.myproject.org/svn/MyProject", + }, + { + name: "SVN HTTP URL with Trunk", + input: pkg.Package{ + Metadata: pkg.PhpComposerLockEntry{ + Dist: pkg.PhpComposerExternalReference{ + URL: "svn+http://svn.myproject.org/svn/MyProject/trunk", + }, + }, + }, + expected: "svn+http://svn.myproject.org/svn/MyProject/trunk", + }, + { + name: "SVN HTTPS URL with Trunk", + input: pkg.Package{ + Metadata: pkg.PhpComposerLockEntry{ + Dist: pkg.PhpComposerExternalReference{ + URL: "svn+https://svn.myproject.org/svn/MyProject/trunk", + }, + }, + }, + expected: "svn+https://svn.myproject.org/svn/MyProject/trunk", + }, + { + name: "SVN URL with C File Fragment", + input: pkg.Package{ + Metadata: pkg.PhpComposerLockEntry{ + Dist: pkg.PhpComposerExternalReference{ + URL: "svn+https://svn.myproject.org/MyProject#src/somefile.c", + }, + }, + }, + expected: "svn+https://svn.myproject.org/MyProject#src/somefile.c", + }, + { + name: "SVN URL with Java Class Fragment", + input: pkg.Package{ + Metadata: pkg.PhpComposerLockEntry{ + Dist: pkg.PhpComposerExternalReference{ + URL: "svn+https://svn.myproject.org/MyProject#src/Class.java", + }, + }, + }, + expected: "svn+https://svn.myproject.org/MyProject#src/Class.java", + }, + { + name: "SVN URL with Trunk and C File Fragment", + input: pkg.Package{ + Metadata: pkg.PhpComposerLockEntry{ + Dist: pkg.PhpComposerExternalReference{ + URL: "svn+https://svn.myproject.org/MyProject/trunk#src/somefile.c", + }, + }, + }, + expected: "svn+https://svn.myproject.org/MyProject/trunk#src/somefile.c", + }, + { + name: "SVN URL with Full File Path", + input: pkg.Package{ + Metadata: pkg.PhpComposerLockEntry{ + Dist: pkg.PhpComposerExternalReference{ + URL: "svn+https://svn.myproject.org/MyProject/trunk/src/somefile.c", + }, + }, + }, + expected: "svn+https://svn.myproject.org/MyProject/trunk/src/somefile.c", + }, + { + name: "SVN URL with Revision Number", + input: pkg.Package{ + Metadata: pkg.PhpComposerLockEntry{ + Dist: pkg.PhpComposerExternalReference{ + URL: "svn+https://svn.myproject.org/svn/MyProject/trunk@2019", + }, + }, + }, + expected: "svn+https://svn.myproject.org/svn/MyProject/trunk@2019", + }, + { + name: "SVN URL with Revision and CPP File Path", + input: pkg.Package{ + Metadata: pkg.PhpComposerLockEntry{ + Dist: pkg.PhpComposerExternalReference{ + URL: "svn+https://svn.myproject.org/MyProject@123#/src/MyClass.cpp", + }, + }, + }, + expected: "svn+https://svn.myproject.org/MyProject@123#/src/MyClass.cpp", + }, + { + name: "SVN URL with Trunk, Revision and Ruby File Path", + input: pkg.Package{ + Metadata: pkg.PhpComposerLockEntry{ + Dist: pkg.PhpComposerExternalReference{ + URL: "svn+https://svn.myproject.org/MyProject/trunk@1234#lib/variable/variable.rb", + }, + }, + }, + expected: "svn+https://svn.myproject.org/MyProject/trunk@1234#lib/variable/variable.rb", + }, + + // Test cases for Bazaar (bzr) URLs + { + name: "Bazaar HTTPS URL with Trunk", + input: pkg.Package{ + Metadata: pkg.PhpComposerLockEntry{ + Dist: pkg.PhpComposerExternalReference{ + URL: "bzr+https://bzr.myproject.org/MyProject/trunk", + }, + }, + }, + expected: "bzr+https://bzr.myproject.org/MyProject/trunk", + }, + { + name: "Bazaar HTTP URL with Trunk", + input: pkg.Package{ + Metadata: pkg.PhpComposerLockEntry{ + Dist: pkg.PhpComposerExternalReference{ + URL: "bzr+http://bzr.myproject.org/MyProject/trunk", + }, + }, + }, + expected: "bzr+http://bzr.myproject.org/MyProject/trunk", + }, + { + name: "Bazaar SFTP URL with Trunk", + input: pkg.Package{ + Metadata: pkg.PhpComposerLockEntry{ + Dist: pkg.PhpComposerExternalReference{ + URL: "bzr+sftp://myproject.org/MyProject/trunk", + }, + }, + }, + expected: "bzr+sftp://myproject.org/MyProject/trunk", + }, + { + name: "Bazaar SSH URL with Trunk", + input: pkg.Package{ + Metadata: pkg.PhpComposerLockEntry{ + Dist: pkg.PhpComposerExternalReference{ + URL: "bzr+ssh://myproject.org/MyProject/trunk", + }, + }, + }, + expected: "bzr+ssh://myproject.org/MyProject/trunk", + }, + { + name: "Bazaar FTP URL with Trunk", + input: pkg.Package{ + Metadata: pkg.PhpComposerLockEntry{ + Dist: pkg.PhpComposerExternalReference{ + URL: "bzr+ftp://myproject.org/MyProject/trunk", + }, + }, + }, + expected: "bzr+ftp://myproject.org/MyProject/trunk", + }, + { + name: "Bazaar Launchpad URL", + input: pkg.Package{ + Metadata: pkg.PhpComposerLockEntry{ + Dist: pkg.PhpComposerExternalReference{ + URL: "bzr+lp:MyProject", + }, + }, + }, + expected: "bzr+lp:MyProject", + }, + { + name: "Bazaar URL with C File Fragment", + input: pkg.Package{ + Metadata: pkg.PhpComposerLockEntry{ + Dist: pkg.PhpComposerExternalReference{ + URL: "bzr+https://bzr.myproject.org/MyProject/trunk#src/somefile.c", + }, + }, + }, + expected: "bzr+https://bzr.myproject.org/MyProject/trunk#src/somefile.c", + }, + { + name: "Bazaar URL with Java Class Fragment", + input: pkg.Package{ + Metadata: pkg.PhpComposerLockEntry{ + Dist: pkg.PhpComposerExternalReference{ + URL: "bzr+https://bzr.myproject.org/MyProject/trunk#src/Class.java", + }, + }, + }, + expected: "bzr+https://bzr.myproject.org/MyProject/trunk#src/Class.java", + }, + { + name: "Bazaar URL with Revision", + input: pkg.Package{ + Metadata: pkg.PhpComposerLockEntry{ + Dist: pkg.PhpComposerExternalReference{ + URL: "bzr+https://bzr.myproject.org/MyProject/trunk@2019", + }, + }, + }, + expected: "bzr+https://bzr.myproject.org/MyProject/trunk@2019", + }, + { + name: "Bazaar URL with Version Tag", + input: pkg.Package{ + Metadata: pkg.PhpComposerLockEntry{ + Dist: pkg.PhpComposerExternalReference{ + URL: "bzr+http://bzr.myproject.org/MyProject/trunk@v1.0", + }, + }, + }, + expected: "bzr+http://bzr.myproject.org/MyProject/trunk@v1.0", + }, + { + name: "Bazaar URL with Revision and C File Fragment", + input: pkg.Package{ + Metadata: pkg.PhpComposerLockEntry{ + Dist: pkg.PhpComposerExternalReference{ + URL: "bzr+https://bzr.myproject.org/MyProject/trunk@2019#src/somefile.c", + }, + }, + }, + expected: "bzr+https://bzr.myproject.org/MyProject/trunk@2019#src/somefile.c", }, } for _, test := range tests { diff --git a/syft/format/internal/spdxutil/helpers/none_if_empty.go b/syft/format/internal/spdxutil/helpers/none_if_empty.go deleted file mode 100644 index 4c037c4ad..000000000 --- a/syft/format/internal/spdxutil/helpers/none_if_empty.go +++ /dev/null @@ -1,12 +0,0 @@ -package helpers - -import ( - "strings" -) - -func NoneIfEmpty(value string) string { - if strings.TrimSpace(value) == "" { - return NONE - } - return value -} diff --git a/syft/format/internal/spdxutil/helpers/none_if_empty_test.go b/syft/format/internal/spdxutil/helpers/none_if_empty_test.go deleted file mode 100644 index 44e5d50e5..000000000 --- a/syft/format/internal/spdxutil/helpers/none_if_empty_test.go +++ /dev/null @@ -1,41 +0,0 @@ -package helpers - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func Test_noneIfEmpty(t *testing.T) { - tests := []struct { - name string - value string - expected string - }{ - { - name: "non-zero value", - value: "something", - expected: "something", - }, - { - name: "empty", - value: "", - expected: NONE, - }, - { - name: "space", - value: " ", - expected: NONE, - }, - { - name: "tab", - value: "\t", - expected: NONE, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - assert.Equal(t, test.expected, NoneIfEmpty(test.value)) - }) - } -}