Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 9 additions & 18 deletions cmd/invoke.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,15 @@ func runInvoke(cmd *cobra.Command, _ []string, newClient ClientFactory) (err err
return fmt.Errorf("--extension flag is only valid with cloudevent format")
}
}
// If --file was specified, read the file contents into cfg.Data now
// that all config (including interactive --confirm prompts) is finalized.
if cfg.File != "" {
content, err := os.ReadFile(cfg.File)
if err != nil {
return err
}
cfg.Data = content
}
Comment thread
Elvand-Lie marked this conversation as resolved.

// Client instance from env vars, flags, args and user prompts (if --confirm)
client, done := newClient(ClientConfig{Verbose: cfg.Verbose, InsecureSkipVerify: cfg.Insecure})
Expand All @@ -197,15 +206,6 @@ func runInvoke(cmd *cobra.Command, _ []string, newClient ClientFactory) (err err
Extensions: cfg.extensionsMap(),
}

// If --file was specified, use its content for message data
if cfg.File != "" {
content, err := os.ReadFile(cfg.File)
if err != nil {
return err
}
m.Data = content
}

// Invoke
metadata, body, err := client.Invoke(cmd.Context(), cfg.Path, cfg.Target, m)
if err != nil {
Expand Down Expand Up @@ -276,15 +276,6 @@ func newInvokeConfig() (cfg invokeConfig, err error) {
Extensions: viper.GetStringSlice("extension"),
}

// If file was passed, read it in as data
if cfg.File != "" {
b, err := os.ReadFile(cfg.File)
if err != nil {
return cfg, err
}
cfg.Data = b
}

switch strings.ToLower(cfg.Format) {
case "cloudevent", "cloudevents":
cfg.Format = "cloudevent"
Expand Down
106 changes: 106 additions & 0 deletions cmd/invoke_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@ import (
"context"
"errors"
"fmt"
"io"
"net"
"net/http"
"os"
"path/filepath"
"strings"
"sync"
"sync/atomic"
"testing"
"time"
Expand Down Expand Up @@ -78,3 +82,105 @@ func TestInvoke(t *testing.T) {
t.Fatal("function was not invoked")
}
}

// TestInvoke_FileFlag ensures that when --file is provided, the file's
// contents are read and sent as the invocation data.
func TestInvoke_FileFlag(t *testing.T) {
root := FromTempDirectory(t)

// Create a test function to be invoked
if _, err := fn.New().Init(fn.Function{Runtime: "go", Root: root}); err != nil {
t.Fatal(err)
}

// Create a test data file
testData := "custom file content for invoke test"
testFile := filepath.Join(root, "testdata.txt")
if err := os.WriteFile(testFile, []byte(testData), 0644); err != nil {
t.Fatal(err)
}

// Track what data is received by the mock server
var (
receivedData []byte
mu sync.Mutex
)

// Mock Runner: starts a service which captures request body
runner := mock.NewRunner()
runner.RunFn = func(ctx context.Context, f fn.Function, _ string, _ time.Duration) (job *fn.Job, err error) {
var (
l net.Listener
h = http.NewServeMux()
s = http.Server{Handler: h}
)
if l, err = net.Listen("tcp4", "127.0.0.1:"); err != nil {
t.Fatal(err)
}
h.HandleFunc("/", func(res http.ResponseWriter, req *http.Request) {
body, _ := io.ReadAll(req.Body)
mu.Lock()
receivedData = body
mu.Unlock()
_, _ = res.Write([]byte("ok"))
Comment thread
Elvand-Lie marked this conversation as resolved.
})
go func() {
if err = s.Serve(l); err != nil && !errors.Is(err, http.ErrServerClosed) {
fmt.Fprintf(os.Stderr, "error serving: %v", err)
}
}()
host, port, _ := net.SplitHostPort(l.Addr().String())
errs := make(chan error, 10)
stop := func() error { _ = s.Close(); return nil }
return fn.NewJob(f, host, port, errs, stop, false)
}

// Run the mock http service
f, err := fn.NewFunction(root)
if err != nil {
t.Fatal(err)
}
client := fn.New(fn.WithRunner(runner))
job, err := client.Run(t.Context(), f)
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = job.Stop() })

// Test that the invoke command reads and sends the file content
cmd := NewInvokeCmd(NewClient)
cmd.SetArgs([]string{"--file", testFile, "--content-type", "text/plain"})

if err := cmd.Execute(); err != nil {
t.Fatal(err)
}

mu.Lock()
got := string(receivedData)
mu.Unlock()
if got != testData {
t.Fatalf("expected file content %q to be sent, got %q", testData, got)
}
}

// TestInvoke_FileFlagNonExistent ensures that specifying a non-existent
// file via --file returns an appropriate error.
func TestInvoke_FileFlagNonExistent(t *testing.T) {
root := FromTempDirectory(t)

// Create a test function
if _, err := fn.New().Init(fn.Function{Runtime: "go", Root: root}); err != nil {
t.Fatal(err)
}

cmd := NewInvokeCmd(NewClient)
cmd.SetArgs([]string{"--file", "nonexistent_file.txt"})
err := cmd.Execute()

if err == nil {
t.Fatal("invoking with a nonexistent file should error")
}
if !strings.Contains(err.Error(), "nonexistent_file.txt") {
t.Fatalf("error should mention the missing file, got: %v", err)
}
}
Loading