package core
import (
"errors"
"testing"
)
// The one derivation of a proposal's branch name. gitx cuts the ref from it and
// db records it on the row; if the two ever computed it separately, a proposal
// whose row and ref disagreed would be a break with no cheap way to trace it.
func TestProposalBranch(t *testing.T) {
for _, tc := range []struct {
id int64
want string
}{
{1, "proposals/1"},
{42, "proposals/42"},
{1000000, "proposals/1000000"},
} {
got, err := ProposalBranch(tc.id)
if err != nil {
t.Errorf("ProposalBranch(%d): %v", tc.id, err)
continue
}
if got != tc.want {
t.Errorf("ProposalBranch(%d) = %q, want %q", tc.id, got, tc.want)
}
}
// Ids come from a sequence starting at 1, so a non-positive one is an
// unwritten row, not a proposal — and "proposals/0" is a name that would go
// on to be created and looked for.
for _, id := range []int64{0, -1} {
if _, err := ProposalBranch(id); !errors.Is(err, ErrInvalidProposalID) {
t.Errorf("ProposalBranch(%d) err = %v, want ErrInvalidProposalID", id, err)
}
}
}
func TestParseProposalState(t *testing.T) {
tests := []struct {
in string
ok bool
}{
{"open", true},
{"merged", true},
{"rejected", true},
// The states that were deliberately collapsed away. Accepting either
// would resurrect a review conversation that has no second party.
{"approved", false},
{"changes-requested", false},
{"closed", false},
{"draft", false},
{"", false},
{"Open", false},
{"OPEN", false},
{"open ", false},
{" open", false},
{"открыт", false},
}
for _, tc := range tests {
st, err := ParseProposalState(tc.in)
if (err == nil) != tc.ok {
t.Errorf("ParseProposalState(%q) = %q, %v, want ok=%v", tc.in, st, err, tc.ok)
}
if err != nil && !errors.Is(err, ErrInvalidState) {
t.Errorf("ParseProposalState(%q) error %v is not ErrInvalidState", tc.in, err)
}
}
}
func TestProposalStatesIsExactlyThree(t *testing.T) {
got := ProposalStates()
if len(got) != 3 {
t.Fatalf("ProposalStates() = %v, want exactly open/merged/rejected", got)
}
want := []ProposalState{StateOpen, StateMerged, StateRejected}
for i := range want {
if got[i] != want[i] {
t.Fatalf("ProposalStates() = %v, want %v", got, want)
}
}
}
func TestProposalTransitions(t *testing.T) {
all := []ProposalState{StateOpen, StateMerged, StateRejected}
// The whole table, explicitly: only the two edges out of `open` exist.
want := map[ProposalState]map[ProposalState]bool{
StateOpen: {StateOpen: false, StateMerged: true, StateRejected: true},
StateMerged: {StateOpen: false, StateMerged: false, StateRejected: false},
StateRejected: {StateOpen: false, StateMerged: false, StateRejected: false},
}
for _, from := range all {
for _, to := range all {
t.Run(string(from)+"->"+string(to), func(t *testing.T) {
expect := want[from][to]
if got := ValidTransition(from, to); got != expect {
t.Fatalf("ValidTransition(%s, %s) = %v, want %v", from, to, got, expect)
}
err := from.CanTransitionTo(to)
if (err == nil) != expect {
t.Fatalf("%s.CanTransitionTo(%s) = %v, want ok=%v", from, to, err, expect)
}
if err != nil && !errors.Is(err, ErrInvalidTransition) {
t.Fatalf("error %v is not ErrInvalidTransition", err)
}
})
}
}
}
func TestCanTransitionToRejectsUnknownStates(t *testing.T) {
// An unparseable state is a different failure class from an illegal edge:
// it means something wrote garbage into the proposal row.
if err := ProposalState("approved").CanTransitionTo(StateMerged); !errors.Is(err, ErrInvalidState) {
t.Fatalf("from=approved: %v, want ErrInvalidState", err)
}
if err := StateOpen.CanTransitionTo("approved"); !errors.Is(err, ErrInvalidState) {
t.Fatalf("to=approved: %v, want ErrInvalidState", err)
}
if err := ProposalState("").CanTransitionTo(StateMerged); !errors.Is(err, ErrInvalidState) {
t.Fatalf("from=empty: %v, want ErrInvalidState", err)
}
}
func TestProposalStateTerminal(t *testing.T) {
tests := []struct {
in ProposalState
want bool
}{
{StateOpen, false},
{StateMerged, true},
{StateRejected, true},
{"", false},
{"approved", false},
}
for _, tc := range tests {
if got := tc.in.Terminal(); got != tc.want {
t.Errorf("%q.Terminal() = %v, want %v", tc.in, got, tc.want)
}
}
}
func TestParseApproval(t *testing.T) {
tests := []struct {
in string
ok bool
}{
{"human", true},
{"policy", true},
{"", false},
{"auto", false},
{"Human", false},
{"human ", false},
{"approved", false},
}
for _, tc := range tests {
a, err := ParseApproval(tc.in)
if (err == nil) != tc.ok {
t.Errorf("ParseApproval(%q) = %q, %v, want ok=%v", tc.in, a, err, tc.ok)
}
if err != nil && !errors.Is(err, ErrInvalidApproval) {
t.Errorf("ParseApproval(%q) error %v is not ErrInvalidApproval", tc.in, err)
}
}
}
// A policy-merged document must be distinguishable from a human-approved one:
// collapsing the two would quietly launder unreviewed agent output as blessed.
func TestApprovalKindsAreDistinct(t *testing.T) {
if ApprovalHuman == ApprovalPolicy {
t.Fatal("human and policy approval must not be the same value")
}
}