github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/cmd/scaffold_test.go (about)

     1  package cmd
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  	"net/http"
    10  	"os"
    11  	"path/filepath"
    12  	"strings"
    13  	"sync"
    14  	"testing"
    15  	"time"
    16  
    17  	gcemd "cloud.google.com/go/compute/metadata"
    18  	"github.com/hashicorp/go-multierror"
    19  	"github.com/stretchr/testify/assert"
    20  	"github.com/stretchr/testify/require"
    21  	"google.golang.org/api/option"
    22  	"google.golang.org/grpc"
    23  	"google.golang.org/grpc/credentials/insecure"
    24  
    25  	"github.com/onflow/flow-go/cmd/bootstrap/utils"
    26  	"github.com/onflow/flow-go/model/bootstrap"
    27  	"github.com/onflow/flow-go/module"
    28  	"github.com/onflow/flow-go/module/component"
    29  	"github.com/onflow/flow-go/module/irrecoverable"
    30  	"github.com/onflow/flow-go/module/profiler"
    31  	p2pbuilder "github.com/onflow/flow-go/network/p2p/builder"
    32  	"github.com/onflow/flow-go/utils/unittest"
    33  )
    34  
    35  // TestLoadSecretsEncryptionKey checks that the key file is read correctly if it exists
    36  // and returns the expected sentinel error if it does not exist.
    37  func TestLoadSecretsEncryptionKey(t *testing.T) {
    38  	myID := unittest.IdentifierFixture()
    39  
    40  	unittest.RunWithTempDir(t, func(dir string) {
    41  		path := filepath.Join(dir, fmt.Sprintf(bootstrap.PathSecretsEncryptionKey, myID))
    42  
    43  		t.Run("should return ErrNotExist if file doesn't exist", func(t *testing.T) {
    44  			require.NoFileExists(t, path)
    45  			_, err := loadSecretsEncryptionKey(dir, myID)
    46  			assert.Error(t, err)
    47  			assert.True(t, errors.Is(err, os.ErrNotExist))
    48  		})
    49  
    50  		t.Run("should return key and no error if file exists", func(t *testing.T) {
    51  			err := os.MkdirAll(filepath.Join(dir, bootstrap.DirPrivateRoot, fmt.Sprintf("private-node-info_%v", myID)), 0700)
    52  			require.NoError(t, err)
    53  			key, err := utils.GenerateSecretsDBEncryptionKey()
    54  			require.NoError(t, err)
    55  			err = os.WriteFile(path, key, 0700)
    56  			require.NoError(t, err)
    57  
    58  			data, err := loadSecretsEncryptionKey(dir, myID)
    59  			assert.NoError(t, err)
    60  			assert.Equal(t, key, data)
    61  		})
    62  	})
    63  }
    64  
    65  // Test the components are started in the correct order, and are run serially
    66  func TestComponentsRunSerially(t *testing.T) {
    67  	ctx, cancel := context.WithCancel(context.Background())
    68  	signalerCtx, _ := irrecoverable.WithSignaler(ctx)
    69  
    70  	nb := FlowNode("scaffold test")
    71  	nb.componentBuilder = component.NewComponentManagerBuilder()
    72  
    73  	logger := &testLog{}
    74  
    75  	name1 := "component 1"
    76  	nb.Component(name1, func(node *NodeConfig) (module.ReadyDoneAware, error) {
    77  		logger.Logf("%s initialized", name1)
    78  		return newMockReadyDone(logger, name1), nil
    79  	})
    80  
    81  	name2 := "component 2"
    82  	nb.Component(name2, func(node *NodeConfig) (module.ReadyDoneAware, error) {
    83  		logger.Logf("%s initialized", name2)
    84  		c := newMockComponent(logger, name2)
    85  		c.startFn = func(ctx irrecoverable.SignalerContext, name string) {
    86  			// add delay to test components are run serially
    87  			time.Sleep(5 * time.Millisecond)
    88  		}
    89  		return c, nil
    90  	})
    91  
    92  	name3 := "component 3"
    93  	nb.Component(name3, func(node *NodeConfig) (module.ReadyDoneAware, error) {
    94  		logger.Logf("%s initialized", name3)
    95  		return newMockReadyDone(logger, name3), nil
    96  	})
    97  
    98  	err := nb.handleComponents()
    99  	assert.NoError(t, err)
   100  
   101  	cm := nb.componentBuilder.Build()
   102  
   103  	cm.Start(signalerCtx)
   104  	<-cm.Ready()
   105  	cancel()
   106  	<-cm.Done()
   107  
   108  	logs := logger.logs
   109  
   110  	assert.Len(t, logs, 10)
   111  
   112  	// components are initialized in a specific order, so check that the order is correct
   113  	startLogs := logs[:len(logs)-3]
   114  	assert.Equal(t, []string{
   115  		"component 1 initialized",
   116  		"component 1 ready",
   117  		"component 2 initialized",
   118  		"component 2 started",
   119  		"component 2 ready",
   120  		"component 3 initialized",
   121  		"component 3 ready",
   122  	}, startLogs)
   123  
   124  	// components are stopped via context cancellation, so the specific order is random
   125  	doneLogs := logs[len(logs)-3:]
   126  	assert.ElementsMatch(t, []string{
   127  		"component 1 done",
   128  		"component 2 done",
   129  		"component 3 done",
   130  	}, doneLogs)
   131  }
   132  
   133  func TestPostShutdown(t *testing.T) {
   134  	nb := FlowNode("scaffold test")
   135  
   136  	logger := testLog{}
   137  
   138  	err1 := errors.New("error 1")
   139  	err3 := errors.New("error 3")
   140  	errExpected := multierror.Append(&multierror.Error{}, err1, err3)
   141  	nb.
   142  		ShutdownFunc(func() error {
   143  			logger.Log("shutdown 1")
   144  			return err1
   145  		}).
   146  		ShutdownFunc(func() error {
   147  			logger.Log("shutdown 2")
   148  			return nil
   149  		}).
   150  		ShutdownFunc(func() error {
   151  			logger.Log("shutdown 3")
   152  			return err3
   153  		})
   154  
   155  	err := nb.postShutdown()
   156  	assert.EqualError(t, err, errExpected.Error())
   157  
   158  	logs := logger.logs
   159  	assert.Len(t, logs, 3)
   160  	assert.Equal(t, []string{
   161  		"shutdown 1",
   162  		"shutdown 2",
   163  		"shutdown 3",
   164  	}, logs)
   165  }
   166  
   167  func TestOverrideComponent(t *testing.T) {
   168  	ctx, cancel := context.WithCancel(context.Background())
   169  	signalerCtx, _ := irrecoverable.WithSignaler(ctx)
   170  
   171  	nb := FlowNode("scaffold test")
   172  	nb.componentBuilder = component.NewComponentManagerBuilder()
   173  
   174  	logger := &testLog{}
   175  
   176  	name1 := "component 1"
   177  	nb.Component(name1, func(node *NodeConfig) (module.ReadyDoneAware, error) {
   178  		logger.Logf("%s initialized", name1)
   179  		return newMockReadyDone(logger, name1), nil
   180  	})
   181  
   182  	name2 := "component 2"
   183  	nb.Component(name2, func(node *NodeConfig) (module.ReadyDoneAware, error) {
   184  		logger.Logf("%s initialized", name2)
   185  		return newMockReadyDone(logger, name2), nil
   186  	})
   187  
   188  	name3 := "component 3"
   189  	nb.Component(name3, func(node *NodeConfig) (module.ReadyDoneAware, error) {
   190  		logger.Logf("%s initialized", name3)
   191  		return newMockReadyDone(logger, name3), nil
   192  	})
   193  
   194  	// Overrides second component
   195  	nb.OverrideComponent(name2, func(node *NodeConfig) (module.ReadyDoneAware, error) {
   196  		logger.Logf("%s overridden", name2)
   197  		return newMockReadyDone(logger, name2), nil
   198  	})
   199  
   200  	err := nb.handleComponents()
   201  	assert.NoError(t, err)
   202  
   203  	cm := nb.componentBuilder.Build()
   204  
   205  	cm.Start(signalerCtx)
   206  
   207  	<-cm.Ready()
   208  
   209  	logs := logger.logs
   210  
   211  	assert.Len(t, logs, 6)
   212  
   213  	// components are initialized in a specific order, so check that the order is correct
   214  	assert.Equal(t, []string{
   215  		"component 1 initialized",
   216  		"component 1 ready",
   217  		"component 2 overridden", // overridden version of 2 should be initialized.
   218  		"component 2 ready",
   219  		"component 3 initialized",
   220  		"component 3 ready",
   221  	}, logs)
   222  
   223  	cancel()
   224  	<-cm.Done()
   225  }
   226  
   227  func TestOverrideModules(t *testing.T) {
   228  	ctx, cancel := context.WithCancel(context.Background())
   229  	signalerCtx, _ := irrecoverable.WithSignaler(ctx)
   230  
   231  	nb := FlowNode("scaffold test")
   232  	nb.componentBuilder = component.NewComponentManagerBuilder()
   233  
   234  	logger := &testLog{}
   235  
   236  	name1 := "module 1"
   237  	nb.Module(name1, func(nodeConfig *NodeConfig) error {
   238  		logger.Logf("%s initialized", name1)
   239  		return nil
   240  	})
   241  
   242  	name2 := "module 2"
   243  	nb.Module(name2, func(nodeConfig *NodeConfig) error {
   244  		logger.Logf("%s initialized", name2)
   245  		return nil
   246  	})
   247  
   248  	name3 := "module 3"
   249  	nb.Module(name3, func(nodeConfig *NodeConfig) error {
   250  		logger.Logf("%s initialized", name3)
   251  		return nil
   252  	})
   253  
   254  	// Overrides second module
   255  	nb.OverrideModule(name2, func(nodeConfig *NodeConfig) error {
   256  		logger.Logf("%s overridden", name2)
   257  		return nil
   258  	})
   259  
   260  	err := nb.handleModules()
   261  	assert.NoError(t, err)
   262  
   263  	cm := nb.componentBuilder.Build()
   264  	require.NoError(t, err)
   265  
   266  	cm.Start(signalerCtx)
   267  
   268  	<-cm.Ready()
   269  
   270  	logs := logger.logs
   271  
   272  	assert.Len(t, logs, 3)
   273  
   274  	// components are initialized in a specific order, so check that the order is correct
   275  	assert.Equal(t, []string{
   276  		"module 1 initialized",
   277  		"module 2 overridden", // overridden version of 2 should be initialized.
   278  		"module 3 initialized",
   279  	}, logs)
   280  
   281  	cancel()
   282  	<-cm.Done()
   283  }
   284  
   285  type testComponentDefinition struct {
   286  	name         string
   287  	factory      ReadyDoneFactory
   288  	errorHandler component.OnError
   289  }
   290  
   291  func runRestartableTest(t *testing.T, components []testComponentDefinition, expectedErr error, shutdown <-chan struct{}) {
   292  	ctx, cancel := context.WithCancel(context.Background())
   293  	signalerCtx, errChan := irrecoverable.WithSignaler(ctx)
   294  
   295  	go func() {
   296  		select {
   297  		case <-ctx.Done():
   298  			return
   299  		case err := <-errChan:
   300  			if expectedErr == nil {
   301  				assert.NoError(t, err, "unexpected unhandled irrecoverable error")
   302  			} else {
   303  				assert.ErrorIs(t, err, expectedErr)
   304  			}
   305  		}
   306  	}()
   307  
   308  	nb := FlowNode("scaffold test")
   309  	nb.componentBuilder = component.NewComponentManagerBuilder()
   310  
   311  	for _, c := range components {
   312  		if c.errorHandler == nil {
   313  			nb.Component(c.name, c.factory)
   314  		} else {
   315  			nb.RestartableComponent(c.name, c.factory, c.errorHandler)
   316  		}
   317  	}
   318  
   319  	err := nb.handleComponents()
   320  	assert.NoError(t, err)
   321  
   322  	cm := nb.componentBuilder.Build()
   323  
   324  	cm.Start(signalerCtx)
   325  
   326  	<-shutdown
   327  	cancel()
   328  
   329  	<-cm.Done()
   330  }
   331  
   332  func TestRestartableRestartsSuccessfully(t *testing.T) {
   333  
   334  	logger := &testLog{}
   335  	shutdown := make(chan struct{})
   336  
   337  	name := "component 1"
   338  	err := fmt.Errorf("%s error", name)
   339  
   340  	starts := 0
   341  	factory := func(node *NodeConfig) (module.ReadyDoneAware, error) {
   342  		logger.Logf("%s initialized", name)
   343  		c := newMockComponent(logger, name)
   344  		c.startFn = func(signalCtx irrecoverable.SignalerContext, name string) {
   345  			go func() {
   346  				<-c.Ready()
   347  				starts++
   348  				if starts == 1 {
   349  					signalCtx.Throw(err)
   350  				}
   351  				close(shutdown)
   352  			}()
   353  		}
   354  		return c, nil
   355  	}
   356  
   357  	runRestartableTest(t, []testComponentDefinition{
   358  		{
   359  			name:         name,
   360  			factory:      factory,
   361  			errorHandler: testErrorHandler(logger, err),
   362  		},
   363  	}, nil, shutdown)
   364  
   365  	assert.Equal(t, []string{
   366  		"component 1 initialized",
   367  		"component 1 started",
   368  		"component 1 ready",
   369  		"component 1 done",
   370  		"handled error: component 1 error",
   371  		"component 1 initialized",
   372  		"component 1 started",
   373  		"component 1 ready",
   374  		"component 1 done",
   375  	}, logger.logs)
   376  }
   377  
   378  func TestRestartableStopsSuccessfully(t *testing.T) {
   379  	logger := &testLog{}
   380  	shutdown := make(chan struct{})
   381  
   382  	name := "component 1"
   383  	err := fmt.Errorf("%s error", name)
   384  	unexpectedErr := fmt.Errorf("%s unexpected error", name)
   385  
   386  	starts := 0
   387  	factory := func(node *NodeConfig) (module.ReadyDoneAware, error) {
   388  		logger.Logf("%s initialized", name)
   389  		c := newMockComponent(logger, name)
   390  		c.startFn = func(signalCtx irrecoverable.SignalerContext, name string) {
   391  			go func() {
   392  				<-c.Ready()
   393  				starts++
   394  				if starts < 2 {
   395  					signalCtx.Throw(err)
   396  				}
   397  				if starts == 2 {
   398  					defer close(shutdown)
   399  					signalCtx.Throw(unexpectedErr)
   400  				}
   401  			}()
   402  		}
   403  		return c, nil
   404  	}
   405  
   406  	runRestartableTest(t, []testComponentDefinition{
   407  		{
   408  			name:         name,
   409  			factory:      factory,
   410  			errorHandler: testErrorHandler(logger, err),
   411  		},
   412  	}, unexpectedErr, shutdown)
   413  
   414  	assert.Equal(t, []string{
   415  		"component 1 initialized",
   416  		"component 1 started",
   417  		"component 1 ready",
   418  		"component 1 done",
   419  		"handled error: component 1 error",
   420  		"component 1 initialized",
   421  		"component 1 started",
   422  		"component 1 ready",
   423  		"component 1 done",
   424  		"handled unexpected error: component 1 unexpected error",
   425  	}, logger.logs)
   426  }
   427  
   428  func TestRestartableWithMultipleComponents(t *testing.T) {
   429  	logger := &testLog{}
   430  	shutdown := make(chan struct{})
   431  
   432  	// synchronization is needed since RestartableComponents are non-blocking
   433  	readyComponents := sync.WaitGroup{}
   434  	readyComponents.Add(3)
   435  
   436  	c1 := func() testComponentDefinition {
   437  		name := "component 1"
   438  		factory := func(node *NodeConfig) (module.ReadyDoneAware, error) {
   439  			logger.Logf("%s initialized", name)
   440  			c := newMockReadyDone(logger, name)
   441  			c.readyFn = func(name string) {
   442  				// delay to demonstrate that components are started serially
   443  				time.Sleep(5 * time.Millisecond)
   444  				readyComponents.Done()
   445  			}
   446  			return c, nil
   447  		}
   448  
   449  		return testComponentDefinition{
   450  			name:    name,
   451  			factory: factory,
   452  		}
   453  	}
   454  
   455  	c2Initialized := make(chan struct{})
   456  	c2 := func() testComponentDefinition {
   457  		name := "component 2"
   458  		err := fmt.Errorf("%s error", name)
   459  		factory := func(node *NodeConfig) (module.ReadyDoneAware, error) {
   460  			defer close(c2Initialized)
   461  			logger.Logf("%s initialized", name)
   462  			c := newMockComponent(logger, name)
   463  			c.startFn = func(ctx irrecoverable.SignalerContext, name string) {
   464  				// delay to demonstrate the RestartableComponent startup is non-blocking
   465  				time.Sleep(5 * time.Millisecond)
   466  			}
   467  			c.readyFn = func(name string) {
   468  				readyComponents.Done()
   469  			}
   470  			return c, nil
   471  		}
   472  
   473  		return testComponentDefinition{
   474  			name:         name,
   475  			factory:      factory,
   476  			errorHandler: testErrorHandler(logger, err),
   477  		}
   478  	}
   479  
   480  	c3 := func() testComponentDefinition {
   481  		name := "component 3"
   482  		err := fmt.Errorf("%s error", name)
   483  		starts := 0
   484  		factory := func(node *NodeConfig) (module.ReadyDoneAware, error) {
   485  			logger.Logf("%s initialized", name)
   486  			c := newMockComponent(logger, name)
   487  			c.startFn = func(signalCtx irrecoverable.SignalerContext, name string) {
   488  				go func() {
   489  					<-c.Ready()
   490  					starts++
   491  					if starts == 1 {
   492  						signalCtx.Throw(err)
   493  					}
   494  					<-c2Initialized // can't use ready since it may not be initialized yet
   495  					readyComponents.Done()
   496  				}()
   497  			}
   498  			return c, nil
   499  		}
   500  
   501  		return testComponentDefinition{
   502  			name:         name,
   503  			factory:      factory,
   504  			errorHandler: testErrorHandler(logger, err),
   505  		}
   506  	}
   507  
   508  	go func() {
   509  		readyComponents.Wait()
   510  		close(shutdown)
   511  	}()
   512  
   513  	runRestartableTest(t, []testComponentDefinition{c1(), c2(), c3()}, nil, shutdown)
   514  
   515  	logs := logger.logs
   516  
   517  	// make sure component 1 is started and ready before any other components start
   518  	assert.Equal(t, []string{
   519  		"component 1 initialized",
   520  		"component 1 ready",
   521  	}, logs[:2])
   522  
   523  	// now split logs by component, and verify we got the right messages/order
   524  	component1 := []string{}
   525  	component2 := []string{}
   526  	component3 := []string{}
   527  	unexpected := []string{}
   528  	for _, l := range logs {
   529  		switch {
   530  		case strings.Contains(l, "component 1"):
   531  			component1 = append(component1, l)
   532  		case strings.Contains(l, "component 2"):
   533  			component2 = append(component2, l)
   534  		case strings.Contains(l, "component 3"):
   535  			component3 = append(component3, l)
   536  		default:
   537  			unexpected = append(unexpected, l)
   538  		}
   539  	}
   540  
   541  	// no unexpected logs
   542  	assert.Len(t, unexpected, 0)
   543  
   544  	assert.Equal(t, []string{
   545  		"component 1 initialized",
   546  		"component 1 ready",
   547  		"component 1 done",
   548  	}, component1)
   549  
   550  	assert.Equal(t, []string{
   551  		"component 2 initialized",
   552  		"component 2 started",
   553  		"component 2 ready",
   554  		"component 2 done",
   555  	}, component2)
   556  
   557  	assert.Equal(t, []string{
   558  		"component 3 initialized",
   559  		"component 3 started",
   560  		"component 3 ready",
   561  		"component 3 done",
   562  		"handled error: component 3 error",
   563  		"component 3 initialized",
   564  		"component 3 started",
   565  		"component 3 ready",
   566  		"component 3 done",
   567  	}, component3)
   568  
   569  	// components are stopped via context cancellation, so the specific order is random
   570  	doneLogs := logs[len(logs)-3:]
   571  	assert.ElementsMatch(t, []string{
   572  		"component 1 done",
   573  		"component 2 done",
   574  		"component 3 done",
   575  	}, doneLogs)
   576  }
   577  
   578  func testErrorHandler(logger *testLog, expected error) component.OnError {
   579  	return func(err error) component.ErrorHandlingResult {
   580  		if errors.Is(err, expected) {
   581  			logger.Logf("handled error: %s", err)
   582  			return component.ErrorHandlingRestart
   583  		}
   584  		logger.Logf("handled unexpected error: %s", err)
   585  		return component.ErrorHandlingStop
   586  	}
   587  }
   588  
   589  // TestDependableComponentWaitForDependencies tests that dependable components are started after
   590  // their dependencies are ready
   591  // In this test:
   592  // * Components 1 & 2 are DependableComponents
   593  // * Component 3 is a normal Component
   594  // * 1 depends on 3
   595  // * 2 depends on 1
   596  // * Start order should be 3, 1, 2
   597  // run test 10 times to ensure order is consistent
   598  func TestDependableComponentWaitForDependencies(t *testing.T) {
   599  	for i := 0; i < 10; i++ {
   600  		testDependableComponentWaitForDependencies(t)
   601  	}
   602  }
   603  
   604  func testDependableComponentWaitForDependencies(t *testing.T) {
   605  	ctx, cancel := context.WithCancel(context.Background())
   606  	signalerCtx, _ := irrecoverable.WithSignaler(ctx)
   607  
   608  	nb := FlowNode("scaffold test")
   609  	nb.componentBuilder = component.NewComponentManagerBuilder()
   610  
   611  	logger := &testLog{}
   612  
   613  	component1Dependable := module.NewProxiedReadyDoneAware()
   614  	component3Dependable := module.NewProxiedReadyDoneAware()
   615  
   616  	name1 := "component 1"
   617  	nb.DependableComponent(name1, func(node *NodeConfig) (module.ReadyDoneAware, error) {
   618  		logger.Logf("%s initialized", name1)
   619  		c := newMockComponent(logger, name1)
   620  		component1Dependable.Init(c)
   621  		return c, nil
   622  	}, &DependencyList{[]module.ReadyDoneAware{component3Dependable}})
   623  
   624  	name2 := "component 2"
   625  	nb.DependableComponent(name2, func(node *NodeConfig) (module.ReadyDoneAware, error) {
   626  		logger.Logf("%s initialized", name2)
   627  		return newMockComponent(logger, name2), nil
   628  	}, &DependencyList{[]module.ReadyDoneAware{component1Dependable}})
   629  
   630  	name3 := "component 3"
   631  	nb.Component(name3, func(node *NodeConfig) (module.ReadyDoneAware, error) {
   632  		logger.Logf("%s initialized", name3)
   633  		c := newMockComponent(logger, name3)
   634  		c.startFn = func(ctx irrecoverable.SignalerContext, name string) {
   635  			// add delay to test components are run serially
   636  			time.Sleep(5 * time.Millisecond)
   637  		}
   638  		component3Dependable.Init(c)
   639  		return c, nil
   640  	})
   641  
   642  	err := nb.handleComponents()
   643  	require.NoError(t, err)
   644  
   645  	cm := nb.componentBuilder.Build()
   646  
   647  	cm.Start(signalerCtx)
   648  	<-cm.Ready()
   649  
   650  	cancel()
   651  	<-cm.Done()
   652  
   653  	logs := logger.logs
   654  
   655  	assert.Len(t, logs, 12)
   656  
   657  	// components are initialized in a specific order, so check that the order is correct
   658  	startLogs := logs[:len(logs)-3]
   659  	assert.Equal(t, []string{
   660  		"component 3 initialized",
   661  		"component 3 started",
   662  		"component 3 ready",
   663  		"component 1 initialized",
   664  		"component 1 started",
   665  		"component 1 ready",
   666  		"component 2 initialized",
   667  		"component 2 started",
   668  		"component 2 ready",
   669  	}, startLogs)
   670  
   671  	// components are stopped via context cancellation, so the specific order is random
   672  	doneLogs := logs[len(logs)-3:]
   673  	assert.ElementsMatch(t, []string{
   674  		"component 1 done",
   675  		"component 2 done",
   676  		"component 3 done",
   677  	}, doneLogs)
   678  }
   679  
   680  func TestCreateUploader(t *testing.T) {
   681  	t.Parallel()
   682  	t.Run("create uploader", func(t *testing.T) {
   683  		t.Parallel()
   684  		nb := FlowNode("scaffold_uploader")
   685  		mockHttp := &http.Client{
   686  			Transport: &mockRoundTripper{
   687  				DoFunc: func(req *http.Request) (*http.Response, error) {
   688  					switch req.URL.Path {
   689  					case "/computeMetadata/v1/project/project-id":
   690  						return &http.Response{
   691  							StatusCode: 200,
   692  							Body:       io.NopCloser(bytes.NewBufferString("test-project-id")),
   693  						}, nil
   694  					case "/computeMetadata/v1/instance/id":
   695  						return &http.Response{
   696  							StatusCode: 200,
   697  							Body:       io.NopCloser(bytes.NewBufferString("test-instance-id")),
   698  						}, nil
   699  					default:
   700  						return nil, fmt.Errorf("unexpected request: %s", req.URL.Path)
   701  					}
   702  				},
   703  			},
   704  		}
   705  
   706  		testClient := gcemd.NewClient(mockHttp)
   707  		uploader, err := nb.createGCEProfileUploader(
   708  			testClient,
   709  
   710  			option.WithoutAuthentication(),
   711  			option.WithGRPCDialOption(grpc.WithTransportCredentials(insecure.NewCredentials())),
   712  		)
   713  		require.NoError(t, err)
   714  		require.NotNil(t, uploader)
   715  
   716  		uploaderImpl, ok := uploader.(*profiler.UploaderImpl)
   717  		require.True(t, ok)
   718  
   719  		assert.Equal(t, "test-project-id", uploaderImpl.Deployment.ProjectId)
   720  		assert.Equal(t, "unknown-scaffold_uploader", uploaderImpl.Deployment.Target)
   721  		assert.Equal(t, "test-instance-id", uploaderImpl.Deployment.Labels["instance"])
   722  		assert.Equal(t, "undefined-undefined", uploaderImpl.Deployment.Labels["version"])
   723  	})
   724  }
   725  
   726  type mockRoundTripper struct {
   727  	DoFunc func(req *http.Request) (*http.Response, error)
   728  }
   729  
   730  func (m *mockRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
   731  	return m.DoFunc(req)
   732  }
   733  
   734  // TestDhtSystemActivationStatus tests that the DHT system activation status is correctly
   735  // determined based on the role string.
   736  // This test is not exhaustive, but should cover the most common cases.
   737  func TestDhtSystemActivationStatus(t *testing.T) {
   738  	tests := []struct {
   739  		name      string
   740  		roleStr   string
   741  		enabled   bool
   742  		expected  p2pbuilder.DhtSystemActivation
   743  		expectErr bool
   744  	}{
   745  		{
   746  			name:      "ghost role returns disabled",
   747  			roleStr:   "ghost",
   748  			enabled:   true,
   749  			expected:  p2pbuilder.DhtSystemDisabled,
   750  			expectErr: false,
   751  		},
   752  		{
   753  			name:      "access role returns enabled",
   754  			roleStr:   "access",
   755  			enabled:   true,
   756  			expected:  p2pbuilder.DhtSystemEnabled,
   757  			expectErr: false,
   758  		},
   759  		{
   760  			name:      "execution role returns enabled",
   761  			roleStr:   "execution",
   762  			enabled:   true,
   763  			expected:  p2pbuilder.DhtSystemEnabled,
   764  			expectErr: false,
   765  		},
   766  		{
   767  			name:      "access role with disabled returns disabled",
   768  			roleStr:   "access",
   769  			enabled:   false,
   770  			expected:  p2pbuilder.DhtSystemDisabled,
   771  			expectErr: false,
   772  		},
   773  		{
   774  			name:      "execution role with disabled returns disabled",
   775  			roleStr:   "execution",
   776  			enabled:   false,
   777  			expected:  p2pbuilder.DhtSystemDisabled,
   778  			expectErr: false,
   779  		},
   780  		{
   781  			name:      "collection role returns disabled",
   782  			roleStr:   "collection",
   783  			enabled:   true,
   784  			expected:  p2pbuilder.DhtSystemDisabled,
   785  			expectErr: false,
   786  		},
   787  		{
   788  			name:      "consensus role returns disabled",
   789  			roleStr:   "consensus",
   790  			enabled:   true,
   791  			expected:  p2pbuilder.DhtSystemDisabled,
   792  			expectErr: false,
   793  		},
   794  		{
   795  			name:      "verification nodes return disabled",
   796  			roleStr:   "verification",
   797  			enabled:   true,
   798  			expected:  p2pbuilder.DhtSystemDisabled,
   799  			expectErr: false,
   800  		},
   801  		{
   802  			name:      "invalid role returns error",
   803  			roleStr:   "invalidRole",
   804  			enabled:   true,
   805  			expected:  p2pbuilder.DhtSystemDisabled,
   806  			expectErr: true,
   807  		}, // Add more test cases for other roles, if needed.
   808  	}
   809  
   810  	for _, tt := range tests {
   811  		t.Run(tt.name, func(t *testing.T) {
   812  			result, err := DhtSystemActivationStatus(tt.roleStr, tt.enabled)
   813  			require.Equal(t, tt.expectErr, err != nil, "unexpected error status")
   814  			require.Equal(t, tt.expected, result, "unexpected activation status")
   815  		})
   816  	}
   817  }