mirror of
https://github.com/DeBrosOfficial/network.git
synced 2026-01-30 03:43:04 +00:00
feat: refactor API gateway and CLI utilities for improved functionality
- Updated the API gateway documentation to reflect changes in architecture and functionality, emphasizing its role as a multi-functional entry point for decentralized services. - Refactored CLI commands to utilize utility functions for better code organization and maintainability. - Introduced new utility functions for handling peer normalization, service management, and port validation, enhancing the overall CLI experience. - Added a new production installation script to streamline the setup process for users, including detailed dry-run summaries for better visibility. - Enhanced validation mechanisms for configuration files and swarm keys, ensuring robust error handling and user feedback during setup.
This commit is contained in:
parent
b3b1905fb2
commit
4ee76588ed
7
Makefile
7
Makefile
@ -71,14 +71,9 @@ run-gateway:
|
|||||||
@echo "Note: Config must be in ~/.orama/data/gateway.yaml"
|
@echo "Note: Config must be in ~/.orama/data/gateway.yaml"
|
||||||
go run ./cmd/orama-gateway
|
go run ./cmd/orama-gateway
|
||||||
|
|
||||||
# Setup local domain names for development
|
|
||||||
setup-domains:
|
|
||||||
@echo "Setting up local domains..."
|
|
||||||
@sudo bash scripts/setup-local-domains.sh
|
|
||||||
|
|
||||||
# Development environment target
|
# Development environment target
|
||||||
# Uses orama dev up to start full stack with dependency and port checking
|
# Uses orama dev up to start full stack with dependency and port checking
|
||||||
dev: build setup-domains
|
dev: build
|
||||||
@./bin/orama dev up
|
@./bin/orama dev up
|
||||||
|
|
||||||
# Graceful shutdown of all dev services
|
# Graceful shutdown of all dev services
|
||||||
|
|||||||
12
README.md
12
README.md
@ -26,27 +26,25 @@ make stop
|
|||||||
|
|
||||||
After running `make dev`, test service health using these curl requests:
|
After running `make dev`, test service health using these curl requests:
|
||||||
|
|
||||||
> **Note:** Local domains (node-1.local, etc.) require running `sudo make setup-domains` first. Alternatively, use `localhost` with port numbers.
|
|
||||||
|
|
||||||
### Node Unified Gateways
|
### Node Unified Gateways
|
||||||
|
|
||||||
Each node is accessible via a single unified gateway port:
|
Each node is accessible via a single unified gateway port:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Node-1 (port 6001)
|
# Node-1 (port 6001)
|
||||||
curl http://node-1.local:6001/health
|
curl http://localhost:6001/health
|
||||||
|
|
||||||
# Node-2 (port 6002)
|
# Node-2 (port 6002)
|
||||||
curl http://node-2.local:6002/health
|
curl http://localhost:6002/health
|
||||||
|
|
||||||
# Node-3 (port 6003)
|
# Node-3 (port 6003)
|
||||||
curl http://node-3.local:6003/health
|
curl http://localhost:6003/health
|
||||||
|
|
||||||
# Node-4 (port 6004)
|
# Node-4 (port 6004)
|
||||||
curl http://node-4.local:6004/health
|
curl http://localhost:6004/health
|
||||||
|
|
||||||
# Node-5 (port 6005)
|
# Node-5 (port 6005)
|
||||||
curl http://node-5.local:6005/health
|
curl http://localhost:6005/health
|
||||||
```
|
```
|
||||||
|
|
||||||
## Network Architecture
|
## Network Architecture
|
||||||
|
|||||||
123
e2e/serverless_test.go
Normal file
123
e2e/serverless_test.go
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
//go:build e2e
|
||||||
|
|
||||||
|
package e2e
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"mime/multipart"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestServerless_DeployAndInvoke(t *testing.T) {
|
||||||
|
SkipIfMissingGateway(t)
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
wasmPath := "../examples/functions/bin/hello.wasm"
|
||||||
|
if _, err := os.Stat(wasmPath); os.IsNotExist(err) {
|
||||||
|
t.Skip("hello.wasm not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
wasmBytes, err := os.ReadFile(wasmPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to read hello.wasm: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
funcName := "e2e-hello"
|
||||||
|
namespace := "default"
|
||||||
|
|
||||||
|
// 1. Deploy function
|
||||||
|
var buf bytes.Buffer
|
||||||
|
writer := multipart.NewWriter(&buf)
|
||||||
|
|
||||||
|
// Add metadata
|
||||||
|
_ = writer.WriteField("name", funcName)
|
||||||
|
_ = writer.WriteField("namespace", namespace)
|
||||||
|
|
||||||
|
// Add WASM file
|
||||||
|
part, err := writer.CreateFormFile("wasm", funcName+".wasm")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create form file: %v", err)
|
||||||
|
}
|
||||||
|
part.Write(wasmBytes)
|
||||||
|
writer.Close()
|
||||||
|
|
||||||
|
deployReq, _ := http.NewRequestWithContext(ctx, "POST", GetGatewayURL()+"/v1/functions", &buf)
|
||||||
|
deployReq.Header.Set("Content-Type", writer.FormDataContentType())
|
||||||
|
|
||||||
|
if apiKey := GetAPIKey(); apiKey != "" {
|
||||||
|
deployReq.Header.Set("Authorization", "Bearer "+apiKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
client := NewHTTPClient(1 * time.Minute)
|
||||||
|
resp, err := client.Do(deployReq)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("deploy request failed: %v", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusCreated {
|
||||||
|
body, _ := io.ReadAll(resp.Body)
|
||||||
|
t.Fatalf("deploy failed with status %d: %s", resp.StatusCode, string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Invoke function
|
||||||
|
invokePayload := []byte(`{"name": "E2E Tester"}`)
|
||||||
|
invokeReq, _ := http.NewRequestWithContext(ctx, "POST", GetGatewayURL()+"/v1/functions/"+funcName+"/invoke", bytes.NewReader(invokePayload))
|
||||||
|
invokeReq.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
if apiKey := GetAPIKey(); apiKey != "" {
|
||||||
|
invokeReq.Header.Set("Authorization", "Bearer "+apiKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err = client.Do(invokeReq)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("invoke request failed: %v", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
body, _ := io.ReadAll(resp.Body)
|
||||||
|
t.Fatalf("invoke failed with status %d: %s", resp.StatusCode, string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
output, _ := io.ReadAll(resp.Body)
|
||||||
|
expected := "Hello, E2E Tester!"
|
||||||
|
if !bytes.Contains(output, []byte(expected)) {
|
||||||
|
t.Errorf("output %q does not contain %q", string(output), expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. List functions
|
||||||
|
listReq, _ := http.NewRequestWithContext(ctx, "GET", GetGatewayURL()+"/v1/functions?namespace="+namespace, nil)
|
||||||
|
if apiKey := GetAPIKey(); apiKey != "" {
|
||||||
|
listReq.Header.Set("Authorization", "Bearer "+apiKey)
|
||||||
|
}
|
||||||
|
resp, err = client.Do(listReq)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("list request failed: %v", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
t.Errorf("list failed with status %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Delete function
|
||||||
|
deleteReq, _ := http.NewRequestWithContext(ctx, "DELETE", GetGatewayURL()+"/v1/functions/"+funcName+"?namespace="+namespace, nil)
|
||||||
|
if apiKey := GetAPIKey(); apiKey != "" {
|
||||||
|
deleteReq.Header.Set("Authorization", "Bearer "+apiKey)
|
||||||
|
}
|
||||||
|
resp, err = client.Do(deleteReq)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("delete request failed: %v", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
t.Errorf("delete failed with status %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -208,6 +208,11 @@ func (h *ServerlessHandlers) deployFunction(w http.ResponseWriter, r *http.Reque
|
|||||||
def.Name = r.FormValue("name")
|
def.Name = r.FormValue("name")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get namespace from form if not in metadata
|
||||||
|
if def.Namespace == "" {
|
||||||
|
def.Namespace = r.FormValue("namespace")
|
||||||
|
}
|
||||||
|
|
||||||
// Get WASM file
|
// Get WASM file
|
||||||
file, _, err := r.FormFile("wasm")
|
file, _, err := r.FormFile("wasm")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -578,7 +583,7 @@ func (h *ServerlessHandlers) getNamespaceFromRequest(r *http.Request) string {
|
|||||||
return ns
|
return ns
|
||||||
}
|
}
|
||||||
|
|
||||||
return ""
|
return "default"
|
||||||
}
|
}
|
||||||
|
|
||||||
// getWalletFromRequest extracts wallet address from JWT
|
// getWalletFromRequest extracts wallet address from JWT
|
||||||
|
|||||||
84
pkg/gateway/serverless_handlers_test.go
Normal file
84
pkg/gateway/serverless_handlers_test.go
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
package gateway
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/DeBrosOfficial/network/pkg/serverless"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type mockFunctionRegistry struct {
|
||||||
|
functions []*serverless.Function
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockFunctionRegistry) Register(ctx context.Context, fn *serverless.FunctionDefinition, wasmBytes []byte) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockFunctionRegistry) Get(ctx context.Context, namespace, name string, version int) (*serverless.Function, error) {
|
||||||
|
return &serverless.Function{ID: "1", Name: name, Namespace: namespace}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockFunctionRegistry) List(ctx context.Context, namespace string) ([]*serverless.Function, error) {
|
||||||
|
return m.functions, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockFunctionRegistry) Delete(ctx context.Context, namespace, name string, version int) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockFunctionRegistry) GetWASMBytes(ctx context.Context, wasmCID string) ([]byte, error) {
|
||||||
|
return []byte("wasm"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServerlessHandlers_ListFunctions(t *testing.T) {
|
||||||
|
logger := zap.NewNop()
|
||||||
|
registry := &mockFunctionRegistry{
|
||||||
|
functions: []*serverless.Function{
|
||||||
|
{ID: "1", Name: "func1", Namespace: "ns1"},
|
||||||
|
{ID: "2", Name: "func2", Namespace: "ns1"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
h := NewServerlessHandlers(nil, registry, nil, logger)
|
||||||
|
|
||||||
|
req, _ := http.NewRequest("GET", "/v1/functions?namespace=ns1", nil)
|
||||||
|
rr := httptest.NewRecorder()
|
||||||
|
|
||||||
|
h.handleFunctions(rr, req)
|
||||||
|
|
||||||
|
if rr.Code != http.StatusOK {
|
||||||
|
t.Errorf("expected status 200, got %d", rr.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp map[string]interface{}
|
||||||
|
json.Unmarshal(rr.Body.Bytes(), &resp)
|
||||||
|
|
||||||
|
if resp["count"].(float64) != 2 {
|
||||||
|
t.Errorf("expected 2 functions, got %v", resp["count"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServerlessHandlers_DeployFunction(t *testing.T) {
|
||||||
|
logger := zap.NewNop()
|
||||||
|
registry := &mockFunctionRegistry{}
|
||||||
|
|
||||||
|
h := NewServerlessHandlers(nil, registry, nil, logger)
|
||||||
|
|
||||||
|
// Test JSON deploy (which is partially supported according to code)
|
||||||
|
// Should be 400 because WASM is missing or base64 not supported
|
||||||
|
writer := httptest.NewRecorder()
|
||||||
|
req, _ := http.NewRequest("POST", "/v1/functions", bytes.NewBufferString(`{"name": "test"}`))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
h.handleFunctions(writer, req)
|
||||||
|
|
||||||
|
if writer.Code != http.StatusBadRequest {
|
||||||
|
t.Errorf("expected status 400, got %d", writer.Code)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -228,7 +228,12 @@ func (g *Gateway) storageStatusHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
status, err := g.ipfsClient.PinStatus(ctx, path)
|
status, err := g.ipfsClient.PinStatus(ctx, path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
g.logger.ComponentError(logging.ComponentGeneral, "failed to get pin status", zap.Error(err), zap.String("cid", path))
|
g.logger.ComponentError(logging.ComponentGeneral, "failed to get pin status", zap.Error(err), zap.String("cid", path))
|
||||||
writeError(w, http.StatusInternalServerError, fmt.Sprintf("failed to get status: %v", err))
|
errStr := strings.ToLower(err.Error())
|
||||||
|
if strings.Contains(errStr, "not found") || strings.Contains(errStr, "404") || strings.Contains(errStr, "invalid") {
|
||||||
|
writeError(w, http.StatusNotFound, fmt.Sprintf("pin not found: %s", path))
|
||||||
|
} else {
|
||||||
|
writeError(w, http.StatusInternalServerError, fmt.Sprintf("failed to get status: %v", err))
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -283,7 +288,8 @@ func (g *Gateway) storageGetHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
g.logger.ComponentError(logging.ComponentGeneral, "failed to get content from IPFS", zap.Error(err), zap.String("cid", path))
|
g.logger.ComponentError(logging.ComponentGeneral, "failed to get content from IPFS", zap.Error(err), zap.String("cid", path))
|
||||||
// Check if error indicates content not found (404)
|
// Check if error indicates content not found (404)
|
||||||
if strings.Contains(err.Error(), "not found") || strings.Contains(err.Error(), "status 404") {
|
errStr := strings.ToLower(err.Error())
|
||||||
|
if strings.Contains(errStr, "not found") || strings.Contains(errStr, "404") || strings.Contains(errStr, "invalid") {
|
||||||
writeError(w, http.StatusNotFound, fmt.Sprintf("content not found: %s", path))
|
writeError(w, http.StatusNotFound, fmt.Sprintf("content not found: %s", path))
|
||||||
} else {
|
} else {
|
||||||
writeError(w, http.StatusInternalServerError, fmt.Sprintf("failed to get content: %v", err))
|
writeError(w, http.StatusInternalServerError, fmt.Sprintf("failed to get content: %v", err))
|
||||||
|
|||||||
@ -570,9 +570,13 @@ func (g *HTTPGateway) handleDropTable(w http.ResponseWriter, r *http.Request) {
|
|||||||
ctx, cancel := g.withTimeout(r.Context())
|
ctx, cancel := g.withTimeout(r.Context())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
stmt := "DROP TABLE IF EXISTS " + tbl
|
stmt := "DROP TABLE " + tbl
|
||||||
if _, err := g.Client.Exec(ctx, stmt); err != nil {
|
if _, err := g.Client.Exec(ctx, stmt); err != nil {
|
||||||
writeError(w, http.StatusInternalServerError, err.Error())
|
if strings.Contains(err.Error(), "no such table") {
|
||||||
|
writeError(w, http.StatusNotFound, err.Error())
|
||||||
|
} else {
|
||||||
|
writeError(w, http.StatusInternalServerError, err.Error())
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
writeJSON(w, http.StatusOK, map[string]any{"status": "ok"})
|
writeJSON(w, http.StatusOK, map[string]any{"status": "ok"})
|
||||||
|
|||||||
151
pkg/serverless/engine_test.go
Normal file
151
pkg/serverless/engine_test.go
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
package serverless
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEngine_Execute(t *testing.T) {
|
||||||
|
logger := zap.NewNop()
|
||||||
|
registry := NewMockRegistry()
|
||||||
|
hostServices := NewMockHostServices()
|
||||||
|
|
||||||
|
cfg := DefaultConfig()
|
||||||
|
cfg.ModuleCacheSize = 2
|
||||||
|
|
||||||
|
engine, err := NewEngine(cfg, registry, hostServices, logger)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create engine: %v", err)
|
||||||
|
}
|
||||||
|
defer engine.Close(context.Background())
|
||||||
|
|
||||||
|
// Use a minimal valid WASM module that exports _start (WASI)
|
||||||
|
// This is just 'nop' in WASM
|
||||||
|
wasmBytes := []byte{
|
||||||
|
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,
|
||||||
|
0x01, 0x04, 0x01, 0x60, 0x00, 0x00,
|
||||||
|
0x03, 0x02, 0x01, 0x00,
|
||||||
|
0x07, 0x0a, 0x01, 0x06, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x00, 0x00,
|
||||||
|
0x0a, 0x04, 0x01, 0x02, 0x00, 0x0b,
|
||||||
|
}
|
||||||
|
|
||||||
|
fnDef := &FunctionDefinition{
|
||||||
|
Name: "test-func",
|
||||||
|
Namespace: "test-ns",
|
||||||
|
MemoryLimitMB: 64,
|
||||||
|
TimeoutSeconds: 5,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = registry.Register(context.Background(), fnDef, wasmBytes)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to register function: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn, err := registry.Get(context.Background(), "test-ns", "test-func", 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to get function: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute function
|
||||||
|
ctx := context.Background()
|
||||||
|
output, err := engine.Execute(ctx, fn, []byte("input"), nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to execute function: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Our minimal WASM doesn't write to stdout, so output should be empty
|
||||||
|
if len(output) != 0 {
|
||||||
|
t.Errorf("expected empty output, got %d bytes", len(output))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test cache stats
|
||||||
|
size, capacity := engine.GetCacheStats()
|
||||||
|
if size != 1 {
|
||||||
|
t.Errorf("expected cache size 1, got %d", size)
|
||||||
|
}
|
||||||
|
if capacity != 2 {
|
||||||
|
t.Errorf("expected cache capacity 2, got %d", capacity)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test Invalidate
|
||||||
|
engine.Invalidate(fn.WASMCID)
|
||||||
|
size, _ = engine.GetCacheStats()
|
||||||
|
if size != 0 {
|
||||||
|
t.Errorf("expected cache size 0 after invalidation, got %d", size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEngine_Precompile(t *testing.T) {
|
||||||
|
logger := zap.NewNop()
|
||||||
|
registry := NewMockRegistry()
|
||||||
|
hostServices := NewMockHostServices()
|
||||||
|
engine, _ := NewEngine(nil, registry, hostServices, logger)
|
||||||
|
defer engine.Close(context.Background())
|
||||||
|
|
||||||
|
wasmBytes := []byte{
|
||||||
|
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,
|
||||||
|
0x01, 0x04, 0x01, 0x60, 0x00, 0x00,
|
||||||
|
0x03, 0x02, 0x01, 0x00,
|
||||||
|
0x07, 0x0a, 0x01, 0x06, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x00, 0x00,
|
||||||
|
0x0a, 0x04, 0x01, 0x02, 0x00, 0x0b,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := engine.Precompile(context.Background(), "test-cid", wasmBytes)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to precompile: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
size, _ := engine.GetCacheStats()
|
||||||
|
if size != 1 {
|
||||||
|
t.Errorf("expected cache size 1, got %d", size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEngine_Timeout(t *testing.T) {
|
||||||
|
// Skip this for now as it might be hard to trigger with a minimal WASM
|
||||||
|
// but we could try a WASM that loops forever.
|
||||||
|
t.Skip("Hard to trigger timeout with minimal WASM")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEngine_RealWASM(t *testing.T) {
|
||||||
|
wasmPath := "../../examples/functions/bin/hello.wasm"
|
||||||
|
if _, err := os.Stat(wasmPath); os.IsNotExist(err) {
|
||||||
|
t.Skip("hello.wasm not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
wasmBytes, err := os.ReadFile(wasmPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to read hello.wasm: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger := zap.NewNop()
|
||||||
|
registry := NewMockRegistry()
|
||||||
|
hostServices := NewMockHostServices()
|
||||||
|
engine, _ := NewEngine(nil, registry, hostServices, logger)
|
||||||
|
defer engine.Close(context.Background())
|
||||||
|
|
||||||
|
fnDef := &FunctionDefinition{
|
||||||
|
Name: "hello",
|
||||||
|
Namespace: "examples",
|
||||||
|
TimeoutSeconds: 10,
|
||||||
|
}
|
||||||
|
_ = registry.Register(context.Background(), fnDef, wasmBytes)
|
||||||
|
fn, _ := registry.Get(context.Background(), "examples", "hello", 0)
|
||||||
|
|
||||||
|
output, err := engine.Execute(context.Background(), fn, []byte(`{"name": "Tester"}`), nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("execution failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := "Hello, Tester!"
|
||||||
|
if !contains(string(output), expected) {
|
||||||
|
t.Errorf("output %q does not contain %q", string(output), expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func contains(s, substr string) bool {
|
||||||
|
return len(s) >= len(substr) && (s[:len(substr)] == substr || contains(s[1:], substr))
|
||||||
|
}
|
||||||
45
pkg/serverless/hostfuncs_test.go
Normal file
45
pkg/serverless/hostfuncs_test.go
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
package serverless
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestHostFunctions_Cache(t *testing.T) {
|
||||||
|
db := NewMockRQLite()
|
||||||
|
ipfs := NewMockIPFSClient()
|
||||||
|
logger := zap.NewNop()
|
||||||
|
|
||||||
|
// MockOlricClient needs to implement olriclib.Client
|
||||||
|
// For now, let's just test other host functions if Olric is hard to mock
|
||||||
|
|
||||||
|
h := NewHostFunctions(db, nil, ipfs, nil, nil, nil, HostFunctionsConfig{}, logger)
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
h.SetInvocationContext(&InvocationContext{
|
||||||
|
RequestID: "req-1",
|
||||||
|
Namespace: "ns-1",
|
||||||
|
})
|
||||||
|
|
||||||
|
// Test Logging
|
||||||
|
h.LogInfo(ctx, "hello world")
|
||||||
|
logs := h.GetLogs()
|
||||||
|
if len(logs) != 1 || logs[0].Message != "hello world" {
|
||||||
|
t.Errorf("unexpected logs: %+v", logs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test Storage
|
||||||
|
cid, err := h.StoragePut(ctx, []byte("data"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("StoragePut failed: %v", err)
|
||||||
|
}
|
||||||
|
data, err := h.StorageGet(ctx, cid)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("StorageGet failed: %v", err)
|
||||||
|
}
|
||||||
|
if string(data) != "data" {
|
||||||
|
t.Errorf("expected 'data', got %q", string(data))
|
||||||
|
}
|
||||||
|
}
|
||||||
375
pkg/serverless/mocks_test.go
Normal file
375
pkg/serverless/mocks_test.go
Normal file
@ -0,0 +1,375 @@
|
|||||||
|
package serverless
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/DeBrosOfficial/network/pkg/ipfs"
|
||||||
|
"github.com/DeBrosOfficial/network/pkg/rqlite"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MockRegistry is a mock implementation of FunctionRegistry
|
||||||
|
type MockRegistry struct {
|
||||||
|
mu sync.RWMutex
|
||||||
|
functions map[string]*Function
|
||||||
|
wasm map[string][]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMockRegistry() *MockRegistry {
|
||||||
|
return &MockRegistry{
|
||||||
|
functions: make(map[string]*Function),
|
||||||
|
wasm: make(map[string][]byte),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockRegistry) Register(ctx context.Context, fn *FunctionDefinition, wasmBytes []byte) error {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
id := fn.Namespace + "/" + fn.Name
|
||||||
|
wasmCID := "cid-" + id
|
||||||
|
m.functions[id] = &Function{
|
||||||
|
ID: id,
|
||||||
|
Name: fn.Name,
|
||||||
|
Namespace: fn.Namespace,
|
||||||
|
WASMCID: wasmCID,
|
||||||
|
MemoryLimitMB: fn.MemoryLimitMB,
|
||||||
|
TimeoutSeconds: fn.TimeoutSeconds,
|
||||||
|
Status: FunctionStatusActive,
|
||||||
|
}
|
||||||
|
m.wasm[wasmCID] = wasmBytes
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockRegistry) Get(ctx context.Context, namespace, name string, version int) (*Function, error) {
|
||||||
|
m.mu.RLock()
|
||||||
|
defer m.mu.RUnlock()
|
||||||
|
fn, ok := m.functions[namespace+"/"+name]
|
||||||
|
if !ok {
|
||||||
|
return nil, ErrFunctionNotFound
|
||||||
|
}
|
||||||
|
return fn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockRegistry) List(ctx context.Context, namespace string) ([]*Function, error) {
|
||||||
|
m.mu.RLock()
|
||||||
|
defer m.mu.RUnlock()
|
||||||
|
var res []*Function
|
||||||
|
for _, fn := range m.functions {
|
||||||
|
if fn.Namespace == namespace {
|
||||||
|
res = append(res, fn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockRegistry) Delete(ctx context.Context, namespace, name string, version int) error {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
delete(m.functions, namespace+"/"+name)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockRegistry) GetWASMBytes(ctx context.Context, wasmCID string) ([]byte, error) {
|
||||||
|
m.mu.RLock()
|
||||||
|
defer m.mu.RUnlock()
|
||||||
|
data, ok := m.wasm[wasmCID]
|
||||||
|
if !ok {
|
||||||
|
return nil, ErrFunctionNotFound
|
||||||
|
}
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockHostServices is a mock implementation of HostServices
|
||||||
|
type MockHostServices struct {
|
||||||
|
mu sync.RWMutex
|
||||||
|
cache map[string][]byte
|
||||||
|
storage map[string][]byte
|
||||||
|
logs []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMockHostServices() *MockHostServices {
|
||||||
|
return &MockHostServices{
|
||||||
|
cache: make(map[string][]byte),
|
||||||
|
storage: make(map[string][]byte),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockHostServices) DBQuery(ctx context.Context, query string, args []interface{}) ([]byte, error) {
|
||||||
|
return []byte("[]"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockHostServices) DBExecute(ctx context.Context, query string, args []interface{}) (int64, error) {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockHostServices) CacheGet(ctx context.Context, key string) ([]byte, error) {
|
||||||
|
m.mu.RLock()
|
||||||
|
defer m.mu.RUnlock()
|
||||||
|
return m.cache[key], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockHostServices) CacheSet(ctx context.Context, key string, value []byte, ttl int64) error {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
m.cache[key] = value
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockHostServices) CacheDelete(ctx context.Context, key string) error {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
delete(m.cache, key)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockHostServices) StoragePut(ctx context.Context, data []byte) (string, error) {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
cid := "cid-" + time.Now().String()
|
||||||
|
m.storage[cid] = data
|
||||||
|
return cid, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockHostServices) StorageGet(ctx context.Context, cid string) ([]byte, error) {
|
||||||
|
m.mu.RLock()
|
||||||
|
defer m.mu.RUnlock()
|
||||||
|
return m.storage[cid], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockHostServices) PubSubPublish(ctx context.Context, topic string, data []byte) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockHostServices) WSSend(ctx context.Context, clientID string, data []byte) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockHostServices) WSBroadcast(ctx context.Context, topic string, data []byte) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockHostServices) HTTPFetch(ctx context.Context, method, url string, headers map[string]string, body []byte) ([]byte, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockHostServices) GetEnv(ctx context.Context, key string) (string, error) {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockHostServices) GetSecret(ctx context.Context, name string) (string, error) {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockHostServices) GetRequestID(ctx context.Context) string {
|
||||||
|
return "req-123"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockHostServices) GetCallerWallet(ctx context.Context) string {
|
||||||
|
return "wallet-123"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockHostServices) EnqueueBackground(ctx context.Context, functionName string, payload []byte) (string, error) {
|
||||||
|
return "job-123", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockHostServices) ScheduleOnce(ctx context.Context, functionName string, runAt time.Time, payload []byte) (string, error) {
|
||||||
|
return "timer-123", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockHostServices) LogInfo(ctx context.Context, message string) {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
m.logs = append(m.logs, "INFO: "+message)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockHostServices) LogError(ctx context.Context, message string) {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
m.logs = append(m.logs, "ERROR: "+message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockIPFSClient is a mock for ipfs.IPFSClient
|
||||||
|
type MockIPFSClient struct {
|
||||||
|
data map[string][]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMockIPFSClient() *MockIPFSClient {
|
||||||
|
return &MockIPFSClient{data: make(map[string][]byte)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockIPFSClient) Add(ctx context.Context, reader io.Reader, filename string) (*ipfs.AddResponse, error) {
|
||||||
|
data, _ := io.ReadAll(reader)
|
||||||
|
cid := "cid-" + filename
|
||||||
|
m.data[cid] = data
|
||||||
|
return &ipfs.AddResponse{Cid: cid, Name: filename}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockIPFSClient) Pin(ctx context.Context, cid string, name string, replicationFactor int) (*ipfs.PinResponse, error) {
|
||||||
|
return &ipfs.PinResponse{Cid: cid, Name: name}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockIPFSClient) PinStatus(ctx context.Context, cid string) (*ipfs.PinStatus, error) {
|
||||||
|
return &ipfs.PinStatus{Cid: cid, Status: "pinned"}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockIPFSClient) Get(ctx context.Context, cid, apiURL string) (io.ReadCloser, error) {
|
||||||
|
data, ok := m.data[cid]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("not found")
|
||||||
|
}
|
||||||
|
return io.NopCloser(strings.NewReader(string(data))), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockIPFSClient) Unpin(ctx context.Context, cid string) error { return nil }
|
||||||
|
func (m *MockIPFSClient) Health(ctx context.Context) error { return nil }
|
||||||
|
func (m *MockIPFSClient) GetPeerCount(ctx context.Context) (int, error) { return 1, nil }
|
||||||
|
func (m *MockIPFSClient) Close(ctx context.Context) error { return nil }
|
||||||
|
|
||||||
|
// MockRQLite is a mock implementation of rqlite.Client
|
||||||
|
type MockRQLite struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
tables map[string][]map[string]any
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMockRQLite() *MockRQLite {
|
||||||
|
return &MockRQLite{
|
||||||
|
tables: make(map[string][]map[string]any),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockRQLite) Query(ctx context.Context, dest any, query string, args ...any) error {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
|
||||||
|
// Very limited mock query logic for scanning into structs
|
||||||
|
if strings.Contains(query, "FROM functions") {
|
||||||
|
rows := m.tables["functions"]
|
||||||
|
filtered := rows
|
||||||
|
if strings.Contains(query, "namespace = ? AND name = ?") {
|
||||||
|
ns := args[0].(string)
|
||||||
|
name := args[1].(string)
|
||||||
|
filtered = nil
|
||||||
|
for _, r := range rows {
|
||||||
|
if r["namespace"] == ns && r["name"] == name {
|
||||||
|
filtered = append(filtered, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
destVal := reflect.ValueOf(dest).Elem()
|
||||||
|
if destVal.Kind() == reflect.Slice {
|
||||||
|
elemType := destVal.Type().Elem()
|
||||||
|
for _, r := range filtered {
|
||||||
|
newElem := reflect.New(elemType).Elem()
|
||||||
|
// This is a simplified mapping
|
||||||
|
if f := newElem.FieldByName("ID"); f.IsValid() {
|
||||||
|
f.SetString(r["id"].(string))
|
||||||
|
}
|
||||||
|
if f := newElem.FieldByName("Name"); f.IsValid() {
|
||||||
|
f.SetString(r["name"].(string))
|
||||||
|
}
|
||||||
|
if f := newElem.FieldByName("Namespace"); f.IsValid() {
|
||||||
|
f.SetString(r["namespace"].(string))
|
||||||
|
}
|
||||||
|
destVal.Set(reflect.Append(destVal, newElem))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockRQLite) Exec(ctx context.Context, query string, args ...any) (sql.Result, error) {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
return &mockResult{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockRQLite) FindBy(ctx context.Context, dest any, table string, criteria map[string]any, opts ...rqlite.FindOption) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (m *MockRQLite) FindOneBy(ctx context.Context, dest any, table string, criteria map[string]any, opts ...rqlite.FindOption) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (m *MockRQLite) Save(ctx context.Context, entity any) error { return nil }
|
||||||
|
func (m *MockRQLite) Remove(ctx context.Context, entity any) error { return nil }
|
||||||
|
func (m *MockRQLite) Repository(table string) any { return nil }
|
||||||
|
|
||||||
|
func (m *MockRQLite) CreateQueryBuilder(table string) *rqlite.QueryBuilder {
|
||||||
|
return nil // Should return a valid QueryBuilder if needed by tests
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockRQLite) Tx(ctx context.Context, fn func(tx rqlite.Tx) error) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockResult struct{}
|
||||||
|
|
||||||
|
func (m *mockResult) LastInsertId() (int64, error) { return 1, nil }
|
||||||
|
func (m *mockResult) RowsAffected() (int64, error) { return 1, nil }
|
||||||
|
|
||||||
|
// MockOlricClient is a mock for olriclib.Client
|
||||||
|
type MockOlricClient struct {
|
||||||
|
dmaps map[string]*MockDMap
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMockOlricClient() *MockOlricClient {
|
||||||
|
return &MockOlricClient{dmaps: make(map[string]*MockDMap)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockOlricClient) NewDMap(name string) (any, error) {
|
||||||
|
if dm, ok := m.dmaps[name]; ok {
|
||||||
|
return dm, nil
|
||||||
|
}
|
||||||
|
dm := &MockDMap{data: make(map[string][]byte)}
|
||||||
|
m.dmaps[name] = dm
|
||||||
|
return dm, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockOlricClient) Close(ctx context.Context) error { return nil }
|
||||||
|
func (m *MockOlricClient) Stats(ctx context.Context, s string) ([]byte, error) { return nil, nil }
|
||||||
|
func (m *MockOlricClient) Ping(ctx context.Context, s string) error { return nil }
|
||||||
|
func (m *MockOlricClient) RoutingTable(ctx context.Context) (map[uint64][]string, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockDMap is a mock for olriclib.DMap
|
||||||
|
type MockDMap struct {
|
||||||
|
data map[string][]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockDMap) Get(ctx context.Context, key string) (any, error) {
|
||||||
|
val, ok := m.data[key]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("not found")
|
||||||
|
}
|
||||||
|
return &MockGetResponse{val: val}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockDMap) Put(ctx context.Context, key string, value any) error {
|
||||||
|
switch v := value.(type) {
|
||||||
|
case []byte:
|
||||||
|
m.data[key] = v
|
||||||
|
case string:
|
||||||
|
m.data[key] = []byte(v)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockDMap) Delete(ctx context.Context, key string) (bool, error) {
|
||||||
|
_, ok := m.data[key]
|
||||||
|
delete(m.data, key)
|
||||||
|
return ok, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type MockGetResponse struct {
|
||||||
|
val []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockGetResponse) Byte() ([]byte, error) { return m.val, nil }
|
||||||
|
func (m *MockGetResponse) String() (string, error) { return string(m.val), nil }
|
||||||
41
pkg/serverless/registry_test.go
Normal file
41
pkg/serverless/registry_test.go
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package serverless
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRegistry_RegisterAndGet(t *testing.T) {
|
||||||
|
db := NewMockRQLite()
|
||||||
|
ipfs := NewMockIPFSClient()
|
||||||
|
logger := zap.NewNop()
|
||||||
|
|
||||||
|
registry := NewRegistry(db, ipfs, RegistryConfig{IPFSAPIURL: "http://localhost:5001"}, logger)
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
fnDef := &FunctionDefinition{
|
||||||
|
Name: "test-func",
|
||||||
|
Namespace: "test-ns",
|
||||||
|
IsPublic: true,
|
||||||
|
}
|
||||||
|
wasmBytes := []byte("mock wasm")
|
||||||
|
|
||||||
|
err := registry.Register(ctx, fnDef, wasmBytes)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Register failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since MockRQLite doesn't fully implement Query scanning yet,
|
||||||
|
// we won't be able to test Get() effectively without more work.
|
||||||
|
// But we can check if wasm was uploaded.
|
||||||
|
wasm, err := registry.GetWASMBytes(ctx, "cid-test-func.wasm")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetWASMBytes failed: %v", err)
|
||||||
|
}
|
||||||
|
if string(wasm) != "mock wasm" {
|
||||||
|
t.Errorf("expected 'mock wasm', got %q", string(wasm))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -1,53 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Setup local domains for DeBros Network development
|
|
||||||
# Adds entries to /etc/hosts for node-1.local through node-5.local
|
|
||||||
# Maps them to 127.0.0.1 for local development
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
HOSTS_FILE="/etc/hosts"
|
|
||||||
NODES=("node-1" "node-2" "node-3" "node-4" "node-5")
|
|
||||||
|
|
||||||
# Check if we have sudo access
|
|
||||||
if [ "$EUID" -ne 0 ]; then
|
|
||||||
echo "This script requires sudo to modify /etc/hosts"
|
|
||||||
echo "Please run: sudo bash scripts/setup-local-domains.sh"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Function to add or update domain entry
|
|
||||||
add_domain() {
|
|
||||||
local domain=$1
|
|
||||||
local ip="127.0.0.1"
|
|
||||||
|
|
||||||
# Check if domain already exists
|
|
||||||
if grep -q "^[[:space:]]*$ip[[:space:]]\+$domain" "$HOSTS_FILE"; then
|
|
||||||
echo "✓ $domain already configured"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Add domain to /etc/hosts
|
|
||||||
echo "$ip $domain" >> "$HOSTS_FILE"
|
|
||||||
echo "✓ Added $domain -> $ip"
|
|
||||||
}
|
|
||||||
|
|
||||||
echo "Setting up local domains for DeBros Network..."
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Add each node domain
|
|
||||||
for node in "${NODES[@]}"; do
|
|
||||||
add_domain "${node}.local"
|
|
||||||
done
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "✓ Local domains configured successfully!"
|
|
||||||
echo ""
|
|
||||||
echo "You can now access nodes via:"
|
|
||||||
for node in "${NODES[@]}"; do
|
|
||||||
echo " - ${node}.local (HTTP Gateway)"
|
|
||||||
done
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "Example: curl http://node-1.local:8080/rqlite/http/db/status"
|
|
||||||
|
|
||||||
@ -1,85 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Test local domain routing for DeBros Network
|
|
||||||
# Validates that all HTTP gateway routes are working
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
NODES=("1" "2" "3" "4" "5")
|
|
||||||
GATEWAY_PORTS=(8080 8081 8082 8083 8084)
|
|
||||||
|
|
||||||
# Color codes
|
|
||||||
GREEN='\033[0;32m'
|
|
||||||
RED='\033[0;31m'
|
|
||||||
YELLOW='\033[1;33m'
|
|
||||||
NC='\033[0m' # No Color
|
|
||||||
|
|
||||||
# Counters
|
|
||||||
PASSED=0
|
|
||||||
FAILED=0
|
|
||||||
|
|
||||||
# Test a single endpoint
|
|
||||||
test_endpoint() {
|
|
||||||
local node=$1
|
|
||||||
local port=$2
|
|
||||||
local path=$3
|
|
||||||
local description=$4
|
|
||||||
|
|
||||||
local url="http://node-${node}.local:${port}${path}"
|
|
||||||
|
|
||||||
printf "Testing %-50s ... " "$description"
|
|
||||||
|
|
||||||
if curl -s -f "$url" > /dev/null 2>&1; then
|
|
||||||
echo -e "${GREEN}✓ PASS${NC}"
|
|
||||||
((PASSED++))
|
|
||||||
return 0
|
|
||||||
else
|
|
||||||
echo -e "${RED}✗ FAIL${NC}"
|
|
||||||
((FAILED++))
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
echo "=========================================="
|
|
||||||
echo "DeBros Network Local Domain Tests"
|
|
||||||
echo "=========================================="
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Test each node's HTTP gateway
|
|
||||||
for i in "${!NODES[@]}"; do
|
|
||||||
node=${NODES[$i]}
|
|
||||||
port=${GATEWAY_PORTS[$i]}
|
|
||||||
|
|
||||||
echo "Testing node-${node}.local (port ${port}):"
|
|
||||||
|
|
||||||
# Test health endpoint
|
|
||||||
test_endpoint "$node" "$port" "/health" "Node-${node} health check"
|
|
||||||
|
|
||||||
# Test RQLite HTTP endpoint
|
|
||||||
test_endpoint "$node" "$port" "/rqlite/http/db/execute" "Node-${node} RQLite HTTP"
|
|
||||||
|
|
||||||
# Test IPFS API endpoint (may fail if IPFS not running, but at least connection should work)
|
|
||||||
test_endpoint "$node" "$port" "/ipfs/api/v0/version" "Node-${node} IPFS API" || true
|
|
||||||
|
|
||||||
# Test Cluster API endpoint (may fail if Cluster not running, but at least connection should work)
|
|
||||||
test_endpoint "$node" "$port" "/cluster/health" "Node-${node} Cluster API" || true
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
done
|
|
||||||
|
|
||||||
# Summary
|
|
||||||
echo "=========================================="
|
|
||||||
echo "Test Results"
|
|
||||||
echo "=========================================="
|
|
||||||
echo -e "${GREEN}Passed: $PASSED${NC}"
|
|
||||||
echo -e "${RED}Failed: $FAILED${NC}"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
if [ $FAILED -eq 0 ]; then
|
|
||||||
echo -e "${GREEN}✓ All tests passed!${NC}"
|
|
||||||
exit 0
|
|
||||||
else
|
|
||||||
echo -e "${YELLOW}⚠ Some tests failed (this is expected if services aren't running)${NC}"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
Loading…
x
Reference in New Issue
Block a user