Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/installer/cache_integrity.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,12 @@ func verifyCachedNodeArchiveIntegrity(ctx context.Context, version, archivePath
return finishDownloadCacheVerification(version, archivePath)
}

// local_install_only: never fetch SHASUMS256.txt. Trust the staged
// archive the same way machine-policy LocalInstallDir does.
if cfg.LocalOnly {
return finishDownloadCacheVerification(version, archivePath)
}

if err := verifyArchiveWithMirrorSHASUMFn(ctx, version, archivePath, cfg); err != nil {
return err
}
Expand Down
43 changes: 43 additions & 0 deletions src/installer/cache_integrity_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,49 @@ func TestVerifyCachedNodeArchiveIntegrityTrustedLocalInstallBypass(t *testing.T)
}
}

func TestVerifyCachedNodeArchiveIntegrityLocalOnlySkipsMirror(t *testing.T) {
dir := t.TempDir()
archivePath := filepath.Join(dir, "node-v22.0.0-win-x64.7z")
if err := os.WriteFile(archivePath, []byte("airgapped local archive"), 0o644); err != nil {
t.Fatalf("WriteFile() error = %v", err)
}

origVerify := verifyDownloadArchiveCacheFn
verifyDownloadArchiveCacheFn = func(string) error { return verifycache.ErrDownloadCacheMiss }
t.Cleanup(func() { verifyDownloadArchiveCacheFn = origVerify })

origTrusted := isMachinePolicyTrustedLocalArchiveFn
isMachinePolicyTrustedLocalArchiveFn = func(string) (bool, error) { return false, nil }
t.Cleanup(func() { isMachinePolicyTrustedLocalArchiveFn = origTrusted })

mirrorCalled := false
origMirror := verifyArchiveWithMirrorSHASUMFn
verifyArchiveWithMirrorSHASUMFn = func(context.Context, string, string, InstallConfig) error {
mirrorCalled = true
return errors.New("mirror should not be called")
}
t.Cleanup(func() { verifyArchiveWithMirrorSHASUMFn = origMirror })

signed := false
origSign := signDownloadArchiveCacheFn
signDownloadArchiveCacheFn = func(string) error {
signed = true
return nil
}
t.Cleanup(func() { signDownloadArchiveCacheFn = origSign })

cfg := InstallConfig{LocalOnly: true}
if err := verifyCachedNodeArchiveIntegrity(t.Context(), "22.0.0", archivePath, cfg); err != nil {
t.Fatalf("verifyCachedNodeArchiveIntegrity() error = %v", err)
}
if mirrorCalled {
t.Fatal("mirror SHASUM verification was called, want local-only skip")
}
if !signed {
t.Fatal("expected SignDownloadArchiveCache after local-only trust")
}
}

func TestVerifyCachedNodeArchiveIntegrityFallsBackToMirrorSHASUM(t *testing.T) {
dir := t.TempDir()
archiveName := "node-v22.0.0-win-x64.7z"
Expand Down
29 changes: 28 additions & 1 deletion src/test/commands/cli_t3_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package commands_test

import (
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"sync/atomic"
"testing"

"nvm/test/clitest"
Expand Down Expand Up @@ -57,15 +60,39 @@ func TestCLIInstallSkipsAlreadyInstalled(t *testing.T) {

func TestCLIInstallLocalOnlyMissingArchive(t *testing.T) {
sb := clitest.NewSandbox(t)
sb.ApplyNodeMirrorFixture(t, clitest.TestdataPath(t, "index.tab"))
if err := sb.WriteSetting("local_install_only", true); err != nil {
t.Fatalf("WriteSetting(local_install_only) error = %v", err)
}

prevFixtures := os.Getenv("NVM_TEST_HTTP_FIXTURES")
if err := os.Setenv("NVM_TEST_HTTP_FIXTURES", "1"); err != nil {
t.Fatalf("Setenv(NVM_TEST_HTTP_FIXTURES) error = %v", err)
}
t.Cleanup(func() {
if prevFixtures == "" {
_ = os.Unsetenv("NVM_TEST_HTTP_FIXTURES")
return
}
_ = os.Setenv("NVM_TEST_HTTP_FIXTURES", prevFixtures)
})

var hits atomic.Int32
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
hits.Add(1)
http.Error(w, "catalog should not be fetched", http.StatusServiceUnavailable)
}))
t.Cleanup(srv.Close)
if err := sb.WriteSetting("node_mirror", srv.URL); err != nil {
t.Fatalf("WriteSetting(node_mirror) error = %v", err)
}

stdout, stderr, err := sb.ExecuteWithSyncStub("install", "22.0.0")
if err == nil {
t.Fatal("Execute(install 22.0.0) expected error, got nil")
}
if n := hits.Load(); n != 0 {
t.Fatalf("catalog fetched %d time(s), want 0 for exact local-only install", n)
}
combined := stdout + stderr + err.Error()
if !strings.Contains(combined, "local install directory") {
t.Fatalf("Execute(install 22.0.0) output = stdout:%q stderr:%q err:%v, want local install error", stdout, stderr, err)
Expand Down
Loading