// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package members
import (
"encoding/csv"
"fmt"
"math/rand"
"sort"
"strings"
"testing"
"github.com/mitchellh/cli"
"github.com/stretchr/testify/require"
"github.com/hashicorp/consul/agent"
consulapi "github.com/hashicorp/consul/api"
)
// TODO(partitions): split these tests
func TestMembersCommand_noTabs ( t * testing . T ) {
t . Parallel ( )
if strings . ContainsRune ( New ( cli . NewMockUi ( ) ) . Help ( ) , '\t' ) {
t . Fatal ( "help has tabs" )
}
}
func TestMembersCommand ( t * testing . T ) {
if testing . Short ( ) {
t . Skip ( "too slow for testing.Short" )
}
t . Parallel ( )
a := agent . NewTestAgent ( t , ` ` )
defer a . Shutdown ( )
ui := cli . NewMockUi ( )
c := New ( ui )
c . flags . SetOutput ( ui . ErrorWriter )
args := [ ] string { "-http-addr=" + a . HTTPAddr ( ) }
code := c . Run ( args )
if code != 0 {
t . Fatalf ( "bad: %d. %#v" , code , ui . ErrorWriter . String ( ) )
}
// Name
if ! strings . Contains ( ui . OutputWriter . String ( ) , a . Config . NodeName ) {
t . Fatalf ( "bad: %#v" , ui . OutputWriter . String ( ) )
}
// Agent type
if ! strings . Contains ( ui . OutputWriter . String ( ) , "server" ) {
t . Fatalf ( "bad: %#v" , ui . OutputWriter . String ( ) )
}
// Datacenter
if ! strings . Contains ( ui . OutputWriter . String ( ) , "dc1" ) {
t . Fatalf ( "bad: %#v" , ui . OutputWriter . String ( ) )
}
}
func TestMembersCommand_WAN ( t * testing . T ) {
if testing . Short ( ) {
t . Skip ( "too slow for testing.Short" )
}
t . Parallel ( )
a := agent . NewTestAgent ( t , ` ` )
defer a . Shutdown ( )
ui := cli . NewMockUi ( )
c := New ( ui )
c . flags . SetOutput ( ui . ErrorWriter )
args := [ ] string { "-http-addr=" + a . HTTPAddr ( ) , "-wan" }
code := c . Run ( args )
if code != 0 {
t . Fatalf ( "bad: %d. %#v" , code , ui . ErrorWriter . String ( ) )
}
if ! strings . Contains ( ui . OutputWriter . String ( ) , fmt . Sprintf ( "%d" , a . Config . SerfPortWAN ) ) {
t . Fatalf ( "bad: %#v" , ui . OutputWriter . String ( ) )
}
}
func TestMembersCommand_statusFilter ( t * testing . T ) {
if testing . Short ( ) {
t . Skip ( "too slow for testing.Short" )
}
t . Parallel ( )
a := agent . NewTestAgent ( t , ` ` )
defer a . Shutdown ( )
ui := cli . NewMockUi ( )
c := New ( ui )
c . flags . SetOutput ( ui . ErrorWriter )
args := [ ] string {
"-http-addr=" + a . HTTPAddr ( ) ,
"-status=a.*e" ,
}
code := c . Run ( args )
if code != 0 {
t . Fatalf ( "bad: %d. %#v" , code , ui . ErrorWriter . String ( ) )
}
if ! strings . Contains ( ui . OutputWriter . String ( ) , a . Config . NodeName ) {
t . Fatalf ( "bad: %#v" , ui . OutputWriter . String ( ) )
}
}
func TestMembersCommand_statusFilter_failed ( t * testing . T ) {
if testing . Short ( ) {
t . Skip ( "too slow for testing.Short" )
}
t . Parallel ( )
a := agent . NewTestAgent ( t , ` ` )
defer a . Shutdown ( )
ui := cli . NewMockUi ( )
c := New ( ui )
c . flags . SetOutput ( ui . ErrorWriter )
args := [ ] string {
"-http-addr=" + a . HTTPAddr ( ) ,
"-status=(fail|left)" ,
}
code := c . Run ( args )
if code == 1 {
t . Fatalf ( "bad: %d. %#v" , code , ui . ErrorWriter . String ( ) )
}
if strings . Contains ( ui . OutputWriter . String ( ) , a . Config . NodeName ) {
t . Fatalf ( "bad: %#v" , ui . OutputWriter . String ( ) )
}
if code != 2 {
t . Fatalf ( "bad: %d" , code )
}
}
func TestMembersCommand_verticalBar ( t * testing . T ) {
if testing . Short ( ) {
t . Skip ( "too slow for testing.Short" )
}
t . Parallel ( )
nodeName := "name|with|bars"
a := agent . NewTestAgent ( t , ` node_name = " ` + nodeName + ` " ` )
defer a . Shutdown ( )
ui := cli . NewMockUi ( )
c := New ( ui )
c . flags . SetOutput ( ui . ErrorWriter )
args := [ ] string {
"-http-addr=" + a . HTTPAddr ( ) ,
}
code := c . Run ( args )
if code == 1 {
t . Fatalf ( "bad: %d. %#v" , code , ui . ErrorWriter . String ( ) )
}
// Check for nodeName presense because it should not be parsed by columnize
if ! strings . Contains ( ui . OutputWriter . String ( ) , nodeName ) {
t . Fatalf ( "bad: %#v" , ui . OutputWriter . String ( ) )
}
}
func decodeOutput ( t * testing . T , data string ) [ ] map [ string ] string {
r := csv . NewReader ( strings . NewReader ( data ) )
r . Comma = ' '
r . TrimLeadingSpace = true
lines , err := r . ReadAll ( )
require . NoError ( t , err )
if len ( lines ) < 2 {
return nil
}
var out [ ] map [ string ] string
for i := 1 ; i < len ( lines ) ; i ++ {
m := zip ( t , lines [ 0 ] , lines [ i ] )
out = append ( out , m )
}
return out
}
func zip ( t * testing . T , k , v [ ] string ) map [ string ] string {
require . Equal ( t , len ( k ) , len ( v ) )
m := make ( map [ string ] string )
for i := 0 ; i < len ( k ) ; i ++ {
m [ k [ i ] ] = v [ i ]
}
return m
}
func TestSortByMemberNamePartitionAndSegment ( t * testing . T ) {
// For the test data we'll give them names that would sort them backwards
// if we only sorted by name.
newData := func ( ) [ ] * consulapi . AgentMember {
// NOTE: This should be sorted for assertions.
return [ ] * consulapi . AgentMember {
// servers
{ Name : "p-betty" , Tags : map [ string ] string { "role" : "consul" } } ,
{ Name : "q-bob" , Tags : map [ string ] string { "role" : "consul" } } ,
{ Name : "r-bonnie" , Tags : map [ string ] string { "role" : "consul" } } ,
// default clients
{ Name : "m-betty" , Tags : map [ string ] string { } } ,
{ Name : "n-bob" , Tags : map [ string ] string { } } ,
{ Name : "o-bonnie" , Tags : map [ string ] string { } } ,
// segment 1 clients
{ Name : "j-betty" , Tags : map [ string ] string { "segment" : "alpha" } } ,
{ Name : "k-bob" , Tags : map [ string ] string { "segment" : "alpha" } } ,
{ Name : "l-bonnie" , Tags : map [ string ] string { "segment" : "alpha" } } ,
// segment 2 clients
{ Name : "g-betty" , Tags : map [ string ] string { "segment" : "beta" } } ,
{ Name : "h-bob" , Tags : map [ string ] string { "segment" : "beta" } } ,
{ Name : "i-bonnie" , Tags : map [ string ] string { "segment" : "beta" } } ,
// partition 1 clients
{ Name : "d-betty" , Tags : map [ string ] string { "ap" : "part1" } } ,
{ Name : "e-bob" , Tags : map [ string ] string { "ap" : "part1" } } ,
{ Name : "f-bonnie" , Tags : map [ string ] string { "ap" : "part1" } } ,
// partition 2 clients
{ Name : "a-betty" , Tags : map [ string ] string { "ap" : "part2" } } ,
{ Name : "b-bob" , Tags : map [ string ] string { "ap" : "part2" } } ,
{ Name : "c-bonnie" , Tags : map [ string ] string { "ap" : "part2" } } ,
}
}
stringify := func ( data [ ] * consulapi . AgentMember ) [ ] string {
var out [ ] string
for _ , m := range data {
out = append ( out , fmt . Sprintf ( "<%s, %s, %s, %s>" ,
m . Tags [ "role" ] ,
m . Tags [ "ap" ] ,
m . Tags [ "segment" ] ,
m . Name ) )
}
return out
}
expect := newData ( )
for i := 0 ; i < 10 ; i ++ {
data := newData ( )
rand . Shuffle ( len ( data ) , func ( i , j int ) {
data [ i ] , data [ j ] = data [ j ] , data [ i ]
} )
sort . Sort ( ByMemberNamePartitionAndSegment ( data ) )
require . Equal ( t , stringify ( expect ) , stringify ( data ) , "iteration #%d" , i )
}
}