package httpserver import ( "context" "encoding/json" "strings" "testing" "time" "git.yuntuo.hk/k2safe/orbit/internal/fault" "git.yuntuo.hk/k2safe/orbit/internal/jsonwire" "git.yuntuo.hk/k2safe/orbit/internal/testcontract" core "git.yuntuo.hk/k2safe/orbit/model" "git.yuntuo.hk/k2safe/orbit/platform/auth" pulse "git.yuntuo.hk/k2safe/orbit/pulse/model" "git.yuntuo.hk/k2safe/orbit/pulse/service" "git.yuntuo.hk/k2safe/orbit/pulse/worker" "github.com/santhosh-tekuri/jsonschema/v6" ) // 此存储只接收已通过应用校验的命令,测试不伪造执行成功或当前租约。 type contractStore struct{ worker.Store } func (contractStore) Connect(context.Context, auth.Token, worker.ConnectInput) (worker.Binding, worker.Welcome, error) { return worker.Binding{}, worker.Welcome{}, nil } func (contractStore) Capacity(context.Context, auth.Token, worker.Binding, worker.CapacityInput) (worker.Welcome, error) { return worker.Welcome{}, nil } func (contractStore) Resume(context.Context, auth.Token, worker.Binding, worker.AttemptRef) (worker.LeaseStatus, error) { return worker.LeaseStatus{}, nil } func (contractStore) Start(context.Context, auth.Token, worker.Binding, core.ID, worker.AttemptRef) (worker.CommandResult, error) { return worker.CommandResult{}, nil } func (contractStore) Renew(context.Context, auth.Token, worker.Binding, core.ID, worker.AttemptRef) (worker.CommandResult, error) { return worker.CommandResult{}, nil } func (contractStore) Finish(context.Context, auth.Token, worker.Binding, core.ID, worker.FinishInput) (worker.CommandResult, error) { return worker.CommandResult{}, nil } func validateClientContract(raw []byte) error { var kind struct { Type string `json:"type"` } if err := json.Unmarshal(raw, &kind); err != nil { return err } f, err := decodeWorkerRequest(raw, kind.Type == "worker.hello") if err != nil { return err } svc := worker.New(contractStore{}) ctx := context.Background() token, binding := auth.Token{}, worker.Binding{} switch f.Type { case "worker.hello": var in worker.ConnectInput if err := decodeWorkerJSON(f.Body, &in); err != nil { return err } _, _, err = svc.Connect(ctx, token, in) case "worker.capacity": var in worker.CapacityInput if err := decodeWorkerJSON(f.Body, &in); err != nil { return err } _, err = svc.Capacity(ctx, token, binding, in) case "worker.resume", "task.start", "task.renew": var in worker.AttemptRef if err := decodeWorkerJSON(f.Body, &in); err != nil { return err } switch f.Type { case "worker.resume": _, err = svc.Resume(ctx, token, binding, in) case "task.start": _, err = svc.Start(ctx, token, binding, f.MessageID, in) case "task.renew": _, err = svc.Renew(ctx, token, binding, f.MessageID, in) } case "task.finish": var in worker.FinishInput if err := decodeWorkerJSON(f.Body, &in); err != nil { return err } _, err = svc.Finish(ctx, token, binding, f.MessageID, in) default: return fault.Invalid("unsupported worker message type") } return err } func TestWorkerPublishedContract(t *testing.T) { id := core.ID(strings.Repeat("1", 32)) secret := strings.Repeat("A", 43) ref := worker.AttemptRef{TaskID: id, AttemptID: id, Epoch: 1, LeaseSecret: secret} hello := worker.ConnectInput{InstanceID: id, InstanceSecret: secret, QueueID: id, Capacity: 1, Capabilities: []worker.Capability{{Handler: "text.normalize", HandlerVersion: "v1", PayloadVersion: "v1"}}} helloWithCancellation := hello helloWithCancellation.CancellationNotifications = true helloWithKeys := hello helloWithKeys.ConcurrencyKeys = true cases := []struct { kind string body any }{ {"worker.hello", hello}, {"worker.capacity", worker.CapacityInput{Sequence: 1, Capacity: 0, Drain: true}}, {"worker.resume", ref}, {"task.start", ref}, {"task.renew", ref}, {"task.finish", worker.FinishInput{Ref: ref, Outcome: "SUCCEEDED", Result: json.RawMessage(`{"n":9007199254740993}`)}}, {"task.finish", worker.FinishInput{Ref: ref, Outcome: "FAILED", ErrorCode: "OBJECT_REJECTED", Result: json.RawMessage(`{}`)}}, {"task.finish", worker.FinishInput{Ref: ref, Outcome: "UNKNOWN", ErrorCode: "LEASE_LOST", Result: json.RawMessage(`{}`)}}, {"task.finish", worker.FinishInput{Ref: ref, Outcome: "FAILED", Retryable: true, ErrorCode: "TEMPORARY_REJECTION", Result: json.RawMessage(`{}`)}}, {"task.finish", worker.FinishInput{Ref: ref, Outcome: "CANCELLED", ErrorCode: "CANCEL_CONFIRMED", Result: json.RawMessage(`{}`)}}, {"worker.hello", helloWithCancellation}, {"worker.hello", helloWithKeys}, } var valid [][]byte for _, tc := range cases { body, _ := jsonwire.Marshal(tc.body) f := workerFrame{Version: "1", Type: tc.kind, MessageID: id, SessionID: id, Body: body} if tc.kind == "worker.hello" { f.SessionID = "" } raw, err := jsonwire.Marshal(f) if err != nil { t.Fatal(err) } testcontract.WorkerFrame(t, "client", raw) if err := validateClientContract(raw); err != nil { t.Fatalf("valid %s rejected: %v", tc.kind, err) } valid = append(valid, raw) } // 修改报文而非重新构造 Go DTO,覆盖不同语言接入时的字段别名、缺失和类型边界。 invalid := []struct { name string raw string }{ {"hello_has_session", strings.Replace(string(valid[0]), `"body":`, `"session_id":"", "body":`, 1)}, {"response_null", strings.Replace(string(valid[0]), `"body":`, `"error":null, "body":`, 1)}, {"response_reply", strings.Replace(string(valid[0]), `"body":`, `"reply_to":"", "body":`, 1)}, {"capability_alias", strings.Replace(string(valid[0]), `"handler":`, `"Handler":`, 1)}, {"capability_conflicting_alias", strings.Replace(string(valid[0]), `"handler":`, `"Handler":"wrong", "handler":`, 1)}, {"missing_drain", strings.Replace(string(valid[1]), `,"drain":true`, ``, 1)}, {"counter_numeric", strings.Replace(string(valid[1]), `"sequence":"1"`, `"sequence":1`, 1)}, {"counter_leading_zero", strings.Replace(string(valid[1]), `"sequence":"1"`, `"sequence":"01"`, 1)}, {"counter_overflow", strings.Replace(string(valid[1]), `"sequence":"1"`, `"sequence":"18446744073709551616"`, 1)}, {"counter_zero", strings.Replace(string(valid[1]), `"sequence":"1"`, `"sequence":"0"`, 1)}, {"capacity_limit", strings.Replace(string(valid[1]), `"capacity":0`, `"capacity":257`, 1)}, {"secret_noncanonical", strings.Replace(string(valid[2]), secret, strings.Repeat("A", 42)+"B", 1)}, {"null_result", strings.Replace(string(valid[5]), `{"n":9007199254740993}`, `null`, 1)}, {"failure_missing_code", strings.Replace(string(valid[6]), `,"error_code":"OBJECT_REJECTED"`, ``, 1)}, {"success_with_code", strings.Replace(string(valid[6]), `"FAILED"`, `"SUCCEEDED"`, 1)}, {"unknown_retry_flag", strings.Replace(string(valid[8]), `"FAILED"`, `"UNKNOWN"`, 1)}, {"cancelled_retry_flag", strings.Replace(string(valid[8]), `"FAILED"`, `"CANCELLED"`, 1)}, } for _, tc := range invalid { t.Run(tc.name, func(t *testing.T) { value, err := jsonschema.UnmarshalJSON(strings.NewReader(tc.raw)) if err != nil { t.Fatal("fixture must remain valid JSON") } if testcontract.WorkerSchema(t, "client").Validate(value) == nil { t.Fatal("Schema accepted invalid message") } if validateClientContract([]byte(tc.raw)) == nil { t.Fatal("runtime accepted invalid message") } }) } // 标准 Schema 无法表达原始字节层重复键和 Unicode 替换,运行时必须独立拒绝。 for _, raw := range []string{ strings.Replace(string(valid[1]), `"drain":true`, `"drain":true,"drain":true`, 1), strings.Replace(string(valid[5]), `{"n":9007199254740993}`, `{"text":"\ud800"}`, 1), strings.Replace(string(valid[5]), `{"n":9007199254740993}`, `{"n":1e999}`, 1), } { if validateClientContract([]byte(raw)) == nil { t.Fatal("runtime lost byte-level guard") } } maximum := strings.Replace(string(valid[1]), `"sequence":"1"`, `"sequence":"18446744073709551615"`, 1) testcontract.WorkerFrame(t, "client", []byte(maximum)) if err := validateClientContract([]byte(maximum)); err != nil { t.Fatal(err) } now := time.Date(2026, 9, 17, 8, 0, 0, 0, time.UTC) welcome := worker.Welcome{SessionID: id, Epoch: 1, ServerTime: now, ExpiresAt: now.Add(45 * time.Second), LeaseDurationMS: 30000} lease := worker.LeaseStatus{Active: true, State: pulse.AttemptRunning, LeaseUntil: now.Add(30 * time.Second), ServerTime: now, CancelRequestedAt: &now} receipt := worker.Receipt{Operation: "start", TaskState: pulse.TaskRunning, AttemptState: pulse.AttemptRunning, TaskRevision: 3, LeaseUntil: lease.LeaseUntil, Disposition: "APPLIED"} task := service.TaskView{ID: id, NamespaceID: id, QueueID: id, State: pulse.TaskLeased, Revision: 2, Handler: "text.normalize", HandlerVersion: "v1", PayloadVersion: "v1", Payload: json.RawMessage(`{}`), AvailableAt: now, DeadlineAt: now.Add(time.Hour), RecoverySafety: pulse.RecoveryReconcileOnly, Retry: pulse.RetryPolicy{MaxAttempts: 1, AttemptTimeoutMS: 30000}, AttemptCount: 1, CreatedAt: now, UpdatedAt: now} assignment := worker.Assignment{Task: task, AttemptID: id, Epoch: 1, LeaseSecret: secret, LeaseUntil: lease.LeaseUntil, ServerTime: now} keyed := assignment keyed.Task.ConcurrencyKey = "object/共享资源" keyed.Task.Priority = 9 keyed.ConcurrencyToken = ^uint64(0) - 1 for _, tc := range []struct { kind string body any }{ {"worker.welcome", welcome}, {"ack", welcome}, {"ack", lease}, {"ack", worker.CommandResult{Receipt: receipt, Lease: lease, Deduplicated: true}}, {"task.assign", assignment}, {"task.cancel", worker.Cancellation{TaskID: id, AttemptID: id, Epoch: 1, RequestedAt: now}}, {"task.assign", keyed}, } { raw, _ := jsonwire.Marshal(tc.body) f := workerFrame{Version: "1", Type: tc.kind, MessageID: id, SessionID: id, ReplyTo: id, Body: raw} if tc.kind == "task.assign" || tc.kind == "task.cancel" { f.ReplyTo = "" } frame, _ := jsonwire.Marshal(f) testcontract.WorkerFrame(t, "server", frame) } keyedBody, _ := jsonwire.Marshal(keyed) keyedFrame, _ := jsonwire.Marshal(workerFrame{Version: "1", Type: "task.assign", MessageID: id, SessionID: id, Body: keyedBody}) for _, raw := range []string{ strings.Replace(string(keyedFrame), `"priority":9`, `"priority":10`, 1), strings.Replace(string(keyedFrame), `"priority":9`, `"priority":-1`, 1), strings.Replace(string(keyedFrame), `"concurrency_token":"18446744073709551614",`, "", 1), strings.Replace(string(keyedFrame), `"concurrency_key":"object/共享资源",`, "", 1), strings.Replace(string(keyedFrame), `"concurrency_token":"18446744073709551614"`, `"concurrency_token":0`, 1), strings.Replace(string(keyedFrame), `"concurrency_token":"18446744073709551614"`, `"concurrency_token":"0"`, 1), } { value, err := jsonschema.UnmarshalJSON(strings.NewReader(raw)) if err != nil || testcontract.WorkerSchema(t, "server").Validate(value) == nil { t.Fatal("invalid concurrency key/token contract accepted") } } for _, f := range []workerFrame{ {Version: "1", Type: "error", MessageID: id, ReplyTo: id, Error: fault.Unauthorized}, {Version: "1", Type: "error", MessageID: id, SessionID: id, Error: worker.StaleSession}, {Version: "1", Type: "error", MessageID: id, SessionID: id, ReplyTo: id, Error: worker.LeaseLost}, } { raw, _ := jsonwire.Marshal(f) testcontract.WorkerFrame(t, "server", raw) } invalidResponse, _ := jsonwire.Marshal(workerFrame{Version: "1", Type: "task.assign", MessageID: id, SessionID: id, ReplyTo: id, Body: json.RawMessage(`{}`)}) value, _ := jsonschema.UnmarshalJSON(strings.NewReader(string(invalidResponse))) if testcontract.WorkerSchema(t, "server").Validate(value) == nil { t.Fatal("invalid server assignment accepted") } }