package main import ( "bytes" "context" "crypto/x509" "encoding/pem" "errors" "net/http" "net/http/httptest" "os" "path/filepath" "strings" "testing" agentpb "git.ipao.vip/rogee/go-sip/gen/agent" "git.ipao.vip/rogee/go-sip/internal/ai" "git.ipao.vip/rogee/go-sip/internal/config" ) type isolatedAgentRecordingClient struct { agentpb.AgentControlServiceClient } func currentAgentSetupFixture(t *testing.T) (config.AgentEnvironment, approvedMockScenario) { t.Helper() root := t.TempDir() if err := os.Chmod(root, 0700); err != nil { t.Fatal(err) } settings := config.AgentEnvironment{ AgentID: "agent-mock", CellID: "cell-mock", DispatcherID: "11111111-1111-4111-8111-111111111111", Listen: "127.0.0.1:0", SessionPath: filepath.Join(root, "session.json"), RecoveryRoot: root, DispatcherEndpoint: "127.0.0.1:39443", DispatcherServerName: "dispatcher.local", CAFile: filepath.Join(root, "ca.pem"), CertFile: filepath.Join(root, "agent.pem"), KeyFile: filepath.Join(root, "agent.key"), PeerFingerprints: map[string]struct{}{strings.Repeat("a", 64): {}}, } ca, _, _, _, _, _ := localCommandCertificates(t) if err := os.WriteFile(settings.CAFile, ca, 0600); err != nil { t.Fatal(err) } scenario := approvedMockScenario{ InboundPCM16: bytes.Repeat([]byte{1, 0}, 1600), Script: ai.ApprovedMockScript{Turns: []ai.ApprovedMockTurn{{Transcript: "synthetic ASR fixture"}}}, MaxWAVBytes: 4096, ExpectedRecording: true, Outcome: "answered", ReasonMessage: "isolated Mock answered", } return settings, scenario } func TestNewAgentServerBindsMockCallsToOneSessionAndRecoveryRoot(t *testing.T) { settings, scenario := currentAgentSetupFixture(t) server, err := newAgentServer(context.Background(), settings, scenario, map[string]int64{"trunk-mock": 8}, &isolatedAgentRecordingClient{}) if err != nil || server == nil { t.Fatalf("valid isolated Agent could not be assembled: %v", err) } if _, err := os.Stat(settings.SessionPath); !os.IsNotExist(err) { t.Fatalf("server assembly opened durable session before activation: %v", err) } } func TestNewAgentServerRefusesLegacySpoolWithoutChangingIt(t *testing.T) { for _, tc := range []struct { name string marker string dir bool }{ {name: "old upload attempts", marker: ".uploads", dir: true}, {name: "old upload locks", marker: ".upload-locks", dir: true}, {name: "old execution state", marker: filepath.Join("old-execution", "state.json")}, } { t.Run(tc.name, func(t *testing.T) { settings, scenario := currentAgentSetupFixture(t) marker := filepath.Join(settings.RecoveryRoot, tc.marker) if tc.dir { if err := os.Mkdir(marker, 0700); err != nil { t.Fatal(err) } } else { if err := os.Mkdir(filepath.Dir(marker), 0700); err != nil { t.Fatal(err) } if err := os.WriteFile(marker, []byte(`{"schema_version":"1"}`), 0600); err != nil { t.Fatal(err) } } server, err := newAgentServer(context.Background(), settings, scenario, map[string]int64{"trunk-mock": 8}, &isolatedAgentRecordingClient{}) if err == nil || server != nil { t.Fatalf("legacy spool was admitted: %v", err) } if _, err := os.Lstat(marker); err != nil { t.Fatalf("legacy spool changed: %v", err) } if _, err := os.Lstat(settings.SessionPath); !os.IsNotExist(err) { t.Fatalf("rejected Agent created session state: %v", err) } }) } } func TestNewAgentServerRefusesUnsafeAdaptersBeforeResources(t *testing.T) { settings, scenario := currentAgentSetupFixture(t) for _, tc := range []struct { name string change func(*config.AgentEnvironment, *approvedMockScenario, *map[string]int64, *agentpb.AgentControlServiceClient) }{ {"missing Agent identity", func(c *config.AgentEnvironment, _ *approvedMockScenario, _ *map[string]int64, _ *agentpb.AgentControlServiceClient) { c.AgentID = "" }}, {"missing authorized Dispatcher", func(c *config.AgentEnvironment, _ *approvedMockScenario, _ *map[string]int64, _ *agentpb.AgentControlServiceClient) { c.DispatcherID = "" }}, {"missing SIP evidence", func(_ *config.AgentEnvironment, _ *approvedMockScenario, sip *map[string]int64, _ *agentpb.AgentControlServiceClient) { *sip = nil }}, {"unknown SIP revision", func(_ *config.AgentEnvironment, _ *approvedMockScenario, sip *map[string]int64, _ *agentpb.AgentControlServiceClient) { (*sip)["trunk-mock"] = 0 }}, {"missing recovery", func(c *config.AgentEnvironment, _ *approvedMockScenario, _ *map[string]int64, _ *agentpb.AgentControlServiceClient) { c.RecoveryRoot = filepath.Join(t.TempDir(), "missing") }}, {"missing scenario", func(_ *config.AgentEnvironment, s *approvedMockScenario, _ *map[string]int64, _ *agentpb.AgentControlServiceClient) { s.MaxWAVBytes = 0 }}, {"missing Dispatcher transport", func(_ *config.AgentEnvironment, _ *approvedMockScenario, _ *map[string]int64, client *agentpb.AgentControlServiceClient) { *client = nil }}, {"typed-nil Dispatcher transport", func(_ *config.AgentEnvironment, _ *approvedMockScenario, _ *map[string]int64, client *agentpb.AgentControlServiceClient) { var typed *isolatedAgentRecordingClient *client = typed }}, } { t.Run(tc.name, func(t *testing.T) { cfg, media := settings, scenario loaded := map[string]int64{"trunk-mock": 8} var client agentpb.AgentControlServiceClient = &isolatedAgentRecordingClient{} tc.change(&cfg, &media, &loaded, &client) if server, err := newAgentServer(context.Background(), cfg, media, loaded, client); err == nil || server != nil { t.Fatalf("unsafe current Agent assembly was accepted: %v", err) } if _, err := os.Stat(settings.SessionPath); !os.IsNotExist(err) { t.Fatalf("rejected Agent assembly wrote durable session: %v", err) } }) } } func TestLocalMockHTTPClientTrustsOnlyApprovedLocalHTTPSCertificate(t *testing.T) { local := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) })) defer local.Close() trustedPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: local.Certificate().Raw}) trusted, err := localMockHTTPClient(trustedPEM) if err != nil { t.Fatal(err) } response, err := trusted.Get(local.URL) if err != nil || response.StatusCode != http.StatusOK { t.Fatalf("verified local HTTPS was refused: response=%v err=%v", response, err) } _ = response.Body.Close() unrelatedCA, _, _, _, _, _ := localCommandCertificates(t) untrusted, err := localMockHTTPClient(unrelatedCA) if err != nil { t.Fatal(err) } response, err = untrusted.Get(local.URL) var unknownAuthority x509.UnknownAuthorityError if response != nil || !errors.As(err, &unknownAuthority) { t.Fatalf("unknown local HTTPS certificate was admitted: response=%v err=%v", response, err) } if client, err := localMockHTTPClient([]byte("invalid trust bundle")); err == nil || client != nil { t.Fatalf("invalid trust bundle was accepted: %v", err) } } func TestLocalMockHTTPClientRefusesExternalAndRedirectedTargets(t *testing.T) { ca, _, _, _, _, _ := localCommandCertificates(t) client, err := localMockHTTPClient(ca) if err != nil { t.Fatal(err) } if response, err := client.Get("https://oss.example.invalid/approved"); err == nil || response != nil || !strings.Contains(err.Error(), "not local") { t.Fatalf("Mock uploader contacted an external target: %v", err) } local := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path == "/redirect" { http.Redirect(w, r, "https://oss.example.invalid/approved", http.StatusFound) return } w.WriteHeader(http.StatusOK) })) defer local.Close() response, err := client.Get(local.URL + "/ok") if err != nil || response.StatusCode != http.StatusOK { t.Fatalf("local Mock endpoint was refused: response=%v err=%v", response, err) } _ = response.Body.Close() response, err = client.Get(local.URL + "/redirect") if err != nil || response.StatusCode != http.StatusFound { t.Fatalf("Mock uploader followed an external redirect: response=%v err=%v", response, err) } _ = response.Body.Close() }