Initial commit: AetherForge Linux (forge-mesh) v0.1.0-dev
Some checks failed
Test / test (push) Has been cancelled
Some checks failed
Test / test (push) Has been cancelled
This commit is contained in:
206
cmd/server/main_test.go
Normal file
206
cmd/server/main_test.go
Normal file
@@ -0,0 +1,206 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestServerHealthViaSubprocess(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
port := freePort(t)
|
||||
cfgPath := writeServerConfig(t, dir, port)
|
||||
|
||||
bin := buildServerBinary(t)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
cmd := exec.CommandContext(ctx, bin, "-config", cfgPath)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Start(); err != nil {
|
||||
t.Fatalf("start server: %v", err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
cancel()
|
||||
_ = cmd.Wait()
|
||||
})
|
||||
|
||||
base := fmt.Sprintf("http://127.0.0.1:%d", port)
|
||||
waitForHealth(t, base+"/api/v1/health", 15*time.Second)
|
||||
|
||||
resp, err := http.Get(base + "/api/v1/health")
|
||||
if err != nil {
|
||||
t.Fatalf("health GET: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("health status: got %d want 200", resp.StatusCode)
|
||||
}
|
||||
|
||||
var body struct {
|
||||
Status string `json:"status"`
|
||||
Service string `json:"service"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
|
||||
t.Fatalf("decode health: %v", err)
|
||||
}
|
||||
if body.Status != "ok" {
|
||||
t.Fatalf("status: got %q want ok", body.Status)
|
||||
}
|
||||
if body.Service != "forge-mesh-server" {
|
||||
t.Fatalf("service: got %q", body.Service)
|
||||
}
|
||||
if body.Version == "" {
|
||||
t.Fatal("expected version in health response")
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerUsesConfigDataDirAndPort(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
port := freePort(t)
|
||||
cfgPath := writeServerConfig(t, dir, port)
|
||||
|
||||
cfgData, err := os.ReadFile(cfgPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !containsAll(string(cfgData), dir, fmt.Sprintf("127.0.0.1:%d", port)) {
|
||||
t.Fatalf("config missing data_dir or listen_addr: %s", cfgData)
|
||||
}
|
||||
|
||||
dbPath := filepath.Join(dir, "forge-mesh.db")
|
||||
artifacts := filepath.Join(dir, "artifacts")
|
||||
|
||||
bin := buildServerBinary(t)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 12*time.Second)
|
||||
defer cancel()
|
||||
|
||||
cmd := exec.CommandContext(ctx, bin, "-config", cfgPath)
|
||||
if err := cmd.Start(); err != nil {
|
||||
t.Fatalf("start: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
cancel()
|
||||
_ = cmd.Wait()
|
||||
}()
|
||||
|
||||
waitForHealth(t, fmt.Sprintf("http://127.0.0.1:%d/api/v1/health", port), 12*time.Second)
|
||||
|
||||
for _, path := range []string{dbPath, artifacts, filepath.Join(dir, "signing.key")} {
|
||||
if _, err := os.Stat(path); err != nil {
|
||||
t.Fatalf("expected server to create %s: %v", path, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func buildServerBinary(t *testing.T) string {
|
||||
t.Helper()
|
||||
out := filepath.Join(t.TempDir(), "forge-mesh-server")
|
||||
cmd := exec.Command("go", "build", "-o", out, "./cmd/server")
|
||||
cmd.Dir = repoRoot(t)
|
||||
if outBytes, err := cmd.CombinedOutput(); err != nil {
|
||||
t.Fatalf("build server: %v\n%s", err, outBytes)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func writeServerConfig(t *testing.T, dir string, port int) string {
|
||||
t.Helper()
|
||||
cfgPath := filepath.Join(dir, "config.json")
|
||||
content := fmt.Sprintf(`{
|
||||
"listen_addr": "127.0.0.1:%d",
|
||||
"data_dir": %q,
|
||||
"database_path": %q,
|
||||
"auth": {
|
||||
"basic_username": "admin",
|
||||
"basic_password": "changeme",
|
||||
"fleet_secret": "test-fleet-secret"
|
||||
},
|
||||
"forge": {
|
||||
"signing_key_path": %q,
|
||||
"artifacts_dir": %q
|
||||
}
|
||||
}`, port, dir, filepath.Join(dir, "forge-mesh.db"),
|
||||
filepath.Join(dir, "signing.key"), filepath.Join(dir, "artifacts"))
|
||||
if err := os.WriteFile(cfgPath, []byte(content), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return cfgPath
|
||||
}
|
||||
|
||||
func freePort(t *testing.T) int {
|
||||
t.Helper()
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer ln.Close()
|
||||
return ln.Addr().(*net.TCPAddr).Port
|
||||
}
|
||||
|
||||
func waitForHealth(t *testing.T, url string, timeout time.Duration) {
|
||||
t.Helper()
|
||||
deadline := time.Now().Add(timeout)
|
||||
for time.Now().Before(deadline) {
|
||||
resp, err := http.Get(url)
|
||||
if err == nil {
|
||||
resp.Body.Close()
|
||||
if resp.StatusCode == http.StatusOK {
|
||||
return
|
||||
}
|
||||
}
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
}
|
||||
t.Fatalf("server did not become healthy at %s within %s", url, timeout)
|
||||
}
|
||||
|
||||
func repoRoot(t *testing.T) string {
|
||||
t.Helper()
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for {
|
||||
if _, err := os.Stat(filepath.Join(wd, "go.mod")); err == nil {
|
||||
return wd
|
||||
}
|
||||
parent := filepath.Dir(wd)
|
||||
if parent == wd {
|
||||
t.Fatal("go.mod not found")
|
||||
}
|
||||
wd = parent
|
||||
}
|
||||
}
|
||||
|
||||
func containsAll(s string, parts ...string) bool {
|
||||
for _, p := range parts {
|
||||
if !contains(s, p) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func contains(s, sub string) bool {
|
||||
return len(sub) == 0 || (len(s) >= len(sub) && indexOf(s, sub) >= 0)
|
||||
}
|
||||
|
||||
func indexOf(s, sub string) int {
|
||||
for i := 0; i+len(sub) <= len(s); i++ {
|
||||
if s[i:i+len(sub)] == sub {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
Reference in New Issue
Block a user