Files
go-sip/internal/config/nonprod_call_gate_test.go
T

83 lines
5.5 KiB
Go

package config
import (
"context"
"os"
"os/exec"
"os/user"
"path/filepath"
"strings"
"testing"
"time"
)
func TestNonprodCallEvidenceFailsBeforeDialWithoutRequiredGates(t *testing.T) {
cases := []struct {
name, hour, environment, enabled, active, ari, endpoint, want string
beforeEvidence bool
}{
{name: "before real window", hour: "0859", environment: "real", enabled: "enabled", active: "active", ari: "ready", want: "outside Asia/Shanghai 09:00-20:00", beforeEvidence: true},
{name: "at real window end", hour: "2000", environment: "real", enabled: "enabled", active: "active", ari: "ready", want: "outside Asia/Shanghai 09:00-20:00", beforeEvidence: true},
{name: "invalid local clock", hour: "error", environment: "real", enabled: "enabled", active: "active", ari: "ready", want: "Asia/Shanghai clock unavailable", beforeEvidence: true},
{name: "window opens at nine", hour: "0900", environment: "real", enabled: "disabled", active: "active", ari: "ready", want: "Asterisk service must be enabled and active"},
{name: "last permitted minute", hour: "1959", environment: "real", enabled: "disabled", active: "active", ari: "ready", want: "Asterisk service must be enabled and active"},
{name: "invalid nonproduction environment", hour: "1000", environment: "Production", enabled: "enabled", active: "active", ari: "ready", want: "invalid non-production environment", beforeEvidence: true},
{name: "asterisk not enabled", hour: "1000", environment: "real", enabled: "disabled", active: "active", ari: "ready", want: "Asterisk service must be enabled and active"},
{name: "asterisk not active", hour: "1000", environment: "real", enabled: "enabled", active: "inactive", ari: "ready", want: "Asterisk service must be enabled and active"},
{name: "ARI module not loaded", hour: "1000", environment: "real", enabled: "enabled", active: "active", ari: "module-absent", want: "ARI module or HTTP route unavailable"},
{name: "ARI HTTP route not enabled", hour: "1000", environment: "real", enabled: "enabled", active: "active", ari: "http-absent", want: "ARI module or HTTP route unavailable"},
{name: "PJSIP endpoint missing", hour: "1000", environment: "real", enabled: "enabled", active: "active", ari: "ready", endpoint: "missing", want: "PJSIP endpoint unavailable"},
{name: "missing installed artifact hashes", hour: "1000", environment: "real", enabled: "enabled", active: "active", ari: "ready", want: "installed package/config SHA-256 unavailable"},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
tools := t.TempDir()
write := func(name, body string) string {
t.Helper()
path := filepath.Join(tools, name)
if err := os.WriteFile(path, []byte("#!/bin/sh\n"+body), 0700); err != nil {
t.Fatal(err)
}
return path
}
write("id", "if [ \"$1\" = -u ]; then echo 0; else exec /usr/bin/id \"$@\"; fi\n")
write("date", "if [ \"${TZ-}\" = Asia/Shanghai ] && [ \"$1\" = +%H%M ]; then if [ "+tc.hour+" = error ]; then exit 1; fi; echo "+tc.hour+"; else exec /usr/bin/date \"$@\"; fi\n")
write("ip", "echo 'lo UNKNOWN 127.0.0.1/8'\n")
write("ss", "echo 'udp 127.0.0.1:5060'\n")
write("systemctl", "case \"$1\" in is-enabled) echo \"$TEST_ENABLED\";; is-active) echo \"$TEST_ACTIVE\";; *) exit 1;; esac\n")
asterisk := write("asterisk", "case \"$2\" in 'module show like res_ari.so') if [ \"$TEST_ARI\" = module-absent ]; then echo '0 modules loaded'; else echo 'res_ari.so Asterisk REST Interface 0 Running'; fi;; 'http show status') if [ \"$TEST_ARI\" = http-absent ]; then echo 'Server Disabled'; else echo 'Server Enabled and Bound to 127.0.0.1:8088'; echo '/ari/...'; fi;; 'pjsip show endpoint '*) if [ \"$TEST_ENDPOINT\" = missing ]; then echo 'Unable to find object'; else echo 'Endpoint: provider-primary'; fi;; *) echo 'mock Asterisk status';; esac\n")
tcpdump := write("tcpdump", "case \" $* \" in *' -c 1 '*) exit 124;; *) exit 91;; esac\n")
marker := filepath.Join(tools, "DIALED")
write("runuser", "touch \"$TEST_DIAL_MARKER\"; exit 88\n")
currentUser, err := user.Current()
if err != nil {
t.Fatal(err)
}
evidence := filepath.Join(t.TempDir(), "evidence")
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
command := exec.CommandContext(ctx, "bash", "../../deploys/test/nonprod-call-evidence.sh",
"--environment", tc.environment, "--trunk", "provider-primary", "--target", "15003164745",
"--interface", "lo", "--run-as", currentUser.Username, "--recording-dir", filepath.Join(tools, "recordings"),
"--evidence-dir", evidence, "--attempt-ledger", filepath.Join(tools, "attempts.tsv"), "--", "/bin/true")
command.Env = append(os.Environ(), "PATH="+tools+":"+os.Getenv("PATH"), "ASTERISK_BIN="+asterisk, "TCPDUMP_BIN="+tcpdump,
"TEST_DIAL_MARKER="+marker, "TEST_ENABLED="+tc.enabled, "TEST_ACTIVE="+tc.active, "TEST_ARI="+tc.ari, "TEST_ENDPOINT="+tc.endpoint)
output, err := command.CombinedOutput()
if ctx.Err() != nil {
t.Fatalf("isolated diagnostic gate hung: %v", ctx.Err())
}
if err == nil || !strings.Contains(string(output), tc.want) {
t.Fatalf("required gate was skipped or failed for another reason: err=%v want=%q output=%s", err, tc.want, output)
}
if _, err := os.Stat(marker); !os.IsNotExist(err) {
t.Fatalf("diagnostic script invoked the call command: marker err=%v", err)
}
if tc.beforeEvidence {
if _, err := os.Stat(evidence); !os.IsNotExist(err) {
t.Fatalf("preflight reject wrote host evidence or opened capture: err=%v", err)
}
}
})
}
}