Files
tutti/services/tuttid/api/automation_rules_feature_gate_test.go
agent-radar 2ed3415fb7
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
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

208 lines
9.6 KiB
Go

package api
import (
"context"
"errors"
"testing"
tuttigenerated "github.com/tutti-os/tutti/services/tuttid/api/generated"
automationrulebiz "github.com/tutti-os/tutti/services/tuttid/biz/automationrule"
collabrunbiz "github.com/tutti-os/tutti/services/tuttid/biz/collabrun"
preferencesbiz "github.com/tutti-os/tutti/services/tuttid/biz/preferences"
automationruleservice "github.com/tutti-os/tutti/services/tuttid/service/automationrule"
collabrunservice "github.com/tutti-os/tutti/services/tuttid/service/collabrun"
)
func gateTestPreferences(flags map[string]bool, err error) stubPreferencesService {
return stubPreferencesService{
getFn: func(context.Context) (preferencesbiz.DesktopPreferences, error) {
if err != nil {
return preferencesbiz.DesktopPreferences{}, err
}
return preferencesbiz.DesktopPreferences{FeatureFlags: flags}, nil
},
}
}
type gateStubAutomationRuleService struct{}
func (gateStubAutomationRuleService) ListRules(context.Context, string) ([]automationrulebiz.Rule, error) {
return []automationrulebiz.Rule{}, nil
}
func (gateStubAutomationRuleService) GetRule(context.Context, string, string) (automationrulebiz.Rule, error) {
return automationrulebiz.Rule{}, nil
}
func (gateStubAutomationRuleService) CreateRule(context.Context, automationruleservice.PutRuleInput) (automationrulebiz.Rule, error) {
return automationrulebiz.Rule{}, nil
}
func (gateStubAutomationRuleService) UpdateRule(context.Context, automationruleservice.PutRuleInput) (automationrulebiz.Rule, error) {
return automationrulebiz.Rule{}, nil
}
func (gateStubAutomationRuleService) DeleteRule(context.Context, string, string) error {
return nil
}
func (gateStubAutomationRuleService) GetSessionOverride(context.Context, string, string) (automationrulebiz.SessionOverride, bool, error) {
return automationrulebiz.SessionOverride{}, false, nil
}
func (gateStubAutomationRuleService) SetSessionOverride(_ context.Context, input automationrulebiz.SessionOverride) (automationrulebiz.SessionOverride, error) {
return input, nil
}
type gateStubCollaborationRunService struct{}
func (gateStubCollaborationRunService) StartConsult(context.Context, collabrunservice.StartConsultInput) (collabrunbiz.Run, error) {
return collabrunbiz.Run{}, nil
}
func (gateStubCollaborationRunService) RecordRun(context.Context, collabrunservice.RecordRunInput) (collabrunbiz.Run, error) {
return collabrunbiz.Run{}, nil
}
func (gateStubCollaborationRunService) SetAdoption(context.Context, string, string, string) (collabrunbiz.Run, error) {
return collabrunbiz.Run{}, nil
}
func (gateStubCollaborationRunService) CancelConsult(context.Context, string, string) (collabrunbiz.Run, error) {
return collabrunbiz.Run{}, nil
}
func (gateStubCollaborationRunService) ListRuns(context.Context, string, string, int) ([]collabrunbiz.Run, error) {
return []collabrunbiz.Run{}, nil
}
func TestAutomationRulesWriteGateRejectsWritesWhenFlagOff(t *testing.T) {
t.Parallel()
ctx := context.Background()
workspaceID := tuttigenerated.WorkspaceID("ws-1")
cases := []struct {
name string
flags map[string]bool
err error
}{
{name: "flag explicitly false", flags: map[string]bool{AutomationRulesFeatureFlag: false}},
{name: "flag absent", flags: map[string]bool{}},
{name: "preferences unreadable", err: errors.New("preferences store down")},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
api := DaemonAPI{
AutomationRuleService: gateStubAutomationRuleService{},
CollaborationRunService: gateStubCollaborationRunService{},
PreferencesService: gateTestPreferences(tc.flags, tc.err),
}
createResponse, err := api.CreateAutomationRule(ctx, tuttigenerated.CreateAutomationRuleRequestObject{WorkspaceID: workspaceID})
if err != nil {
t.Fatalf("CreateAutomationRule() error = %v", err)
}
rejected, ok := createResponse.(tuttigenerated.CreateAutomationRule400JSONResponse)
if !ok {
t.Fatalf("CreateAutomationRule() response = %T, want 400 rejection", createResponse)
}
if reason := tuttigenerated.ApiErrorResponse(rejected.InvalidRequestErrorJSONResponse).Error.Reason; reason == nil || *reason != "automation_rules_disabled" {
t.Fatalf("CreateAutomationRule() rejection reason = %v, want automation_rules_disabled", reason)
}
updateResponse, err := api.UpdateAutomationRule(ctx, tuttigenerated.UpdateAutomationRuleRequestObject{WorkspaceID: workspaceID, AutomationRuleID: "automation-rule:one"})
if err != nil {
t.Fatalf("UpdateAutomationRule() error = %v", err)
}
if _, ok := updateResponse.(tuttigenerated.UpdateAutomationRule400JSONResponse); !ok {
t.Fatalf("UpdateAutomationRule() response = %T, want 400 rejection", updateResponse)
}
deleteResponse, err := api.DeleteAutomationRule(ctx, tuttigenerated.DeleteAutomationRuleRequestObject{WorkspaceID: workspaceID, AutomationRuleID: "automation-rule:one"})
if err != nil {
t.Fatalf("DeleteAutomationRule() error = %v", err)
}
if _, ok := deleteResponse.(tuttigenerated.DeleteAutomationRule400JSONResponse); !ok {
t.Fatalf("DeleteAutomationRule() response = %T, want 400 rejection", deleteResponse)
}
overrideResponse, err := api.SetAgentSessionAutomationRuleOverride(ctx, tuttigenerated.SetAgentSessionAutomationRuleOverrideRequestObject{WorkspaceID: workspaceID, AgentSessionID: "session-1"})
if err != nil {
t.Fatalf("SetAgentSessionAutomationRuleOverride() error = %v", err)
}
if _, ok := overrideResponse.(tuttigenerated.SetAgentSessionAutomationRuleOverride400JSONResponse); !ok {
t.Fatalf("SetAgentSessionAutomationRuleOverride() response = %T, want 400 rejection", overrideResponse)
}
collabCreateResponse, err := api.CreateCollaborationRun(ctx, tuttigenerated.CreateCollaborationRunRequestObject{WorkspaceID: workspaceID})
if err != nil {
t.Fatalf("CreateCollaborationRun() error = %v", err)
}
if _, ok := collabCreateResponse.(tuttigenerated.CreateCollaborationRun400JSONResponse); !ok {
t.Fatalf("CreateCollaborationRun() response = %T, want 400 rejection", collabCreateResponse)
}
adoptionResponse, err := api.SetCollaborationRunAdoption(ctx, tuttigenerated.SetCollaborationRunAdoptionRequestObject{WorkspaceID: workspaceID, CollaborationRunID: "run-1"})
if err != nil {
t.Fatalf("SetCollaborationRunAdoption() error = %v", err)
}
if _, ok := adoptionResponse.(tuttigenerated.SetCollaborationRunAdoption400JSONResponse); !ok {
t.Fatalf("SetCollaborationRunAdoption() response = %T, want 400 rejection", adoptionResponse)
}
cancelResponse, err := api.CancelCollaborationRun(ctx, tuttigenerated.CancelCollaborationRunRequestObject{WorkspaceID: workspaceID, CollaborationRunID: "run-1"})
if err != nil {
t.Fatalf("CancelCollaborationRun() error = %v", err)
}
if _, ok := cancelResponse.(tuttigenerated.CancelCollaborationRun400JSONResponse); !ok {
t.Fatalf("CancelCollaborationRun() response = %T, want 400 rejection", cancelResponse)
}
})
}
}
func TestAutomationRulesWriteGateKeepsReadsWorkingWhenFlagOff(t *testing.T) {
t.Parallel()
api := DaemonAPI{
AutomationRuleService: gateStubAutomationRuleService{},
CollaborationRunService: gateStubCollaborationRunService{},
PreferencesService: gateTestPreferences(map[string]bool{}, nil),
}
rulesResponse, err := api.ListAutomationRules(context.Background(), tuttigenerated.ListAutomationRulesRequestObject{WorkspaceID: "ws-1"})
if err != nil {
t.Fatalf("ListAutomationRules() error = %v", err)
}
if _, ok := rulesResponse.(tuttigenerated.ListAutomationRules200JSONResponse); !ok {
t.Fatalf("ListAutomationRules() response = %T, want 200 with writes gated", rulesResponse)
}
runsResponse, err := api.ListCollaborationRuns(context.Background(), tuttigenerated.ListCollaborationRunsRequestObject{WorkspaceID: "ws-1"})
if err != nil {
t.Fatalf("ListCollaborationRuns() error = %v", err)
}
if _, ok := runsResponse.(tuttigenerated.ListCollaborationRuns200JSONResponse); !ok {
t.Fatalf("ListCollaborationRuns() response = %T, want 200 with writes gated", runsResponse)
}
overrideResponse, err := api.GetAgentSessionAutomationRuleOverride(context.Background(), tuttigenerated.GetAgentSessionAutomationRuleOverrideRequestObject{WorkspaceID: "ws-1", AgentSessionID: "session-1"})
if err != nil {
t.Fatalf("GetAgentSessionAutomationRuleOverride() error = %v", err)
}
if _, ok := overrideResponse.(tuttigenerated.GetAgentSessionAutomationRuleOverride200JSONResponse); !ok {
t.Fatalf("GetAgentSessionAutomationRuleOverride() response = %T, want 200 with writes gated", overrideResponse)
}
}
func TestAutomationRulesWriteGateAllowsWritesWhenFlagOn(t *testing.T) {
t.Parallel()
ctx := context.Background()
// Nil services: reaching the 503 service-unavailable branch proves the
// gate passed the request through to the handler.
api := DaemonAPI{
PreferencesService: gateTestPreferences(map[string]bool{AutomationRulesFeatureFlag: true}, nil),
}
createResponse, err := api.CreateAutomationRule(ctx, tuttigenerated.CreateAutomationRuleRequestObject{WorkspaceID: "ws-1"})
if err != nil {
t.Fatalf("CreateAutomationRule() error = %v", err)
}
if _, ok := createResponse.(tuttigenerated.CreateAutomationRule503JSONResponse); !ok {
t.Fatalf("CreateAutomationRule() response = %T, want 503 passthrough with flag on", createResponse)
}
collabCreateResponse, err := api.CreateCollaborationRun(ctx, tuttigenerated.CreateCollaborationRunRequestObject{WorkspaceID: "ws-1"})
if err != nil {
t.Fatalf("CreateCollaborationRun() error = %v", err)
}
if _, ok := collabCreateResponse.(tuttigenerated.CreateCollaborationRun503JSONResponse); !ok {
t.Fatalf("CreateCollaborationRun() response = %T, want 503 passthrough with flag on", collabCreateResponse)
}
}