blob: 0ddc9c0f3e661b130f8a7d5f5199eaffd4ba2523 [file]
// Copyright 2026 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
package pw_ghish
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
"testing"
"github.com/andygrunwald/go-gerrit"
)
func TestReviewCmd_Approve(t *testing.T) {
server := NewMockGerritServer(t)
server.OnJSON("POST", "/changes/123/revisions/current/review", http.StatusOK, map[string]any{})
cmd := newReviewCmd()
cmd.SetContext(context.Background())
cmd.SetArgs([]string{"123", "--approve", "-m", "Looks good"})
if err := cmd.Execute(); err != nil {
t.Fatalf("cmd.Execute failed: %v", err)
}
var capturedInput gerrit.ReviewInput
if req := server.LastRequest(); req != nil {
json.Unmarshal(req.Body, &capturedInput)
}
if capturedInput.Message != "Looks good" {
t.Errorf("got message %q, want %q", capturedInput.Message, "Looks good")
}
if capturedInput.Labels["Code-Review"] != 2 {
t.Errorf("got Code-Review %d, want 2", capturedInput.Labels["Code-Review"])
}
}
func TestReviewCmd_RequestChanges(t *testing.T) {
server := NewMockGerritServer(t)
server.OnJSON("POST", "/changes/456/revisions/current/review", http.StatusOK, map[string]any{})
cmd := newReviewCmd()
cmd.SetContext(context.Background())
cmd.SetArgs([]string{"456", "--request-changes", "-m", "Needs fixes"})
if err := cmd.Execute(); err != nil {
t.Fatalf("cmd.Execute failed: %v", err)
}
var capturedInput gerrit.ReviewInput
if req := server.LastRequest(); req != nil {
json.Unmarshal(req.Body, &capturedInput)
}
if capturedInput.Message != "Needs fixes" {
t.Errorf("got message %q, want %q", capturedInput.Message, "Needs fixes")
}
if capturedInput.Labels["Code-Review"] != -1 {
t.Errorf("got Code-Review %d, want -1", capturedInput.Labels["Code-Review"])
}
}
func TestReviewCmd_ErrorWhenBothApproveAndRequestChanges(t *testing.T) {
cmd := newReviewCmd()
cmd.SetContext(context.Background())
cmd.SetArgs([]string{"123", "--approve", "--request-changes"})
err := cmd.Execute()
if err == nil {
t.Fatal("Expected error when both --approve and --request-changes are specified, got nil")
}
}
func TestReviewCmd_DefaultActivePR(t *testing.T) {
server := NewMockGerritServer(t)
server.OnJSON("POST", "/changes/I1234567890abcdef1234567890abcdef12345678/revisions/current/review", http.StatusOK, map[string]any{})
mockGit := SetMockGit(t, &MockGitRunner{
RunFn: func(ctx context.Context, stdout, stderr io.Writer, args ...string) error {
if len(args) >= 2 && args[0] == "branch" && args[1] == "--show-current" {
stdout.Write([]byte("my-feature\n"))
return nil
}
if len(args) >= 3 && args[0] == "log" && args[1] == "-1" {
stdout.Write([]byte("Commit on feature branch\n\nChange-Id: I1234567890abcdef1234567890abcdef12345678\n"))
return nil
}
return fmt.Errorf("not handled")
},
})
cmd := newReviewCmd()
cmd.SetContext(context.Background())
SetConfig(cmd, &Config{Git: mockGit})
cmd.SetArgs([]string{"--approve", "-m", "LGTM from active branch"})
if err := cmd.Execute(); err != nil {
t.Fatalf("cmd.Execute failed: %v", err)
}
req := server.LastRequest()
if req == nil || req.Path != "/changes/I1234567890abcdef1234567890abcdef12345678/revisions/current/review" {
t.Errorf("got path %v, want /changes/I1234567890abcdef1234567890abcdef12345678/revisions/current/review", req)
}
var capturedInput gerrit.ReviewInput
if req != nil {
json.Unmarshal(req.Body, &capturedInput)
}
if capturedInput.Message != "LGTM from active branch" {
t.Errorf("got message %q, want %q", capturedInput.Message, "LGTM from active branch")
}
if capturedInput.Labels["Code-Review"] != 2 {
t.Errorf("got Code-Review %d, want 2", capturedInput.Labels["Code-Review"])
}
}
func TestReviewCmd_SpecificPatchset(t *testing.T) {
server := NewMockGerritServer(t)
server.OnJSON("POST", "/changes/472267/revisions/3/review", http.StatusOK, map[string]any{})
cmd := newReviewCmd()
cmd.SetContext(context.Background())
cmd.SetArgs([]string{"472267/3", "--approve"})
if err := cmd.Execute(); err != nil {
t.Fatalf("cmd.Execute failed: %v", err)
}
if server.CallCount("POST", "/changes/472267/revisions/3/review") != 1 {
t.Errorf("expected 1 call to /changes/472267/revisions/3/review, got %d", server.CallCount("POST", "/changes/472267/revisions/3/review"))
}
}
func TestReviewCmd_URL(t *testing.T) {
server := NewMockGerritServer(t)
server.OnJSON("POST", "/changes/472267/revisions/4/review", http.StatusOK, map[string]any{})
cmd := newReviewCmd()
cmd.SetContext(context.Background())
cmd.SetArgs([]string{"https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/472267/4", "--approve"})
if err := cmd.Execute(); err != nil {
t.Fatalf("cmd.Execute failed: %v", err)
}
if server.CallCount("POST", "/changes/472267/revisions/4/review") != 1 {
t.Errorf("expected 1 call to /changes/472267/revisions/4/review, got %d", server.CallCount("POST", "/changes/472267/revisions/4/review"))
}
}
func TestReviewCmd_ErrorWhenNoFlagsSpecified(t *testing.T) {
server := NewMockGerritServer(t)
server.OnJSON("GET", "/changes/123", http.StatusOK, map[string]any{
"_number": 123,
})
cmd := newReviewCmd()
cmd.SetContext(context.Background())
cmd.SetArgs([]string{"123"})
err := cmd.Execute()
if err == nil {
t.Fatal("expected error when running review with no flags, got nil")
}
if !strings.Contains(err.Error(), "no review action or message specified") {
t.Errorf("expected error to mention 'no review action or message specified', got: %v", err)
}
if !strings.Contains(err.Error(), "--approve") {
t.Errorf("expected error to provide debug crumbs with '--approve', got: %v", err)
}
}
func TestReviewCmd_CommentFlagWithMessage(t *testing.T) {
server := NewMockGerritServer(t)
server.OnJSON("POST", "/changes/123/revisions/current/review", http.StatusOK, map[string]any{})
cmd := newReviewCmd()
cmd.SetContext(context.Background())
cmd.SetArgs([]string{"123", "--comment", "-m", "Just a question"})
if err := cmd.Execute(); err != nil {
t.Fatalf("cmd.Execute failed: %v", err)
}
var capturedInput gerrit.ReviewInput
if req := server.LastRequest(); req != nil {
json.Unmarshal(req.Body, &capturedInput)
}
if capturedInput.Message != "Just a question" {
t.Errorf("got message %q, want %q", capturedInput.Message, "Just a question")
}
if len(capturedInput.Labels) != 0 {
t.Errorf("expected no label votes with --comment, got: %v", capturedInput.Labels)
}
}
func TestReviewCmd_ErrorWhenCommentWithoutMessage(t *testing.T) {
cmd := newReviewCmd()
cmd.SetContext(context.Background())
cmd.SetArgs([]string{"123", "--comment"})
err := cmd.Execute()
if err == nil {
t.Fatal("expected error when running review with --comment but no message, got nil")
}
if !strings.Contains(err.Error(), "--comment requires a message") {
t.Errorf("expected error to mention '--comment requires a message', got: %v", err)
}
}
func TestReviewCmd_ErrorWhenMultipleActions(t *testing.T) {
cmd := newReviewCmd()
cmd.SetContext(context.Background())
cmd.SetArgs([]string{"123", "--approve", "--comment", "-m", "hi"})
err := cmd.Execute()
if err == nil {
t.Fatal("expected error when combining --approve and --comment, got nil")
}
if !strings.Contains(err.Error(), "cannot specify more than one") {
t.Errorf("expected error to mention 'cannot specify more than one', got: %v", err)
}
}
func TestReviewCmd_CQ_Default(t *testing.T) {
server := NewMockGerritServer(t)
server.OnJSON("POST", "/changes/123/revisions/current/review", http.StatusOK, map[string]any{})
cmd := newReviewCmd()
cmd.SetContext(context.Background())
cmd.SetArgs(NormalizeCQArgs([]string{"123", "--cq"}))
if err := cmd.Execute(); err != nil {
t.Fatalf("cmd.Execute failed: %v", err)
}
var capturedInput gerrit.ReviewInput
if req := server.LastRequest(); req != nil {
json.Unmarshal(req.Body, &capturedInput)
}
if capturedInput.Labels["Commit-Queue"] != 1 {
t.Errorf("got Commit-Queue = %d, want 1", capturedInput.Labels["Commit-Queue"])
}
}
func TestReviewCmd_ApproveAndCQ(t *testing.T) {
server := NewMockGerritServer(t)
server.OnJSON("POST", "/changes/123/revisions/current/review", http.StatusOK, map[string]any{})
cmd := newReviewCmd()
cmd.SetContext(context.Background())
cmd.SetArgs(NormalizeCQArgs([]string{"123", "--approve", "--cq", "-m", "LGTM"}))
if err := cmd.Execute(); err != nil {
t.Fatalf("cmd.Execute failed: %v", err)
}
var capturedInput gerrit.ReviewInput
if req := server.LastRequest(); req != nil {
json.Unmarshal(req.Body, &capturedInput)
}
if capturedInput.Labels["Code-Review"] != 2 {
t.Errorf("got Code-Review = %d, want 2", capturedInput.Labels["Code-Review"])
}
if capturedInput.Labels["Commit-Queue"] != 1 {
t.Errorf("got Commit-Queue = %d, want 1", capturedInput.Labels["Commit-Queue"])
}
}
func TestReviewCmd_CQ_Explicit(t *testing.T) {
server := NewMockGerritServer(t)
server.OnJSON("POST", "/changes/123/revisions/current/review", http.StatusOK, map[string]any{})
cmd := newReviewCmd()
cmd.SetContext(context.Background())
cmd.SetArgs(NormalizeCQArgs([]string{"123", "--cq", "2"}))
if err := cmd.Execute(); err != nil {
t.Fatalf("cmd.Execute failed: %v", err)
}
var capturedInput gerrit.ReviewInput
if req := server.LastRequest(); req != nil {
json.Unmarshal(req.Body, &capturedInput)
}
if capturedInput.Labels["Commit-Queue"] != 2 {
t.Errorf("got Commit-Queue = %d, want 2", capturedInput.Labels["Commit-Queue"])
}
}