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
4 changes: 2 additions & 2 deletions docs/content/docs/guide/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -454,8 +454,8 @@ The payload is the JSON content of the webhook request, The headers file support
tkn pac cel -b <body.json> -H <headers.txt>
```

* `-b, --body`: Path to JSON body file (webhook payload)
* `-H, --headers`: Path to headers file (plain text, JSON, or gosmee script)
* `-b, --body`: Path to JSON body file (webhook payload) **[required]**
* `-H, --headers`: Path to headers file (plain text, JSON, or gosmee script) **[required]**
* `-p, --provider`: Provider (auto, github, gitlab, bitbucket-cloud, bitbucket-datacenter, gitea)

#### Interactive Mode
Expand Down
8 changes: 6 additions & 2 deletions pkg/cmd/tknpac/cel/cel.go
Original file line number Diff line number Diff line change
Expand Up @@ -736,9 +736,13 @@ that would be used in PipelineRun configurations.`,
Annotations: map[string]string{"commandType": "main"},
}

cmd.Flags().StringVarP(&bodyFile, bodyFileFlag, "b", "", "path to JSON body file")
cmd.Flags().StringVarP(&headersFile, headersFileFlag, "H", "", "path to headers file (JSON, HTTP format, or gosmee-generated shell script)")
cmd.Flags().StringVarP(&bodyFile, bodyFileFlag, "b", "", "path to JSON body file (required)")
cmd.Flags().StringVarP(&headersFile, headersFileFlag, "H", "", "path to headers file (required, JSON, HTTP format, or gosmee-generated shell script)")
cmd.Flags().StringVarP(&provider, providerFlag, "p", "auto", "payload provider (auto, github, gitlab, bitbucket-cloud, bitbucket-datacenter, gitea)")
cmd.Flags().StringVarP(&githubToken, githubTokenFlag, "t", "", "GitHub personal access token for API enrichment (enables full event processing)")
// Mark body and headers flags as required.
// These errors are safe to ignore as the flags are defined above.
_ = cmd.MarkFlagRequired(bodyFileFlag)
_ = cmd.MarkFlagRequired(headersFileFlag)
return cmd
}
149 changes: 97 additions & 52 deletions pkg/cmd/tknpac/cel/cel_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,38 @@ const testPullRequestPayload = `{
}
}`

// minimalGitHubPRPayload returns a minimal valid GitHub pull request webhook payload
// for testing purposes.
const minimalGitHubPRPayload = `{
"action": "opened",
"pull_request": {
"number": 1,
"title": "Test",
"head": {
"ref": "test-branch",
"sha": "abc123",
"repo": {
"html_url": "https://github.com/test/test"
}
},
"base": {
"ref": "main",
"repo": {
"html_url": "https://github.com/test/test"
}
},
"html_url": "https://github.com/test/test/pull/1",
"labels": []
},
"repository": {
"name": "test",
"owner": {"login": "test"},
"html_url": "https://github.com/test/test",
"default_branch": "main"
},
"sender": {"login": "test"}
}`

func newIOStream() (*cli.IOStreams, *bytes.Buffer, *bytes.Buffer) {
in := &bytes.Buffer{}
out := &bytes.Buffer{}
Expand All @@ -82,6 +114,38 @@ func newIOStream() (*cli.IOStreams, *bytes.Buffer, *bytes.Buffer) {
}, out, errOut
}

// setupCELTest creates temporary files with the given body and headers content,
// executes the CEL command, and returns the error (if any).
func setupCELTest(t *testing.T, bodyContent, headersContent, provider string) error {
t.Helper()

tempDir := fs.NewDir(t, "cel-test")
defer tempDir.Remove()

// Create body file
bodyFile := tempDir.Join("body.json")
err := os.WriteFile(bodyFile, []byte(bodyContent), 0o600)
assert.NilError(t, err)

// Create headers file
headersFile := tempDir.Join("headers.txt")
err = os.WriteFile(headersFile, []byte(headersContent), 0o600)
assert.NilError(t, err)

ioStreams, _, _ := newIOStream()
// Write empty input to stdin to exit immediately
ioStreams.In = io.NopCloser(strings.NewReader("\n"))

cmd := Command(ioStreams)
cmd.SetArgs([]string{
"--provider", provider,
"--body", bodyFile,
"--headers", headersFile,
})

return cmd.Execute()
}

func TestParseHTTPHeaders(t *testing.T) {
tests := []struct {
name string
Expand Down Expand Up @@ -971,7 +1035,7 @@ X-GitHub-Event: pull_request`
bodyContent: pullRequestPayload,
headersContent: headers,
provider: "github",
wantErr: false, // Interactive mode should exit gracefully on EOF
wantErr: false,
wantOutContains: []string{
"Important Notice",
"Provider: github",
Expand All @@ -981,14 +1045,14 @@ X-GitHub-Event: pull_request`
name: "no files provided",
provider: "github",
wantErr: true,
wantErrContains: "unknown X-Github-Event",
wantErrContains: "required flag(s) \"body\", \"headers\" not set",
},
{
name: "no body file",
headersContent: headers,
provider: "github",
wantErr: true,
wantErrContains: "unexpected end of JSON input",
wantErrContains: "required flag(s) \"body\" not set",
},
{
name: "unsupported provider",
Expand All @@ -998,14 +1062,6 @@ X-GitHub-Event: pull_request`
wantErr: true,
wantErrContains: "unsupported provider invalid-provider",
},
{
name: "invalid json body",
bodyContent: `{"invalid": json}`,
headersContent: headers,
provider: "github",
wantErr: true,
wantErrContains: "invalid character",
},
}

for _, tt := range tests {
Expand Down Expand Up @@ -1081,77 +1137,51 @@ X-GitHub-Event: pull_request`
}
}

