Files

124 lines
2.7 KiB
Go

package consensus
import (
"errors"
"testing"
)
// Common consensus errors
var (
ErrTimeout = errors.New("consensus timeout")
ErrInvalidVote = errors.New("invalid vote")
ErrNoQuorum = errors.New("no quorum")
ErrConflict = errors.New("conflicting decisions")
ErrNotFinalized = errors.New("block not finalized")
ErrAlreadyDecided = errors.New("already decided")
ErrInvalidState = errors.New("invalid state transition")
ErrNetworkPartition = errors.New("network partition detected")
)
func TestErrorTypes(t *testing.T) {
tests := []struct {
err error
expected string
}{
{ErrTimeout, "consensus timeout"},
{ErrInvalidVote, "invalid vote"},
{ErrNoQuorum, "no quorum"},
{ErrConflict, "conflicting decisions"},
{ErrNotFinalized, "block not finalized"},
{ErrAlreadyDecided, "already decided"},
{ErrInvalidState, "invalid state transition"},
{ErrNetworkPartition, "network partition detected"},
}
for _, test := range tests {
if test.err.Error() != test.expected {
t.Errorf("expected error %q, got %q", test.expected, test.err.Error())
}
}
}
func TestErrorWrapping(t *testing.T) {
base := ErrTimeout
wrapped := WrapError(base, "block 123")
if !errors.Is(wrapped, base) {
t.Error("wrapped error should match base error")
}
expected := "block 123: consensus timeout"
if wrapped.Error() != expected {
t.Errorf("expected %q, got %q", expected, wrapped.Error())
}
}
func TestErrorChaining(t *testing.T) {
err1 := ErrNoQuorum
err2 := WrapError(err1, "round 1")
err3 := WrapError(err2, "validator set A")
if !errors.Is(err3, ErrNoQuorum) {
t.Error("chained error should match original")
}
expected := "validator set A: round 1: no quorum"
if err3.Error() != expected {
t.Errorf("expected %q, got %q", expected, err3.Error())
}
}
func TestIsRetryable(t *testing.T) {
retryable := []error{
ErrTimeout,
ErrNoQuorum,
ErrNetworkPartition,
}
notRetryable := []error{
ErrInvalidVote,
ErrConflict,
ErrAlreadyDecided,
ErrInvalidState,
}
for _, err := range retryable {
if !IsRetryable(err) {
t.Errorf("%v should be retryable", err)
}
}
for _, err := range notRetryable {
if IsRetryable(err) {
t.Errorf("%v should not be retryable", err)
}
}
}
// Helper functions
func WrapError(err error, context string) error {
return &WrappedError{
context: context,
err: err,
}
}
type WrappedError struct {
context string
err error
}
func (w *WrappedError) Error() string {
return w.context + ": " + w.err.Error()
}
func (w *WrappedError) Unwrap() error {
return w.err
}
func IsRetryable(err error) bool {
return errors.Is(err, ErrTimeout) ||
errors.Is(err, ErrNoQuorum) ||
errors.Is(err, ErrNetworkPartition)
}