142 lines
4.9 KiB
Go
142 lines
4.9 KiB
Go
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)
|
||
}
|
||
}
|