Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
214 lines
8.3 KiB
Go
214 lines
8.3 KiB
Go
package implementationhost
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"testing"
|
|
"time"
|
|
|
|
market "github.com/tutti-os/tutti/packages/connector/daemon/core"
|
|
connectorruntime "github.com/tutti-os/tutti/packages/connector/runtime"
|
|
)
|
|
|
|
func TestDeactivateRuntimeAllConnectionsRemovesRotatedConnectorRoutes(t *testing.T) {
|
|
generation := market.HostGeneration{BootEpoch: "boot-1", Generation: 7}
|
|
host := &Host{
|
|
routes: connectorruntime.NewRouteTable(),
|
|
authorizationRoutes: make(map[string]*connectorRoute),
|
|
authorizationProvider: newManagedCredentialAuthorizationProvider(nil),
|
|
registry: NewRouteRegistry(),
|
|
mcpRegistry: NewMCPRegistry(),
|
|
}
|
|
host.authorizationProvider.host = host
|
|
shimPath := filepath.Join(t.TempDir(), "tutti-connector-github")
|
|
routes := []*connectorRoute{
|
|
testUninstallRoute("connection-before-rotation", "github", "superseded-release", generation),
|
|
testUninstallRoute("connection-after-rotation", "github", "release-1", generation),
|
|
}
|
|
routes[0].cliShimPath = shimPath
|
|
routes[0].cliShimContent = []byte("shim")
|
|
if err := os.WriteFile(shimPath, routes[0].cliShimContent, 0o700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, route := range routes {
|
|
if err := host.routes.Commit(route); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
unrelated := testUninstallRoute("connection-other", "notion", "release-2", generation)
|
|
if err := host.routes.Commit(unrelated); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err := host.DeactivateRuntime(context.Background(), market.RuntimeDeactivationRequest{
|
|
ConnectionID: "connection-after-rotation", ConnectorKey: "github", ReleaseDigest: "release-1",
|
|
AllConnections: true, Generation: generation, Deadline: time.Now().Add(time.Second),
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, route := range routes {
|
|
if host.routes.Route(route.id) != nil || !route.processes.IsFenced() {
|
|
t.Fatalf("route %q survived all-connection deactivation", route.id)
|
|
}
|
|
}
|
|
if host.routes.Route(unrelated.id) != unrelated {
|
|
t.Fatal("unrelated connector route was removed")
|
|
}
|
|
if _, err := os.Stat(shimPath); !os.IsNotExist(err) {
|
|
t.Fatalf("CLI shim still exists: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestDisabledReconcileRemovesRotatedConnectorRoutes(t *testing.T) {
|
|
generation := market.HostGeneration{BootEpoch: "boot-1", Generation: 7}
|
|
host := &Host{
|
|
routes: connectorruntime.NewRouteTable(),
|
|
authorizationRoutes: make(map[string]*connectorRoute),
|
|
authorizationProvider: newManagedCredentialAuthorizationProvider(nil),
|
|
registry: NewRouteRegistry(),
|
|
mcpRegistry: NewMCPRegistry(),
|
|
}
|
|
host.authorizationProvider.host = host
|
|
routes := []*connectorRoute{
|
|
testUninstallRoute("connection-before-rotation", "github", "superseded-release", generation),
|
|
testUninstallRoute("connection-after-rotation", "github", "release-1", generation),
|
|
}
|
|
for _, route := range routes {
|
|
if err := host.routes.Commit(route); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
receipt, err := host.Reconcile(context.Background(), ReconcileRequest{Runtime: market.RuntimeReconcileRequest{
|
|
OperationID: "disable-github", ConnectionID: "connection-after-rotation",
|
|
Connector: market.Connector{Key: "github", Installation: market.Installation{
|
|
InstalledReleaseDigest: "release-1",
|
|
}},
|
|
Enabled: false, Generation: generation,
|
|
}})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, route := range routes {
|
|
if host.routes.Route(route.id) != nil || !route.processes.IsFenced() {
|
|
t.Fatalf("route %q survived disabled convergence", route.id)
|
|
}
|
|
}
|
|
if receipt.Readiness.State != market.RuntimeReadinessBlocked ||
|
|
receipt.Readiness.ReasonCode != market.RuntimeReadinessReasonRuntimeDisabled {
|
|
t.Fatalf("disabled receipt = %#v", receipt)
|
|
}
|
|
}
|
|
|
|
func TestDisabledCandidateReconcileReturnsRequestedReleaseDigest(t *testing.T) {
|
|
host := &Host{
|
|
routes: connectorruntime.NewRouteTable(),
|
|
authorizationRoutes: make(map[string]*connectorRoute),
|
|
authorizationProvider: newManagedCredentialAuthorizationProvider(nil),
|
|
registry: NewRouteRegistry(),
|
|
mcpRegistry: NewMCPRegistry(),
|
|
}
|
|
host.authorizationProvider.host = host
|
|
const releaseDigest = "candidate-release"
|
|
receipt, err := host.Reconcile(context.Background(), ReconcileRequest{Runtime: market.RuntimeReconcileRequest{
|
|
OperationID: "disable-unauthed-linear", ConnectionID: "account-linear",
|
|
Connector: market.Connector{
|
|
Key: "linear",
|
|
Release: market.Release{ReleaseDigest: releaseDigest},
|
|
Installation: market.Installation{
|
|
State: market.InstallationStateInstalling,
|
|
CandidateReleaseDigest: releaseDigest,
|
|
},
|
|
},
|
|
Enabled: false, Generation: market.HostGeneration{BootEpoch: "boot-1", Generation: 8},
|
|
}})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if receipt.ReleaseDigest != releaseDigest {
|
|
t.Fatalf("receipt release digest = %q, want %q", receipt.ReleaseDigest, releaseDigest)
|
|
}
|
|
if receipt.Readiness.State != market.RuntimeReadinessBlocked ||
|
|
receipt.Readiness.ReasonCode != market.RuntimeReadinessReasonRuntimeDisabled {
|
|
t.Fatalf("disabled candidate receipt = %#v", receipt)
|
|
}
|
|
}
|
|
|
|
func TestDeactivateRuntimeAllConnectionsCancelsPendingAuthorizationRoute(t *testing.T) {
|
|
host := &Host{
|
|
routes: connectorruntime.NewRouteTable(),
|
|
authorizationRoutes: make(map[string]*connectorRoute),
|
|
authorizationProvider: newManagedCredentialAuthorizationProvider(nil),
|
|
registry: NewRouteRegistry(),
|
|
mcpRegistry: NewMCPRegistry(),
|
|
}
|
|
host.authorizationProvider.host = host
|
|
generation := market.HostGeneration{BootEpoch: "boot-1", Generation: 3}
|
|
route := testUninstallRoute("account-pending", "github", "superseded-release", market.HostGeneration{BootEpoch: "authorization", Generation: 2})
|
|
host.authorizationRoutes[route.id] = route
|
|
canceled := make(chan struct{})
|
|
const operationID = "authorization-pending"
|
|
host.authorizationProvider.sessions[operationID] = &credentialBrokerSession{
|
|
operationID: operationID, route: route,
|
|
cancel: func() { close(canceled) }, changed: make(chan struct{}), state: market.AuthorizationStatePending,
|
|
}
|
|
host.authorizationProvider.activeByRoute[route.id] = operationID
|
|
|
|
if err := host.DeactivateRuntime(context.Background(), market.RuntimeDeactivationRequest{
|
|
ConnectionID: "current-connection", ConnectorKey: "github", ReleaseDigest: "release-1",
|
|
AllConnections: true, Generation: generation, Deadline: time.Now().Add(time.Second),
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
select {
|
|
case <-canceled:
|
|
default:
|
|
t.Fatal("pending credential broker session was not canceled")
|
|
}
|
|
if host.authorizationRoutes[route.id] != nil || host.authorizationProvider.sessions[operationID] != nil || !route.processes.IsFenced() {
|
|
t.Fatal("pending authorization route survived uninstall")
|
|
}
|
|
}
|
|
|
|
func TestDeactivateRuntimeAllConnectionsRemovesOrphanedManagedCLIShim(t *testing.T) {
|
|
binDir := t.TempDir()
|
|
host := &Host{
|
|
binDir: binDir,
|
|
routes: connectorruntime.NewRouteTable(),
|
|
authorizationRoutes: make(map[string]*connectorRoute),
|
|
authorizationProvider: newManagedCredentialAuthorizationProvider(nil),
|
|
registry: NewRouteRegistry(),
|
|
mcpRegistry: NewMCPRegistry(),
|
|
}
|
|
host.authorizationProvider.host = host
|
|
command := "tutti-connector-github"
|
|
content := "#!/bin/sh\nexport TUTTI_CONNECTOR_KEY='github'\n"
|
|
if runtime.GOOS == "windows" {
|
|
command += ".cmd"
|
|
content = "@echo off\r\nset \"TUTTI_CONNECTOR_KEY=github\"\r\n"
|
|
}
|
|
shimPath := filepath.Join(binDir, command)
|
|
if err := os.WriteFile(shimPath, []byte(content), 0o700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err := host.DeactivateRuntime(context.Background(), market.RuntimeDeactivationRequest{
|
|
ConnectionID: "missing", ConnectorKey: "github", ReleaseDigest: "release-1", AllConnections: true,
|
|
Generation: market.HostGeneration{BootEpoch: "boot-1", Generation: 1}, Deadline: time.Now().Add(time.Second),
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := os.Stat(shimPath); !os.IsNotExist(err) {
|
|
t.Fatalf("orphaned CLI shim still exists: %v", err)
|
|
}
|
|
}
|
|
|
|
func testUninstallRoute(connectionID, connectorKey, releaseDigest string, generation market.HostGeneration) *connectorRoute {
|
|
return &connectorRoute{
|
|
id: connectorRouteKey(connectionID, connectorKey), connectionID: connectionID,
|
|
connectorKey: connectorKey, releaseDigest: releaseDigest, generation: generation,
|
|
mcpTools: make(map[string]registeredMCPTool), processes: connectorruntime.NewProcessGroup(),
|
|
}
|
|
}
|