diff --git a/cli/cli.go b/cli/cli.go index ac256ad..dd60fc1 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -95,15 +95,16 @@ func (c *CLI) Execute(ctx context.Context, args []string) { resp, err = c.pl.DescribeKey(ctx, &request) } case plugin.CommandGenerateSignature: - var request plugin.VerifySignatureRequest + var request plugin.GenerateSignatureRequest err = c.unmarshalRequest(&request) if err == nil { - c.logger.Debugf("executing %s plugin's VerifySignature function", reflect.TypeOf(c.pl)) - resp, err = c.pl.VerifySignature(ctx, &request) + c.logger.Debugf("executing %s plugin's GenerateSignature function", reflect.TypeOf(c.pl)) + resp, err = c.pl.GenerateSignature(ctx, &request) } case plugin.Version: rescueStdOut() c.printVersion(ctx) + return default: // should never happen rescueStdOut() @@ -115,7 +116,7 @@ func (c *CLI) Execute(ctx context.Context, args []string) { if pluginErr != nil { deliverError(pluginErr.Error()) } - fmt.Println(op) + fmt.Print(op) } // printVersion prints version of executable @@ -123,7 +124,6 @@ func (c *CLI) printVersion(ctx context.Context) { md := c.getMetadata(ctx, c.pl) fmt.Printf("%s - %s\nVersion: %s \n", md.Name, md.Description, md.Version) - os.Exit(0) } // validateArgs validate commands/arguments passed to executable. diff --git a/cli/cli_test.go b/cli/cli_test.go index 59e1f1d..41df833 100644 --- a/cli/cli_test.go +++ b/cli/cli_test.go @@ -16,6 +16,7 @@ package cli import ( "context" "fmt" + "io" "log" "os" "os/exec" @@ -27,7 +28,15 @@ import ( "github.com/notaryproject/notation-plugin-framework-go/plugin" ) -var cli, _ = New(&mockPlugin{}) +var cli, _ = New(mock.NewPlugin(false)) +var errorCli, _ = New(mock.NewPlugin(true)) + +func TestNewWithLogger(t *testing.T) { + _, err := NewWithLogger(nil, &discardLogger{}) + if err == nil { + t.Fatalf("NewWithLogger() expected error but not found") + } +} func TestMarshalResponse(t *testing.T) { res := plugin.GenerateEnvelopeResponse{ @@ -94,19 +103,10 @@ func TestUnmarshalRequestError(t *testing.T) { } } -func TestGetMetadata(t *testing.T) { - pl := mock.NewPlugin(false) - ctx := context.Background() - - cli.getMetadata(ctx, pl) -} - func TestGetMetadataError(t *testing.T) { if os.Getenv("TEST_OS_EXIT") == "1" { - pl := mock.NewPlugin(true) ctx := context.Background() - - cli.getMetadata(ctx, pl) + errorCli.Execute(ctx, []string{string(plugin.CommandGetMetadata)}) return } cmd := exec.Command(os.Args[0], "-test.run=TestGetMetadataError") @@ -118,6 +118,52 @@ func TestGetMetadataError(t *testing.T) { t.Fatalf("process ran with err %v, want exit status 1", err) } +func TestExecuteSuccess(t *testing.T) { + sigGenCli, _ := New(mock.NewSigGeneratorPlugin(false)) + tests := map[string]struct { + c *CLI + op string + }{ + string(plugin.CommandGetMetadata): { + c: cli, + op: "{\"name\":\"Example Plugin\",\"description\":\"This is an description of example plugin. 🍺\",\"version\":\"1.0.0\",\"url\":\"https://example.com/notation/plugin\",\"capabilities\":[\"SIGNATURE_VERIFIER.TRUSTED_IDENTITY\",\"SIGNATURE_VERIFIER.REVOCATION_CHECK\",\"SIGNATURE_GENERATOR.ENVELOPE\"]}", + }, + string(plugin.Version): { + c: cli, + op: "Example Plugin - This is an description of example plugin. 🍺\nVersion: 1.0.0 \n", + }, + string(plugin.CommandGenerateEnvelope): { + c: cli, + op: "{\"signatureEnvelope\":\"\",\"signatureEnvelopeType\":\"\",\"annotations\":{\"manifestAnntnKey1\":\"value1\"}}", + }, + string(plugin.CommandVerifySignature): { + c: cli, + op: "{\"verificationResults\":{\"SIGNATURE_VERIFIER.REVOCATION_CHECK\":{\"success\":true,\"reason\":\"Not revoked\"},\"SIGNATURE_VERIFIER.TRUSTED_IDENTITY\":{\"success\":true,\"reason\":\"Valid trusted Identity\"}},\"processedAttributes\":[]}", + }, + string(plugin.CommandGenerateSignature): { + c: sigGenCli, + op: "{\"keyId\":\"someKeyId\",\"signature\":\"YWJjZA==\",\"signingAlgorithm\":\"RSASSA-PSS-SHA-256\",\"certificateChain\":[\"YWJjZA==\",\"d3h5eg==\"]}", + }, + string(plugin.CommandDescribeKey): { + c: sigGenCli, + op: "{\"keyId\":\"someKeyId\",\"keySpec\":\"RSA-2048\"}", + }, + } + for name, test := range tests { + t.Run(name, func(t *testing.T) { + closer := setupReader("{}") + defer closer() + op := captureStdOut(func() { + test.c.Execute(context.Background(), []string{"notation", name}) + }) + fmt.Println(op) + if op != test.op { + t.Errorf("Execute() with '%s' args, expected '%s' but got '%s'", name, test.op, op) + } + }) + } +} + func setupReader(content string) func() { tmpfile, err := os.CreateTemp("", "example") if err != nil { @@ -142,6 +188,17 @@ func setupReader(content string) func() { } } +func captureStdOut(f func()) string { + orig := os.Stdout + r, w, _ := os.Pipe() + os.Stdout = w + f() + os.Stdout = orig + w.Close() + out, _ := io.ReadAll(r) + return string(out) +} + func assertErr(t *testing.T, err error, code plugin.ErrorCode) { if plgErr, ok := err.(*plugin.Error); ok { if reflect.DeepEqual(code, plgErr.ErrCode) { @@ -152,27 +209,3 @@ func assertErr(t *testing.T, err error, code plugin.ErrorCode) { t.Errorf("expected error of type PluginError but found %s", reflect.TypeOf(err)) } - -type mockPlugin struct { -} - -func (p *mockPlugin) DescribeKey(_ context.Context, _ *plugin.DescribeKeyRequest) (*plugin.DescribeKeyResponse, error) { - return nil, plugin.NewUnsupportedError("DescribeKey operation is not implemented by example plugin") -} - -func (p *mockPlugin) GenerateSignature(_ context.Context, _ *plugin.GenerateSignatureRequest) (*plugin.GenerateSignatureResponse, error) { - return nil, plugin.NewUnsupportedError("GenerateSignature operation is not implemented by example plugin") -} - -func (p *mockPlugin) GenerateEnvelope(_ context.Context, _ *plugin.GenerateEnvelopeRequest) (*plugin.GenerateEnvelopeResponse, error) { - return nil, plugin.NewUnsupportedError("GenerateEnvelope operation is not implemented by example plugin") -} - -func (p *mockPlugin) VerifySignature(_ context.Context, _ *plugin.VerifySignatureRequest) (*plugin.VerifySignatureResponse, error) { - return nil, plugin.NewUnsupportedError("VerifySignature operation is not implemented by example plugin") - -} - -func (p *mockPlugin) GetMetadata(_ context.Context, _ *plugin.GetMetadataRequest) (*plugin.GetMetadataResponse, error) { - return nil, plugin.NewUnsupportedError("GetMetadata operation is not implemented by mock plugin") -} diff --git a/cli/internal/mock/plugin.go b/cli/internal/mock/plugin.go index c896dc6..692ba77 100644 --- a/cli/internal/mock/plugin.go +++ b/cli/internal/mock/plugin.go @@ -23,35 +23,60 @@ import ( // Mock plugin used only for testing. type mockPlugin struct { - fail bool + fail bool + envGenerator bool } func NewPlugin(failPlugin bool) *mockPlugin { - return &mockPlugin{fail: failPlugin} + return &mockPlugin{ + fail: failPlugin, + envGenerator: true, + } +} + +func NewSigGeneratorPlugin(failPlugin bool) *mockPlugin { + return &mockPlugin{ + fail: failPlugin, + envGenerator: false, + } } func (p *mockPlugin) DescribeKey(ctx context.Context, req *plugin.DescribeKeyRequest) (*plugin.DescribeKeyResponse, error) { - return nil, plugin.NewUnsupportedError("DescribeKey operation is not implemented by example plugin") + if p.fail || p.envGenerator { + return nil, fmt.Errorf("DescribeKey() expected error") + } + return &plugin.DescribeKeyResponse{ + KeyID: "someKeyId", + KeySpec: plugin.KeySpecRSA2048, + }, nil } func (p *mockPlugin) GenerateSignature(ctx context.Context, req *plugin.GenerateSignatureRequest) (*plugin.GenerateSignatureResponse, error) { - return nil, plugin.NewUnsupportedError("GenerateSignature operation is not implemented by example plugin") + if p.fail || p.envGenerator { + return nil, fmt.Errorf("GenerateSignature() expected error") + } + return &plugin.GenerateSignatureResponse{ + KeyID: "someKeyId", + Signature: []byte("abcd"), + SigningAlgorithm: plugin.SignatureAlgorithmRSASSA_PSS_SHA256, + CertificateChain: [][]byte{[]byte("abcd"), []byte("wxyz")}, + }, nil } func (p *mockPlugin) GenerateEnvelope(ctx context.Context, req *plugin.GenerateEnvelopeRequest) (*plugin.GenerateEnvelopeResponse, error) { - if p.fail { - return nil, fmt.Errorf("expected error") + if p.fail || !p.envGenerator { + return nil, fmt.Errorf("GenerateEnvelope() expected error") } return &plugin.GenerateEnvelopeResponse{ SignatureEnvelope: []byte(""), - SignatureEnvelopeType: "application/jose+json", + SignatureEnvelopeType: req.SignatureEnvelopeType, Annotations: map[string]string{"manifestAnntnKey1": "value1"}, }, nil } func (p *mockPlugin) VerifySignature(ctx context.Context, req *plugin.VerifySignatureRequest) (*plugin.VerifySignatureResponse, error) { if p.fail { - return nil, fmt.Errorf("expected error") + return nil, fmt.Errorf("VerifySignature() expected error") } upAttrs := req.Signature.UnprocessedAttributes pAttrs := make([]interface{}, len(upAttrs)) @@ -76,16 +101,24 @@ func (p *mockPlugin) VerifySignature(ctx context.Context, req *plugin.VerifySign func (p *mockPlugin) GetMetadata(ctx context.Context, req *plugin.GetMetadataRequest) (*plugin.GetMetadataResponse, error) { if p.fail { - return nil, fmt.Errorf("expected error") + return nil, fmt.Errorf("GetMetadata() expected error") + } + + cap := []plugin.Capability{ + plugin.CapabilityTrustedIdentityVerifier, + plugin.CapabilityRevocationCheckVerifier, + } + if p.envGenerator { + cap = append(cap, plugin.CapabilityEnvelopeGenerator) + } else { + cap = append(cap, plugin.CapabilitySignatureGenerator) } + return &plugin.GetMetadataResponse{ - Name: "Example Plugin", - Description: "This is an description of example plugin. 🍺", - URL: "https://example.com/notation/plugin", - Version: "1.0.0", - Capabilities: []plugin.Capability{ - plugin.CapabilityEnvelopeGenerator, - plugin.CapabilityTrustedIdentityVerifier, - plugin.CapabilityRevocationCheckVerifier}, + Name: "Example Plugin", + Description: "This is an description of example plugin. 🍺", + URL: "https://example.com/notation/plugin", + Version: "1.0.0", + Capabilities: cap, }, nil }