package dispatcher import ( "errors" "testing" "time" agentpb "git.ipao.vip/rogee/go-sip/gen/agent" "git.ipao.vip/rogee/go-sip/internal/session" "git.ipao.vip/rogee/go-sip/internal/store" "google.golang.org/protobuf/proto" ) type inboundSessionTestClient struct { agentpb.AgentControlServiceClient } func TestInboundAgentFactRequiresCurrentUnexpiredActivatedSession(t *testing.T) { now := time.Date(2026, 9, 21, 2, 0, 0, 0, time.UTC) coordinator := NewAgentCoordinator(func() time.Time { return now }) if err := coordinator.Register("agent-a", &inboundSessionTestClient{}); err != nil { t.Fatal(err) } coordinator.sessions["agent-a"] = AgentSession{ AgentID: "agent-a", CellID: "cell-a", BootID: "boot-a", DispatcherEpoch: "epoch-a", SessionGeneration: 3, ExpiresAtUnixMs: now.Add(time.Minute).UnixMilli(), } meta := &agentpb.RequestMeta{ ProtocolVersion: "agent.v1", AgentId: "agent-a", CellId: "cell-a", BootId: "boot-a", DispatcherEpoch: "epoch-a", SessionGeneration: 3, } if err := coordinator.AuthorizeInboundMeta(meta); err != nil { t.Fatalf("active Agent was refused: %v", err) } for _, tc := range []struct { name string change func(*agentpb.RequestMeta) transition bool }{ {"old boot", func(m *agentpb.RequestMeta) { m.BootId = "boot-old" }, false}, {"old generation", func(m *agentpb.RequestMeta) { m.SessionGeneration = 2 }, true}, {"next generation before activation response", func(m *agentpb.RequestMeta) { m.SessionGeneration = 4 }, true}, {"distant generation", func(m *agentpb.RequestMeta) { m.SessionGeneration = 7 }, false}, {"missing generation", func(m *agentpb.RequestMeta) { m.SessionGeneration = 0 }, false}, {"other cell", func(m *agentpb.RequestMeta) { m.CellId = "cell-other" }, false}, {"other epoch", func(m *agentpb.RequestMeta) { m.DispatcherEpoch = "epoch-other" }, false}, {"unregistered Agent", func(m *agentpb.RequestMeta) { m.AgentId = "agent-other" }, false}, {"old boot and generation", func(m *agentpb.RequestMeta) { m.BootId = "boot-old"; m.SessionGeneration = 2 }, false}, } { t.Run(tc.name, func(t *testing.T) { changed := proto.Clone(meta).(*agentpb.RequestMeta) tc.change(changed) err := coordinator.AuthorizeInboundMeta(changed) if !errors.Is(err, store.ErrCommandConflict) || errors.Is(err, session.ErrGenerationTransition) != tc.transition { t.Fatalf("Agent report refusal classification: got %v, transition=%t", err, tc.transition) } }) } now = now.Add(time.Minute) if err := coordinator.AuthorizeInboundMeta(meta); !errors.Is(err, store.ErrCommandConflict) { t.Fatalf("expired Agent report passed: %v", err) } stale := proto.Clone(meta).(*agentpb.RequestMeta) stale.SessionGeneration = 2 if err := coordinator.AuthorizeInboundMeta(stale); errors.Is(err, session.ErrGenerationTransition) { t.Fatalf("expired Agent session misclassified as a transient cutover: %v", err) } }