Files
go-sip/cmd/sip-go-agent/current_agent_setup_test.go
T

167 lines
6.7 KiB
Go

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 TestNewCurrentAgentServerBindsMockCallsToOneSessionAndRecoveryRoot(t *testing.T) {
settings, scenario := currentAgentSetupFixture(t)
server, err := newCurrentAgentServer(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 TestNewCurrentAgentServerRefusesUnsafeAdaptersBeforeResources(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 := newCurrentAgentServer(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()
}