protocol_test.go 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. package faultRecordProtocol
  2. import (
  3. "encoding/csv"
  4. "encoding/json"
  5. "os"
  6. "strings"
  7. "testing"
  8. )
  9. const sampleLog = "../../../docs/fault-record/CloudLog_2026-10-14_104644.csv"
  10. // loadSampleMessages 读取 2026-10-14 样本日志,按 cmdType 分组返回原始报文
  11. func loadSampleMessages(t *testing.T) map[uint][][]byte {
  12. t.Helper()
  13. f, err := os.Open(sampleLog)
  14. if err != nil {
  15. t.Skipf("sample log not found: %v", err)
  16. }
  17. defer f.Close()
  18. rows, err := csv.NewReader(f).ReadAll()
  19. if err != nil {
  20. t.Fatal(err)
  21. }
  22. msgs := map[uint][][]byte{}
  23. for _, row := range rows[1:] {
  24. raw := []byte(row[7]) // logData
  25. cmdType, _, _, err := SplitEnvelope(raw)
  26. if err != nil {
  27. t.Fatalf("split envelope: %v", err)
  28. }
  29. msgs[cmdType] = append(msgs[cmdType], raw)
  30. }
  31. return msgs
  32. }
  33. func TestSampleMessages(t *testing.T) {
  34. msgs := loadSampleMessages(t)
  35. for _, raw := range msgs[CmdTypeAck] {
  36. _, _, da, _ := SplitEnvelope(raw)
  37. ack, err := ParseAck(da)
  38. if err != nil || !ack.Ok() {
  39. t.Errorf("378 parse: %+v %v", ack, err)
  40. }
  41. }
  42. var gotDir, gotRecord bool
  43. for _, raw := range msgs[CmdTypeReply] {
  44. _, equipNo, da, _ := SplitEnvelope(raw)
  45. r, err := ParseReply(da)
  46. if err != nil || !r.Ok() {
  47. t.Fatalf("379 parse: %+v %v", r, err)
  48. }
  49. info, ok := r.FindInfo(DeviceTypeAGate, equipNo)
  50. if !ok {
  51. t.Fatal("379 info not found")
  52. }
  53. switch r.Cmd {
  54. case CmdRealtime:
  55. gotDir = len(info.HexList) == 1 && info.HexList[0].Code == 3 && len(info.HexList[0].HexData) == 422
  56. case CmdGetRecord:
  57. gotRecord = r.Filename != "" && len(info.IndexInfo) == 2 && info.IndexInfo[0].Index == 4 && info.IndexInfo[1].Index == 5
  58. }
  59. }
  60. if !gotDir || !gotRecord {
  61. t.Errorf("379 content: dir=%v record=%v", gotDir, gotRecord)
  62. }
  63. _, _, da, _ := SplitEnvelope(msgs[CmdTypeUploadCall][0])
  64. call, err := ParseUploadCall(da)
  65. if err != nil || call.FaultRecordID == 0 || call.FileName == "" {
  66. t.Errorf("607 parse: %+v %v", call, err)
  67. }
  68. // 旧版本 608 的第二个 result 实为 id,应等于同批次 607.faultRecordID
  69. ids := map[string]int64{}
  70. for _, raw := range msgs[CmdTypeUploadCall] {
  71. _, _, da, _ := SplitEnvelope(raw)
  72. c, _ := ParseUploadCall(da)
  73. ids[c.FileName] = c.FaultRecordID
  74. }
  75. for _, raw := range msgs[CmdTypeUploadAck] {
  76. _, _, da, _ := SplitEnvelope(raw)
  77. ack, err := ParseUploadAck(da)
  78. if err != nil || ack.Result != 0 || ack.Filename == "" || ack.Id == 0 || ack.Id != ids[ack.Filename] {
  79. t.Errorf("608 parse: %+v %v", ack, err)
  80. }
  81. }
  82. for _, raw := range msgs[CmdTypeUploadDone] {
  83. _, _, da, _ := SplitEnvelope(raw)
  84. done, err := ParseUploadDone(da)
  85. if err != nil || !done.Ok() || BatchFromUploadFileName(done.FileName) == done.FileName {
  86. t.Errorf("609 parse: %+v %v", done, err)
  87. }
  88. }
  89. }
  90. func TestBuildRequests(t *testing.T) {
  91. req, err := NewGetRecordRequest("agate", "AFCI_TEST_0713", []int{7})
  92. if err != nil {
  93. t.Fatal(err)
  94. }
  95. b, _ := json.Marshal(req)
  96. want := `{"cmd":2,"data":{"list":[{"type":"aGate","info":[{"sn":"AFCI_TEST_0713","index":[7]}]}]}}`
  97. if string(b) != want {
  98. t.Errorf("cmd=2 request:\n got %s\nwant %s", b, want)
  99. }
  100. b, _ = json.Marshal(NewExitRequest())
  101. if string(b) != `{"cmd":4,"data":{}}` {
  102. t.Errorf("cmd=4 request: %s", b)
  103. }
  104. if _, err := NewRealtimeRequest("pe", "X", []int{4}); err == nil {
  105. t.Error("code=4 should be rejected for cmd=0")
  106. }
  107. if _, err := NewGetRecordRequest("mppt", "X", []int{1}); err == nil {
  108. t.Error("unknown device type should be rejected")
  109. }
  110. req, _ = NewGetRecordRequest("aGate", "X", []int{7, 9, 3})
  111. if b, _ = json.Marshal(req); !strings.Contains(string(b), `"index":[7,9,3]`) {
  112. t.Errorf("multi-record request: %s", b)
  113. }
  114. for _, bad := range [][]int{{}, {7, 7}, {0}, {256}} {
  115. if _, err := NewGetRecordRequest("aGate", "X", bad); err == nil {
  116. t.Errorf("indexes %v should be rejected", bad)
  117. }
  118. }
  119. }
  120. func TestSetVal(t *testing.T) {
  121. ok := []ParaItem{{1, ""}, {2, "021001100A"}, {4, "07EA0A100C0000"}, {5, "1194"}, {6, "010100015984"}, {7, "00"}}
  122. for _, p := range ok {
  123. if err := ValidateSetVal(p.Id, p.SetVal); err != nil {
  124. t.Errorf("id=%d %q: %v", p.Id, p.SetVal, err)
  125. }
  126. }
  127. bad := []ParaItem{{1, "00"}, {2, "0210"}, {5, "11"}, {6, "0101"}, {7, "02"}, {8, ""}, {5, "XYZ1"}}
  128. for _, p := range bad {
  129. if err := ValidateSetVal(p.Id, p.SetVal); err == nil {
  130. t.Errorf("id=%d %q should be invalid", p.Id, p.SetVal)
  131. }
  132. }
  133. if v := DecodeIdData(5, "1194").(map[string]any)["frequencyHz"]; v != uint16(4500) {
  134. t.Errorf("id=5 decode: %v", v)
  135. }
  136. c := DecodeIdData(6, "04030003FD34").(ConfigChannel)
  137. if c.Index != 4 || c.Mode != 0 || c.TypeName != "Float32" || c.Address != 0x3FD34 {
  138. t.Errorf("id=6 decode: %+v", c)
  139. }
  140. }
  141. func TestDescribeResultOne(t *testing.T) {
  142. cases := map[string]string{
  143. "ok": "成功",
  144. "cancomerr:suberrcode=31": "子设备错误码 0x31:requestOutOfRange 请求超过范围",
  145. "cancomerr:index=e": "设备内部通信错误 0xE:记录传输阶段:接收超时",
  146. "something new": "something new",
  147. "cancomerr:suberrcode=zz": "子设备错误码 zz",
  148. "emserr:can p null": "未取到目标子设备",
  149. }
  150. for in, want := range cases {
  151. if got := DescribeResultOne(in); got != want {
  152. t.Errorf("DescribeResultOne(%q) = %q, want %q", in, got, want)
  153. }
  154. }
  155. }
  156. func TestParseUploadAckVersions(t *testing.T) {
  157. cases := []struct {
  158. name string
  159. in string
  160. want UploadAck
  161. }{
  162. {"旧版本:第二个 result 为 id", `{"result":0,"filename":"B","result":5226}`, UploadAck{Result: 0, Id: 5226, Filename: "B"}},
  163. {"旧版本:带空白", `{ "result" : 1 , "filename":"B", "result" : 7 }`, UploadAck{Result: 1, Id: 7, Filename: "B"}},
  164. {"修复后版本", `{"result":0,"filename":"B","id":5226}`, UploadAck{Result: 0, Id: 5226, Filename: "B"}},
  165. {"值中含 result 字样不受影响", `{"result":0,"filename":"result","result":9}`, UploadAck{Result: 0, Id: 9, Filename: "result"}},
  166. {"只有一个 result", `{"result":2,"filename":"B"}`, UploadAck{Result: 2, Filename: "B"}},
  167. }
  168. for _, c := range cases {
  169. got, err := ParseUploadAck([]byte(c.in))
  170. if err != nil || got != c.want {
  171. t.Errorf("%s: got %+v %v, want %+v", c.name, got, err, c.want)
  172. }
  173. }
  174. if _, err := ParseUploadAck([]byte(`[1]`)); err == nil {
  175. t.Error("non-object should fail")
  176. }
  177. }