package httpserver import ( "encoding/json" "os" "strings" "testing" "git.yuntuo.hk/k2safe/orbit/internal/testcontract" "git.yuntuo.hk/k2safe/orbit/platform/events" ) func TestEventFrameContract(t *testing.T) { id := strings.Repeat("1", 32) secret, _, err := events.NewSecret() if err != nil { t.Fatal(err) } valid := []string{ `{"protocol_version":"1","type":"events.hello","message_id":"` + id + `","body":{"subscription_id":"` + id + `","capacity":1}}`, `{"protocol_version":"1","type":"events.ping","message_id":"` + id + `","session_id":"` + id + `","body":{}}`, `{"protocol_version":"1","type":"event.ack","message_id":"` + id + `","session_id":"` + id + `","body":{"delivery_id":"` + id + `","epoch":"1","lease_secret":"` + secret + `"}}`, } for i, raw := range valid { testcontract.EventFrame(t, "client", []byte(raw)) f, err := decodeEventRequest([]byte(raw), i == 0) if err != nil { t.Fatal(err) } switch i { case 0: var v eventHello err = decodeEventJSON(f.Body, &v) case 1: var v struct{} err = decodeEventJSON(f.Body, &v) case 2: var v events.AckInput err = decodeEventJSON(f.Body, &v) } if err != nil { t.Fatal(err) } } for _, number := range []string{"0", "01", "18446744073709551616", "18446744073709551615"} { raw := strings.Replace(valid[2], `"epoch":"1"`, `"epoch":"`+number+`"`, 1) var frame eventFrame _ = json.Unmarshal([]byte(raw), &frame) var input events.AckInput decodeErr := decodeEventJSON(frame.Body, &input) var value any _ = json.Unmarshal([]byte(raw), &value) schemaErr := testcontract.EventSchema(t, "client").Validate(value) wantValid := number == "18446744073709551615" if (decodeErr == nil) != wantValid || (schemaErr == nil) != wantValid { t.Fatal("event counter range disagrees with contract") } } bad := []string{ strings.Replace(valid[0], `"body":`, `"session_id":null,"body":`, 1), strings.Replace(valid[1], `"body":`, `"reply_to":"","body":`, 1), strings.Replace(valid[1], `"body":`, `"error":null,"body":`, 1), strings.Replace(valid[1], `"type":"events.ping"`, `"Type":"events.ping"`, 1), strings.Replace(valid[1], `"protocol_version":"1"`, `"protocol_version":"1","protocol_version":"1"`, 1), strings.Replace(valid[1], `"events.ping"`, `"worker.capacity"`, 1), strings.Replace(valid[1], `"body":{}`, `"body":null`, 1), } for _, raw := range bad { if _, err := decodeEventRequest([]byte(raw), strings.Contains(raw, "events.hello")); err == nil { t.Fatal("accepted invalid event envelope") } } // 标准Schema无法识别重复JSON键;实际解码器负责拒绝,其他方向约束由两者共同验证。 for _, raw := range bad[:4] { var value any if err := json.Unmarshal([]byte(raw), &value); err != nil { t.Fatal(err) } if testcontract.EventSchema(t, "client").Validate(value) == nil { t.Fatal("schema accepted direction or field violation") } } } func TestEventHTTPRequiredFieldsAndCounterCanonicalization(t *testing.T) { id := strings.Repeat("1", 32) for _, number := range []string{`"00"`, `"+1"`, `"01"`, `1`, `true`, `"18446744073709551616"`} { var in events.CreateInput raw := `{"name":"results","queue_id":"` + id + `","from_sequence":` + number + `}` if decodeEventJSON([]byte(raw), &in) == nil { t.Fatal("accepted invalid sequence") } } for _, number := range []string{`"0"`, `null`, `"18446744073709551615"`} { var in events.CreateInput if err := decodeEventJSON([]byte(`{"name":"results","queue_id":"`+id+`","from_sequence":`+number+`}`), &in); err != nil { t.Fatal(err) } } for _, raw := range []string{`{"expected_revision":"1","reason":"pause"}`, `{"expected_revision":"01","reason":"pause","enabled":false}`, `{"expected_revision":"1","reason":"pause","enabled":null}`, `{"expected_revision":"1","reason":"pause","Enabled":false}`} { var in events.ControlInput if decodeEventJSON([]byte(raw), &in) == nil { t.Fatal("accepted ambiguous control intent") } } var good events.ControlInput if err := decodeEventJSON([]byte(`{"expected_revision":"1","reason":"pause","enabled":false}`), &good); err != nil { t.Fatal(err) } } func TestEventDocumentationFramesMatchPublishedSchema(t *testing.T) { raw, err := os.ReadFile("../../../docs/api/subscriptions.md") if err != nil { t.Fatal(err) } frames := 0 for _, block := range strings.Split(string(raw), "```json\n")[1:] { snippet := strings.SplitN(block, "\n```", 2)[0] var value map[string]json.RawMessage if json.Unmarshal([]byte(snippet), &value) != nil { t.Fatal("invalid documented JSON") } if value["protocol_version"] == nil { continue } var kind string if json.Unmarshal(value["type"], &kind) != nil { t.Fatal("documented frame lacks type") } side := "server" if kind == "events.hello" || kind == "events.ping" || kind == "event.ack" { side = "client" } testcontract.EventFrame(t, side, []byte(snippet)) frames++ } if frames != 6 { t.Fatalf("expected six complete documented frames, got %d", frames) } }