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  }