104 lines
3.9 KiB
Go
104 lines
3.9 KiB
Go
package edge_node
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
|
|
"gorm.io/driver/sqlite"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
func edgeNodeTestService(t *testing.T) (*Service, *gorm.DB, time.Time) {
|
|
t.Helper()
|
|
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err = db.AutoMigrate(&Node{}, &Event{}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
now := time.Date(2026, 8, 28, 4, 0, 0, 0, time.UTC)
|
|
service := NewService(db)
|
|
service.Now = func() time.Time { return now }
|
|
return service, db, now
|
|
}
|
|
|
|
func TestListClassifiesOnlineOfflineAndRecoveringWithoutErasingLastKnownState(t *testing.T) {
|
|
service, db, now := edgeNodeTestService(t)
|
|
if err := SeedSyntheticFixture(db, now); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
page, err := service.List(PageRequest{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if page.Count != 3 || page.Summary.Online != 1 || page.Summary.Offline != 1 || page.Summary.Recovering != 1 || page.Summary.BackfillQueueDepth != 17 {
|
|
t.Fatalf("unexpected summary: %+v", page.Summary)
|
|
}
|
|
var offline Response
|
|
for _, item := range page.List {
|
|
if item.Status == StatusOffline {
|
|
offline = item
|
|
}
|
|
}
|
|
if !offline.Stale || offline.ControlTunnelStatus != ChannelReady || offline.VideoPlaneStatus != ChannelReady || offline.BackfillQueueDepth != 12 {
|
|
t.Fatalf("offline projection lost last-known state: %+v", offline)
|
|
}
|
|
}
|
|
|
|
func TestApplyHeartbeatRecordsRecoveryAndPreservesIndependentChannelConvergence(t *testing.T) {
|
|
service, db, now := edgeNodeTestService(t)
|
|
old := Node{ID: "EDGE-1", Name: "旧节点", StartedAt: now.Add(-time.Hour), LastHeartbeatAt: now.Add(-5 * time.Minute), LastCollectedAt: now.Add(-5 * time.Minute), LoadCapacity: 16, ControlTunnelStatus: ChannelReady, VideoPlaneStatus: ChannelReady, BackfillStatus: BackfillPending, BackfillQueueDepth: 8, ProjectionVersion: 3}
|
|
if err := db.Create(&old).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
sample := HeartbeatSample{Node: Node{ID: "EDGE-1", Name: "旧节点", StartedAt: old.StartedAt, LastHeartbeatAt: now.Add(-5 * time.Second), LoadUsed: 4, LoadCapacity: 16, ControlTunnelStatus: ChannelReady, VideoPlaneStatus: ChannelUnavailable, BackfillStatus: BackfillPending, BackfillQueueDepth: 3, RecoveryPhase: "video_reconnecting"}, CollectedAt: now}
|
|
if err := service.ApplyHeartbeat(context.Background(), sample); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
item, err := service.Get("EDGE-1")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if item.Status != StatusRecovering || item.ProjectionVersion != 4 || item.VideoPlaneStatus != ChannelUnavailable || item.BackfillQueueDepth != 3 {
|
|
t.Fatalf("unexpected recovery projection: %+v", item)
|
|
}
|
|
if len(item.Events) != 2 || item.Events[0].EventType != "node_recovered" || item.Events[0].FromStatus != StatusOffline || item.Events[0].ToStatus != StatusRecovering || item.Events[1].EventType != "heartbeat_timeout" || item.Events[1].ToStatus != StatusOffline {
|
|
t.Fatalf("unexpected events: %+v", item.Events)
|
|
}
|
|
}
|
|
|
|
func TestApplyHeartbeatRejectsInvalidSampleAndDoesNotPersist(t *testing.T) {
|
|
service, db, now := edgeNodeTestService(t)
|
|
err := service.ApplyHeartbeat(context.Background(), HeartbeatSample{Node: Node{ID: "EDGE-2", Name: "节点", StartedAt: now.Add(-time.Hour), LastHeartbeatAt: now, LoadUsed: 17, LoadCapacity: 16}, CollectedAt: now})
|
|
if !errors.Is(err, ErrInvalidSample) {
|
|
t.Fatalf("err=%v", err)
|
|
}
|
|
var count int64
|
|
db.Model(&Node{}).Count(&count)
|
|
if count != 0 {
|
|
t.Fatalf("count=%d", count)
|
|
}
|
|
}
|
|
|
|
func TestGetRejectsMissingOrMalformedID(t *testing.T) {
|
|
service, _, _ := edgeNodeTestService(t)
|
|
if _, err := service.Get(""); !errors.Is(err, ErrInvalidQuery) {
|
|
t.Fatalf("err=%v", err)
|
|
}
|
|
if _, err := service.Get("missing"); !errors.Is(err, ErrNotFound) {
|
|
t.Fatalf("err=%v", err)
|
|
}
|
|
}
|
|
|
|
func TestListRejectsOversizedPage(t *testing.T) {
|
|
service, _, _ := edgeNodeTestService(t)
|
|
request := PageRequest{}
|
|
request.PageSize = 101
|
|
if _, err := service.List(request); !errors.Is(err, ErrInvalidQuery) {
|
|
t.Fatalf("err=%v", err)
|
|
}
|
|
}
|