manual-approval/approval_test.go
jaime merino 9e098fbb6e - Fixed Approver Expansion: Repaired the broken expandGroupFromUser function in approvers.go. It now correctly utilizes the Forgejo SDK to search for teams within an organization and expand them into individual users, while respecting the option to exclude the workflow initiator.
- Removed GitHub Dependencies: Excised the unused newGithubClient and its associated dependencies (google/go-github and oauth2) from main.go and go.mod.

   - Project Infrastructure Updates:
       - Updated Dockerfile to use golang:1.25 for consistency with go.mod.
       - Modified action.yaml to run from the local Dockerfile, ensuring that the Forgejo-specific logic is utilized.
   - Validation: Verified all changes through a successful build and by running the full test suite, all of which passed.
2026-04-22 15:28:23 +02:00

505 lines
12 KiB
Go

package main
import (
"errors"
"os"
"strings"
"testing"
"codeberg.org/mvdkleijn/forgejo-sdk/forgejo/v3"
)
func TestApprovalFromComments(t *testing.T) {
login1 := "login1"
login2 := "login2"
login3 := "login3"
bodyApproved := "Approved"
bodyDenied := "Denied"
bodyPending := "not approval or denial"
login1u := strings.ToUpper(login1)
testCases := []struct {
name string
comments []*forgejo.Comment
approvers []string
minimumApprovals int
expectedStatus approvalStatus
}{
{
name: "single_approver_single_comment_approved",
comments: []*forgejo.Comment{
{
Poster: &forgejo.User{UserName: login1},
Body: bodyApproved,
},
},
approvers: []string{login1},
expectedStatus: approvalStatusApproved,
},
{
name: "single_approver_single_comment_denied",
comments: []*forgejo.Comment{
{
Poster: &forgejo.User{UserName: login1},
Body: bodyDenied,
},
},
approvers: []string{login1},
expectedStatus: approvalStatusDenied,
},
{
name: "single_approver_single_comment_pending",
comments: []*forgejo.Comment{
{
Poster: &forgejo.User{UserName: login1},
Body: bodyPending,
},
},
approvers: []string{login1},
expectedStatus: approvalStatusPending,
},
{
name: "single_approver_multi_comment_approved",
comments: []*forgejo.Comment{
{
Poster: &forgejo.User{UserName: login1},
Body: bodyPending,
},
{
Poster: &forgejo.User{UserName: login1},
Body: bodyApproved,
},
},
approvers: []string{login1},
expectedStatus: approvalStatusApproved,
},
{
name: "multi_approver_approved",
comments: []*forgejo.Comment{
{
Poster: &forgejo.User{UserName: login1},
Body: bodyApproved,
},
{
Poster: &forgejo.User{UserName: login2},
Body: bodyApproved,
},
},
approvers: []string{login1, login2},
expectedStatus: approvalStatusApproved,
},
{
name: "multi_approver_mixed",
comments: []*forgejo.Comment{
{
Poster: &forgejo.User{UserName: login1},
Body: bodyPending,
},
{
Poster: &forgejo.User{UserName: login2},
Body: bodyApproved,
},
},
approvers: []string{login1, login2},
expectedStatus: approvalStatusPending,
},
{
name: "multi_approver_denied",
comments: []*forgejo.Comment{
{
Poster: &forgejo.User{UserName: login1},
Body: bodyDenied,
},
{
Poster: &forgejo.User{UserName: login2},
Body: bodyApproved,
},
},
approvers: []string{login1, login2},
expectedStatus: approvalStatusDenied,
},
{
name: "multi_approver_minimum_one_approval",
comments: []*forgejo.Comment{
{
Poster: &forgejo.User{UserName: login1},
Body: bodyPending,
},
{
Poster: &forgejo.User{UserName: login2},
Body: bodyApproved,
},
},
approvers: []string{login1, login2},
expectedStatus: approvalStatusApproved,
minimumApprovals: 1,
},
{
name: "multi_approver_minimum_two_approvals",
comments: []*forgejo.Comment{
{
Poster: &forgejo.User{UserName: login1},
Body: bodyApproved,
},
{
Poster: &forgejo.User{UserName: login2},
Body: bodyApproved,
},
},
approvers: []string{login1, login2, login3},
expectedStatus: approvalStatusApproved,
minimumApprovals: 2,
},
{
name: "multi_approver_approvals_less_than_minimum",
comments: []*forgejo.Comment{
{
Poster: &forgejo.User{UserName: login1},
Body: bodyApproved,
},
},
approvers: []string{login1, login2, login3},
expectedStatus: approvalStatusPending,
minimumApprovals: 2,
},
{
name: "single_approver_single_comment_approved_case_insensitive",
comments: []*forgejo.Comment{
{
Poster: &forgejo.User{UserName: login1u},
Body: bodyApproved,
},
},
approvers: []string{login1},
expectedStatus: approvalStatusApproved,
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
actual, err := approvalFromComments(testCase.comments, testCase.approvers, testCase.minimumApprovals)
if err != nil {
t.Fatalf("error getting approval from comments: %v", err)
}
if actual != testCase.expectedStatus {
t.Fatalf("actual %s, expected %s", actual, testCase.expectedStatus)
}
})
}
}
func TestApprovedCommentBody(t *testing.T) {
testCases := []struct {
name string
commentBody string
isSuccess bool
customApprovalWord string
}{
{
name: "approved_lowercase_no_punctuation",
commentBody: "approved",
isSuccess: true,
customApprovalWord: "",
},
{
name: "approve_lowercase_no_punctuation",
commentBody: "approve",
isSuccess: true,
customApprovalWord: "",
},
{
name: "lgtm_lowercase_no_punctuation",
commentBody: "lgtm",
isSuccess: true,
customApprovalWord: "",
},
{
name: "yes_lowercase_no_punctuation",
commentBody: "yes",
isSuccess: true,
customApprovalWord: "",
},
{
name: "approve_uppercase_no_punctuation",
commentBody: "APPROVE",
isSuccess: true,
customApprovalWord: "",
},
{
name: "approved_titlecase_period",
commentBody: "Approved.",
isSuccess: true,
customApprovalWord: "",
},
{
name: "approved_titlecase_exclamation",
commentBody: "Approved!",
isSuccess: true,
customApprovalWord: "",
},
{
name: "approved_titlecase_multi_exclamation",
commentBody: "Approved!!",
isSuccess: true,
customApprovalWord: "",
},
{
name: "approved_titlecase_question",
commentBody: "Approved?",
isSuccess: false,
customApprovalWord: "",
},
{
name: "sentence_with_keyword",
commentBody: "should i approve this",
isSuccess: false,
customApprovalWord: "",
},
{
name: "sentence_without_keyword",
commentBody: "this is just some random comment",
isSuccess: false,
customApprovalWord: "",
},
{
name: "approved_with_newline",
commentBody: "approved\n",
isSuccess: true,
customApprovalWord: "",
},
{
name: "approved_with_exclamation_newline",
commentBody: "approved!\n",
isSuccess: true,
customApprovalWord: "",
},
{
name: "approved_with_multi_exclamation_multi_newline",
commentBody: "approved!!!\n\n\n",
isSuccess: true,
customApprovalWord: "",
},
{
name: "approved_with_custom_approval_word",
commentBody: "shipit",
isSuccess: true,
customApprovalWord: "shipit",
},
{
name: "approved_with_github_emoji_syntax",
commentBody: ":shipit:",
isSuccess: true,
customApprovalWord: ":shipit:",
},
{
name: "approved_with_custom_hashtag",
commentBody: "#shipit",
isSuccess: true,
customApprovalWord: "#shipit",
},
{
name: "approved_with_actual_emoji_✅",
commentBody: "✅ ",
isSuccess: true,
customApprovalWord: "✅",
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
// before each
word := testCase.customApprovalWord
if len(word) > 0 {
approvedWords = append(approvedWords, word)
}
// test
actual, err := isApproved(testCase.commentBody)
if err != nil {
t.Fatalf("error getting approval: %v", err)
}
if actual != testCase.isSuccess {
t.Fatalf("expected %v but got %v", testCase.isSuccess, actual)
}
// after each
if len(word) > 0 {
approvedWords = approvedWords[:len(approvedWords)-1]
}
})
}
}
func TestDeniedCommentBody(t *testing.T) {
testCases := []struct {
name string
commentBody string
isSuccess bool
customDenialWord string
}{
{
name: "denied_lowercase_no_punctuation",
commentBody: "denied",
isSuccess: true,
customDenialWord: "",
},
{
name: "deny_lowercase_no_punctuation",
commentBody: "deny",
isSuccess: true,
customDenialWord: "",
},
{
name: "no_lowercase_no_punctuation",
commentBody: "no",
isSuccess: true,
customDenialWord: "",
},
{
name: "deny_uppercase_no_punctuation",
commentBody: "DENY",
isSuccess: true,
customDenialWord: "",
},
{
name: "denied_titlecase_period",
commentBody: "Denied.",
isSuccess: true,
customDenialWord: "",
},
{
name: "denied_titlecase_exclamation",
commentBody: "Denied!",
isSuccess: true,
customDenialWord: "",
},
{
name: "deny_titlecase_question",
commentBody: "Deny?",
isSuccess: false,
customDenialWord: "",
},
{
name: "sentence_with_keyword",
commentBody: "should i deny this",
isSuccess: false,
customDenialWord: "",
},
{
name: "sentence_without_keyword",
commentBody: "this is just some random comment",
isSuccess: false,
customDenialWord: "",
},
{
name: "denied_with_newline",
commentBody: "denied\n",
isSuccess: true,
customDenialWord: "",
},
{
name: "denied_with_custom_word",
commentBody: "naw",
isSuccess: true,
customDenialWord: "naw",
},
{
name: "denied_with_github_emoji",
commentBody: ":no_entry_sign: ",
isSuccess: true,
customDenialWord: ":no_entry_sign:",
},
{
name: "denied_with_hashtag",
commentBody: "#noway",
isSuccess: true,
customDenialWord: "#noway",
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
// before each
word := testCase.customDenialWord
if len(word) > 0 {
deniedWords = append(deniedWords, word)
}
// test
actual, err := isDenied(testCase.commentBody)
if err != nil {
t.Fatalf("error getting approval: %v", err)
}
if actual != testCase.isSuccess {
t.Fatalf("expected %v but got %v", testCase.isSuccess, actual)
}
// after each
if len(word) > 0 {
deniedWords = deniedWords[:len(deniedWords)-1]
}
})
}
}
func TestSaveOutput(t *testing.T) {
testCases := []struct {
name string
approvalIssueNumber int
env_github_output string
isSuccess bool
}{
{
name: "save_output_with_env",
approvalIssueNumber: 123,
env_github_output: "./output.txt",
isSuccess: true,
},
{
name: "fail_save_output_without_env",
approvalIssueNumber: 123,
env_github_output: "",
isSuccess: false,
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
t.Setenv("GITHUB_OUTPUT", testCase.env_github_output)
a := approvalEnvironment{
forgejoClient: nil,
repoFullName: "",
repo: "",
repoOwner: "",
runID: -1,
approvalIssueNumber: testCase.approvalIssueNumber,
issueTitle: "",
issueBody: "",
issueApprovers: nil,
minimumApprovals: 0,
}
if err := os.Remove(testCase.env_github_output); err != nil && !os.IsNotExist(err) {
t.Fatalf("failed to remove file: %v", err)
}
actual, err := a.SetActionOutputs(nil)
if err != nil {
t.Fatalf("error creating output file: %v: %v", testCase.env_github_output, err)
}
if actual != testCase.isSuccess {
t.Fatalf("expected %v but got %v", testCase.isSuccess, actual)
}
if actual == true {
if _, err := os.Stat(testCase.env_github_output); errors.Is(err, os.ErrNotExist) {
t.Fatalf("expected create output file %v but it was not", testCase.env_github_output)
}
}
})
}
}