diff --git a/go.mod b/go.mod index d59388e3f..4cef1c1a8 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 65f0c2b12..240917aa1 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 4e2f194fc..418b2d126 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 diff --git a/internal/super/flix_test.go b/internal/super/flix_test.go index c1b2c117b..be6d33692 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) }