fix(cline): make Go hook installation transactional
This commit is contained in:
1 parent
bb7186630d
commit
050d0043ea
14 files changed
+1681
-484
No files matched your search
@@ -0,0 +1,96 @@
|
||||
package plugins
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func clineOfficialPayload(event string) map[string]any {
|
||||
payload := map[string]any{
|
||||
"clineVersion": "3.0.46",
|
||||
"hookName": event,
|
||||
"timestamp": "2026-07-19T12:00:00.000Z",
|
||||
"taskId": "task-1",
|
||||
"workspaceRoots": []any{
|
||||
"C:/workspace",
|
||||
},
|
||||
"userId": "user-1",
|
||||
"agent_id": "cline-agent",
|
||||
"parent_agent_id": nil,
|
||||
}
|
||||
if event == "tool_call" {
|
||||
payload["tool_call"] = map[string]any{
|
||||
"id": "call-1", "name": "read_file",
|
||||
"input": map[string]any{"path": "README.md"},
|
||||
}
|
||||
payload["preToolUse"] = map[string]any{
|
||||
"toolName": "read_file",
|
||||
"parameters": map[string]any{"path": "README.md"},
|
||||
}
|
||||
}
|
||||
return payload
|
||||
}
|
||||
|
||||
func marshalClinePayload(t *testing.T, payload map[string]any) []byte {
|
||||
t.Helper()
|
||||
encoded, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
t.Fatalf("marshal Cline payload: %v", err)
|
||||
}
|
||||
return encoded
|
||||
}
|
||||
|
||||
func installClineRuntimeFixture(
|
||||
t *testing.T,
|
||||
status string,
|
||||
) (*clineFixture, *codexTestAPI, *httptest.Server) {
|
||||
t.Helper()
|
||||
api := &codexTestAPI{status: status}
|
||||
server := httptest.NewServer(api)
|
||||
fixture := prepareClineFixture(t, clineFixtureOptions{})
|
||||
fixture.config.BaseURL = server.URL
|
||||
if err := fixture.plugin.Install(fixture.config); err != nil {
|
||||
server.Close()
|
||||
t.Fatalf("install Cline hooks: %v", err)
|
||||
}
|
||||
return fixture, api, server
|
||||
}
|
||||
|
||||
func TestClineGeneratedGuardEmitsPureCancellationForFrozenAndRevokedAgents(t *testing.T) {
|
||||
for _, status := range []string{"frozen", "revoked"} {
|
||||
t.Run(status, func(t *testing.T) {
|
||||
fixture, _, server := installClineRuntimeFixture(t, status)
|
||||
defer server.Close()
|
||||
result := runClineWrapper(
|
||||
t,
|
||||
fixture,
|
||||
fixture.guardWrapper,
|
||||
marshalClinePayload(t, clineOfficialPayload("tool_call")),
|
||||
)
|
||||
control := decodeClineControl(t, result.stdout)
|
||||
if result.exitCode != 0 || control["cancel"] != true ||
|
||||
!strings.Contains(strings.ToLower(result.stderr), status) ||
|
||||
!strings.Contains(result.stderr, "Tool execution blocked") {
|
||||
t.Fatalf("guard result = %#v, %#v", result, control)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestClineGeneratedRuntimesKeepFailuresObservableAndFailOpen(t *testing.T) {
|
||||
fixture, _, server := installClineRuntimeFixture(t, "active")
|
||||
payload := marshalClinePayload(t, clineOfficialPayload("tool_call"))
|
||||
server.Close()
|
||||
guard := runClineWrapper(t, fixture, fixture.guardWrapper, payload)
|
||||
audit := runClineWrapper(t, fixture, fixture.auditWrapper, []byte("{ malformed"))
|
||||
log, err := os.ReadFile(filepath.Join(fixture.agentDir, "error.log"))
|
||||
if guard.exitCode != 0 || !strings.Contains(guard.stderr, "Failed to resolve agent status") ||
|
||||
audit.exitCode != 0 || err != nil ||
|
||||
!strings.Contains(strings.ToLower(string(log)), "invalid json") {
|
||||
t.Fatalf("fail-open results = %#v, %#v, log=%q, err=%v", guard, audit, log, err)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user