Repository: onflow/flow-cli
Pull request #1339 (2024-01-11 04:09:59)

Update flix tests and flixkit-go version used
Closes https://github.com/onflow/flow-cli/issues/1335

## Description

Simplify flix logic in flow-cli
Reorganize flix.go to be mockable and add unit tests
Use new version of flixkit-go v1.1.0

______

For contributor use:

- [x] Targeted PR against `master` branch
- [ ] Linked to Github issue with discussion and accepted design OR link to spec that describes this work
- [ ] Code follows the [standards mentioned here](https://github.com/onflow/flow-cli/blob/master/CONTRIBUTING.md#styleguides)
- [ ] Updated relevant documentation
- [x] Re-reviewed `Files changed` in the Github PR explorer
- [ ] Added appropriate labels



Patch:
diff --git a/go.mod b/go.mod
index d59388e3..4cef1c1a 100644
--- a/go.mod
+++ b/go.mod
@@ -12,7 +12,7 @@ require (
 	github.com/onflow/cadence-tools/languageserver v0.33.3
 	github.com/onflow/cadence-tools/test v0.14.5
 	github.com/onflow/fcl-dev-wallet v0.7.4
-	github.com/onflow/flixkit-go v1.0.2
+	github.com/onflow/flixkit-go v1.1.0
 	github.com/onflow/flow-cli/flowkit v1.6.1-0.20231110211255-b41f57a8b8c7
 	github.com/onflow/flow-core-contracts/lib/go/templates v1.2.4-0.20231016154253-a00dbf7c061f
 	github.com/onflow/flow-emulator v0.59.0
diff --git a/go.sum b/go.sum
index 65f0c2b1..240917aa 100644
--- a/go.sum
+++ b/go.sum
@@ -875,8 +875,8 @@ github.com/onflow/cadence-tools/test v0.14.5 h1:u1kYkotKdwKEf9c3h65mI3VMevBkHY+7
 github.com/onflow/cadence-tools/test v0.14.5/go.mod h1:ix09Bb3GQ/fZMNpSR8E+vSFItGF54fzP9gFxU7AsOIw=
 github.com/onflow/fcl-dev-wallet v0.7.4 h1:vI6t3U0AO88R/Iitn5KsnniSpbN9Lqsqwvi9EJT4C0k=
 github.com/onflow/fcl-dev-wallet v0.7.4/go.mod h1:kc42jkiuoPJmxMRFjfbRO9XvnR/3XLheaOerxVMDTiw=
-github.com/onflow/flixkit-go v1.0.2 h1:6PVLLb8qQj9L6cs57HUO3wszzbgPD46pc9MAGWHnb1s=
-github.com/onflow/flixkit-go v1.0.2/go.mod h1:KGL7oMu4uzt7s0qsNkaqGBYIt+Z38Qbf0JG56qK/Sg0=
+github.com/onflow/flixkit-go v1.1.0 h1:yju2lotk2LQBXUwb0rZeOtVbHW2KVSzzRRu/3rPtzok=
+github.com/onflow/flixkit-go v1.1.0/go.mod h1:KGL7oMu4uzt7s0qsNkaqGBYIt+Z38Qbf0JG56qK/Sg0=
 github.com/onflow/flow-core-contracts/lib/go/contracts v1.2.4-0.20231016154253-a00dbf7c061f h1:S8yIZw9LFXfYD1V5H9BiixihHw3GrXVPrmfplSzYaww=
 github.com/onflow/flow-core-contracts/lib/go/contracts v1.2.4-0.20231016154253-a00dbf7c061f/go.mod h1:jM6GMAL+m0hjusUgiYDNrixPQ6b9s8xjoJQoEu5bHQI=
 github.com/onflow/flow-core-contracts/lib/go/templates v1.2.4-0.20231016154253-a00dbf7c061f h1:Ep+Mpo2miWMe4pjPGIaEvEzshRep30dvNgxqk+//FrQ=
diff --git a/internal/super/flix.go b/internal/super/flix.go
index 4e2f194f..418b2d12 100644
--- a/internal/super/flix.go
+++ b/internal/super/flix.go
@@ -21,9 +21,7 @@ package super
 import (
 	"context"
 	"fmt"
-	"net/url"
 	"os"
-	"path/filepath"
 
 	"github.com/onflow/flixkit-go/flixkit"
 
@@ -107,17 +105,28 @@ func init() {
 
 func executeCmd(
 	args []string,
-	_ command.GlobalFlags,
+	flags command.GlobalFlags,
 	logger output.Logger,
 	flow flowkit.Services,
 	state *flowkit.State,
 ) (result command.Result, err error) {
-	flixService := flixkit.NewFlixService(&flixkit.Config{
+	flixService := flixkit.NewFlixService(&flixkit.FlixServiceConfig{
 		FileReader: state,
 	})
+	return executeFlixCmd(args, flags, logger, flow, state, flixService)
+}
+
+func executeFlixCmd(
+	args []string,
+	_ command.GlobalFlags,
+	logger output.Logger,
+	flow flowkit.Services,
+	state *flowkit.State,
+	flixService flixkit.FlixService,
+) (result command.Result, err error) {
 	flixQuery := args[0]
 	ctx := context.Background()
-	cadenceWithImportsReplaced, err := flixService.GetAndReplaceCadenceImports(ctx, flixQuery, flow.Network().Name)
+	cadenceWithImportsReplaced, err := flixService.GetTemplateAndReplaceImports(ctx, flixQuery, flow.Network().Name)
 	if err != nil {
 		logger.Error("could not replace imports")
 		return nil, err
@@ -152,37 +161,25 @@ func packageCmd(
 	flow flowkit.Services,
 	state *flowkit.State,
 ) (result command.Result, err error) {
-	flixService := flixkit.NewFlixService(&flixkit.Config{
+	flixService := flixkit.NewFlixService(&flixkit.FlixServiceConfig{
 		FileReader: state,
 	})
+
+	return packageFlixCmd(args, gFlags, logger, flow, state, flixService, flags)
+}
+
+func packageFlixCmd(
+	args []string,
+	gFlags command.GlobalFlags,
+	logger output.Logger,
+	flow flowkit.Services,
+	state *flowkit.State,
+	flixService flixkit.FlixService,
+	flags flixFlags,
+) (result command.Result, err error) {
 	flixQuery := args[0]
 	ctx := context.Background()
-	template, err := flixService.GetTemplate(ctx, flixQuery)
-	if err != nil {
-		return nil, err
-	}
-	if !isUrl(flixQuery) {
-		if gFlags.Save != "" {
-			// resolve template file location to relative path to be used by binding file
-			flixQuery, err = GetRelativePath(flixQuery, gFlags.Save)
-			if err != nil {
-				logger.Error("could not resolve relative path to template")
-				return nil, err
-			}
-		}
-	}
-
-	var out string
-	var gen flixkit.FclGenerator
-	switch flags.Lang {
-	case "js":
-		gen = *flixkit.NewFclJSGenerator()
-	case "ts":
-		gen = *flixkit.NewFclTSGenerator()
-	default:
-		return nil, fmt.Errorf("language %s not supported", flags.Lang)
-	}
-	out, err = gen.Generate(template, flixQuery)
+	out, err := flixService.GetTemplateAndCreateBinding(ctx, flixQuery, flags.Lang, gFlags.Save)
 	if err != nil {
 		return nil, err
 	}
@@ -194,14 +191,31 @@ func packageCmd(
 }
 
 func generateCmd(
+	args []string,
+	gFlags command.GlobalFlags,
+	logger output.Logger,
+	flow flowkit.Services,
+	state *flowkit.State,
+) (result command.Result, err error) {
+	flixService := flixkit.NewFlixService(&flixkit.FlixServiceConfig{
+		FileReader: state,
+		Logger:     logger,
+	})
+
+	return generateFlixCmd(args, gFlags, logger, flow, state, flixService, flags)
+}
+
+func generateFlixCmd(
 	args []string,
 	_ command.GlobalFlags,
 	logger output.Logger,
 	flow flowkit.Services,
 	state *flowkit.State,
+	flixService flixkit.FlixService,
+	flags flixFlags,
 ) (result command.Result, err error) {
 	cadenceFile := args[0]
-
+	depContracts := getDeployedContracts(state)
 	if cadenceFile == "" {
 		return nil, fmt.Errorf("no cadence code found")
 	}
@@ -211,25 +225,12 @@ func generateCmd(
 		return nil, fmt.Errorf("could not read cadence file %s: %w", cadenceFile, err)
 	}
 
-	depContracts := GetDeployedContracts(state)
-	generator, err := flixkit.NewGenerator(depContracts, logger)
 	if err != nil {
 		return nil, fmt.Errorf("could not create flix generator %w", err)
 	}
 
 	ctx := context.Background()
-	var template string
-	if flags.PreFill != "" {
-		flixService := flixkit.NewFlixService(&flixkit.Config{
-			FileReader: state,
-		})
-		template, err = flixService.GetTemplate(ctx, flags.PreFill)
-		if err != nil {
-			return nil, fmt.Errorf("could not parse template from pre fill %w", err)
-		}
-	}
-
-	prettyJSON, err := generator.Generate(ctx, string(code), template)
+	prettyJSON, err := flixService.CreateTemplate(ctx, depContracts, string(code), flags.PreFill)
 	if err != nil {
 		return nil, fmt.Errorf("could not generate flix %w", err)
 	}
@@ -256,24 +257,7 @@ func (fr *flixResult) Oneliner() string {
 	return fr.result
 }
 
-// GetRelativePath computes the relative path from generated file to flix json file.
-// This path is used in the binding file to reference the flix json file.
-func GetRelativePath(configFile, bindingFile string) (string, error) {
-	relPath, err := filepath.Rel(filepath.Dir(bindingFile), configFile)
-	if err != nil {
-		return "", err
-	}
-
-	// If the file is in the same directory and doesn't start with "./", prepend it.
-	if !filepath.IsAbs(relPath) && relPath[0] != '.' {
-		relPath = "./" + relPath
-	}
-
-	// Currently binding files are js, we need to convert the path to unix style
-	return filepath.ToSlash(relPath), nil
-}
-
-func GetDeployedContracts(state *flowkit.State) flixkit.ContractInfos {
+func getDeployedContracts(state *flowkit.State) flixkit.ContractInfos {
 	allContracts := make(flixkit.ContractInfos)
 	depNetworks := make([]string, 0)
 	// get all configured networks in flow.json
@@ -284,7 +268,6 @@ func GetDeployedContracts(state *flowkit.State) flixkit.ContractInfos {
 	// get all deployed and alias contracts for configured networks
 	for _, network := range depNetworks {
 		contracts, err := state.DeploymentContractsByNetwork(config.Network{Name: network})
-		fmt.Println("contracts len", network, len(contracts))
 		if err != nil {
 			continue
 		}
@@ -309,11 +292,6 @@ func GetDeployedContracts(state *flowkit.State) flixkit.ContractInfos {
 	return allContracts
 }
 
-func isUrl(str string) bool {
-	u, err := url.Parse(str)
-	return err == nil && u.Scheme != "" && u.Host != ""
-}
-
 func isPath(path string) bool {
 	_, err := os.Stat(path)
 	return err == nil


Test patch:
diff --git a/internal/super/flix_test.go b/internal/super/flix_test.go
index c1b2c117..be6d3369 100644
--- a/internal/super/flix_test.go
+++ b/internal/super/flix_test.go
@@ -19,17 +19,138 @@
 package super
 
 import (
+	"context"
 	"testing"
 
 	"github.com/spf13/afero"
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/mock"
+
+	"github.com/onflow/flixkit-go/flixkit"
+	"github.com/onflow/flow-go-sdk/crypto"
 
 	"github.com/onflow/flow-cli/flowkit"
 	"github.com/onflow/flow-cli/flowkit/config"
+	"github.com/onflow/flow-cli/flowkit/mocks"
+	"github.com/onflow/flow-cli/flowkit/output"
 	"github.com/onflow/flow-cli/flowkit/tests"
+	"github.com/onflow/flow-cli/internal/command"
+	"github.com/onflow/flow-cli/internal/util"
 )
 
-func Test_flix_generate(t *testing.T) {
+type MockFlixService struct {
+	mock.Mock
+}
+
+var TEMPLATE_STR = "{ \"f_type\": \"IniteractionTemplate\", \"f_version\": \"1.1.0\", \"id\": \"0ea\",}"
+
+func (m *MockFlixService) GetTemplate(ctx context.Context, templateName string) (string, string, error) {
+	args := m.Called(ctx, templateName)
+	return TEMPLATE_STR, args.String(0), args.Error(1)
+}
+
+var CADENCE_SCRIPT = "pub fun main() {\n    log(\"Hello, World!\")\n}"
+
+func (m *MockFlixService) GetTemplateAndReplaceImports(ctx context.Context, templateName string, network string) (*flixkit.FlowInteractionTemplateExecution, error) {
+	result := &flixkit.FlowInteractionTemplateExecution{
+		Network:       "emulator",
+		IsTransaciton: false,
+		IsScript:      true,
+		Cadence:       CADENCE_SCRIPT,
+	}
+	return result, nil
+}
+
+func (m *MockFlixService) CreateTemplate(ctx context.Context, contractInfos flixkit.ContractInfos, code string, preFill string) (string, error) {
+	args := m.Called(ctx, contractInfos, code, preFill)
+	return TEMPLATE_STR, args.Error(1)
+}
+
+var JS_CODE = "export async function request() { const info = await fcl.query({ template: flixTemplate }); return info; }"
+
+func (m *MockFlixService) GetTemplateAndCreateBinding(ctx context.Context, templateName string, lang string, destFile string) (string, error) {
+	args := m.Called(ctx, templateName, lang, destFile)
+	return JS_CODE, args.Error(1)
+}
+
+func Test_ExecuteFlixScript(t *testing.T) {
+	ctx := context.Background()
+	logger := output.NewStdoutLogger(output.NoneLog)
+	srv, state, _ := util.TestMocks(t)
+	mockFlixService := new(MockFlixService)
+	testCadenceScript := "pub fun main() {\n    log(\"Hello, World!\")\n}"
+	mockFlixService.On("GetTemplateAndReplaceImports", ctx, "templateName", "emulator").Return(&flixkit.FlowInteractionTemplateExecution{
+		Network:       "emulator",
+		IsTransaciton: false,
+		IsScript:      true,
+		Cadence:       testCadenceScript,
+	}, nil)
+
+	// Set up a mock return value for the Network method
+	mockNetwork := config.Network{
+		Name: "emulator",
+		Host: "localhost:3569",
+	}
+	srv.Network.Run(func(args mock.Arguments) {}).Return(mockNetwork, nil)
+	srv.ExecuteScript.Run(func(args mock.Arguments) {
+		script := args.Get(1).(flowkit.Script)
+		assert.Equal(t, testCadenceScript, string(script.Code))
+	}).Return(nil, nil)
+
+	result, err := executeFlixCmd([]string{"transfer-token"}, command.GlobalFlags{}, logger, srv.Mock, state, mockFlixService)
+	assert.NoError(t, err)
+	assert.NotNil(t, result)
+}
+
+func Test_ExecuteFlixTransaction(t *testing.T) {
+	ctx := context.Background()
+	logger := output.NewStdoutLogger(output.NoneLog)
+	srv, state, _ := util.TestMocks(t)
+	mockFlixService := new(MockFlixService)
+	testCadenceTx := "transaction { prepare(signer: AuthAccount) { /* prepare logic */ } execute { log(\"Hello, Cadence!\") } }"
+	mockFlixService.On("GetTemplateAndReplaceImports", ctx, "templateName", "emulator").Return(&flixkit.FlowInteractionTemplateExecution{
+		Network:       "emulator",
+		IsTransaciton: false,
+		IsScript:      true,
+		Cadence:       testCadenceTx,
+	}, nil)
+
+	// Set up a mock return value for the Network method
+	mockNetwork := config.Network{
+		Name: "emulator",
+		Host: "localhost:3569",
+	}
+	srv.Network.Run(func(args mock.Arguments) {}).Return(mockNetwork, nil)
+	srv.SendTransaction.Run(func(args mock.Arguments) {
+		script := args.Get(2).(flowkit.Script)
+		assert.Equal(t, testCadenceTx, string(script.Code))
+	}).Return(nil, nil)
+
+	result, err := executeFlixCmd([]string{"transfer-token"}, command.GlobalFlags{}, logger, srv.Mock, state, mockFlixService)
+	assert.NoError(t, err)
+	assert.NotNil(t, result)
+}
+
+func Test_PackageFlix(t *testing.T) {
+	ctx := context.Background()
+	logger := output.NewStdoutLogger(output.NoneLog)
+	srv, state, _ := util.TestMocks(t)
+	mockFlixService := new(MockFlixService)
+	templateName := "templateName"
+	mockFlixService.On("GetTemplateAndCreateBinding", ctx, templateName, "js", "").Return(JS_CODE, nil)
+
+	result, err := packageFlixCmd([]string{templateName}, command.GlobalFlags{}, logger, srv.Mock, state, mockFlixService, flixFlags{Lang: "js"})
+	assert.NoError(t, err)
+	assert.NotNil(t, result)
+	assert.Equal(t, JS_CODE, result.String())
+}
+func Test_GenerateFlix(t *testing.T) {
+	srv := mocks.DefaultMockServices()
+	cadenceFile := "cadence.cdc"
+	cadenceCode := "pub fun main() {\n    log(\"Hello, World!\")\n}"
+
+	mockFlixService := new(MockFlixService)
+
 	configJson := []byte(`{
 		"contracts": {},
 		"accounts": {
@@ -48,6 +169,8 @@ func Test_flix_generate(t *testing.T) {
 	af := afero.Afero{Fs: afero.NewMemMapFs()}
 	err := afero.WriteFile(af.Fs, "flow.json", configJson, 0644)
 	assert.NoError(t, err)
+	err = afero.WriteFile(af.Fs, cadenceFile, []byte(cadenceCode), 0644)
+	assert.NoError(t, err)
 	err = afero.WriteFile(af.Fs, tests.ContractHelloString.Filename, []byte(tests.ContractHelloString.Source), 0644)
 	assert.NoError(t, err)
 	paths := []string{"flow.json"}
@@ -73,13 +196,37 @@ func Test_flix_generate(t *testing.T) {
 	assert.NoError(t, err)
 	assert.Equal(t, 1, len(contracts))
 
-	cs := GetDeployedContracts(state)
-	assert.Equal(t, 1, len(cs))
-	networkContract := cs[tests.ContractHelloString.Name]
-	assert.NotNil(t, networkContract)
-	addr := networkContract["emulator"]
-	acct, err := state.Accounts().ByName("emulator-account")
+	logger := output.NewStdoutLogger(output.NoneLog)
+	contractInfos := make(flixkit.ContractInfos)
+	contractInfos[tests.ContractHelloString.Name] = make(flixkit.NetworkAddressMap)
+	contractInfos[tests.ContractHelloString.Name]["emulator"] = "f8d6e0586b0a20c7"
+
+	ctx := context.Background()
+	mockFlixService.On("CreateTemplate", ctx, contractInfos, cadenceCode, "").Return(TEMPLATE_STR, nil)
+
+	result, err := generateFlixCmd([]string{cadenceFile}, command.GlobalFlags{}, logger, srv.Mock, state, mockFlixService, flixFlags{})
+	assert.NoError(t, err)
+	assert.NotNil(t, result)
+	assert.Equal(t, TEMPLATE_STR, result.String())
+}
+
+func Test_GenerateFlixPrefill(t *testing.T) {
+	logger := output.NewStdoutLogger(output.NoneLog)
+	srv := mocks.DefaultMockServices()
+	templateName := "templateName"
+	cadenceFile := "cadence.cdc"
+
+	var mockFS = afero.NewMemMapFs()
+	var rw = afero.Afero{Fs: mockFS}
+	err := rw.WriteFile(cadenceFile, []byte(CADENCE_SCRIPT), 0644)
 	assert.NoError(t, err)
-	assert.Equal(t, acct.Address.String(), addr)
+	state, _ := flowkit.Init(rw, crypto.ECDSA_P256, crypto.SHA3_256)
 
+	mockFlixService := new(MockFlixService)
+	ctx := context.Background()
+	mockFlixService.On("CreateTemplate", ctx, flixkit.ContractInfos{}, CADENCE_SCRIPT, templateName).Return(TEMPLATE_STR, nil)
+
+	result, err := generateFlixCmd([]string{cadenceFile}, command.GlobalFlags{}, logger, srv.Mock, state, mockFlixService, flixFlags{PreFill: templateName})
+	assert.NoError(t, err)
+	assert.NotNil(t, result)
 }