From c84e84f962406e94369c18b60198592eba52c589 Mon Sep 17 00:00:00 2001 From: Akshay Pant Date: Fri, 5 Dec 2025 15:15:37 +0530 Subject: [PATCH] fix(cel): add nil checks to prevent panics in webhook parsers Add comprehensive nil pointer checks in PopulateEvent methods for webhook parsers to prevent panics when accessing nested fields that may be nil or missing from webhook payloads. Jira: https://issues.redhat.com/browse/SRVKP-9396 Signed-off-by: Akshay Pant --- pkg/cmd/tknpac/cel/parser.go | 272 ++++++++++++------ pkg/cmd/tknpac/cel/parser_test.go | 448 ++++++++++++++++++++++++++++++ 2 files changed, 629 insertions(+), 91 deletions(-) create mode 100644 pkg/cmd/tknpac/cel/parser_test.go diff --git a/pkg/cmd/tknpac/cel/parser.go b/pkg/cmd/tknpac/cel/parser.go index c8e9981e1c..c040cde825 100644 --- a/pkg/cmd/tknpac/cel/parser.go +++ b/pkg/cmd/tknpac/cel/parser.go @@ -136,58 +136,103 @@ func (p *GitHubParser) ParsePayload(eventType string, body []byte) (any, error) func (p *GitHubParser) PopulateEvent(event *info.Event, parsedEvent any) error { switch gitEvent := parsedEvent.(type) { case *github.PullRequestEvent: - event.Organization = gitEvent.GetRepo().GetOwner().GetLogin() - event.Repository = gitEvent.GetRepo().GetName() - event.Sender = gitEvent.GetSender().GetLogin() - event.URL = gitEvent.GetRepo().GetHTMLURL() - event.SHA = gitEvent.GetPullRequest().GetHead().GetSHA() - event.SHAURL = fmt.Sprintf("%s/commit/%s", gitEvent.GetPullRequest().GetHTMLURL(), gitEvent.GetPullRequest().GetHead().GetSHA()) - event.PullRequestTitle = gitEvent.GetPullRequest().GetTitle() - event.HeadBranch = gitEvent.GetPullRequest().GetHead().GetRef() - event.BaseBranch = gitEvent.GetPullRequest().GetBase().GetRef() - event.HeadURL = gitEvent.GetPullRequest().GetHead().GetRepo().GetHTMLURL() - event.BaseURL = gitEvent.GetPullRequest().GetBase().GetRepo().GetHTMLURL() - event.DefaultBranch = gitEvent.GetRepo().GetDefaultBranch() - event.PullRequestNumber = gitEvent.GetPullRequest().GetNumber() - event.TriggerTarget = triggertype.PullRequest - for _, label := range gitEvent.GetPullRequest().Labels { - event.PullRequestLabel = append(event.PullRequestLabel, label.GetName()) + if repo := gitEvent.GetRepo(); repo != nil { + if owner := repo.GetOwner(); owner != nil { + event.Organization = owner.GetLogin() + } + event.Repository = repo.GetName() + event.URL = repo.GetHTMLURL() + event.DefaultBranch = repo.GetDefaultBranch() + } + if pr := gitEvent.GetPullRequest(); pr != nil { + if head := pr.GetHead(); head != nil { + event.SHA = head.GetSHA() + if pr.GetHTMLURL() != "" && head.GetSHA() != "" { + event.SHAURL = fmt.Sprintf("%s/commit/%s", pr.GetHTMLURL(), head.GetSHA()) + } + event.HeadBranch = head.GetRef() + if headRepo := head.GetRepo(); headRepo != nil { + event.HeadURL = headRepo.GetHTMLURL() + } + } + if base := pr.GetBase(); base != nil { + event.BaseBranch = base.GetRef() + if baseRepo := base.GetRepo(); baseRepo != nil { + event.BaseURL = baseRepo.GetHTMLURL() + } + } + event.PullRequestTitle = pr.GetTitle() + event.PullRequestNumber = pr.GetNumber() + for _, label := range pr.Labels { + if label != nil { + event.PullRequestLabel = append(event.PullRequestLabel, label.GetName()) + } + } } + if sender := gitEvent.GetSender(); sender != nil { + event.Sender = sender.GetLogin() + } + event.TriggerTarget = triggertype.PullRequest case *github.PushEvent: - event.Organization = gitEvent.GetRepo().GetOwner().GetLogin() - event.Repository = gitEvent.GetRepo().GetName() - event.Sender = gitEvent.GetSender().GetLogin() - event.URL = gitEvent.GetRepo().GetHTMLURL() - event.SHA = gitEvent.GetHeadCommit().GetID() - event.SHAURL = gitEvent.GetHeadCommit().GetURL() - event.SHATitle = gitEvent.GetHeadCommit().GetMessage() + if repo := gitEvent.GetRepo(); repo != nil { + if owner := repo.GetOwner(); owner != nil { + event.Organization = owner.GetLogin() + } + event.Repository = repo.GetName() + event.URL = repo.GetHTMLURL() + event.DefaultBranch = repo.GetDefaultBranch() + event.HeadURL = repo.GetHTMLURL() + event.BaseURL = repo.GetHTMLURL() + } + if headCommit := gitEvent.GetHeadCommit(); headCommit != nil { + event.SHA = headCommit.GetID() + event.SHAURL = headCommit.GetURL() + event.SHATitle = headCommit.GetMessage() + } + if sender := gitEvent.GetSender(); sender != nil { + event.Sender = sender.GetLogin() + } event.HeadBranch = gitEvent.GetRef() event.BaseBranch = gitEvent.GetRef() - event.HeadURL = gitEvent.GetRepo().GetHTMLURL() - event.BaseURL = gitEvent.GetRepo().GetHTMLURL() - event.DefaultBranch = gitEvent.GetRepo().GetDefaultBranch() event.TriggerTarget = triggertype.Push case *github.IssueCommentEvent: - if gitEvent.GetIssue().GetPullRequestLinks() == nil { + issue := gitEvent.GetIssue() + if issue == nil || issue.GetPullRequestLinks() == nil { return fmt.Errorf("issue comment is not from a pull request") } - event.Organization = gitEvent.GetRepo().GetOwner().GetLogin() - event.Repository = gitEvent.GetRepo().GetName() - event.Sender = gitEvent.GetSender().GetLogin() - event.URL = gitEvent.GetRepo().GetHTMLURL() - event.DefaultBranch = gitEvent.GetRepo().GetDefaultBranch() + if repo := gitEvent.GetRepo(); repo != nil { + if owner := repo.GetOwner(); owner != nil { + event.Organization = owner.GetLogin() + } + event.Repository = repo.GetName() + event.URL = repo.GetHTMLURL() + event.DefaultBranch = repo.GetDefaultBranch() + } + if sender := gitEvent.GetSender(); sender != nil { + event.Sender = sender.GetLogin() + } event.TriggerTarget = triggertype.PullRequest - event.TriggerComment = gitEvent.GetComment().GetBody() - event.PullRequestNumber = gitEvent.GetIssue().GetNumber() + if comment := gitEvent.GetComment(); comment != nil { + event.TriggerComment = comment.GetBody() + } + event.PullRequestNumber = issue.GetNumber() case *github.CommitCommentEvent: - event.Organization = gitEvent.GetRepo().GetOwner().GetLogin() - event.Repository = gitEvent.GetRepo().GetName() - event.Sender = gitEvent.GetSender().GetLogin() - event.URL = gitEvent.GetRepo().GetHTMLURL() - event.SHA = gitEvent.GetComment().GetCommitID() - event.DefaultBranch = gitEvent.GetRepo().GetDefaultBranch() + if repo := gitEvent.GetRepo(); repo != nil { + if owner := repo.GetOwner(); owner != nil { + event.Organization = owner.GetLogin() + } + event.Repository = repo.GetName() + event.URL = repo.GetHTMLURL() + event.DefaultBranch = repo.GetDefaultBranch() + } + if comment := gitEvent.GetComment(); comment != nil { + event.SHA = comment.GetCommitID() + event.TriggerComment = comment.GetBody() + } + if sender := gitEvent.GetSender(); sender != nil { + event.Sender = sender.GetLogin() + } event.TriggerTarget = triggertype.Push - event.TriggerComment = gitEvent.GetComment().GetBody() default: return fmt.Errorf("unsupported GitHub event type: %T", gitEvent) } @@ -242,8 +287,11 @@ func (p *GitLabParser) PopulateEvent(event *info.Event, parsedEvent any) error { case *gitlab.MergeEvent: event.Organization = extractOrgFromPath(gitEvent.Project.PathWithNamespace) event.Repository = extractRepoFromPath(gitEvent.Project.PathWithNamespace) - event.Sender = gitEvent.User.Username event.URL = gitEvent.Project.WebURL + event.DefaultBranch = gitEvent.Project.DefaultBranch + if gitEvent.User != nil { + event.Sender = gitEvent.User.Username + } event.SHA = gitEvent.ObjectAttributes.LastCommit.ID event.SHAURL = gitEvent.ObjectAttributes.LastCommit.URL event.SHATitle = gitEvent.ObjectAttributes.LastCommit.Title @@ -257,7 +305,6 @@ func (p *GitLabParser) PopulateEvent(event *info.Event, parsedEvent any) error { } event.PullRequestNumber = gitEvent.ObjectAttributes.IID event.PullRequestTitle = gitEvent.ObjectAttributes.Title - event.DefaultBranch = gitEvent.Project.DefaultBranch event.TriggerTarget = triggertype.PullRequest if gitEvent.ObjectAttributes.Action == "close" { event.TriggerTarget = triggertype.PullRequestClosed @@ -272,34 +319,38 @@ func (p *GitLabParser) PopulateEvent(event *info.Event, parsedEvent any) error { lastCommitIdx := len(gitEvent.Commits) - 1 event.Organization = extractOrgFromPath(gitEvent.Project.PathWithNamespace) event.Repository = extractRepoFromPath(gitEvent.Project.PathWithNamespace) - event.Sender = gitEvent.UserUsername event.URL = gitEvent.Project.WebURL - event.SHA = gitEvent.Commits[lastCommitIdx].ID - event.SHAURL = gitEvent.Commits[lastCommitIdx].URL - event.SHATitle = gitEvent.Commits[lastCommitIdx].Title - event.HeadBranch = gitEvent.Ref - event.BaseBranch = gitEvent.Ref event.HeadURL = gitEvent.Project.WebURL event.BaseURL = gitEvent.Project.WebURL event.DefaultBranch = gitEvent.Project.DefaultBranch + event.Sender = gitEvent.UserUsername + if gitEvent.Commits[lastCommitIdx] != nil { + event.SHA = gitEvent.Commits[lastCommitIdx].ID + event.SHAURL = gitEvent.Commits[lastCommitIdx].URL + event.SHATitle = gitEvent.Commits[lastCommitIdx].Title + } + event.HeadBranch = gitEvent.Ref + event.BaseBranch = gitEvent.Ref event.TriggerTarget = triggertype.Push case *gitlab.TagEvent: if len(gitEvent.Commits) == 0 { return fmt.Errorf("no commits attached to this tag event") } lastCommitIdx := len(gitEvent.Commits) - 1 + event.Sender = gitEvent.UserUsername event.Organization = extractOrgFromPath(gitEvent.Project.PathWithNamespace) event.Repository = extractRepoFromPath(gitEvent.Project.PathWithNamespace) - event.Sender = gitEvent.UserUsername + event.DefaultBranch = gitEvent.Project.DefaultBranch event.URL = gitEvent.Project.WebURL - event.SHA = gitEvent.Commits[lastCommitIdx].ID - event.SHAURL = gitEvent.Commits[lastCommitIdx].URL - event.SHATitle = gitEvent.Commits[lastCommitIdx].Title - event.HeadBranch = gitEvent.Ref - event.BaseBranch = gitEvent.Ref event.HeadURL = gitEvent.Project.WebURL event.BaseURL = gitEvent.Project.WebURL - event.DefaultBranch = gitEvent.Project.DefaultBranch + event.HeadBranch = gitEvent.Ref + event.BaseBranch = gitEvent.Ref + if gitEvent.Commits[lastCommitIdx] != nil { + event.SHA = gitEvent.Commits[lastCommitIdx].ID + event.SHAURL = gitEvent.Commits[lastCommitIdx].URL + event.SHATitle = gitEvent.Commits[lastCommitIdx].Title + } event.TriggerTarget = triggertype.Push default: return fmt.Errorf("unsupported GitLab event type: %T", gitEvent) @@ -578,55 +629,94 @@ func (p *GiteaParser) ParsePayload(eventType string, body []byte) (any, error) { func (p *GiteaParser) PopulateEvent(event *info.Event, parsedEvent any) error { switch gitEvent := parsedEvent.(type) { case *giteaStructs.PullRequestPayload: - event.Organization = gitEvent.Repository.Owner.UserName - event.Repository = gitEvent.Repository.Name - event.Sender = gitEvent.Sender.UserName - event.URL = gitEvent.Repository.HTMLURL - event.SHA = gitEvent.PullRequest.Head.Sha - event.SHAURL = fmt.Sprintf("%s/commit/%s", gitEvent.PullRequest.HTMLURL, gitEvent.PullRequest.Head.Sha) - event.HeadBranch = gitEvent.PullRequest.Head.Ref - event.BaseBranch = gitEvent.PullRequest.Base.Ref - event.HeadURL = gitEvent.PullRequest.Head.Repository.HTMLURL - event.BaseURL = gitEvent.PullRequest.Base.Repository.HTMLURL - event.PullRequestNumber = int(gitEvent.Index) - event.PullRequestTitle = gitEvent.PullRequest.Title - event.DefaultBranch = gitEvent.Repository.DefaultBranch + if gitEvent.Repository != nil { + if gitEvent.Repository.Owner != nil { + event.Organization = gitEvent.Repository.Owner.UserName + } + event.Repository = gitEvent.Repository.Name + event.URL = gitEvent.Repository.HTMLURL + event.DefaultBranch = gitEvent.Repository.DefaultBranch + } + if gitEvent.Sender != nil { + event.Sender = gitEvent.Sender.UserName + } + if gitEvent.PullRequest != nil { + if gitEvent.PullRequest.Head != nil { + event.SHA = gitEvent.PullRequest.Head.Sha + if gitEvent.PullRequest.HTMLURL != "" && gitEvent.PullRequest.Head.Sha != "" { + event.SHAURL = fmt.Sprintf("%s/commit/%s", gitEvent.PullRequest.HTMLURL, gitEvent.PullRequest.Head.Sha) + } + event.HeadBranch = gitEvent.PullRequest.Head.Ref + if gitEvent.PullRequest.Head.Repository != nil { + event.HeadURL = gitEvent.PullRequest.Head.Repository.HTMLURL + } + } + if gitEvent.PullRequest.Base != nil { + event.BaseBranch = gitEvent.PullRequest.Base.Ref + if gitEvent.PullRequest.Base.Repository != nil { + event.BaseURL = gitEvent.PullRequest.Base.Repository.HTMLURL + } + } + event.PullRequestNumber = int(gitEvent.Index) + event.PullRequestTitle = gitEvent.PullRequest.Title + for _, label := range gitEvent.PullRequest.Labels { + if label != nil { + event.PullRequestLabel = append(event.PullRequestLabel, label.Name) + } + } + } event.TriggerTarget = triggertype.PullRequest if gitEvent.Action == giteaStructs.HookIssueClosed { event.TriggerTarget = triggertype.PullRequestClosed } - for _, label := range gitEvent.PullRequest.Labels { - event.PullRequestLabel = append(event.PullRequestLabel, label.Name) - } case *giteaStructs.PushPayload: - event.Organization = gitEvent.Repo.Owner.UserName - event.Repository = gitEvent.Repo.Name - event.Sender = gitEvent.Sender.UserName - event.URL = gitEvent.Repo.HTMLURL - event.SHA = gitEvent.HeadCommit.ID - if event.SHA == "" { + if gitEvent.Repo != nil { + if gitEvent.Repo.Owner != nil { + event.Organization = gitEvent.Repo.Owner.UserName + } + event.Repository = gitEvent.Repo.Name + event.URL = gitEvent.Repo.HTMLURL + event.HeadURL = gitEvent.Repo.HTMLURL + event.BaseURL = gitEvent.Repo.HTMLURL + event.DefaultBranch = gitEvent.Repo.DefaultBranch + } + if gitEvent.Sender != nil { + event.Sender = gitEvent.Sender.UserName + } + if gitEvent.HeadCommit != nil { + event.SHA = gitEvent.HeadCommit.ID + if event.SHA == "" { + event.SHA = gitEvent.Before + } + event.SHAURL = gitEvent.HeadCommit.URL + event.SHATitle = gitEvent.HeadCommit.Message + } else if gitEvent.Before != "" { event.SHA = gitEvent.Before } - event.SHAURL = gitEvent.HeadCommit.URL - event.SHATitle = gitEvent.HeadCommit.Message event.HeadBranch = gitEvent.Ref event.BaseBranch = gitEvent.Ref - event.HeadURL = gitEvent.Repo.HTMLURL - event.BaseURL = gitEvent.Repo.HTMLURL - event.DefaultBranch = gitEvent.Repo.DefaultBranch event.TriggerTarget = triggertype.Push case *giteaStructs.IssueCommentPayload: - if gitEvent.Issue.PullRequest == nil { + issue := gitEvent.Issue + if issue == nil || issue.PullRequest == nil { return fmt.Errorf("issue comment is not from a pull request") } - event.Organization = gitEvent.Repository.Owner.UserName - event.Repository = gitEvent.Repository.Name - event.Sender = gitEvent.Sender.UserName - event.URL = gitEvent.Repository.HTMLURL - event.DefaultBranch = gitEvent.Repository.DefaultBranch + if gitEvent.Repository != nil { + if gitEvent.Repository.Owner != nil { + event.Organization = gitEvent.Repository.Owner.UserName + } + event.Repository = gitEvent.Repository.Name + event.URL = gitEvent.Repository.HTMLURL + event.DefaultBranch = gitEvent.Repository.DefaultBranch + } + if gitEvent.Sender != nil { + event.Sender = gitEvent.Sender.UserName + } event.TriggerTarget = triggertype.PullRequest - event.TriggerComment = gitEvent.Comment.Body - event.PullRequestNumber = extractPullRequestNumber(gitEvent.Issue.URL) + if gitEvent.Comment != nil { + event.TriggerComment = gitEvent.Comment.Body + } + event.PullRequestNumber = extractPullRequestNumber(issue.URL) default: return fmt.Errorf("unsupported Gitea event type: %T", gitEvent) } diff --git a/pkg/cmd/tknpac/cel/parser_test.go b/pkg/cmd/tknpac/cel/parser_test.go new file mode 100644 index 0000000000..123a0e4ba2 --- /dev/null +++ b/pkg/cmd/tknpac/cel/parser_test.go @@ -0,0 +1,448 @@ +package cel + +import ( + "strings" + "testing" + + "github.com/openshift-pipelines/pipelines-as-code/pkg/params/info" + "gotest.tools/v3/assert" +) + +func TestGitHubParserWithMissingFields(t *testing.T) { + tests := []struct { + name string + payload string + wantErr bool + checks func(t *testing.T, event *info.Event) + }{ + { + name: "missing repository field", + payload: `{ + "action": "opened", + "pull_request": { + "number": 1, + "title": "Test PR", + "head": {"ref": "feature", "sha": "abc123"}, + "base": {"ref": "main"} + }, + "sender": {"login": "testuser"} + }`, + wantErr: false, + checks: func(t *testing.T, event *info.Event) { + assert.Equal(t, event.Organization, "") + assert.Equal(t, event.Repository, "") + assert.Equal(t, event.Sender, "testuser") + assert.Equal(t, event.PullRequestNumber, 1) + }, + }, + { + name: "missing pull request field", + payload: `{ + "action": "opened", + "repository": { + "name": "test-repo", + "owner": {"login": "test-org"}, + "html_url": "https://github.com/test-org/test-repo" + }, + "sender": {"login": "testuser"} + }`, + wantErr: false, + checks: func(t *testing.T, event *info.Event) { + assert.Equal(t, event.Organization, "test-org") + assert.Equal(t, event.Repository, "test-repo") + assert.Equal(t, event.Sender, "testuser") + assert.Equal(t, event.SHA, "") + assert.Equal(t, event.PullRequestNumber, 0) + }, + }, + { + name: "missing sender field", + payload: `{ + "action": "opened", + "pull_request": { + "number": 1, + "title": "Test PR", + "head": {"ref": "feature", "sha": "abc123"}, + "base": {"ref": "main"} + }, + "repository": { + "name": "test-repo", + "owner": {"login": "test-org"} + } + }`, + wantErr: false, + checks: func(t *testing.T, event *info.Event) { + assert.Equal(t, event.Sender, "") + assert.Equal(t, event.Organization, "test-org") + }, + }, + { + name: "missing owner in repository", + payload: `{ + "action": "opened", + "pull_request": { + "number": 1, + "title": "Test PR", + "head": {"ref": "feature", "sha": "abc123"}, + "base": {"ref": "main"} + }, + "repository": {"name": "test-repo"}, + "sender": {"login": "testuser"} + }`, + wantErr: false, + checks: func(t *testing.T, event *info.Event) { + assert.Equal(t, event.Organization, "") + assert.Equal(t, event.Repository, "test-repo") + }, + }, + { + name: "missing head in pull request", + payload: `{ + "action": "opened", + "pull_request": { + "number": 1, + "title": "Test PR", + "base": {"ref": "main"} + }, + "repository": { + "name": "test-repo", + "owner": {"login": "test-org"} + }, + "sender": {"login": "testuser"} + }`, + wantErr: false, + checks: func(t *testing.T, event *info.Event) { + assert.Equal(t, event.SHA, "") + assert.Equal(t, event.HeadBranch, "") + assert.Equal(t, event.BaseBranch, "main") + }, + }, + { + name: "missing base in pull request", + payload: `{ + "action": "opened", + "pull_request": { + "number": 1, + "title": "Test PR", + "head": {"ref": "feature", "sha": "abc123"} + }, + "repository": { + "name": "test-repo", + "owner": {"login": "test-org"} + }, + "sender": {"login": "testuser"} + }`, + wantErr: false, + checks: func(t *testing.T, event *info.Event) { + assert.Equal(t, event.HeadBranch, "feature") + assert.Equal(t, event.BaseBranch, "") + }, + }, + { + name: "missing head repo in pull request", + payload: `{ + "action": "opened", + "pull_request": { + "number": 1, + "title": "Test PR", + "head": {"ref": "feature", "sha": "abc123"}, + "base": {"ref": "main"} + }, + "repository": { + "name": "test-repo", + "owner": {"login": "test-org"} + }, + "sender": {"login": "testuser"} + }`, + wantErr: false, + checks: func(t *testing.T, event *info.Event) { + assert.Equal(t, event.HeadURL, "") + }, + }, + { + name: "push event missing head commit", + payload: `{ + "ref": "refs/heads/main", + "repository": { + "name": "test-repo", + "owner": {"login": "test-org"}, + "html_url": "https://github.com/test-org/test-repo" + }, + "sender": {"login": "testuser"} + }`, + wantErr: false, + checks: func(t *testing.T, event *info.Event) { + assert.Equal(t, event.SHA, "") + assert.Equal(t, event.SHAURL, "") + assert.Equal(t, event.Organization, "test-org") + assert.Equal(t, event.HeadBranch, "refs/heads/main") + }, + }, + { + name: "issue comment with issue but no pull request links", + payload: `{ + "action": "created", + "issue": { + "number": 1 + }, + "comment": {"body": "/retest"}, + "repository": { + "name": "test-repo", + "owner": {"login": "test-org"} + }, + "sender": {"login": "testuser"} + }`, + wantErr: true, + }, + { + name: "commit comment event missing comment", + payload: `{ + "action": "created", + "repository": { + "name": "test-repo", + "owner": {"login": "test-org"} + }, + "sender": {"login": "testuser"} + }`, + wantErr: false, + checks: func(t *testing.T, event *info.Event) { + assert.Equal(t, event.SHA, "") + assert.Equal(t, event.TriggerComment, "") + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + headers := map[string]string{ + "X-GitHub-Event": "pull_request", + } + // Determine event type from payload based on test name + switch { + case strings.Contains(tt.name, "push"): + headers["X-GitHub-Event"] = "push" + case strings.Contains(tt.name, "issue comment"): + headers["X-GitHub-Event"] = "issue_comment" + case strings.Contains(tt.name, "commit comment"): + headers["X-GitHub-Event"] = "commit_comment" + } + + event, err := eventFromGitHub([]byte(tt.payload), headers) + if tt.wantErr { + assert.Assert(t, err != nil) + return + } + assert.NilError(t, err) + assert.Assert(t, event != nil) + if tt.checks != nil { + tt.checks(t, event) + } + }) + } +} + +func TestGiteaParserWithMissingFields(t *testing.T) { + tests := []struct { + name string + payload string + wantErr bool + checks func(t *testing.T, event *info.Event) + }{ + { + name: "pull request with missing repository", + payload: `{ + "action": "opened", + "number": 1, + "pull_request": { + "title": "Test PR", + "head": {"ref": "feature", "sha": "abc123"}, + "base": {"ref": "main"} + }, + "sender": {"login": "testuser"} + }`, + wantErr: false, + checks: func(t *testing.T, event *info.Event) { + assert.Equal(t, event.Organization, "") + assert.Equal(t, event.Repository, "") + assert.Equal(t, event.Sender, "testuser") + }, + }, + { + name: "pull request with missing sender", + payload: `{ + "action": "opened", + "number": 1, + "pull_request": { + "title": "Test PR", + "head": {"ref": "feature", "sha": "abc123"}, + "base": {"ref": "main"} + }, + "repository": { + "name": "test-repo", + "owner": {"login": "test-org"} + } + }`, + wantErr: false, + checks: func(t *testing.T, event *info.Event) { + assert.Equal(t, event.Sender, "") + assert.Equal(t, event.Repository, "test-repo") + }, + }, + { + name: "pull request with missing pull request field", + payload: `{ + "action": "opened", + "number": 1, + "repository": { + "name": "test-repo", + "owner": {"login": "test-org"} + }, + "sender": {"login": "testuser"} + }`, + wantErr: false, + checks: func(t *testing.T, event *info.Event) { + assert.Equal(t, event.SHA, "") + assert.Equal(t, event.HeadBranch, "") + }, + }, + { + name: "pull request with missing owner", + payload: `{ + "action": "opened", + "number": 1, + "pull_request": { + "title": "Test PR", + "head": {"ref": "feature", "sha": "abc123"}, + "base": {"ref": "main"} + }, + "repository": {"name": "test-repo"}, + "sender": {"login": "testuser"} + }`, + wantErr: false, + checks: func(t *testing.T, event *info.Event) { + assert.Equal(t, event.Organization, "") + assert.Equal(t, event.Repository, "test-repo") + }, + }, + { + name: "push with missing repo", + payload: `{ + "ref": "refs/heads/main", + "head_commit": { + "id": "abc123", + "message": "Test commit" + }, + "sender": {"login": "testuser"} + }`, + wantErr: false, + checks: func(t *testing.T, event *info.Event) { + assert.Equal(t, event.Organization, "") + assert.Equal(t, event.Repository, "") + assert.Equal(t, event.SHA, "abc123") + }, + }, + { + name: "push with missing head commit", + payload: `{ + "ref": "refs/heads/main", + "before": "def456", + "repository": { + "name": "test-repo", + "owner": {"login": "test-org"} + }, + "sender": {"login": "testuser"} + }`, + wantErr: false, + checks: func(t *testing.T, event *info.Event) { + assert.Equal(t, event.SHA, "def456") + assert.Equal(t, event.SHAURL, "") + }, + }, + { + name: "issue comment with issue but no pull request", + payload: `{ + "action": "created", + "issue": {"number": 1}, + "comment": {"body": "/retest"}, + "repository": { + "name": "test-repo", + "owner": {"login": "test-org"} + }, + "sender": {"login": "testuser"} + }`, + wantErr: true, + }, + { + name: "issue comment with missing comment", + payload: `{ + "action": "created", + "issue": { + "number": 1, + "pull_request": {} + }, + "repository": { + "name": "test-repo", + "owner": {"login": "test-org"} + }, + "sender": {"login": "testuser"} + }`, + wantErr: false, + checks: func(t *testing.T, event *info.Event) { + assert.Equal(t, event.TriggerComment, "") + }, + }, + { + name: "pull request with missing head repository", + payload: `{ + "action": "opened", + "number": 1, + "pull_request": { + "title": "Test PR", + "head": {"ref": "feature", "sha": "abc123"}, + "base": { + "ref": "main", + "repo": {"html_url": "https://gitea.com/test-org/test-repo"} + }, + "html_url": "https://gitea.com/test-org/test-repo/pulls/1" + }, + "repository": { + "name": "test-repo", + "owner": {"login": "test-org"} + }, + "sender": {"login": "testuser"} + }`, + wantErr: false, + checks: func(t *testing.T, event *info.Event) { + assert.Equal(t, event.HeadURL, "") + assert.Equal(t, event.BaseURL, "https://gitea.com/test-org/test-repo") + assert.Equal(t, event.SHAURL, "https://gitea.com/test-org/test-repo/pulls/1/commit/abc123") + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + headers := map[string]string{ + "X-Gitea-Event-Type": "pull_request", + } + // Determine event type based on test name + switch { + case strings.Contains(tt.name, "push"): + headers["X-Gitea-Event-Type"] = "push" + case strings.Contains(tt.name, "issue comment"): + headers["X-Gitea-Event-Type"] = "issue_comment" + } + + event, err := eventFromGitea([]byte(tt.payload), headers) + if tt.wantErr { + assert.Assert(t, err != nil) + return + } + assert.NilError(t, err) + assert.Assert(t, event != nil) + if tt.checks != nil { + tt.checks(t, event) + } + }) + } +}