package node import ( "reflect" "testing" "time" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/structpb" runtime "iop/packages/go/execution" iop "iop/proto/gen/iop" ) func TestRunRequestFromProtoPreservesWireFields(t *testing.T) { policy, err := structpb.NewStruct(map[string]any{"allow": true}) if err != nil { t.Fatal(err) } input, err := structpb.NewStruct(map[string]any{"prompt": "hello"}) if err != nil { t.Fatal(err) } wire := &iop.RunRequest{ RunId: "run-1", Adapter: "ollama", Target: "model", SessionId: "correlation-1", Background: true, Policy: policy, Input: input, TimeoutSec: 30, Metadata: map[string]string{"source": "test"}, } got := runRequestFromProto(wire) if got.RunID != wire.RunId || got.Adapter != wire.Adapter || got.Target != wire.Target || got.SessionID != wire.SessionId || got.Background != wire.Background || got.TimeoutSec != int(wire.TimeoutSec) { t.Fatalf("runRequestFromProto() = %#v", got) } if !reflect.DeepEqual(got.Policy, policy.AsMap()) || !reflect.DeepEqual(got.Input, input.AsMap()) || !reflect.DeepEqual(got.Metadata, wire.Metadata) { t.Fatalf("mapped maps = policy %#v input %#v metadata %#v", got.Policy, got.Input, got.Metadata) } } func TestRunEventToProtoPreservesLegacyValues(t *testing.T) { timestamp := time.Unix(0, 1234) event := runtime.RuntimeEvent{ RunID: "run-1", Type: runtime.EventTypeError, Error: "legacy error", Failure: &runtime.Failure{Code: runtime.FailureCodeProvider, Message: "typed error"}, Metadata: map[string]string{"key": "value"}, Usage: &runtime.UsageStats{ InputTokens: 1, OutputTokens: 2, ReasoningTokens: 3, CachedInputTokens: 4, }, Timestamp: timestamp, } got := runEventToProto(event, "node-1", "session-1", true) if got.GetRunId() != "run-1" || got.GetType() != "error" || got.GetError() != "legacy error" || got.GetNodeId() != "node-1" || got.GetSessionId() != "session-1" || !got.GetBackground() || got.GetTimestamp() != timestamp.UnixNano() { t.Fatalf("runEventToProto() = %#v", got) } if got.GetUsage().GetInputTokens() != 1 || got.GetUsage().GetOutputTokens() != 2 || got.GetUsage().GetReasoningTokens() != 3 || got.GetUsage().GetCachedInputTokens() != 4 { t.Fatalf("usage = %#v", got.GetUsage()) } if !reflect.DeepEqual(got.GetMetadata(), event.Metadata) { t.Fatalf("metadata = %#v, want %#v", got.GetMetadata(), event.Metadata) } } func TestRunEventToProtoUsesTypedFailureMessageAsFallback(t *testing.T) { got := runEventToProto(runtime.RuntimeEvent{ RunID: "run-1", Type: runtime.EventTypeError, Failure: &runtime.Failure{Code: runtime.FailureCodeUnavailable, Message: "unavailable"}, Timestamp: time.Unix(0, 1), }, "node-1", "default", false) if got.GetError() != "unavailable" { t.Fatalf("error = %q, want unavailable", got.GetError()) } } func TestRunRequestFromProtoLeavesRawStallTimeoutForHandlerValidation(t *testing.T) { // The handler, not the protobuf mapper, validates and resolves the raw value. req := &iop.RunRequest{ RunId: "r1", Adapter: "ollama", } runtimeReq := runRequestFromProto(req) if runtimeReq.ResponseStallTimeoutMS != 0 { t.Errorf("zero wire must remain raw before handler validation, got %d", runtimeReq.ResponseStallTimeoutMS) } // Positive override → passes through. req2 := &iop.RunRequest{ RunId: "r2", Adapter: "ollama", ResponseStallTimeoutMs: 60000, } runtimeReq2 := runRequestFromProto(req2) if runtimeReq2.ResponseStallTimeoutMS != 0 { t.Errorf("positive wire value must remain raw before handler validation, got %d", runtimeReq2.ResponseStallTimeoutMS) } } func TestValidateStallTimeoutOnWire(t *testing.T) { cases := []struct { name string raw int64 want int64 bad bool }{ {name: "zero defaults", raw: 0, want: runtime.DefaultResponseStallTimeoutMS}, {name: "positive preserved", raw: 60000, want: 60000}, {name: "negative rejected", raw: -1, bad: true}, {name: "overflow rejected", raw: 99999999999999, bad: true}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { got, err := ValidateStallTimeoutOnWire(tc.raw) if (err != nil) != tc.bad { t.Fatalf("ValidateStallTimeoutOnWire(%d) error = %v, want bad=%t", tc.raw, err, tc.bad) } if !tc.bad && got != tc.want { t.Errorf("ValidateStallTimeoutOnWire(%d) = %d, want %d", tc.raw, got, tc.want) } }) } } func TestResponseStallTimeoutWireRoundTrip(t *testing.T) { values := []int64{0, 60000, -1, (int64(1) << 62) / int64(time.Millisecond)} for _, value := range values { t.Run("run", func(t *testing.T) { original := &iop.RunRequest{ResponseStallTimeoutMs: value} encoded, err := proto.Marshal(original) if err != nil { t.Fatal(err) } decoded := &iop.RunRequest{} if err := proto.Unmarshal(encoded, decoded); err != nil { t.Fatal(err) } if decoded.GetResponseStallTimeoutMs() != value { t.Fatalf("RunRequest round trip = %d, want %d", decoded.GetResponseStallTimeoutMs(), value) } }) t.Run("tunnel", func(t *testing.T) { original := &iop.ProviderTunnelRequest{ResponseStallTimeoutMs: value} encoded, err := proto.Marshal(original) if err != nil { t.Fatal(err) } decoded := &iop.ProviderTunnelRequest{} if err := proto.Unmarshal(encoded, decoded); err != nil { t.Fatal(err) } if decoded.GetResponseStallTimeoutMs() != value { t.Fatalf("ProviderTunnelRequest round trip = %d, want %d", decoded.GetResponseStallTimeoutMs(), value) } }) } } func TestRuntimeEventToProtoPreservesTypedFailure(t *testing.T) { t.Run("stalled failure populated with allowlisted metadata", func(t *testing.T) { inputMeta := map[string]string{ "failure_code": "response_stalled", "provider_health": "available", "liveness_classification": "request_stalled", "idle_duration_ms": "5000", "run_id": "run-1", "attempt_id": "run-1", "attempt_fence": "confirmed", "adapter": "ollama", "target": "llama3", "health_observation_seq": "1", "recovery_eligible": "true", "secret_key": "sensitive", } event := runtime.RuntimeEvent{ RunID: eventTypeStalledRunID(), Type: runtime.EventTypeError, Error: "provider response stalled", Failure: &runtime.Failure{ Code: runtime.FailureCodeResponseStalled, Message: "provider response stalled", Retryable: true, Metadata: inputMeta, }, } wire := runEventToProto(event, "node-1", "session-1", false) if wire.GetFailure() == nil { t.Fatal("expected non-nil wire.Failure") } if wire.GetFailure().GetCode() != "response_stalled" { t.Fatalf("code = %q, want response_stalled", wire.GetFailure().GetCode()) } if wire.GetFailure().GetMessage() != "provider response stalled" { t.Fatalf("message = %q", wire.GetFailure().GetMessage()) } if !wire.GetFailure().GetRetryable() { t.Fatal("expected retryable = true") } meta := wire.GetFailure().GetMetadata() if meta["provider_health"] != "available" || meta["liveness_classification"] != "request_stalled" || meta["health_observation_seq"] != "1" { t.Fatalf("allowlisted metadata missing or invalid = %#v", meta) } if meta["recovery_eligible"] != "" || meta["secret_key"] != "" { t.Fatalf("non-allowlisted metadata present in wire failure: %#v", meta) } // Verify defensive cloning: mutating input map must not alter wire failure metadata inputMeta["attempt_fence"] = "mutated" if meta["attempt_fence"] != "confirmed" { t.Fatal("wire failure metadata shared mutable alias with input metadata") } }) t.Run("non-stalled failure leaves wire failure nil", func(t *testing.T) { event := runtime.RuntimeEvent{ RunID: "run-2", Type: runtime.EventTypeError, Error: "cancelled error", Failure: &runtime.Failure{Code: runtime.FailureCodeCancelled, Message: "cancelled error"}, } wire := runEventToProto(event, "node-1", "session-1", false) if wire.GetFailure() != nil { t.Fatalf("expected nil wire.Failure for non-stalled code, got %#v", wire.GetFailure()) } if wire.GetError() != "cancelled error" { t.Fatalf("error string = %q, want cancelled error", wire.GetError()) } }) t.Run("nil failure leaves wire failure nil", func(t *testing.T) { event := runtime.RuntimeEvent{ RunID: "run-3", Type: runtime.EventTypeComplete, } wire := runEventToProto(event, "node-1", "session-1", false) if wire.GetFailure() != nil { t.Fatalf("expected nil wire.Failure for nil failure, got %#v", wire.GetFailure()) } }) } func eventTypeStalledRunID() string { return "run-1" }