summaryrefslogtreecommitdiffstats
path: root/services/webhook/sourcehut
diff options
context:
space:
mode:
Diffstat (limited to 'services/webhook/sourcehut')
-rw-r--r--services/webhook/sourcehut/builds.go301
-rw-r--r--services/webhook/sourcehut/builds_test.go386
-rw-r--r--services/webhook/sourcehut/testdata/repo.git/HEAD1
-rw-r--r--services/webhook/sourcehut/testdata/repo.git/config4
-rw-r--r--services/webhook/sourcehut/testdata/repo.git/description1
-rw-r--r--services/webhook/sourcehut/testdata/repo.git/info/exclude6
-rw-r--r--services/webhook/sourcehut/testdata/repo.git/objects/3c/3d4b799b3933ba687b263eeef2034300a5315ebin0 -> 83 bytes
-rw-r--r--services/webhook/sourcehut/testdata/repo.git/objects/58/771003157b81abc6bf41df0c5db4147a3e3c832
-rw-r--r--services/webhook/sourcehut/testdata/repo.git/objects/69/b217caa89166a02b8cd368b64fb83a44720e141
-rw-r--r--services/webhook/sourcehut/testdata/repo.git/objects/99/fb389b232e5497f0dcdb1c1065eac1d10d3794bin0 -> 57 bytes
-rw-r--r--services/webhook/sourcehut/testdata/repo.git/objects/9e/4b777f81b316a1c75a0797b33add68ee49b0d0bin0 -> 54 bytes
-rw-r--r--services/webhook/sourcehut/testdata/repo.git/objects/a5/4082fdb8e55055382725f10a81bb4dc2b130294
-rw-r--r--services/webhook/sourcehut/testdata/repo.git/objects/aa/3905af404394f576f88f00e7f0919b4b97453fbin0 -> 57 bytes
-rw-r--r--services/webhook/sourcehut/testdata/repo.git/objects/b0/404943256a1f5a50c3726f4378756b4c1e5704bin0 -> 160 bytes
-rw-r--r--services/webhook/sourcehut/testdata/repo.git/objects/d2/e0862c8b8097ba4bdd72946c20479751d307a04
-rw-r--r--services/webhook/sourcehut/testdata/repo.git/refs/heads/main1
16 files changed, 711 insertions, 0 deletions
diff --git a/services/webhook/sourcehut/builds.go b/services/webhook/sourcehut/builds.go
new file mode 100644
index 0000000..7b7ace1
--- /dev/null
+++ b/services/webhook/sourcehut/builds.go
@@ -0,0 +1,301 @@
+// Copyright 2024 The Forgejo Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package sourcehut
+
+import (
+ "cmp"
+ "context"
+ "fmt"
+ "html/template"
+ "io/fs"
+ "net/http"
+ "strings"
+
+ webhook_model "code.gitea.io/gitea/models/webhook"
+ "code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/gitrepo"
+ "code.gitea.io/gitea/modules/json"
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/setting"
+ api "code.gitea.io/gitea/modules/structs"
+ webhook_module "code.gitea.io/gitea/modules/webhook"
+ gitea_context "code.gitea.io/gitea/services/context"
+ "code.gitea.io/gitea/services/forms"
+ "code.gitea.io/gitea/services/webhook/shared"
+
+ "gitea.com/go-chi/binding"
+ "gopkg.in/yaml.v3"
+)
+
+type BuildsHandler struct{}
+
+func (BuildsHandler) Type() webhook_module.HookType { return webhook_module.SOURCEHUT_BUILDS }
+func (BuildsHandler) Metadata(w *webhook_model.Webhook) any {
+ s := &BuildsMeta{}
+ if err := json.Unmarshal([]byte(w.Meta), s); err != nil {
+ log.Error("sourcehut.BuildsHandler.Metadata(%d): %v", w.ID, err)
+ }
+ return s
+}
+
+func (BuildsHandler) Icon(size int) template.HTML {
+ return shared.ImgIcon("sourcehut.svg", size)
+}
+
+type buildsForm struct {
+ forms.WebhookCoreForm
+ PayloadURL string `binding:"Required;ValidUrl"`
+ ManifestPath string `binding:"Required"`
+ Visibility string `binding:"Required;In(PUBLIC,UNLISTED,PRIVATE)"`
+ Secrets bool
+ AccessToken string `binding:"Required"`
+}
+
+var _ binding.Validator = &buildsForm{}
+
+// Validate implements binding.Validator.
+func (f *buildsForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := gitea_context.GetWebContext(req)
+ if !fs.ValidPath(f.ManifestPath) {
+ errs = append(errs, binding.Error{
+ FieldNames: []string{"ManifestPath"},
+ Classification: "",
+ Message: ctx.Locale.TrString("repo.settings.add_webhook.invalid_path"),
+ })
+ }
+ f.AuthorizationHeader = "Bearer " + strings.TrimSpace(f.AccessToken)
+ return errs
+}
+
+func (BuildsHandler) UnmarshalForm(bind func(any)) forms.WebhookForm {
+ var form buildsForm
+ bind(&form)
+
+ return forms.WebhookForm{
+ WebhookCoreForm: form.WebhookCoreForm,
+ URL: form.PayloadURL,
+ ContentType: webhook_model.ContentTypeJSON,
+ Secret: "",
+ HTTPMethod: http.MethodPost,
+ Metadata: &BuildsMeta{
+ ManifestPath: form.ManifestPath,
+ Visibility: form.Visibility,
+ Secrets: form.Secrets,
+ },
+ }
+}
+
+type (
+ graphqlPayload[V any] struct {
+ Query string `json:"query,omitempty"`
+ Error string `json:"error,omitempty"`
+ Variables V `json:"variables,omitempty"`
+ }
+ // buildsVariables according to https://man.sr.ht/builds.sr.ht/graphql.md
+ buildsVariables struct {
+ Manifest string `json:"manifest"`
+ Tags []string `json:"tags"`
+ Note string `json:"note"`
+ Secrets bool `json:"secrets"`
+ Execute bool `json:"execute"`
+ Visibility string `json:"visibility"`
+ }
+
+ // BuildsMeta contains the metadata for the webhook
+ BuildsMeta struct {
+ ManifestPath string `json:"manifest_path"`
+ Visibility string `json:"visibility"`
+ Secrets bool `json:"secrets"`
+ }
+)
+
+type sourcehutConvertor struct {
+ ctx context.Context
+ meta BuildsMeta
+}
+
+var _ shared.PayloadConvertor[graphqlPayload[buildsVariables]] = sourcehutConvertor{}
+
+func (BuildsHandler) NewRequest(ctx context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
+ meta := BuildsMeta{}
+ if err := json.Unmarshal([]byte(w.Meta), &meta); err != nil {
+ return nil, nil, fmt.Errorf("newSourcehutRequest meta json: %w", err)
+ }
+ pc := sourcehutConvertor{
+ ctx: ctx,
+ meta: meta,
+ }
+ return shared.NewJSONRequest(pc, w, t, false)
+}
+
+// Create implements PayloadConvertor Create method
+func (pc sourcehutConvertor) Create(p *api.CreatePayload) (graphqlPayload[buildsVariables], error) {
+ return pc.newPayload(p.Repo, p.Sha, p.Ref, p.RefType+" "+git.RefName(p.Ref).ShortName()+" created", true)
+}
+
+// Delete implements PayloadConvertor Delete method
+func (pc sourcehutConvertor) Delete(_ *api.DeletePayload) (graphqlPayload[buildsVariables], error) {
+ return graphqlPayload[buildsVariables]{}, shared.ErrPayloadTypeNotSupported
+}
+
+// Fork implements PayloadConvertor Fork method
+func (pc sourcehutConvertor) Fork(_ *api.ForkPayload) (graphqlPayload[buildsVariables], error) {
+ return graphqlPayload[buildsVariables]{}, shared.ErrPayloadTypeNotSupported
+}
+
+// Push implements PayloadConvertor Push method
+func (pc sourcehutConvertor) Push(p *api.PushPayload) (graphqlPayload[buildsVariables], error) {
+ return pc.newPayload(p.Repo, p.HeadCommit.ID, p.Ref, p.HeadCommit.Message, true)
+}
+
+// Issue implements PayloadConvertor Issue method
+func (pc sourcehutConvertor) Issue(_ *api.IssuePayload) (graphqlPayload[buildsVariables], error) {
+ return graphqlPayload[buildsVariables]{}, shared.ErrPayloadTypeNotSupported
+}
+
+// IssueComment implements PayloadConvertor IssueComment method
+func (pc sourcehutConvertor) IssueComment(_ *api.IssueCommentPayload) (graphqlPayload[buildsVariables], error) {
+ return graphqlPayload[buildsVariables]{}, shared.ErrPayloadTypeNotSupported
+}
+
+// PullRequest implements PayloadConvertor PullRequest method
+func (pc sourcehutConvertor) PullRequest(_ *api.PullRequestPayload) (graphqlPayload[buildsVariables], error) {
+ // TODO
+ return graphqlPayload[buildsVariables]{}, shared.ErrPayloadTypeNotSupported
+}
+
+// Review implements PayloadConvertor Review method
+func (pc sourcehutConvertor) Review(_ *api.PullRequestPayload, _ webhook_module.HookEventType) (graphqlPayload[buildsVariables], error) {
+ return graphqlPayload[buildsVariables]{}, shared.ErrPayloadTypeNotSupported
+}
+
+// Repository implements PayloadConvertor Repository method
+func (pc sourcehutConvertor) Repository(_ *api.RepositoryPayload) (graphqlPayload[buildsVariables], error) {
+ return graphqlPayload[buildsVariables]{}, shared.ErrPayloadTypeNotSupported
+}
+
+// Wiki implements PayloadConvertor Wiki method
+func (pc sourcehutConvertor) Wiki(_ *api.WikiPayload) (graphqlPayload[buildsVariables], error) {
+ return graphqlPayload[buildsVariables]{}, shared.ErrPayloadTypeNotSupported
+}
+
+// Release implements PayloadConvertor Release method
+func (pc sourcehutConvertor) Release(_ *api.ReleasePayload) (graphqlPayload[buildsVariables], error) {
+ return graphqlPayload[buildsVariables]{}, shared.ErrPayloadTypeNotSupported
+}
+
+func (pc sourcehutConvertor) Package(_ *api.PackagePayload) (graphqlPayload[buildsVariables], error) {
+ return graphqlPayload[buildsVariables]{}, shared.ErrPayloadTypeNotSupported
+}
+
+// mustBuildManifest adjusts the manifest to submit to the builds service
+//
+// in case of an error the Error field will be set, to be visible by the end-user under recent deliveries
+func (pc sourcehutConvertor) newPayload(repo *api.Repository, commitID, ref, note string, trusted bool) (graphqlPayload[buildsVariables], error) {
+ manifest, err := pc.buildManifest(repo, commitID, ref)
+ if err != nil {
+ if len(manifest) == 0 {
+ return graphqlPayload[buildsVariables]{}, err
+ }
+ // the manifest contains an error for the user: log the actual error and construct the payload
+ // the error will be visible under the "recent deliveries" of the webhook settings.
+ log.Warn("sourcehut.builds: could not construct manifest for %s: %v", repo.FullName, err)
+ msg := fmt.Sprintf("%s:%s %s", repo.FullName, ref, manifest)
+ return graphqlPayload[buildsVariables]{
+ Error: msg,
+ }, nil
+ }
+
+ gitRef := git.RefName(ref)
+ return graphqlPayload[buildsVariables]{
+ Query: `mutation (
+ $manifest: String!
+ $tags: [String!]
+ $note: String!
+ $secrets: Boolean!
+ $execute: Boolean!
+ $visibility: Visibility!
+) {
+ submit(
+ manifest: $manifest
+ tags: $tags
+ note: $note
+ secrets: $secrets
+ execute: $execute
+ visibility: $visibility
+ ) {
+ id
+ }
+}`, Variables: buildsVariables{
+ Manifest: string(manifest),
+ Tags: []string{repo.FullName, gitRef.RefType() + "/" + gitRef.ShortName(), pc.meta.ManifestPath},
+ Note: note,
+ Secrets: pc.meta.Secrets && trusted,
+ Execute: trusted,
+ Visibility: cmp.Or(pc.meta.Visibility, "PRIVATE"),
+ },
+ }, nil
+}
+
+// buildManifest adjusts the manifest to submit to the builds service
+// in case of an error the []byte might contain an error that can be displayed to the user
+func (pc sourcehutConvertor) buildManifest(repo *api.Repository, commitID, gitRef string) ([]byte, error) {
+ gitRepo, err := gitrepo.OpenRepository(pc.ctx, repo)
+ if err != nil {
+ msg := "could not open repository"
+ return []byte(msg), fmt.Errorf(msg+": %w", err)
+ }
+ defer gitRepo.Close()
+
+ commit, err := gitRepo.GetCommit(commitID)
+ if err != nil {
+ msg := fmt.Sprintf("could not get commit %q", commitID)
+ return []byte(msg), fmt.Errorf(msg+": %w", err)
+ }
+ entry, err := commit.GetTreeEntryByPath(pc.meta.ManifestPath)
+ if err != nil {
+ msg := fmt.Sprintf("could not open manifest %q", pc.meta.ManifestPath)
+ return []byte(msg), fmt.Errorf(msg+": %w", err)
+ }
+ r, err := entry.Blob().DataAsync()
+ if err != nil {
+ msg := fmt.Sprintf("could not read manifest %q", pc.meta.ManifestPath)
+ return []byte(msg), fmt.Errorf(msg+": %w", err)
+ }
+ defer r.Close()
+
+ // reference: https://man.sr.ht/builds.sr.ht/manifest.md
+ var manifest struct {
+ Sources []string `yaml:"sources"`
+ Environment map[string]string `yaml:"environment"`
+
+ Rest map[string]yaml.Node `yaml:",inline"`
+ }
+ if err := yaml.NewDecoder(r).Decode(&manifest); err != nil {
+ msg := fmt.Sprintf("could not decode manifest %q", pc.meta.ManifestPath)
+ return []byte(msg), fmt.Errorf(msg+": %w", err)
+ }
+
+ if manifest.Environment == nil {
+ manifest.Environment = make(map[string]string)
+ }
+ manifest.Environment["BUILD_SUBMITTER"] = "forgejo"
+ manifest.Environment["BUILD_SUBMITTER_URL"] = setting.AppURL
+ manifest.Environment["GIT_REF"] = gitRef
+
+ source := repo.CloneURL + "#" + commitID
+ found := false
+ for i, s := range manifest.Sources {
+ if s == repo.CloneURL {
+ manifest.Sources[i] = source
+ found = true
+ break
+ }
+ }
+ if !found {
+ manifest.Sources = append(manifest.Sources, source)
+ }
+
+ return yaml.Marshal(manifest)
+}
diff --git a/services/webhook/sourcehut/builds_test.go b/services/webhook/sourcehut/builds_test.go
new file mode 100644
index 0000000..1a37279
--- /dev/null
+++ b/services/webhook/sourcehut/builds_test.go
@@ -0,0 +1,386 @@
+// Copyright 2024 The Forgejo Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package sourcehut
+
+import (
+ "context"
+ "testing"
+
+ webhook_model "code.gitea.io/gitea/models/webhook"
+ "code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/json"
+ "code.gitea.io/gitea/modules/setting"
+ api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/test"
+ webhook_module "code.gitea.io/gitea/modules/webhook"
+ "code.gitea.io/gitea/services/webhook/shared"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func gitInit(t testing.TB) {
+ if setting.Git.HomePath != "" {
+ return
+ }
+ t.Cleanup(test.MockVariableValue(&setting.Git.HomePath, t.TempDir()))
+ require.NoError(t, git.InitSimple(context.Background()))
+}
+
+func TestSourcehutBuildsPayload(t *testing.T) {
+ gitInit(t)
+ defer test.MockVariableValue(&setting.RepoRootPath, ".")()
+ defer test.MockVariableValue(&setting.AppURL, "https://example.forgejo.org/")()
+
+ repo := &api.Repository{
+ HTMLURL: "http://localhost:3000/testdata/repo",
+ Name: "repo",
+ FullName: "testdata/repo",
+ Owner: &api.User{
+ UserName: "testdata",
+ },
+ CloneURL: "http://localhost:3000/testdata/repo.git",
+ }
+
+ pc := sourcehutConvertor{
+ ctx: git.DefaultContext,
+ meta: BuildsMeta{
+ ManifestPath: "adjust me in each test",
+ Visibility: "UNLISTED",
+ Secrets: true,
+ },
+ }
+ t.Run("Create/branch", func(t *testing.T) {
+ p := &api.CreatePayload{
+ Sha: "58771003157b81abc6bf41df0c5db4147a3e3c83",
+ Ref: "refs/heads/test",
+ RefType: "branch",
+ Repo: repo,
+ }
+
+ pc.meta.ManifestPath = "simple.yml"
+ pl, err := pc.Create(p)
+ require.NoError(t, err)
+
+ assert.Equal(t, `sources:
+ - http://localhost:3000/testdata/repo.git#58771003157b81abc6bf41df0c5db4147a3e3c83
+environment:
+ BUILD_SUBMITTER: forgejo
+ BUILD_SUBMITTER_URL: https://example.forgejo.org/
+ GIT_REF: refs/heads/test
+image: alpine/edge
+tasks:
+ - say-hello: |
+ echo hello
+ - say-world: echo world
+`, pl.Variables.Manifest)
+ assert.Equal(t, buildsVariables{
+ Manifest: pl.Variables.Manifest, // the manifest correctness is checked above, for nicer diff on error
+ Note: "branch test created",
+ Tags: []string{"testdata/repo", "branch/test", "simple.yml"},
+ Secrets: true,
+ Execute: true,
+ Visibility: "UNLISTED",
+ }, pl.Variables)
+ })
+ t.Run("Create/tag", func(t *testing.T) {
+ p := &api.CreatePayload{
+ Sha: "58771003157b81abc6bf41df0c5db4147a3e3c83",
+ Ref: "refs/tags/v1.0.0",
+ RefType: "tag",
+ Repo: repo,
+ }
+
+ pc.meta.ManifestPath = "simple.yml"
+ pl, err := pc.Create(p)
+ require.NoError(t, err)
+
+ assert.Equal(t, `sources:
+ - http://localhost:3000/testdata/repo.git#58771003157b81abc6bf41df0c5db4147a3e3c83
+environment:
+ BUILD_SUBMITTER: forgejo
+ BUILD_SUBMITTER_URL: https://example.forgejo.org/
+ GIT_REF: refs/tags/v1.0.0
+image: alpine/edge
+tasks:
+ - say-hello: |
+ echo hello
+ - say-world: echo world
+`, pl.Variables.Manifest)
+ assert.Equal(t, buildsVariables{
+ Manifest: pl.Variables.Manifest, // the manifest correctness is checked above, for nicer diff on error
+ Note: "tag v1.0.0 created",
+ Tags: []string{"testdata/repo", "tag/v1.0.0", "simple.yml"},
+ Secrets: true,
+ Execute: true,
+ Visibility: "UNLISTED",
+ }, pl.Variables)
+ })
+
+ t.Run("Delete", func(t *testing.T) {
+ p := &api.DeletePayload{}
+
+ pl, err := pc.Delete(p)
+ require.Equal(t, shared.ErrPayloadTypeNotSupported, err)
+ require.Equal(t, graphqlPayload[buildsVariables]{}, pl)
+ })
+
+ t.Run("Fork", func(t *testing.T) {
+ p := &api.ForkPayload{}
+
+ pl, err := pc.Fork(p)
+ require.Equal(t, shared.ErrPayloadTypeNotSupported, err)
+ require.Equal(t, graphqlPayload[buildsVariables]{}, pl)
+ })
+
+ t.Run("Push/simple", func(t *testing.T) {
+ p := &api.PushPayload{
+ Ref: "refs/heads/main",
+ HeadCommit: &api.PayloadCommit{
+ ID: "58771003157b81abc6bf41df0c5db4147a3e3c83",
+ Message: "add simple",
+ },
+ Repo: repo,
+ }
+
+ pc.meta.ManifestPath = "simple.yml"
+ pl, err := pc.Push(p)
+ require.NoError(t, err)
+
+ assert.Equal(t, `sources:
+ - http://localhost:3000/testdata/repo.git#58771003157b81abc6bf41df0c5db4147a3e3c83
+environment:
+ BUILD_SUBMITTER: forgejo
+ BUILD_SUBMITTER_URL: https://example.forgejo.org/
+ GIT_REF: refs/heads/main
+image: alpine/edge
+tasks:
+ - say-hello: |
+ echo hello
+ - say-world: echo world
+`, pl.Variables.Manifest)
+ assert.Equal(t, buildsVariables{
+ Manifest: pl.Variables.Manifest, // the manifest correctness is checked above, for nicer diff on error
+ Note: "add simple",
+ Tags: []string{"testdata/repo", "branch/main", "simple.yml"},
+ Secrets: true,
+ Execute: true,
+ Visibility: "UNLISTED",
+ }, pl.Variables)
+ })
+ t.Run("Push/complex", func(t *testing.T) {
+ p := &api.PushPayload{
+ Ref: "refs/heads/main",
+ HeadCommit: &api.PayloadCommit{
+ ID: "b0404943256a1f5a50c3726f4378756b4c1e5704",
+ Message: "replace simple with complex",
+ },
+ Repo: repo,
+ }
+
+ pc.meta.ManifestPath = "complex.yaml"
+ pc.meta.Visibility = "PRIVATE"
+ pc.meta.Secrets = false
+ pl, err := pc.Push(p)
+ require.NoError(t, err)
+
+ assert.Equal(t, `sources:
+ - http://localhost:3000/testdata/repo.git#b0404943256a1f5a50c3726f4378756b4c1e5704
+environment:
+ BUILD_SUBMITTER: forgejo
+ BUILD_SUBMITTER_URL: https://example.forgejo.org/
+ GIT_REF: refs/heads/main
+ deploy: synapse@synapse-bt.org
+image: archlinux
+packages:
+ - nodejs
+ - npm
+ - rsync
+secrets:
+ - 7ebab768-e5e4-4c9d-ba57-ec41a72c5665
+tasks: []
+triggers:
+ - condition: failure
+ action: email
+ to: Jim Jimson <jim@example.org>
+ # report back the status
+ - condition: always
+ action: webhook
+ url: https://hook.example.org
+`, pl.Variables.Manifest)
+ assert.Equal(t, buildsVariables{
+ Manifest: pl.Variables.Manifest, // the manifest correctness is checked above, for nicer diff on error
+ Note: "replace simple with complex",
+ Tags: []string{"testdata/repo", "branch/main", "complex.yaml"},
+ Secrets: false,
+ Execute: true,
+ Visibility: "PRIVATE",
+ }, pl.Variables)
+ })
+
+ t.Run("Push/error", func(t *testing.T) {
+ p := &api.PushPayload{
+ Ref: "refs/heads/main",
+ HeadCommit: &api.PayloadCommit{
+ ID: "58771003157b81abc6bf41df0c5db4147a3e3c83",
+ Message: "add simple",
+ },
+ Repo: repo,
+ }
+
+ pc.meta.ManifestPath = "non-existing.yml"
+ pl, err := pc.Push(p)
+ require.NoError(t, err)
+
+ assert.Equal(t, graphqlPayload[buildsVariables]{
+ Error: "testdata/repo:refs/heads/main could not open manifest \"non-existing.yml\"",
+ }, pl)
+ })
+
+ t.Run("Issue", func(t *testing.T) {
+ p := &api.IssuePayload{}
+
+ p.Action = api.HookIssueOpened
+ pl, err := pc.Issue(p)
+ require.Equal(t, shared.ErrPayloadTypeNotSupported, err)
+ require.Equal(t, graphqlPayload[buildsVariables]{}, pl)
+
+ p.Action = api.HookIssueClosed
+ pl, err = pc.Issue(p)
+ require.Equal(t, shared.ErrPayloadTypeNotSupported, err)
+ require.Equal(t, graphqlPayload[buildsVariables]{}, pl)
+ })
+
+ t.Run("IssueComment", func(t *testing.T) {
+ p := &api.IssueCommentPayload{}
+
+ pl, err := pc.IssueComment(p)
+ require.Equal(t, shared.ErrPayloadTypeNotSupported, err)
+ require.Equal(t, graphqlPayload[buildsVariables]{}, pl)
+ })
+
+ t.Run("PullRequest", func(t *testing.T) {
+ p := &api.PullRequestPayload{}
+
+ pl, err := pc.PullRequest(p)
+ require.Equal(t, shared.ErrPayloadTypeNotSupported, err)
+ require.Equal(t, graphqlPayload[buildsVariables]{}, pl)
+ })
+
+ t.Run("PullRequestComment", func(t *testing.T) {
+ p := &api.IssueCommentPayload{
+ IsPull: true,
+ }
+
+ pl, err := pc.IssueComment(p)
+ require.Equal(t, shared.ErrPayloadTypeNotSupported, err)
+ require.Equal(t, graphqlPayload[buildsVariables]{}, pl)
+ })
+
+ t.Run("Review", func(t *testing.T) {
+ p := &api.PullRequestPayload{}
+ p.Action = api.HookIssueReviewed
+
+ pl, err := pc.Review(p, webhook_module.HookEventPullRequestReviewApproved)
+ require.Equal(t, shared.ErrPayloadTypeNotSupported, err)
+ require.Equal(t, graphqlPayload[buildsVariables]{}, pl)
+ })
+
+ t.Run("Repository", func(t *testing.T) {
+ p := &api.RepositoryPayload{}
+
+ pl, err := pc.Repository(p)
+ require.Equal(t, shared.ErrPayloadTypeNotSupported, err)
+ require.Equal(t, graphqlPayload[buildsVariables]{}, pl)
+ })
+
+ t.Run("Package", func(t *testing.T) {
+ p := &api.PackagePayload{}
+
+ pl, err := pc.Package(p)
+ require.Equal(t, shared.ErrPayloadTypeNotSupported, err)
+ require.Equal(t, graphqlPayload[buildsVariables]{}, pl)
+ })
+
+ t.Run("Wiki", func(t *testing.T) {
+ p := &api.WikiPayload{}
+
+ p.Action = api.HookWikiCreated
+ pl, err := pc.Wiki(p)
+ require.Equal(t, shared.ErrPayloadTypeNotSupported, err)
+ require.Equal(t, graphqlPayload[buildsVariables]{}, pl)
+
+ p.Action = api.HookWikiEdited
+ pl, err = pc.Wiki(p)
+ require.Equal(t, shared.ErrPayloadTypeNotSupported, err)
+ require.Equal(t, graphqlPayload[buildsVariables]{}, pl)
+
+ p.Action = api.HookWikiDeleted
+ pl, err = pc.Wiki(p)
+ require.Equal(t, shared.ErrPayloadTypeNotSupported, err)
+ require.Equal(t, graphqlPayload[buildsVariables]{}, pl)
+ })
+
+ t.Run("Release", func(t *testing.T) {
+ p := &api.ReleasePayload{}
+
+ pl, err := pc.Release(p)
+ require.Equal(t, shared.ErrPayloadTypeNotSupported, err)
+ require.Equal(t, graphqlPayload[buildsVariables]{}, pl)
+ })
+}
+
+func TestSourcehutJSONPayload(t *testing.T) {
+ gitInit(t)
+ defer test.MockVariableValue(&setting.RepoRootPath, ".")()
+ defer test.MockVariableValue(&setting.AppURL, "https://example.forgejo.org/")()
+
+ repo := &api.Repository{
+ HTMLURL: "http://localhost:3000/testdata/repo",
+ Name: "repo",
+ FullName: "testdata/repo",
+ Owner: &api.User{
+ UserName: "testdata",
+ },
+ CloneURL: "http://localhost:3000/testdata/repo.git",
+ }
+
+ p := &api.PushPayload{
+ Ref: "refs/heads/main",
+ HeadCommit: &api.PayloadCommit{
+ ID: "58771003157b81abc6bf41df0c5db4147a3e3c83",
+ Message: "json test",
+ },
+ Repo: repo,
+ }
+ data, err := p.JSONPayload()
+ require.NoError(t, err)
+
+ hook := &webhook_model.Webhook{
+ RepoID: 3,
+ IsActive: true,
+ Type: webhook_module.MATRIX,
+ URL: "https://sourcehut.example.com/api/jobs",
+ Meta: `{"manifest_path":"simple.yml"}`,
+ }
+ task := &webhook_model.HookTask{
+ HookID: hook.ID,
+ EventType: webhook_module.HookEventPush,
+ PayloadContent: string(data),
+ PayloadVersion: 2,
+ }
+
+ req, reqBody, err := BuildsHandler{}.NewRequest(context.Background(), hook, task)
+ require.NoError(t, err)
+ require.NotNil(t, req)
+ require.NotNil(t, reqBody)
+
+ assert.Equal(t, "POST", req.Method)
+ assert.Equal(t, "/api/jobs", req.URL.Path)
+ assert.Equal(t, "application/json", req.Header.Get("Content-Type"))
+ var body graphqlPayload[buildsVariables]
+ err = json.NewDecoder(req.Body).Decode(&body)
+ require.NoError(t, err)
+ assert.Equal(t, "json test", body.Variables.Note)
+}
diff --git a/services/webhook/sourcehut/testdata/repo.git/HEAD b/services/webhook/sourcehut/testdata/repo.git/HEAD
new file mode 100644
index 0000000..b870d82
--- /dev/null
+++ b/services/webhook/sourcehut/testdata/repo.git/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/main
diff --git a/services/webhook/sourcehut/testdata/repo.git/config b/services/webhook/sourcehut/testdata/repo.git/config
new file mode 100644
index 0000000..07d359d
--- /dev/null
+++ b/services/webhook/sourcehut/testdata/repo.git/config
@@ -0,0 +1,4 @@
+[core]
+ repositoryformatversion = 0
+ filemode = true
+ bare = true
diff --git a/services/webhook/sourcehut/testdata/repo.git/description b/services/webhook/sourcehut/testdata/repo.git/description
new file mode 100644
index 0000000..498b267
--- /dev/null
+++ b/services/webhook/sourcehut/testdata/repo.git/description
@@ -0,0 +1 @@
+Unnamed repository; edit this file 'description' to name the repository.
diff --git a/services/webhook/sourcehut/testdata/repo.git/info/exclude b/services/webhook/sourcehut/testdata/repo.git/info/exclude
new file mode 100644
index 0000000..a5196d1
--- /dev/null
+++ b/services/webhook/sourcehut/testdata/repo.git/info/exclude
@@ -0,0 +1,6 @@
+# git ls-files --others --exclude-from=.git/info/exclude
+# Lines that start with '#' are comments.
+# For a project mostly in C, the following would be a good set of
+# exclude patterns (uncomment them if you want to use them):
+# *.[oa]
+# *~
diff --git a/services/webhook/sourcehut/testdata/repo.git/objects/3c/3d4b799b3933ba687b263eeef2034300a5315e b/services/webhook/sourcehut/testdata/repo.git/objects/3c/3d4b799b3933ba687b263eeef2034300a5315e
new file mode 100644
index 0000000..f03b45d
--- /dev/null
+++ b/services/webhook/sourcehut/testdata/repo.git/objects/3c/3d4b799b3933ba687b263eeef2034300a5315e
Binary files differ
diff --git a/services/webhook/sourcehut/testdata/repo.git/objects/58/771003157b81abc6bf41df0c5db4147a3e3c83 b/services/webhook/sourcehut/testdata/repo.git/objects/58/771003157b81abc6bf41df0c5db4147a3e3c83
new file mode 100644
index 0000000..e9ff0d0
--- /dev/null
+++ b/services/webhook/sourcehut/testdata/repo.git/objects/58/771003157b81abc6bf41df0c5db4147a3e3c83
@@ -0,0 +1,2 @@
+x=0D=+nBXhVk%?_Pm̔b C̠D{
+;F&qm<5e8|[/ O5 GYK)\ iOKJ3 PƝjU>VX܃絈7\p; \ No newline at end of file
diff --git a/services/webhook/sourcehut/testdata/repo.git/objects/69/b217caa89166a02b8cd368b64fb83a44720e14 b/services/webhook/sourcehut/testdata/repo.git/objects/69/b217caa89166a02b8cd368b64fb83a44720e14
new file mode 100644
index 0000000..1aed811
--- /dev/null
+++ b/services/webhook/sourcehut/testdata/repo.git/objects/69/b217caa89166a02b8cd368b64fb83a44720e14
@@ -0,0 +1 @@
+x=n {)^Z ,EUN}&TAy6aT=ŵĢ5O \m\uFTG׈F;NQ^[֓aQokiW~+ppui ha3J?:7([VK|͙TI7uİӑ>sP =C}ˢO \ No newline at end of file
diff --git a/services/webhook/sourcehut/testdata/repo.git/objects/99/fb389b232e5497f0dcdb1c1065eac1d10d3794 b/services/webhook/sourcehut/testdata/repo.git/objects/99/fb389b232e5497f0dcdb1c1065eac1d10d3794
new file mode 100644
index 0000000..43dd885
--- /dev/null
+++ b/services/webhook/sourcehut/testdata/repo.git/objects/99/fb389b232e5497f0dcdb1c1065eac1d10d3794
Binary files differ
diff --git a/services/webhook/sourcehut/testdata/repo.git/objects/9e/4b777f81b316a1c75a0797b33add68ee49b0d0 b/services/webhook/sourcehut/testdata/repo.git/objects/9e/4b777f81b316a1c75a0797b33add68ee49b0d0
new file mode 100644
index 0000000..081cfcd
--- /dev/null
+++ b/services/webhook/sourcehut/testdata/repo.git/objects/9e/4b777f81b316a1c75a0797b33add68ee49b0d0
Binary files differ
diff --git a/services/webhook/sourcehut/testdata/repo.git/objects/a5/4082fdb8e55055382725f10a81bb4dc2b13029 b/services/webhook/sourcehut/testdata/repo.git/objects/a5/4082fdb8e55055382725f10a81bb4dc2b13029
new file mode 100644
index 0000000..071f79e
--- /dev/null
+++ b/services/webhook/sourcehut/testdata/repo.git/objects/a5/4082fdb8e55055382725f10a81bb4dc2b13029
@@ -0,0 +1,4 @@
+xUn0 wk
+l4z0 %fm~@Dc<(ŝ% m]NjDR
+A閌9Xxu{;Nȅ4(Gy:QO?/9 lh|0cΌl8*$?dԻ**>7ȖXomUJItmKqrh8>)ҺڋF,77,8 {:0zZfya)
+5 ʴ狉7ΑLܯ)z yivoQ78J}臤 \ No newline at end of file
diff --git a/services/webhook/sourcehut/testdata/repo.git/objects/aa/3905af404394f576f88f00e7f0919b4b97453f b/services/webhook/sourcehut/testdata/repo.git/objects/aa/3905af404394f576f88f00e7f0919b4b97453f
new file mode 100644
index 0000000..cc96171
--- /dev/null
+++ b/services/webhook/sourcehut/testdata/repo.git/objects/aa/3905af404394f576f88f00e7f0919b4b97453f
Binary files differ
diff --git a/services/webhook/sourcehut/testdata/repo.git/objects/b0/404943256a1f5a50c3726f4378756b4c1e5704 b/services/webhook/sourcehut/testdata/repo.git/objects/b0/404943256a1f5a50c3726f4378756b4c1e5704
new file mode 100644
index 0000000..a2cff63
--- /dev/null
+++ b/services/webhook/sourcehut/testdata/repo.git/objects/b0/404943256a1f5a50c3726f4378756b4c1e5704
Binary files differ
diff --git a/services/webhook/sourcehut/testdata/repo.git/objects/d2/e0862c8b8097ba4bdd72946c20479751d307a0 b/services/webhook/sourcehut/testdata/repo.git/objects/d2/e0862c8b8097ba4bdd72946c20479751d307a0
new file mode 100644
index 0000000..f57ab8a
--- /dev/null
+++ b/services/webhook/sourcehut/testdata/repo.git/objects/d2/e0862c8b8097ba4bdd72946c20479751d307a0
@@ -0,0 +1,4 @@
+xENIn0 YD#ȁ ۍ,
+"$\f9ئ9~,+L-㒶ɀ=og#&OUo߷jU!,꺮DGP
+e>L狡t[
+#?C~ z2!,qCtQZ<.@78\I \ No newline at end of file
diff --git a/services/webhook/sourcehut/testdata/repo.git/refs/heads/main b/services/webhook/sourcehut/testdata/repo.git/refs/heads/main
new file mode 100644
index 0000000..a7ab419
--- /dev/null
+++ b/services/webhook/sourcehut/testdata/repo.git/refs/heads/main
@@ -0,0 +1 @@
+b0404943256a1f5a50c3726f4378756b4c1e5704