github.com/jfrog/jfrog-cli-core/v2@v2.51.0/utils/reposnapshot/snapshotmanager_test.go (about)

     1  package reposnapshot
     2  
     3  import (
     4  	"encoding/json"
     5  	"os"
     6  	"path"
     7  	"path/filepath"
     8  	"testing"
     9  
    10  	clientutils "github.com/jfrog/jfrog-client-go/utils"
    11  	"github.com/jfrog/jfrog-client-go/utils/io/fileutils"
    12  	"github.com/stretchr/testify/assert"
    13  )
    14  
    15  const dummyRepoKey = "dummy-repo-local"
    16  
    17  var expectedFile = filepath.Join("testdata", dummyRepoKey)
    18  
    19  func TestLoadDoesNotExist(t *testing.T) {
    20  	_, exists, err := LoadRepoSnapshotManager(dummyRepoKey, "/path/to/file")
    21  	assert.NoError(t, err)
    22  	assert.False(t, exists)
    23  }
    24  
    25  func TestLoad(t *testing.T) {
    26  	sm, exists, err := LoadRepoSnapshotManager(dummyRepoKey, expectedFile)
    27  	assert.NoError(t, err)
    28  	assert.True(t, exists)
    29  	// Convert to wrapper in order to compare
    30  	expectedRoot := createTestSnapshotTree(t)
    31  	expectedWrapper, err := expectedRoot.convertToWrapper()
    32  	assert.NoError(t, err)
    33  	rootWrapper, err := sm.root.convertToWrapper()
    34  	assert.NoError(t, err)
    35  
    36  	// Marshal json to compare strings
    37  	expected, err := json.Marshal(expectedWrapper)
    38  	assert.NoError(t, err)
    39  	actual, err := json.Marshal(rootWrapper)
    40  	assert.NoError(t, err)
    41  
    42  	// Compare
    43  	assert.Equal(t, clientutils.IndentJson(expected), clientutils.IndentJson(actual))
    44  	assert.Equal(t, expectedFile, sm.snapshotFilePath)
    45  	assert.Equal(t, dummyRepoKey, sm.repoKey)
    46  }
    47  
    48  func TestSaveToFile(t *testing.T) {
    49  	manager := initSnapshotManagerTest(t)
    50  	assert.NoError(t, manager.root.convertAndSaveToFile(manager.snapshotFilePath))
    51  
    52  	// Assert file written as expected.
    53  	expected, err := os.ReadFile(expectedFile)
    54  	assert.NoError(t, err)
    55  	actual, err := os.ReadFile(manager.snapshotFilePath)
    56  	assert.NoError(t, err)
    57  	assert.Equal(t, clientutils.IndentJson(expected), clientutils.IndentJson(actual))
    58  }
    59  
    60  func TestNodeCompletedAndTreeCollapsing(t *testing.T) {
    61  	manager := initSnapshotManagerTest(t)
    62  	path0 := "0"
    63  	node0, err := manager.LookUpNode(path0)
    64  	assert.NoError(t, err)
    65  	assert.NotNil(t, node0)
    66  	actualPath, err := node0.getActualPath()
    67  	assert.NoError(t, err)
    68  	assert.Equal(t, path.Join(".", path0), actualPath)
    69  
    70  	// Try setting a dir with no files as completed. Should not succeed as children are not completed.
    71  	assert.NoError(t, node0.CheckCompleted())
    72  	assertNotCompleted(t, node0)
    73  
    74  	if len(node0.children) != 1 {
    75  		assert.Len(t, node0.children, 1)
    76  		return
    77  	}
    78  	node0a := getChild(node0, "a")
    79  
    80  	// Try setting a dir with no children as completed. Should not succeed as it still contains files.
    81  	assert.NoError(t, node0a.CheckCompleted())
    82  	assertNotCompleted(t, node0a)
    83  
    84  	setAllNodeFilesCompleted(node0a)
    85  	assert.NoError(t, node0a.CheckCompleted())
    86  	// Node should be completed as all files completed.
    87  	assertCompleted(t, node0a)
    88  	// Tree collapsing expected - parent should also be completed as it has no files and all children are completed.
    89  	assertCompleted(t, node0)
    90  	// root should not be completed as it still has uncompleted children.
    91  	assertNotCompleted(t, manager.root)
    92  }
    93  
    94  func TestNodeCompletedWhileExploring(t *testing.T) {
    95  	manager := initSnapshotManagerTest(t)
    96  	// Mark a node without files or children as unexplored and try to set it as completed. Should not succeed.
    97  	path2 := "2"
    98  	node2, err := manager.LookUpNode(path2)
    99  	assert.NoError(t, err)
   100  	assert.NotNil(t, node2)
   101  	node2.NodeStatus = Exploring
   102  	assert.NoError(t, node2.CheckCompleted())
   103  	assertNotCompleted(t, node2)
   104  
   105  	// Mark it as explored and try again.
   106  	node2.NodeStatus = DoneExploring
   107  	assert.NoError(t, node2.CheckCompleted())
   108  	assertCompleted(t, node2)
   109  }
   110  
   111  func assertCompleted(t *testing.T, node *Node) {
   112  	assert.Equal(t, Completed, node.NodeStatus)
   113  	assert.Nil(t, node.parent)
   114  	assert.Len(t, node.children, 0)
   115  }
   116  
   117  func assertNotCompleted(t *testing.T, node *Node) {
   118  	assert.Less(t, node.NodeStatus, Completed)
   119  }
   120  
   121  type lookUpAndPathTestSuite struct {
   122  	testName      string
   123  	path          string
   124  	errorExpected bool
   125  }
   126  
   127  func TestLookUpNodeAndActualPath(t *testing.T) {
   128  	manager := initSnapshotManagerTest(t)
   129  
   130  	tests := []lookUpAndPathTestSuite{
   131  		{"root", ".", false},
   132  		{"dir on root", "2", false},
   133  		{"complex path with separator suffix", "1/a/", false},
   134  		{"complex path with no separator suffix", "1/a", false},
   135  		{"empty path", "", true},
   136  	}
   137  
   138  	for _, test := range tests {
   139  		t.Run(test.testName, func(t *testing.T) {
   140  			node, err := manager.LookUpNode(test.path)
   141  			if test.errorExpected {
   142  				assert.ErrorContains(t, err, getLookUpNodeError(test.path))
   143  				return
   144  			} else {
   145  				assert.NoError(t, err)
   146  			}
   147  			if node == nil {
   148  				assert.NotNil(t, node)
   149  				return
   150  			}
   151  			actualPath, err := node.getActualPath()
   152  			assert.NoError(t, err)
   153  			assert.Equal(t, path.Join(".", test.path), actualPath)
   154  		})
   155  	}
   156  }
   157  
   158  func setAllNodeFilesCompleted(node *Node) {
   159  	node.filesCount = 0
   160  }
   161  
   162  // Tree dirs representation:
   163  // root -> 0 -> a + 3 files
   164  // ------> 1 -> a + 1 file
   165  // -----------> b + 2 files
   166  // ---------- + 1 file
   167  // ------> 2
   168  // ----- + 1 file
   169  func createTestSnapshotTree(t *testing.T) *Node {
   170  	root := createNodeBase(t, ".", 1, nil)
   171  	dir0 := createNodeBase(t, "0", 0, root)
   172  	dir1 := createNodeBase(t, "1", 1, root)
   173  	dir2 := createNodeBase(t, "2", 0, root)
   174  
   175  	dir0a := createNodeBase(t, "a", 3, dir0)
   176  	dir1a := createNodeBase(t, "a", 1, dir1)
   177  	dir1b := createNodeBase(t, "b", 2, dir1)
   178  
   179  	addChildren(root, dir0, dir1, dir2)
   180  	addChildren(dir0, dir0a)
   181  	addChildren(dir1, dir1a, dir1b)
   182  	return root
   183  }
   184  
   185  func addChildren(node *Node, children ...*Node) {
   186  	node.children = append(node.children, children...)
   187  }
   188  
   189  func createNodeBase(t *testing.T, name string, filesCount int, parent *Node) *Node {
   190  	node := CreateNewNode(name, parent)
   191  	node.NodeStatus = DoneExploring
   192  	for i := 0; i < filesCount; i++ {
   193  		assert.NoError(t, node.IncrementFilesCount(uint64(i)))
   194  	}
   195  	return node
   196  }
   197  
   198  func getChild(node *Node, childName string) *Node {
   199  	for _, child := range node.children {
   200  		if child.name == childName {
   201  			return child
   202  		}
   203  	}
   204  	return nil
   205  }
   206  
   207  func initSnapshotManagerTest(t *testing.T) RepoSnapshotManager {
   208  	file, err := fileutils.CreateTempFile()
   209  	assert.NoError(t, err)
   210  	assert.NoError(t, file.Close())
   211  	return newRepoSnapshotManager(createTestSnapshotTree(t), dummyRepoKey, file.Name())
   212  }
   213  
   214  func TestGetDirectorySnapshotNodeWithLruLRU(t *testing.T) {
   215  	originalCacheSize := cacheSize
   216  	cacheSize = 3
   217  	defer func() {
   218  		cacheSize = originalCacheSize
   219  	}()
   220  	manager := initSnapshotManagerTest(t)
   221  
   222  	// Assert lru cache is empty before getting nodes.
   223  	assert.Zero(t, manager.lruCache.Len())
   224  
   225  	// Get 3 nodes which will cause the cache to reach its cache size.
   226  	_ = getNodeAndAssert(t, manager, "1/b/", 1)
   227  	_ = getNodeAndAssert(t, manager, "./", 2)
   228  	_ = getNodeAndAssert(t, manager, "2/", 3)
   229  
   230  	// Get another node that exceeds the cache size and assert the LRU node was removed.
   231  	_ = getNodeAndAssert(t, manager, "0/a/", 3)
   232  	_, exists := manager.lruCache.Get("1/b/")
   233  	assert.False(t, exists)
   234  }
   235  
   236  func assertReturnedNode(t *testing.T, manager RepoSnapshotManager, node *Node, relativePath string, expectedLen int) {
   237  	if !assert.NotNil(t, node) {
   238  		return
   239  	}
   240  	actualPath, err := node.getActualPath()
   241  	assert.NoError(t, err)
   242  	assert.Equal(t, relativePath, actualPath)
   243  	assert.Equal(t, expectedLen, manager.lruCache.Len())
   244  }
   245  
   246  func getNodeAndAssert(t *testing.T, manager RepoSnapshotManager, relativePath string, expectedLen int) *Node {
   247  	node, err := manager.GetDirectorySnapshotNodeWithLru(relativePath)
   248  	assert.NoError(t, err)
   249  	assertReturnedNode(t, manager, node, path.Dir(relativePath), expectedLen)
   250  	return node
   251  }