mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-30 06:16:37 +08:00
ac7b776378
-transition `of_config_versions` primary key from `id` to `version` string. -add database migration files `202606270001_make_version_primary_key.sql` for PostgreSQL and SQLite. -introduce AfterFind/AfterCreate GORM hooks to preserve JSON backward compatibility. -refactor API controllers, logics, and front-end typescript definitions to receive `string` parameter.
199 lines
6.1 KiB
Go
199 lines
6.1 KiB
Go
// Copyright 2026 Arctel.net
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package integration
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"testing"
|
|
|
|
"github.com/Rain-kl/Wavelet/internal/apps/openflare/agent"
|
|
ofnode "github.com/Rain-kl/Wavelet/internal/apps/openflare/node"
|
|
"github.com/Rain-kl/Wavelet/internal/db"
|
|
"github.com/Rain-kl/Wavelet/internal/model"
|
|
"github.com/Rain-kl/Wavelet/internal/testhelper"
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/glebarez/sqlite"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
func setupProtocolTestEnv(t *testing.T) (*gin.Engine, func()) {
|
|
t.Helper()
|
|
|
|
sqliteDB, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{
|
|
DisableForeignKeyConstraintWhenMigrating: true,
|
|
})
|
|
require.NoError(t, err)
|
|
require.NoError(t, sqliteDB.AutoMigrate(
|
|
&model.OpenFlareNode{},
|
|
&model.SystemConfig{},
|
|
&model.OpenFlareApplyLog{},
|
|
&model.OpenFlareNodeSystemProfile{},
|
|
&model.OpenFlareHealthEvent{},
|
|
&model.ConfigVersion{},
|
|
))
|
|
|
|
db.SetDB(sqliteDB)
|
|
agent.ResetAuthCacheForTest()
|
|
resetAccessLogStore := model.SetAccessLogStoreForTest(model.NewMemoryAccessLogStore())
|
|
resetObservabilityStore := model.SetObservabilityStoreForTest(model.NewMemoryObservabilityStore())
|
|
|
|
engine := testhelper.NewTestGinEngine()
|
|
mountOpenFlareTestRoutes(engine)
|
|
|
|
cleanup := func() {
|
|
resetObservabilityStore()
|
|
resetAccessLogStore()
|
|
db.SetDB(nil)
|
|
agent.ResetAuthCacheForTest()
|
|
}
|
|
return engine, cleanup
|
|
}
|
|
|
|
func TestAgentRelayFlaredProtocol(t *testing.T) {
|
|
engine, cleanup := setupProtocolTestEnv(t)
|
|
defer cleanup()
|
|
|
|
ctx := context.Background()
|
|
|
|
t.Run("create edge node and heartbeat with X-Agent-Token", func(t *testing.T) {
|
|
edge, err := ofnode.CreateNode(ctx, ofnode.Input{
|
|
Name: "edge-1",
|
|
IP: "10.0.0.1",
|
|
})
|
|
require.NoError(t, err)
|
|
require.NotEmpty(t, edge.AccessToken)
|
|
assert.Equal(t, "edge_node", edge.NodeType)
|
|
|
|
rec := performJSONRequest(t, engine, http.MethodPost, "/api/v1/agent/nodes/heartbeat", map[string]any{
|
|
"name": "edge-1",
|
|
"ip": "203.0.113.10",
|
|
"version": "0.1.0",
|
|
}, map[string]string{
|
|
"X-Agent-Token": edge.AccessToken,
|
|
})
|
|
assert.Equal(t, http.StatusOK, rec.Code)
|
|
|
|
resp := requireAPIOK(t, rec)
|
|
data := unmarshalAPIMap(t, resp.Data)
|
|
assert.NotNil(t, data["agent_settings"])
|
|
})
|
|
|
|
t.Run("create tunnel_relay node and relay heartbeat", func(t *testing.T) {
|
|
relayNode, err := ofnode.CreateNode(ctx, ofnode.Input{
|
|
Name: "relay-1",
|
|
NodeType: "tunnel_relay",
|
|
})
|
|
require.NoError(t, err)
|
|
require.NotEmpty(t, relayNode.AccessToken)
|
|
|
|
rec := performJSONRequest(t, engine, http.MethodPost, "/api/v1/relay/heartbeat", map[string]any{
|
|
"version": "v0.1.0",
|
|
"frp_version": "0.61.0",
|
|
"relay_status": "healthy",
|
|
"name": "relay-1",
|
|
"ip": "203.0.113.20",
|
|
}, map[string]string{
|
|
"X-Agent-Token": relayNode.AccessToken,
|
|
})
|
|
assert.Equal(t, http.StatusOK, rec.Code)
|
|
|
|
resp := requireAPIOK(t, rec)
|
|
var heartbeatData struct {
|
|
RelayConfig map[string]any `json:"relay_config"`
|
|
RelaySettings map[string]any `json:"relay_settings"`
|
|
}
|
|
unmarshalAPIData(t, resp.Data, &heartbeatData)
|
|
assert.NotNil(t, heartbeatData.RelayConfig)
|
|
assert.NotNil(t, heartbeatData.RelaySettings)
|
|
|
|
stored, err := model.GetOpenFlareNodeByNodeID(ctx, relayNode.NodeID)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "online", stored.Status)
|
|
assert.Equal(t, "healthy", stored.RelayStatus)
|
|
})
|
|
|
|
t.Run("create tunnel_client node and flared heartbeat with X-Tunnel-Token", func(t *testing.T) {
|
|
clientNode, err := ofnode.CreateNode(ctx, ofnode.Input{
|
|
Name: "client-1",
|
|
NodeType: "tunnel_client",
|
|
})
|
|
require.NoError(t, err)
|
|
require.NotEmpty(t, clientNode.AccessToken)
|
|
|
|
rec := performJSONRequest(t, engine, http.MethodPost, "/api/v1/tunnel/heartbeat", map[string]any{
|
|
"client_version": "v0.2.0",
|
|
"frp_version": "0.61.0",
|
|
"tunnel_status": "running",
|
|
}, map[string]string{
|
|
"X-Tunnel-Token": clientNode.AccessToken,
|
|
})
|
|
assert.Equal(t, http.StatusOK, rec.Code)
|
|
requireAPIOK(t, rec)
|
|
|
|
stored, err := model.GetOpenFlareNodeByNodeID(ctx, clientNode.NodeID)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "online", stored.Status)
|
|
assert.Equal(t, "v0.2.0", stored.Version)
|
|
})
|
|
|
|
t.Run("agent register with discovery token from options", func(t *testing.T) {
|
|
bootstrap, err := ofnode.GetBootstrapToken(ctx)
|
|
require.NoError(t, err)
|
|
require.NotEmpty(t, bootstrap.DiscoveryToken)
|
|
|
|
rec := performJSONRequest(t, engine, http.MethodPost, "/api/v1/agent/nodes/register", map[string]any{
|
|
"name": "discovered-edge",
|
|
"ip": "203.0.113.30",
|
|
"version": "0.2.0",
|
|
}, map[string]string{
|
|
"X-Agent-Token": bootstrap.DiscoveryToken,
|
|
})
|
|
assert.Equal(t, http.StatusOK, rec.Code)
|
|
|
|
resp := requireAPIOK(t, rec)
|
|
var registration agent.RegistrationResponse
|
|
unmarshalAPIData(t, resp.Data, ®istration)
|
|
assert.NotEmpty(t, registration.NodeID)
|
|
assert.NotEmpty(t, registration.AccessToken)
|
|
assert.Equal(t, "discovered-edge", registration.Name)
|
|
|
|
stored, err := model.GetOpenFlareNodeByNodeID(ctx, registration.NodeID)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "online", stored.Status)
|
|
assert.Equal(t, registration.AccessToken, stored.AccessToken)
|
|
})
|
|
|
|
t.Run("POST agent apply-logs", func(t *testing.T) {
|
|
edge, err := ofnode.CreateNode(ctx, ofnode.Input{
|
|
Name: "edge-apply",
|
|
IP: "10.0.0.2",
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
rec := performJSONRequest(t, engine, http.MethodPost, "/api/v1/agent/apply-logs", map[string]any{
|
|
"version": "20260618-001",
|
|
"result": "success",
|
|
"message": "apply ok",
|
|
}, map[string]string{
|
|
"X-Agent-Token": edge.AccessToken,
|
|
})
|
|
assert.Equal(t, http.StatusOK, rec.Code)
|
|
|
|
resp := requireAPIOK(t, rec)
|
|
var applyLog model.OpenFlareApplyLog
|
|
unmarshalAPIData(t, resp.Data, &applyLog)
|
|
assert.Equal(t, edge.NodeID, applyLog.NodeID)
|
|
assert.Equal(t, "success", applyLog.Result)
|
|
assert.Equal(t, "20260618-001", applyLog.Version)
|
|
|
|
stored, err := model.GetOpenFlareNodeByNodeID(ctx, edge.NodeID)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "online", stored.Status)
|
|
assert.Equal(t, "20260618-001", stored.CurrentVersion)
|
|
})
|
|
}
|