@@ -1686,3 +1686,225 @@ func TestNpmPublishWithLocalGitVcsProps(t *testing.T) {
16861686 tests .VcsFixtureMainURL , tests .VcsFixtureMainRevision , tests .VcsFixtureMainBranch )
16871687 assert .Greater (t , count , 0 )
16881688}
1689+
1690+ // TestNpmInstallFailOnMissingDepsWithoutBuildInfo tests the --fail-on-missing-deps flag
1691+ // when build-info collection is not enabled. The flag should be recognized but have no effect.
1692+ // STEP 1: Initialize test environment
1693+ // STEP 2: Create npm project with dependencies
1694+ // STEP 3: Run "jfrog npm install --fail-on-missing-deps" (WITHOUT build-name/build-number)
1695+ // STEP 4: Verify command succeeds (flag ignored when no build-info collection)
1696+ func TestNpmInstallFailOnMissingDepsWithoutBuildInfo (t * testing.T ) {
1697+ initNpmTest (t ) // STEP 1: Initialize test with mock Artifactory
1698+ defer cleanNpmTest (t )
1699+
1700+ wd , err := os .Getwd ()
1701+ require .NoError (t , err )
1702+
1703+ // STEP 2: Setup npm project in temporary directory
1704+ npmPath := initNpmProjectTest (t )
1705+ chdirCallBack := clientTestUtils .ChangeDirWithCallback (t , wd , npmPath )
1706+ defer chdirCallBack ()
1707+
1708+ // STEP 3: Run npm install with --fail-on-missing-deps but WITHOUT build-info collection
1709+ // This should succeed because the flag only affects build-info collection
1710+ runJfrogCli (t , "npm" , "install" , "--fail-on-missing-deps" )
1711+
1712+ // STEP 4: Verify success - flag is ignored when no build-info collection
1713+ // (No assertion needed - runJfrogCli asserts NoError internally)
1714+ clientTestUtils .ChangeDirAndAssert (t , wd )
1715+ }
1716+
1717+ // TestNpmInstallWithoutFailOnMissingDepsFlag tests npm install with build-info collection
1718+ // but WITHOUT the --fail-on-missing-deps flag (legacy behavior with available deps).
1719+ // STEP 1: Initialize test environment
1720+ // STEP 2: Create npm project with dependencies
1721+ // STEP 3: Run "jfrog npm install --build-name=X --build-number=Y" (WITHOUT --fail-on-missing-deps)
1722+ // STEP 4: Verify command succeeds (legacy behavior - warns on missing deps, doesn't fail)
1723+ // STEP 5: Verify build-info was published
1724+ func TestNpmInstallWithoutFailOnMissingDepsFlag (t * testing.T ) {
1725+ initNpmTest (t ) // STEP 1: Initialize test with mock Artifactory
1726+ defer cleanNpmTest (t )
1727+
1728+ buildName := "npm-no-strict-test"
1729+ buildNumber := "1"
1730+
1731+ // STEP 1 (continued): Clean old build if exists
1732+ inttestutils .DeleteBuild (serverDetails .ArtifactoryUrl , buildName , artHttpDetails )
1733+ defer inttestutils .DeleteBuild (serverDetails .ArtifactoryUrl , buildName , artHttpDetails )
1734+
1735+ wd , err := os .Getwd ()
1736+ require .NoError (t , err )
1737+
1738+ // STEP 2: Setup npm project in temporary directory
1739+ npmPath := initNpmProjectTest (t )
1740+ chdirCallBack := clientTestUtils .ChangeDirWithCallback (t , wd , npmPath )
1741+ defer chdirCallBack ()
1742+
1743+ // STEP 3: Run npm install with build-info collection but WITHOUT strict mode
1744+ runJfrogCli (t , "npm" , "install" , "--build-name=" + buildName , "--build-number=" + buildNumber )
1745+
1746+ // STEP 4: Verify success (legacy behavior - warns on missing, doesn't fail)
1747+ clientTestUtils .ChangeDirAndAssert (t , wd )
1748+
1749+ // STEP 5: Verify build publish succeeds (publishes local build-info to Artifactory)
1750+ publishErr := artifactoryCli .Exec ("bp" , buildName , buildNumber )
1751+ assert .NoError (t , publishErr , "Build publish should SUCCEED and publish build-info to Artifactory" )
1752+
1753+ // STEP 6: Verify build-info was published to Artifactory
1754+ publishedBuildInfo , found , err := tests .GetBuildInfo (serverDetails , buildName , buildNumber )
1755+ assert .NoError (t , err )
1756+ assert .True (t , found , "Build info should be found in Artifactory after bp publish" )
1757+ assert .NotNil (t , publishedBuildInfo )
1758+ }
1759+
1760+ // TestNpmInstallLegacyModeWarnsWithMissingCache installs xml/json from Artifactory,
1761+ // then clears npm _cacache tarballs while keeping node_modules.
1762+ // Without --fail-on-missing-deps the command succeeds and still publishes partial build-info.
1763+ func TestNpmInstallLegacyModeWarnsWithMissingCache (t * testing.T ) {
1764+ initNpmTest (t )
1765+ defer cleanNpmTest (t )
1766+
1767+ buildName := "npm-legacy-warn-test"
1768+ buildNumber := "1"
1769+
1770+ inttestutils .DeleteBuild (serverDetails .ArtifactoryUrl , buildName , artHttpDetails )
1771+ defer inttestutils .DeleteBuild (serverDetails .ArtifactoryUrl , buildName , artHttpDetails )
1772+
1773+ wd , err := os .Getwd ()
1774+ require .NoError (t , err )
1775+
1776+ npmPath := initNpmProjectTest (t )
1777+ chdirCallBack := clientTestUtils .ChangeDirWithCallback (t , wd , npmPath )
1778+ defer chdirCallBack ()
1779+
1780+ cacheDir := t .TempDir ()
1781+ err = runJfrogCliWithoutAssertion ("npm" , "install" , "--cache=" + cacheDir )
1782+ assert .NoError (t , err , "Initial npm install should populate node_modules and the isolated cache from Artifactory" )
1783+
1784+ wipeNpmCacacheTarballs (t , cacheDir )
1785+
1786+ err = runJfrogCliWithoutAssertion ("npm" , "install" ,
1787+ "--cache=" + cacheDir ,
1788+ "--build-name=" + buildName ,
1789+ "--build-number=" + buildNumber )
1790+ assert .NoError (t , err , "Without --fail-on-missing-deps, missing cache tarballs should not fail the command" )
1791+
1792+ clientTestUtils .ChangeDirAndAssert (t , wd )
1793+ require .NoError (t , artifactoryCli .Exec ("bp" , buildName , buildNumber ))
1794+ publishedBuildInfo , found , err := tests .GetBuildInfo (serverDetails , buildName , buildNumber )
1795+ assert .NoError (t , err )
1796+ assert .True (t , found , "Partial build-info should still be published in legacy (warn) mode" )
1797+ assert .NotNil (t , publishedBuildInfo )
1798+ }
1799+
1800+ // TestNpmInstallWithFailOnMissingDepsFlag tests npm install with the --fail-on-missing-deps
1801+ // flag enabled. When all dependencies (regular/peer/bundled/optional) are available, this should succeed.
1802+ // In strict mode, 100% dependency resolution is required for ALL 4 categories.
1803+ // STEP 1: Initialize test environment
1804+ // STEP 2: Create npm project with dependencies
1805+ // STEP 3: Run "jfrog npm install --build-name=X --build-number=Y --fail-on-missing-deps"
1806+ // STEP 4: Verify command succeeds (all 4 dep categories available)
1807+ // STEP 5: Verify build-info was published with all dependencies
1808+ func TestNpmInstallWithFailOnMissingDepsFlag (t * testing.T ) {
1809+ initNpmTest (t ) // STEP 1: Initialize test with mock Artifactory
1810+ defer cleanNpmTest (t )
1811+
1812+ buildName := "npm-strict-test"
1813+ buildNumber := "1"
1814+
1815+ // STEP 1 (continued): Clean old build if exists
1816+ inttestutils .DeleteBuild (serverDetails .ArtifactoryUrl , buildName , artHttpDetails )
1817+ defer inttestutils .DeleteBuild (serverDetails .ArtifactoryUrl , buildName , artHttpDetails )
1818+
1819+ wd , err := os .Getwd ()
1820+ require .NoError (t , err )
1821+
1822+ // STEP 2: Setup npm project in temporary directory
1823+ npmPath := initNpmProjectTest (t )
1824+ chdirCallBack := clientTestUtils .ChangeDirWithCallback (t , wd , npmPath )
1825+ defer chdirCallBack ()
1826+
1827+ // STEP 3: Run npm install with strict mode enabled (all deps must be available)
1828+ // With a normal npm project, this should succeed (all deps available)
1829+ runJfrogCli (t , "npm" , "install" ,
1830+ "--build-name=" + buildName ,
1831+ "--build-number=" + buildNumber ,
1832+ "--fail-on-missing-deps" )
1833+
1834+ // STEP 4: Verify success - command completed without failing
1835+ clientTestUtils .ChangeDirAndAssert (t , wd )
1836+
1837+ // STEP 5: Verify build publish succeeds (publishes local build-info to Artifactory)
1838+ publishErr := artifactoryCli .Exec ("bp" , buildName , buildNumber )
1839+ assert .NoError (t , publishErr , "Build publish should SUCCEED when strict mode succeeds and all deps are available" )
1840+
1841+ // STEP 6: Verify build-info was published to Artifactory (strict mode didn't prevent it)
1842+ publishedBuildInfo , found , err := tests .GetBuildInfo (serverDetails , buildName , buildNumber )
1843+ assert .NoError (t , err )
1844+ assert .True (t , found , "Build info should be published to Artifactory when using --fail-on-missing-deps with available deps" )
1845+ assert .NotNil (t , publishedBuildInfo )
1846+ }
1847+
1848+ // wipeNpmCacacheTarballs removes cached tarballs but keeps the _cacache directory.
1849+ // GetNpmConfigCache requires _cacache to exist; deleting the whole cache dir is a different error path.
1850+ // node_modules is left in place so the next npm install stays up to date and does not refill the cache.
1851+ func wipeNpmCacacheTarballs (t * testing.T , cacheDir string ) {
1852+ cacachePath := filepath .Join (cacheDir , "_cacache" )
1853+ require .NoError (t , os .RemoveAll (filepath .Join (cacachePath , "content-v2" )))
1854+ require .NoError (t , os .RemoveAll (filepath .Join (cacachePath , "index-v5" )))
1855+ require .NoError (t , os .MkdirAll (cacachePath , 0755 ))
1856+ }
1857+
1858+ // TestNpmInstallFailsWithMissingCacheStrict tests STRICT MODE FAILURE SCENARIO
1859+ // when npm _cacache tarballs are missing (corrupted/cleared cache).
1860+ // With packages in node_modules but cache corrupted, strict mode should fail.
1861+ // STEP 1: Create npm project with dependencies from Artifactory
1862+ // STEP 2: npm install populates both node_modules AND _cacache
1863+ // STEP 3: Wipe _cacache tarballs (keeps _cacache dir, simulates cache corruption)
1864+ // STEP 4: npm install with --fail-on-missing-deps finds cache entries missing
1865+ // STEP 5: Strict mode fails, build-info NOT published
1866+ func TestNpmInstallFailsWithMissingCacheStrict (t * testing.T ) {
1867+ initNpmTest (t )
1868+ defer cleanNpmTest (t )
1869+
1870+ buildName := "npm-missing-cache-test"
1871+ buildNumber := "1"
1872+
1873+ inttestutils .DeleteBuild (serverDetails .ArtifactoryUrl , buildName , artHttpDetails )
1874+ defer inttestutils .DeleteBuild (serverDetails .ArtifactoryUrl , buildName , artHttpDetails )
1875+
1876+ wd , err := os .Getwd ()
1877+ require .NoError (t , err )
1878+
1879+ npmPath := initNpmProjectTest (t )
1880+ chdirCallBack := clientTestUtils .ChangeDirWithCallback (t , wd , npmPath )
1881+ defer chdirCallBack ()
1882+
1883+ cacheDir := t .TempDir ()
1884+ err = runJfrogCliWithoutAssertion ("npm" , "install" , "--cache=" + cacheDir )
1885+ assert .NoError (t , err , "Initial npm install should populate node_modules and the isolated cache from Artifactory" )
1886+
1887+ wipeNpmCacacheTarballs (t , cacheDir )
1888+
1889+ err = runJfrogCliWithoutAssertion ("npm" , "install" ,
1890+ "--cache=" + cacheDir ,
1891+ "--build-name=" + buildName ,
1892+ "--build-number=" + buildNumber ,
1893+ "--fail-on-missing-deps" )
1894+ assert .Error (t , err , "npm install with --fail-on-missing-deps should fail when cache tarballs are missing" )
1895+ if err != nil {
1896+ assert .True (t ,
1897+ strings .Contains (err .Error (), "cannot be 100% resolved" ) ||
1898+ strings .Contains (err .Error (), "will not be included in the build-info" ),
1899+ "Error should mention unresolved build-info dependencies, got: %v" , err )
1900+ }
1901+
1902+ clientTestUtils .ChangeDirAndAssert (t , wd )
1903+ publishedBuildInfo , found , err := tests .GetBuildInfo (serverDetails , buildName , buildNumber )
1904+ assert .NoError (t , err )
1905+ assert .False (t , found , "Build info should not exist in Artifactory when collection failed" )
1906+ assert .Nil (t , publishedBuildInfo )
1907+
1908+ publishErr := artifactoryCli .Exec ("bp" , buildName , buildNumber )
1909+ assert .Error (t , publishErr , "bp should fail because SaveBuildInfo was skipped" )
1910+ }
0 commit comments