feat: dispatch queued commands after reconciliation

This commit is contained in:
2026-09-06 07:34:01 +00:00
parent 0035e1cf43
commit 467ca65f60
7 changed files with 337 additions and 2 deletions
@@ -2,6 +2,7 @@ package clientagent_test
import (
"context"
"crypto/sha256"
"net/http/httptest"
"path/filepath"
"strings"
@@ -11,8 +12,10 @@ import (
rvboxv1 "github.com/rvbox/rvbox/gen/go/rvbox/v1"
"github.com/rvbox/rvbox/internal/agentproto"
"github.com/rvbox/rvbox/internal/client/agent"
"github.com/rvbox/rvbox/internal/domain"
"github.com/rvbox/rvbox/internal/server/session"
"github.com/rvbox/rvbox/internal/server/store"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/timestamppb"
)
@@ -55,3 +58,55 @@ func TestWebSocketHelloWelcome_HP_SES_06(t *testing.T) {
t.Fatalf("discard local terminal issues = %v", got)
}
}
func TestWebSocketDispatchAfterReconciliation_HP_DISPATCH_03(t *testing.T) {
ctx := context.Background()
persistence, err := store.Open(ctx, store.Options{DataDir: filepath.Join(t.TempDir(), "state"), BusyTimeout: time.Second})
if err != nil {
t.Fatal(err)
}
defer persistence.Close()
server := httptest.NewServer(&session.AgentServer{Store: persistence, Registry: session.NewRegistry(), Path: "/v1/agent", Limits: agentproto.DefaultLimits()})
defer server.Close()
address := "ws" + strings.TrimPrefix(server.URL, "http") + "/v1/agent"
transport, err := agent.DialWebSocket(ctx, address, nil)
if err != nil {
t.Fatal(err)
}
defer transport.Close()
hello := &rvboxv1.ClientHello{ClientId: "win-dispatch-client", SupportedProtocol: &rvboxv1.ProtocolRange{Major: 1, MinMinor: 0, MaxMinor: 0}, DaemonVersion: "test", Platform: rvboxv1.Platform_PLATFORM_WINDOWS, Architecture: "amd64", DaemonCwd: `C:\`, SupportedShells: []rvboxv1.ShellType{rvboxv1.ShellType_SHELL_POWERSHELL}, ClientInstanceId: "019c46f1-1d02-7000-8000-000000000074", MaxRunningCommands: 1, MaxQueuedCommands: 1, SentAt: timestamppb.Now()}
accepted, err := agent.Handshake(ctx, transport, hello, agentproto.DefaultLimits())
if err != nil {
t.Fatal(err)
}
issue, err := domain.ParseUUIDv7("019c46f1-1d02-7000-8000-000000000075")
if err != nil {
t.Fatal(err)
}
spec := &rvboxv1.ExecutionSpec{ShellType: rvboxv1.ShellType_SHELL_POWERSHELL, Source: &rvboxv1.ExecutionSpec_CommandText{CommandText: "Write-Output dispatch"}}
serialized, err := proto.Marshal(spec)
if err != nil {
t.Fatal(err)
}
requestHash := sha256.Sum256([]byte("dispatch request"))
if _, err := persistence.QueueCommand(ctx, store.QueueCommandInput{IssueUUID: issue, ClientID: hello.GetClientId(), IssueTime: time.Now().UTC(), ReceiptTime: time.Now().UTC(), ImmutableSHA256: requestHash, ExecutionSpec: serialized}); err != nil {
t.Fatal(err)
}
if _, err := agent.Reconcile(ctx, transport, accepted, &rvboxv1.ReconcileSnapshot{}, agentproto.DefaultLimits()); err != nil {
t.Fatal(err)
}
readContext, cancel := context.WithTimeout(ctx, time.Second)
defer cancel()
encoded, err := transport.Read(readContext)
if err != nil {
t.Fatal(err)
}
envelope, err := agentproto.DecodeEnvelope(encoded, agentproto.DefaultLimits(), rvboxv1.Platform_PLATFORM_WINDOWS)
if err != nil || envelope.GetSessionId() != accepted.ID || envelope.GetSessionGeneration() != accepted.Generation {
t.Fatalf("dispatch envelope = %#v, %v", envelope, err)
}
dispatch := envelope.GetCommandDispatch()
if dispatch == nil || dispatch.GetIssueUuid() != issue.String() || dispatch.GetCommandRevision() != 1 || dispatch.GetTargetSessionGeneration() != accepted.Generation || !proto.Equal(dispatch.GetSpec(), spec) {
t.Fatalf("dispatch = %#v", dispatch)
}
}