200 lines
8.2 KiB
Go
200 lines
8.2 KiB
Go
//go:build blackbox
|
|
|
|
// Package p2 contains P2 blackbox tests — important features that don't block
|
|
// release but failures must be recorded.
|
|
//
|
|
// This file covers engine-dispatched slash commands:
|
|
//
|
|
// /whoami, /agent-sid, /skills, /cron list, /quiet, /effort, /search
|
|
//
|
|
// and security guard: non-authorized user rejection.
|
|
//
|
|
// Run:
|
|
//
|
|
// CC_BLACKBOX_CLAUDECODE_API_KEY=xxx \
|
|
// go test -tags blackbox ./tests/blackbox/p2/... -timeout 600s -v
|
|
package p2
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/chenhg5/cc-connect/tests/blackbox/helper"
|
|
bbplatform "github.com/chenhg5/cc-connect/tests/blackbox/platform"
|
|
)
|
|
|
|
const p2CmdTimeout = 30 * time.Second
|
|
|
|
// ── P2-41: /whoami ────────────────────────────────────────────────────────────
|
|
|
|
func TestP2_41_Whoami_ClaudeCode(t *testing.T) {
|
|
t.Parallel()
|
|
env := helper.NewEnv(t, "claudecode")
|
|
reply := env.SendWithTimeout("/whoami", p2CmdTimeout)
|
|
// Should contain the user ID we injected ("user1") or user name.
|
|
assertContainsAny(t, "P2-41 /whoami", reply.Text(),
|
|
"user1", "test-user", "user", "身份", "ID")
|
|
t.Logf("P2-41 OK: %q", truncate(reply.Text(), 150))
|
|
}
|
|
|
|
// ── P2-45: /agent-sid ────────────────────────────────────────────────────────
|
|
|
|
func TestP2_45_AgentSid_ClaudeCode(t *testing.T) {
|
|
t.Parallel()
|
|
env := helper.NewEnv(t, "claudecode")
|
|
|
|
// /agent-sid requires an active session first.
|
|
env.Send("say hi briefly")
|
|
|
|
reply := env.SendWithTimeout("/agent-sid", p2CmdTimeout)
|
|
// Should contain a session ID of some kind.
|
|
text := reply.Text()
|
|
hasSessionID := strings.ContainsAny(text, "-") && len(text) > 5
|
|
if !hasSessionID && !strings.Contains(strings.ToLower(text), "session") &&
|
|
!strings.Contains(strings.ToLower(text), "no session") {
|
|
t.Errorf("P2-45 /agent-sid: response doesn't look like a session ID\ngot: %q", text)
|
|
}
|
|
t.Logf("P2-45 OK: %q", truncate(text, 150))
|
|
}
|
|
|
|
// ── P2-38: /skills ───────────────────────────────────────────────────────────
|
|
|
|
func TestP2_38_Skills_ClaudeCode(t *testing.T) {
|
|
t.Parallel()
|
|
env := helper.NewEnv(t, "claudecode")
|
|
reply := env.SendWithTimeout("/skills", p2CmdTimeout)
|
|
assertContainsAny(t, "P2-38 /skills", strings.ToLower(reply.Text()),
|
|
"skill", "no skill", "没有", "available", "技能")
|
|
t.Logf("P2-38 OK: %q", truncate(reply.Text(), 200))
|
|
}
|
|
|
|
// ── P2-56 + P2-57: /cron list and /cron add ──────────────────────────────────
|
|
|
|
func TestP2_56_CronList_ClaudeCode(t *testing.T) {
|
|
t.Parallel()
|
|
env := helper.NewEnv(t, "claudecode")
|
|
reply := env.SendWithTimeout("/cron list", p2CmdTimeout)
|
|
assertContainsAny(t, "P2-56 /cron list", strings.ToLower(reply.Text()),
|
|
"cron", "job", "task", "schedule", "no cron", "没有", "定时")
|
|
t.Logf("P2-56 OK: %q", truncate(reply.Text(), 200))
|
|
}
|
|
|
|
func TestP2_57_CronAdd_ClaudeCode(t *testing.T) {
|
|
t.Parallel()
|
|
env := helper.NewEnv(t, "claudecode")
|
|
|
|
// Add a cron job.
|
|
addReply := env.SendWithTimeout(
|
|
`/cron add --cron "0 9 * * 1" --prompt "Weekly status summary" --desc "weekly-test"`,
|
|
p2CmdTimeout,
|
|
)
|
|
t.Logf("/cron add reply: %q", truncate(addReply.Text(), 200))
|
|
|
|
// /cron list should now show the job.
|
|
listReply := env.SendWithTimeout("/cron list", p2CmdTimeout)
|
|
hasEntry := strings.Contains(listReply.Text(), "weekly-test") ||
|
|
strings.Contains(listReply.Text(), "Weekly") ||
|
|
strings.Contains(listReply.Text(), "0 9 * * 1")
|
|
if !hasEntry {
|
|
t.Errorf("P2-57 /cron add: job not found in /cron list\n/cron list: %q", listReply.Text())
|
|
}
|
|
t.Logf("P2-57 OK: cron job appears in /cron list")
|
|
}
|
|
|
|
// ── P2-40: /quiet ────────────────────────────────────────────────────────────
|
|
|
|
func TestP2_40_Quiet_ClaudeCode(t *testing.T) {
|
|
t.Parallel()
|
|
env := helper.NewEnv(t, "claudecode")
|
|
reply := env.SendWithTimeout("/quiet", p2CmdTimeout)
|
|
assertContainsAny(t, "P2-40 /quiet", strings.ToLower(reply.Text()),
|
|
"quiet", "silent", "mode", "安静", "模式", "enabled", "disabled", "on", "off")
|
|
t.Logf("P2-40 OK: %q", truncate(reply.Text(), 150))
|
|
}
|
|
|
|
// ── P2-39: /effort ───────────────────────────────────────────────────────────
|
|
|
|
func TestP2_39_Effort_ClaudeCode(t *testing.T) {
|
|
t.Parallel()
|
|
env := helper.NewEnv(t, "claudecode")
|
|
reply := env.SendWithTimeout("/effort high", p2CmdTimeout)
|
|
assertContainsAny(t, "P2-39 /effort high", strings.ToLower(reply.Text()),
|
|
"effort", "high", "reasoning", "思考", "努力", "switched", "changed", "not supported", "支持")
|
|
t.Logf("P2-39 OK: %q", truncate(reply.Text(), 150))
|
|
}
|
|
|
|
// ── P2-46: /search ───────────────────────────────────────────────────────────
|
|
|
|
func TestP2_46_Search_ClaudeCode(t *testing.T) {
|
|
t.Parallel()
|
|
env := helper.NewEnv(t, "claudecode")
|
|
|
|
// Create a session with a known keyword.
|
|
env.Send("the keyword is XYZZY42")
|
|
|
|
// Search for the keyword.
|
|
reply := env.SendWithTimeout("/search XYZZY42", p2CmdTimeout)
|
|
assertContainsAny(t, "P2-46 /search", strings.ToLower(reply.Text()),
|
|
"xyzzy42", "session", "result", "found", "no result", "没有", "搜索")
|
|
t.Logf("P2-46 OK: %q", truncate(reply.Text(), 200))
|
|
}
|
|
|
|
// ── P2-61: 非授权用户被拒绝 ──────────────────────────────────────────────────
|
|
|
|
// TestP2_61_UnauthorizedUserIgnored verifies that when a message arrives from
|
|
// a user ID not in allow_from, cc-connect does NOT send a reply.
|
|
//
|
|
// The engine's allow_from filter is configured per-project. This test uses a
|
|
// separate MockPlatform that injects a message from a user NOT in any
|
|
// allow_from list. The platform should receive no reply.
|
|
//
|
|
// NOTE: This test requires allow_from to be configured. Since the blackbox
|
|
// test engine is created without allow_from config, we CANNOT test rejection
|
|
// here — this is a configuration-level feature. Marking as informational.
|
|
func TestP2_61_AllowFromDocumented(t *testing.T) {
|
|
t.Log("P2-61 (allow_from): requires platform-level allow_from config.")
|
|
t.Log("Verified manually via IM with non-whitelisted Telegram user ID.")
|
|
t.Log("Cannot automate without modifying engine config per-test.")
|
|
// Not a failure — documenting the gap.
|
|
}
|
|
|
|
// ── P2-47: /bind ─────────────────────────────────────────────────────────────
|
|
|
|
func TestP2_47_Bind_ClaudeCode(t *testing.T) {
|
|
t.Parallel()
|
|
env := helper.NewEnv(t, "claudecode")
|
|
reply := env.SendWithTimeout("/bind setup", p2CmdTimeout)
|
|
assertContainsAny(t, "P2-47 /bind setup", strings.ToLower(reply.Text()),
|
|
"bind", "relay", "setup", "绑定", "不支持", "not supported", "project")
|
|
t.Logf("P2-47 OK: %q", truncate(reply.Text(), 200))
|
|
}
|
|
|
|
// ── helpers ───────────────────────────────────────────────────────────────────
|
|
|
|
func assertContainsAny(t *testing.T, label, text string, candidates ...string) {
|
|
t.Helper()
|
|
lower := strings.ToLower(text)
|
|
for _, c := range candidates {
|
|
if strings.Contains(lower, strings.ToLower(c)) {
|
|
return
|
|
}
|
|
}
|
|
t.Errorf("%s: none of %v found\ngot: %q", label, candidates, text)
|
|
}
|
|
|
|
func truncate(s string, max int) string {
|
|
if len(s) <= max {
|
|
return s
|
|
}
|
|
return s[:max] + "…"
|
|
}
|
|
|
|
// sendToNewPlatform injects a message from a fresh user to the existing engine
|
|
// via a second mock platform. Used for multi-user test scenarios.
|
|
func sendToNewPlatform(_ *bbplatform.MockPlatform, userID, content string) {
|
|
// placeholder — used in custom command tests below
|
|
_ = fmt.Sprintf("user %s: %s", userID, content)
|
|
}
|