initial commit
This commit is contained in:
@@ -0,0 +1,174 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
// MockHealthResponse represents the expected health response structure
|
||||
type MockHealthResponse struct {
|
||||
Status string `json:"status"`
|
||||
Role string `json:"role"`
|
||||
Redis bool `json:"redis"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
}
|
||||
|
||||
func TestHealthResponseFormat(t *testing.T) {
|
||||
// Test that the health response has the expected format
|
||||
response := MockHealthResponse{
|
||||
Status: "ok",
|
||||
Role: "leader",
|
||||
Redis: true,
|
||||
Timestamp: "2024-01-01T00:00:00Z",
|
||||
}
|
||||
|
||||
data, err := json.Marshal(response)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to marshal: %v", err)
|
||||
}
|
||||
|
||||
var parsed map[string]interface{}
|
||||
if err := json.Unmarshal(data, &parsed); err != nil {
|
||||
t.Fatalf("Failed to unmarshal: %v", err)
|
||||
}
|
||||
|
||||
// Check required fields
|
||||
requiredFields := []string{"status", "role", "redis", "timestamp"}
|
||||
for _, field := range requiredFields {
|
||||
if _, exists := parsed[field]; !exists {
|
||||
t.Errorf("Missing required field: %s", field)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRouterSetup(t *testing.T) {
|
||||
router := mux.NewRouter()
|
||||
|
||||
// Register routes (simulating what the server does)
|
||||
routes := []struct {
|
||||
path string
|
||||
method string
|
||||
}{
|
||||
{"/health", "GET"},
|
||||
{"/status", "GET"},
|
||||
{"/leader", "GET"},
|
||||
{"/role", "GET"},
|
||||
{"/meta/{hash}", "GET"},
|
||||
{"/meta/{hash}", "PUT"},
|
||||
{"/meta/{hash}", "DELETE"},
|
||||
{"/meta", "GET"},
|
||||
{"/data/{hash}/path", "GET"},
|
||||
{"/data/{hash}", "HEAD"},
|
||||
{"/services", "GET"},
|
||||
{"/services/{name}", "GET"},
|
||||
}
|
||||
|
||||
// Register dummy handlers
|
||||
for _, route := range routes {
|
||||
path := route.path
|
||||
router.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}).Methods(route.method)
|
||||
}
|
||||
|
||||
// Test that routes are registered
|
||||
testCases := []struct {
|
||||
method string
|
||||
path string
|
||||
}{
|
||||
{"GET", "/health"},
|
||||
{"GET", "/status"},
|
||||
{"GET", "/leader"},
|
||||
{"GET", "/role"},
|
||||
{"GET", "/meta/testhash123"},
|
||||
{"PUT", "/meta/testhash123"},
|
||||
{"DELETE", "/meta/testhash123"},
|
||||
{"GET", "/meta"},
|
||||
{"GET", "/data/testhash123/path"},
|
||||
{"HEAD", "/data/testhash123"},
|
||||
{"GET", "/services"},
|
||||
{"GET", "/services/meta-sort"},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
req := httptest.NewRequest(tc.method, tc.path, nil)
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
if rr.Code != http.StatusOK {
|
||||
t.Errorf("Route %s %s returned status %d, expected 200", tc.method, tc.path, rr.Code)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCORSHeaders(t *testing.T) {
|
||||
// Create a simple handler
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
|
||||
// Wrap with CORS middleware
|
||||
corsHandler := func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
|
||||
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
|
||||
|
||||
if r.Method == "OPTIONS" {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return
|
||||
}
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
req := httptest.NewRequest("GET", "/health", nil)
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
corsHandler(handler).ServeHTTP(rr, req)
|
||||
|
||||
// Check CORS headers
|
||||
if rr.Header().Get("Access-Control-Allow-Origin") != "*" {
|
||||
t.Error("Missing or incorrect Access-Control-Allow-Origin header")
|
||||
}
|
||||
|
||||
if rr.Header().Get("Access-Control-Allow-Methods") == "" {
|
||||
t.Error("Missing Access-Control-Allow-Methods header")
|
||||
}
|
||||
}
|
||||
|
||||
func TestOptionsRequest(t *testing.T) {
|
||||
corsHandler := func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
|
||||
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
|
||||
|
||||
if r.Method == "OPTIONS" {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return
|
||||
}
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
|
||||
req := httptest.NewRequest("OPTIONS", "/meta/test", nil)
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
corsHandler(handler).ServeHTTP(rr, req)
|
||||
|
||||
if rr.Code != http.StatusOK {
|
||||
t.Errorf("OPTIONS request returned status %d, expected 200", rr.Code)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/metazla/meta-core/internal/config"
|
||||
)
|
||||
|
||||
func TestConfigDefaults(t *testing.T) {
|
||||
// Clear environment
|
||||
os.Clearenv()
|
||||
|
||||
cfg := config.Load()
|
||||
|
||||
// Check defaults
|
||||
if cfg.MetaCorePath != "/meta-core" {
|
||||
t.Errorf("Expected MetaCorePath '/meta-core', got '%s'", cfg.MetaCorePath)
|
||||
}
|
||||
|
||||
if cfg.FilesPath != "/files" {
|
||||
t.Errorf("Expected FilesPath '/files', got '%s'", cfg.FilesPath)
|
||||
}
|
||||
|
||||
if cfg.ServiceName != "meta-core" {
|
||||
t.Errorf("Expected ServiceName 'meta-core', got '%s'", cfg.ServiceName)
|
||||
}
|
||||
|
||||
if cfg.HTTPPort != 9000 {
|
||||
t.Errorf("Expected HTTPPort 9000, got %d", cfg.HTTPPort)
|
||||
}
|
||||
|
||||
if cfg.RedisPort != 6379 {
|
||||
t.Errorf("Expected RedisPort 6379, got %d", cfg.RedisPort)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigFromEnv(t *testing.T) {
|
||||
// Set environment variables
|
||||
os.Setenv("META_CORE_PATH", "/custom/meta-core")
|
||||
os.Setenv("FILES_PATH", "/custom/files")
|
||||
os.Setenv("SERVICE_NAME", "test-service")
|
||||
os.Setenv("META_CORE_HTTP_PORT", "8080")
|
||||
os.Setenv("REDIS_PORT", "6380")
|
||||
defer os.Clearenv()
|
||||
|
||||
cfg := config.Load()
|
||||
|
||||
if cfg.MetaCorePath != "/custom/meta-core" {
|
||||
t.Errorf("Expected MetaCorePath '/custom/meta-core', got '%s'", cfg.MetaCorePath)
|
||||
}
|
||||
|
||||
if cfg.FilesPath != "/custom/files" {
|
||||
t.Errorf("Expected FilesPath '/custom/files', got '%s'", cfg.FilesPath)
|
||||
}
|
||||
|
||||
if cfg.ServiceName != "test-service" {
|
||||
t.Errorf("Expected ServiceName 'test-service', got '%s'", cfg.ServiceName)
|
||||
}
|
||||
|
||||
if cfg.HTTPPort != 8080 {
|
||||
t.Errorf("Expected HTTPPort 8080, got %d", cfg.HTTPPort)
|
||||
}
|
||||
|
||||
if cfg.RedisPort != 6380 {
|
||||
t.Errorf("Expected RedisPort 6380, got %d", cfg.RedisPort)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigPaths(t *testing.T) {
|
||||
os.Setenv("META_CORE_PATH", "/test")
|
||||
defer os.Clearenv()
|
||||
|
||||
cfg := config.Load()
|
||||
|
||||
if cfg.LockFilePath() != "/test/locks/kv-leader.lock" {
|
||||
t.Errorf("Unexpected LockFilePath: %s", cfg.LockFilePath())
|
||||
}
|
||||
|
||||
if cfg.InfoFilePath() != "/test/locks/kv-leader.info" {
|
||||
t.Errorf("Unexpected InfoFilePath: %s", cfg.InfoFilePath())
|
||||
}
|
||||
|
||||
if cfg.RedisDataDir() != "/test/db/redis" {
|
||||
t.Errorf("Unexpected RedisDataDir: %s", cfg.RedisDataDir())
|
||||
}
|
||||
|
||||
if cfg.ServicesDir() != "/test/services" {
|
||||
t.Errorf("Unexpected ServicesDir: %s", cfg.ServicesDir())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/metazla/meta-core/internal/leader"
|
||||
)
|
||||
|
||||
func TestLeaderLockInfoJSON(t *testing.T) {
|
||||
info := &leader.LeaderLockInfo{
|
||||
Host: "test-host",
|
||||
API: "redis://10.0.0.1:6379",
|
||||
HTTP: "http://10.0.0.1:8180",
|
||||
BaseURL: "http://localhost:8180",
|
||||
Timestamp: 1704067200000,
|
||||
PID: 12345,
|
||||
}
|
||||
|
||||
// Marshal to JSON
|
||||
data, err := json.MarshalIndent(info, "", " ")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to marshal: %v", err)
|
||||
}
|
||||
|
||||
// Verify JSON structure matches TypeScript format
|
||||
var parsed map[string]interface{}
|
||||
if err := json.Unmarshal(data, &parsed); err != nil {
|
||||
t.Fatalf("Failed to unmarshal: %v", err)
|
||||
}
|
||||
|
||||
if parsed["host"] != "test-host" {
|
||||
t.Errorf("Expected host 'test-host', got '%v'", parsed["host"])
|
||||
}
|
||||
|
||||
if parsed["api"] != "redis://10.0.0.1:6379" {
|
||||
t.Errorf("Expected api 'redis://10.0.0.1:6379', got '%v'", parsed["api"])
|
||||
}
|
||||
|
||||
if parsed["http"] != "http://10.0.0.1:8180" {
|
||||
t.Errorf("Expected http 'http://10.0.0.1:8180', got '%v'", parsed["http"])
|
||||
}
|
||||
|
||||
if parsed["baseUrl"] != "http://localhost:8180" {
|
||||
t.Errorf("Expected baseUrl 'http://localhost:8180', got '%v'", parsed["baseUrl"])
|
||||
}
|
||||
|
||||
// Timestamp should be a number
|
||||
if _, ok := parsed["timestamp"].(float64); !ok {
|
||||
t.Errorf("Expected timestamp to be a number, got %T", parsed["timestamp"])
|
||||
}
|
||||
|
||||
// PID should be a number
|
||||
if _, ok := parsed["pid"].(float64); !ok {
|
||||
t.Errorf("Expected pid to be a number, got %T", parsed["pid"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestLeaderLockInfoJSONOmitEmpty(t *testing.T) {
|
||||
info := &leader.LeaderLockInfo{
|
||||
Host: "test-host",
|
||||
API: "redis://10.0.0.1:6379",
|
||||
HTTP: "http://10.0.0.1:8180",
|
||||
BaseURL: "", // Empty - should be omitted
|
||||
Timestamp: 1704067200000,
|
||||
PID: 12345,
|
||||
}
|
||||
|
||||
data, err := json.Marshal(info)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to marshal: %v", err)
|
||||
}
|
||||
|
||||
// baseUrl should not be present when empty
|
||||
var parsed map[string]interface{}
|
||||
if err := json.Unmarshal(data, &parsed); err != nil {
|
||||
t.Fatalf("Failed to unmarshal: %v", err)
|
||||
}
|
||||
|
||||
if _, exists := parsed["baseUrl"]; exists {
|
||||
t.Error("Expected baseUrl to be omitted when empty")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLeaderInfoFileFormat(t *testing.T) {
|
||||
// Create a temp directory
|
||||
tmpDir, err := os.MkdirTemp("", "meta-core-test")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
info := &leader.LeaderLockInfo{
|
||||
Host: "test-host",
|
||||
API: "redis://10.0.0.1:6379",
|
||||
HTTP: "http://10.0.0.1:8180",
|
||||
Timestamp: 1704067200000,
|
||||
PID: 12345,
|
||||
}
|
||||
|
||||
// Write to file
|
||||
infoPath := filepath.Join(tmpDir, "kv-leader.info")
|
||||
data, err := json.MarshalIndent(info, "", " ")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to marshal: %v", err)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(infoPath, data, 0644); err != nil {
|
||||
t.Fatalf("Failed to write file: %v", err)
|
||||
}
|
||||
|
||||
// Read and parse
|
||||
readData, err := os.ReadFile(infoPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read file: %v", err)
|
||||
}
|
||||
|
||||
var parsed leader.LeaderLockInfo
|
||||
if err := json.Unmarshal(readData, &parsed); err != nil {
|
||||
t.Fatalf("Failed to unmarshal: %v", err)
|
||||
}
|
||||
|
||||
if parsed.Host != info.Host {
|
||||
t.Errorf("Host mismatch: expected '%s', got '%s'", info.Host, parsed.Host)
|
||||
}
|
||||
|
||||
if parsed.API != info.API {
|
||||
t.Errorf("API mismatch: expected '%s', got '%s'", info.API, parsed.API)
|
||||
}
|
||||
|
||||
if parsed.HTTP != info.HTTP {
|
||||
t.Errorf("HTTP mismatch: expected '%s', got '%s'", info.HTTP, parsed.HTTP)
|
||||
}
|
||||
|
||||
if parsed.Timestamp != info.Timestamp {
|
||||
t.Errorf("Timestamp mismatch: expected %d, got %d", info.Timestamp, parsed.Timestamp)
|
||||
}
|
||||
|
||||
if parsed.PID != info.PID {
|
||||
t.Errorf("PID mismatch: expected %d, got %d", info.PID, parsed.PID)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user