feat: Adding very preliminar and broken test harness.

This commit is contained in:
James Wells 2023-04-03 18:39:01 -07:00
parent ac8c225752
commit 7f9a5777bd
Signed by: jwells
GPG key ID: 73196D10B8E65666
14 changed files with 678 additions and 10 deletions

View file

@ -0,0 +1,65 @@
package chaindb
import (
"bytes"
"os"
"testing"
"time"
gstructs "github.com/dragonheim/gagent/internal/gstructs"
)
const testChainDBPath = "test_chaindb.hcl"
func TestGagentDb(t *testing.T) {
// Create a new GagentDb
db := NewGagentDb()
// Add a row to the database
row := &GagentDbRow{
DBName: "testDB",
Agent: gstructs.AgentDetails{
Client: "testAgent",
Shasum: "v1.0.0",
},
}
db.AddRow(row)
// Check if the row was added correctly
if len(db.ChainRow) != 1 {
t.Errorf("Expected length of ChainRow to be 1, but got %d", len(db.ChainRow))
}
// Check if the timestamp was set correctly
if db.ChainRow[0].Timestamp.After(time.Now()) {
t.Error("Timestamp is incorrectly set in the future")
}
// Write the database to an HCL file
err := db.WriteHCL(testChainDBPath)
if err != nil {
t.Errorf("Error writing HCL file: %v", err)
}
// Load the database from the HCL file
loadedDb := NewGagentDb()
err = loadedDb.LoadHCL(testChainDBPath)
if err != nil {
t.Errorf("Error loading HCL file: %v", err)
}
// Check if the loaded database is the same as the original one
if !bytes.Equal(loadedDb.ChainRow[0].DbCurrHash[:], db.ChainRow[0].DbCurrHash[:]) {
t.Error("Loaded database has a different current hash than the original one")
}
if !bytes.Equal(loadedDb.ChainRow[0].DbPrevHash[:], db.ChainRow[0].DbPrevHash[:]) {
t.Error("Loaded database has a different previous hash than the original one")
}
// Clean up the test HCL file
err = os.Remove(testChainDBPath)
if err != nil {
t.Errorf("Error cleaning up test HCL file: %v", err)
}
}

View file