func TestCommandFileHandling(t *testing.T) {
func TestHeaderFormats(t *testing.T) {
tests := []struct {
name string
bodyContent string
headersContent string
isJSON bool
wantErr bool
wantErrContains string
}{
{
name: "plain text headers",
name: "plain text headers",
bodyContent: minimalGitHubPRPayload,
headersContent: `Accept: */*
Content-Type: application/json
X-GitHub-Event: pull_request`,
isJSON: false,
wantErr: true,
wantErrContains: "unexpected end of JSON input",
wantErr: false,
},
{
name: "json headers",
name: "json headers",
bodyContent: minimalGitHubPRPayload,
headersContent: `{
"Accept": "*/*",
"Content-Type": "application/json",
"X-GitHub-Event": "pull_request"
}`,
isJSON: true,
wantErr: true,
wantErrContains: "unexpected end of JSON input",
wantErr: false,
},
{
name: "invalid json headers",
bodyContent: minimalGitHubPRPayload,
headersContent: `{"invalid": json}`,
isJSON: true,
wantErr: true,
wantErrContains: "invalid character",
},
{
name: "empty headers file",
bodyContent: minimalGitHubPRPayload,
headersContent: "",
wantErr: true,
wantErrContains: "unknown X-Github-Event",
},
{
name: "gosmee script",
headersContent: `#!/usr/bin/env bash
set -euxfo pipefail
curl -sSi -H "Content-Type: application/json" -H "X-GitHub-Event: pull_request" -H "User-Agent: GitHub-Hookshot/2d5e4d4" -X POST -d @payload.json http://localhost:8080`,
wantErr: true,
wantErrContains: "unexpected end of JSON input", // Still expects body file
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tempDir := fs.NewDir(t, "cel-headers-test")
defer tempDir.Remove()

headersFile := tempDir.Join("headers.txt")
err := os.WriteFile(headersFile, []byte(tt.headersContent), 0o600)
assert.NilError(t, err)

ioStreams, _, _ := newIOStream()
// Write empty input to stdin to exit immediately
ioStreams.In = io.NopCloser(strings.NewReader("\n"))

cmd := Command(ioStreams)
cmd.SetArgs([]string{
"--provider", "github",
"--headers", headersFile,
})

err = cmd.Execute()
err := setupCELTest(t, tt.bodyContent, tt.headersContent, "github")

if tt.wantErr {
assert.Assert(t, err != nil)
Expand All @@ -1174,12 +1204,12 @@ func TestCommandFlags(t *testing.T) {
bodyFlag := cmd.Flags().Lookup("body")
assert.Assert(t, bodyFlag != nil)
assert.Equal(t, bodyFlag.Shorthand, "b")
assert.Equal(t, bodyFlag.Usage, "path to JSON body file")
assert.Equal(t, bodyFlag.Usage, "path to JSON body file (required)")

headersFlag := cmd.Flags().Lookup("headers")
assert.Assert(t, headersFlag != nil)
assert.Equal(t, headersFlag.Shorthand, "H")
assert.Equal(t, headersFlag.Usage, "path to headers file (JSON, HTTP format, or gosmee-generated shell script)")
assert.Equal(t, headersFlag.Usage, "path to headers file (required, JSON, HTTP format, or gosmee-generated shell script)")

providerFlag := cmd.Flags().Lookup("provider")
assert.Assert(t, providerFlag != nil)
Expand Down Expand Up @@ -1222,10 +1252,22 @@ func TestInvalidFiles(t *testing.T) {
tempDir := fs.NewDir(t, "cel-invalid-test")
defer tempDir.Remove()

// Create valid body file (unless we're testing body file errors)
validBodyFile := tempDir.Join("valid-body.json")
err := os.WriteFile(validBodyFile, []byte(minimalGitHubPRPayload), 0o600)
assert.NilError(t, err)

// Create valid headers file (unless we're testing headers file errors)
validHeadersFile := tempDir.Join("valid-headers.txt")
validHeadersContent := `X-GitHub-Event: pull_request
Content-Type: application/json`
err = os.WriteFile(validHeadersFile, []byte(validHeadersContent), 0o600)
assert.NilError(t, err)

var filePath string
if tt.createFile {
filePath = tempDir.Join("test-file")
err := os.WriteFile(filePath, []byte(tt.fileContent), 0o600)
err = os.WriteFile(filePath, []byte(tt.fileContent), 0o600)
assert.NilError(t, err)
} else {
filePath = filepath.Join(tempDir.Path(), "non-existent-file")
Expand All @@ -1237,15 +1279,18 @@ func TestInvalidFiles(t *testing.T) {
cmd := Command(ioStreams)
args := []string{"--provider", "github"}

// Add both body and headers flags, using the invalid one for the test target
if tt.fileFlag == "body" {
args = append(args, "--body", filePath)
args = append(args, "--headers", validHeadersFile)
} else {
args = append(args, "--body", validBodyFile)
args = append(args, "--headers", filePath)
}

cmd.SetArgs(args)

err := cmd.Execute()
err = cmd.Execute()
assert.Assert(t, err != nil)
assert.Assert(t, strings.Contains(err.Error(), tt.wantErrContains),
"error %q should contain %q", err.Error(), tt.wantErrContains)
Expand Down