You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
296 lines
8.2 KiB
Go
296 lines
8.2 KiB
Go
package http
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/rqlite/rqlite/v8/store"
|
|
)
|
|
|
|
func Test_NewNodeFromServer(t *testing.T) {
|
|
server := &store.Server{ID: "1", Addr: "192.168.1.1", Suffrage: "Voter"}
|
|
node := NewNodeFromServer(server)
|
|
|
|
if node.ID != server.ID || node.Addr != server.Addr || !node.Voter {
|
|
t.Fatalf("NewNodeFromServer did not correctly initialize Node from Server")
|
|
}
|
|
}
|
|
|
|
func Test_NewNodesFromServers(t *testing.T) {
|
|
servers := []*store.Server{
|
|
{ID: "1", Addr: "192.168.1.1", Suffrage: "Voter"},
|
|
{ID: "2", Addr: "192.168.1.2", Suffrage: "Nonvoter"},
|
|
}
|
|
nodes := NewNodesFromServers(servers)
|
|
|
|
if len(nodes) != len(servers) {
|
|
t.Fatalf("NewNodesFromServers did not create the correct number of nodes")
|
|
}
|
|
for i, node := range nodes {
|
|
if node.ID != servers[i].ID || node.Addr != servers[i].Addr {
|
|
t.Fatalf("NewNodesFromServers did not correctly initialize Node %d from Server", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
func Test_NodesVoters(t *testing.T) {
|
|
nodes := Nodes{
|
|
{ID: "1", Voter: true},
|
|
{ID: "2", Voter: false},
|
|
}
|
|
voters := nodes.Voters()
|
|
|
|
if len(voters) != 1 || !voters[0].Voter {
|
|
t.Fatalf("Voters method did not correctly filter voter nodes")
|
|
}
|
|
}
|
|
|
|
func Test_NodeTestLeader(t *testing.T) {
|
|
node := &Node{ID: "1", Addr: "leader-raft-addr", APIAddr: "leader-api-addr"}
|
|
mockGA := newMockGetAddresser("leader-api-addr", nil)
|
|
|
|
node.Test(mockGA, "leader-raft-addr", 10*time.Second)
|
|
if !node.Reachable || !node.Leader {
|
|
t.Fatalf("Test method did not correctly update node status %s", asJSON(node))
|
|
}
|
|
}
|
|
|
|
func Test_NodeTestNotLeader(t *testing.T) {
|
|
node := &Node{ID: "1", Addr: "follower-raft-addr", APIAddr: "follower-api-addr"}
|
|
mockGA := newMockGetAddresser("follower-api-addr", nil)
|
|
|
|
node.Test(mockGA, "leader-raft-addr", 10*time.Second)
|
|
if !node.Reachable || node.Leader {
|
|
t.Fatalf("Test method did not correctly update node status %s", asJSON(node))
|
|
}
|
|
}
|
|
|
|
func Test_NodeTestDouble(t *testing.T) {
|
|
node1 := &Node{ID: "1", Addr: "leader-raft-addr", APIAddr: "leader-api-addr"}
|
|
node2 := &Node{ID: "2", Addr: "follower-raft-addr", APIAddr: "follower-api-addr"}
|
|
mockGA := &mockGetAddresser{}
|
|
mockGA.getAddrFn = func(addr string, timeout time.Duration) (string, error) {
|
|
if addr == "leader-raft-addr" {
|
|
return "leader-api-addr", nil
|
|
}
|
|
return "", fmt.Errorf("not reachable")
|
|
}
|
|
|
|
nodes := Nodes{node1, node2}
|
|
nodes.Test(mockGA, "leader-raft-addr", 10*time.Second)
|
|
if !node1.Reachable || !node1.Leader || node2.Reachable || node2.Leader || node2.Error != "not reachable" {
|
|
t.Fatalf("Test method did not correctly update node status %s", asJSON(nodes))
|
|
}
|
|
|
|
if !nodes.HasAddr("leader-raft-addr") {
|
|
t.Fatalf("HasAddr method did not correctly find node")
|
|
}
|
|
if nodes.HasAddr("not-found") {
|
|
t.Fatalf("HasAddr method incorrectly found node")
|
|
}
|
|
}
|
|
|
|
func Test_NodeTestDouble_Timeout(t *testing.T) {
|
|
node1 := &Node{ID: "1", Addr: "leader-raft-addr", APIAddr: "leader-api-addr"}
|
|
node2 := &Node{ID: "2", Addr: "follower-raft-addr", APIAddr: "follower-api-addr"}
|
|
mockGA := &mockGetAddresser{}
|
|
mockGA.getAddrFn = func(addr string, timeout time.Duration) (string, error) {
|
|
if addr == "leader-raft-addr" {
|
|
return "leader-api-addr", nil
|
|
}
|
|
time.Sleep(10 * time.Second) // Simulate a node just hanging when contacted.
|
|
return "", nil
|
|
}
|
|
|
|
nodes := Nodes{node1, node2}
|
|
nodes.Test(mockGA, "leader-raft-addr", 1*time.Second)
|
|
if !node1.Reachable || !node1.Leader || node2.Reachable || node2.Leader || node2.Error != "timeout waiting for node to respond" {
|
|
t.Fatalf("Test method did not correctly update node status %s", asJSON(nodes))
|
|
}
|
|
|
|
if !nodes.HasAddr("leader-raft-addr") {
|
|
t.Fatalf("HasAddr method did not correctly find node")
|
|
}
|
|
if nodes.HasAddr("not-found") {
|
|
t.Fatalf("HasAddr method incorrectly found node")
|
|
}
|
|
}
|
|
|
|
func Test_NodesRespEncodeStandard(t *testing.T) {
|
|
nodes := mockNodes()
|
|
buffer := new(bytes.Buffer)
|
|
encoder := NewNodesRespEncoder(buffer, false)
|
|
|
|
err := encoder.Encode(nodes)
|
|
if err != nil {
|
|
t.Errorf("Encode failed: %v", err)
|
|
}
|
|
|
|
m := make(map[string]interface{})
|
|
if err := json.Unmarshal(buffer.Bytes(), &m); err != nil {
|
|
t.Errorf("Encode failed: %v", err)
|
|
}
|
|
if len(m) != 1 {
|
|
t.Errorf("unexpected number of keys")
|
|
}
|
|
if _, ok := m["nodes"]; !ok {
|
|
t.Errorf("nodes key missing")
|
|
}
|
|
nodesArray, ok := m["nodes"].([]interface{})
|
|
if !ok {
|
|
t.Errorf("nodes key is not an array")
|
|
}
|
|
if len(nodesArray) != 1 {
|
|
t.Errorf("unexpected number of nodes")
|
|
}
|
|
node, ok := nodesArray[0].(map[string]interface{})
|
|
if !ok {
|
|
t.Errorf("node is not a map")
|
|
}
|
|
checkNode(t, node)
|
|
}
|
|
|
|
func Test_NodeRespEncodeLegacy(t *testing.T) {
|
|
nodes := mockNodes()
|
|
buffer := new(bytes.Buffer)
|
|
encoder := NewNodesRespEncoder(buffer, true)
|
|
|
|
err := encoder.Encode(nodes)
|
|
if err != nil {
|
|
t.Errorf("Encode failed: %v", err)
|
|
}
|
|
|
|
m := make(map[string]interface{})
|
|
if err := json.Unmarshal(buffer.Bytes(), &m); err != nil {
|
|
t.Errorf("Encode failed: %v", err)
|
|
}
|
|
if len(m) != 1 {
|
|
t.Errorf("unexpected number of keys")
|
|
}
|
|
if _, ok := m["1"]; !ok {
|
|
t.Errorf("node key missing")
|
|
}
|
|
node, ok := m["1"].(map[string]interface{})
|
|
if !ok {
|
|
t.Errorf("nodes key is not an map")
|
|
}
|
|
checkNode(t, node)
|
|
}
|
|
|
|
func Test_NodesRespDecoder_Decode_ValidJSON(t *testing.T) {
|
|
jsonInput := `{"nodes":[{"id":"1","addr":"192.168.1.1","voter":true},{"id":"2","addr":"192.168.1.2","voter":false}]}`
|
|
reader := strings.NewReader(jsonInput)
|
|
decoder := NewNodesRespDecoder(reader)
|
|
|
|
var nodes Nodes
|
|
err := decoder.Decode(&nodes)
|
|
if err != nil {
|
|
t.Errorf("Decode failed with valid JSON: %v", err)
|
|
}
|
|
|
|
if len(nodes) != 2 || nodes[0].ID != "1" || nodes[1].ID != "2" {
|
|
t.Errorf("Decode did not properly decode the JSON into Nodes")
|
|
}
|
|
}
|
|
|
|
func Test_NodesRespDecoder_Decode_InvalidJSON(t *testing.T) {
|
|
invalidJsonInput := `{"nodes": "invalid"}`
|
|
reader := strings.NewReader(invalidJsonInput)
|
|
decoder := NewNodesRespDecoder(reader)
|
|
|
|
var nodes Nodes
|
|
err := decoder.Decode(&nodes)
|
|
if err == nil {
|
|
t.Error("Decode should fail with invalid JSON")
|
|
}
|
|
}
|
|
|
|
func Test_NodesRespDecoder_Decode_EmptyJSON(t *testing.T) {
|
|
emptyJsonInput := `{}`
|
|
reader := strings.NewReader(emptyJsonInput)
|
|
decoder := NewNodesRespDecoder(reader)
|
|
|
|
var nodes Nodes
|
|
err := decoder.Decode(&nodes)
|
|
if err != nil {
|
|
t.Errorf("Decode failed with empty JSON: %v", err)
|
|
}
|
|
|
|
if len(nodes) != 0 {
|
|
t.Errorf("Decode should result in an empty Nodes slice for empty JSON")
|
|
}
|
|
}
|
|
|
|
// mockGetAddresser is a mock implementation of the GetAddresser interface.
|
|
type mockGetAddresser struct {
|
|
apiAddr string
|
|
err error
|
|
getAddrFn func(addr string, timeout time.Duration) (string, error)
|
|
}
|
|
|
|
// newMockGetAddresser creates a new instance of mockGetAddresser.
|
|
// You can customize the return values for GetNodeAPIAddr by setting apiAddr and err.
|
|
func newMockGetAddresser(apiAddr string, err error) *mockGetAddresser {
|
|
return &mockGetAddresser{apiAddr: apiAddr, err: err}
|
|
}
|
|
|
|
// GetNodeAPIAddr is the mock implementation of the GetNodeAPIAddr method.
|
|
func (m *mockGetAddresser) GetNodeAPIAddr(addr string, timeout time.Duration) (string, error) {
|
|
if m.getAddrFn != nil {
|
|
return m.getAddrFn(addr, timeout)
|
|
}
|
|
return m.apiAddr, m.err
|
|
}
|
|
|
|
func mockNodes() Nodes {
|
|
return Nodes{
|
|
&Node{ID: "1", APIAddr: "http://localhost:4001", Addr: "localhost:4002", Reachable: true, Leader: true},
|
|
}
|
|
}
|
|
|
|
func checkNode(t *testing.T, node map[string]interface{}) {
|
|
t.Helper()
|
|
if _, ok := node["id"]; !ok {
|
|
t.Errorf("node is missing id")
|
|
}
|
|
if node["id"] != "1" {
|
|
t.Errorf("unexpected node id")
|
|
}
|
|
if _, ok := node["api_addr"]; !ok {
|
|
t.Errorf("node is missing api_addr")
|
|
}
|
|
if node["api_addr"] != "http://localhost:4001" {
|
|
t.Errorf("unexpected node api_addr")
|
|
}
|
|
if _, ok := node["addr"]; !ok {
|
|
t.Errorf("node is missing addr")
|
|
}
|
|
if node["addr"] != "localhost:4002" {
|
|
t.Errorf("unexpected node addr")
|
|
}
|
|
if _, ok := node["reachable"]; !ok {
|
|
t.Errorf("node is missing reachable")
|
|
}
|
|
if node["reachable"] != true {
|
|
t.Errorf("unexpected node reachable")
|
|
}
|
|
if _, ok := node["leader"]; !ok {
|
|
t.Errorf("node is missing leader")
|
|
}
|
|
if node["leader"] != true {
|
|
t.Errorf("unexpected node leader")
|
|
}
|
|
}
|
|
|
|
func asJSON(v interface{}) string {
|
|
b, err := json.Marshal(v)
|
|
if err != nil {
|
|
panic(fmt.Sprintf("failed to JSON marshal value: %s", err.Error()))
|
|
}
|
|
return string(b)
|
|
}
|