@ -0,0 +1,152 @@
package client
import (
"bytes"
"errors"
"io/ioutil"
"log"
"os"
"sync"
"testing"
gstructs "github.com/dragonheim/gagent/internal/gstructs"
zmq "github.com/pebbe/zmq4"
)
type mockSocket struct {
sendMessageError error
}
func (m *mockSocket) Close() error { return nil }
func (m *mockSocket) Bind(endpoint string) error { return nil }
func (m *mockSocket) Connect(endpoint string) error { return nil }
func (m *mockSocket) SetIdentity(identity string) error { return nil }
func (m *mockSocket) SendMessage(parts ...interface{}) (int, error) { return 0, m.sendMessageError }
func (m *mockSocket) RecvMessage(flags zmq.Flag) ([]string, error) { return nil, nil }
func (m *mockSocket) RecvMessageBytes(flags zmq.Flag) ([][]byte, error) { return nil, nil }
func (m *mockSocket) RecvMessageString(flags zmq.Flag) ([]string, error) { return nil, nil }
func (m *mockSocket) SetSubscribe(filter string) error { return nil }
func (m *mockSocket) SetUnsubscribe(filter string) error { return nil }
func (m *mockSocket) Send(msg string, flags zmq.Flag) (int, error) { return 0, nil }
func (m *mockSocket) SendBytes(msg []byte, flags zmq.Flag) (int, error) { return 0, nil }
func (m *mockSocket) SendFrame(msg []byte, flags zmq.Flag) (int, error) { return 0, nil }
func (m *mockSocket) SendMultipart(parts [][]byte, flags zmq.Flag) (int, error) { return 0, nil }
func (m *mockSocket) Recv(flags zmq.Flag) (string, error) { return "", nil }
func (m *mockSocket) RecvBytes(flags zmq.Flag) ([]byte, error) { return nil, nil }
func (m *mockSocket) RecvFrame(flags zmq.Flag) ([]byte, error) { return nil, nil }
func (m *mockSocket) RecvMultipart(flags zmq.Flag) ([][]byte, error) { return nil, nil }
func (m *mockSocket) SetOption(option zmq.SocketOption, value interface{}) error { return nil }
func (m *mockSocket) GetOption(option zmq.SocketOption) (interface{}, error) { return nil, nil }
func (m *mockSocket) Events() zmq.State { return 0 }
func (m *mockSocket) String() string { return "" }
func TestGetTagsFromHints(t *testing.T) {
agent := gstructs.AgentDetails{
Script: []byte(`*set GHINT[split "tag1,tag2,tag3",]`),
}
expectedHints := []string{"tag1", "tag2", "tag3"}
hints := getTagsFromHints(agent)
if !equalStringSlices(hints, expectedHints) {
t.Errorf("Expected hints %v, but got %v", expectedHints, hints)
}
}
func TestSendAgent(t *testing.T) {
wg := &sync.WaitGroup{}
config := gstructs.GagentConfig{
UUID: "test-uuid",
ClientPort: 1234,
Routers: map[string]gstructs.Router{
"test-router": {
RouterAddr: "127.0.0.1",
ClientPort: 1234,
},
},
}
agent := gstructs.AgentDetails{
Client: "test-client",
Script: []byte(`*set GHINT[split "tag1,tag2,tag3",]`),
}
// Replace zmq.NewSocket with a function that returns a mock socket
origNewSocket := newSocket
defer func() { newSocket = origNewSocket }()
newSocket = func(t zmq.Type) (zmq.Socket, error) {
return &mockSocket{}, nil
}
wg.Add(1)
go sendAgent(wg, config.UUID, "tcp://127.0.0.1:1234", agent)
wg.Wait()
// Test with an error in sending a message
newSocket = func(t zmq.Type) (zmq.Socket, error) {
return &mockSocket{sendMessageError: errors.New("send message error")}, nil
}
wg.Add(1)
go sendAgent(wg, config.UUID, "tcp://127.0.0.1:1234", agent)
wg.Wait()
}
func equalStringSlices(a, b []string) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}
func TestMain(t *testing.T) {
// Prepare a temporary agent file for testing
tmpAgentFile, err := ioutil.TempFile("", "agent")
if err != nil {
t.Fatal(err)
}
defer os.Remove(tmpAgentFile.Name())
content := []byte(`*set GHINT[split "tag1,tag2,tag3",]`)
if _, err := tmpAgentFile.Write(content); err != nil {
t.Fatal(err)
}
if err := tmpAgentFile.Close(); err != nil {
t.Fatal(err)
}
config := gstructs.GagentConfig{
CMode: true,
UUID: "test-uuid",
ClientPort: 1234,
Agent: tmpAgentFile.Name(),
Routers: map[string]gstructs.Router{
"test-router": {
RouterAddr: "127.0.0.1",
ClientPort: 1234,
},
},
}
// Replace log output with a buffer to suppress output during testing
origLogOutput := log.Writer()
defer log.SetOutput(origLogOutput)
log.SetOutput(&bytes.Buffer{})
// Replace zmq.NewSocket with a function that returns a mock socket
origNewSocket := newSocket
defer func() { newSocket = origNewSocket }()
newSocket = func(t zmq.Type) (zmq.Socket, error) {
return &mockSocket{}, nil
}
wg := &sync.WaitGroup{}
wg.Add(1)
go Main(wg, config)
wg.Wait()
}

View file

@ -0,0 +1,35 @@
package gstructs
import (
"fmt"
)
type AgentStatus []string
var AgentStatuses = AgentStatus{
"ERROR",
"INIT",
"SENDING",
"RECEIVING",
"ROUTING",
"PROCESSING",
"COMPLETED",
"RETURNING",
"ERROR",
}
func (a AgentStatus) GetByIndex(index int) (string, error) {
if index < 0 || index >= len(a) {
return "", fmt.Errorf("invalid index: %d", index)
}
return a[index], nil
}
func (a AgentStatus) GetByName(name string) (byte, error) {
for i, status := range a {
if status == name {
return byte(i), nil
}
}
return 0, fmt.Errorf("value not found: %s", name)
}

View file

