package agent import ( "encoding/json" "strings" "testing" ) func TestDecodeRemoteRun_TypedSuccess(t *testing.T) { body := []byte("hello") rv := BuildResultV1("ws1", "success", false, "", []struct { RelPath string Content []byte }{{"output.md", body}}) raw, _ := json.Marshal(map[string]any{ "ok": true, "run_id": "ws1", "runner_protocol": "typed-v1", "capabilities": map[string]any{"typed_result": true}, "patches": []map[string]string{{"rel_path": "output.md", "content": "hello"}}, "result_v1": rv, }) got, err := DecodeRemoteRun(raw, "ws1", true) if err != nil || !got.OK || got.Legacy { t.Fatalf("typed success: err=%v ok=%v legacy=%v", err, got != nil && got.OK, got != nil && got.Legacy) } if string(got.Incoming["output.md"]) != "hello" { t.Fatal("patch body") } } func TestDecodeRemoteRun_TypedPresentInvalidNoFallback(t *testing.T) { rv := BuildResultV1("ws1", "success", false, "", []struct { RelPath string Content []byte }{{"output.md", []byte("hello")}}) raw, _ := json.Marshal(map[string]any{ "ok": true, "run_id": "ws1", "runner_protocol": "typed-v1", "patches": []map[string]string{{"rel_path": "output.md", "content": "TAMPER"}}, "result_v1": rv, }) if _, err := DecodeRemoteRun(raw, "ws1", true); err == nil { t.Fatal("tampered typed body must fail closed") } } func TestDecodeRemoteRun_RunMismatch(t *testing.T) { rv := BuildResultV1("other-run", "success", false, "", []struct { RelPath string Content []byte }{{"output.md", []byte("hello")}}) raw, _ := json.Marshal(map[string]any{ "ok": true, "run_id": "other-run", "patches": []map[string]string{{"rel_path": "output.md", "content": "hello"}}, "result_v1": rv, }) if _, err := DecodeRemoteRun(raw, "ws1", true); err == nil { t.Fatal("foreign run_id must fail closed") } } func TestDecodeRemoteRun_HandshakeDowngrade(t *testing.T) { raw, _ := json.Marshal(map[string]any{ "ok": true, "run_id": "ws1", "capabilities": map[string]any{"typed_result": true}, "patches": []map[string]string{{"rel_path": "output.md", "content": "hello"}}, }) if _, err := DecodeRemoteRun(raw, "ws1", true); err == nil { t.Fatal("typed capability without result_v1 is a downgrade attack") } } func TestDecodeRemoteRun_MissingTypedWithoutLegacyHandshake(t *testing.T) { raw, _ := json.Marshal(map[string]any{ "ok": true, "patches": []map[string]string{{"rel_path": "output.md", "content": "hello"}}, }) if _, err := DecodeRemoteRun(raw, "ws1", true); err == nil { t.Fatal("implicit legacy must fail after P4") } } func TestDecodeRemoteRun_LegacyHandshakeStillWorks(t *testing.T) { raw, _ := json.Marshal(map[string]any{ "ok": true, "runner_protocol": "legacy", "fallback_reason": "Connection error.", "retryable": true, "patches": []map[string]string{{"rel_path": "output.md", "content": "hello"}}, }) got, err := DecodeRemoteRun(raw, "ws1", true) if err != nil || !got.OK || !got.Legacy { t.Fatalf("legacy handshake: err=%v got=%+v", err, got) } } func TestDecodeRemoteRun_LegacyDoesNotLeakPaths(t *testing.T) { raw, _ := json.Marshal(map[string]any{ "ok": false, "runner_protocol": "legacy", "fallback_reason": "failed /tmp/secret/token=abc", "patches": []any{}, }) got, err := DecodeRemoteRun(raw, "ws1", true) if err != nil { t.Fatal(err) } if got.OK { t.Fatal("want failure") } if strings.Contains(got.Reason, "/tmp") || strings.Contains(got.Reason, "token") { t.Fatalf("leaked reason %q", got.Reason) } } func TestDecodeRemoteRun_PathEscapeMember(t *testing.T) { rv := ResultV1{ SchemaVersion: 1, RunID: "ws1", Outcome: "success", Members: []ResultV1Member{{Kind: "file", Path: "../etc/passwd", Digest: DigestBytes([]byte("x")), Size: 1}}, } rv.ContentDigest = rv.MembersDigest() raw, _ := json.Marshal(map[string]any{ "ok": true, "result_v1": rv, "patches": []map[string]string{{"rel_path": "../etc/passwd", "content": "x"}}, }) if _, err := DecodeRemoteRun(raw, "ws1", true); err == nil { t.Fatal("escaped member path must fail closed") } } func TestDecodeRemoteRun_OversizedMember(t *testing.T) { big := strings.Repeat("a", MaxResultMemberBytes+1) raw, _ := json.Marshal(map[string]any{ "ok": true, "runner_protocol": "legacy", "patches": []map[string]string{{"rel_path": "output.md", "content": big}}, }) if _, err := DecodeRemoteRun(raw, "ws1", true); err == nil { t.Fatal("oversized member must fail closed") } }