Files
knowledge-governance/internal/qaopsorg/directory_runner_test.go
T

99 lines
4.0 KiB
Go

package qaopsorg
import (
"context"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"time"
)
func TestDirectoryBridgeRegressionSuiteRunsInDefaultCI(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
cmd := exec.CommandContext(ctx, "python3", "-m", "unittest", "discover", "-s", "scripts", "-p", "test_qa_ops_directory_sync.py", "-v")
cmd.Dir = filepath.Join("..", "..")
if output, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("directory bridge regression failed: %v\n%s", err, output)
}
}
func TestDirectoryRunnerParsesPrivateOutputAndRemovesSpool(t *testing.T) {
t.Setenv("DINGTALK_CLIENT_SECRET", "fixture-credential-do-not-forward")
root := t.TempDir()
spool := filepath.Join(root, "spool")
if err := os.Mkdir(spool, 0700); err != nil {
t.Fatal(err)
}
script := filepath.Join(root, "fake-sync.sh")
fixture := `{"source":"weekly_report_live","synced_at":"2026-10-01T00:00:00Z","complete":true,"directory":{"departments":[{"id":"root","parent_id":"","name":"隔离测试部门"}],"people":[{"id":"fixture-user","department_ids":["root"]}]}}`
body := "#!/bin/sh\nif [ -n \"${DINGTALK_CLIENT_SECRET:-}\" ]; then exit 3; fi\nwhile [ \"$#\" -gt 0 ]; do if [ \"$1\" = --output ]; then output=$2; shift; fi; shift; done\numask 077\nprintf '%s' '" + fixture + "' > \"$output\"\n"
if err := os.WriteFile(script, []byte(body), 0700); err != nil {
t.Fatal(err)
}
runner := ExecDirectoryRunner{Python: "/bin/sh", Script: script, RuntimeRoot: root, SpoolRoot: spool, Timeout: time.Second}
result, err := runner.Run(context.Background())
if err != nil || len(result.Directory.People) != 1 {
t.Fatalf("runner failed: %v", err)
}
entries, err := os.ReadDir(spool)
if err != nil || len(entries) != 0 {
t.Fatal("personnel spool leaked")
}
if err := os.WriteFile(script, []byte("#!/bin/sh\nprintf 'secret-driver-detail' >&2\nexit 1\n"), 0700); err != nil {
t.Fatal(err)
}
_, err = runner.Run(context.Background())
if err == nil || strings.Contains(err.Error(), "secret-driver-detail") {
t.Fatal("runner error is missing or leaks child stderr")
}
entries, _ = os.ReadDir(spool)
if len(entries) != 0 {
t.Fatal("failed runner leaked spool")
}
}
func TestDirectoryRunnerRejectsMalformedOrPublicPersonnelOutput(t *testing.T) {
root := t.TempDir()
for _, mode := range []string{"malformed", "public"} {
script := filepath.Join(root, mode+".sh")
body := "#!/bin/sh\numask 077\nwhile [ \"$#\" -gt 0 ]; do if [ \"$1\" = --output ]; then output=$2; shift; fi; shift; done\nprintf '%s' '{not-json}' > \"$output\"\n"
if mode == "public" {
body += "printf '%s' '{\"source\":\"weekly_report_live\",\"synced_at\":\"2026-10-01T00:00:00Z\",\"complete\":true,\"directory\":{\"departments\":[{\"id\":\"root\",\"parent_id\":\"\",\"name\":\"fixture\"}],\"people\":[{\"id\":\"fixture-user\",\"department_ids\":[\"root\"]}]}}' > \"$output\"\nchmod 644 \"$output\"\n"
}
if err := os.WriteFile(script, []byte(body), 0700); err != nil {
t.Fatal(err)
}
runner := ExecDirectoryRunner{Python: "/bin/sh", Script: script, RuntimeRoot: root, SpoolRoot: root, Timeout: time.Second}
if _, err := runner.Run(context.Background()); err == nil {
t.Fatal("invalid output accepted")
}
}
}
func TestDirectoryRunnerDeadlineStopsChildAndCleansPrivateSpool(t *testing.T) {
root := t.TempDir()
spool := filepath.Join(root, "spool")
if err := os.Mkdir(spool, 0700); err != nil {
t.Fatal(err)
}
script := filepath.Join(root, "timeout.sh")
if err := os.WriteFile(script, []byte("#!/bin/sh\nexec sleep 30\n"), 0700); err != nil {
t.Fatal(err)
}
runner := ExecDirectoryRunner{Python: "/bin/sh", Script: script, RuntimeRoot: root, SpoolRoot: spool, Timeout: 50 * time.Millisecond}
started := time.Now()
if _, err := runner.Run(context.Background()); err == nil {
t.Fatal("timed-out source reported success")
}
if time.Since(started) > 5*time.Second {
t.Fatal("source process cleanup is unbounded")
}
entries, err := os.ReadDir(spool)
if err != nil || len(entries) != 0 {
t.Fatal("timed-out source retained personnel spool")
}
}