Add UTs for publish and invoke (#477)

* Refactor publish, invoke and add tests

* remove process from interface

* address review comments

* rename invoke methods.
This commit is contained in:
Mukundan Sundararajan 2020-10-09 19:03:39 -07:00 committed by GitHub
parent 24ca35d78b
commit e2e592943f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 348 additions and 48 deletions

View File

@ -6,10 +6,8 @@
package cmd
import (
"fmt"
"os"
"github.com/dapr/cli/pkg/invoke"
"github.com/dapr/cli/pkg/print"
"github.com/spf13/cobra"
)
@ -24,16 +22,11 @@ var InvokeCmd = &cobra.Command{
Use: "invoke",
Short: "Invokes a Dapr app with an optional payload (deprecated, use invokePost)",
Run: func(cmd *cobra.Command, args []string) {
response, err := invoke.Post(invokeAppID, invokeAppMethod, invokePayload)
err := invokePost(invokeAppID, invokeAppMethod, invokePayload)
if err != nil {
print.FailureStatusEvent(os.Stdout, fmt.Sprintf("Error invoking app %s: %s", invokeAppID, err))
return
// exit with error
os.Exit(1)
}
if response != "" {
fmt.Println(response)
}
print.SuccessStatusEvent(os.Stdout, "App invoked successfully")
},
}

View File

@ -4,8 +4,8 @@ import (
"fmt"
"os"
"github.com/dapr/cli/pkg/invoke"
"github.com/dapr/cli/pkg/print"
"github.com/dapr/cli/pkg/standalone"
"github.com/spf13/cobra"
)
@ -14,11 +14,12 @@ var invokeGetCmd = &cobra.Command{
Use: "invokeGet",
Short: "Issue HTTP GET to Dapr app",
Run: func(cmd *cobra.Command, args []string) {
response, err := invoke.Get(invokeAppID, invokeAppMethod)
client := standalone.NewClient()
response, err := client.InvokeGet(invokeAppID, invokeAppMethod)
if err != nil {
print.FailureStatusEvent(os.Stdout, fmt.Sprintf("Error invoking app %s: %s", invokeAppID, err))
return
print.FailureStatusEvent(os.Stdout, fmt.Sprintf("error invoking app %s: %s", invokeAppID, err))
// exit with error
os.Exit(1)
}
if response != "" {

View File

@ -9,8 +9,8 @@ import (
"fmt"
"os"
"github.com/dapr/cli/pkg/invoke"
"github.com/dapr/cli/pkg/print"
"github.com/dapr/cli/pkg/standalone"
"github.com/spf13/cobra"
)
@ -18,21 +18,30 @@ var invokePostCmd = &cobra.Command{
Use: "invokePost",
Short: "Issue HTTP POST to Dapr app with an optional payload",
Run: func(cmd *cobra.Command, args []string) {
response, err := invoke.Post(invokeAppID, invokeAppMethod, invokePayload)
err := invokePost(invokeAppID, invokeAppMethod, invokePayload)
if err != nil {
print.FailureStatusEvent(os.Stdout, fmt.Sprintf("Error invoking app %s: %s", invokeAppID, err))
return
// exit with error
os.Exit(1)
}
if response != "" {
fmt.Println(response)
}
print.SuccessStatusEvent(os.Stdout, fmt.Sprintf("HTTP Post to method %s invoked successfully", invokeAppMethod))
},
}
func invokePost(invokeAppID, invokeAppMethod, invokePayload string) error {
client := standalone.NewClient()
response, err := client.InvokePost(invokeAppID, invokeAppMethod, invokePayload)
if err != nil {
er := fmt.Errorf("error invoking app %s: %s", invokeAppID, err)
print.FailureStatusEvent(os.Stdout, er.Error())
return er
}
if response != "" {
fmt.Println(response)
}
return nil
}
func init() {
invokePostCmd.Flags().StringVarP(&invokeAppID, "app-id", "a", "", "the app id to invoke")
invokePostCmd.Flags().StringVarP(&invokeAppMethod, "method", "m", "", "the method to invoke")

View File

@ -10,7 +10,7 @@ import (
"os"
"github.com/dapr/cli/pkg/print"
"github.com/dapr/cli/pkg/publish"
"github.com/dapr/cli/pkg/standalone"
"github.com/spf13/cobra"
)
@ -24,10 +24,11 @@ var PublishCmd = &cobra.Command{
Use: "publish",
Short: "Publish an event to multiple consumers",
Run: func(cmd *cobra.Command, args []string) {
err := publish.SendPayloadToTopic(publishTopic, publishPayload, pubsubName)
client := standalone.NewClient()
err := client.Publish(publishTopic, publishPayload, pubsubName)
if err != nil {
print.FailureStatusEvent(os.Stdout, fmt.Sprintf("Error publishing topic %s: %s", publishTopic, err))
return
os.Exit(1)
}
print.SuccessStatusEvent(os.Stdout, "Event published successfully")

View File

@ -0,0 +1,19 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
// ------------------------------------------------------------
package metadata
import (
"fmt"
"testing"
"github.com/dapr/cli/pkg/api"
"github.com/stretchr/testify/assert"
)
func TestMakeMetadataGetEndpoint(t *testing.T) {
actual := makeMetadataGetEndpoint(9999)
assert.Equal(t, fmt.Sprintf("http://127.0.0.1:9999/v%s/metadata", api.RuntimeAPIVersion), actual, "expected strings to match")
}

31
pkg/standalone/client.go Normal file
View File

@ -0,0 +1,31 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
// ------------------------------------------------------------
package standalone
type DaprProcess interface {
List() ([]ListOutput, error)
}
type daprProcess struct {
}
// Client is the interface the wraps all the methods exposed by the Dapr CLI.
type Client interface {
// InvokeGet is used to invoke a method on a Dapr application with GET verb.
InvokeGet(appID, method string) (string, error)
// InvokePost is used to invoke a method on a Dapr application with POST verb.
InvokePost(appID, method, payload string) (string, error)
// Publish is used to publish event to a topic in a pubsub.
Publish(topic, payload, pubsubName string) error
}
type Standalone struct {
process DaprProcess
}
func NewClient() Client {
return &Standalone{process: &daprProcess{}}
}

View File

@ -3,7 +3,7 @@
// Licensed under the MIT License.
// ------------------------------------------------------------
package invoke
package standalone
import (
"bytes"
@ -12,12 +12,11 @@ import (
"net/http"
"github.com/dapr/cli/pkg/api"
"github.com/dapr/cli/pkg/standalone"
)
// Get invokes the application via HTTP GET.
func Get(appID, method string) (string, error) {
list, err := standalone.List()
// InvokeGet invokes the application via HTTP GET.
func (s *Standalone) InvokeGet(appID, method string) (string, error) {
list, err := s.process.List()
if err != nil {
return "", err
}
@ -38,9 +37,9 @@ func Get(appID, method string) (string, error) {
return "", fmt.Errorf("app ID %s not found", appID)
}
// Post invokes the application via HTTP POST.
func Post(appID, method, payload string) (string, error) {
list, err := standalone.List()
// InvokePost invokes the application via HTTP POST.
func (s *Standalone) InvokePost(appID, method, payload string) (string, error) {
list, err := s.process.List()
if err != nil {
return "", err
}
@ -62,7 +61,7 @@ func Post(appID, method, payload string) (string, error) {
return "", fmt.Errorf("app ID %s not found", appID)
}
func makeEndpoint(lo standalone.ListOutput, method string) string {
func makeEndpoint(lo ListOutput, method string) string {
return fmt.Sprintf("http://127.0.0.1:%s/v%s/invoke/%s/method/%s", fmt.Sprintf("%v", lo.HTTPPort), api.RuntimeAPIVersion, lo.AppID, method)
}

View File

@ -0,0 +1,107 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
// ------------------------------------------------------------
package standalone
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestInvoke(t *testing.T) {
testCases := []struct {
name string
errorExpected bool
errString string
appID string
method string
lo ListOutput
listErr error
expectedPath string
postResponse string
resp string
}{
{
name: "list apps error",
errorExpected: true,
errString: assert.AnError.Error(),
listErr: assert.AnError,
},
{
name: "appID not found",
errorExpected: true,
appID: "invalid",
errString: "app ID invalid not found",
lo: ListOutput{
AppID: "testapp",
},
},
{
name: "appID found successful invoke empty response",
appID: "testapp",
method: "test",
lo: ListOutput{
AppID: "testapp",
},
},
{
name: "appID found successful invoke",
appID: "testapp",
method: "test",
lo: ListOutput{
AppID: "testapp",
},
expectedPath: "/v1.0/invoke/testapp/method/test",
postResponse: "test payload",
resp: "successful invoke",
},
}
for _, tc := range testCases {
t.Run(tc.name+" get", func(t *testing.T) {
ts, port := getTestServer(tc.expectedPath, tc.resp)
ts.Start()
defer ts.Close()
tc.lo.HTTPPort = port
client := &Standalone{
process: &mockDaprProcess{
Lo: []ListOutput{
tc.lo,
},
Err: tc.listErr,
},
}
res, err := client.InvokeGet(tc.appID, tc.method)
if tc.errorExpected {
assert.Error(t, err, "expected an error")
assert.Equal(t, tc.errString, err.Error(), "expected error strings to match")
} else {
assert.NoError(t, err, "expected no error")
assert.Equal(t, tc.resp, res, "expected response to match")
}
})
t.Run(tc.name+" post", func(t *testing.T) {
ts, port := getTestServer(tc.expectedPath, tc.resp)
ts.Start()
defer ts.Close()
tc.lo.HTTPPort = port
client := &Standalone{
process: &mockDaprProcess{
Lo: []ListOutput{tc.lo},
Err: tc.listErr,
},
}
res, err := client.InvokePost(tc.appID, tc.method, "test payload")
if tc.errorExpected {
assert.Error(t, err, "expected an error")
assert.Equal(t, tc.errString, err.Error(), "expected error strings to match")
} else {
assert.NoError(t, err, "expected no error")
assert.Equal(t, tc.postResponse, res, "expected response to match")
}
})
}
}

View File

@ -41,6 +41,10 @@ type runData struct {
appCmd string
}
func (d *daprProcess) List() ([]ListOutput, error) {
return List()
}
// List outputs all the applications.
func List() ([]ListOutput, error) {
list := []ListOutput{}

View File

@ -3,7 +3,7 @@
// Licensed under the MIT License.
// ------------------------------------------------------------
package publish
package standalone
import (
"bytes"
@ -12,11 +12,10 @@ import (
"net/http"
"github.com/dapr/cli/pkg/api"
"github.com/dapr/cli/pkg/standalone"
)
// SendPayloadToTopic publishes the topic.
func SendPayloadToTopic(topic, payload, pubsubName string) error {
// Publish publishes payload to topic in pubsub referenced by pubsubName.
func (s *Standalone) Publish(topic, payload, pubsubName string) error {
if topic == "" {
return errors.New("topic is missing")
}
@ -24,7 +23,7 @@ func SendPayloadToTopic(topic, payload, pubsubName string) error {
return errors.New("pubsubName is missing")
}
l, err := standalone.List()
l, err := s.process.List()
if err != nil {
return err
}
@ -43,19 +42,18 @@ func SendPayloadToTopic(topic, payload, pubsubName string) error {
url := fmt.Sprintf("http://localhost:%s/v%s/publish/%s/%s", fmt.Sprintf("%v", daprHTTPPort), api.RuntimeAPIVersion, pubsubName, topic)
// nolint: gosec
r, err := http.Post(url, "application/json", bytes.NewBuffer(b))
if r != nil {
defer r.Body.Close()
}
if err != nil {
return err
}
defer r.Body.Close()
if r.StatusCode != http.StatusOK {
return fmt.Errorf("unexpected status code %d on publishing to %s in %s", r.StatusCode, topic, pubsubName)
}
return nil
}
func getDaprHTTPPort(list []standalone.ListOutput) (int, error) {
func getDaprHTTPPort(list []ListOutput) (int, error) {
for i := 0; i < len(list); i++ {
if list[i].AppID != "" {
return list[i].HTTPPort, nil

View File

@ -0,0 +1,96 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
// ------------------------------------------------------------
package standalone
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestPublish(t *testing.T) {
testCases := []struct {
name string
pubsubName string
payload string
topic string
lo ListOutput
listErr error
expectedPath string
postResponse string
resp string
errorExpected bool
errString string
}{
{
name: "test empty topic",
payload: "test",
pubsubName: "test",
errString: "topic is missing",
errorExpected: true,
},
{
name: "test empty pubsubName",
payload: "test",
topic: "test",
errString: "pubsubName is missing",
errorExpected: true,
},
{
name: "test list error",
payload: "test",
topic: "test",
pubsubName: "test",
listErr: assert.AnError,
errString: assert.AnError.Error(),
errorExpected: true,
},
{
name: "test empty appID in list output",
payload: "test",
topic: "test",
pubsubName: "test",
lo: ListOutput{
// empty appID
Command: "test",
},
errString: "couldn't find a running Dapr instance",
errorExpected: true,
},
{
name: "successful call",
pubsubName: "testPubsubName",
topic: "testTopic",
payload: "test payload",
expectedPath: "/v1.0/publish/testPubsubName/testTopic",
postResponse: "test payload",
lo: ListOutput{
AppID: "notempty",
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
ts, port := getTestServer(tc.expectedPath, tc.resp)
ts.Start()
defer ts.Close()
tc.lo.HTTPPort = port
client := &Standalone{
process: &mockDaprProcess{
Lo: []ListOutput{tc.lo},
Err: tc.listErr,
},
}
err := client.Publish(tc.topic, tc.payload, tc.pubsubName)
if tc.errorExpected {
assert.Error(t, err, "expected an error")
assert.Equal(t, tc.errString, err.Error(), "expected error strings to match")
} else {
assert.NoError(t, err, "expected no error")
}
})
}
}

View File

@ -0,0 +1,42 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
// ------------------------------------------------------------
package standalone
import (
"bytes"
"net"
"net/http"
"net/http/httptest"
)
type mockDaprProcess struct {
Lo []ListOutput
Err error
}
func (m *mockDaprProcess) List() ([]ListOutput, error) {
return m.Lo, m.Err
}
func getTestServer(expectedPath, resp string) (*httptest.Server, int) {
ts := httptest.NewUnstartedServer(http.HandlerFunc(func(
w http.ResponseWriter, r *http.Request) {
if r.RequestURI != expectedPath {
w.WriteHeader(http.StatusInternalServerError)
return
}
if r.Method == http.MethodPost {
buf := new(bytes.Buffer)
buf.ReadFrom(r.Body)
w.Write(buf.Bytes())
} else if r.Method == http.MethodGet {
w.Write([]byte(resp))
}
}))
return ts, ts.Listener.Addr().(*net.TCPAddr).Port
}