github.com/joey-fossa/fossa-cli@v0.7.34-0.20190708193710-569f1e8679f0/analyzers/nodejs/nodejs_test.go (about) 1 package nodejs_test 2 3 import ( 4 "log" 5 "path/filepath" 6 "testing" 7 8 "github.com/stretchr/testify/assert" 9 10 "github.com/fossas/fossa-cli/analyzers" 11 "github.com/fossas/fossa-cli/analyzers/nodejs" 12 "github.com/fossas/fossa-cli/module" 13 "github.com/fossas/fossa-cli/pkg" 14 ) 15 16 /* 17 └─┬ a@1.0.0 18 ├─┬ b@2.0.0 19 │ ├── c@3.0.0 20 │ └── d@4.0.0 21 └── c@3.0.0 22 */ 23 24 // TestNDepsTransitiveImports verifies that each dependency returned by Analyze() 25 // in the transitive dependency list contains the correct dependency imports. 26 func TestNDepsTransitiveImports(t *testing.T) { 27 m := module.Module{ 28 Dir: filepath.Join("testdata", "transitive-deps"), 29 Type: pkg.NodeJS, 30 } 31 32 a, err := analyzers.New(m) 33 assert.NoError(t, err) 34 35 a.(*nodejs.Analyzer).NPM = MockNPM{ 36 JSONFilename: filepath.Join("testdata", "transitive-deps", "npm-ls-json.json"), 37 } 38 39 deps, err := a.Analyze() 40 assert.NoError(t, err) 41 42 assert.Equal(t, 1, len(deps.Direct)) 43 assertImport(t, deps.Direct, "a", "1.0.0") 44 45 assert.Equal(t, 4, len(deps.Transitive)) 46 47 packageA := findPackage(deps.Transitive, "a", "1.0.0") 48 assert.NotZero(t, packageA) 49 assert.Equal(t, 2, len(packageA.Imports)) 50 assertImport(t, packageA.Imports, "b", "2.0.0") 51 assertImport(t, packageA.Imports, "c", "3.0.0") 52 53 packageB := findPackage(deps.Transitive, "b", "2.0.0") 54 assert.NotZero(t, packageB) 55 assert.Equal(t, 2, len(packageB.Imports)) 56 assertImport(t, packageB.Imports, "c", "3.0.0") 57 assertImport(t, packageB.Imports, "d", "4.0.0") 58 59 packageC := findPackage(deps.Transitive, "c", "3.0.0") 60 assert.NotZero(t, packageC) 61 assert.Equal(t, 0, len(packageC.Imports)) 62 63 packageD := findPackage(deps.Transitive, "d", "4.0.0") 64 assert.NotZero(t, packageD) 65 assert.Equal(t, 0, len(packageD.Imports)) 66 } 67 68 // TestNoDependencies checks that there is no error even when `package.json` is 69 // missing a `dependencies` key or has an empty object as the value for 70 // `dependencies`. 71 func TestNoDependencies(t *testing.T) { 72 m := module.Module{ 73 Dir: filepath.Join("testdata", "empty"), 74 Type: pkg.NodeJS, 75 } 76 77 a, err := analyzers.New(m) 78 assert.NoError(t, err) 79 80 a.(*nodejs.Analyzer).NPM = MockNPM{ 81 JSONFilename: filepath.Join("testdata", "empty", "npm-ls-json.json"), 82 } 83 84 deps, err := a.Analyze() 85 assert.NoError(t, err) 86 assert.Empty(t, deps.Direct) 87 assert.Empty(t, deps.Transitive) 88 } 89 90 // TestDuplicateDependencies checks that analysis correctly handles duplicate 91 // dependencies, even in the case where duplicates may not have the same set of 92 // imports listed. 93 // 94 // For example, try running `npm ls --json` in the `testdata/duplicates` folder. 95 // Notice that `babel-runtime` is included as a dependency twice: once by 96 // `babel-polyfill` and once by `jira-client`. However, the _dependencies_ of 97 // `babel-runtime` are only listed _once,_ when it's imported by 98 // `babel-polyfill`. This means that we must ensure that we get transitive 99 // dependencies from the original dependency entry, not the deduplicated entry. 100 // 101 // See #257 for details. 102 func TestDuplicateDependencies(t *testing.T) { 103 m := module.Module{ 104 BuildTarget: filepath.Join("testdata", "duplicates", "package.json"), 105 Dir: filepath.Join("testdata", "duplicates"), 106 Type: pkg.NodeJS, 107 } 108 109 a, err := analyzers.New(m) 110 assert.NoError(t, err) 111 112 a.(*nodejs.Analyzer).NPM = MockNPM{ 113 JSONFilename: filepath.Join("testdata", "duplicates", "npm-ls-json.json"), 114 } 115 116 // We run this multiple times because this bug may flake; map traversal order 117 // is random in Go. 118 var failed pkg.Deps 119 for i := 0; i < 10; i++ { 120 deps, err := a.Analyze() 121 assert.NoError(t, err) 122 id := pkg.ID{ 123 Type: pkg.NodeJS, 124 Name: "regenerator-runtime", 125 Revision: "0.11.1", 126 Location: "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", 127 } 128 ok := assert.Contains(t, deps.Transitive, id) 129 if !ok { 130 failed = deps.Transitive 131 } 132 } 133 134 if t.Failed() { 135 log.Printf("%#v", failed) 136 } 137 } 138 139 var chaiDirectDep = pkg.Import{ 140 Target: "chai", 141 Resolved: pkg.ID{ 142 Name: "chai", 143 Revision: "4.1.2", 144 Type: pkg.NodeJS, 145 }, 146 } 147 148 var npmChaiFixtures = []string{ 149 filepath.Join("testdata", "chai", "installed"), 150 filepath.Join("testdata", "chai", "installed-lockfile"), 151 filepath.Join("testdata", "chai", "dev-deps"), 152 } 153 154 func TestAnalyzeWithNpmLs(t *testing.T) { 155 t.Parallel() 156 for _, fixturePath := range npmChaiFixtures { 157 t.Run(fixturePath, func(t *testing.T) { 158 t.Parallel() 159 testAnalyzeWithNpmLs(t, fixturePath) 160 }) 161 } 162 } 163 164 func testAnalyzeWithNpmLs(t *testing.T, buildTarget string) { 165 nodeModule := module.Module{ 166 Name: "test", 167 Type: pkg.NodeJS, 168 BuildTarget: buildTarget, 169 Options: map[string]interface{}{}, 170 } 171 172 analyzer, err := nodejs.New(nodeModule) 173 assert.NoError(t, err) 174 175 analyzer.NPM = MockNPM{ 176 JSONFilename: filepath.Join(buildTarget, "npm-ls-json.json"), 177 } 178 179 analysisResults, err := analyzer.Analyze() 180 assert.NoError(t, err) 181 182 assert.Len(t, analysisResults.Direct, 1) 183 assert.Len(t, analysisResults.Transitive, 7) 184 } 185 186 func TestUsingNodeModuleFallback(t *testing.T) { 187 t.Parallel() 188 for _, fixturePath := range npmChaiFixtures { 189 t.Run(fixturePath, func(t *testing.T) { 190 t.Parallel() 191 testUsingNodeModuleFallback(t, fixturePath) 192 193 }) 194 } 195 } 196 197 func testUsingNodeModuleFallback(t *testing.T, buildTarget string) { 198 nodeModule := module.Module{ 199 Name: "test", 200 Type: pkg.NodeJS, 201 BuildTarget: buildTarget, 202 Options: map[string]interface{}{}, 203 } 204 205 analyzer, err := nodejs.New(nodeModule) 206 assert.NoError(t, err) 207 208 analyzer.NPM = MockNPMFailure{} 209 210 analysisResults, err := analyzer.Analyze() 211 assert.NoError(t, err) 212 213 chaiProject := analysisResults.Transitive[chaiDirectDep.Resolved] 214 assert.NotNil(t, chaiProject) 215 assertImport(t, chaiProject.Imports, "assertion-error", "1.1.0") 216 assertImport(t, chaiProject.Imports, "check-error", "1.0.2") 217 assertImport(t, chaiProject.Imports, "get-func-name", "2.0.0") 218 assertImport(t, chaiProject.Imports, "pathval", "1.1.0") 219 assertImport(t, chaiProject.Imports, "deep-eql", "3.0.1") 220 assertImport(t, chaiProject.Imports, "type-detect", "4.0.8") 221 } 222 223 func TestUsingYarnLockfileFallback(t *testing.T) { 224 buildTarget := filepath.Join("testdata", "chai", "installed-yarn-lockfile") 225 226 nodeModule := module.Module{ 227 Name: "test", 228 Type: pkg.NodeJS, 229 BuildTarget: buildTarget, 230 Options: map[string]interface{}{}, 231 } 232 233 analyzer, err := nodejs.New(nodeModule) 234 assert.NoError(t, err) 235 236 analyzer.NPM = MockNPMFailure{} 237 238 analysisResults, err := analyzer.Analyze() 239 assert.NoError(t, err) 240 241 chaiProject := analysisResults.Transitive[chaiDirectDep.Resolved] 242 assertImport(t, chaiProject.Imports, "assertion-error", "1.1.0") 243 assertImport(t, chaiProject.Imports, "check-error", "1.0.2") 244 assertImport(t, chaiProject.Imports, "get-func-name", "2.0.0") 245 assertImport(t, chaiProject.Imports, "pathval", "1.1.0") 246 assertImport(t, chaiProject.Imports, "deep-eql", "3.0.1") 247 assertImport(t, chaiProject.Imports, "type-detect", "4.0.8") 248 } 249 250 func findPackage(packages map[pkg.ID]pkg.Package, name, revision string) pkg.Package { 251 for id := range packages { 252 if id.Name == name && id.Revision == revision { 253 return packages[id] 254 } 255 } 256 257 return pkg.Package{} 258 } 259 260 func assertImport(t *testing.T, imports pkg.Imports, name, revision string) { 261 for _, importedProj := range imports { 262 if importedProj.Resolved.Name == name { 263 if importedProj.Resolved.Revision == revision { 264 return 265 } 266 267 assert.Fail(t, "found "+name+"@"+importedProj.Resolved.Revision+" instead of "+revision) 268 } 269 } 270 271 assert.Fail(t, "missing "+name+"@"+revision) 272 }