diff --git a/.gitignore b/.gitignore index dad165db..f2d634cc 100644 Binary files a/.gitignore and b/.gitignore differ diff --git a/tools/configtxlator/main.go b/tools/configtxlator/main.go index c1c0bfa2..a831ac04 100644 --- a/tools/configtxlator/main.go +++ b/tools/configtxlator/main.go @@ -14,6 +14,7 @@ import ( "os" "reflect" "runtime" + "time" "github.com/alecthomas/kingpin/v2" "github.com/cockroachdb/errors" @@ -41,30 +42,51 @@ var ( version = metadata.Version ) -// command line flags +// command line flags. var ( app = kingpin.New("configtxlator", "Utility for generating Hyperledger Fabric channel configurations") start = app.Command("start", "Start the configtxlator REST server") - hostname = start.Flag("hostname", "The hostname or IP on which the REST server will listen").Default("0.0.0.0").String() - port = start.Flag("port", "The port on which the REST server will listen").Default("7059").Int() - cors = start.Flag("CORS", "Allowable CORS domains, e.g. '*' or 'www.example.com' (may be repeated).").Strings() - - protoEncode = app.Command("proto_encode", "Converts a JSON document to protobuf.") - protoEncodeType = protoEncode.Flag("type", "The type of protobuf structure to encode to. For example, 'common.Config'.").Required().String() + hostname = start.Flag( + "hostname", + "The hostname or IP on which the REST server will listen", + ).Default("0.0.0.0").String() + port = start.Flag("port", "The port on which the REST server will listen").Default("7059").Int() + cors = start.Flag("CORS", "Allowable CORS domains, e.g. '*' or 'www.example.com' (may be repeated).").Strings() + + protoEncode = app.Command("proto_encode", "Converts a JSON document to protobuf.") + protoEncodeType = protoEncode.Flag( + "type", + "The type of protobuf structure to encode to. For example, 'common.Config'.", + ).Required().String() protoEncodeSource = protoEncode.Flag("input", "A file containing the JSON document.").Default(os.Stdin.Name()).File() - protoEncodeDest = protoEncode.Flag("output", "A file to write the output to.").Default(os.Stdout.Name()).OpenFile(os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o600) - - protoDecode = app.Command("proto_decode", "Converts a proto message to JSON.") - protoDecodeType = protoDecode.Flag("type", "The type of protobuf structure to decode from. For example, 'common.Config'.").Required().String() + protoEncodeDest = protoEncode.Flag( + "output", + "A file to write the output to.", + ).Default(os.Stdout.Name()).OpenFile(os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o600) + + protoDecode = app.Command("proto_decode", "Converts a proto message to JSON.") + protoDecodeType = protoDecode.Flag( + "type", + "The type of protobuf structure to decode from. For example, 'common.Config'.", + ).Required().String() protoDecodeSource = protoDecode.Flag("input", "A file containing the proto message.").Default(os.Stdin.Name()).File() - protoDecodeDest = protoDecode.Flag("output", "A file to write the JSON document to.").Default(os.Stdout.Name()).OpenFile(os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o600) + protoDecodeDest = protoDecode.Flag( + "output", + "A file to write the JSON document to.", + ).Default(os.Stdout.Name()).OpenFile(os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o600) computeUpdate = app.Command("compute_update", "Takes two marshaled common.Config messages and computes the config update which transitions between the two.") computeUpdateOriginal = computeUpdate.Flag("original", "The original config message.").File() computeUpdateUpdated = computeUpdate.Flag("updated", "The updated config message.").File() - computeUpdateChannelID = computeUpdate.Flag("channel_id", "The name of the channel for this update.").Required().String() - computeUpdateDest = computeUpdate.Flag("output", "A file to write the JSON document to.").Default(os.Stdout.Name()).OpenFile(os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o600) + computeUpdateChannelID = computeUpdate.Flag( + "channel_id", + "The name of the channel for this update.", + ).Required().String() + computeUpdateDest = computeUpdate.Flag( + "output", + "A file to write the JSON document to.", + ).Default(os.Stdout.Name()).OpenFile(os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o600) versionCmd = app.Command("version", "Show version information") ) @@ -79,23 +101,51 @@ func main() { startServer(fmt.Sprintf("%s:%d", *hostname, *port), *cors) // "proto_encode" command case protoEncode.FullCommand(): - defer (*protoEncodeSource).Close() - defer (*protoEncodeDest).Close() + defer func() { + if err := (*protoEncodeSource).Close(); err != nil { + logger.Warnf("error closing protoEncodeSource: %s", err) + } + }() + defer func() { + if err := (*protoEncodeDest).Close(); err != nil { + logger.Warnf("error closing protoEncodeDest: %s", err) + } + }() err := encodeProto(*protoEncodeType, *protoEncodeSource, *protoEncodeDest) if err != nil { app.Fatalf("Error decoding: %s", err) } case protoDecode.FullCommand(): - defer (*protoDecodeSource).Close() - defer (*protoDecodeDest).Close() + defer func() { + if err := (*protoDecodeSource).Close(); err != nil { + logger.Warnf("error closing protoDecodeSource: %s", err) + } + }() + defer func() { + if err := (*protoDecodeDest).Close(); err != nil { + logger.Warnf("error closing protoDecodeDest: %s", err) + } + }() err := decodeProto(*protoDecodeType, *protoDecodeSource, *protoDecodeDest) if err != nil { app.Fatalf("Error decoding: %s", err) } case computeUpdate.FullCommand(): - defer (*computeUpdateOriginal).Close() - defer (*computeUpdateUpdated).Close() - defer (*computeUpdateDest).Close() + defer func() { + if err := (*computeUpdateOriginal).Close(); err != nil { + logger.Warnf("error closing computeUpdateOriginal: %s", err) + } + }() + defer func() { + if err := (*computeUpdateUpdated).Close(); err != nil { + logger.Warnf("error closing computeUpdateUpdated: %s", err) + } + }() + defer func() { + if err := (*computeUpdateDest).Close(); err != nil { + logger.Warnf("error closing computeUpdateDest: %s", err) + } + }() err := computeUpdt(*computeUpdateOriginal, *computeUpdateUpdated, *computeUpdateDest, *computeUpdateChannelID) if err != nil { app.Fatalf("Error computing update: %s", err) @@ -114,19 +164,24 @@ func startServer(address string, cors []string) { app.Fatalf("Could not bind to address '%s': %s", address, err) } + server := &http.Server{ + ReadTimeout: 30 * time.Second, + WriteTimeout: 30 * time.Second, + IdleTimeout: 120 * time.Second, + } + if len(cors) > 0 { origins := handlers.AllowedOrigins(cors) - // Note, configtxlator only exposes POST APIs for the time being, this - // list will need to be expanded if new non-POST APIs are added methods := handlers.AllowedMethods([]string{http.MethodPost}) headers := handlers.AllowedHeaders([]string{"Content-Type"}) logger.Infof("Serving HTTP requests on %s with CORS %v", listener.Addr(), cors) - err = http.Serve(listener, handlers.CORS(origins, methods, headers)(rest.NewRouter())) + server.Handler = handlers.CORS(origins, methods, headers)(rest.NewRouter()) } else { logger.Infof("Serving HTTP requests on %s", listener.Addr()) - err = http.Serve(listener, rest.NewRouter()) + server.Handler = rest.NewRouter() } + err = server.Serve(listener) app.Fatalf("Error starting server:[%s]\n", err) } @@ -223,11 +278,12 @@ func computeUpdt(original, updated, output *os.File, channelID string) error { return errors.Wrapf(err, "error computing config update") } - cu.ChannelId = channelID - if cu == nil { return errors.New("error marshaling computed config update: proto: Marshal called with nil") } + + cu.ChannelId = channelID + outBytes, err := proto.Marshal(cu) if err != nil { return errors.Wrapf(err, "error marshaling computed config update") diff --git a/tools/cryptogen/main.go b/tools/cryptogen/main.go index 5015ebb9..32ee845a 100644 --- a/tools/cryptogen/main.go +++ b/tools/cryptogen/main.go @@ -26,7 +26,7 @@ var ( version = metadata.Version ) -// command line flags +// command line flags. var ( app = kingpin.New("cryptogen", "Utility for generating Hyperledger Fabric key material") diff --git a/tools/fxconfig/internal/cli/v1/cliio/printer_test.go b/tools/fxconfig/internal/cli/v1/cliio/printer_test.go index 297b4865..98dd02ca 100644 --- a/tools/fxconfig/internal/cli/v1/cliio/printer_test.go +++ b/tools/fxconfig/internal/cli/v1/cliio/printer_test.go @@ -14,6 +14,8 @@ import ( "gopkg.in/yaml.v3" ) +const testValue1 = "value1" + func TestNewCLIPrinter(t *testing.T) { t.Parallel() @@ -34,7 +36,7 @@ func TestCLIPrinter_Print_JSON(t *testing.T) { printer := NewCLIPrinter(&out, &errOut, FormatJSON) data := map[string]any{ - "key1": "value1", + "key1": testValue1, "key2": 123, } @@ -43,7 +45,7 @@ func TestCLIPrinter_Print_JSON(t *testing.T) { var result map[string]any err := json.Unmarshal(out.Bytes(), &result) require.NoError(t, err) - require.Equal(t, "value1", result["key1"]) + require.Equal(t, testValue1, result["key1"]) require.InEpsilon(t, float64(123), result["key2"], 0.001) }) @@ -84,7 +86,7 @@ func TestCLIPrinter_Print_YAML(t *testing.T) { printer := NewCLIPrinter(&out, &errOut, FormatYAML) data := map[string]any{ - "key1": "value1", + "key1": testValue1, "key2": 123, } @@ -93,7 +95,7 @@ func TestCLIPrinter_Print_YAML(t *testing.T) { var result map[string]any err := yaml.Unmarshal(out.Bytes(), &result) require.NoError(t, err) - require.Equal(t, "value1", result["key1"]) + require.Equal(t, testValue1, result["key1"]) require.Equal(t, 123, result["key2"]) }) @@ -135,14 +137,14 @@ func TestCLIPrinter_Print_Table(t *testing.T) { } data := testStruct{ - Field1: "value1", + Field1: testValue1, Field2: 42, } printer.Print(data) output := out.String() - require.Contains(t, output, "value1") + require.Contains(t, output, testValue1) require.Contains(t, output, "42") }) } @@ -196,4 +198,4 @@ func TestFormat_Constants(t *testing.T) { require.Equal(t, FormatTable, Format("table")) require.Equal(t, FormatJSON, Format("json")) require.Equal(t, FormatYAML, Format("yaml")) -} +} \ No newline at end of file diff --git a/tools/fxconfig/internal/cli/v1/flags_test.go b/tools/fxconfig/internal/cli/v1/flags_test.go index 734ba6e7..b15d920a 100644 --- a/tools/fxconfig/internal/cli/v1/flags_test.go +++ b/tools/fxconfig/internal/cli/v1/flags_test.go @@ -13,10 +13,12 @@ import ( "github.com/stretchr/testify/require" ) +const testCmdName = "test" + func TestOutputFlag_Bind(t *testing.T) { t.Parallel() - cmd := &cobra.Command{Use: "test"} + cmd := &cobra.Command{Use: testCmdName} var f outputFlag f.bind(cmd) @@ -28,7 +30,7 @@ func TestOutputFlag_Bind(t *testing.T) { func TestPolicyFlag_Bind(t *testing.T) { t.Parallel() - cmd := &cobra.Command{Use: "test"} + cmd := &cobra.Command{Use: testCmdName} var f policyFlag f.bind(cmd) @@ -45,7 +47,7 @@ func TestPolicyFlag_Bind(t *testing.T) { func TestVersionFlag_Bind(t *testing.T) { t.Parallel() - cmd := &cobra.Command{Use: "test"} + cmd := &cobra.Command{Use: testCmdName} var f versionFlag f.bind(cmd) @@ -62,7 +64,7 @@ func TestVersionFlag_Bind(t *testing.T) { func TestNamespaceDeployFlags_Bind(t *testing.T) { t.Parallel() - cmd := &cobra.Command{Use: "test"} + cmd := &cobra.Command{Use: testCmdName} var f namespaceDeployFlags f.bind(cmd) @@ -80,11 +82,11 @@ func TestNamespaceDeployFlags_Bind(t *testing.T) { func TestWaitFlag_Bind(t *testing.T) { t.Parallel() - cmd := &cobra.Command{Use: "test"} + cmd := &cobra.Command{Use: testCmdName} var f waitFlag f.bind(cmd) flag := cmd.Flags().Lookup("wait") require.NotNil(t, flag) require.Equal(t, "false", flag.DefValue) -} +} \ No newline at end of file diff --git a/tools/fxconfig/internal/cli/v1/namespace_create_test.go b/tools/fxconfig/internal/cli/v1/namespace_create_test.go index 5b9d14e8..2fc147e4 100644 --- a/tools/fxconfig/internal/cli/v1/namespace_create_test.go +++ b/tools/fxconfig/internal/cli/v1/namespace_create_test.go @@ -19,13 +19,13 @@ import ( "github.com/hyperledger/fabric-x/tools/fxconfig/internal/cli/v1/cliio" ) +const testNamespace = "my-namespace" + func TestNewCreateCommand(t *testing.T) { t.Parallel() - // Execute cmd := newNsCreateCommand(&CLIContext{App: &testApp{}}) - // Assert require.NotNil(t, cmd, "newNsCreateCommand should return a non-nil command") require.Equal(t, "create [name]", cmd.Use, "command use should be 'create [name]'") require.NotEmpty(t, cmd.Short, "command should have a short description") @@ -57,7 +57,7 @@ func TestNewCreateCommandRun_TxReturned(t *testing.T) { cmd.SetOut(&cmdOut) require.NoError(t, cmd.Flags().Set("policy", "OR('Org1MSP.member')")) - err := cmd.RunE(cmd, []string{"my-namespace"}) + err := cmd.RunE(cmd, []string{testNamespace}) require.NoError(t, err) require.Contains(t, cmdOut.String(), "tx-123") @@ -78,7 +78,7 @@ func TestNewCreateCommandRun_NoTx(t *testing.T) { }) require.NoError(t, cmd.Flags().Set("policy", "OR('Org1MSP.member')")) - err := cmd.RunE(cmd, []string{"my-namespace"}) + err := cmd.RunE(cmd, []string{testNamespace}) require.NoError(t, err) require.Contains(t, printerOut.String(), "Transaction status: STATUS_UNSPECIFIED") @@ -140,4 +140,4 @@ func (t *testApp) SubmitTransactionWithWait( ) (app.TxStatus, error) { args := t.Called(ctx, txID, tx) return args.Int(0), args.Error(1) -} +} \ No newline at end of file diff --git a/tools/fxconfig/internal/cli/v1/namespace_update_test.go b/tools/fxconfig/internal/cli/v1/namespace_update_test.go index a21cc754..b7320ad9 100644 --- a/tools/fxconfig/internal/cli/v1/namespace_update_test.go +++ b/tools/fxconfig/internal/cli/v1/namespace_update_test.go @@ -21,16 +21,13 @@ import ( func TestNewUpdateCommand(t *testing.T) { t.Parallel() - // Execute cmd := newNsUpdateCommand(&CLIContext{App: &testApp{}}) - // Assert require.NotNil(t, cmd, "newNsUpdateCommand should return a non-nil command") require.Equal(t, "update [name]", cmd.Use, "command use should be 'update [name]'") require.NotEmpty(t, cmd.Short, "command should have a short description") require.NotNil(t, cmd.RunE, "command should have a RunE function") - // Verify command-specific required flags version := cmd.Flag("version") require.NotNil(t, version, "version flag should exist") @@ -58,7 +55,7 @@ func TestNsUpdateCommandRun_TxReturned(t *testing.T) { require.NoError(t, cmd.Flags().Set("policy", "OR('Org1MSP.member')")) require.NoError(t, cmd.Flags().Set("version", "1")) - err := cmd.RunE(cmd, []string{"my-namespace"}) + err := cmd.RunE(cmd, []string{testNamespace}) require.NoError(t, err) require.Contains(t, outBuf.String(), "tx-456") @@ -79,9 +76,9 @@ func TestNsUpdateCommandRun_NoTx(t *testing.T) { require.NoError(t, cmd.Flags().Set("policy", "OR('Org1MSP.member')")) require.NoError(t, cmd.Flags().Set("version", "0")) - err := cmd.RunE(cmd, []string{"my-namespace"}) + err := cmd.RunE(cmd, []string{testNamespace}) require.NoError(t, err) require.Contains(t, printerOut.String(), "Transaction status: STATUS_UNSPECIFIED") mockApp.AssertExpectations(t) -} +} \ No newline at end of file diff --git a/tools/fxconfig/internal/cli/v1/root.go b/tools/fxconfig/internal/cli/v1/root.go index 040aa69f..3cc7dc31 100644 --- a/tools/fxconfig/internal/cli/v1/root.go +++ b/tools/fxconfig/internal/cli/v1/root.go @@ -16,6 +16,8 @@ import ( "github.com/hyperledger/fabric-x/tools/fxconfig/internal/config" ) +const appName = "fxconfig" + // NewRootCommand constructs and returns the root command for fxconfig. // It sets up configuration loading, flag registration, and all subcommands. // Configuration is loaded in PersistentPreRunE. @@ -23,7 +25,7 @@ func NewRootCommand(cliCtx *CLIContext, buildApp func(cfg *config.Config) (app.A // cli flags var cfgFile string rootCmd := &cobra.Command{ - Use: "fxconfig", + Use: appName, Short: "CLI tool for managing Fabric-X namespaces and transactions", Long: `fxconfig is a command-line tool for managing Fabric-X namespaces and transactions. @@ -80,4 +82,4 @@ Configuration can be provided via: rootCmd.SilenceUsage = true return rootCmd -} +} \ No newline at end of file diff --git a/tools/fxconfig/internal/cli/v1/root_test.go b/tools/fxconfig/internal/cli/v1/root_test.go index 9e584141..2dfa6a26 100644 --- a/tools/fxconfig/internal/cli/v1/root_test.go +++ b/tools/fxconfig/internal/cli/v1/root_test.go @@ -26,7 +26,7 @@ func TestNewRootCommand(t *testing.T) { }) require.NotNil(t, rootCmd) - require.Equal(t, "fxconfig", rootCmd.Use) + require.Equal(t, appName, rootCmd.Use) require.NotEmpty(t, rootCmd.Short) // --config flag must be registered @@ -37,7 +37,7 @@ func TestNewRootCommand(t *testing.T) { for _, sub := range rootCmd.Commands() { subCmds[sub.Name()] = true } - require.True(t, subCmds["version"]) + require.True(t, subCmds[versionCmd]) require.True(t, subCmds["info"]) require.True(t, subCmds["namespace"]) require.True(t, subCmds["tx"]) @@ -59,7 +59,7 @@ func TestPersistentPreRunE_ViaConfigFlag(t *testing.T) { rootCmd := NewRootCommand(cliCtx, func(_ *config.Config) (app.Application, error) { return &testApp{}, nil }) - rootCmd.SetArgs([]string{"--config", configPath, "version"}) + rootCmd.SetArgs([]string{"--config", configPath, versionCmd}) require.NoError(t, rootCmd.Execute()) @@ -85,7 +85,7 @@ func TestPersistentPreRunE_ViaProjectConfig(t *testing.T) { //nolint:paralleltes rootCmd := NewRootCommand(cliCtx, func(_ *config.Config) (app.Application, error) { return &testApp{}, nil }) - rootCmd.SetArgs([]string{"version"}) + rootCmd.SetArgs([]string{versionCmd}) require.NoError(t, rootCmd.Execute()) @@ -94,4 +94,4 @@ func TestPersistentPreRunE_ViaProjectConfig(t *testing.T) { //nolint:paralleltes require.NotNil(t, cliCtx.IOTransactionCodec) require.NotNil(t, cliCtx.App) require.Equal(t, "TestMSP", cliCtx.Config.MSP.LocalMspID) -} +} \ No newline at end of file diff --git a/tools/fxconfig/internal/cli/v1/version.go b/tools/fxconfig/internal/cli/v1/version.go index 5e797129..c7fae90e 100644 --- a/tools/fxconfig/internal/cli/v1/version.go +++ b/tools/fxconfig/internal/cli/v1/version.go @@ -16,11 +16,13 @@ import ( "github.com/hyperledger/fabric-x/tools/fxconfig/internal/cli/v1/cliio" ) +const versionCmd = "version" + // NewVersionCommand returns a command that displays version information. // It shows the fxconfig version, Go version, commit SHA, and OS/architecture. func NewVersionCommand() *cobra.Command { cmd := &cobra.Command{ - Use: "version", + Use: versionCmd, Short: "Display version information", Long: `Display detailed version information including: • fxconfig version @@ -30,7 +32,7 @@ func NewVersionCommand() *cobra.Command { Run: func(cmd *cobra.Command, _ []string) { osArch := fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH) p := cliio.NewCLIPrinter(cmd.OutOrStdout(), cmd.ErrOrStderr(), cliio.FormatTable) - p.Print("fxconfig\n") + p.Print(appName + "\n") p.Print(fmt.Sprintf(" %-16s %s\n", "Version:", metadata.Version)) p.Print(fmt.Sprintf(" %-16s %s\n", "Go Version:", runtime.Version())) p.Print(fmt.Sprintf(" %-16s %s\n", "Commit:", metadata.CommitSHA)) @@ -39,4 +41,4 @@ func NewVersionCommand() *cobra.Command { } return cmd -} +} \ No newline at end of file diff --git a/tools/fxconfig/internal/cli/v1/version_test.go b/tools/fxconfig/internal/cli/v1/version_test.go index f6080adb..679c9ee7 100644 --- a/tools/fxconfig/internal/cli/v1/version_test.go +++ b/tools/fxconfig/internal/cli/v1/version_test.go @@ -26,19 +26,19 @@ func TestVersionCommand(t *testing.T) { }{ { name: "version command with no args", - args: []string{"version"}, - expectedOutput: []string{"fxconfig", "Version:", "Go Version:", "OS/Arch:"}, + args: []string{versionCmd}, + expectedOutput: []string{appName, "Version:", "Go Version:", "OS/Arch:"}, expectError: false, }, { name: "version command with help flag", - args: []string{"version", "--help"}, - expectedOutput: []string{"Usage:", "fxconfig version"}, + args: []string{versionCmd, "--help"}, + expectedOutput: []string{"Usage:", appName + " " + versionCmd}, expectError: false, }, { name: "version command with invalid flag", - args: []string{"version", "--invalid"}, + args: []string{versionCmd, "--invalid"}, expectedOutput: []string{"unknown flag: --invalid"}, expectError: true, }, @@ -48,8 +48,7 @@ func TestVersionCommand(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - // Setup - rootCmd := &cobra.Command{Use: "fxconfig"} + rootCmd := &cobra.Command{Use: appName} rootCmd.AddCommand(NewVersionCommand()) var outBuf, errBuf bytes.Buffer @@ -57,10 +56,8 @@ func TestVersionCommand(t *testing.T) { rootCmd.SetErr(&errBuf) rootCmd.SetArgs(tt.args) - // Execute err := rootCmd.Execute() - // Assert if tt.expectError { require.Error(t, err) output := errBuf.String() @@ -81,26 +78,22 @@ func TestVersionCommand(t *testing.T) { func TestVersionCommand_OutputFormat(t *testing.T) { t.Parallel() - // Setup - rootCmd := &cobra.Command{Use: "fxconfig"} + rootCmd := &cobra.Command{Use: appName} rootCmd.AddCommand(NewVersionCommand()) var outBuf bytes.Buffer rootCmd.SetOut(&outBuf) - rootCmd.SetArgs([]string{"version"}) + rootCmd.SetArgs([]string{versionCmd}) - // Execute err := rootCmd.Execute() require.NoError(t, err) - // Assert output format output := outBuf.String() lines := strings.Split(strings.TrimSpace(output), "\n") require.GreaterOrEqual(t, len(lines), 5, "version output should have at least 5 lines") - require.Equal(t, "fxconfig", lines[0], "first line should be 'fxconfig'") + require.Equal(t, appName, lines[0], "first line should be 'fxconfig'") - // Verify subsequent lines have the expected format (key: value) for i := 1; i < len(lines); i++ { line := lines[i] if strings.TrimSpace(line) == "" { @@ -113,12 +106,10 @@ func TestVersionCommand_OutputFormat(t *testing.T) { func TestNewVersionCommand(t *testing.T) { t.Parallel() - // Execute cmd := NewVersionCommand() - // Assert require.NotNil(t, cmd, "NewVersionCommand should return a non-nil command") - require.Equal(t, "version", cmd.Use, "command use should be 'version'") + require.Equal(t, versionCmd, cmd.Use, "command use should be 'version'") require.NotEmpty(t, cmd.Short, "command should have a short description") require.NotNil(t, cmd.Run, "command should have a Run function") -} +} \ No newline at end of file diff --git a/tools/fxconfig/internal/client/client_test.go b/tools/fxconfig/internal/client/client_test.go index 3b73eb5f..b4c72a77 100644 --- a/tools/fxconfig/internal/client/client_test.go +++ b/tools/fxconfig/internal/client/client_test.go @@ -27,7 +27,7 @@ import ( "github.com/hyperledger/fabric-x/tools/fxconfig/internal/config" ) -// Test helper functions +const testLocalhost = "localhost" // boolPtr returns a pointer to a bool value. func boolPtr(b bool) *bool { @@ -65,7 +65,6 @@ func generateCertificate(t *testing.T, caCert *x509.Certificate, caKey *ecdsa.Pr ) (keyPEM, certPEM []byte) { t.Helper() - // Generate ECDSA P-256 key key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) require.NoError(t, err) @@ -80,7 +79,6 @@ func generateCertificate(t *testing.T, caCert *x509.Certificate, caKey *ecdsa.Pr KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, ExtKeyUsage: []x509.ExtKeyUsage{extKeyUsage}, } - // Split dnsNames into proper DNS SANs and IP SANs for _, name := range dnsNames { if ip := net.ParseIP(name); ip != nil { template.IPAddresses = append(template.IPAddresses, ip) @@ -92,7 +90,6 @@ func generateCertificate(t *testing.T, caCert *x509.Certificate, caKey *ecdsa.Pr certDER, err := x509.CreateCertificate(rand.Reader, template, caCert, &key.PublicKey, caKey) require.NoError(t, err) - // Encode key to PEM keyBytes, err := x509.MarshalECPrivateKey(key) require.NoError(t, err) keyPEM = pem.EncodeToMemory(&pem.Block{ @@ -100,14 +97,12 @@ func generateCertificate(t *testing.T, caCert *x509.Certificate, caKey *ecdsa.Pr Bytes: keyBytes, }) - // Encode certificate to PEM certPEM = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER}) return keyPEM, certPEM } -// generateServerConfig creates a comm.ServerConfig with generated ECDSA certificates -// Returns the server config and paths to CA cert and client cert/key for client configuration. +// generateServerConfig creates a comm.ServerConfig with generated ECDSA certificates. // //nolint:revive func generateServerConfig(t *testing.T, tlsMode string) ( @@ -119,13 +114,11 @@ func generateServerConfig(t *testing.T, tlsMode string) ( t.Helper() if tlsMode == "none" { - // No TLS configuration needed return comm.ServerConfig{}, "", "", "" } tmpDir := t.TempDir() - // Generate CA with ECDSA P-256 key caKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) require.NoError(t, err) @@ -147,32 +140,26 @@ func generateServerConfig(t *testing.T, tlsMode string) ( require.NoError(t, err) caCertPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: caCertDER}) - // Write CA certificate to file (for client to read) caCertPath = filepath.Join(tmpDir, "ca.pem") require.NoError(t, os.WriteFile(caCertPath, caCertPEM, 0o600)) - // Generate server certificate serverKey, serverCertPEM := generateCertificate(t, caTemplate, caKey, "server", - []string{"localhost", "127.0.0.1"}, x509.ExtKeyUsageServerAuth) + []string{testLocalhost, "127.0.0.1"}, x509.ExtKeyUsageServerAuth) - // Configure server with TLS serverConfig.SecOpts.UseTLS = true serverConfig.SecOpts.Certificate = serverCertPEM serverConfig.SecOpts.Key = serverKey if tlsMode == "mtls" { - // Generate client certificate for mTLS clientKey, clientCertPEM := generateCertificate(t, caTemplate, caKey, "client", nil, x509.ExtKeyUsageClientAuth) - // Write client key and cert to files (for client to read) clientKeyPath = filepath.Join(tmpDir, "client-key.pem") require.NoError(t, os.WriteFile(clientKeyPath, clientKey, 0o600)) clientCertPath = filepath.Join(tmpDir, "client-cert.pem") require.NoError(t, os.WriteFile(clientCertPath, clientCertPEM, 0o600)) - // Configure server to require client certificates serverConfig.SecOpts.RequireClientCert = true serverConfig.SecOpts.ClientRootCAs = [][]byte{caCertPEM} } @@ -184,7 +171,6 @@ func generateServerConfig(t *testing.T, tlsMode string) ( func startTestServer(t *testing.T, serverConfig comm.ServerConfig) (address string, cleanup func()) { t.Helper() - // Create listener with free port lis, err := net.Listen("tcp", "127.0.0.1:0") require.NoError(t, err) address = lis.Addr().String() @@ -192,12 +178,10 @@ func startTestServer(t *testing.T, serverConfig comm.ServerConfig) (address stri grpcServer, err := comm.NewGRPCServerFromListener(lis, serverConfig) require.NoError(t, err) - // Start server in background go func() { _ = grpcServer.Start() }() - // Poll until the server is accepting TCP connections deadline := time.Now().Add(5 * time.Second) for { c, dialErr := net.DialTimeout("tcp", address, 10*time.Millisecond) @@ -216,8 +200,6 @@ func startTestServer(t *testing.T, serverConfig comm.ServerConfig) (address stri return address, cleanup } -// Test loadFile function - func TestLoadFile_Success(t *testing.T) { t.Parallel() @@ -242,7 +224,6 @@ func TestLoadFile_Success_EmptyFile(t *testing.T) { func TestLoadFile_Success_LargeFile(t *testing.T) { t.Parallel() - // Create 1MB file content := make([]byte, 1024*1024) for i := range content { content[i] = byte(i % 256) @@ -296,8 +277,6 @@ func TestLoadFile_Error_EmptyPath(t *testing.T) { require.Nil(t, result) } -// Test createSecOpts function - No TLS scenarios - func TestCreateSecOpts_NoTLS_ExplicitlyDisabled(t *testing.T) { t.Parallel() @@ -337,8 +316,6 @@ func TestCreateSecOpts_NoTLS_EnabledFlagNil(t *testing.T) { require.False(t, secOpts.UseTLS) } -// Test createSecOpts function - TLS scenarios - func TestCreateSecOpts_TLS_Success(t *testing.T) { t.Parallel() @@ -449,8 +426,6 @@ func TestCreateSecOpts_TLS_Error_EmptyRootCertPath(t *testing.T) { require.Nil(t, secOpts) } -// Test createSecOpts function - mTLS scenarios - func TestCreateSecOpts_mTLS_Success(t *testing.T) { t.Parallel() @@ -648,19 +623,13 @@ func TestCreateSecOpts_mTLS_OnlyClientCertProvided(t *testing.T) { require.Nil(t, secOpts.Certificate) } -// Test newClientConn function - No TLS - func TestNewClientConn_NoTLS_Success(t *testing.T) { t.Parallel() - // Generate server config without TLS serverConfig, _, _, _ := generateServerConfig(t, "none") - - // Start test server address, cleanup := startTestServer(t, serverConfig) defer cleanup() - // Create client configuration cfg := &config.EndpointServiceConfig{ Address: address, ConnectionTimeout: 5 * time.Second, @@ -669,96 +638,77 @@ func TestNewClientConn_NoTLS_Success(t *testing.T) { }, } - // Attempt connection conn, err := newClientConn(cfg) require.NoError(t, err) require.NotNil(t, conn) defer conn.Close() //nolint:errcheck - // Verify connection state require.Equal(t, connectivity.Ready, conn.GetState()) } func TestNewClientConn_NoTLS_TLSConfigNil(t *testing.T) { t.Parallel() - // Generate server config without TLS serverConfig, _, _, _ := generateServerConfig(t, "none") - - // Start test server address, cleanup := startTestServer(t, serverConfig) defer cleanup() - // Create client configuration with nil TLS config cfg := &config.EndpointServiceConfig{ Address: address, ConnectionTimeout: 5 * time.Second, TLS: nil, } - // Attempt connection conn, err := newClientConn(cfg) require.NoError(t, err) require.NotNil(t, conn) defer conn.Close() //nolint:errcheck } -// Test newClientConn function - TLS - func TestNewClientConn_TLS_Success(t *testing.T) { t.Parallel() - // Generate server config with TLS and get CA cert path for client serverConfig, caCertPath, _, _ := generateServerConfig(t, "tls") - - // Start test server with TLS address, cleanup := startTestServer(t, serverConfig) defer cleanup() - // Create client configuration with server CA cfg := &config.EndpointServiceConfig{ Address: address, ConnectionTimeout: 5 * time.Second, TLS: &config.TLSConfig{ Enabled: boolPtr(true), RootCertPaths: []string{caCertPath}, - ServerNameOverride: "localhost", + ServerNameOverride: testLocalhost, }, } - // Attempt connection conn, err := newClientConn(cfg) require.NoError(t, err) require.NotNil(t, conn) defer conn.Close() //nolint:errcheck - // Verify TLS connection established require.Equal(t, connectivity.Ready, conn.GetState()) } func TestNewClientConn_TLS_MultipleRootCerts(t *testing.T) { t.Parallel() - // Generate two separate CAs serverConfig1, caCertPath1, _, _ := generateServerConfig(t, "tls") _, caCertPath2, _, _ := generateServerConfig(t, "tls") - // Start server with first CA address, cleanup := startTestServer(t, serverConfig1) defer cleanup() - // Configure client with both CAs cfg := &config.EndpointServiceConfig{ Address: address, ConnectionTimeout: 5 * time.Second, TLS: &config.TLSConfig{ Enabled: boolPtr(true), RootCertPaths: []string{caCertPath1, caCertPath2}, - ServerNameOverride: "localhost", + ServerNameOverride: testLocalhost, }, } - // Attempt connection conn, err := newClientConn(cfg) require.NoError(t, err) require.NotNil(t, conn) @@ -768,12 +718,10 @@ func TestNewClientConn_TLS_MultipleRootCerts(t *testing.T) { func TestNewClientConn_TLS_Error_RootCertNotFound(t *testing.T) { t.Parallel() - // Start server with TLS serverConfig, _, _, _ := generateServerConfig(t, "tls") address, cleanup := startTestServer(t, serverConfig) defer cleanup() - // Configure client with non-existent CA file cfg := &config.EndpointServiceConfig{ Address: address, ConnectionTimeout: 5 * time.Second, @@ -793,46 +741,34 @@ func TestNewClientConn_TLS_Error_RootCertNotFound(t *testing.T) { func TestNewClientConn_TLS_Error_WrongCA(t *testing.T) { t.Parallel() - // Generate server with its own CA serverConfig, _, _, _ := generateServerConfig(t, "tls") address, cleanup := startTestServer(t, serverConfig) defer cleanup() - // Generate a different CA for client (wrong CA) _, wrongCACertPath, _, _ := generateServerConfig(t, "tls") - // Configure client with wrong CA cfg := &config.EndpointServiceConfig{ Address: address, ConnectionTimeout: 2 * time.Second, TLS: &config.TLSConfig{ Enabled: boolPtr(true), RootCertPaths: []string{wrongCACertPath}, - ServerNameOverride: "localhost", + ServerNameOverride: testLocalhost, }, } - // Connection attempt should fail due to TLS certificate verification error. - // The 2s ConnectionTimeout is intentional: gRPC must attempt the handshake - // before the mismatch is detected, so this test will take the full timeout. conn, err := newClientConn(cfg) require.Error(t, err) require.Nil(t, conn) } -// Test newClientConn function - mTLS - func TestNewClientConn_mTLS_Success(t *testing.T) { t.Parallel() - // Generate server config with mTLS and get cert paths for client serverConfig, caCertPath, clientKeyPath, clientCertPath := generateServerConfig(t, "mtls") - - // Start test server with mTLS address, cleanup := startTestServer(t, serverConfig) defer cleanup() - // Create client configuration with full mTLS cfg := &config.EndpointServiceConfig{ Address: address, ConnectionTimeout: 5 * time.Second, @@ -841,60 +777,49 @@ func TestNewClientConn_mTLS_Success(t *testing.T) { RootCertPaths: []string{caCertPath}, ClientKeyPath: clientKeyPath, ClientCertPath: clientCertPath, - ServerNameOverride: "localhost", + ServerNameOverride: testLocalhost, }, } - // Attempt connection conn, err := newClientConn(cfg) require.NoError(t, err) require.NotNil(t, conn) defer conn.Close() //nolint:errcheck - // Verify mTLS connection established require.Equal(t, connectivity.Ready, conn.GetState()) } func TestNewClientConn_mTLS_NoClientCert(t *testing.T) { t.Parallel() - // Start mTLS server that requires client certificates serverConfig, caCertPath, _, _ := generateServerConfig(t, "mtls") address, cleanup := startTestServer(t, serverConfig) defer cleanup() - // Configure client with TLS but without client certificate cfg := &config.EndpointServiceConfig{ Address: address, ConnectionTimeout: 2 * time.Second, TLS: &config.TLSConfig{ Enabled: boolPtr(true), RootCertPaths: []string{caCertPath}, - ServerNameOverride: "localhost", - // No ClientKeyPath or ClientCertPath - server will reject this + ServerNameOverride: testLocalhost, }, } - // Connection should fail: server requires client certificate conn, err := newClientConn(cfg) require.Error(t, err) require.Nil(t, conn) } -// Test newClientConn function - connection failure scenarios - func TestNewClientConn_TLS_ClientToNoTLSServer(t *testing.T) { t.Parallel() - // Start server without TLS serverConfig, _, _, _ := generateServerConfig(t, "none") address, cleanup := startTestServer(t, serverConfig) defer cleanup() - // Generate a separate CA (any valid CA will do for this test) _, caCertPath, _, _ := generateServerConfig(t, "tls") - // Configure client with TLS enabled cfg := &config.EndpointServiceConfig{ Address: address, ConnectionTimeout: 2 * time.Second, @@ -904,8 +829,7 @@ func TestNewClientConn_TLS_ClientToNoTLSServer(t *testing.T) { }, } - // Connection should fail: TLS handshake against non-TLS server conn, err := newClientConn(cfg) require.Error(t, err) require.Nil(t, conn) -} +} \ No newline at end of file diff --git a/tools/fxconfig/internal/transaction/endorse_test.go b/tools/fxconfig/internal/transaction/endorse_test.go index 32510f16..561c2e9e 100644 --- a/tools/fxconfig/internal/transaction/endorse_test.go +++ b/tools/fxconfig/internal/transaction/endorse_test.go @@ -21,6 +21,13 @@ import ( "github.com/hyperledger/fabric-x-common/msp" ) +const ( + testNs1 = "ns1" + testNs2 = "ns2" + testNs3 = "ns3" + testOrg1 = "Org1MSP" +) + // mockSigningIdentity is a mock signing identity for testing. type mockSigningIdentity struct { signFunc func([]byte) ([]byte, error) @@ -120,7 +127,7 @@ func TestEndorse(t *testing.T) { }, }, signer: &mockSigningIdentity{ - mspID: "Org1MSP", + mspID: testOrg1, }, txID: "tx-123", expectError: false, @@ -130,13 +137,13 @@ func TestEndorse(t *testing.T) { name: "successful endorsement with multiple namespaces", tx: &applicationpb.Tx{ Namespaces: []*applicationpb.TxNamespace{ - {NsId: "ns1", NsVersion: 0}, - {NsId: "ns2", NsVersion: 1}, - {NsId: "ns3", NsVersion: 2}, + {NsId: testNs1, NsVersion: 0}, + {NsId: testNs2, NsVersion: 1}, + {NsId: testNs3, NsVersion: 2}, }, }, signer: &mockSigningIdentity{ - mspID: "Org1MSP", + mspID: testOrg1, }, txID: "tx-456", expectError: false, @@ -150,7 +157,7 @@ func TestEndorse(t *testing.T) { }, }, signer: &mockSigningIdentity{ - mspID: "Org1MSP", + mspID: testOrg1, signFunc: func([]byte) ([]byte, error) { return nil, errors.New("signing failed") }, diff --git a/tools/fxconfig/internal/transaction/merge_test.go b/tools/fxconfig/internal/transaction/merge_test.go index 27c4048d..6b28f0cf 100644 --- a/tools/fxconfig/internal/transaction/merge_test.go +++ b/tools/fxconfig/internal/transaction/merge_test.go @@ -15,6 +15,11 @@ import ( "github.com/hyperledger/fabric-x-common/api/msppb" ) +const ( + testOrg2 = "Org2MSP" + testOrg3 = "Org3MSP" +) + // Helper function to create a test transaction with endorsements. func createTestTx(namespaces []string, endorsements map[string][]string) *applicationpb.Tx { tx := &applicationpb.Tx{ @@ -33,7 +38,6 @@ func createTestTx(namespaces []string, endorsements map[string][]string) *applic if mspIDs, ok := endorsements[ns]; ok { for _, mspID := range mspIDs { - // Create identity using the msppb package identity := &msppb.Identity{ MspId: mspID, } @@ -68,7 +72,7 @@ func TestMerge_ErrorCases(t *testing.T) { { name: "single transaction", txs: []*applicationpb.Tx{ - createTestTx([]string{"ns1"}, map[string][]string{"ns1": {"Org1MSP"}}), + createTestTx([]string{testNs1}, map[string][]string{testNs1: {testOrg1}}), }, }, } @@ -88,8 +92,8 @@ func TestMerge_ErrorCases(t *testing.T) { t.Run("transaction content mismatch", func(t *testing.T) { t.Parallel() - tx1 := createTestTx([]string{"ns1"}, map[string][]string{"ns1": {"Org1MSP"}}) - tx2 := createTestTx([]string{"ns2"}, map[string][]string{"ns2": {"Org2MSP"}}) + tx1 := createTestTx([]string{testNs1}, map[string][]string{testNs1: {testOrg1}}) + tx2 := createTestTx([]string{testNs2}, map[string][]string{testNs2: {testOrg2}}) result, err := Merge([]*applicationpb.Tx{tx1, tx2}) require.Error(t, err) @@ -100,8 +104,8 @@ func TestMerge_ErrorCases(t *testing.T) { t.Run("transaction with empty endorsements", func(t *testing.T) { t.Parallel() - tx1 := createTestTx([]string{"ns1"}, map[string][]string{"ns1": {"Org1MSP"}}) - tx2 := createTestTx([]string{"ns1"}, map[string][]string{"ns1": {}}) + tx1 := createTestTx([]string{testNs1}, map[string][]string{testNs1: {testOrg1}}) + tx2 := createTestTx([]string{testNs1}, map[string][]string{testNs1: {}}) result, err := Merge([]*applicationpb.Tx{tx1, tx2}) require.Error(t, err) @@ -112,8 +116,8 @@ func TestMerge_ErrorCases(t *testing.T) { t.Run("conflicting namespace writes", func(t *testing.T) { t.Parallel() - tx1 := createTestTx([]string{"ns1"}, map[string][]string{"ns1": {"Org1MSP"}}) - tx2 := createTestTx([]string{"ns1"}, map[string][]string{"ns1": {"Org2MSP"}}) + tx1 := createTestTx([]string{testNs1}, map[string][]string{testNs1: {testOrg1}}) + tx2 := createTestTx([]string{testNs1}, map[string][]string{testNs1: {testOrg2}}) tx1.Namespaces[0].ReadWrites = []*applicationpb.ReadWrite{{ Key: []byte("asset-1"), @@ -145,75 +149,69 @@ func TestMerge_SingleNamespace(t *testing.T) { t.Run("two transactions with different endorsements", func(t *testing.T) { t.Parallel() - tx1 := createTestTx([]string{"ns1"}, map[string][]string{"ns1": {"Org1MSP"}}) - tx2 := createTestTx([]string{"ns1"}, map[string][]string{"ns1": {"Org2MSP"}}) + tx1 := createTestTx([]string{testNs1}, map[string][]string{testNs1: {testOrg1}}) + tx2 := createTestTx([]string{testNs1}, map[string][]string{testNs1: {testOrg2}}) result, err := Merge([]*applicationpb.Tx{tx1, tx2}) require.NoError(t, err) require.NotNil(t, result) - // Should have 2 endorsements require.Len(t, result.Endorsements, 1) require.Len(t, result.Endorsements[0].EndorsementsWithIdentity, 2) - // Should be sorted by MspId - require.Equal(t, "Org1MSP", result.Endorsements[0].EndorsementsWithIdentity[0].Identity.GetMspId()) - require.Equal(t, "Org2MSP", result.Endorsements[0].EndorsementsWithIdentity[1].Identity.GetMspId()) + require.Equal(t, testOrg1, result.Endorsements[0].EndorsementsWithIdentity[0].Identity.GetMspId()) + require.Equal(t, testOrg2, result.Endorsements[0].EndorsementsWithIdentity[1].Identity.GetMspId()) }) t.Run("three transactions with different endorsements", func(t *testing.T) { t.Parallel() - tx1 := createTestTx([]string{"ns1"}, map[string][]string{"ns1": {"Org1MSP"}}) - tx2 := createTestTx([]string{"ns1"}, map[string][]string{"ns1": {"Org2MSP"}}) - tx3 := createTestTx([]string{"ns1"}, map[string][]string{"ns1": {"Org3MSP"}}) + tx1 := createTestTx([]string{testNs1}, map[string][]string{testNs1: {testOrg1}}) + tx2 := createTestTx([]string{testNs1}, map[string][]string{testNs1: {testOrg2}}) + tx3 := createTestTx([]string{testNs1}, map[string][]string{testNs1: {testOrg3}}) result, err := Merge([]*applicationpb.Tx{tx1, tx2, tx3}) require.NoError(t, err) require.NotNil(t, result) - // Should have 3 endorsements require.Len(t, result.Endorsements, 1) require.Len(t, result.Endorsements[0].EndorsementsWithIdentity, 3) - // Should be sorted by MspId - require.Equal(t, "Org1MSP", result.Endorsements[0].EndorsementsWithIdentity[0].Identity.GetMspId()) - require.Equal(t, "Org2MSP", result.Endorsements[0].EndorsementsWithIdentity[1].Identity.GetMspId()) - require.Equal(t, "Org3MSP", result.Endorsements[0].EndorsementsWithIdentity[2].Identity.GetMspId()) + require.Equal(t, testOrg1, result.Endorsements[0].EndorsementsWithIdentity[0].Identity.GetMspId()) + require.Equal(t, testOrg2, result.Endorsements[0].EndorsementsWithIdentity[1].Identity.GetMspId()) + require.Equal(t, testOrg3, result.Endorsements[0].EndorsementsWithIdentity[2].Identity.GetMspId()) }) t.Run("deduplication - same MspId in multiple transactions", func(t *testing.T) { t.Parallel() - tx1 := createTestTx([]string{"ns1"}, map[string][]string{"ns1": {"Org1MSP"}}) - tx2 := createTestTx([]string{"ns1"}, map[string][]string{"ns1": {"Org1MSP"}}) + tx1 := createTestTx([]string{testNs1}, map[string][]string{testNs1: {testOrg1}}) + tx2 := createTestTx([]string{testNs1}, map[string][]string{testNs1: {testOrg1}}) result, err := Merge([]*applicationpb.Tx{tx1, tx2}) require.NoError(t, err) require.NotNil(t, result) - // Should have only 1 endorsement (deduplicated) require.Len(t, result.Endorsements, 1) require.Len(t, result.Endorsements[0].EndorsementsWithIdentity, 1) - require.Equal(t, "Org1MSP", result.Endorsements[0].EndorsementsWithIdentity[0].Identity.GetMspId()) + require.Equal(t, testOrg1, result.Endorsements[0].EndorsementsWithIdentity[0].Identity.GetMspId()) }) t.Run("sorting - unsorted input should be sorted in output", func(t *testing.T) { t.Parallel() - tx1 := createTestTx([]string{"ns1"}, map[string][]string{"ns1": {"Org3MSP"}}) - tx2 := createTestTx([]string{"ns1"}, map[string][]string{"ns1": {"Org1MSP"}}) - tx3 := createTestTx([]string{"ns1"}, map[string][]string{"ns1": {"Org2MSP"}}) + tx1 := createTestTx([]string{testNs1}, map[string][]string{testNs1: {testOrg3}}) + tx2 := createTestTx([]string{testNs1}, map[string][]string{testNs1: {testOrg1}}) + tx3 := createTestTx([]string{testNs1}, map[string][]string{testNs1: {testOrg2}}) result, err := Merge([]*applicationpb.Tx{tx1, tx2, tx3}) require.NoError(t, err) require.NotNil(t, result) - // Should be sorted alphabetically by MspId require.Len(t, result.Endorsements[0].EndorsementsWithIdentity, 3) - require.Equal(t, "Org1MSP", result.Endorsements[0].EndorsementsWithIdentity[0].Identity.GetMspId()) - require.Equal(t, "Org2MSP", result.Endorsements[0].EndorsementsWithIdentity[1].Identity.GetMspId()) - require.Equal(t, "Org3MSP", result.Endorsements[0].EndorsementsWithIdentity[2].Identity.GetMspId()) + require.Equal(t, testOrg1, result.Endorsements[0].EndorsementsWithIdentity[0].Identity.GetMspId()) + require.Equal(t, testOrg2, result.Endorsements[0].EndorsementsWithIdentity[1].Identity.GetMspId()) + require.Equal(t, testOrg3, result.Endorsements[0].EndorsementsWithIdentity[2].Identity.GetMspId()) }) } @@ -224,17 +222,17 @@ func TestMerge_MultipleNamespaces(t *testing.T) { t.Parallel() tx1 := createTestTx( - []string{"ns1", "ns2"}, + []string{testNs1, testNs2}, map[string][]string{ - "ns1": {"Org1MSP"}, - "ns2": {"Org1MSP"}, + testNs1: {testOrg1}, + testNs2: {testOrg1}, }, ) tx2 := createTestTx( - []string{"ns1", "ns2"}, + []string{testNs1, testNs2}, map[string][]string{ - "ns1": {"Org2MSP"}, - "ns2": {"Org3MSP"}, + testNs1: {testOrg2}, + testNs2: {testOrg3}, }, ) @@ -242,37 +240,34 @@ func TestMerge_MultipleNamespaces(t *testing.T) { require.NoError(t, err) require.NotNil(t, result) - // Should have 2 namespaces require.Len(t, result.Endorsements, 2) - // ns1 should have 2 endorsements (Org1MSP, Org2MSP) require.Len(t, result.Endorsements[0].EndorsementsWithIdentity, 2) - require.Equal(t, "Org1MSP", result.Endorsements[0].EndorsementsWithIdentity[0].Identity.GetMspId()) - require.Equal(t, "Org2MSP", result.Endorsements[0].EndorsementsWithIdentity[1].Identity.GetMspId()) + require.Equal(t, testOrg1, result.Endorsements[0].EndorsementsWithIdentity[0].Identity.GetMspId()) + require.Equal(t, testOrg2, result.Endorsements[0].EndorsementsWithIdentity[1].Identity.GetMspId()) - // ns2 should have 2 endorsements (Org1MSP, Org3MSP) require.Len(t, result.Endorsements[1].EndorsementsWithIdentity, 2) - require.Equal(t, "Org1MSP", result.Endorsements[1].EndorsementsWithIdentity[0].Identity.GetMspId()) - require.Equal(t, "Org3MSP", result.Endorsements[1].EndorsementsWithIdentity[1].Identity.GetMspId()) + require.Equal(t, testOrg1, result.Endorsements[1].EndorsementsWithIdentity[0].Identity.GetMspId()) + require.Equal(t, testOrg3, result.Endorsements[1].EndorsementsWithIdentity[1].Identity.GetMspId()) }) t.Run("three namespaces with mixed endorsements", func(t *testing.T) { t.Parallel() tx1 := createTestTx( - []string{"ns1", "ns2", "ns3"}, + []string{testNs1, testNs2, testNs3}, map[string][]string{ - "ns1": {"Org1MSP"}, - "ns2": {"Org2MSP"}, - "ns3": {"Org3MSP"}, + testNs1: {testOrg1}, + testNs2: {testOrg2}, + testNs3: {testOrg3}, }, ) tx2 := createTestTx( - []string{"ns1", "ns2", "ns3"}, + []string{testNs1, testNs2, testNs3}, map[string][]string{ - "ns1": {"Org4MSP"}, - "ns2": {"Org5MSP"}, - "ns3": {"Org6MSP"}, + testNs1: {"Org4MSP"}, + testNs2: {"Org5MSP"}, + testNs3: {"Org6MSP"}, }, ) @@ -280,10 +275,8 @@ func TestMerge_MultipleNamespaces(t *testing.T) { require.NoError(t, err) require.NotNil(t, result) - // Should have 3 namespaces require.Len(t, result.Endorsements, 3) - // Each namespace should have 2 endorsements for i := range 3 { require.Len(t, result.Endorsements[i].EndorsementsWithIdentity, 2) } @@ -293,17 +286,17 @@ func TestMerge_MultipleNamespaces(t *testing.T) { t.Parallel() tx1 := createTestTx( - []string{"ns1", "ns2"}, + []string{testNs1, testNs2}, map[string][]string{ - "ns1": {"Org1MSP"}, - "ns2": {"Org1MSP"}, + testNs1: {testOrg1}, + testNs2: {testOrg1}, }, ) tx2 := createTestTx( - []string{"ns1", "ns2"}, + []string{testNs1, testNs2}, map[string][]string{ - "ns1": {"Org1MSP"}, - "ns2": {"Org1MSP"}, + testNs1: {testOrg1}, + testNs2: {testOrg1}, }, ) @@ -311,12 +304,11 @@ func TestMerge_MultipleNamespaces(t *testing.T) { require.NoError(t, err) require.NotNil(t, result) - // Each namespace should have only 1 endorsement (deduplicated) require.Len(t, result.Endorsements, 2) require.Len(t, result.Endorsements[0].EndorsementsWithIdentity, 1) require.Len(t, result.Endorsements[1].EndorsementsWithIdentity, 1) - require.Equal(t, "Org1MSP", result.Endorsements[0].EndorsementsWithIdentity[0].Identity.GetMspId()) - require.Equal(t, "Org1MSP", result.Endorsements[1].EndorsementsWithIdentity[0].Identity.GetMspId()) + require.Equal(t, testOrg1, result.Endorsements[0].EndorsementsWithIdentity[0].Identity.GetMspId()) + require.Equal(t, testOrg1, result.Endorsements[1].EndorsementsWithIdentity[0].Identity.GetMspId()) }) } @@ -326,12 +318,12 @@ func TestMerge_PreservesTransactionContent(t *testing.T) { t.Run("merged transaction preserves namespace data", func(t *testing.T) { t.Parallel() - tx1 := createTestTx([]string{"ns1"}, map[string][]string{"ns1": {"Org1MSP"}}) + tx1 := createTestTx([]string{testNs1}, map[string][]string{testNs1: {testOrg1}}) tx1.Namespaces[0].ReadWrites = []*applicationpb.ReadWrite{ {Key: []byte("key1"), Value: []byte("value1")}, } - tx2 := createTestTx([]string{"ns1"}, map[string][]string{"ns1": {"Org2MSP"}}) + tx2 := createTestTx([]string{testNs1}, map[string][]string{testNs1: {testOrg2}}) tx2.Namespaces[0].ReadWrites = []*applicationpb.ReadWrite{ {Key: []byte("key1"), Value: []byte("value1")}, } @@ -340,13 +332,11 @@ func TestMerge_PreservesTransactionContent(t *testing.T) { require.NoError(t, err) require.NotNil(t, result) - // Namespace data should be preserved require.Len(t, result.Namespaces, 1) - require.Equal(t, "ns1", result.Namespaces[0].NsId) + require.Equal(t, testNs1, result.Namespaces[0].NsId) require.Len(t, result.Namespaces[0].ReadWrites, 1) require.Equal(t, []byte("key1"), result.Namespaces[0].ReadWrites[0].Key) - // But endorsements should be merged require.Len(t, result.Endorsements[0].EndorsementsWithIdentity, 2) }) } @@ -354,8 +344,8 @@ func TestMerge_PreservesTransactionContent(t *testing.T) { func TestMerge_EmptyReadWriteSetIsValid(t *testing.T) { t.Parallel() - tx1 := createTestTx([]string{"ns1"}, map[string][]string{"ns1": {"Org1MSP"}}) - tx2 := createTestTx([]string{"ns1"}, map[string][]string{"ns1": {"Org2MSP"}}) + tx1 := createTestTx([]string{testNs1}, map[string][]string{testNs1: {testOrg1}}) + tx2 := createTestTx([]string{testNs1}, map[string][]string{testNs1: {testOrg2}}) result, err := Merge([]*applicationpb.Tx{tx1, tx2}) require.NoError(t, err) @@ -368,15 +358,15 @@ func TestMerge_EmptyReadWriteSetIsValid(t *testing.T) { func TestMerge_DuplicateEndorsements(t *testing.T) { t.Parallel() - tx1 := createTestTx([]string{"ns1"}, map[string][]string{"ns1": {"Org1MSP", "Org1MSP", "Org2MSP"}}) - tx2 := createTestTx([]string{"ns1"}, map[string][]string{"ns1": {"Org2MSP", "Org3MSP"}}) + tx1 := createTestTx([]string{testNs1}, map[string][]string{testNs1: {testOrg1, testOrg1, testOrg2}}) + tx2 := createTestTx([]string{testNs1}, map[string][]string{testNs1: {testOrg2, testOrg3}}) result, err := Merge([]*applicationpb.Tx{tx1, tx2}) require.NoError(t, err) require.NotNil(t, result) require.Len(t, result.Endorsements, 1) require.Len(t, result.Endorsements[0].EndorsementsWithIdentity, 3) - require.Equal(t, "Org1MSP", result.Endorsements[0].EndorsementsWithIdentity[0].Identity.GetMspId()) - require.Equal(t, "Org2MSP", result.Endorsements[0].EndorsementsWithIdentity[1].Identity.GetMspId()) - require.Equal(t, "Org3MSP", result.Endorsements[0].EndorsementsWithIdentity[2].Identity.GetMspId()) -} + require.Equal(t, testOrg1, result.Endorsements[0].EndorsementsWithIdentity[0].Identity.GetMspId()) + require.Equal(t, testOrg2, result.Endorsements[0].EndorsementsWithIdentity[1].Identity.GetMspId()) + require.Equal(t, testOrg3, result.Endorsements[0].EndorsementsWithIdentity[2].Identity.GetMspId()) +} \ No newline at end of file diff --git a/tools/fxconfig/internal/transaction/policy.go b/tools/fxconfig/internal/transaction/policy.go index 05d2ad13..53dc5b65 100644 --- a/tools/fxconfig/internal/transaction/policy.go +++ b/tools/fxconfig/internal/transaction/policy.go @@ -19,6 +19,8 @@ import ( "github.com/hyperledger/fabric-x-common/protoutil" ) +const publicKeyPEMType = "PUBLIC KEY" + // CreateMspPolicy creates an MSP-based namespace policy from a DSL expression. // Example: "OR('Org1MSP.member', 'Org2MSP.member')" or "AND('Org1MSP.admin', 'Org2MSP.admin')". func CreateMspPolicy(policy string) (*applicationpb.NamespacePolicy, error) { @@ -77,7 +79,7 @@ func getPubKeyFromPemData(pemContent []byte) ([]byte, error) { } return pem.EncodeToMemory(&pem.Block{ - Type: "PUBLIC KEY", + Type: publicKeyPEMType, Bytes: key, }), nil } @@ -114,4 +116,4 @@ func parseCertificateOrPublicKey(blockBytes []byte) ([]byte, error) { return nil, fmt.Errorf("marshalling public key from failed: %w", err) } return key, nil -} +} \ No newline at end of file diff --git a/tools/fxconfig/internal/transaction/policy_test.go b/tools/fxconfig/internal/transaction/policy_test.go index 5042f71b..4715078e 100644 --- a/tools/fxconfig/internal/transaction/policy_test.go +++ b/tools/fxconfig/internal/transaction/policy_test.go @@ -35,7 +35,7 @@ func TestGetPubKeyFromPemData(t *testing.T) { // Create PEM encoded public key pubKeyPEM := pem.EncodeToMemory(&pem.Block{ - Type: "PUBLIC KEY", + Type: publicKeyPEMType, Bytes: pubKeyDER, }) @@ -115,7 +115,7 @@ MIIBogIBAAJBALRiMLAA // Verify result is valid PEM block, _ := pem.Decode(result) require.NotNil(t, block) - require.Equal(t, "PUBLIC KEY", block.Type) + require.Equal(t, publicKeyPEMType, block.Type) } }) } @@ -163,7 +163,7 @@ func TestCreateThresholdPolicy(t *testing.T) { pubKeyDER, err := x509.MarshalPKIXPublicKey(&privateKey.PublicKey) require.NoError(t, err) - pubKeyPEM := pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: pubKeyDER}) + pubKeyPEM := pem.EncodeToMemory(&pem.Block{Type: publicKeyPEMType, Bytes: pubKeyDER}) tmpDir := t.TempDir() keyFile := filepath.Join(tmpDir, "key.pem") @@ -189,4 +189,4 @@ func TestCreateThresholdPolicy(t *testing.T) { require.Error(t, err) require.Nil(t, policy) }) -} +} \ No newline at end of file