This guide walks you through testing the Hiddify Usage Engine (HUE) in Go.
- Built binary: Run
make buildorgo build -o bin/hue.exe ./cmd/hue - Environment variables: Set
HUE_AUTH_SECRET(required) - Optional: MaxMind GeoLite2 database for geo-location features
# Windows PowerShell
$env:HUE_AUTH_SECRET = "test-secret-key-12345"
$env:HUE_PORT = "50051"
$env:HUE_HTTP_PORT = "50052"
$env:HUE_LOG_LEVEL = "debug"# Linux/macOS
export HUE_AUTH_SECRET="test-secret-key-12345"
export HUE_PORT="50051"
export HUE_HTTP_PORT="50052"
export HUE_LOG_LEVEL="debug".\bin\hue.exeYou should see:
{"level":"info","msg":"starting HUE server","port":"50051","http_port":"50052"}
{"level":"info","msg":"gRPC server started","port":"50051"}
{"level":"info","msg":"HTTP server started","port":"50052"}
Build benchmark tool:
go build -o bin/benchmark.exe ./cmd/benchmarkRun single benchmark:
# 100 users
.\bin\benchmark.exe -users 100 -duration 1m -interval 1s
# 1000 users
.\bin\benchmark.exe -users 1000 -duration 1m -interval 1s
# 10000 users
.\bin\benchmark.exe -users 10000 -duration 1m -interval 1sRun 5 mini benchmarks (includes 100 / 1000 / 10000 user scenarios):
.\bin\benchmark.exe -suiteWhat this benchmark does:
- Creates users + active packages in SQLite
- Simulates per-user sessions and usage reports concurrently
- Runs quota checks and records accepted usage to stress core engine paths
- Prints requests/sec, errors/rejections, and memory/goroutine peaks
All HTTP endpoints require the secret query parameter.
# Health check (no auth required)
Invoke-WebRequest -Uri "http://localhost:50052/health" -Method GETcurl http://localhost:50052/healthExpected Response:
{"status": "ok"}$headers = @{
"Content-Type" = "application/json"
}
$body = @{
username = "testuser"
password = "testpass123"
public_key = "ssh-rsa AAAA..."
groups = @("premium", "vpn")
allowed_devices = @("device1", "device2")
active_package_id = ""
} | ConvertTo-Json
Invoke-WebRequest -Uri "http://localhost:50052/api/v1/users?secret=test-secret-key-12345" -Method POST -Headers $headers -Body $bodycurl -X POST "http://localhost:50052/api/v1/users?secret=test-secret-key-12345" \
-H "Content-Type: application/json" \
-d '{
"username": "testuser",
"password": "testpass123",
"public_key": "ssh-rsa AAAA...",
"groups": ["premium", "vpn"],
"allowed_devices": ["device1", "device2"]
}'Expected Response:
{
"id": "uuid-here",
"username": "testuser",
"public_key": "ssh-rsa AAAA...",
"groups": ["premium", "vpn"],
"status": "active",
"created_at": 1700000000
}Invoke-WebRequest -Uri "http://localhost:50052/api/v1/users?secret=test-secret-key-12345" -Method GETcurl "http://localhost:50052/api/v1/users?secret=test-secret-key-12345"$userId = "uuid-from-create-response"
Invoke-WebRequest -Uri "http://localhost:50052/api/v1/users/$userId?secret=test-secret-key-12345" -Method GETcurl "http://localhost:50052/api/v1/users/{user-id}?secret=test-secret-key-12345"$body = @{
username = "testuser_updated"
status = "active"
groups = @("premium", "vpn", "admin")
} | ConvertTo-Json
Invoke-WebRequest -Uri "http://localhost:50052/api/v1/users/$userId?secret=test-secret-key-12345" -Method PUT -Headers $headers -Body $bodycurl -X PUT "http://localhost:50052/api/v1/users/{user-id}?secret=test-secret-key-12345" \
-H "Content-Type: application/json" \
-d '{"username": "testuser_updated", "status": "active", "groups": ["premium", "vpn", "admin"]}'Invoke-WebRequest -Uri "http://localhost:50052/api/v1/users/$userId?secret=test-secret-key-12345" -Method DELETEcurl -X DELETE "http://localhost:50052/api/v1/users/{user-id}?secret=test-secret-key-12345"$body = @{
user_id = $userId
total_traffic = 107374182400 # 100GB in bytes
upload_limit = 53687091200 # 50GB
download_limit = 53687091200 # 50GB
reset_mode = "monthly"
duration = 2592000 # 30 days in seconds
max_concurrent = 3
} | ConvertTo-Json
Invoke-WebRequest -Uri "http://localhost:50052/api/v1/packages?secret=test-secret-key-12345" -Method POST -Headers $headers -Body $bodycurl -X POST "http://localhost:50052/api/v1/packages?secret=test-secret-key-12345" \
-H "Content-Type: application/json" \
-d '{
"user_id": "{user-id}",
"total_traffic": 107374182400,
"upload_limit": 53687091200,
"download_limit": 53687091200,
"reset_mode": "monthly",
"duration": 2592000,
"max_concurrent": 3
}'$body = @{
name = "node-us-east-1"
secret_key = "node-secret-key-abc"
allowed_ips = @("192.168.1.0/24", "10.0.0.0/8")
traffic_multiplier = 1.0
reset_mode = "monthly"
reset_day = 1
country = "US"
city = "New York"
isp = "AWS"
} | ConvertTo-Json
Invoke-WebRequest -Uri "http://localhost:50052/api/v1/nodes?secret=test-secret-key-12345" -Method POST -Headers $headers -Body $bodycurl -X POST "http://localhost:50052/api/v1/nodes?secret=test-secret-key-12345" \
-H "Content-Type: application/json" \
-d '{
"name": "node-us-east-1",
"secret_key": "node-secret-key-abc",
"allowed_ips": ["192.168.1.0/24", "10.0.0.0/8"],
"traffic_multiplier": 1.0,
"reset_mode": "monthly",
"reset_day": 1,
"country": "US",
"city": "New York",
"isp": "AWS"
}'Invoke-WebRequest -Uri "http://localhost:50052/api/v1/nodes?secret=test-secret-key-12345" -Method GETcurl "http://localhost:50052/api/v1/nodes?secret=test-secret-key-12345"$body = @{
node_id = $nodeId
secret_key = "service-secret-key-xyz"
name = "vless-service"
protocol = "vless"
allowed_auth_methods = @("uuid", "password")
callback_url = "https://example.com/callback"
} | ConvertTo-Json
Invoke-WebRequest -Uri "http://localhost:50052/api/v1/services?secret=test-secret-key-12345" -Method POST -Headers $headers -Body $bodycurl -X POST "http://localhost:50052/api/v1/services?secret=test-secret-key-12345" \
-H "Content-Type: application/json" \
-d '{
"node_id": "{node-id}",
"secret_key": "service-secret-key-xyz",
"name": "vless-service",
"protocol": "vless",
"allowed_auth_methods": ["uuid", "password"],
"callback_url": "https://example.com/callback"
}'Invoke-WebRequest -Uri "http://localhost:50052/api/v1/stats?secret=test-secret-key-12345" -Method GETcurl "http://localhost:50052/api/v1/stats?secret=test-secret-key-12345"Expected Response:
{
"total_users": 10,
"active_users": 5,
"total_nodes": 3,
"total_services": 5,
"total_upload": 1073741824,
"total_download": 2147483648
}Install grpcurl: https://github.com/fullstorydev/grpcurl
# List available services
grpcurl -plaintext localhost:50051 list
# Describe a service
grpcurl -plaintext localhost:50051 describe hue.UsageService
# Report usage
grpcurl -plaintext -d '{
"report": {
"user_id": "{user-id}",
"node_id": "{node-id}",
"service_id": "{service-id}",
"upload": 1048576,
"download": 2097152,
"session_id": "session-123",
"client_ip": "192.168.1.100",
"timestamp": 1700000000
}
}' localhost:50051 hue.UsageService/ReportUsage# List available services
grpcurl -plaintext localhost:50051 list
# Report usage
grpcurl -plaintext -d '{
"report": {
"user_id": "{user-id}",
"node_id": "{node-id}",
"service_id": "{service-id}",
"upload": 1048576,
"download": 2097152,
"session_id": "session-123",
"client_ip": "192.168.1.100",
"timestamp": 1700000000
}
}' localhost:50051 hue.UsageService/ReportUsageCreate a test client:
package main
import (
"context"
"log"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
pb "github.com/hiddify/hue-go/pkg/proto"
)
func main() {
conn, err := grpc.Dial("localhost:50051", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatal(err)
}
defer conn.Close()
client := pb.NewUsageServiceClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
resp, err := client.ReportUsage(ctx, &pb.ReportUsageRequest{
Report: &pb.UsageReport{
UserId: "user-uuid",
NodeId: "node-uuid",
ServiceId: "service-uuid",
Upload: 1048576,
Download: 2097152,
SessionId: "session-123",
Timestamp: time.Now().Unix(),
},
})
if err != nil {
log.Fatal(err)
}
log.Printf("Result: accepted=%v, quota_exceeded=%v", resp.Result.Accepted, resp.Result.QuotaExceeded)
}docker build -t hue -f deployments/docker/Dockerfile .docker build -t hue -f deployments/docker/Dockerfile .docker run -d `
-p 50051:50051 `
-p 50052:50052 `
-e HUE_AUTH_SECRET=test-secret-key-12345 `
-e HUE_LOG_LEVEL=debug `
-v hue-data:/data `
--name hue-test `
huedocker run -d \
-p 50051:50051 \
-p 50052:50052 \
-e HUE_AUTH_SECRET=test-secret-key-12345 \
-e HUE_LOG_LEVEL=debug \
-v hue-data:/data \
--name hue-test \
huedocker logs hue-test -fdocker logs -f hue-testcd deployments/docker
docker-compose up -d
docker-compose logs -fcd deployments/docker
docker-compose up -d
docker-compose logs -fgo test ./... -vgo test ./... -vgo test ./internal/engine -v
go test ./internal/storage/sqlite -vgo test ./internal/engine -v
go test ./internal/storage/sqlite -vgo test ./... -coverprofile=coverage.out
go tool cover -html=coverage.out -o coverage.htmlgo test ./... -coverprofile=coverage.out
go tool cover -html=coverage.out -o coverage.htmlCreate a PowerShell script test-integration.ps1:
# Integration test script for HUE-GO
$SECRET = "test-secret-key-12345"
$BASE_URL = "http://localhost:50052"
Write-Host "=== HUE-GO Integration Test ===" -ForegroundColor Green
# Test 1: Health Check
Write-Host "`n[Test 1] Health Check..." -ForegroundColor Yellow
$response = Invoke-WebRequest -Uri "$BASE_URL/health" -Method GET
Write-Host "Response: $($response.Content)" -ForegroundColor Cyan
# Test 2: Create User
Write-Host "`n[Test 2] Create User..." -ForegroundColor Yellow
$body = @{
username = "testuser_$(Get-Random)"
password = "testpass123"
groups = @("test")
} | ConvertTo-Json
$response = Invoke-WebRequest -Uri "$BASE_URL/api/v1/users?secret=$SECRET" -Method POST -Headers @{"Content-Type"="application/json"} -Body $body
$user = $response.Content | ConvertFrom-Json
$userId = $user.id
Write-Host "Created user: $userId" -ForegroundColor Cyan
# Test 3: Get User
Write-Host "`n[Test 3] Get User..." -ForegroundColor Yellow
$response = Invoke-WebRequest -Uri "$BASE_URL/api/v1/users/$userId`?secret=$SECRET" -Method GET
Write-Host "Response: $($response.Content)" -ForegroundColor Cyan
# Test 4: Create Package
Write-Host "`n[Test 4] Create Package..." -ForegroundColor Yellow
$body = @{
user_id = $userId
total_traffic = 107374182400
reset_mode = "monthly"
duration = 2592000
max_concurrent = 3
} | ConvertTo-Json
$response = Invoke-WebRequest -Uri "$BASE_URL/api/v1/packages?secret=$SECRET" -Method POST -Headers @{"Content-Type"="application/json"} -Body $body
$pkg = $response.Content | ConvertFrom-Json
Write-Host "Created package: $($pkg.id)" -ForegroundColor Cyan
# Test 5: Create Node
Write-Host "`n[Test 5] Create Node..." -ForegroundColor Yellow
$body = @{
name = "test-node-$(Get-Random)"
secret_key = "node-secret-$(Get-Random)"
traffic_multiplier = 1.0
} | ConvertTo-Json
$response = Invoke-WebRequest -Uri "$BASE_URL/api/v1/nodes?secret=$SECRET" -Method POST -Headers @{"Content-Type"="application/json"} -Body $body
$node = $response.Content | ConvertFrom-Json
$nodeId = $node.id
Write-Host "Created node: $nodeId" -ForegroundColor Cyan
# Test 6: List Nodes
Write-Host "`n[Test 6] List Nodes..." -ForegroundColor Yellow
$response = Invoke-WebRequest -Uri "$BASE_URL/api/v1/nodes?secret=$SECRET" -Method GET
Write-Host "Response: $($response.Content)" -ForegroundColor Cyan
# Test 7: Get Stats
Write-Host "`n[Test 7] Get Stats..." -ForegroundColor Yellow
$response = Invoke-WebRequest -Uri "$BASE_URL/api/v1/stats?secret=$SECRET" -Method GET
Write-Host "Response: $($response.Content)" -ForegroundColor Cyan
# Test 8: Delete User
Write-Host "`n[Test 8] Delete User..." -ForegroundColor Yellow
$response = Invoke-WebRequest -Uri "$BASE_URL/api/v1/users/$userId`?secret=$SECRET" -Method DELETE
Write-Host "User deleted" -ForegroundColor Cyan
Write-Host "`n=== All Tests Passed ===" -ForegroundColor Green- Create a user with a package that has
total_traffic = 10485760(10MB) - Report usage that exceeds the limit
- The response should have
quota_exceeded = trueandshould_disconnect = true
- Create a user with a package that has
max_concurrent = 2 - Start 3 sessions with different session IDs
- The 3rd session should be rejected with
session_limit_hit = true
- User exceeds concurrent session limit
- Penalty is applied for configured duration
- User cannot connect until penalty expires or is cleared
- Ensure
secretquery parameter matchesHUE_AUTH_SECRET - Check that the secret is URL-encoded if it contains special characters
- Verify the user ID is correct
- Check if the user was deleted
- Check file permissions for SQLite database files
- Ensure the data directory is writable
# Find process using port
netstat -ano | findstr :50051
netstat -ano | findstr :50052
# Kill process (replace PID)
taskkill /PID <pid> /F# Find process using port
lsof -i :50051
lsof -i :50052
# Kill process
kill -9 <pid>| Variable | Default | Description |
|---|---|---|
HUE_DB_URL |
sqlite://./hue.db |
Database connection string |
HUE_PORT |
50051 |
gRPC server port |
HUE_HTTP_PORT |
50052 |
HTTP REST API port |
HUE_AUTH_SECRET |
required | Authentication secret key |
HUE_LOG_LEVEL |
info |
Log level: debug, info, warn, error |
HUE_DB_FLUSH_INTERVAL |
5m |
Buffered write flush interval |
HUE_CONCURRENT_WINDOW |
5m |
Session counting window |
HUE_PENALTY_DURATION |
10m |
Penalty duration for violations |
HUE_MAXMIND_DB_PATH |
"" |
Path to MaxMind GeoLite2 database |
HUE_EVENT_STORE_TYPE |
db |
Event storage: db, file, none |