@ -0,0 +1,59 @@
package gstructs_test
import (
"testing"
"github.com/dragonheim/gagent/internal/gstructs"
"github.com/stretchr/testify/assert"
)
func TestGetByIndex(t *testing.T) {
agentStatuses := gstructs.AgentStatuses
tests := []struct {
index int
expected string
shouldReturn bool
}{
{0, "ERROR", true},
{1, "INIT", true},
{8, "ERROR", true},
{9, "", false},
{-1, "", false},
}
for _, test := range tests {
res, err := agentStatuses.GetByIndex(test.index)
if test.shouldReturn {
assert.NoError(t, err)
assert.Equal(t, test.expected, res)
} else {
assert.Error(t, err)
}
}
}
func TestGetByName(t *testing.T) {
agentStatuses := gstructs.AgentStatuses
tests := []struct {
name string
expected byte
shouldReturn bool
}{
{"ERROR", 0, true},
{"INIT", 1, true},
{"COMPLETED", 6, true},
{"INVALID", 0, false},
}
for _, test := range tests {
res, err := agentStatuses.GetByName(test.name)
if test.shouldReturn {
assert.NoError(t, err)
assert.Equal(t, test.expected, res)
} else {
assert.Error(t, err)
}
}
}

View file

@ -111,10 +111,10 @@ type WorkerDetails struct {
}
type AgentDetails struct {
Status byte `hcl:"status"`
Client string `hcl:"client"`
Shasum string `hcl:"shasum"`
Hints []string
Script []byte
Answer []byte
Status byte `hcl:"status"`
Client string `hcl:"client"`
Shasum string `hcl:"shasum"`
Hints []string `hcl:"hints"`
Script []byte `hcl:"script"`
Answer []byte `hcl:"answer"`
}

View file

@ -0,0 +1,76 @@
package gstructs_test
import (
"testing"
"github.com/dragonheim/gagent/internal/gstructs"
)
func TestGagentConfig(t *testing.T) {
config := gstructs.GagentConfig{
Name: "test-config",
Mode: "client",
UUID: "test-uuid",
ListenAddr: "127.0.0.1",
ChainDBPath: "/tmp/chaindb",
MonitorPort: 8888,
ClientPort: 1234,
RouterPort: 5678,
WorkerPort: 9012,
Clients: []*gstructs.ClientDetails{
{
ClientName: "test-client",
ClientID: "client-id",
},
},
Routers: []*gstructs.RouterDetails{
{
RouterName: "test-router",
RouterID: "router-id",
RouterAddr: "192.168.1.1",
RouterTags: []string{"tag1", "tag2"},
ClientPort: 1234,
RouterPort: 5678,
WorkerPort: 9012,
},
},
Workers: []*gstructs.WorkerDetails{
{
WorkerName: "test-worker",
WorkerID: "worker-id",
WorkerTags: []string{"tag3", "tag4"},
},
},
Version: "1.0.0",
File: "config.hcl",
Agent: "agent.gagent",
CMode: true,
}
if config.Name != "test-config" {
t.Errorf("Expected config name to be 'test-config', got %s", config.Name)
}
if config.Mode != "client" {
t.Errorf("Expected config mode to be 'client', got %s", config.Mode)
}
// TODO: add more assertions for other config fields
}
func TestAgentDetails(t *testing.T) {
agent := gstructs.AgentDetails{
Status: 1,
Client: "test-client",
Shasum: "123456789abcdef",
Hints: []string{"tag1", "tag2", "tag3"},
Script: []byte("sample script content"),
Answer: []byte("sample answer content"),
}
if agent.Status != 1 {
t.Errorf("Expected agent status to be 1, got %d", agent.Status)
}
if agent.Client != "test-client" {
t.Errorf("Expected agent client to be 'test-client', got %s", agent.Client)
}
// TODO: add more assertions for other agent fields
}

View file

