fix(tests): path validation before docker check + a2a queue mock in tests

- container_files.go: move validateRelPath before h.docker==nil check in
  deleteViaEphemeral so F1085 traversal tests fire even when Docker is
  absent in CI (fixes TestDeleteViaEphemeral_F1085_RejectsTraversal)

- a2a_proxy_test.go: add EnqueueA2A mock expectation in
  TestHandleA2ADispatchError_ContextDeadline — DeadlineExceeded now
  triggers the #1870 queue path; mock the INSERT to return an error so
  the test correctly falls through to the expected 503 Retry-After shape

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Molecule AI Core Platform Lead 2026-04-24 11:07:43 +00:00
parent 9d5115b5db
commit adb9c68185
2 changed files with 18 additions and 8 deletions

View File

@ -1158,13 +1158,18 @@ func TestDispatchA2A_ContextDeadline_NoCancelAdded(t *testing.T) {
// --- handleA2ADispatchError ---
func TestHandleA2ADispatchError_ContextDeadline(t *testing.T) {
setupTestDB(t)
mock := setupTestDB(t)
setupTestRedis(t)
handler := NewWorkspaceHandler(newTestBroadcaster(), nil, "http://localhost:8080", t.TempDir())
// No workspace row expected — maybeMarkContainerDead with nil
// provisioner short-circuits, and activity-log insert is suppressed
// (logActivity=false).
// maybeMarkContainerDead with nil provisioner short-circuits (no DB call).
// activity-log insert is suppressed (logActivity=false).
// DeadlineExceeded → isUpstreamBusyError=true → EnqueueA2A attempted.
// Mock the INSERT INTO a2a_queue to fail so we fall through to 503.
mock.ExpectQuery(`INSERT INTO a2a_queue`).
WithArgs("ws-dl", nil, PriorityTask, "{}", "message/send", nil).
WillReturnError(fmt.Errorf("test: queue unavailable"))
_, _, perr := handler.handleA2ADispatchError(
context.Background(), "ws-dl", "", []byte("{}"), "message/send",
context.DeadlineExceeded, 1, false,
@ -1172,7 +1177,7 @@ func TestHandleA2ADispatchError_ContextDeadline(t *testing.T) {
if perr == nil {
t.Fatal("expected error, got nil")
}
// DeadlineExceeded is classified as upstream-busy → 503 with Retry-After.
// EnqueueA2A failed → falls through to legacy 503 with Retry-After.
if perr.Status != http.StatusServiceUnavailable {
t.Errorf("got status %d, want 503", perr.Status)
}

View File

@ -159,9 +159,6 @@ func (h *TemplatesHandler) writeViaEphemeral(ctx context.Context, volumeName str
// deleteViaEphemeral deletes a file from a named volume using an ephemeral container.
func (h *TemplatesHandler) deleteViaEphemeral(ctx context.Context, volumeName, filePath string) error {
if h.docker == nil {
return fmt.Errorf("docker not available")
}
// CWE-78/CWE-22: exec form binds rm to the /configs volume regardless
// of path traversal in filePath. The bind mount volumeName:/configs
// constrains rm; exec form prevents shell interpolation.
@ -169,9 +166,17 @@ func (h *TemplatesHandler) deleteViaEphemeral(ctx context.Context, volumeName, f
// The concat form is the critical fix: rm receives ONE path argument
// so ".." is processed literally — rm -rf /configs/foo/../bar resolves
// to /configs/bar (inside volume), not bar (outside volume).
//
// Path validation MUST come before the docker-available check so that
// traversal inputs are rejected even in test/CI environments where
// Docker is absent. This ensures F1085 regression tests catch real
// violations rather than short-circuiting on "docker not available".
if err := validateRelPath(filePath); err != nil {
return err
}
if h.docker == nil {
return fmt.Errorf("docker not available")
}
resp, err := h.docker.ContainerCreate(ctx, &container.Config{
Image: "alpine:latest",