Compare commits
2 Commits
| Author | SHA256 | Date | |
|---|---|---|---|
| 7ec663db27 | |||
| 3b83ba96e4 |
@@ -92,10 +92,13 @@ func ConnectToExchangeForPublish(host, username, password string) {
|
|||||||
auth = username + ":" + password + "@"
|
auth = username + ":" + password + "@"
|
||||||
}
|
}
|
||||||
|
|
||||||
connection, err := rabbitmq.DialTLS("amqps://"+auth+host, &tls.Config{
|
connection, err := rabbitmq.DialConfig("amqps://"+auth+host, rabbitmq.Config{
|
||||||
ServerName: host,
|
Dial: rabbitmq.DefaultDial(10 * time.Second),
|
||||||
|
TLSClientConfig: &tls.Config{
|
||||||
|
ServerName: host,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
failOnError(err, "Cannot connect to rabbit.opensuse.org")
|
failOnError(err, "Cannot connect to "+host)
|
||||||
defer connection.Close()
|
defer connection.Close()
|
||||||
|
|
||||||
ch, err := connection.Channel()
|
ch, err := connection.Channel()
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
OBS Status Service
|
|
||||||
==================
|
|
||||||
|
|
||||||
Caches and reports status of a PR as SVG or JSON
|
|
||||||
|
|
||||||
Requests for individual PRs statuses:
|
|
||||||
GET /${PR_HASH}
|
|
||||||
|
|
||||||
Update requests for individual PRs statuses:
|
|
||||||
POST /${PR_HASH}
|
|
||||||
|
|
||||||
POST requires cert auth to function.
|
|
||||||
|
|
||||||
|
|
||||||
Areas of Responsibility
|
|
||||||
-----------------------
|
|
||||||
|
|
||||||
* Listens for PR status reports from workflow-pr bot (or other interface)
|
|
||||||
* Produces SVG output based on GET request
|
|
||||||
* Produces JSON output based on GET request
|
|
||||||
|
|
||||||
|
|
||||||
Target Usage
|
|
||||||
------------
|
|
||||||
|
|
||||||
* comment section of a PR
|
|
||||||
* 3rd party tooling
|
|
||||||
|
|
||||||
|
|
||||||
PR Encoding
|
|
||||||
-----------
|
|
||||||
|
|
||||||
PRs are encoded as SHA256 hashes with a salt. This allows the existence of
|
|
||||||
individual PRs to remain secret while the hash is known to tools that have
|
|
||||||
read access to the PRs
|
|
||||||
|
|
||||||
Encoding data is input into the sha256 hash as follows:
|
|
||||||
|
|
||||||
* Salt string, min 100 bytes.
|
|
||||||
* Organization name of the PR
|
|
||||||
* Repository name of the PR
|
|
||||||
* PR number (decimal string)
|
|
||||||
|
|
||||||
Encoded PR is then passed to process as mime64 encoded without trailing =.
|
|
||||||
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of Autogits.
|
|
||||||
*
|
|
||||||
* Copyright © 2024 SUSE LLC
|
|
||||||
*
|
|
||||||
* Autogits is free software: you can redistribute it and/or modify it under
|
|
||||||
* the terms of the GNU General Public License as published by the Free Software
|
|
||||||
* Foundation, either version 2 of the License, or (at your option) any later
|
|
||||||
* version.
|
|
||||||
*
|
|
||||||
* Autogits is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
||||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
|
||||||
* PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License along with
|
|
||||||
* Foobar. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"flag"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"src.opensuse.org/autogits/common"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
AppName = "obs-status-service"
|
|
||||||
)
|
|
||||||
|
|
||||||
var obs *common.ObsClient
|
|
||||||
var debug bool
|
|
||||||
var salt string
|
|
||||||
var RabbitMQHost, Topic, orgs *string
|
|
||||||
|
|
||||||
func LogDebug(v ...any) {
|
|
||||||
if debug {
|
|
||||||
log.Println(v...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
cert := flag.String("cert-file", "", "TLS certificates file")
|
|
||||||
key := flag.String("key-file", "", "Private key for the TLS certificate")
|
|
||||||
listen := flag.String("listen", "[::1]:8080", "Listening string")
|
|
||||||
disableTls := flag.Bool("no-tls", false, "Disable TLS")
|
|
||||||
obsHost := flag.String("obs-host", "https://api.opensuse.org", "OBS API endpoint for package status information")
|
|
||||||
flag.BoolVar(&debug, "debug", false, "Enable debug logging")
|
|
||||||
RabbitMQHost = flag.String("rabbit-mq", "amqps://rabbit.opensuse.org", "RabbitMQ message bus server")
|
|
||||||
Topic = flag.String("topic", "opensuse.obs", "RabbitMQ topic prefix")
|
|
||||||
orgs = flag.String("orgs", "opensuse", "Comma separated list of orgs to watch")
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
salt = os.Getenv("PR_STATUS_SALT")
|
|
||||||
if len(salt) < 100 {
|
|
||||||
log.Fatal("PR_STATUS_SALT must be at least 100 bytes")
|
|
||||||
}
|
|
||||||
|
|
||||||
common.PanicOnError(common.RequireObsSecretToken())
|
|
||||||
|
|
||||||
var err error
|
|
||||||
if obs, err = common.NewObsClient(*obsHost); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
http.HandleFunc("GET /{PR_HASH}", func(res http.ResponseWriter, req *http.Request) {
|
|
||||||
hash := req.PathValue("PR_HASH")
|
|
||||||
isJSON := strings.HasSuffix(hash, ".json") || req.Header.Get("Accept") == "application/json"
|
|
||||||
hash = strings.TrimSuffix(hash, ".json")
|
|
||||||
hash = strings.TrimSuffix(hash, ".svg")
|
|
||||||
|
|
||||||
status := GetPRStatus(hash)
|
|
||||||
if status == nil {
|
|
||||||
res.WriteHeader(http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if isJSON {
|
|
||||||
res.Header().Set("Content-Type", "application/json")
|
|
||||||
json.NewEncoder(res).Encode(status)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// default SVG
|
|
||||||
res.Header().Set("Content-Type", "image/svg+xml")
|
|
||||||
res.Write([]byte(status.ToSVG()))
|
|
||||||
})
|
|
||||||
|
|
||||||
http.HandleFunc("POST /{PR_HASH}", func(res http.ResponseWriter, req *http.Request) {
|
|
||||||
hash := req.PathValue("PR_HASH")
|
|
||||||
|
|
||||||
var status PRStatus
|
|
||||||
if err := json.NewDecoder(req.Body).Decode(&status); err != nil {
|
|
||||||
res.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
UpdatePRStatus(hash, &status)
|
|
||||||
res.WriteHeader(http.StatusNoContent)
|
|
||||||
})
|
|
||||||
|
|
||||||
go ProcessUpdates()
|
|
||||||
|
|
||||||
if *disableTls {
|
|
||||||
log.Fatal(http.ListenAndServe(*listen, nil))
|
|
||||||
} else {
|
|
||||||
log.Fatal(http.ListenAndServeTLS(*listen, *cert, *key, nil))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"src.opensuse.org/autogits/common"
|
|
||||||
)
|
|
||||||
|
|
||||||
type PRHandler struct{}
|
|
||||||
|
|
||||||
func (h *PRHandler) ProcessFunc(request *common.Request) error {
|
|
||||||
event, ok := request.Data.(*common.PullRequestWebhookEvent)
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
org := event.Repository.Owner.Username
|
|
||||||
repo := event.Repository.Name
|
|
||||||
prNum := fmt.Sprint(event.Number)
|
|
||||||
hash := CalculatePRHash(salt, org, repo, prNum)
|
|
||||||
|
|
||||||
status := &PRStatus{
|
|
||||||
PR: fmt.Sprintf("%s/%s#%s", org, repo, prNum),
|
|
||||||
IsReviewed: false,
|
|
||||||
IsMergeable: event.Pull_Request.State == "open",
|
|
||||||
MergeStatus: event.Pull_Request.State,
|
|
||||||
}
|
|
||||||
|
|
||||||
UpdatePRStatus(hash, status)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type ReviewHandler struct {
|
|
||||||
Approved bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ReviewHandler) ProcessFunc(request *common.Request) error {
|
|
||||||
event, ok := request.Data.(*common.PullRequestWebhookEvent)
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
org := event.Repository.Owner.Username
|
|
||||||
repo := event.Repository.Name
|
|
||||||
prNum := fmt.Sprint(event.Number)
|
|
||||||
hash := CalculatePRHash(salt, org, repo, prNum)
|
|
||||||
|
|
||||||
status := GetPRStatus(hash)
|
|
||||||
if status == nil {
|
|
||||||
status = &PRStatus{
|
|
||||||
PR: fmt.Sprintf("%s/%s#%s", org, repo, prNum),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
status.IsReviewed = true
|
|
||||||
isApproved := IsApproved_No
|
|
||||||
if h.Approved {
|
|
||||||
isApproved = IsApproved_Yes
|
|
||||||
status.MergeStatus = "approved"
|
|
||||||
} else {
|
|
||||||
status.MergeStatus = "rejected"
|
|
||||||
}
|
|
||||||
|
|
||||||
found := false
|
|
||||||
for i, r := range status.Reviews {
|
|
||||||
if r.Reviewer == event.Sender.Username {
|
|
||||||
status.Reviews[i].IsApproved = isApproved
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
status.Reviews = append(status.Reviews, ReviewStatus{
|
|
||||||
Reviewer: event.Sender.Username,
|
|
||||||
IsApproved: isApproved,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
UpdatePRStatus(hash, status)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ProcessUpdates() {
|
|
||||||
if RabbitMQHost == nil || *RabbitMQHost == "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
u, err := url.Parse(*RabbitMQHost)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
processor := &common.RabbitMQGiteaEventsProcessor{
|
|
||||||
Handlers: map[string]common.RequestProcessor{
|
|
||||||
common.RequestType_PR: &PRHandler{},
|
|
||||||
common.RequestType_PRReviewAccepted: &ReviewHandler{Approved: true},
|
|
||||||
common.RequestType_PRReviewRejected: &ReviewHandler{Approved: false},
|
|
||||||
},
|
|
||||||
Orgs: strings.Split(*orgs, ","),
|
|
||||||
}
|
|
||||||
processor.Connection().RabbitURL = u
|
|
||||||
|
|
||||||
err = common.ProcessRabbitMQEvents(processor)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/sha256"
|
|
||||||
"encoding/base64"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
IsApproved_Pending = 0
|
|
||||||
IsApproved_Yes = 1
|
|
||||||
IsApproved_No = 2
|
|
||||||
)
|
|
||||||
|
|
||||||
type ReviewStatus struct {
|
|
||||||
Reviewer string
|
|
||||||
IsApproved int
|
|
||||||
}
|
|
||||||
|
|
||||||
type PRStatus struct {
|
|
||||||
PR string
|
|
||||||
IsReviewed bool
|
|
||||||
IsMergeable bool
|
|
||||||
|
|
||||||
MergeStatus string
|
|
||||||
Reviews []ReviewStatus
|
|
||||||
}
|
|
||||||
|
|
||||||
func (status *PRStatus) ToSVG() string {
|
|
||||||
mergeableText := "NO"
|
|
||||||
mergeableColor := "red"
|
|
||||||
if status.IsMergeable {
|
|
||||||
mergeableText = "YES"
|
|
||||||
mergeableColor = "green"
|
|
||||||
}
|
|
||||||
|
|
||||||
reviewedText := "NO"
|
|
||||||
reviewedColor := "red"
|
|
||||||
if status.IsReviewed {
|
|
||||||
reviewedText = "YES"
|
|
||||||
reviewedColor = "green"
|
|
||||||
}
|
|
||||||
|
|
||||||
var reviewsBuilder strings.Builder
|
|
||||||
for i, r := range status.Reviews {
|
|
||||||
color := "orange"
|
|
||||||
if r.IsApproved == IsApproved_Yes {
|
|
||||||
color = "green"
|
|
||||||
} else if r.IsApproved == IsApproved_No {
|
|
||||||
color = "red"
|
|
||||||
}
|
|
||||||
if i > 0 {
|
|
||||||
reviewsBuilder.WriteString(", ")
|
|
||||||
}
|
|
||||||
fmt.Fprintf(&reviewsBuilder, `<tspan fill="%s">%s</tspan>`, color, r.Reviewer)
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf(`<svg xmlns="http://www.w3.org/2000/svg" width="400" height="100">
|
|
||||||
<rect width="400" height="100" fill="#f8f9fa" stroke="#dee2e6"/>
|
|
||||||
<text x="10" y="25" font-family="sans-serif" font-size="14" fill="#333">Is Mergeable: <tspan fill="%s" font-weight="bold">%s</tspan></text>
|
|
||||||
<text x="10" y="45" font-family="sans-serif" font-size="14" fill="#333">Is Reviewed?: <tspan fill="%s" font-weight="bold">%s</tspan></text>
|
|
||||||
<text x="10" y="65" font-family="sans-serif" font-size="14" fill="#333">Merge Status: %s</text>
|
|
||||||
<text x="10" y="85" font-family="sans-serif" font-size="14" fill="#333">Reviews: %s</text>
|
|
||||||
</svg>`, mergeableColor, mergeableText, reviewedColor, reviewedText, status.MergeStatus, reviewsBuilder.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
var prStatuses = make(map[string]*PRStatus)
|
|
||||||
var prStatusesLock sync.RWMutex
|
|
||||||
|
|
||||||
func GetPRStatus(hash string) *PRStatus {
|
|
||||||
prStatusesLock.RLock()
|
|
||||||
defer prStatusesLock.RUnlock()
|
|
||||||
return prStatuses[hash]
|
|
||||||
}
|
|
||||||
|
|
||||||
func UpdatePRStatus(hash string, status *PRStatus) {
|
|
||||||
prStatusesLock.Lock()
|
|
||||||
defer prStatusesLock.Unlock()
|
|
||||||
prStatuses[hash] = status
|
|
||||||
}
|
|
||||||
|
|
||||||
func CalculatePRHash(salt, org, repo, prNum string) string {
|
|
||||||
h := sha256.New()
|
|
||||||
h.Write([]byte(salt))
|
|
||||||
h.Write([]byte(org))
|
|
||||||
h.Write([]byte(repo))
|
|
||||||
h.Write([]byte(prNum))
|
|
||||||
return base64.RawStdEncoding.EncodeToString(h.Sum(nil))
|
|
||||||
}
|
|
||||||
@@ -1,185 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"src.opensuse.org/autogits/common"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestCalculatePRHash(t *testing.T) {
|
|
||||||
salt = "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" // 100 bytes
|
|
||||||
org := "opensuse"
|
|
||||||
repo := "pr-status-service"
|
|
||||||
prNum := "1"
|
|
||||||
|
|
||||||
hash := CalculatePRHash(salt, org, repo, prNum)
|
|
||||||
if hash == "" {
|
|
||||||
t.Errorf("Hash is empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
hash2 := CalculatePRHash(salt, org, repo, prNum)
|
|
||||||
if hash != hash2 {
|
|
||||||
t.Errorf("Hash is not consistent")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Different inputs should give different hashes
|
|
||||||
hash3 := CalculatePRHash(salt, org, repo, "2")
|
|
||||||
if hash == hash3 {
|
|
||||||
t.Errorf("Hashes for different PR numbers are same")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPRStatusToSVG(t *testing.T) {
|
|
||||||
status := &PRStatus{
|
|
||||||
PR: "org/repo#1",
|
|
||||||
IsMergeable: true,
|
|
||||||
IsReviewed: true,
|
|
||||||
MergeStatus: "can be merged",
|
|
||||||
Reviews: []ReviewStatus{
|
|
||||||
{Reviewer: "alice", IsApproved: IsApproved_Yes},
|
|
||||||
{Reviewer: "bob", IsApproved: IsApproved_No},
|
|
||||||
{Reviewer: "charlie", IsApproved: IsApproved_Pending},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
svg := status.ToSVG()
|
|
||||||
|
|
||||||
// Check for key elements in the SVG
|
|
||||||
expectedStrings := []string{
|
|
||||||
`Is Mergeable: <tspan fill="green" font-weight="bold">YES</tspan>`,
|
|
||||||
`Is Reviewed?: <tspan fill="green" font-weight="bold">YES</tspan>`,
|
|
||||||
`Merge Status: can be merged`,
|
|
||||||
`<tspan fill="green">alice</tspan>`,
|
|
||||||
`<tspan fill="red">bob</tspan>`,
|
|
||||||
`<tspan fill="orange">charlie</tspan>`,
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, expected := range expectedStrings {
|
|
||||||
if !contains(svg, expected) {
|
|
||||||
t.Errorf("SVG missing expected string: %s", expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPRStatusStore(t *testing.T) {
|
|
||||||
hash := "test-hash"
|
|
||||||
status := &PRStatus{PR: "org/repo#123"}
|
|
||||||
|
|
||||||
UpdatePRStatus(hash, status)
|
|
||||||
|
|
||||||
ret := GetPRStatus(hash)
|
|
||||||
if ret == nil || ret.PR != status.PR {
|
|
||||||
t.Errorf("Retrieved status does not match stored status")
|
|
||||||
}
|
|
||||||
|
|
||||||
retNil := GetPRStatus("non-existent")
|
|
||||||
if retNil != nil {
|
|
||||||
t.Errorf("Expected nil for non-existent hash")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHandlers(t *testing.T) {
|
|
||||||
salt = "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"
|
|
||||||
|
|
||||||
repo := &common.Repository{
|
|
||||||
Name: "test-repo",
|
|
||||||
Owner: &common.Organization{
|
|
||||||
Username: "test-org",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
prHandler := &PRHandler{}
|
|
||||||
prEvent := &common.PullRequestWebhookEvent{
|
|
||||||
Number: 1,
|
|
||||||
Repository: repo,
|
|
||||||
Pull_Request: &common.PullRequest{
|
|
||||||
State: "open",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
err := prHandler.ProcessFunc(&common.Request{Data: prEvent})
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("PRHandler error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
hash := CalculatePRHash(salt, "test-org", "test-repo", "1")
|
|
||||||
status := GetPRStatus(hash)
|
|
||||||
if status == nil {
|
|
||||||
t.Fatalf("Status not found after PRHandler")
|
|
||||||
}
|
|
||||||
|
|
||||||
if status.IsMergeable != true {
|
|
||||||
t.Errorf("Expected mergeable true")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test ReviewHandler
|
|
||||||
reviewHandler := &ReviewHandler{Approved: true}
|
|
||||||
reviewEvent := &common.PullRequestWebhookEvent{
|
|
||||||
Number: 1,
|
|
||||||
Repository: repo,
|
|
||||||
Sender: common.User{
|
|
||||||
Username: "reviewer1",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
err = reviewHandler.ProcessFunc(&common.Request{Data: reviewEvent})
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("ReviewHandler error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
status = GetPRStatus(hash)
|
|
||||||
if !status.IsReviewed {
|
|
||||||
t.Errorf("Expected IsReviewed true")
|
|
||||||
}
|
|
||||||
if len(status.Reviews) != 1 || status.Reviews[0].Reviewer != "reviewer1" || status.Reviews[0].IsApproved != IsApproved_Yes {
|
|
||||||
t.Errorf("Review not correctly recorded")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReviewUpdate(t *testing.T) {
|
|
||||||
repo := &common.Repository{
|
|
||||||
Name: "test-repo",
|
|
||||||
Owner: &common.Organization{
|
|
||||||
Username: "test-org",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
hash := CalculatePRHash(salt, "test-org", "test-repo", "1")
|
|
||||||
UpdatePRStatus(hash, &PRStatus{PR: "test-org/test-repo#1"})
|
|
||||||
|
|
||||||
reviewHandler := &ReviewHandler{Approved: true}
|
|
||||||
reviewEvent := &common.PullRequestWebhookEvent{
|
|
||||||
Number: 1,
|
|
||||||
Repository: repo,
|
|
||||||
Sender: common.User{
|
|
||||||
Username: "reviewer1",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
reviewHandler.ProcessFunc(&common.Request{Data: reviewEvent})
|
|
||||||
|
|
||||||
// Update same review
|
|
||||||
reviewHandler.Approved = false
|
|
||||||
reviewHandler.ProcessFunc(&common.Request{Data: reviewEvent})
|
|
||||||
|
|
||||||
status := GetPRStatus(hash)
|
|
||||||
if len(status.Reviews) != 1 || status.Reviews[0].IsApproved != IsApproved_No {
|
|
||||||
t.Errorf("Review not correctly updated")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHandlerErrors(t *testing.T) {
|
|
||||||
prHandler := &PRHandler{}
|
|
||||||
err := prHandler.ProcessFunc(&common.Request{Data: nil})
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Expected nil error for nil data in PRHandler")
|
|
||||||
}
|
|
||||||
|
|
||||||
reviewHandler := &ReviewHandler{}
|
|
||||||
err = reviewHandler.ProcessFunc(&common.Request{Data: nil})
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Expected nil error for nil data in ReviewHandler")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func contains(s, substr string) bool {
|
|
||||||
return len(s) >= len(substr) && (s == substr || (len(substr) > 0 && (s[:len(substr)] == substr || contains(s[1:], substr))))
|
|
||||||
}
|
|
||||||
@@ -470,7 +470,8 @@ func (pr *PRProcessor) Process(req *models.PullRequest) error {
|
|||||||
if pr.PR.State == "open" {
|
if pr.PR.State == "open" {
|
||||||
org, repo, idx := pr.PRComponents()
|
org, repo, idx := pr.PRComponents()
|
||||||
if prjGitPR.PR.HasMerged {
|
if prjGitPR.PR.HasMerged {
|
||||||
Gitea.AddComment(pr.PR, "This PR is merged via the associated Project PR.")
|
// TODO: use timeline here because this can spam if ManualMergePR fails
|
||||||
|
// Gitea.AddComment(pr.PR, "This PR is merged via the associated Project PR.")
|
||||||
err = Gitea.ManualMergePR(org, repo, idx, pr.PR.Head.Sha, false)
|
err = Gitea.ManualMergePR(org, repo, idx, pr.PR.Head.Sha, false)
|
||||||
if _, ok := err.(*repository.RepoMergePullRequestConflict); !ok {
|
if _, ok := err.(*repository.RepoMergePullRequestConflict); !ok {
|
||||||
common.PanicOnError(err)
|
common.PanicOnError(err)
|
||||||
|
|||||||
Reference in New Issue
Block a user