返回 DeepSeek-Reasonix
contract_test.go
根目录 / desktop / internal / hostrpc / contract_test.go
1 package hostrpc
2
3 import (
4 "bytes"
5 "encoding/json"
6 "strings"
7 "testing"
8 )
9
10 type contractSmall struct{}
11
12 func (*contractSmall) One() string { return "" }
13
14 type contractLarge struct{}
15
16 func (*contractLarge) One() string { return "" }
17 func (*contractLarge) Two() int { return 0 }
18
19 func TestContractDigestIsStableAndSensitiveToCommands(t *testing.T) {
20 small := Build(mustRegistry(t, &contractSmall{}, nil), []string{"b:event", "a:event", "b:event"})
21 again := Build(mustRegistry(t, &contractSmall{}, nil), []string{"a:event", "b:event"})
22 large := Build(mustRegistry(t, &contractLarge{}, nil), []string{"a:event", "b:event"})
23 fewer := Build(mustRegistry(t, &contractSmall{}, nil), []string{"a:event"})
24
25 if small.Digest() != again.Digest() {
26 t.Fatalf("digest changed across identical builds: %s vs %s", small.Digest(), again.Digest())
27 }
28 if small.Digest() == large.Digest() {
29 t.Fatal("adding a command must change the digest")
30 }
31 if small.Digest() == fewer.Digest() {
32 t.Fatal("removing an event must change the digest")
33 }
34 if !strings.HasPrefix(small.Digest(), "sha256:") || len(small.Digest()) != len("sha256:")+64 {
35 t.Fatalf("digest format = %q", small.Digest())
36 }
37 if small.ProtocolVersion != ProtocolVersion {
38 t.Fatalf("protocol version = %d", small.ProtocolVersion)
39 }
40 if strings.Join(small.Events, ",") != "a:event,b:event" {
41 t.Fatalf("events = %v", small.Events)
42 }
43 }
44
45 func TestContractCanonicalJSONHasSortedKeysAndNoWhitespace(t *testing.T) {
46 c := Build(mustRegistry(t, &fixtureTarget{}, nil), []string{"z", "a"})
47 canonical := c.Canonical()
48 if bytes.ContainsAny(canonical, "\n\t") || bytes.Contains(canonical, []byte(`": `)) {
49 t.Fatal("canonical JSON must not contain layout whitespace")
50 }
51 if !json.Valid(canonical) {
52 t.Fatal("canonical JSON is invalid")
53 }
54 if !bytes.HasPrefix(canonical, []byte(`{"commands":[{"cancellation":"before-dispatch","name":"Explode","params":[]`)) {
55 t.Fatalf("keys not sorted: %.80s", canonical)
56 }
57 var buf bytes.Buffer
58 if err := WriteJSON(&buf, c); err != nil {
59 t.Fatal(err)
60 }
61 var indented, compact any
62 if err := json.Unmarshal(buf.Bytes(), &indented); err != nil {
63 t.Fatal(err)
64 }
65 if err := json.Unmarshal(canonical, &compact); err != nil {
66 t.Fatal(err)
67 }
68 if !bytes.HasSuffix(buf.Bytes(), []byte("}\n")) {
69 t.Fatal("WriteJSON must end with a newline")
70 }
71 compactAgain, _ := json.Marshal(indented)
72 compactRef, _ := json.Marshal(compact)
73 if !bytes.Equal(compactAgain, compactRef) {
74 t.Fatal("WriteJSON content differs from Canonical")
75 }
76 }
77
78 func TestContractOmitsNilCollectionsAsEmpty(t *testing.T) {
79 type bare struct{}
80 c := Build(mustRegistry(t, &bare{}, nil), nil)
81 canonical := string(c.Canonical())
82 for _, want := range []string{`"commands":[]`, `"events":[]`, `"types":{}`} {
83 if !strings.Contains(canonical, want) {
84 t.Errorf("canonical %s lacks %s", canonical, want)
85 }
86 }
87 }
88
88 lines GO