| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198 |
- package faultRecordProtocol
- import (
- "encoding/csv"
- "encoding/json"
- "os"
- "strings"
- "testing"
- )
- const sampleLog = "../../../docs/fault-record/CloudLog_2026-10-14_104644.csv"
- // loadSampleMessages 读取 2026-10-14 样本日志,按 cmdType 分组返回原始报文
- func loadSampleMessages(t *testing.T) map[uint][][]byte {
- t.Helper()
- f, err := os.Open(sampleLog)
- if err != nil {
- t.Skipf("sample log not found: %v", err)
- }
- defer f.Close()
- rows, err := csv.NewReader(f).ReadAll()
- if err != nil {
- t.Fatal(err)
- }
- msgs := map[uint][][]byte{}
- for _, row := range rows[1:] {
- raw := []byte(row[7]) // logData
- cmdType, _, _, err := SplitEnvelope(raw)
- if err != nil {
- t.Fatalf("split envelope: %v", err)
- }
- msgs[cmdType] = append(msgs[cmdType], raw)
- }
- return msgs
- }
- func TestSampleMessages(t *testing.T) {
- msgs := loadSampleMessages(t)
- for _, raw := range msgs[CmdTypeAck] {
- _, _, da, _ := SplitEnvelope(raw)
- ack, err := ParseAck(da)
- if err != nil || !ack.Ok() {
- t.Errorf("378 parse: %+v %v", ack, err)
- }
- }
- var gotDir, gotRecord bool
- for _, raw := range msgs[CmdTypeReply] {
- _, equipNo, da, _ := SplitEnvelope(raw)
- r, err := ParseReply(da)
- if err != nil || !r.Ok() {
- t.Fatalf("379 parse: %+v %v", r, err)
- }
- info, ok := r.FindInfo(DeviceTypeAGate, equipNo)
- if !ok {
- t.Fatal("379 info not found")
- }
- switch r.Cmd {
- case CmdRealtime:
- gotDir = len(info.HexList) == 1 && info.HexList[0].Code == 3 && len(info.HexList[0].HexData) == 422
- case CmdGetRecord:
- gotRecord = r.Filename != "" && len(info.IndexInfo) == 2 && info.IndexInfo[0].Index == 4 && info.IndexInfo[1].Index == 5
- }
- }
- if !gotDir || !gotRecord {
- t.Errorf("379 content: dir=%v record=%v", gotDir, gotRecord)
- }
- _, _, da, _ := SplitEnvelope(msgs[CmdTypeUploadCall][0])
- call, err := ParseUploadCall(da)
- if err != nil || call.FaultRecordID == 0 || call.FileName == "" {
- t.Errorf("607 parse: %+v %v", call, err)
- }
- // 旧版本 608 的第二个 result 实为 id,应等于同批次 607.faultRecordID
- ids := map[string]int64{}
- for _, raw := range msgs[CmdTypeUploadCall] {
- _, _, da, _ := SplitEnvelope(raw)
- c, _ := ParseUploadCall(da)
- ids[c.FileName] = c.FaultRecordID
- }
- for _, raw := range msgs[CmdTypeUploadAck] {
- _, _, da, _ := SplitEnvelope(raw)
- ack, err := ParseUploadAck(da)
- if err != nil || ack.Result != 0 || ack.Filename == "" || ack.Id == 0 || ack.Id != ids[ack.Filename] {
- t.Errorf("608 parse: %+v %v", ack, err)
- }
- }
- for _, raw := range msgs[CmdTypeUploadDone] {
- _, _, da, _ := SplitEnvelope(raw)
- done, err := ParseUploadDone(da)
- if err != nil || !done.Ok() || BatchFromUploadFileName(done.FileName) == done.FileName {
- t.Errorf("609 parse: %+v %v", done, err)
- }
- }
- }
- func TestBuildRequests(t *testing.T) {
- req, err := NewGetRecordRequest("agate", "AFCI_TEST_0713", []int{7})
- if err != nil {
- t.Fatal(err)
- }
- b, _ := json.Marshal(req)
- want := `{"cmd":2,"data":{"list":[{"type":"aGate","info":[{"sn":"AFCI_TEST_0713","index":[7]}]}]}}`
- if string(b) != want {
- t.Errorf("cmd=2 request:\n got %s\nwant %s", b, want)
- }
- b, _ = json.Marshal(NewExitRequest())
- if string(b) != `{"cmd":4,"data":{}}` {
- t.Errorf("cmd=4 request: %s", b)
- }
- if _, err := NewRealtimeRequest("pe", "X", []int{4}); err == nil {
- t.Error("code=4 should be rejected for cmd=0")
- }
- if _, err := NewGetRecordRequest("mpt", "X", []int{1}); err == nil {
- t.Error("unknown device type should be rejected")
- }
- for in, want := range map[string]string{"pe": "pe", "AGATE": "aGate", "MPPT": "mppt", "apbox": "apbox", "dif": "dif"} {
- if got, err := NormalizeDeviceType(in); err != nil || got != want {
- t.Errorf("NormalizeDeviceType(%q) = %q, %v", in, got, err)
- }
- }
- req, _ = NewGetRecordRequest("aGate", "X", []int{7, 9, 3})
- if b, _ = json.Marshal(req); !strings.Contains(string(b), `"index":[7,9,3]`) {
- t.Errorf("multi-record request: %s", b)
- }
- for _, bad := range [][]int{{}, {7, 7}, {0}, {256}} {
- if _, err := NewGetRecordRequest("aGate", "X", bad); err == nil {
- t.Errorf("indexes %v should be rejected", bad)
- }
- }
- }
- func TestSetVal(t *testing.T) {
- ok := []ParaItem{{1, ""}, {2, "021001100A"}, {4, "07EA0A100C0000"}, {5, "1194"}, {6, "010100015984"}, {7, "00"}}
- for _, p := range ok {
- if err := ValidateSetVal(p.Id, p.SetVal); err != nil {
- t.Errorf("id=%d %q: %v", p.Id, p.SetVal, err)
- }
- }
- bad := []ParaItem{{1, "00"}, {2, "0210"}, {5, "11"}, {6, "0101"}, {7, "02"}, {8, ""}, {5, "XYZ1"}}
- for _, p := range bad {
- if err := ValidateSetVal(p.Id, p.SetVal); err == nil {
- t.Errorf("id=%d %q should be invalid", p.Id, p.SetVal)
- }
- }
- if v := DecodeIdData(5, "1194").(map[string]any)["frequencyHz"]; v != uint16(4500) {
- t.Errorf("id=5 decode: %v", v)
- }
- c := DecodeIdData(6, "04030003FD34").(ConfigChannel)
- if c.Index != 4 || c.Mode != 0 || c.TypeName != "Float32" || c.Address != 0x3FD34 {
- t.Errorf("id=6 decode: %+v", c)
- }
- }
- func TestDescribeResultOne(t *testing.T) {
- cases := map[string]string{
- "ok": "成功",
- "cancomerr:suberrcode=31": "子设备错误码 0x31:requestOutOfRange 请求超过范围",
- "cancomerr:index=e": "设备内部通信错误 0xE:记录传输阶段:接收超时",
- "something new": "something new",
- "cancomerr:suberrcode=zz": "子设备错误码 zz",
- "emserr:can p null": "未取到目标子设备",
- }
- for in, want := range cases {
- if got := DescribeResultOne(in); got != want {
- t.Errorf("DescribeResultOne(%q) = %q, want %q", in, got, want)
- }
- }
- }
- func TestParseUploadAckVersions(t *testing.T) {
- cases := []struct {
- name string
- in string
- want UploadAck
- }{
- {"旧版本:第二个 result 为 id", `{"result":0,"filename":"B","result":5226}`, UploadAck{Result: 0, Id: 5226, Filename: "B"}},
- {"旧版本:带空白", `{ "result" : 1 , "filename":"B", "result" : 7 }`, UploadAck{Result: 1, Id: 7, Filename: "B"}},
- {"修复后版本", `{"result":0,"filename":"B","id":5226}`, UploadAck{Result: 0, Id: 5226, Filename: "B"}},
- {"值中含 result 字样不受影响", `{"result":0,"filename":"result","result":9}`, UploadAck{Result: 0, Id: 9, Filename: "result"}},
- {"只有一个 result", `{"result":2,"filename":"B"}`, UploadAck{Result: 2, Filename: "B"}},
- }
- for _, c := range cases {
- got, err := ParseUploadAck([]byte(c.in))
- if err != nil || got != c.want {
- t.Errorf("%s: got %+v %v, want %+v", c.name, got, err, c.want)
- }
- }
- if _, err := ParseUploadAck([]byte(`[1]`)); err == nil {
- t.Error("non-object should fail")
- }
- }
|