From 7f9a5777bd32a07fe146b6bf5e68313b654485a4 Mon Sep 17 00:00:00 2001 From: James Wells Date: Mon, 3 Apr 2023 18:39:01 -0700 Subject: [PATCH] feat: Adding very preliminar and broken test harness. --- assets/examples/genesis.json | 9 +- cmd/gagent/main.go | 3 +- cmd/gagent/main_test.go | 71 ++++++++++++ go.mod | 3 + go.sum | 9 ++ internal/chaindb/chaindb_test.go | 65 +++++++++++ internal/client/client_test.go | 152 +++++++++++++++++++++++++ internal/gstructs/agent_status.go | 35 ++++++ internal/gstructs/agent_status_test.go | 59 ++++++++++ internal/gstructs/gstructs.go | 12 +- internal/gstructs/gstructs_test.go | 76 +++++++++++++ internal/router/router_test.go | 72 ++++++++++++ internal/setup/setup_test.go | 57 ++++++++++ internal/worker/worker_test.go | 65 +++++++++++ 14 files changed, 678 insertions(+), 10 deletions(-) create mode 100644 cmd/gagent/main_test.go create mode 100644 internal/chaindb/chaindb_test.go create mode 100644 internal/client/client_test.go create mode 100644 internal/gstructs/agent_status.go create mode 100644 internal/gstructs/agent_status_test.go create mode 100644 internal/gstructs/gstructs_test.go create mode 100644 internal/router/router_test.go create mode 100644 internal/setup/setup_test.go create mode 100644 internal/worker/worker_test.go diff --git a/assets/examples/genesis.json b/assets/examples/genesis.json index 797ef6b..32b325b 100644 --- a/assets/examples/genesis.json +++ b/assets/examples/genesis.json @@ -1,13 +1,16 @@ { - "genesis_time": "2021-10-25:00:00.000000000Z", + "timestamp": "2021-10-25:00:00.000000000Z", "chainid": "gagent_ledger", "agent": { + "status": "complete", "client": "7e9d13fe-5151-5876-66c0-20ca03e8fca4", "shasum": "a76f7c3c7bc0f94b4f8aa63c605f8534db5675bb05d761f4461127fcadbf32d4", - "status": "complete" + "hints": {}, + "script": "", + "answer": "" }, "clients": { - "client": "7e9d13fe-5151-5876-66c0-20ca03e8fca4" + "clientID": "7e9d13fe-5151-5876-66c0-20ca03e8fca4" } } diff --git a/cmd/gagent/main.go b/cmd/gagent/main.go index 350dbb9..44ac06a 100644 --- a/cmd/gagent/main.go +++ b/cmd/gagent/main.go @@ -300,7 +300,8 @@ func init() { if config.MonitorPort != 0 { go func() { log.Printf("[INFO] Starting Prometheus metrics exporter on port %d\n", config.MonitorPort) - log.Fatal(http.ListenAndServe(string(config.ListenAddr)+":"+strconv.Itoa(config.MonitorPort), nil)) + err := http.ListenAndServe(string(config.ListenAddr)+":"+strconv.Itoa(config.MonitorPort), nil) + log.Printf("[ERROR] Prometheus metrics exporter returned: %s\n", err) }() } autorestart.WatchFilename = config.File diff --git a/cmd/gagent/main_test.go b/cmd/gagent/main_test.go new file mode 100644 index 0000000..66c16f0 --- /dev/null +++ b/cmd/gagent/main_test.go @@ -0,0 +1,71 @@ +package main_test + +import ( + "io/ioutil" + "os" + "testing" + + main "github.com/dragonheim/gagent/cmd/gagent" + gstructs "github.com/dragonheim/gagent/internal/gstructs" +) + +// This function will create a temporary config file for testing purposes +func createTestConfigFile() (string, error) { + tmpfile, err := ioutil.TempFile("", "test_config_*.hcl") + if err != nil { + return "", err + } + + content := []byte(`mode = "setup" +listen_addr = "0.0.0.0" +monitor_port = 8888 +client_port = 35572 +router_port = 35570 +worker_port = 35571 +`) + if _, err := tmpfile.Write(content); err != nil { + return "", err + } + if err := tmpfile.Close(); err != nil { + return "", err + } + + return tmpfile.Name(), nil +} + +func TestMain(t *testing.T) { + t.Run("Test setup mode with temp config file", func(t *testing.T) { + tmpConfig, err := createTestConfigFile() + if err != nil { + t.Fatalf("Failed to create temp config file: %v", err) + } + defer os.Remove(tmpConfig) + + config := gstructs.GagentConfig{ + File: tmpConfig, + Mode: "setup", + } + + // Run the main function with the temporary config + main.Run(config) + + // Check if the config has been set up correctly + expectedConfig := gstructs.GagentConfig{ + Mode: "setup", + ListenAddr: "0.0.0.0", + MonitorPort: 8888, + ClientPort: 35572, + RouterPort: 35570, + WorkerPort: 35571, + } + + if config.Mode != expectedConfig.Mode || + config.ListenAddr != expectedConfig.ListenAddr || + config.MonitorPort != expectedConfig.MonitorPort || + config.ClientPort != expectedConfig.ClientPort || + config.RouterPort != expectedConfig.RouterPort || + config.WorkerPort != expectedConfig.WorkerPort { + t.Fatalf("Expected config %+v, got %+v", expectedConfig, config) + } + }) +} diff --git a/go.mod b/go.mod index dcddcfd..bd63971 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/pebbe/zmq4 v1.2.9 github.com/prometheus/client_golang v1.14.0 github.com/slayer/autorestart v0.0.0-20170706172547-5ebd91f955ae + github.com/stretchr/testify v1.8.0 github.com/zclconf/go-cty v1.13.0 ) @@ -20,11 +21,13 @@ require ( github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/kr/pretty v0.2.0 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/common v0.42.0 // indirect github.com/prometheus/procfs v0.9.0 // indirect diff --git a/go.sum b/go.sum index e1c8dd3..bc82f88 100644 --- a/go.sum +++ b/go.sum @@ -12,7 +12,9 @@ github.com/caarlos0/env/v6 v6.10.1 h1:t1mPSxNpei6M5yAeu1qtRdPAK29Nbcf/n3G7x+b3/I github.com/caarlos0/env/v6 v6.10.1/go.mod h1:hvp/ryKXKipEkcuYjs9mI4bBCg+UI0Yhgm5Zu0ddvwc= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= @@ -41,6 +43,7 @@ github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTS github.com/pebbe/zmq4 v1.2.9 h1:JlHcdgq6zpppNR1tH0wXJq0XK03pRUc4lBlHTD7aj/4= github.com/pebbe/zmq4 v1.2.9/go.mod h1:nqnPueOapVhE2wItZ0uOErngczsJdLOGkebMxaO8r48= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= @@ -52,7 +55,11 @@ github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/slayer/autorestart v0.0.0-20170706172547-5ebd91f955ae h1:hnJJroq/kooxO2jUKDc8KXxj8tilWvOlD0hzDDv05ss= github.com/slayer/autorestart v0.0.0-20170706172547-5ebd91f955ae/go.mod h1:p+QQKBy7tS+myk+y3sgnAKx4gUtD/Q9Z6KEd77cLzWY= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/zclconf/go-cty v1.13.0 h1:It5dfKTTZHe9aeppbNOda3mN7Ag7sg6QkBNm6TkyFa0= github.com/zclconf/go-cty v1.13.0/go.mod h1:YKQzy/7pZ7iq2jNFzy5go57xdxdWoLLpaEp4u238AE0= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -66,5 +73,7 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.29.1 h1:7QBf+IK2gx70Ap/hDsOmam3GE0v9HicjfEdAxE62UoM= google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/chaindb/chaindb_test.go b/internal/chaindb/chaindb_test.go new file mode 100644 index 0000000..c735328 --- /dev/null +++ b/internal/chaindb/chaindb_test.go @@ -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) + } +} diff --git a/internal/client/client_test.go b/internal/client/client_test.go new file mode 100644 index 0000000..c2461d1 --- /dev/null +++ b/internal/client/client_test.go @@ -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() +} diff --git a/internal/gstructs/agent_status.go b/internal/gstructs/agent_status.go new file mode 100644 index 0000000..ab36540 --- /dev/null +++ b/internal/gstructs/agent_status.go @@ -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) +} diff --git a/internal/gstructs/agent_status_test.go b/internal/gstructs/agent_status_test.go new file mode 100644 index 0000000..7d84c26 --- /dev/null +++ b/internal/gstructs/agent_status_test.go @@ -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) + } + } +} diff --git a/internal/gstructs/gstructs.go b/internal/gstructs/gstructs.go index 9d2917f..d814ab0 100644 --- a/internal/gstructs/gstructs.go +++ b/internal/gstructs/gstructs.go @@ -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"` } diff --git a/internal/gstructs/gstructs_test.go b/internal/gstructs/gstructs_test.go new file mode 100644 index 0000000..160a47e --- /dev/null +++ b/internal/gstructs/gstructs_test.go @@ -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 +} diff --git a/internal/router/router_test.go b/internal/router/router_test.go new file mode 100644 index 0000000..35b90c2 --- /dev/null +++ b/internal/router/router_test.go @@ -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() +} diff --git a/internal/setup/setup_test.go b/internal/setup/setup_test.go new file mode 100644 index 0000000..5ffc8a5 --- /dev/null +++ b/internal/setup/setup_test.go @@ -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() +} diff --git a/internal/worker/worker_test.go b/internal/worker/worker_test.go new file mode 100644 index 0000000..35bab82 --- /dev/null +++ b/internal/worker/worker_test.go @@ -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() +}