@ -0,0 +1,72 @@
package router_test
import (
"net/http"
"net/http/httptest"
"sync"
"testing"
"time"
gs "github.com/dragonheim/gagent/internal/gstructs"
"github.com/dragonheim/gagent/internal/router"
)
func TestRouterMain(t *testing.T) {
config := gs.GagentConfig{
Name: "test-config",
Mode: "router",
UUID: "test-uuid",
ListenAddr: "127.0.0.1",
ClientPort: 1234,
RouterPort: 5678,
WorkerPort: 9012,
ChainDBPath: "test-chaindb-path",
}
wg := &sync.WaitGroup{}
wg.Add(1)
go router.Main(wg, config)
// Allow router to start before sending HTTP requests
time.Sleep(time.Millisecond * 100)
// Test GET request
resp := makeRequest(t, "GET", "http://localhost:1234/hello")
if resp.StatusCode != http.StatusOK {
t.Errorf("Expected status code %d, got %d", http.StatusOK, resp.StatusCode)
}
// Test POST request
resp = makeRequest(t, "POST", "http://localhost:1234/hello")
if resp.StatusCode != http.StatusOK {
t.Errorf("Expected status code %d, got %d", http.StatusOK, resp.StatusCode)
}
// Test OPTIONS request
resp = makeRequest(t, "OPTIONS", "http://localhost:1234/hello")
if resp.StatusCode != http.StatusNoContent {
t.Errorf("Expected status code %d, got %d", http.StatusNoContent, resp.StatusCode)
}
// Test unsupported method
resp = makeRequest(t, "PUT", "http://localhost:1234/hello")
if resp.StatusCode != http.StatusMethodNotAllowed {
t.Errorf("Expected status code %d, got %d", http.StatusMethodNotAllowed, resp.StatusCode)
}
wg.Wait()
}
func makeRequest(t *testing.T, method, url string) *http.Response {
req, err := http.NewRequest(method, url, nil)
if err != nil {
t.Fatalf("Error creating request: %v", err)
}
rr := httptest.NewRecorder()
handler := http.HandlerFunc(router.AnswerClient)
handler.ServeHTTP(rr, req)
return rr.Result()
}

View file

@ -0,0 +1,57 @@
package setup_test
import (
"bytes"
"io"
"log"
"os"
"testing"
"strings"
"sync"
gs "github.com/dragonheim/gagent/internal/gstructs"
"github.com/dragonheim/gagent/internal/setup"
)
func TestSetupMain(t *testing.T) {
config := gs.GagentConfig{
Name: "test-config",
Mode: "client",
UUID: "test-uuid",
ListenAddr: "127.0.0.1",
ClientPort: 1234,
RouterPort: 5678,
WorkerPort: 9012,
}
wg := &sync.WaitGroup{}
wg.Add(1)
capturedOutput := captureOutput(func() {
setup.Main(wg, config)
})
expectedOutput := `Configuration file created`
if !strings.Contains(capturedOutput, expectedOutput) {
t.Errorf("Expected output to contain '%s', got '%s'", expectedOutput, capturedOutput)
}
wg.Wait()
}
func captureOutput(f func()) string {
original := *log.Writer()
r, w, _ := os.Pipe()
log.SetOutput(w)
f()
w.Close()
log.SetOutput(original)
var buf bytes.Buffer
io.Copy(&buf, r)
return buf.String()
}

View file

@ -0,0 +1,65 @@
package worker_test
import (
"bytes"
"io"
"log"
"os"
"testing"
"strings"
"sync"
gs "github.com/dragonheim/gagent/internal/gstructs"
"github.com/dragonheim/gagent/internal/worker"
)
func TestWorkerMain(t *testing.T) {
config := gs.GagentConfig{
Name: "test-config",
Mode: "worker",
UUID: "test-uuid",
ListenAddr: "127.0.0.1",
ClientPort: 1234,
RouterPort: 5678,
WorkerPort: 9012,
Routers: []*gs.RouterDetails{
{
RouterName: "test-router",
RouterID: "test-router-id",
RouterAddr: "127.0.0.1",
WorkerPort: 9012,
},
},
}
wg := &sync.WaitGroup{}
wg.Add(1)
capturedOutput := captureOutput(func() {
worker.Main(wg, config)
})
expectedOutput := `Starting worker`
if !strings.Contains(capturedOutput, expectedOutput) {
t.Errorf("Expected output to contain '%s', got '%s'", expectedOutput, capturedOutput)
}
wg.Wait()
}
func captureOutput(f func()) string {
original := *log.Writer()
r, w, _ := os.Pipe()
log.SetOutput(w)
f()
w.Close()
log.SetOutput(original)
var buf bytes.Buffer
io.Copy(&buf, r)
return buf.String()
}