@@ -1686,3 +1686,263 @@ 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 tests LEGACY BEHAVIOR
1761+ // when dependencies are missing from cache but flag is NOT used.
1762+ // STEP 1: Create npm project and populate cache
1763+ // STEP 2: Delete npm cache (keep node_modules)
1764+ // STEP 3: Run jfrog npm install WITHOUT --fail-on-missing-deps
1765+ // STEP 4: Verify command SUCCEEDS (legacy mode warns but doesn't fail)
1766+ // STEP 5: Verify build-info is still published (warnings don't block build-info)
1767+ // This validates the WARN behavior that was the default before strict flag was added.
1768+ func TestNpmInstallLegacyModeWarnsWithMissingCache (t * testing.T ) {
1769+ initNpmTest (t )
1770+ defer cleanNpmTest (t )
1771+
1772+ buildName := "npm-legacy-warn-test"
1773+ buildNumber := "1"
1774+
1775+ inttestutils .DeleteBuild (serverDetails .ArtifactoryUrl , buildName , artHttpDetails )
1776+ defer inttestutils .DeleteBuild (serverDetails .ArtifactoryUrl , buildName , artHttpDetails )
1777+
1778+ wd , err := os .Getwd ()
1779+ require .NoError (t , err )
1780+
1781+ // STEP 1: Create npm project and populate cache
1782+ npmPath := initNpmProjectTest (t )
1783+ chdirCallBack := clientTestUtils .ChangeDirWithCallback (t , wd , npmPath )
1784+ defer chdirCallBack ()
1785+
1786+ // First npm install to populate cache
1787+ err = runJfrogCliWithoutAssertion ("npm" , "install" )
1788+ assert .NoError (t , err , "Initial npm install should succeed to populate cache" )
1789+
1790+ // STEP 2: Delete npm cache (but keep node_modules)
1791+ // NOTE: Get the ACTUAL npm cache path (may be custom, not default ~/.npm)
1792+ cacheCmd := exec .Command ("npm" , "get" , "cache" )
1793+ cacheDirBytes , err := cacheCmd .Output ()
1794+ assert .NoError (t , err , "Failed to get npm cache directory" )
1795+ cacheDir := strings .TrimSpace (string (cacheDirBytes ))
1796+ npmCachePath := filepath .Join (cacheDir , "_cacache" )
1797+ if cacheStat , err := os .Stat (npmCachePath ); err == nil && cacheStat .IsDir () {
1798+ err = os .RemoveAll (npmCachePath )
1799+ assert .NoError (t , err , "Failed to delete npm cache directory" )
1800+ }
1801+
1802+ // STEP 3: Run jfrog npm install WITHOUT --fail-on-missing-deps
1803+ // Legacy behavior: should WARN about missing deps but SUCCEED
1804+ err = runJfrogCliWithoutAssertion ("npm" , "install" ,
1805+ "--build-name=" + buildName ,
1806+ "--build-number=" + buildNumber )
1807+ // Note: We can't easily assert the warning message was logged in e2e tests,
1808+ // but we verify that despite missing cache, the command SUCCEEDS (not FAILS)
1809+
1810+ // STEP 4 & 5: Verify SUCCESS (legacy mode doesn't fail on missing deps)
1811+ assert .NoError (t , err ,
1812+ "npm install WITHOUT --fail-on-missing-deps should SUCCEED even with missing cache (legacy behavior)" )
1813+
1814+ // This test verifies: missing cache WITHOUT strict flag → command SUCCEEDS + WARNS but still publishes build-info
1815+ clientTestUtils .ChangeDirAndAssert (t , wd )
1816+
1817+ // Verify build publish succeeds (legacy mode allows partial build-info)
1818+ publishErr := artifactoryCli .Exec ("bp" , buildName , buildNumber )
1819+ assert .NoError (t , publishErr , "Build publish should SUCCEED even when some deps are missing (legacy mode)" )
1820+
1821+ // Verify build-info was published to Artifactory (even with warnings about missing deps)
1822+ publishedBuildInfo , found , err := tests .GetBuildInfo (serverDetails , buildName , buildNumber )
1823+ assert .NoError (t , err )
1824+ assert .True (t , found , "Build info should be published to Artifactory even with legacy warn mode (deps won't be included but build-info is)" )
1825+ assert .NotNil (t , publishedBuildInfo )
1826+ }
1827+
1828+ // TestNpmInstallWithFailOnMissingDepsFlag tests npm install with the --fail-on-missing-deps
1829+ // flag enabled. When all dependencies are available, this should succeed.
1830+ // STEP 1: Initialize test environment
1831+ // STEP 2: Create npm project with dependencies
1832+ // STEP 3: Run "jfrog npm install --build-name=X --build-number=Y --fail-on-missing-deps"
1833+ // STEP 4: Verify command succeeds (all deps available in npm cache)
1834+ // STEP 5: Verify build-info was published with all dependencies
1835+ func TestNpmInstallWithFailOnMissingDepsFlag (t * testing.T ) {
1836+ initNpmTest (t ) // STEP 1: Initialize test with mock Artifactory
1837+ defer cleanNpmTest (t )
1838+
1839+ buildName := "npm-strict-test"
1840+ buildNumber := "1"
1841+
1842+ // STEP 1 (continued): Clean old build if exists
1843+ inttestutils .DeleteBuild (serverDetails .ArtifactoryUrl , buildName , artHttpDetails )
1844+ defer inttestutils .DeleteBuild (serverDetails .ArtifactoryUrl , buildName , artHttpDetails )
1845+
1846+ wd , err := os .Getwd ()
1847+ require .NoError (t , err )
1848+
1849+ // STEP 2: Setup npm project in temporary directory
1850+ npmPath := initNpmProjectTest (t )
1851+ chdirCallBack := clientTestUtils .ChangeDirWithCallback (t , wd , npmPath )
1852+ defer chdirCallBack ()
1853+
1854+ // STEP 3: Run npm install with strict mode enabled (all deps must be available)
1855+ // With a normal npm project, this should succeed (all deps available)
1856+ runJfrogCli (t , "npm" , "install" ,
1857+ "--build-name=" + buildName ,
1858+ "--build-number=" + buildNumber ,
1859+ "--fail-on-missing-deps" )
1860+
1861+ // STEP 4: Verify success - command completed without failing
1862+ clientTestUtils .ChangeDirAndAssert (t , wd )
1863+
1864+ // STEP 5: Verify build publish succeeds (publishes local build-info to Artifactory)
1865+ publishErr := artifactoryCli .Exec ("bp" , buildName , buildNumber )
1866+ assert .NoError (t , publishErr , "Build publish should SUCCEED when strict mode succeeds and all deps are available" )
1867+
1868+ // STEP 6: Verify build-info was published to Artifactory (strict mode didn't prevent it)
1869+ publishedBuildInfo , found , err := tests .GetBuildInfo (serverDetails , buildName , buildNumber )
1870+ assert .NoError (t , err )
1871+ assert .True (t , found , "Build info should be published to Artifactory when using --fail-on-missing-deps with available deps" )
1872+ assert .NotNil (t , publishedBuildInfo )
1873+ }
1874+
1875+ // TestNpmInstallFailsWithMissingCacheStrict tests the FAILURE SCENARIO
1876+ // when npm cache is missing but node_modules exist.
1877+ // This reproduces real-world scenarios: CI cache cleared, network issues, disk space reclaimed.
1878+ // STEP 1: Create npm project and run npm install (populate cache + node_modules)
1879+ // STEP 2: Delete npm cache (keep node_modules)
1880+ // STEP 3: Run jfrog npm install with --fail-on-missing-deps
1881+ // STEP 4: npm install succeeds (packages in node_modules), but strict mode fails (cache missing)
1882+ // STEP 5: Verify error occurs and message mentions missing dependencies
1883+ func TestNpmInstallFailsWithMissingCacheStrict (t * testing.T ) {
1884+ initNpmTest (t )
1885+ defer cleanNpmTest (t )
1886+
1887+ buildName := "npm-missing-cache-test"
1888+ buildNumber := "1"
1889+
1890+ inttestutils .DeleteBuild (serverDetails .ArtifactoryUrl , buildName , artHttpDetails )
1891+ defer inttestutils .DeleteBuild (serverDetails .ArtifactoryUrl , buildName , artHttpDetails )
1892+
1893+ wd , err := os .Getwd ()
1894+ require .NoError (t , err )
1895+
1896+ // STEP 1: Create npm project and populate cache
1897+ npmPath := initNpmProjectTest (t )
1898+ chdirCallBack := clientTestUtils .ChangeDirWithCallback (t , wd , npmPath )
1899+ defer chdirCallBack ()
1900+
1901+ // Run npm install to populate both node_modules AND cache
1902+ err = runJfrogCliWithoutAssertion ("npm" , "install" )
1903+ assert .NoError (t , err , "Initial npm install should succeed to populate cache" )
1904+
1905+ // STEP 2: Delete npm cache (but keep node_modules)
1906+ // This simulates real-world cache loss scenarios:
1907+ // - CI cache cleared between builds
1908+ // - Network interruption during cache sync
1909+ // - Disk space reclaimed
1910+ // NOTE: Get the ACTUAL npm cache path (may be custom, not default ~/.npm)
1911+ cacheCmd := exec .Command ("npm" , "get" , "cache" )
1912+ cacheDirBytes , err := cacheCmd .Output ()
1913+ assert .NoError (t , err , "Failed to get npm cache directory" )
1914+ cacheDir := strings .TrimSpace (string (cacheDirBytes ))
1915+ npmCachePath := filepath .Join (cacheDir , "_cacache" )
1916+ if cacheStat , err := os .Stat (npmCachePath ); err == nil && cacheStat .IsDir () {
1917+ err = os .RemoveAll (npmCachePath )
1918+ assert .NoError (t , err , "Failed to delete npm cache directory" )
1919+ }
1920+
1921+ // STEP 3: Run jfrog npm install with strict mode
1922+ // npm install will SUCCEED (packages already in node_modules)
1923+ // But CalculateNpmDependenciesList will find cache empty
1924+ // Handler will FAIL (strict mode with missing cache)
1925+ err = runJfrogCliWithoutAssertion ("npm" , "install" ,
1926+ "--build-name=" + buildName ,
1927+ "--build-number=" + buildNumber ,
1928+ "--fail-on-missing-deps" )
1929+
1930+ // STEP 4 & 5: Verify FAILURE
1931+ assert .Error (t , err ,
1932+ "npm install with --fail-on-missing-deps should FAIL when cache is missing" )
1933+ assert .Contains (t , err .Error (), "dependencies will not be included" ,
1934+ "Error message should mention missing dependencies in build-info" )
1935+
1936+ // STEP 6: Verify build-info was NOT published (error thrown before SaveBuildInfo)
1937+ clientTestUtils .ChangeDirAndAssert (t , wd )
1938+
1939+ publishedBuildInfo , found , err := tests .GetBuildInfo (serverDetails , buildName , buildNumber )
1940+ assert .NoError (t , err )
1941+ assert .False (t , found , "Build info should NOT be published when strict mode fails" )
1942+ assert .Nil (t , publishedBuildInfo , "Build info should be nil when command fails" )
1943+
1944+ // STEP 7: Verify that `jf rt bp` (build publish) would fail because there's no build-info
1945+ // This proves that strict mode prevented build-info collection completely
1946+ publishErr := artifactoryCli .Exec ("bp" , buildName , buildNumber )
1947+ assert .Error (t , publishErr , "Build publish should FAIL because no build-info was collected (strict mode prevented it)" )
1948+ }
0 commit comments